@abhinav2203/codeflow-canvas 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/abhinav2203-codeflow-canvas-0.1.0.tgz +0 -0
  2. package/dist/bin/cli.d.ts +3 -0
  3. package/dist/bin/cli.d.ts.map +1 -0
  4. package/dist/bin/cli.js +84 -0
  5. package/dist/bin/cli.js.map +1 -0
  6. package/dist/components/blueprint-workbench.d.ts +2 -0
  7. package/dist/components/blueprint-workbench.d.ts.map +1 -0
  8. package/dist/components/blueprint-workbench.js +144 -0
  9. package/dist/components/blueprint-workbench.js.map +1 -0
  10. package/dist/components/code-diff-editor.d.ts +12 -0
  11. package/dist/components/code-diff-editor.d.ts.map +1 -0
  12. package/dist/components/code-diff-editor.js +39 -0
  13. package/dist/components/code-diff-editor.js.map +1 -0
  14. package/dist/components/code-editor.d.ts +25 -0
  15. package/dist/components/code-editor.d.ts.map +1 -0
  16. package/dist/components/code-editor.js +264 -0
  17. package/dist/components/code-editor.js.map +1 -0
  18. package/dist/components/file-tabs.d.ts +5 -0
  19. package/dist/components/file-tabs.d.ts.map +1 -0
  20. package/dist/components/file-tabs.js +164 -0
  21. package/dist/components/file-tabs.js.map +1 -0
  22. package/dist/components/file-tree.d.ts +7 -0
  23. package/dist/components/file-tree.d.ts.map +1 -0
  24. package/dist/components/file-tree.js +176 -0
  25. package/dist/components/file-tree.js.map +1 -0
  26. package/dist/components/graph-canvas.d.ts +25 -0
  27. package/dist/components/graph-canvas.d.ts.map +1 -0
  28. package/dist/components/graph-canvas.js +224 -0
  29. package/dist/components/graph-canvas.js.map +1 -0
  30. package/dist/components/ide-layout.d.ts +10 -0
  31. package/dist/components/ide-layout.d.ts.map +1 -0
  32. package/dist/components/ide-layout.js +40 -0
  33. package/dist/components/ide-layout.js.map +1 -0
  34. package/dist/components/ide-workbench.d.ts +4 -0
  35. package/dist/components/ide-workbench.d.ts.map +1 -0
  36. package/dist/components/ide-workbench.js +6 -0
  37. package/dist/components/ide-workbench.js.map +1 -0
  38. package/dist/components/index.d.ts +13 -0
  39. package/dist/components/index.d.ts.map +1 -0
  40. package/dist/components/index.js +13 -0
  41. package/dist/components/index.js.map +1 -0
  42. package/dist/components/monaco-setup.d.ts +4 -0
  43. package/dist/components/monaco-setup.d.ts.map +1 -0
  44. package/dist/components/monaco-setup.js +34 -0
  45. package/dist/components/monaco-setup.js.map +1 -0
  46. package/dist/components/opencode-settings.d.ts +8 -0
  47. package/dist/components/opencode-settings.d.ts.map +1 -0
  48. package/dist/components/opencode-settings.js +33 -0
  49. package/dist/components/opencode-settings.js.map +1 -0
  50. package/dist/components/policy-workbench.d.ts +2 -0
  51. package/dist/components/policy-workbench.d.ts.map +1 -0
  52. package/dist/components/policy-workbench.js +102 -0
  53. package/dist/components/policy-workbench.js.map +1 -0
  54. package/dist/components/ts-language-service.d.ts +14 -0
  55. package/dist/components/ts-language-service.d.ts.map +1 -0
  56. package/dist/components/ts-language-service.js +123 -0
  57. package/dist/components/ts-language-service.js.map +1 -0
  58. package/dist/index.d.ts +23 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +22 -0
  61. package/dist/index.js.map +1 -0
  62. package/dist/lib/browser/storage.d.ts +16 -0
  63. package/dist/lib/browser/storage.d.ts.map +1 -0
  64. package/dist/lib/browser/storage.js +18 -0
  65. package/dist/lib/browser/storage.js.map +1 -0
  66. package/dist/lib/edit.d.ts +14 -0
  67. package/dist/lib/edit.d.ts.map +1 -0
  68. package/dist/lib/edit.js +57 -0
  69. package/dist/lib/edit.js.map +1 -0
  70. package/dist/lib/flow-view.d.ts +80 -0
  71. package/dist/lib/flow-view.d.ts.map +1 -0
  72. package/dist/lib/flow-view.js +850 -0
  73. package/dist/lib/flow-view.js.map +1 -0
  74. package/dist/lib/heatmap.d.ts +28 -0
  75. package/dist/lib/heatmap.d.ts.map +1 -0
  76. package/dist/lib/heatmap.js +61 -0
  77. package/dist/lib/heatmap.js.map +1 -0
  78. package/dist/lib/index.d.ts +9 -0
  79. package/dist/lib/index.d.ts.map +1 -0
  80. package/dist/lib/index.js +6 -0
  81. package/dist/lib/index.js.map +1 -0
  82. package/dist/lib/node-navigation.d.ts +36 -0
  83. package/dist/lib/node-navigation.d.ts.map +1 -0
  84. package/dist/lib/node-navigation.js +52 -0
  85. package/dist/lib/node-navigation.js.map +1 -0
  86. package/dist/lib/traces.d.ts +3 -0
  87. package/dist/lib/traces.d.ts.map +1 -0
  88. package/dist/lib/traces.js +64 -0
  89. package/dist/lib/traces.js.map +1 -0
  90. package/dist/lib/types.d.ts +57 -0
  91. package/dist/lib/types.d.ts.map +1 -0
  92. package/dist/lib/types.js +7 -0
  93. package/dist/lib/types.js.map +1 -0
  94. package/dist/store/blueprint-store.d.ts +35 -0
  95. package/dist/store/blueprint-store.d.ts.map +1 -0
  96. package/dist/store/blueprint-store.js +79 -0
  97. package/dist/store/blueprint-store.js.map +1 -0
  98. package/dist/store/index.d.ts +3 -0
  99. package/dist/store/index.d.ts.map +1 -0
  100. package/dist/store/index.js +2 -0
  101. package/dist/store/index.js.map +1 -0
  102. package/package.json +52 -0
  103. package/scripts/wrap-cli.mjs +15 -0
  104. package/src/bin/cli.ts +128 -0
  105. package/src/components/blueprint-workbench.tsx +305 -0
  106. package/src/components/code-diff-editor.tsx +80 -0
  107. package/src/components/code-editor.tsx +389 -0
  108. package/src/components/file-tabs.tsx +288 -0
  109. package/src/components/file-tree.tsx +301 -0
  110. package/src/components/graph-canvas.tsx +404 -0
  111. package/src/components/ide-layout.tsx +104 -0
  112. package/src/components/ide-workbench.tsx +5 -0
  113. package/src/components/index.ts +12 -0
  114. package/src/components/monaco-setup.ts +67 -0
  115. package/src/components/opencode-settings.tsx +82 -0
  116. package/src/components/policy-workbench.tsx +233 -0
  117. package/src/components/ts-language-service.ts +170 -0
  118. package/src/index.ts +54 -0
  119. package/src/lib/browser/storage.ts +19 -0
  120. package/src/lib/edit.ts +74 -0
  121. package/src/lib/flow-view.ts +1176 -0
  122. package/src/lib/heatmap.ts +103 -0
  123. package/src/lib/index.ts +41 -0
  124. package/src/lib/node-navigation.ts +76 -0
  125. package/src/lib/traces.ts +79 -0
  126. package/src/lib/types.ts +79 -0
  127. package/src/store/blueprint-store.ts +136 -0
  128. package/src/store/index.ts +2 -0
  129. package/test-fixtures/minimal-blueprint.json +34 -0
  130. package/test-fixtures/sample-blueprint.json +184 -0
  131. package/tsconfig.build.json +9 -0
  132. package/tsconfig.json +22 -0
  133. package/tsconfig.tsbuildinfo +1 -0
  134. package/vitest.config.ts +9 -0
@@ -0,0 +1,850 @@
1
+ import { heatColor, heatGlow } from "./heatmap.js";
2
+ import { emptyContract } from "@abhinav2203/codeflow-core/schema";
3
+ const kindOrder = {
4
+ "ui-screen": 0,
5
+ api: 1,
6
+ module: 2,
7
+ class: 3,
8
+ function: 4
9
+ };
10
+ const kindTheme = (kind, selected, traceStatus) => {
11
+ const palette = {
12
+ "ui-screen": {
13
+ border: "var(--node-ui-border)",
14
+ glow: "var(--node-ui-glow)",
15
+ accent: "var(--node-ui-bg)"
16
+ },
17
+ api: {
18
+ border: "var(--node-api-border)",
19
+ glow: "var(--node-api-glow)",
20
+ accent: "var(--node-api-bg)"
21
+ },
22
+ module: {
23
+ border: "var(--node-module-border)",
24
+ glow: "var(--node-module-glow)",
25
+ accent: "var(--node-module-bg)"
26
+ },
27
+ class: {
28
+ border: "var(--node-class-border)",
29
+ glow: "var(--node-class-glow)",
30
+ accent: "var(--node-class-bg)"
31
+ },
32
+ function: {
33
+ border: "var(--node-function-border)",
34
+ glow: "var(--node-function-glow)",
35
+ accent: "var(--node-function-bg)"
36
+ }
37
+ };
38
+ const theme = palette[kind];
39
+ const traceRing = traceStatus === "error"
40
+ ? "rgba(239, 68, 68, 0.28)"
41
+ : traceStatus === "warning"
42
+ ? "rgba(245, 158, 11, 0.24)"
43
+ : traceStatus === "success"
44
+ ? "rgba(34, 197, 94, 0.22)"
45
+ : theme.glow;
46
+ return {
47
+ width: 252,
48
+ borderRadius: 26,
49
+ border: selected ? `1.5px solid ${theme.border}` : "1px solid var(--node-border-default)",
50
+ background: `linear-gradient(180deg, var(--surface-raised) 0%, ${theme.accent} 100%)`,
51
+ padding: 14,
52
+ boxShadow: selected
53
+ ? `0 24px 56px ${traceRing}, inset 0 1px 0 var(--node-inner-shine)`
54
+ : `0 16px 38px ${theme.glow}, inset 0 1px 0 var(--node-inner-shine)`,
55
+ backdropFilter: "blur(14px)"
56
+ };
57
+ };
58
+ const detailKindColor = (kind) => {
59
+ switch (kind) {
60
+ case "root":
61
+ return "var(--node-module-bg)";
62
+ case "blueprint-node":
63
+ return "var(--node-class-bg)";
64
+ case "attribute":
65
+ return "rgba(250, 204, 21, 0.18)";
66
+ case "method":
67
+ return "rgba(52, 211, 153, 0.16)";
68
+ case "input":
69
+ return "rgba(129, 140, 248, 0.18)";
70
+ case "output":
71
+ return "rgba(251, 113, 133, 0.16)";
72
+ case "dependency":
73
+ return "rgba(251, 146, 60, 0.16)";
74
+ case "call":
75
+ return "rgba(56, 189, 248, 0.16)";
76
+ case "error":
77
+ return "rgba(248, 113, 113, 0.16)";
78
+ case "side-effect":
79
+ return "rgba(250, 204, 21, 0.12)";
80
+ case "note":
81
+ return "rgba(148, 163, 184, 0.16)";
82
+ default:
83
+ return "rgba(148, 163, 184, 0.16)";
84
+ }
85
+ };
86
+ const executionStatusRank = {
87
+ failed: 0,
88
+ blocked: 1,
89
+ running: 2,
90
+ pending: 3,
91
+ warning: 4,
92
+ passed: 5,
93
+ skipped: 6,
94
+ idle: 7
95
+ };
96
+ const executionStatusLabel = {
97
+ failed: "Failed",
98
+ blocked: "Blocked",
99
+ running: "Running",
100
+ pending: "Pending",
101
+ warning: "Warning",
102
+ passed: "Passed",
103
+ skipped: "Skipped",
104
+ idle: "Idle"
105
+ };
106
+ const executionStatusTone = {
107
+ failed: "rgba(239, 68, 68, 0.24)",
108
+ blocked: "rgba(245, 158, 11, 0.22)",
109
+ running: "rgba(59, 130, 246, 0.22)",
110
+ pending: "rgba(100, 116, 139, 0.18)",
111
+ warning: "rgba(251, 146, 60, 0.20)",
112
+ passed: "rgba(34, 197, 94, 0.22)",
113
+ skipped: "rgba(148, 163, 184, 0.18)"
114
+ };
115
+ const executionStatusBorderTone = {
116
+ failed: "rgba(239, 68, 68, 0.42)",
117
+ blocked: "rgba(245, 158, 11, 0.40)",
118
+ running: "rgba(59, 130, 246, 0.42)",
119
+ pending: "rgba(100, 116, 139, 0.34)",
120
+ warning: "rgba(251, 146, 60, 0.38)",
121
+ passed: "rgba(34, 197, 94, 0.42)",
122
+ skipped: "rgba(148, 163, 184, 0.34)"
123
+ };
124
+ const executionStatusClassName = (status) => status && status !== "idle" ? `execution-status-${status}` : undefined;
125
+ const previewExecutionMessage = (message) => {
126
+ if (!message) {
127
+ return null;
128
+ }
129
+ return message.length > 130 ? `${message.slice(0, 127)}...` : message;
130
+ };
131
+ const uniqueStrings = (values) => [...new Set(values.filter((value) => Boolean(value)))];
132
+ const formatContractCheck = (check) => `${check.stage}: ${check.status}${check.expected ? ` · expected ${check.expected}` : ""}${check.message ? ` - ${check.message}` : ""}`;
133
+ const formatTestCase = (testCase) => `${testCase.title} [${testCase.kind}]${testCase.notes.length ? ` - ${testCase.notes.join("; ")}` : ""}`;
134
+ const formatTestResult = (result) => `${result.title}: ${result.status}${result.message ? ` - ${result.message}` : ""}`;
135
+ const summarizeExecutionStates = (states) => {
136
+ const activeStates = states.filter((state) => state.status !== "idle");
137
+ if (!activeStates.length) {
138
+ return undefined;
139
+ }
140
+ const sorted = [...activeStates].sort((left, right) => executionStatusRank[left.status] - executionStatusRank[right.status]);
141
+ const representative = sorted[0];
142
+ const isDirect = activeStates.length === 1 && representative.source === "direct";
143
+ const aggregatedChecks = uniqueContractChecks(activeStates.flatMap((state) => state.contractChecks ?? []));
144
+ return {
145
+ ...representative,
146
+ source: isDirect ? representative.source : representative.source === "direct" ? "aggregated" : representative.source,
147
+ status: representative.status,
148
+ contractChecks: aggregatedChecks.length ? aggregatedChecks : representative.contractChecks,
149
+ artifactIds: uniqueStrings(activeStates.flatMap((state) => state.artifactIds ?? [])),
150
+ childStepIds: uniqueStrings([
151
+ ...activeStates.flatMap((state) => state.childStepIds ?? []),
152
+ ...activeStates.map((state) => state.stepId)
153
+ ]),
154
+ stepCount: activeStates.reduce((count, state) => count + (state.stepCount ?? 1), 0)
155
+ };
156
+ };
157
+ const uniqueContractChecks = (checks) => {
158
+ const seen = new Set();
159
+ return checks.filter((check) => {
160
+ const signature = `${check.stage}:${check.status}:${check.expected ?? ""}:${check.actualPreview ?? ""}:${check.message}`;
161
+ if (seen.has(signature)) {
162
+ return false;
163
+ }
164
+ seen.add(signature);
165
+ return true;
166
+ });
167
+ };
168
+ const aggregateExecutionState = (directState, childStates) => {
169
+ const combinedStates = [...(directState ? [directState] : []), ...childStates].filter((state) => Boolean(state) && state.status !== "idle");
170
+ if (!combinedStates.length) {
171
+ return directState?.status === "idle" ? directState : undefined;
172
+ }
173
+ const summarized = summarizeExecutionStates(combinedStates);
174
+ if (!summarized) {
175
+ return directState;
176
+ }
177
+ return directState && directState.status !== "idle" && combinedStates.length === 1
178
+ ? directState
179
+ : summarized;
180
+ };
181
+ const summarizeSteps = (steps) => {
182
+ if (!steps.length) {
183
+ return undefined;
184
+ }
185
+ const sorted = [...steps].sort((left, right) => {
186
+ const statusDelta = executionStatusRank[left.status] - executionStatusRank[right.status];
187
+ if (statusDelta !== 0) {
188
+ return statusDelta;
189
+ }
190
+ return (new Date(right.completedAt || right.startedAt).getTime() -
191
+ new Date(left.completedAt || left.startedAt).getTime());
192
+ });
193
+ const representative = sorted[0];
194
+ const contractChecks = uniqueContractChecks(steps.flatMap((step) => step.contractChecks));
195
+ return {
196
+ status: representative.status,
197
+ source: steps.length === 1 ? "direct" : "aggregated",
198
+ kind: representative.kind,
199
+ stepId: representative.id,
200
+ runId: representative.runId,
201
+ message: representative.message,
202
+ durationMs: steps.reduce((total, step) => total + step.durationMs, 0),
203
+ blockedByStepId: representative.blockedByStepId,
204
+ inputPreview: representative.inputPreview,
205
+ outputPreview: representative.outputPreview,
206
+ stdout: representative.stdout || undefined,
207
+ stderr: representative.stderr || undefined,
208
+ artifactIds: uniqueStrings(steps.flatMap((step) => step.artifactIds)),
209
+ contractChecks: contractChecks.length ? contractChecks : undefined,
210
+ childStepIds: steps.map((step) => step.id),
211
+ stepCount: steps.length
212
+ };
213
+ };
214
+ export const indexRuntimeExecutionResult = (result) => {
215
+ if (!result) {
216
+ return null;
217
+ }
218
+ const steps = result.steps ?? [];
219
+ const testCases = result.testCases ?? [];
220
+ const testResults = result.testResults ?? [];
221
+ const stepsByNodeId = {};
222
+ const stepsByEdgeId = {};
223
+ const testCasesByNodeId = {};
224
+ const testResultsByNodeId = {};
225
+ const testsByCaseId = new Map(testCases.map((testCase) => [testCase.id, testCase]));
226
+ const artifactsById = {};
227
+ for (const artifact of result.artifacts ?? []) {
228
+ artifactsById[artifact.id] = artifact;
229
+ }
230
+ for (const step of steps) {
231
+ (stepsByNodeId[step.nodeId] ??= []).push(step);
232
+ if (step.edgeId) {
233
+ (stepsByEdgeId[step.edgeId] ??= []).push(step);
234
+ }
235
+ }
236
+ for (const testCase of testCases) {
237
+ (testCasesByNodeId[testCase.nodeId] ??= []).push(testCase);
238
+ }
239
+ for (const testResult of testResults) {
240
+ const testCase = testsByCaseId.get(testResult.caseId);
241
+ if (!testCase) {
242
+ continue;
243
+ }
244
+ (testResultsByNodeId[testCase.nodeId] ??= []).push(testResult);
245
+ }
246
+ return {
247
+ runId: result.runId,
248
+ entryNodeId: result.entryNodeId,
249
+ summary: result.summary,
250
+ stepsById: Object.fromEntries(steps.map((step) => [step.id, step])),
251
+ stepsByNodeId,
252
+ stepsByEdgeId,
253
+ testCasesByNodeId,
254
+ testResultsByNodeId,
255
+ artifactsById
256
+ };
257
+ };
258
+ export const buildExecutionProjection = (graph, executionResult) => {
259
+ const index = indexRuntimeExecutionResult(executionResult);
260
+ if (!index) {
261
+ return null;
262
+ }
263
+ const nodeStateCache = new Map();
264
+ const resolveNodeState = (nodeId) => {
265
+ if (nodeStateCache.has(nodeId)) {
266
+ return nodeStateCache.get(nodeId);
267
+ }
268
+ const node = graph.nodes.find((candidate) => candidate.id === nodeId);
269
+ if (!node) {
270
+ nodeStateCache.set(nodeId, undefined);
271
+ return undefined;
272
+ }
273
+ const directState = summarizeSteps(index.stepsByNodeId[node.id] ?? []);
274
+ const childStates = graph.nodes
275
+ .filter((candidate) => candidate.ownerId === node.id)
276
+ .map((candidate) => resolveNodeState(candidate.id))
277
+ .filter((state) => Boolean(state));
278
+ const resolvedState = aggregateExecutionState(directState, childStates);
279
+ nodeStateCache.set(node.id, resolvedState);
280
+ return resolvedState;
281
+ };
282
+ const nodeStates = {};
283
+ for (const node of graph.nodes) {
284
+ const state = resolveNodeState(node.id);
285
+ if (state) {
286
+ nodeStates[node.id] = state;
287
+ }
288
+ }
289
+ const edgeStates = {};
290
+ for (const edge of graph.edges) {
291
+ const key = `${edge.kind}:${edge.from}:${edge.to}`;
292
+ const state = resolveEdgeState(edge, index, nodeStates);
293
+ if (state) {
294
+ edgeStates[key] = state;
295
+ }
296
+ }
297
+ return {
298
+ index,
299
+ nodeStates,
300
+ edgeStates
301
+ };
302
+ };
303
+ const resolveEdgeState = (edge, index, nodeStates) => {
304
+ const directState = summarizeSteps(index.stepsByEdgeId[`${edge.kind}:${edge.from}:${edge.to}`] ?? []);
305
+ if (directState) {
306
+ return directState;
307
+ }
308
+ const sourceState = nodeStates[edge.from];
309
+ const targetState = nodeStates[edge.to];
310
+ const candidateStates = [sourceState, targetState].filter((state) => Boolean(state) && state.status !== "idle");
311
+ if (!candidateStates.length) {
312
+ return undefined;
313
+ }
314
+ const statuses = candidateStates.map((state) => state.status);
315
+ const status = statuses.includes("failed")
316
+ ? "failed"
317
+ : statuses.includes("blocked")
318
+ ? "blocked"
319
+ : statuses.includes("running")
320
+ ? "running"
321
+ : statuses.includes("warning")
322
+ ? "warning"
323
+ : statuses.includes("passed")
324
+ ? "passed"
325
+ : statuses.includes("skipped")
326
+ ? "skipped"
327
+ : "idle";
328
+ return {
329
+ status,
330
+ source: "inferred",
331
+ kind: "edge",
332
+ message: targetState?.message || sourceState?.message || `Execution inferred from ${edge.label ?? edge.kind}.`,
333
+ stepId: targetState?.stepId ?? sourceState?.stepId,
334
+ runId: targetState?.runId ?? sourceState?.runId,
335
+ durationMs: (sourceState?.durationMs ?? 0) + (targetState?.durationMs ?? 0),
336
+ blockedByStepId: targetState?.blockedByStepId ?? sourceState?.blockedByStepId,
337
+ contractChecks: uniqueContractChecks([
338
+ ...(sourceState?.contractChecks ?? []),
339
+ ...(targetState?.contractChecks ?? [])
340
+ ]),
341
+ artifactIds: uniqueStrings([
342
+ ...(sourceState?.artifactIds ?? []),
343
+ ...(targetState?.artifactIds ?? [])
344
+ ]),
345
+ childStepIds: uniqueStrings([sourceState?.stepId, targetState?.stepId]),
346
+ stepCount: candidateStates.length
347
+ };
348
+ };
349
+ const buildExecutionSections = (execution, nodeId, projection) => {
350
+ const sections = [];
351
+ if (execution && execution.status !== "idle") {
352
+ const executionItems = [
353
+ `Status: ${executionStatusLabel[execution.status]}${execution.source ? ` (${execution.source})` : ""}`,
354
+ execution.kind ? `Kind: ${execution.kind}` : null,
355
+ execution.stepId ? `Step: ${execution.stepId}` : null,
356
+ execution.runId ? `Run: ${execution.runId}` : null,
357
+ typeof execution.durationMs === "number" ? `Duration: ${execution.durationMs}ms` : null,
358
+ execution.message ? `Message: ${execution.message}` : null,
359
+ execution.inputPreview ? `Input: ${execution.inputPreview}` : null,
360
+ execution.outputPreview ? `Output: ${execution.outputPreview}` : null,
361
+ execution.blockedByStepId ? `Blocked by: ${execution.blockedByStepId}` : null,
362
+ execution.stdout ? `Stdout: ${previewExecutionMessage(execution.stdout) ?? execution.stdout}` : null,
363
+ execution.stderr ? `Stderr: ${previewExecutionMessage(execution.stderr) ?? execution.stderr}` : null
364
+ ].filter((value) => Boolean(value));
365
+ if (execution.contractChecks?.length) {
366
+ executionItems.push(`Checks: ${execution.contractChecks.length}`, ...execution.contractChecks.slice(0, 5).map(formatContractCheck));
367
+ }
368
+ if (execution.artifactIds?.length) {
369
+ executionItems.push(`Artifacts: ${execution.artifactIds.length}`);
370
+ }
371
+ sections.push({ title: "Execution", items: executionItems });
372
+ }
373
+ const testCases = projection?.index.testCasesByNodeId[nodeId] ?? [];
374
+ const testResults = projection?.index.testResultsByNodeId[nodeId] ?? [];
375
+ if (testCases.length || testResults.length) {
376
+ const testItems = [
377
+ testCases.length ? `Generated cases: ${testCases.length}` : null,
378
+ ...testCases.slice(0, 5).map(formatTestCase),
379
+ testResults.length ? `Results: ${testResults.length}` : null,
380
+ ...testResults.slice(0, 5).map(formatTestResult)
381
+ ].filter((value) => Boolean(value));
382
+ sections.push({ title: "Tests", items: testItems });
383
+ }
384
+ return sections;
385
+ };
386
+ const formatField = (field) => `${field.name}: ${field.type}${field.description ? ` - ${field.description}` : ""}`;
387
+ const normalizeContract = (contract) => ({
388
+ ...emptyContract(),
389
+ ...contract
390
+ });
391
+ const formatMethodSummary = (method) => method.signature ?? `${method.name}(${method.inputs.map((input) => input.name).join(", ")})`;
392
+ const mergeBoxShadow = (nextShadow, existingShadow) => existingShadow && existingShadow !== "none" ? `${nextShadow}, ${existingShadow}` : nextShadow;
393
+ const resolveNodeHealthState = (node, traceStatus) => {
394
+ const isGhost = (node.status ?? "spec_only") === "spec_only" &&
395
+ !node.sourceRefs?.length &&
396
+ !node.generatedRefs?.length &&
397
+ !node.traceRefs?.length &&
398
+ !node.implementationDraft;
399
+ if (traceStatus === "error" || node.lastVerification?.status === "failure") {
400
+ return "heal";
401
+ }
402
+ if (node.status === "verified" || node.status === "connected") {
403
+ return "aligned";
404
+ }
405
+ if (node.status === "implemented" || Boolean(node.implementationDraft)) {
406
+ return "drift";
407
+ }
408
+ if (isGhost && traceStatus === "idle") {
409
+ return "ghost";
410
+ }
411
+ return "neutral";
412
+ };
413
+ const applyNodeStateStyles = (baseStyle, options) => {
414
+ const style = { ...baseStyle };
415
+ if (options.healthState === "aligned") {
416
+ style.boxShadow = mergeBoxShadow("0 0 0 1px rgba(34, 197, 94, 0.32), 0 0 30px rgba(34, 197, 94, 0.22)", style.boxShadow);
417
+ }
418
+ if (options.healthState === "drift") {
419
+ style.boxShadow = mergeBoxShadow("0 0 0 1px rgba(245, 158, 11, 0.34), 0 0 28px rgba(245, 158, 11, 0.18)", style.boxShadow);
420
+ }
421
+ if (options.healthState === "heal") {
422
+ style.boxShadow = mergeBoxShadow("0 0 0 1px rgba(239, 68, 68, 0.38), 0 0 32px rgba(239, 68, 68, 0.24)", style.boxShadow);
423
+ }
424
+ if (options.isActiveBatch) {
425
+ style.boxShadow = mergeBoxShadow("0 0 0 2px rgba(103, 226, 219, 0.42), 0 0 38px rgba(103, 226, 219, 0.24)", style.boxShadow);
426
+ }
427
+ if (options.isGhost) {
428
+ style.borderStyle = "dashed";
429
+ style.opacity = 0.72;
430
+ }
431
+ return style;
432
+ };
433
+ const applyExecutionStateStyles = (baseStyle, execution) => {
434
+ if (!execution || execution.status === "idle") {
435
+ return baseStyle;
436
+ }
437
+ const status = execution.status;
438
+ const style = { ...baseStyle };
439
+ const tone = executionStatusTone[status];
440
+ const borderTone = executionStatusBorderTone[status];
441
+ style.boxShadow = mergeBoxShadow(`0 0 0 1px ${borderTone}, 0 0 32px ${tone}`, style.boxShadow);
442
+ if (execution.status === "running") {
443
+ style.outline = `2px solid ${borderTone}`;
444
+ }
445
+ if (execution.status === "blocked") {
446
+ style.borderStyle = "dashed";
447
+ }
448
+ return style;
449
+ };
450
+ const mergeEdgeClassNames = (...classNames) => {
451
+ const merged = classNames.filter(Boolean).join(" ").trim();
452
+ return merged || undefined;
453
+ };
454
+ const buildNodeSections = (node, execution, projection) => {
455
+ const sections = [
456
+ ...buildExecutionSections(execution, node.id, projection ?? null),
457
+ { title: "Responsibilities", items: normalizeContract(node.contract).responsibilities },
458
+ { title: "Inputs", items: normalizeContract(node.contract).inputs.map(formatField) },
459
+ { title: "Outputs", items: normalizeContract(node.contract).outputs.map(formatField) },
460
+ { title: "Attributes / State", items: normalizeContract(node.contract).attributes.map(formatField) },
461
+ {
462
+ title: "Methods",
463
+ items: normalizeContract(node.contract).methods.map((method) => `${formatMethodSummary(method)} - ${method.summary}`)
464
+ },
465
+ { title: "Dependencies", items: normalizeContract(node.contract).dependencies },
466
+ {
467
+ title: "Calls",
468
+ items: normalizeContract(node.contract).calls.map((call) => `${call.target}${call.kind ? ` [${call.kind}]` : ""}${call.description ? ` - ${call.description}` : ""}`)
469
+ },
470
+ { title: "Side effects", items: normalizeContract(node.contract).sideEffects },
471
+ { title: "Errors", items: normalizeContract(node.contract).errors },
472
+ { title: "Notes", items: normalizeContract(node.contract).notes }
473
+ ];
474
+ return sections.filter((section) => section.items.length > 0);
475
+ };
476
+ export const buildFlowNodes = (graph, selectedNodeId, heatmapData, activeNodeIds, driftedNodeIds, executionResult) => {
477
+ const rowCounts = new Map();
478
+ const heatMetricByNodeId = heatmapData?.nodes != null
479
+ ? new Map(heatmapData.nodes.map((m) => [m.nodeId, m]))
480
+ : undefined;
481
+ const activeNodeIdSet = new Set(activeNodeIds ?? []);
482
+ const driftedNodeIdSet = new Set(driftedNodeIds ?? []);
483
+ const projection = buildExecutionProjection(graph, executionResult);
484
+ return graph.nodes.map((node) => {
485
+ const column = kindOrder[node.kind];
486
+ const row = rowCounts.get(column) ?? 0;
487
+ rowCounts.set(column, row + 1);
488
+ const traceStatus = node.traceState?.status ?? "idle";
489
+ const heatMetric = heatMetricByNodeId?.get(node.id);
490
+ const intensity = heatMetric?.heatIntensity ?? 0;
491
+ const isActiveBatch = activeNodeIdSet.has(node.id);
492
+ const isDrifted = driftedNodeIdSet.has(node.id);
493
+ // Drifted nodes are forced to the "heal" health state so they render with
494
+ // the red highlight that signals the architecture needs attention.
495
+ const healthState = isDrifted ? "heal" : resolveNodeHealthState(node, traceStatus);
496
+ const isGhost = healthState === "ghost";
497
+ const baseStyle = kindTheme(node.kind, selectedNodeId === node.id, traceStatus);
498
+ const baseBoxShadow = baseStyle?.boxShadow;
499
+ const combinedBoxShadow = baseBoxShadow && baseBoxShadow !== "none"
500
+ ? `${heatGlow(intensity)}, ${String(baseBoxShadow)}`
501
+ : heatGlow(intensity);
502
+ const baseBackground = baseStyle?.background;
503
+ const heatBackground = `linear-gradient(180deg, ${heatColor(intensity)} 0%, transparent 100%)`;
504
+ const execution = projection?.nodeStates[node.id];
505
+ const heatStyle = intensity > 0
506
+ ? {
507
+ ...baseStyle,
508
+ // Layer the heat gradient over the existing background to avoid nested gradients.
509
+ background: baseBackground
510
+ ? `${heatBackground}, ${String(baseBackground)}`
511
+ : heatBackground,
512
+ boxShadow: combinedBoxShadow,
513
+ outline: intensity > 0.66
514
+ ? `2px solid rgba(239,68,68,${(0.3 + intensity * 0.5).toFixed(2)})`
515
+ : intensity > 0.33
516
+ ? `2px solid rgba(245,158,11,${(0.2 + intensity * 0.4).toFixed(2)})`
517
+ : undefined
518
+ }
519
+ : baseStyle;
520
+ const executionStyle = applyExecutionStateStyles(heatStyle, execution);
521
+ const stateStyle = applyNodeStateStyles(executionStyle, {
522
+ healthState,
523
+ isActiveBatch,
524
+ isGhost
525
+ });
526
+ return {
527
+ id: node.id,
528
+ type: "policyNode",
529
+ position: {
530
+ x: 80 + column * 280,
531
+ y: 80 + row * 180
532
+ },
533
+ data: {
534
+ label: node.name,
535
+ summary: node.summary,
536
+ kind: node.kind,
537
+ traceStatus,
538
+ healthState,
539
+ selected: selectedNodeId === node.id,
540
+ isActiveBatch,
541
+ isGhost,
542
+ execution
543
+ },
544
+ style: stateStyle,
545
+ className: [
546
+ intensity > 0.66
547
+ ? "node-pulse-hot"
548
+ : intensity > 0.33
549
+ ? "node-pulse-warm"
550
+ : traceStatus !== "idle"
551
+ ? "node-pulse-active"
552
+ : undefined,
553
+ healthState === "aligned" ? "node-health-aligned" : undefined,
554
+ healthState === "drift" ? "node-health-drift" : undefined,
555
+ healthState === "heal" ? "node-health-heal" : undefined,
556
+ executionStatusClassName(execution?.status),
557
+ isGhost ? "node-ghost" : undefined,
558
+ isActiveBatch ? "node-batch-focus" : undefined,
559
+ isDrifted ? "node-drift-shake" : undefined
560
+ ]
561
+ .filter(Boolean)
562
+ .join(" ")
563
+ };
564
+ });
565
+ };
566
+ export const buildFlowEdges = (graph, activeNodeIds, executionResult) => {
567
+ const activeNodeIdSet = new Set(activeNodeIds ?? []);
568
+ const projection = buildExecutionProjection(graph, executionResult);
569
+ return graph.edges.map((edge) => {
570
+ const isActive = activeNodeIdSet.has(edge.from) || activeNodeIdSet.has(edge.to);
571
+ const execution = projection?.edgeStates[`${edge.kind}:${edge.from}:${edge.to}`];
572
+ const shouldAnimate = isActive || execution?.status === "running";
573
+ const executionClassName = execution?.status && execution.status !== "idle" ? `edge-flow-${execution.status}` : undefined;
574
+ const edgeStroke = execution?.status === "failed"
575
+ ? "rgba(239, 68, 68, 0.92)"
576
+ : execution?.status === "blocked"
577
+ ? "rgba(245, 158, 11, 0.92)"
578
+ : execution?.status === "running"
579
+ ? "rgba(59, 130, 246, 0.92)"
580
+ : execution?.status === "warning"
581
+ ? "rgba(251, 146, 60, 0.92)"
582
+ : execution?.status === "passed"
583
+ ? "rgba(34, 197, 94, 0.92)"
584
+ : execution?.status === "skipped"
585
+ ? "rgba(148, 163, 184, 0.92)"
586
+ : edge.kind === "calls"
587
+ ? "var(--flow-edge-strong)"
588
+ : "var(--flow-edge)";
589
+ return {
590
+ id: `${edge.kind}:${edge.from}:${edge.to}`,
591
+ source: edge.from,
592
+ target: edge.to,
593
+ label: edge.label ?? edge.kind,
594
+ animated: shouldAnimate,
595
+ className: mergeEdgeClassNames(executionClassName ?? (isActive ? "edge-flow-active" : "edge-flow-idle")),
596
+ style: {
597
+ strokeWidth: execution?.status && execution.status !== "idle" ? 2.8 : isActive ? 2.7 : edge.required ? 2.4 : 1.4,
598
+ stroke: edgeStroke,
599
+ strokeDasharray: execution?.status === "blocked"
600
+ ? "6 5"
601
+ : execution?.status === "running"
602
+ ? "4 4"
603
+ : execution?.status === "warning"
604
+ ? "8 4"
605
+ : execution?.status === "skipped"
606
+ ? "10 6"
607
+ : undefined
608
+ },
609
+ labelStyle: {
610
+ fill: "var(--muted)",
611
+ fontSize: 12,
612
+ fontWeight: 600
613
+ }
614
+ };
615
+ });
616
+ };
617
+ export const buildGhostFlowNodes = (ghostNodes, existingNodes) => {
618
+ // Place ghost nodes offset from the rightmost existing node column so they
619
+ // are visually distinct and don't overlap regular nodes.
620
+ const maxX = existingNodes.reduce((acc, n) => Math.max(acc, (n.position?.x ?? 0) + 280), 80);
621
+ const column = maxX;
622
+ return ghostNodes.map((ghost, index) => ({
623
+ id: ghost.id,
624
+ position: {
625
+ x: column,
626
+ y: 80 + index * 180
627
+ },
628
+ data: {
629
+ label: ghost.name,
630
+ summary: ghost.summary,
631
+ kind: ghost.kind,
632
+ traceStatus: "idle",
633
+ healthState: "ghost",
634
+ selected: false,
635
+ isActiveBatch: false,
636
+ isGhost: true,
637
+ ghost: true,
638
+ ghostReason: ghost.reason,
639
+ execution: undefined
640
+ },
641
+ style: {
642
+ width: 252,
643
+ borderRadius: 24,
644
+ border: "1.5px dashed rgba(139, 92, 246, 0.55)",
645
+ background: "linear-gradient(180deg, rgba(255,255,255,0.55) 0%, rgba(237,233,254,0.45) 100%)",
646
+ padding: 18,
647
+ boxShadow: "0 8px 24px rgba(139, 92, 246, 0.12)",
648
+ backdropFilter: "blur(10px)",
649
+ opacity: 0.72,
650
+ cursor: "pointer"
651
+ }
652
+ }));
653
+ };
654
+ const createDetailNode = (item, position, selectedId) => ({
655
+ id: item.id,
656
+ type: "policyNode",
657
+ position,
658
+ data: {
659
+ label: item.label,
660
+ summary: item.summary,
661
+ kind: item.kind,
662
+ traceStatus: "idle",
663
+ healthState: "neutral",
664
+ selected: selectedId === item.id,
665
+ isActiveBatch: false,
666
+ isGhost: false,
667
+ drilldownNodeId: item.drilldownNodeId,
668
+ execution: item.execution
669
+ },
670
+ style: {
671
+ width: 240,
672
+ borderRadius: 22,
673
+ border: selectedId === item.id ? "1.5px solid var(--accent-2)" : "1px solid var(--node-border-default)",
674
+ background: item.execution && item.execution.status !== "idle"
675
+ ? `linear-gradient(180deg, rgba(255,255,255,0.98) 0%, ${detailKindColor(item.kind)} 100%)`
676
+ : `linear-gradient(180deg, var(--surface-raised) 0%, ${detailKindColor(item.kind)} 100%)`,
677
+ padding: 14,
678
+ boxShadow: item.execution && item.execution.status !== "idle"
679
+ ? `0 0 0 1px ${executionStatusBorderTone[item.execution.status]}, 0 18px 36px rgba(15, 23, 42, 0.12)`
680
+ : "0 18px 36px rgba(15, 23, 42, 0.12)"
681
+ },
682
+ className: mergeEdgeClassNames(executionStatusClassName(item.execution?.status), item.execution?.status === "blocked" ? "node-execution-blocked" : undefined)
683
+ });
684
+ export const buildDetailFlow = (graph, rootNodeId, selectedItemId, executionResult) => {
685
+ const rootNode = graph.nodes.find((node) => node.id === rootNodeId);
686
+ if (!rootNode) {
687
+ return null;
688
+ }
689
+ const projection = buildExecutionProjection(graph, executionResult);
690
+ const rootContract = normalizeContract(rootNode.contract);
691
+ const rootExecution = projection?.nodeStates[rootNode.id];
692
+ const items = [];
693
+ const edges = [];
694
+ const itemIdsByBlueprintNodeId = new Map();
695
+ const rootItemId = `detail:root:${rootNode.id}`;
696
+ items.push({
697
+ id: rootItemId,
698
+ label: rootNode.name,
699
+ summary: rootNode.summary,
700
+ kind: "root",
701
+ signature: rootNode.signature,
702
+ path: rootNode.path,
703
+ execution: rootExecution,
704
+ sections: buildNodeSections(rootNode, rootExecution, projection)
705
+ });
706
+ itemIdsByBlueprintNodeId.set(rootNode.id, rootItemId);
707
+ const ownedNodes = graph.nodes.filter((node) => node.ownerId === rootNode.id);
708
+ for (const ownedNode of ownedNodes) {
709
+ const itemId = `detail:blueprint:${ownedNode.id}`;
710
+ const execution = projection?.nodeStates[ownedNode.id];
711
+ items.push({
712
+ id: itemId,
713
+ label: ownedNode.name,
714
+ summary: ownedNode.summary,
715
+ kind: "blueprint-node",
716
+ signature: ownedNode.signature,
717
+ path: ownedNode.path,
718
+ drilldownNodeId: ownedNode.id,
719
+ execution,
720
+ sections: buildNodeSections(ownedNode, execution, projection)
721
+ });
722
+ itemIdsByBlueprintNodeId.set(ownedNode.id, itemId);
723
+ edges.push({
724
+ id: `${rootItemId}:contains:${itemId}`,
725
+ source: rootItemId,
726
+ target: itemId,
727
+ label: "contains"
728
+ });
729
+ }
730
+ for (const edge of graph.edges) {
731
+ const source = itemIdsByBlueprintNodeId.get(edge.from);
732
+ const target = itemIdsByBlueprintNodeId.get(edge.to);
733
+ if (!source || !target || source === target) {
734
+ continue;
735
+ }
736
+ edges.push({
737
+ id: `detail:${edge.kind}:${source}:${target}`,
738
+ source,
739
+ target,
740
+ label: edge.label ?? edge.kind,
741
+ animated: edge.kind === "calls",
742
+ style: {
743
+ strokeWidth: edge.required ? 2 : 1
744
+ }
745
+ });
746
+ }
747
+ const addSatelliteItems = (kind, values, relation) => {
748
+ values.forEach((value, index) => {
749
+ const itemId = `detail:${kind}:${rootNode.id}:${index}`;
750
+ const execution = kind === "method" ? undefined : rootExecution;
751
+ items.push({
752
+ id: itemId,
753
+ label: value.split(" - ")[0] ?? value,
754
+ summary: value,
755
+ kind,
756
+ execution,
757
+ sections: [
758
+ ...buildExecutionSections(execution, rootNode.id, projection),
759
+ { title: "Details", items: [value] }
760
+ ]
761
+ });
762
+ edges.push({
763
+ id: `${rootItemId}:${relation}:${itemId}`,
764
+ source: rootItemId,
765
+ target: itemId,
766
+ label: relation
767
+ });
768
+ });
769
+ };
770
+ if (rootContract.attributes.length) {
771
+ addSatelliteItems("attribute", rootContract.attributes.map(formatField), "state");
772
+ }
773
+ if (ownedNodes.length === 0 && rootContract.methods.length) {
774
+ rootContract.methods.forEach((method, index) => {
775
+ const itemId = `detail:method:${rootNode.id}:${index}`;
776
+ const matchingMethodSteps = projection?.index.stepsByNodeId[rootNode.id]?.filter((step) => step.kind === "method" && (step.methodName === method.name || step.methodName === method.signature)) ?? [];
777
+ const methodExecution = summarizeSteps(matchingMethodSteps) ?? rootExecution;
778
+ items.push({
779
+ id: itemId,
780
+ label: method.name,
781
+ summary: method.summary,
782
+ kind: "method",
783
+ signature: method.signature,
784
+ execution: methodExecution,
785
+ sections: [
786
+ ...buildExecutionSections(methodExecution, rootNode.id, projection),
787
+ { title: "Inputs", items: method.inputs.map(formatField) },
788
+ { title: "Outputs", items: method.outputs.map(formatField) },
789
+ { title: "Side effects", items: method.sideEffects },
790
+ {
791
+ title: "Calls",
792
+ items: method.calls.map((call) => `${call.target}${call.kind ? ` [${call.kind}]` : ""}${call.description ? ` - ${call.description}` : ""}`)
793
+ }
794
+ ].filter((section) => section.items.length > 0)
795
+ });
796
+ edges.push({
797
+ id: `${rootItemId}:method:${itemId}`,
798
+ source: rootItemId,
799
+ target: itemId,
800
+ label: "method"
801
+ });
802
+ });
803
+ }
804
+ addSatelliteItems("input", rootContract.inputs.map(formatField), "accepts");
805
+ addSatelliteItems("output", rootContract.outputs.map(formatField), "returns");
806
+ addSatelliteItems("dependency", rootContract.dependencies, "depends on");
807
+ addSatelliteItems("call", rootContract.calls.map((call) => `${call.target}${call.kind ? ` [${call.kind}]` : ""}${call.description ? ` - ${call.description}` : ""}`), "calls");
808
+ addSatelliteItems("error", rootContract.errors, "may fail");
809
+ addSatelliteItems("side-effect", rootContract.sideEffects, "changes");
810
+ addSatelliteItems("note", rootContract.notes, "notes");
811
+ const buckets = {
812
+ root: items.filter((item) => item.kind === "root"),
813
+ "blueprint-node": items.filter((item) => item.kind === "blueprint-node"),
814
+ method: items.filter((item) => item.kind === "method"),
815
+ attribute: items.filter((item) => item.kind === "attribute"),
816
+ input: items.filter((item) => item.kind === "input"),
817
+ output: items.filter((item) => item.kind === "output"),
818
+ dependency: items.filter((item) => item.kind === "dependency"),
819
+ call: items.filter((item) => item.kind === "call"),
820
+ error: items.filter((item) => item.kind === "error"),
821
+ "side-effect": items.filter((item) => item.kind === "side-effect"),
822
+ note: items.filter((item) => item.kind === "note")
823
+ };
824
+ const positions = new Map();
825
+ const layout = (kind, column, startY, gapY) => {
826
+ buckets[kind].forEach((item, index) => {
827
+ positions.set(item.id, {
828
+ x: 80 + column * 280,
829
+ y: startY + index * gapY
830
+ });
831
+ });
832
+ };
833
+ layout("root", 1, 160, 160);
834
+ layout("blueprint-node", 2, 80, 160);
835
+ layout("method", 2, 80 + buckets["blueprint-node"].length * 170, 160);
836
+ layout("attribute", 0, 80, 130);
837
+ layout("input", 0, 320, 120);
838
+ layout("output", 3, 80, 120);
839
+ layout("dependency", 3, 260, 120);
840
+ layout("call", 3, 440, 120);
841
+ layout("error", 0, 520, 120);
842
+ layout("side-effect", 2, 420, 120);
843
+ layout("note", 1, 420, 120);
844
+ return {
845
+ items,
846
+ edges,
847
+ nodes: items.map((item) => createDetailNode(item, positions.get(item.id) ?? { x: 80, y: 80 }, selectedItemId))
848
+ };
849
+ };
850
+ //# sourceMappingURL=flow-view.js.map