@runfusion/fusion 0.24.0 → 0.25.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 (106) hide show
  1. package/dist/bin.js +2254 -1349
  2. package/dist/client/assets/AgentDetailView-ZbHEbYRT.js +18 -0
  3. package/dist/client/assets/{AgentsView-BkB9FiMT.js → AgentsView-B3jYk8Kt.js} +3 -3
  4. package/dist/client/assets/ChatView-DhPkiEGs.js +1 -0
  5. package/dist/client/assets/{DevServerView-BkvtjZBa.js → DevServerView-DyGDEiBP.js} +1 -1
  6. package/dist/client/assets/{DirectoryPicker-BK-KbnhP.js → DirectoryPicker-D5UIeIl6.js} +1 -1
  7. package/dist/client/assets/{DocumentsView-BEg1CQAk.js → DocumentsView-DNHu1T8K.js} +1 -1
  8. package/dist/client/assets/{EvalsView-Berf9bQm.js → EvalsView-CpRobtDi.js} +1 -1
  9. package/dist/client/assets/{ExperimentalAgentOnboardingModal-jcInE50G.js → ExperimentalAgentOnboardingModal-DOY_oZi7.js} +1 -1
  10. package/dist/client/assets/{InsightsView-BX5bSF1J.js → InsightsView-vp0RE8Mg.js} +1 -1
  11. package/dist/client/assets/MemoryView-PSc5lGJt.js +2 -0
  12. package/dist/client/assets/MemoryView-zaXewZzi.css +1 -0
  13. package/dist/client/assets/{NodesView-DLUOBLf6.js → NodesView-DMj6HGeC.js} +1 -1
  14. package/dist/client/assets/{PiExtensionsManager-COlJf0Kx.js → PiExtensionsManager-DL_QcN56.js} +2 -2
  15. package/dist/client/assets/PluginManager-BtYKm8IT.js +1 -0
  16. package/dist/client/assets/{ResearchView-B256Lr8I.js → ResearchView-BhWqfdV0.js} +1 -1
  17. package/dist/client/assets/{SettingsModal-BeA_nQtW.js → SettingsModal-BAgB4_AR.js} +4 -4
  18. package/dist/client/assets/{SettingsModal-yRqM4DV8.js → SettingsModal-CUCyaAyE.js} +1 -1
  19. package/dist/client/assets/{SetupWizardModal-uUZk3TKT.js → SetupWizardModal-BKscasuh.js} +1 -1
  20. package/dist/client/assets/{SkillsView-CP8JX0P_.js → SkillsView-BdELqTy7.js} +1 -1
  21. package/dist/client/assets/{TodoView-DCRIkDZ-.js → TodoView-DFNGBDNV.js} +1 -1
  22. package/dist/client/assets/{folder-open-DHjELt8-.js → folder-open-k1xmUMyr.js} +1 -1
  23. package/dist/client/assets/index-Qq2JOOWx.css +1 -0
  24. package/dist/client/assets/{index-CQyVRLOb.js → index-TFYXEVpn.js} +160 -160
  25. package/dist/client/assets/{star-DYesq1AV.js → star-ne32r3Y4.js} +1 -1
  26. package/dist/client/assets/{upload-DTWF3Db5.js → upload-MS-2Gx53.js} +1 -1
  27. package/dist/client/assets/{users--syrel4l.js → users-C519GSjH.js} +1 -1
  28. package/dist/client/index.html +2 -2
  29. package/dist/client/version.json +1 -1
  30. package/dist/droid-cli/package.json +1 -1
  31. package/dist/extension.js +1370 -629
  32. package/dist/pi-claude-cli/package.json +1 -1
  33. package/dist/plugins/fusion-plugin-cursor-runtime/bundled.js +9 -11
  34. package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
  35. package/dist/plugins/fusion-plugin-dependency-graph/bundled.js +30 -0
  36. package/dist/plugins/fusion-plugin-dependency-graph/package.json +3 -28
  37. package/dist/plugins/fusion-plugin-droid-runtime/bundled.js +899 -895
  38. package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
  39. package/dist/plugins/fusion-plugin-hermes-runtime/bundled.js +68 -71
  40. package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
  41. package/dist/plugins/fusion-plugin-openclaw-runtime/bundled.js +47 -50
  42. package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
  43. package/dist/plugins/fusion-plugin-paperclip-runtime/bundled.js +155 -109
  44. package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
  45. package/dist/plugins/fusion-plugin-reports/package.json +1 -1
  46. package/dist/plugins/fusion-plugin-reports/src/index.ts +49 -3
  47. package/dist/plugins/fusion-plugin-reports/src/report-schema.ts +38 -0
  48. package/dist/plugins/fusion-plugin-reports/src/store/__tests__/report-schema.test.ts +66 -0
  49. package/dist/plugins/fusion-plugin-reports/src/store/__tests__/report-store.test.ts +177 -0
  50. package/dist/plugins/fusion-plugin-reports/src/store/report-store.ts +341 -0
  51. package/dist/plugins/fusion-plugin-reports/src/store/report-types.ts +77 -0
  52. package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
  53. package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
  54. package/package.json +1 -1
  55. package/dist/client/assets/AgentDetailView-gy_5SUj2.js +0 -18
  56. package/dist/client/assets/ChatView-B_-B8fqu.js +0 -1
  57. package/dist/client/assets/MemoryView-CKElJY_3.js +0 -2
  58. package/dist/client/assets/MemoryView-DiajLXby.css +0 -1
  59. package/dist/client/assets/PluginManager-CfW55BF4.js +0 -1
  60. package/dist/client/assets/createLucideIcon-BazL2hk5.js +0 -21
  61. package/dist/client/assets/dashboard-view-BkTMSZYn.css +0 -1
  62. package/dist/client/assets/dashboard-view-CyWN-d02.js +0 -63
  63. package/dist/client/assets/dashboard-view-DdGlfuu-.css +0 -1
  64. package/dist/client/assets/dashboard-view-lR7YYmSC.js +0 -21
  65. package/dist/client/assets/index-CxA2Nn0_.css +0 -1
  66. package/dist/plugins/fusion-plugin-dependency-graph/src/DependencyGraph.css +0 -58
  67. package/dist/plugins/fusion-plugin-dependency-graph/src/DependencyGraph.tsx +0 -301
  68. package/dist/plugins/fusion-plugin-dependency-graph/src/GraphHighlight.css +0 -27
  69. package/dist/plugins/fusion-plugin-dependency-graph/src/GraphTaskNode.css +0 -157
  70. package/dist/plugins/fusion-plugin-dependency-graph/src/GraphTaskNode.tsx +0 -126
  71. package/dist/plugins/fusion-plugin-dependency-graph/src/GraphToolbar.css +0 -35
  72. package/dist/plugins/fusion-plugin-dependency-graph/src/GraphToolbar.tsx +0 -36
  73. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/DependencyGraph.highlighting.test.tsx +0 -112
  74. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/DependencyGraph.persistence.test.tsx +0 -115
  75. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/DependencyGraph.test.tsx +0 -128
  76. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/GraphTaskNode.drag.test.tsx +0 -82
  77. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/GraphTaskNode.test.tsx +0 -307
  78. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/GraphToolbar.test.tsx +0 -60
  79. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/edges.test.tsx +0 -75
  80. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/filtering.test.tsx +0 -62
  81. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/filters.test.ts +0 -78
  82. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/graphPositionStorage.test.ts +0 -95
  83. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/host-integration.test.ts +0 -74
  84. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/index.test.ts +0 -58
  85. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/interactions.test.tsx +0 -121
  86. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/layout.test.ts +0 -70
  87. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/persistence.test.tsx +0 -89
  88. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/useGraphData.test.ts +0 -86
  89. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/useGraphInteraction.test.ts +0 -167
  90. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/useGraphPositions.test.ts +0 -66
  91. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/useNodeDrag.test.ts +0 -81
  92. package/dist/plugins/fusion-plugin-dependency-graph/src/dashboard-interop.d.ts +0 -35
  93. package/dist/plugins/fusion-plugin-dependency-graph/src/dashboard-view.tsx +0 -19
  94. package/dist/plugins/fusion-plugin-dependency-graph/src/edges.tsx +0 -70
  95. package/dist/plugins/fusion-plugin-dependency-graph/src/filters.ts +0 -8
  96. package/dist/plugins/fusion-plugin-dependency-graph/src/hooks/__tests__/useDependencyChain.test.ts +0 -53
  97. package/dist/plugins/fusion-plugin-dependency-graph/src/hooks/useDependencyChain.ts +0 -60
  98. package/dist/plugins/fusion-plugin-dependency-graph/src/hooks/useGraphPositions.ts +0 -45
  99. package/dist/plugins/fusion-plugin-dependency-graph/src/hooks/useNodeDrag.ts +0 -114
  100. package/dist/plugins/fusion-plugin-dependency-graph/src/index.ts +0 -24
  101. package/dist/plugins/fusion-plugin-dependency-graph/src/layout.ts +0 -91
  102. package/dist/plugins/fusion-plugin-dependency-graph/src/styles/drag.css +0 -15
  103. package/dist/plugins/fusion-plugin-dependency-graph/src/types.ts +0 -21
  104. package/dist/plugins/fusion-plugin-dependency-graph/src/useGraphData.ts +0 -17
  105. package/dist/plugins/fusion-plugin-dependency-graph/src/useGraphInteraction.ts +0 -292
  106. package/dist/plugins/fusion-plugin-dependency-graph/src/utils/graphPositionStorage.ts +0 -65
@@ -1,58 +0,0 @@
1
- .dependency-graph {
2
- position: relative;
3
- height: 100%;
4
- padding: var(--space-md);
5
- }
6
-
7
- .dependency-graph__viewport {
8
- position: relative;
9
- overflow: hidden;
10
- height: 100%;
11
- min-height: calc(var(--space-2xl) * 10);
12
- border: var(--btn-border-width) solid var(--border);
13
- border-radius: var(--radius-md);
14
- background: var(--surface);
15
- touch-action: none;
16
- }
17
-
18
- .graph-canvas-transform {
19
- position: relative;
20
- transform-origin: top left;
21
- }
22
-
23
- .graph-canvas-transform--animate {
24
- transition: transform var(--transition-normal);
25
- }
26
-
27
- .dependency-graph__nodes-layer {
28
- position: relative;
29
- z-index: 1;
30
- }
31
-
32
- .dependency-graph__nodes-layer .graph-task-node {
33
- max-width: min(100%, calc(var(--space-2xl) * 9));
34
- }
35
-
36
- .dependency-graph-edges {
37
- position: absolute;
38
- inset: 0;
39
- width: 100%;
40
- height: 100%;
41
- pointer-events: none;
42
- overflow: visible;
43
- }
44
-
45
-
46
- .dependency-graph__empty {
47
- color: var(--text-muted);
48
- display: flex;
49
- align-items: center;
50
- justify-content: center;
51
- height: 100%;
52
- }
53
-
54
- @media (max-width: 768px) {
55
- .dependency-graph {
56
- padding: var(--space-sm);
57
- }
58
- }
@@ -1,301 +0,0 @@
1
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
- import type { Task } from "@fusion/core";
3
- import { GraphTaskNode } from "./GraphTaskNode";
4
- import { GraphToolbar } from "./GraphToolbar";
5
- import { GraphEdges } from "./edges";
6
- import { filterGraphTasks } from "./filters";
7
- import { computeAutoLayout } from "./layout";
8
- import { useGraphData } from "./useGraphData";
9
- import { useGraphInteraction } from "./useGraphInteraction";
10
- import { useDependencyChain } from "./hooks/useDependencyChain";
11
- import { useGraphPositions } from "./hooks/useGraphPositions";
12
- import { mergePositions, type NodePositions } from "./utils/graphPositionStorage";
13
- import "./DependencyGraph.css";
14
-
15
- const NODE_WIDTH = 280;
16
- const NODE_HEIGHT = 100;
17
-
18
- export interface DependencyGraphProps {
19
- tasks: Task[];
20
- projectId?: string;
21
- onOpenTaskDetail?: (taskId: string) => void;
22
- onOpenDetail?: (task: Task) => void;
23
- addToast?: (message: string, type?: "success" | "error" | "info" | "warning") => void;
24
- globalPaused?: boolean;
25
- onUpdateTask?: (id: string, updates: { title?: string; description?: string; dependencies?: string[] }) => Promise<Task>;
26
- onArchiveTask?: (id: string) => Promise<Task>;
27
- onUnarchiveTask?: (id: string) => Promise<Task>;
28
- onDeleteTask?: (id: string, options?: { removeDependencyReferences?: boolean }) => Promise<Task>;
29
- onRetryTask?: (id: string) => Promise<Task>;
30
- onOpenDetailWithTab?: (task: Task, initialTab: "changes") => void;
31
- taskStuckTimeoutMs?: number;
32
- onOpenMission?: (missionId: string) => void;
33
- onMoveTask?: (id: string, column: Task["column"], optionsOrPosition?: { preserveProgress?: boolean } | number) => Promise<Task>;
34
- lastFetchTimeMs?: number;
35
- workflowStepNameLookup?: ReadonlyMap<string, string>;
36
- }
37
-
38
- const POINTER_MOVE_THRESHOLD = 4;
39
-
40
- export function DependencyGraph({
41
- tasks,
42
- projectId,
43
- onOpenTaskDetail,
44
- onOpenDetail,
45
- addToast,
46
- globalPaused,
47
- onUpdateTask,
48
- onArchiveTask,
49
- onUnarchiveTask,
50
- onDeleteTask,
51
- onRetryTask,
52
- onOpenDetailWithTab,
53
- taskStuckTimeoutMs,
54
- onOpenMission,
55
- onMoveTask,
56
- lastFetchTimeMs,
57
- workflowStepNameLookup,
58
- }: DependencyGraphProps) {
59
- const viewportRef = useRef<HTMLDivElement | null>(null);
60
- const initialFitDoneRef = useRef(false);
61
- const pointerDownRef = useRef<{ x: number; y: number } | null>(null);
62
- const pointerDraggedRef = useRef(false);
63
- const [hoveredTaskId, setHoveredTaskId] = useState<string | null>(null);
64
- const [selectedTaskId, setSelectedTaskId] = useState<string | null>(null);
65
- const filteredTasks = useMemo(() => filterGraphTasks(tasks), [tasks]);
66
- const graphData = useGraphData(filteredTasks);
67
- const { getChain } = useDependencyChain(filteredTasks);
68
- const activeTaskId = hoveredTaskId ?? selectedTaskId;
69
- const highlightedTaskIds = useMemo(() => (activeTaskId ? getChain(activeTaskId) : new Set<string>()), [activeTaskId, getChain]);
70
-
71
- const autoLayoutPositions = useMemo(
72
- () => computeAutoLayout(graphData, { nodeWidth: NODE_WIDTH, nodeHeight: NODE_HEIGHT, horizontalGap: 40, verticalGap: 80 }),
73
- [graphData],
74
- );
75
- const visibleTaskIds = useMemo(() => new Set(filteredTasks.map((task) => task.id)), [filteredTasks]);
76
- const { savedPositions, persistPositions, clearSavedPositions } = useGraphPositions({ projectId, visibleTaskIds });
77
- const [positions, setPositions] = useState<Map<string, { x: number; y: number }>>(autoLayoutPositions);
78
- const [isNodeDragging, setIsNodeDragging] = useState(false);
79
-
80
- useEffect(() => {
81
- const autoLayoutRecord: NodePositions = {};
82
- for (const [taskId, position] of autoLayoutPositions.entries()) {
83
- autoLayoutRecord[taskId] = position;
84
- }
85
-
86
- const merged = savedPositions ? mergePositions(autoLayoutRecord, savedPositions, visibleTaskIds) : autoLayoutRecord;
87
- setPositions(new Map(Object.entries(merged)));
88
- }, [autoLayoutPositions, savedPositions, visibleTaskIds]);
89
-
90
- const {
91
- transform,
92
- zoom,
93
- transitioning,
94
- zoomIn,
95
- zoomOut,
96
- resetView,
97
- fitToGraph,
98
- onPointerDown,
99
- onPointerMove,
100
- onPointerUp,
101
- onWheelZoom,
102
- handleKeyDown,
103
- } = useGraphInteraction();
104
-
105
- useEffect(() => {
106
- if (initialFitDoneRef.current) return;
107
- if (filteredTasks.length === 0) return;
108
-
109
- const hasSavedPositions = Boolean(savedPositions && Object.keys(savedPositions).length > 0);
110
- if (hasSavedPositions) {
111
- initialFitDoneRef.current = true;
112
- return;
113
- }
114
-
115
- const viewport = viewportRef.current;
116
- if (!viewport) return;
117
-
118
- fitToGraph(positions, viewport.clientWidth, viewport.clientHeight, { nodeWidth: NODE_WIDTH, nodeHeight: NODE_HEIGHT });
119
- initialFitDoneRef.current = true;
120
- }, [filteredTasks.length, fitToGraph, positions, savedPositions]);
121
-
122
- const bounds = useMemo(() => {
123
- const values = Array.from(positions.values());
124
- if (values.length === 0) return { width: 0, height: 0 };
125
- const maxX = Math.max(...values.map((pos) => pos.x + NODE_WIDTH));
126
- const maxY = Math.max(...values.map((pos) => pos.y + NODE_HEIGHT));
127
- return { width: maxX, height: maxY };
128
- }, [positions]);
129
-
130
- const handleResetLayout = useCallback(() => {
131
- clearSavedPositions();
132
- const freshLayout = computeAutoLayout(graphData, { nodeWidth: NODE_WIDTH, nodeHeight: NODE_HEIGHT, horizontalGap: 40, verticalGap: 80 });
133
- setPositions(freshLayout);
134
- }, [clearSavedPositions, graphData]);
135
-
136
- const handleNodeDragEnd = useCallback(() => {
137
- const positionRecord: NodePositions = {};
138
- for (const [taskId, position] of positions.entries()) {
139
- positionRecord[taskId] = position;
140
- }
141
- persistPositions(positionRecord);
142
- }, [persistPositions, positions]);
143
-
144
- return (
145
- <section className="dependency-graph" data-testid="dependency-graph">
146
- <div
147
- ref={viewportRef}
148
- className="dependency-graph__viewport"
149
- onPointerDown={(event) => {
150
- if (isNodeDragging) return;
151
- pointerDownRef.current = { x: event.clientX, y: event.clientY };
152
- pointerDraggedRef.current = false;
153
- onPointerDown(event.pointerId, { x: event.clientX, y: event.clientY });
154
- }}
155
- onPointerMove={(event) => {
156
- if (isNodeDragging) return;
157
- const viewport = viewportRef.current;
158
- if (!viewport) return;
159
- const pointerDown = pointerDownRef.current;
160
- if (pointerDown) {
161
- const deltaX = Math.abs(event.clientX - pointerDown.x);
162
- const deltaY = Math.abs(event.clientY - pointerDown.y);
163
- if (deltaX > POINTER_MOVE_THRESHOLD || deltaY > POINTER_MOVE_THRESHOLD) {
164
- pointerDraggedRef.current = true;
165
- }
166
- }
167
- onPointerMove(event.pointerId, { x: event.clientX, y: event.clientY }, viewport.clientWidth, viewport.clientHeight);
168
- }}
169
- onPointerUp={(event) => {
170
- if (!isNodeDragging) {
171
- onPointerUp(event.pointerId);
172
- }
173
- pointerDownRef.current = null;
174
- }}
175
- onPointerCancel={(event) => {
176
- if (!isNodeDragging) {
177
- onPointerUp(event.pointerId);
178
- }
179
- pointerDownRef.current = null;
180
- pointerDraggedRef.current = false;
181
- }}
182
- onWheel={(event) => {
183
- event.preventDefault();
184
- const viewport = viewportRef.current;
185
- if (!viewport) return;
186
- const rect = viewport.getBoundingClientRect();
187
- onWheelZoom(event.deltaY, { x: event.clientX - rect.left, y: event.clientY - rect.top }, viewport.clientWidth, viewport.clientHeight);
188
- }}
189
- onKeyDown={(event) => {
190
- const viewport = viewportRef.current;
191
- if (!viewport) return;
192
- handleKeyDown(event, viewport.clientWidth, viewport.clientHeight, positions, { nodeWidth: NODE_WIDTH, nodeHeight: NODE_HEIGHT });
193
- }}
194
- tabIndex={0}
195
- style={{ outline: "none" }}
196
- onClick={() => {
197
- if (pointerDraggedRef.current || isNodeDragging) return;
198
- setSelectedTaskId(null);
199
- }}
200
- >
201
- {filteredTasks.length === 0 ? (
202
- <div className="dependency-graph__empty">No active tasks to display in graph view.</div>
203
- ) : (
204
- <div className={`graph-canvas-transform${transitioning ? " graph-canvas-transform--animate" : ""}`} style={{ transform, width: `${bounds.width}px`, height: `${bounds.height}px` }}>
205
- <GraphEdges
206
- edges={graphData.edges}
207
- positions={positions}
208
- nodeWidth={NODE_WIDTH}
209
- nodeHeight={NODE_HEIGHT}
210
- highlightedEdgeIds={
211
- highlightedTaskIds.size > 0
212
- ? new Set(
213
- graphData.edges
214
- .filter((edge) => highlightedTaskIds.has(edge.source) && highlightedTaskIds.has(edge.target))
215
- .map((edge) => `${edge.source}->${edge.target}`),
216
- )
217
- : undefined
218
- }
219
- />
220
- <div className="dependency-graph__nodes-layer">
221
- {graphData.nodes.map((node) => {
222
- const position = positions.get(node.task.id);
223
- if (!position) return null;
224
-
225
- return (
226
- <GraphTaskNode
227
- key={node.task.id}
228
- task={node.task}
229
- projectId={projectId}
230
- style={{ minHeight: `${NODE_HEIGHT}px`, left: `${position.x}px`, top: `${position.y}px` }}
231
- position={position}
232
- scale={zoom}
233
- onNodePositionChange={(taskId, nextPosition) => {
234
- setPositions((current) => {
235
- const existing = current.get(taskId);
236
- if (existing && existing.x === nextPosition.x && existing.y === nextPosition.y) return current;
237
- const next = new Map(current);
238
- next.set(taskId, nextPosition);
239
- return next;
240
- });
241
- }}
242
- onNodeDragStateChange={setIsNodeDragging}
243
- onNodeDragEnd={handleNodeDragEnd}
244
- isHighlighted={highlightedTaskIds.size > 0 && highlightedTaskIds.has(node.task.id)}
245
- isDimmed={highlightedTaskIds.size > 0 && !highlightedTaskIds.has(node.task.id)}
246
- onOpenDetail={onOpenDetail ?? ((task) => onOpenTaskDetail?.(task.id))}
247
- addToast={addToast ?? (() => {})}
248
- globalPaused={globalPaused}
249
- onUpdateTask={onUpdateTask}
250
- onArchiveTask={onArchiveTask}
251
- onUnarchiveTask={onUnarchiveTask}
252
- onDeleteTask={onDeleteTask}
253
- onRetryTask={onRetryTask}
254
- onOpenDetailWithTab={onOpenDetailWithTab}
255
- taskStuckTimeoutMs={taskStuckTimeoutMs}
256
- onOpenMission={onOpenMission}
257
- onMoveTask={onMoveTask}
258
- lastFetchTimeMs={lastFetchTimeMs}
259
- workflowStepNameLookup={workflowStepNameLookup}
260
- onMouseEnter={() => setHoveredTaskId(node.task.id)}
261
- onMouseLeave={() => setHoveredTaskId(null)}
262
- onClick={(event) => {
263
- event.stopPropagation();
264
- pointerDraggedRef.current = false;
265
- setSelectedTaskId((current) => (current === node.task.id ? null : node.task.id));
266
- }}
267
- />
268
- );
269
- })}
270
- </div>
271
- </div>
272
- )}
273
- </div>
274
-
275
- <GraphToolbar
276
- zoom={zoom}
277
- onZoomIn={() => {
278
- const viewport = viewportRef.current;
279
- if (!viewport) return;
280
- zoomIn(viewport.clientWidth, viewport.clientHeight);
281
- }}
282
- onZoomOut={() => {
283
- const viewport = viewportRef.current;
284
- if (!viewport) return;
285
- zoomOut(viewport.clientWidth, viewport.clientHeight);
286
- }}
287
- onFitToGraph={() => {
288
- handleResetLayout();
289
- const viewport = viewportRef.current;
290
- if (!viewport) return;
291
- const freshLayout = computeAutoLayout(graphData, { nodeWidth: NODE_WIDTH, nodeHeight: NODE_HEIGHT, horizontalGap: 40, verticalGap: 80 });
292
- fitToGraph(freshLayout, viewport.clientWidth, viewport.clientHeight, { nodeWidth: NODE_WIDTH, nodeHeight: NODE_HEIGHT });
293
- }}
294
- onResetView={() => {
295
- handleResetLayout();
296
- resetView();
297
- }}
298
- />
299
- </section>
300
- );
301
- }
@@ -1,27 +0,0 @@
1
- .graph-node--highlighted {
2
- opacity: 1;
3
- box-shadow: var(--shadow-glow);
4
- }
5
-
6
- .graph-node--dimmed {
7
- opacity: 0.25;
8
- transition: opacity var(--transition-fast);
9
- }
10
-
11
- .graph-edge--highlighted {
12
- opacity: 1;
13
- stroke: var(--todo);
14
- stroke-width: var(--space-xs);
15
- }
16
-
17
- .graph-edge--dimmed {
18
- opacity: 0.15;
19
- transition: opacity var(--transition-fast);
20
- }
21
-
22
- @media (max-width: 768px) {
23
- .graph-node--dimmed,
24
- .graph-edge--dimmed {
25
- transition: opacity var(--transition-fast);
26
- }
27
- }
@@ -1,157 +0,0 @@
1
- .graph-task-node {
2
- position: absolute;
3
- z-index: 1;
4
- width: min(100%, var(--graph-task-node-width, calc(var(--space-2xl) * 9)));
5
- max-width: var(--graph-task-node-max-width, calc(var(--space-2xl) * 9.5));
6
- transition:
7
- box-shadow var(--transition-fast),
8
- z-index var(--transition-fast),
9
- opacity var(--transition-fast),
10
- border-color var(--transition-fast);
11
- border: var(--btn-border-width) solid transparent;
12
- border-radius: var(--radius-sm);
13
- }
14
-
15
- .graph-task-node:hover {
16
- box-shadow: var(--shadow-md);
17
- z-index: 2;
18
- }
19
-
20
- .graph-task-node--highlighted {
21
- box-shadow: 0 0 0 var(--btn-border-width) var(--todo), var(--shadow-md);
22
- z-index: 2;
23
- }
24
-
25
- .graph-task-node--dimmed {
26
- opacity: 0.4;
27
- }
28
-
29
- .graph-task-node--active {
30
- border-color: rgba(var(--in-progress-rgb), 0.5);
31
- box-shadow: 0 0 0 var(--btn-border-width) rgba(var(--in-progress-rgb), 0.45), 0 0 0.75rem rgba(var(--in-progress-rgb), 0.5), 0 0 1.5rem rgba(var(--in-progress-rgb), 0.2);
32
- transform: scale(1.01);
33
- }
34
-
35
- .graph-task-node--in-review {
36
- border-inline-start: calc(var(--btn-border-width) * 3) solid var(--in-review);
37
- border-start-start-radius: var(--radius-md);
38
- border-end-start-radius: var(--radius-md);
39
- }
40
-
41
- .graph-task-active-indicator {
42
- display: flex;
43
- align-items: center;
44
- min-height: 1.25rem;
45
- width: 100%;
46
- padding-inline: var(--space-sm);
47
- background: rgba(var(--in-progress-rgb), 0.85);
48
- border-radius: var(--radius-sm) var(--radius-sm) 0 0;
49
- animation: graph-task-active-pulse calc(var(--transition-normal) * 8) ease-in-out infinite;
50
- }
51
-
52
- .graph-task-active-indicator-text {
53
- font-family: var(--font-mono);
54
- font-size: 0.625rem;
55
- font-weight: 600;
56
- line-height: 1;
57
- letter-spacing: 0.04em;
58
- text-transform: uppercase;
59
- color: var(--card);
60
- }
61
-
62
- .graph-task-node .card {
63
- width: 100%;
64
- height: 100%;
65
- }
66
-
67
- .graph-task-node[data-current-step="0"] .card-steps-list .card-step-item:nth-child(1),
68
- .graph-task-node[data-current-step="1"] .card-steps-list .card-step-item:nth-child(2),
69
- .graph-task-node[data-current-step="2"] .card-steps-list .card-step-item:nth-child(3),
70
- .graph-task-node[data-current-step="3"] .card-steps-list .card-step-item:nth-child(4),
71
- .graph-task-node[data-current-step="4"] .card-steps-list .card-step-item:nth-child(5),
72
- .graph-task-node[data-current-step="5"] .card-steps-list .card-step-item:nth-child(6),
73
- .graph-task-node[data-current-step="6"] .card-steps-list .card-step-item:nth-child(7),
74
- .graph-task-node[data-current-step="7"] .card-steps-list .card-step-item:nth-child(8),
75
- .graph-task-node[data-current-step="8"] .card-steps-list .card-step-item:nth-child(9),
76
- .graph-task-node[data-current-step="9"] .card-steps-list .card-step-item:nth-child(10),
77
- .graph-task-node[data-current-step="10"] .card-steps-list .card-step-item:nth-child(11),
78
- .graph-task-node[data-current-step="11"] .card-steps-list .card-step-item:nth-child(12),
79
- .graph-task-node[data-current-step="12"] .card-steps-list .card-step-item:nth-child(13),
80
- .graph-task-node[data-current-step="13"] .card-steps-list .card-step-item:nth-child(14),
81
- .graph-task-node[data-current-step="14"] .card-steps-list .card-step-item:nth-child(15),
82
- .graph-task-node[data-current-step="15"] .card-steps-list .card-step-item:nth-child(16),
83
- .graph-task-node[data-current-step="16"] .card-steps-list .card-step-item:nth-child(17),
84
- .graph-task-node[data-current-step="17"] .card-steps-list .card-step-item:nth-child(18),
85
- .graph-task-node[data-current-step="18"] .card-steps-list .card-step-item:nth-child(19),
86
- .graph-task-node[data-current-step="19"] .card-steps-list .card-step-item:nth-child(20) {
87
- background: rgba(var(--in-progress-rgb), 0.15);
88
- border-left: calc(var(--btn-border-width) * 2) solid var(--in-progress);
89
- padding-left: var(--space-xs);
90
- font-weight: 500;
91
- }
92
-
93
- .graph-task-node[data-current-step="0"] .card-steps-list .card-step-item:nth-child(1) .card-step-dot,
94
- .graph-task-node[data-current-step="1"] .card-steps-list .card-step-item:nth-child(2) .card-step-dot,
95
- .graph-task-node[data-current-step="2"] .card-steps-list .card-step-item:nth-child(3) .card-step-dot,
96
- .graph-task-node[data-current-step="3"] .card-steps-list .card-step-item:nth-child(4) .card-step-dot,
97
- .graph-task-node[data-current-step="4"] .card-steps-list .card-step-item:nth-child(5) .card-step-dot,
98
- .graph-task-node[data-current-step="5"] .card-steps-list .card-step-item:nth-child(6) .card-step-dot,
99
- .graph-task-node[data-current-step="6"] .card-steps-list .card-step-item:nth-child(7) .card-step-dot,
100
- .graph-task-node[data-current-step="7"] .card-steps-list .card-step-item:nth-child(8) .card-step-dot,
101
- .graph-task-node[data-current-step="8"] .card-steps-list .card-step-item:nth-child(9) .card-step-dot,
102
- .graph-task-node[data-current-step="9"] .card-steps-list .card-step-item:nth-child(10) .card-step-dot,
103
- .graph-task-node[data-current-step="10"] .card-steps-list .card-step-item:nth-child(11) .card-step-dot,
104
- .graph-task-node[data-current-step="11"] .card-steps-list .card-step-item:nth-child(12) .card-step-dot,
105
- .graph-task-node[data-current-step="12"] .card-steps-list .card-step-item:nth-child(13) .card-step-dot,
106
- .graph-task-node[data-current-step="13"] .card-steps-list .card-step-item:nth-child(14) .card-step-dot,
107
- .graph-task-node[data-current-step="14"] .card-steps-list .card-step-item:nth-child(15) .card-step-dot,
108
- .graph-task-node[data-current-step="15"] .card-steps-list .card-step-item:nth-child(16) .card-step-dot,
109
- .graph-task-node[data-current-step="16"] .card-steps-list .card-step-item:nth-child(17) .card-step-dot,
110
- .graph-task-node[data-current-step="17"] .card-steps-list .card-step-item:nth-child(18) .card-step-dot,
111
- .graph-task-node[data-current-step="18"] .card-steps-list .card-step-item:nth-child(19) .card-step-dot,
112
- .graph-task-node[data-current-step="19"] .card-steps-list .card-step-item:nth-child(20) .card-step-dot {
113
- width: 0.5rem;
114
- height: 0.5rem;
115
- animation: graph-task-step-pulse calc(var(--transition-normal) * 10) ease-in-out infinite;
116
- }
117
-
118
- @keyframes graph-task-active-pulse {
119
- 0%,
120
- 100% {
121
- background: rgba(var(--in-progress-rgb), 0.85);
122
- }
123
- 50% {
124
- background: rgba(var(--in-progress-rgb), 0.65);
125
- }
126
- }
127
-
128
- @keyframes graph-task-step-pulse {
129
- 0%,
130
- 100% {
131
- transform: scale(1);
132
- opacity: 1;
133
- }
134
- 50% {
135
- transform: scale(1.2);
136
- opacity: 0.75;
137
- }
138
- }
139
-
140
- @media (max-width: 768px) {
141
- .graph-task-node {
142
- width: min(100%, var(--graph-task-node-mobile-width, calc(var(--space-2xl) * 8)));
143
- }
144
-
145
- .graph-task-node--active {
146
- transform: scale(1.005);
147
- }
148
-
149
- .graph-task-active-indicator {
150
- min-height: 1.5rem;
151
- padding-inline: var(--space-md);
152
- }
153
-
154
- .graph-task-active-indicator-text {
155
- font-size: 0.6875rem;
156
- }
157
- }
@@ -1,126 +0,0 @@
1
- import type { CSSProperties, ComponentProps, HTMLAttributes } from "react";
2
- import type { GraphPosition } from "./types";
3
- import { useNodeDrag } from "./hooks/useNodeDrag";
4
- import { TaskCard } from "@fusion/dashboard/app/components/TaskCard";
5
- import { isTaskStuck } from "@fusion/dashboard/app/utils/taskStuck";
6
- import "./GraphTaskNode.css";
7
- import "./GraphHighlight.css";
8
- import "./styles/drag.css";
9
-
10
- type TaskCardComponentProps = ComponentProps<typeof TaskCard>;
11
-
12
- type TaskCardBridgeProps = Pick<
13
- TaskCardComponentProps,
14
- | "task"
15
- | "projectId"
16
- | "onOpenDetail"
17
- | "addToast"
18
- | "globalPaused"
19
- | "onUpdateTask"
20
- | "onArchiveTask"
21
- | "onUnarchiveTask"
22
- | "onDeleteTask"
23
- | "onRetryTask"
24
- | "onOpenDetailWithTab"
25
- | "taskStuckTimeoutMs"
26
- | "onOpenMission"
27
- | "onMoveTask"
28
- | "lastFetchTimeMs"
29
- | "workflowStepNameLookup"
30
- >;
31
-
32
- export interface GraphTaskNodeProps extends TaskCardBridgeProps, Pick<HTMLAttributes<HTMLDivElement>, "onMouseEnter" | "onMouseLeave" | "onClick"> {
33
- style?: CSSProperties;
34
- position: GraphPosition;
35
- scale: number;
36
- isHighlighted?: boolean;
37
- isDimmed?: boolean;
38
- onNodePositionChange: (taskId: string, position: GraphPosition) => void;
39
- onNodeDragStateChange?: (isDragging: boolean) => void;
40
- onNodeDragEnd?: () => void;
41
- }
42
-
43
- const ACTIVE_STATUSES = new Set(["planning", "researching", "executing", "finalizing", "merging", "merging-fix"]);
44
-
45
- function getStatusLabel(status?: string): string {
46
- if (!status) {
47
- return "Executing";
48
- }
49
-
50
- return status.charAt(0).toUpperCase() + status.slice(1);
51
- }
52
-
53
- export function GraphTaskNode({
54
- style,
55
- position,
56
- scale,
57
- isHighlighted = false,
58
- isDimmed = false,
59
- onMouseEnter,
60
- onMouseLeave,
61
- onClick,
62
- onNodePositionChange,
63
- onNodeDragStateChange,
64
- onNodeDragEnd,
65
- ...taskCardProps
66
- }: GraphTaskNodeProps) {
67
- const { task, globalPaused, taskStuckTimeoutMs, lastFetchTimeMs, onOpenDetail } = taskCardProps;
68
- const isFailed = task.status === "failed";
69
- const isPaused = task.paused === true;
70
- const isStuck = isTaskStuck(task, taskStuckTimeoutMs, lastFetchTimeMs);
71
- const isAwaitingApproval = task.column === "triage" && task.status === "awaiting-approval";
72
- const isActive =
73
- !globalPaused &&
74
- !isFailed &&
75
- !isPaused &&
76
- !isStuck &&
77
- !isAwaitingApproval &&
78
- (task.column === "in-progress" || ACTIVE_STATUSES.has(task.status as string));
79
-
80
- const hasValidCurrentStep =
81
- typeof task.currentStep === "number" &&
82
- task.currentStep >= 0 &&
83
- Array.isArray(task.steps) &&
84
- task.currentStep < task.steps.length;
85
- const isInReview = task.column === "in-review";
86
-
87
- const drag = useNodeDrag({
88
- taskId: task.id,
89
- position,
90
- scale,
91
- onPositionChange: onNodePositionChange,
92
- onDragStateChange: onNodeDragStateChange,
93
- onDragEnd: onNodeDragEnd,
94
- });
95
-
96
- return (
97
- <div
98
- className={`graph-task-node graph-node--draggable${drag.isDragging ? " graph-node--dragging" : ""}${isHighlighted ? " graph-task-node--highlighted graph-node--highlighted" : ""}${isDimmed ? " graph-task-node--dimmed graph-node--dimmed" : ""}${isActive ? " graph-task-node--active" : ""}${isInReview ? " graph-task-node--in-review" : ""}`}
99
- style={style}
100
- draggable={false}
101
- data-testid={`graph-task-node-${task.id}`}
102
- data-current-step={isActive && hasValidCurrentStep ? String(task.currentStep) : undefined}
103
- onMouseEnter={onMouseEnter}
104
- onMouseLeave={onMouseLeave}
105
- onClick={(event) => {
106
- onClick?.(event);
107
- if (event.defaultPrevented) {
108
- return;
109
- }
110
- onOpenDetail(task);
111
- }}
112
- onClickCapture={drag.onClickCapture}
113
- onPointerDown={drag.onPointerDown}
114
- onPointerMove={drag.onPointerMove}
115
- onPointerUp={drag.onPointerUp}
116
- onPointerCancel={drag.onPointerCancel}
117
- >
118
- {isActive ? (
119
- <div className="graph-task-active-indicator">
120
- <span className="graph-task-active-indicator-text">{getStatusLabel(task.status)}</span>
121
- </div>
122
- ) : null}
123
- <TaskCard {...taskCardProps} onOpenDetail={() => {}} disableDrag={true} />
124
- </div>
125
- );
126
- }