@runfusion/fusion 0.16.0 → 0.17.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.
- package/README.md +3 -19
- package/dist/bin.js +3344 -1159
- package/dist/client/assets/{AgentDetailView-KQz9dg0n.js → AgentDetailView-DGqT1oDt.js} +2 -2
- package/dist/client/assets/AgentDetailView-yu8Xltqk.css +1 -0
- package/dist/client/assets/AgentsView-BmemrfrO.js +517 -0
- package/dist/client/assets/AgentsView-Bs03ptrd.css +1 -0
- package/dist/client/assets/ChatView-CZQUBFlV.js +1 -0
- package/dist/client/assets/DevServerView-C3Q0XqDA.js +1 -0
- package/dist/client/assets/{DirectoryPicker-CFj7FOgn.js → DirectoryPicker-BZWVA9ND.js} +1 -1
- package/dist/client/assets/DocumentsView-DO48ivSq.js +1 -0
- package/dist/client/assets/InsightsView-CAngTfMf.js +11 -0
- package/dist/client/assets/{MemoryView-DDbr1DaJ.js → MemoryView-B3rNcAOW.js} +1 -1
- package/dist/client/assets/NodesView-BnV1LWa8.js +14 -0
- package/dist/client/assets/{NodesView-BprfihLf.css → NodesView-DuAXX_0j.css} +1 -1
- package/dist/client/assets/{PiExtensionsManager-QgzWFBAT.js → PiExtensionsManager-C3_Lw4sa.js} +3 -3
- package/dist/client/assets/{PluginManager-KHiz9oLY.js → PluginManager-Vv3nzrJ1.js} +1 -1
- package/dist/client/assets/ResearchView-BzCcDAS4.css +1 -0
- package/dist/client/assets/ResearchView-Dfdsuc21.js +1 -0
- package/dist/client/assets/{RoadmapsView-BlbAZTdi.js → RoadmapsView-BiIpE-b8.js} +2 -2
- package/dist/client/assets/SettingsModal-BN00HYJ2.js +31 -0
- package/dist/client/assets/{SettingsModal-D5EUFR2z.js → SettingsModal-CK4w8Ztb.js} +1 -1
- package/dist/client/assets/{SettingsModal-D0QA2W5K.css → SettingsModal-Dq4a5KSX.css} +1 -1
- package/dist/client/assets/{SetupWizardModal-tTXAm1Wb.js → SetupWizardModal-Dw6N4UvY.js} +1 -1
- package/dist/client/assets/SkillsView-C1196wgA.js +1 -0
- package/dist/client/assets/{folder-open-DObdkm5J.js → folder-open-WVtgE4k3.js} +1 -1
- package/dist/client/assets/index-BIJgrHEn.css +1 -0
- package/dist/client/assets/index-Bv0TGzDH.js +682 -0
- package/dist/client/assets/{star-BkGA2L-k.js → star-MSImEC8V.js} +1 -1
- package/dist/client/assets/{upload-B0NF4J5P.js → upload-Dmvy3xXd.js} +1 -1
- package/dist/client/assets/{users-DgomiHTd.js → users-CncYvHNf.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/version.json +1 -1
- package/dist/extension.js +2228 -715
- package/dist/pi-claude-cli/package.json +1 -1
- package/dist/pi-claude-cli/src/__tests__/process-manager.test.ts +11 -0
- package/dist/pi-claude-cli/src/__tests__/provider.test.ts +25 -0
- package/dist/plugins/fusion-plugin-dependency-graph/package.json +7 -5
- package/dist/plugins/fusion-plugin-dependency-graph/src/DependencyGraphView.css +31 -0
- package/dist/plugins/fusion-plugin-dependency-graph/src/DependencyGraphView.tsx +156 -48
- package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/DependencyGraphView.test.tsx +261 -0
- package/package.json +1 -1
- package/skill/fusion/SKILL.md +5 -5
- package/skill/fusion/references/engine-tools.md +2 -2
- package/skill/fusion/references/extension-tools.md +3 -3
- package/skill/fusion/references/fusion-capabilities.md +1 -1
- package/skill/fusion/references/skill-patterns.md +1 -1
- package/skill/fusion/workflows/dashboard-cli.md +3 -3
- package/skill/fusion/workflows/task-management.md +1 -1
- package/dist/client/assets/AgentDetailView-BoEpPOjM.css +0 -1
- package/dist/client/assets/AgentsView-8Xo-c04o.js +0 -517
- package/dist/client/assets/AgentsView-V5GhlBYu.css +0 -1
- package/dist/client/assets/ChatView-CjgOh8x7.js +0 -1
- package/dist/client/assets/DevServerView-BOPrzi-j.js +0 -1
- package/dist/client/assets/DocumentsView-CcJNmH06.js +0 -1
- package/dist/client/assets/InsightsView-B0DKRIYV.js +0 -11
- package/dist/client/assets/NodesView-BqtHfXsl.js +0 -14
- package/dist/client/assets/ResearchView-CVxPC1vz.css +0 -1
- package/dist/client/assets/ResearchView-s3SrjNBm.js +0 -1
- package/dist/client/assets/SettingsModal-c9MG4sxl.js +0 -31
- package/dist/client/assets/SkillsView-CS4ONN3D.js +0 -1
- package/dist/client/assets/index-BRaIPmpp.js +0 -682
- package/dist/client/assets/index-DeED_ky2.css +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fusion/pi-claude-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Fusion vendored fork: pi coding-agent extension that routes LLM calls through the Claude Code CLI. Forked from rchern/pi-claude-cli (MIT). See UPSTREAM.md.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": true,
|
|
@@ -713,6 +713,17 @@ describe("resume session flag", () => {
|
|
|
713
713
|
expect(args).not.toContain("--resume");
|
|
714
714
|
});
|
|
715
715
|
|
|
716
|
+
it("does not include --session-id when --resume is provided", () => {
|
|
717
|
+
spawnClaude("claude-sonnet-4-5-20250929", undefined, {
|
|
718
|
+
resumeSessionId: "session-abc",
|
|
719
|
+
newSessionId: "session-new",
|
|
720
|
+
});
|
|
721
|
+
const args = (spawn as any).mock.calls[0][1] as string[];
|
|
722
|
+
|
|
723
|
+
expect(args).toContain("--resume");
|
|
724
|
+
expect(args).not.toContain("--session-id");
|
|
725
|
+
});
|
|
726
|
+
|
|
716
727
|
it("includes both --resume and --effort when both are provided", () => {
|
|
717
728
|
spawnClaude("claude-sonnet-4-5-20250929", undefined, {
|
|
718
729
|
resumeSessionId: "session-abc",
|
|
@@ -1512,6 +1512,31 @@ describe("streamViaCli", { timeout: 90_000 }, () => {
|
|
|
1512
1512
|
});
|
|
1513
1513
|
});
|
|
1514
1514
|
|
|
1515
|
+
describe("resume behavior", () => {
|
|
1516
|
+
it("uses --resume for follow-up quick-chat turns when sessionId is present", async () => {
|
|
1517
|
+
const model = mockModels[0] as any;
|
|
1518
|
+
const context = {
|
|
1519
|
+
messages: [
|
|
1520
|
+
{ role: "user", content: "Turn 1" },
|
|
1521
|
+
{ role: "assistant", content: "Reply 1" },
|
|
1522
|
+
{ role: "user", content: "Follow-up" },
|
|
1523
|
+
],
|
|
1524
|
+
};
|
|
1525
|
+
|
|
1526
|
+
streamViaCli(model, context, { sessionId: "session-follow-up" } as any);
|
|
1527
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
1528
|
+
|
|
1529
|
+
const args = (spawn as any).mock.calls[0][1] as string[];
|
|
1530
|
+
expect(args).toContain("--resume");
|
|
1531
|
+
expect(args).toContain("session-follow-up");
|
|
1532
|
+
expect(args).not.toContain("--session-id");
|
|
1533
|
+
|
|
1534
|
+
const proc = (spawn as any).mock.results[0].value;
|
|
1535
|
+
proc.stdout.end();
|
|
1536
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
1537
|
+
});
|
|
1538
|
+
});
|
|
1539
|
+
|
|
1515
1540
|
describe("MCP config with custom tool results", () => {
|
|
1516
1541
|
it("keeps MCP config even when conversation ends with custom tool result", async () => {
|
|
1517
1542
|
const model = mockModels[0] as any;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fusion-plugin-examples/dependency-graph",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Dependency graph dashboard view plugin for Fusion",
|
|
6
6
|
"private": true,
|
|
@@ -19,14 +19,16 @@
|
|
|
19
19
|
"test": "vitest run --silent=passed-only --reporter=dot"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@fusion/
|
|
23
|
-
"@fusion/
|
|
22
|
+
"@fusion/core": "workspace:*",
|
|
23
|
+
"@fusion/plugin-sdk": "workspace:*"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
+
"@testing-library/react": "^16.3.2",
|
|
26
27
|
"@types/node": "^25.5.2",
|
|
27
28
|
"@types/react": "^19.0.0",
|
|
29
|
+
"react": "^19.0.0",
|
|
30
|
+
"react-dom": "^19.2.4",
|
|
28
31
|
"typescript": "^5.7.0",
|
|
29
|
-
"vitest": "^3.2.4"
|
|
30
|
-
"react": "^19.0.0"
|
|
32
|
+
"vitest": "^3.2.4"
|
|
31
33
|
}
|
|
32
34
|
}
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
background: var(--surface);
|
|
24
24
|
min-height: var(--dependency-graph-canvas-min-height);
|
|
25
25
|
cursor: grab;
|
|
26
|
+
touch-action: none;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
.dependency-graph-canvas:active {
|
|
@@ -86,16 +87,46 @@
|
|
|
86
87
|
filter: saturate(0.8);
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
.dependency-graph-empty {
|
|
91
|
+
display: flex;
|
|
92
|
+
align-items: center;
|
|
93
|
+
justify-content: center;
|
|
94
|
+
height: 100%;
|
|
95
|
+
min-height: var(--dependency-graph-canvas-min-height);
|
|
96
|
+
padding: var(--space-xl);
|
|
97
|
+
color: var(--text-muted);
|
|
98
|
+
text-align: center;
|
|
99
|
+
}
|
|
100
|
+
|
|
89
101
|
@media (max-width: 768px) {
|
|
90
102
|
.dependency-graph-view {
|
|
91
103
|
padding: var(--space-md);
|
|
92
104
|
}
|
|
93
105
|
|
|
106
|
+
.dependency-graph-controls {
|
|
107
|
+
position: sticky;
|
|
108
|
+
top: 0;
|
|
109
|
+
z-index: 1;
|
|
110
|
+
background: var(--bg);
|
|
111
|
+
padding: var(--space-sm) 0;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.dependency-graph-controls .btn {
|
|
115
|
+
min-height: 44px;
|
|
116
|
+
min-width: 44px;
|
|
117
|
+
}
|
|
118
|
+
|
|
94
119
|
.dependency-graph-canvas {
|
|
120
|
+
flex: 1;
|
|
121
|
+
min-height: 0;
|
|
95
122
|
min-height: var(--dependency-graph-canvas-min-height-mobile);
|
|
96
123
|
}
|
|
97
124
|
|
|
98
125
|
.dependency-graph-node {
|
|
99
126
|
width: min(100%, var(--dependency-graph-node-max-width-mobile)) !important;
|
|
100
127
|
}
|
|
128
|
+
|
|
129
|
+
.dependency-graph-empty {
|
|
130
|
+
min-height: var(--dependency-graph-canvas-min-height-mobile);
|
|
131
|
+
}
|
|
101
132
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useMemo, useRef, useState } from "react";
|
|
2
|
-
import type { PointerEvent as ReactPointerEvent, ReactNode } from "react";
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import type { PointerEvent as ReactPointerEvent, ReactNode, WheelEvent as ReactWheelEvent } from "react";
|
|
3
3
|
import type { Task } from "@fusion/core";
|
|
4
4
|
import { loadPositions, savePositions } from "./storage";
|
|
5
5
|
import "./DependencyGraphView.css";
|
|
@@ -14,6 +14,8 @@ const SCENE_PADDING_REM = 2;
|
|
|
14
14
|
const FIT_PADDING_REM = 2;
|
|
15
15
|
const MIN_SCALE = 0.4;
|
|
16
16
|
const MAX_SCALE = 2;
|
|
17
|
+
const WHEEL_ZOOM_FACTOR = 0.002;
|
|
18
|
+
const MOBILE_BREAKPOINT = 768;
|
|
17
19
|
|
|
18
20
|
export interface DependencyGraphHostContext {
|
|
19
21
|
projectId?: string;
|
|
@@ -29,9 +31,12 @@ export interface PluginDashboardViewComponentProps {
|
|
|
29
31
|
type Position = { x: number; y: number };
|
|
30
32
|
|
|
31
33
|
function getDistance(a: Position, b: Position): number {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
return Math.hypot(a.x - b.x, a.y - b.y);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isMobileViewport(): boolean {
|
|
38
|
+
if (typeof window === "undefined") return false;
|
|
39
|
+
return window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT}px)`).matches;
|
|
35
40
|
}
|
|
36
41
|
|
|
37
42
|
export function DependencyGraphView({ context }: PluginDashboardViewComponentProps) {
|
|
@@ -42,11 +47,16 @@ export function DependencyGraphView({ context }: PluginDashboardViewComponentPro
|
|
|
42
47
|
const [hoveredTaskId, setHoveredTaskId] = useState<string | null>(null);
|
|
43
48
|
const persisted = useMemo(() => loadPositions(context.projectId), [context.projectId]);
|
|
44
49
|
const canvasRef = useRef<HTMLDivElement | null>(null);
|
|
50
|
+
|
|
51
|
+
// Multi-pointer tracking (no setPointerCapture — it breaks two-finger gestures)
|
|
52
|
+
const pointersRef = useRef<Map<number, Position>>(new Map());
|
|
45
53
|
const interactionRef = useRef<
|
|
46
54
|
| { kind: "node"; taskId: string; startPointer: Position; startNode: Position; moved: boolean }
|
|
47
55
|
| { kind: "pan"; startPointer: Position; startPan: Position; moved: boolean }
|
|
48
56
|
| null
|
|
49
57
|
>(null);
|
|
58
|
+
const pinchRef = useRef<{ startDistance: number; startScale: number } | null>(null);
|
|
59
|
+
const autoFitDoneRef = useRef(false);
|
|
50
60
|
|
|
51
61
|
const tasks = useMemo(
|
|
52
62
|
() => context.tasks.filter((task) => ACTIVE_COLUMNS.has(task.column)),
|
|
@@ -162,7 +172,7 @@ export function DependencyGraphView({ context }: PluginDashboardViewComponentPro
|
|
|
162
172
|
return related;
|
|
163
173
|
}, [dependencyGraph.downstream, dependencyGraph.upstream, focusTaskId]);
|
|
164
174
|
|
|
165
|
-
const fitToGraph = () => {
|
|
175
|
+
const fitToGraph = useCallback(() => {
|
|
166
176
|
const canvas = canvasRef.current;
|
|
167
177
|
if (!canvas) return;
|
|
168
178
|
|
|
@@ -179,7 +189,26 @@ export function DependencyGraphView({ context }: PluginDashboardViewComponentPro
|
|
|
179
189
|
|
|
180
190
|
setScale(nextScale);
|
|
181
191
|
setPan({ x: centeredPanX, y: centeredPanY });
|
|
182
|
-
};
|
|
192
|
+
}, [bounds.width, bounds.height]);
|
|
193
|
+
|
|
194
|
+
// Auto-fit on initial mobile load
|
|
195
|
+
useEffect(() => {
|
|
196
|
+
if (autoFitDoneRef.current) return;
|
|
197
|
+
if (!isMobileViewport()) return;
|
|
198
|
+
if (positioned.length === 0) return;
|
|
199
|
+
|
|
200
|
+
const canvas = canvasRef.current;
|
|
201
|
+
if (!canvas) return;
|
|
202
|
+
|
|
203
|
+
// Ensure the canvas has non-zero dimensions before fitting
|
|
204
|
+
if (canvas.clientWidth === 0 || canvas.clientHeight === 0) return;
|
|
205
|
+
|
|
206
|
+
autoFitDoneRef.current = true;
|
|
207
|
+
// Use rAF to ensure layout is settled
|
|
208
|
+
requestAnimationFrame(() => {
|
|
209
|
+
fitToGraph();
|
|
210
|
+
});
|
|
211
|
+
}, [fitToGraph, positioned.length]);
|
|
183
212
|
|
|
184
213
|
const persistPosition = (taskId: string, next: Position) => {
|
|
185
214
|
setNodeOverrides((current) => ({ ...current, [taskId]: next }));
|
|
@@ -193,6 +222,10 @@ export function DependencyGraphView({ context }: PluginDashboardViewComponentPro
|
|
|
193
222
|
event.stopPropagation();
|
|
194
223
|
const hit = map.get(taskId);
|
|
195
224
|
if (!hit) return;
|
|
225
|
+
|
|
226
|
+
// Track this pointer in the global map
|
|
227
|
+
pointersRef.current.set(event.pointerId, { x: event.clientX, y: event.clientY });
|
|
228
|
+
|
|
196
229
|
interactionRef.current = {
|
|
197
230
|
kind: "node",
|
|
198
231
|
taskId,
|
|
@@ -200,21 +233,48 @@ export function DependencyGraphView({ context }: PluginDashboardViewComponentPro
|
|
|
200
233
|
startNode: { x: hit.x, y: hit.y },
|
|
201
234
|
moved: false,
|
|
202
235
|
};
|
|
203
|
-
event.currentTarget.setPointerCapture(event.pointerId);
|
|
204
236
|
};
|
|
205
237
|
|
|
206
238
|
const handleCanvasPointerDown = (event: ReactPointerEvent<HTMLDivElement>) => {
|
|
207
239
|
if (event.button !== 0) return;
|
|
240
|
+
|
|
241
|
+
// Track this pointer
|
|
242
|
+
pointersRef.current.set(event.pointerId, { x: event.clientX, y: event.clientY });
|
|
243
|
+
|
|
244
|
+
// If we already have another pointer, this is the start of a pinch gesture
|
|
245
|
+
if (pointersRef.current.size === 2) {
|
|
246
|
+
// Cancel any ongoing pan interaction
|
|
247
|
+
interactionRef.current = null;
|
|
248
|
+
const [p1, p2] = Array.from(pointersRef.current.values());
|
|
249
|
+
const distance = getDistance(p1, p2);
|
|
250
|
+
pinchRef.current = { startDistance: distance, startScale: scale };
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
208
254
|
interactionRef.current = {
|
|
209
255
|
kind: "pan",
|
|
210
256
|
startPointer: { x: event.clientX, y: event.clientY },
|
|
211
257
|
startPan: pan,
|
|
212
258
|
moved: false,
|
|
213
259
|
};
|
|
214
|
-
event.currentTarget.setPointerCapture(event.pointerId);
|
|
215
260
|
};
|
|
216
261
|
|
|
217
262
|
const handlePointerMove = (event: ReactPointerEvent<HTMLDivElement>) => {
|
|
263
|
+
// Update tracked pointer position
|
|
264
|
+
if (pointersRef.current.has(event.pointerId)) {
|
|
265
|
+
pointersRef.current.set(event.pointerId, { x: event.clientX, y: event.clientY });
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Handle pinch gesture when two pointers are active
|
|
269
|
+
if (pointersRef.current.size >= 2 && pinchRef.current) {
|
|
270
|
+
const [p1, p2] = Array.from(pointersRef.current.values());
|
|
271
|
+
const currentDistance = getDistance(p1, p2);
|
|
272
|
+
const scaleFactor = currentDistance / pinchRef.current.startDistance;
|
|
273
|
+
const newScale = Math.min(MAX_SCALE, Math.max(MIN_SCALE, pinchRef.current.startScale * scaleFactor));
|
|
274
|
+
setScale(newScale);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
218
278
|
const current = interactionRef.current;
|
|
219
279
|
if (!current) return;
|
|
220
280
|
|
|
@@ -241,6 +301,16 @@ export function DependencyGraphView({ context }: PluginDashboardViewComponentPro
|
|
|
241
301
|
};
|
|
242
302
|
|
|
243
303
|
const handlePointerUp = (event: ReactPointerEvent<HTMLDivElement>) => {
|
|
304
|
+
pointersRef.current.delete(event.pointerId);
|
|
305
|
+
|
|
306
|
+
// If we had a pinch and one finger remains, end pinch mode
|
|
307
|
+
if (pinchRef.current) {
|
|
308
|
+
if (pointersRef.current.size < 2) {
|
|
309
|
+
pinchRef.current = null;
|
|
310
|
+
}
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
244
314
|
const current = interactionRef.current;
|
|
245
315
|
interactionRef.current = null;
|
|
246
316
|
if (!current) return;
|
|
@@ -256,8 +326,38 @@ export function DependencyGraphView({ context }: PluginDashboardViewComponentPro
|
|
|
256
326
|
|
|
257
327
|
persistPosition(current.taskId, { x: hit.x, y: hit.y });
|
|
258
328
|
}
|
|
329
|
+
};
|
|
259
330
|
|
|
260
|
-
|
|
331
|
+
const handlePointerCancel = (event: ReactPointerEvent<HTMLDivElement>) => {
|
|
332
|
+
pointersRef.current.delete(event.pointerId);
|
|
333
|
+
pinchRef.current = null;
|
|
334
|
+
interactionRef.current = null;
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const handleWheel = (event: ReactWheelEvent<HTMLDivElement>) => {
|
|
338
|
+
event.preventDefault();
|
|
339
|
+
const canvas = canvasRef.current;
|
|
340
|
+
if (!canvas) return;
|
|
341
|
+
|
|
342
|
+
const rootFontSize = Number.parseFloat(globalThis.getComputedStyle(document.documentElement).fontSize) || 16;
|
|
343
|
+
const delta = -event.deltaY * WHEEL_ZOOM_FACTOR;
|
|
344
|
+
const newScale = Math.min(MAX_SCALE, Math.max(MIN_SCALE, scale * (1 + delta)));
|
|
345
|
+
|
|
346
|
+
// Zoom toward the pointer position
|
|
347
|
+
const rect = canvas.getBoundingClientRect();
|
|
348
|
+
const pointerX = event.clientX - rect.left;
|
|
349
|
+
const pointerY = event.clientY - rect.top;
|
|
350
|
+
|
|
351
|
+
// How much the point under the cursor should shift in rem
|
|
352
|
+
const scaleRatio = newScale / scale;
|
|
353
|
+
const panOffsetX = (pointerX / rootFontSize) * (1 - scaleRatio) / scale;
|
|
354
|
+
const panOffsetY = (pointerY / rootFontSize) * (1 - scaleRatio) / scale;
|
|
355
|
+
|
|
356
|
+
setScale(newScale);
|
|
357
|
+
setPan((prev) => ({
|
|
358
|
+
x: prev.x + panOffsetX * newScale / scale,
|
|
359
|
+
y: prev.y + panOffsetY * newScale / scale,
|
|
360
|
+
}));
|
|
261
361
|
};
|
|
262
362
|
|
|
263
363
|
return (
|
|
@@ -274,46 +374,54 @@ export function DependencyGraphView({ context }: PluginDashboardViewComponentPro
|
|
|
274
374
|
onPointerDown={handleCanvasPointerDown}
|
|
275
375
|
onPointerMove={handlePointerMove}
|
|
276
376
|
onPointerUp={handlePointerUp}
|
|
377
|
+
onPointerCancel={handlePointerCancel}
|
|
378
|
+
onWheel={handleWheel}
|
|
277
379
|
>
|
|
278
|
-
|
|
279
|
-
className="dependency-graph-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
380
|
+
{tasks.length === 0 ? (
|
|
381
|
+
<div className="dependency-graph-empty">
|
|
382
|
+
<p>No tasks to display. Tasks in Triage, Todo, In Progress, or In Review columns will appear here.</p>
|
|
383
|
+
</div>
|
|
384
|
+
) : (
|
|
385
|
+
<div
|
|
386
|
+
className="dependency-graph-scene"
|
|
387
|
+
style={{
|
|
388
|
+
width: `${bounds.width}rem`,
|
|
389
|
+
height: `${bounds.height}rem`,
|
|
390
|
+
transform: `translate(${pan.x}rem, ${pan.y}rem) scale(${scale})`,
|
|
391
|
+
transformOrigin: "top left",
|
|
392
|
+
}}
|
|
393
|
+
>
|
|
394
|
+
<svg className="dependency-graph-edges" viewBox={`0 0 ${bounds.width} ${bounds.height}`}>
|
|
395
|
+
{edgesForRender.map((edge) => (
|
|
396
|
+
<line
|
|
397
|
+
key={`${edge.from}-${edge.to}`}
|
|
398
|
+
x1={edge.renderX1}
|
|
399
|
+
y1={edge.renderY1}
|
|
400
|
+
x2={edge.renderX2}
|
|
401
|
+
y2={edge.renderY2}
|
|
402
|
+
className={`dependency-graph-edge${relatedTaskIds ? relatedTaskIds.has(edge.from) && relatedTaskIds.has(edge.to) ? " is-related" : " is-dimmed" : ""}`}
|
|
403
|
+
/>
|
|
404
|
+
))}
|
|
405
|
+
</svg>
|
|
406
|
+
|
|
407
|
+
{positionedForRender.map((node) => (
|
|
408
|
+
<div
|
|
409
|
+
key={node.task.id}
|
|
410
|
+
className={`dependency-graph-node${selectedTaskId === node.task.id ? " is-selected" : ""}${relatedTaskIds ? relatedTaskIds.has(node.task.id) ? " is-related" : " is-dimmed" : ""}`}
|
|
411
|
+
style={{
|
|
412
|
+
width: `${NODE_WIDTH_REM}rem`,
|
|
413
|
+
minHeight: `${NODE_HEIGHT_REM}rem`,
|
|
414
|
+
transform: `translate(${node.renderX}rem, ${node.renderY}rem)`,
|
|
415
|
+
}}
|
|
416
|
+
onPointerDown={(event) => handlePointerDownOnNode(node.task.id, event)}
|
|
417
|
+
onPointerEnter={() => setHoveredTaskId(node.task.id)}
|
|
418
|
+
onPointerLeave={() => setHoveredTaskId((current) => (current === node.task.id ? null : current))}
|
|
419
|
+
>
|
|
420
|
+
{context.renderTaskCard(node.task)}
|
|
421
|
+
</div>
|
|
297
422
|
))}
|
|
298
|
-
</
|
|
299
|
-
|
|
300
|
-
{positionedForRender.map((node) => (
|
|
301
|
-
<div
|
|
302
|
-
key={node.task.id}
|
|
303
|
-
className={`dependency-graph-node${selectedTaskId === node.task.id ? " is-selected" : ""}${relatedTaskIds ? relatedTaskIds.has(node.task.id) ? " is-related" : " is-dimmed" : ""}`}
|
|
304
|
-
style={{
|
|
305
|
-
width: `${NODE_WIDTH_REM}rem`,
|
|
306
|
-
minHeight: `${NODE_HEIGHT_REM}rem`,
|
|
307
|
-
transform: `translate(${node.renderX}rem, ${node.renderY}rem)`,
|
|
308
|
-
}}
|
|
309
|
-
onPointerDown={(event) => handlePointerDownOnNode(node.task.id, event)}
|
|
310
|
-
onPointerEnter={() => setHoveredTaskId(node.task.id)}
|
|
311
|
-
onPointerLeave={() => setHoveredTaskId((current) => (current === node.task.id ? null : current))}
|
|
312
|
-
>
|
|
313
|
-
{context.renderTaskCard(node.task)}
|
|
314
|
-
</div>
|
|
315
|
-
))}
|
|
316
|
-
</div>
|
|
423
|
+
</div>
|
|
424
|
+
)}
|
|
317
425
|
</div>
|
|
318
426
|
</section>
|
|
319
427
|
);
|