@runfusion/fusion 0.24.0 → 0.26.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 (167) hide show
  1. package/README.md +6 -0
  2. package/dist/bin.js +18646 -15669
  3. package/dist/client/assets/AgentDetailView-Cv-vgOj3.js +18 -0
  4. package/dist/client/assets/{AgentsView-BkB9FiMT.js → AgentsView-D6Zi5zfP.js} +4 -4
  5. package/dist/client/assets/ChatView-CAHjY9uO.js +1 -0
  6. package/dist/client/assets/{DevServerView-BkvtjZBa.js → DevServerView--_WBvIDQ.js} +1 -1
  7. package/dist/client/assets/{DirectoryPicker-BK-KbnhP.js → DirectoryPicker-xedtR-Rd.js} +1 -1
  8. package/dist/client/assets/{DocumentsView-BEg1CQAk.js → DocumentsView-Bg2oaZks.js} +1 -1
  9. package/dist/client/assets/{EvalsView-Berf9bQm.js → EvalsView-B3uOCXfr.js} +1 -1
  10. package/dist/client/assets/{ExperimentalAgentOnboardingModal-jcInE50G.js → ExperimentalAgentOnboardingModal-Bx6yXVS5.js} +1 -1
  11. package/dist/client/assets/{InsightsView-BX5bSF1J.js → InsightsView-Q1zvtF4F.js} +1 -1
  12. package/dist/client/assets/MemoryView-xcN_eouf.js +2 -0
  13. package/dist/client/assets/MemoryView-zaXewZzi.css +1 -0
  14. package/dist/client/assets/{NodesView-DLUOBLf6.js → NodesView-RxXg58_Q.js} +1 -1
  15. package/dist/client/assets/{PiExtensionsManager-COlJf0Kx.js → PiExtensionsManager-Cc8aAZXg.js} +2 -2
  16. package/dist/client/assets/PluginManager-BEkyBajl.js +1 -0
  17. package/dist/client/assets/{ResearchView-BzCcDAS4.css → ResearchView-BEI4ZSGs.css} +1 -1
  18. package/dist/client/assets/ResearchView-CERNf7sJ.js +1 -0
  19. package/dist/client/assets/{SettingsModal-yRqM4DV8.js → SettingsModal-B1r0yASu.js} +1 -1
  20. package/dist/client/assets/SettingsModal-BLsac7CJ.js +31 -0
  21. package/dist/client/assets/SettingsModal-Cis-4Lot.css +1 -0
  22. package/dist/client/assets/{SetupWizardModal-uUZk3TKT.js → SetupWizardModal-D1q548_L.js} +1 -1
  23. package/dist/client/assets/{SkillsView-CP8JX0P_.js → SkillsView-ClLM6u6p.js} +1 -1
  24. package/dist/client/assets/StashRecoveryView-B_8WIQEo.css +1 -0
  25. package/dist/client/assets/StashRecoveryView-ze0pEZ5U.js +1 -0
  26. package/dist/client/assets/{TodoView-DCRIkDZ-.js → TodoView-CTmIfy2M.js} +1 -1
  27. package/dist/client/assets/{dashboard-view-lR7YYmSC.js → dashboard-view-4xAN3yO5.js} +2 -2
  28. package/dist/client/assets/{folder-open-DHjELt8-.js → folder-open-BZuKESeq.js} +1 -1
  29. package/dist/client/assets/index-Bdw6llW6.js +692 -0
  30. package/dist/client/assets/index-CZGlyJuS.css +1 -0
  31. package/dist/client/assets/{star-DYesq1AV.js → star-D75YKEq-.js} +1 -1
  32. package/dist/client/assets/{upload-DTWF3Db5.js → upload-BYYTgWFj.js} +1 -1
  33. package/dist/client/assets/{users--syrel4l.js → users-RS90Aii3.js} +1 -1
  34. package/dist/client/index.html +2 -2
  35. package/dist/client/version.json +1 -1
  36. package/dist/droid-cli/package.json +1 -1
  37. package/dist/extension.js +5640 -3618
  38. package/dist/pi-claude-cli/package.json +1 -1
  39. package/dist/plugins/fusion-plugin-cli-printing-press/manifest.json +6 -0
  40. package/dist/plugins/fusion-plugin-cli-printing-press/package.json +26 -0
  41. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/manifest.test.ts +20 -0
  42. package/dist/plugins/fusion-plugin-cli-printing-press/src/index.ts +14 -0
  43. package/dist/plugins/fusion-plugin-cursor-runtime/bundled.js +9 -11
  44. package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
  45. package/dist/plugins/fusion-plugin-dependency-graph/bundled.js +30 -0
  46. package/dist/plugins/fusion-plugin-dependency-graph/package.json +3 -28
  47. package/dist/plugins/fusion-plugin-droid-runtime/bundled.js +899 -895
  48. package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
  49. package/dist/plugins/fusion-plugin-hermes-runtime/bundled.js +68 -71
  50. package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
  51. package/dist/plugins/fusion-plugin-openclaw-runtime/bundled.js +47 -50
  52. package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
  53. package/dist/plugins/fusion-plugin-paperclip-runtime/bundled.js +155 -109
  54. package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
  55. package/dist/plugins/fusion-plugin-reports/package.json +1 -1
  56. package/dist/plugins/fusion-plugin-reports/src/index.ts +49 -3
  57. package/dist/plugins/fusion-plugin-reports/src/report-schema.ts +38 -0
  58. package/dist/plugins/fusion-plugin-reports/src/store/__tests__/report-schema.test.ts +66 -0
  59. package/dist/plugins/fusion-plugin-reports/src/store/__tests__/report-store.test.ts +177 -0
  60. package/dist/plugins/fusion-plugin-reports/src/store/report-store.ts +341 -0
  61. package/dist/plugins/fusion-plugin-reports/src/store/report-types.ts +77 -0
  62. package/dist/plugins/fusion-plugin-roadmap/{src/dashboard/RoadmapsView.css → bundled.css} +13 -219
  63. package/dist/plugins/fusion-plugin-roadmap/bundled.js +29535 -0
  64. package/dist/plugins/fusion-plugin-roadmap/package.json +4 -41
  65. package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
  66. package/package.json +2 -3
  67. package/dist/client/assets/AgentDetailView-gy_5SUj2.js +0 -18
  68. package/dist/client/assets/ChatView-B_-B8fqu.js +0 -1
  69. package/dist/client/assets/MemoryView-CKElJY_3.js +0 -2
  70. package/dist/client/assets/MemoryView-DiajLXby.css +0 -1
  71. package/dist/client/assets/PluginManager-CfW55BF4.js +0 -1
  72. package/dist/client/assets/ResearchView-B256Lr8I.js +0 -1
  73. package/dist/client/assets/SettingsModal-BeA_nQtW.js +0 -31
  74. package/dist/client/assets/SettingsModal-DzsLquBu.css +0 -1
  75. package/dist/client/assets/index-CQyVRLOb.js +0 -692
  76. package/dist/client/assets/index-CxA2Nn0_.css +0 -1
  77. package/dist/plugins/fusion-plugin-dependency-graph/src/DependencyGraph.css +0 -58
  78. package/dist/plugins/fusion-plugin-dependency-graph/src/DependencyGraph.tsx +0 -301
  79. package/dist/plugins/fusion-plugin-dependency-graph/src/GraphHighlight.css +0 -27
  80. package/dist/plugins/fusion-plugin-dependency-graph/src/GraphTaskNode.css +0 -157
  81. package/dist/plugins/fusion-plugin-dependency-graph/src/GraphTaskNode.tsx +0 -126
  82. package/dist/plugins/fusion-plugin-dependency-graph/src/GraphToolbar.css +0 -35
  83. package/dist/plugins/fusion-plugin-dependency-graph/src/GraphToolbar.tsx +0 -36
  84. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/DependencyGraph.highlighting.test.tsx +0 -112
  85. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/DependencyGraph.persistence.test.tsx +0 -115
  86. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/DependencyGraph.test.tsx +0 -128
  87. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/GraphTaskNode.drag.test.tsx +0 -82
  88. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/GraphTaskNode.test.tsx +0 -307
  89. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/GraphToolbar.test.tsx +0 -60
  90. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/edges.test.tsx +0 -75
  91. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/filtering.test.tsx +0 -62
  92. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/filters.test.ts +0 -78
  93. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/graphPositionStorage.test.ts +0 -95
  94. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/host-integration.test.ts +0 -74
  95. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/index.test.ts +0 -58
  96. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/interactions.test.tsx +0 -121
  97. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/layout.test.ts +0 -70
  98. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/persistence.test.tsx +0 -89
  99. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/useGraphData.test.ts +0 -86
  100. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/useGraphInteraction.test.ts +0 -167
  101. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/useGraphPositions.test.ts +0 -66
  102. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/useNodeDrag.test.ts +0 -81
  103. package/dist/plugins/fusion-plugin-dependency-graph/src/dashboard-interop.d.ts +0 -35
  104. package/dist/plugins/fusion-plugin-dependency-graph/src/dashboard-view.tsx +0 -19
  105. package/dist/plugins/fusion-plugin-dependency-graph/src/edges.tsx +0 -70
  106. package/dist/plugins/fusion-plugin-dependency-graph/src/filters.ts +0 -8
  107. package/dist/plugins/fusion-plugin-dependency-graph/src/hooks/__tests__/useDependencyChain.test.ts +0 -53
  108. package/dist/plugins/fusion-plugin-dependency-graph/src/hooks/useDependencyChain.ts +0 -60
  109. package/dist/plugins/fusion-plugin-dependency-graph/src/hooks/useGraphPositions.ts +0 -45
  110. package/dist/plugins/fusion-plugin-dependency-graph/src/hooks/useNodeDrag.ts +0 -114
  111. package/dist/plugins/fusion-plugin-dependency-graph/src/index.ts +0 -24
  112. package/dist/plugins/fusion-plugin-dependency-graph/src/layout.ts +0 -91
  113. package/dist/plugins/fusion-plugin-dependency-graph/src/styles/drag.css +0 -15
  114. package/dist/plugins/fusion-plugin-dependency-graph/src/types.ts +0 -21
  115. package/dist/plugins/fusion-plugin-dependency-graph/src/useGraphData.ts +0 -17
  116. package/dist/plugins/fusion-plugin-dependency-graph/src/useGraphInteraction.ts +0 -292
  117. package/dist/plugins/fusion-plugin-dependency-graph/src/utils/graphPositionStorage.ts +0 -65
  118. package/dist/plugins/fusion-plugin-roadmap/src/__tests__/api-client.test.ts +0 -101
  119. package/dist/plugins/fusion-plugin-roadmap/src/__tests__/index.test.ts +0 -92
  120. package/dist/plugins/fusion-plugin-roadmap/src/__tests__/roadmap-routes.test.ts +0 -48
  121. package/dist/plugins/fusion-plugin-roadmap/src/__tests__/roadmap-suggestions.test.ts +0 -31
  122. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/RoadmapsView.tsx +0 -2559
  123. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/__tests__/RoadmapsView.test.tsx +0 -1144
  124. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/__tests__/useRoadmaps.test.ts +0 -1756
  125. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/api.ts +0 -70
  126. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/test-setup.ts +0 -7
  127. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/types.ts +0 -1
  128. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/useConfirm.ts +0 -8
  129. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/useRoadmaps.ts +0 -1188
  130. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/useViewportMode.ts +0 -20
  131. package/dist/plugins/fusion-plugin-roadmap/src/dashboard-view.tsx +0 -6
  132. package/dist/plugins/fusion-plugin-roadmap/src/index.ts +0 -74
  133. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-routes.ts +0 -1
  134. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-schema.ts +0 -41
  135. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-suggestions.d.ts +0 -15
  136. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-suggestions.ts +0 -15
  137. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.d.ts +0 -283
  138. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.d.ts.map +0 -1
  139. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.js +0 -21
  140. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.js.map +0 -1
  141. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.ts +0 -310
  142. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.d.ts +0 -5
  143. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.d.ts.map +0 -1
  144. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.js +0 -361
  145. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.js.map +0 -1
  146. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.ts +0 -408
  147. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.d.ts +0 -68
  148. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.d.ts.map +0 -1
  149. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.js +0 -300
  150. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.js.map +0 -1
  151. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.ts +0 -381
  152. package/dist/plugins/fusion-plugin-roadmap/src/server/index.d.ts +0 -3
  153. package/dist/plugins/fusion-plugin-roadmap/src/server/index.ts +0 -1
  154. package/dist/plugins/fusion-plugin-roadmap/src/store/__tests__/roadmap-handoff.test.ts +0 -445
  155. package/dist/plugins/fusion-plugin-roadmap/src/store/__tests__/roadmap-ordering.test.ts +0 -334
  156. package/dist/plugins/fusion-plugin-roadmap/src/store/__tests__/roadmap-store.test.ts +0 -1318
  157. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-handoff.ts +0 -163
  158. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.d.ts +0 -37
  159. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.d.ts.map +0 -1
  160. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.js +0 -188
  161. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.js.map +0 -1
  162. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.ts +0 -311
  163. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.d.ts +0 -299
  164. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.d.ts.map +0 -1
  165. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.js +0 -765
  166. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.js.map +0 -1
  167. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.ts +0 -1001
@@ -1,35 +0,0 @@
1
- .graph-toolbar {
2
- position: absolute;
3
- right: var(--space-md);
4
- bottom: var(--space-md);
5
- display: flex;
6
- flex-direction: column;
7
- gap: var(--space-xs);
8
- padding: var(--space-sm);
9
- background: var(--surface);
10
- border: var(--btn-border-width) solid var(--border);
11
- border-radius: var(--radius-md);
12
- box-shadow: var(--shadow-md);
13
- z-index: 10;
14
- }
15
-
16
- .graph-toolbar__zoom-label {
17
- min-width: 3.5ch;
18
- color: var(--text-muted);
19
- font-family: var(--font-mono);
20
- font-size: 0.75rem;
21
- text-align: center;
22
- }
23
-
24
- @media (max-width: 768px) {
25
- .graph-toolbar {
26
- right: var(--space-sm);
27
- bottom: var(--space-sm);
28
- padding: var(--space-md);
29
- }
30
-
31
- .graph-toolbar .btn-icon {
32
- min-width: calc(var(--space-xs) * 11);
33
- min-height: calc(var(--space-xs) * 11);
34
- }
35
- }
@@ -1,36 +0,0 @@
1
- import { Maximize, RotateCcw, ZoomIn, ZoomOut } from "lucide-react";
2
- import "./GraphToolbar.css";
3
-
4
- export interface GraphToolbarProps {
5
- zoom: number;
6
- onZoomIn: () => void;
7
- onZoomOut: () => void;
8
- onFitToGraph: () => void;
9
- onResetView: () => void;
10
- }
11
-
12
- export function GraphToolbar({
13
- zoom,
14
- onZoomIn,
15
- onZoomOut,
16
- onFitToGraph,
17
- onResetView,
18
- }: GraphToolbarProps) {
19
- return (
20
- <div className="graph-toolbar" data-testid="graph-toolbar">
21
- <button className="btn btn-icon" title="Zoom in (Ctrl+=)" aria-label="Zoom in" onClick={onZoomIn}>
22
- <ZoomIn size={16} />
23
- </button>
24
- <button className="btn btn-icon" title="Zoom out (Ctrl+-)" aria-label="Zoom out" onClick={onZoomOut}>
25
- <ZoomOut size={16} />
26
- </button>
27
- <div className="graph-toolbar__zoom-label" aria-live="polite">{Math.round(zoom * 100)}%</div>
28
- <button className="btn btn-icon" title="Fit to graph (Ctrl+Shift+F)" aria-label="Fit to graph" onClick={onFitToGraph}>
29
- <Maximize size={16} />
30
- </button>
31
- <button className="btn btn-icon" title="Reset view (Ctrl+0)" aria-label="Reset view" onClick={onResetView}>
32
- <RotateCcw size={16} />
33
- </button>
34
- </div>
35
- );
36
- }
@@ -1,112 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from "vitest";
2
- import { cleanup, fireEvent, render, screen } from "@testing-library/react";
3
- import type { Task } from "@fusion/core";
4
- import { DependencyGraph } from "../DependencyGraph";
5
-
6
- vi.mock("@fusion/dashboard/app/components/TaskCard", () => ({
7
- TaskCard: ({ task, onOpenDetail }: { task: Task; onOpenDetail: (task: Task) => void }) => (
8
- <button data-testid={`task-${task.id}`} onClick={() => onOpenDetail(task)}>{task.id}</button>
9
- ),
10
- }));
11
-
12
- vi.mock("../useGraphInteraction", () => ({
13
- useGraphInteraction: () => ({
14
- transform: "translate(0px, 0px) scale(1)",
15
- zoom: 1,
16
- transitioning: false,
17
- zoomIn: vi.fn(),
18
- zoomOut: vi.fn(),
19
- resetView: vi.fn(),
20
- fitToGraph: vi.fn(),
21
- onPointerDown: vi.fn(),
22
- onPointerMove: vi.fn(),
23
- onPointerUp: vi.fn(),
24
- onWheelZoom: vi.fn(),
25
- handleKeyDown: vi.fn(),
26
- }),
27
- }));
28
-
29
- function createTask(id: string, dependencies: string[] = []): Task {
30
- return {
31
- id,
32
- description: id,
33
- column: "todo",
34
- dependencies,
35
- steps: [],
36
- currentStep: 0,
37
- log: [],
38
- createdAt: new Date().toISOString(),
39
- updatedAt: new Date().toISOString(),
40
- } as Task;
41
- }
42
-
43
- afterEach(() => {
44
- cleanup();
45
- });
46
-
47
- describe("DependencyGraph highlighting", () => {
48
- const tasks = [createTask("A"), createTask("B", ["A"]), createTask("C", ["B"]), createTask("D")];
49
-
50
- it("highlights chain on hover and returns to neutral on mouse leave", () => {
51
- render(<DependencyGraph tasks={tasks} onOpenDetail={vi.fn()} />);
52
-
53
- fireEvent.mouseEnter(screen.getByTestId("graph-task-node-C"));
54
- expect(screen.getByTestId("graph-task-node-A").className).toContain("graph-task-node--highlighted");
55
- expect(screen.getByTestId("graph-task-node-B").className).toContain("graph-task-node--highlighted");
56
- expect(screen.getByTestId("graph-task-node-C").className).toContain("graph-task-node--highlighted");
57
- expect(screen.getByTestId("graph-task-node-D").className).toContain("graph-task-node--dimmed");
58
-
59
- fireEvent.mouseLeave(screen.getByTestId("graph-task-node-C"));
60
- expect(screen.getByTestId("graph-task-node-A").className).not.toContain("graph-task-node--highlighted");
61
- expect(screen.getByTestId("graph-task-node-D").className).not.toContain("graph-task-node--dimmed");
62
- });
63
-
64
- it("keeps selection until toggled or pane clicked", () => {
65
- render(<DependencyGraph tasks={tasks} onOpenDetail={vi.fn()} />);
66
-
67
- fireEvent.click(screen.getByTestId("graph-task-node-B"));
68
- expect(screen.getByTestId("graph-task-node-A").className).toContain("graph-task-node--highlighted");
69
-
70
- fireEvent.click(screen.getByTestId("graph-task-node-B"));
71
- expect(screen.getByTestId("graph-task-node-A").className).not.toContain("graph-task-node--highlighted");
72
-
73
- fireEvent.click(screen.getByTestId("graph-task-node-B"));
74
- fireEvent.click(document.querySelector(".dependency-graph__viewport") as Element);
75
- expect(screen.getByTestId("graph-task-node-B").className).not.toContain("graph-task-node--highlighted");
76
- });
77
-
78
- it("hover overrides selection and reverts when hover leaves", () => {
79
- render(<DependencyGraph tasks={tasks} onOpenDetail={vi.fn()} />);
80
-
81
- fireEvent.click(screen.getByTestId("graph-task-node-B"));
82
- fireEvent.mouseEnter(screen.getByTestId("graph-task-node-D"));
83
- expect(screen.getByTestId("graph-task-node-D").className).toContain("graph-task-node--highlighted");
84
- expect(screen.getByTestId("graph-task-node-A").className).toContain("graph-task-node--dimmed");
85
-
86
- fireEvent.mouseLeave(screen.getByTestId("graph-task-node-D"));
87
- expect(screen.getByTestId("graph-task-node-A").className).toContain("graph-task-node--highlighted");
88
- });
89
-
90
- it("applies edge dimming/highlighting and preserves click-to-detail", () => {
91
- const onOpenDetail = vi.fn();
92
- render(<DependencyGraph tasks={tasks} onOpenDetail={onOpenDetail} />);
93
-
94
- fireEvent.mouseEnter(screen.getByTestId("graph-task-node-C"));
95
- const edges = screen.getAllByTestId("dependency-edge");
96
- const edgeAB = edges.find((edge) => edge.getAttribute("data-edge-id") === "B->A");
97
- const edgeCB = edges.find((edge) => edge.getAttribute("data-edge-id") === "C->B");
98
-
99
- expect(edgeAB?.className.baseVal || edgeAB?.className).toContain("graph-edge--highlighted");
100
- expect(edgeCB?.className.baseVal || edgeCB?.className).toContain("graph-edge--highlighted");
101
-
102
- fireEvent.click(screen.getByTestId("task-C"));
103
- expect(onOpenDetail).toHaveBeenCalledTimes(1);
104
- expect(onOpenDetail).toHaveBeenCalledWith(expect.objectContaining({ id: "C" }));
105
- });
106
-
107
- it("highlights only isolated node with no dependencies", () => {
108
- render(<DependencyGraph tasks={[createTask("X")]} onOpenDetail={vi.fn()} />);
109
- fireEvent.mouseEnter(screen.getByTestId("graph-task-node-X"));
110
- expect(screen.getByTestId("graph-task-node-X").className).toContain("graph-task-node--highlighted");
111
- });
112
- });
@@ -1,115 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
- import { cleanup, fireEvent, render, screen } from "@testing-library/react";
3
- import type { Task } from "@fusion/core";
4
- import { DependencyGraph } from "../DependencyGraph";
5
-
6
- const fitToGraph = vi.fn();
7
-
8
- vi.mock("@fusion/dashboard/app/components/TaskCard", () => ({
9
- TaskCard: ({ task, onOpenDetail }: { task: Task; onOpenDetail: (task: Task) => void }) => (
10
- <button data-testid={`task-${task.id}`} onClick={() => onOpenDetail(task)}>{task.id}</button>
11
- ),
12
- }));
13
-
14
- vi.mock("../useGraphInteraction", () => ({
15
- useGraphInteraction: () => ({
16
- transform: "translate(0px, 0px) scale(1)",
17
- zoom: 1,
18
- transitioning: false,
19
- zoomIn: vi.fn(),
20
- zoomOut: vi.fn(),
21
- resetView: vi.fn(),
22
- fitToGraph,
23
- onPointerDown: vi.fn(),
24
- onPointerMove: vi.fn(),
25
- onPointerUp: vi.fn(),
26
- onWheelZoom: vi.fn(),
27
- handleKeyDown: vi.fn(),
28
- }),
29
- }));
30
-
31
- vi.mock("../layout", () => ({
32
- computeAutoLayout: ({ nodes }: { nodes: Array<{ task: { id: string } }> }) => {
33
- const map = new Map<string, { x: number; y: number }>();
34
- for (const node of nodes) {
35
- if (node.task.id === "A") map.set("A", { x: 0, y: 0 });
36
- if (node.task.id === "B") map.set("B", { x: 200, y: 0 });
37
- }
38
- return map;
39
- },
40
- }));
41
-
42
- function createStorage() {
43
- const store = new Map<string, string>();
44
- return {
45
- getItem: (key: string) => store.get(key) ?? null,
46
- setItem: (key: string, value: string) => {
47
- store.set(key, value);
48
- },
49
- removeItem: (key: string) => {
50
- store.delete(key);
51
- },
52
- };
53
- }
54
-
55
- function createTask(id: string, column: Task["column"] = "todo"): Task {
56
- return { id, description: id, column, dependencies: [], steps: [], currentStep: 0, log: [] } as Task;
57
- }
58
-
59
- describe("DependencyGraph persistence", () => {
60
- afterEach(() => {
61
- cleanup();
62
- });
63
-
64
- beforeEach(() => {
65
- Object.defineProperty(window, "localStorage", { value: createStorage(), configurable: true });
66
- fitToGraph.mockReset();
67
- });
68
-
69
- it("persists dragged node position across remount", () => {
70
- const { unmount } = render(<DependencyGraph tasks={[createTask("A")]} projectId="p1" />);
71
-
72
- const node = screen.getByTestId("graph-task-node-A");
73
- fireEvent.pointerDown(node, { pointerId: 1, isPrimary: true, clientX: 10, clientY: 10 });
74
- fireEvent.pointerMove(node, { pointerId: 1, isPrimary: true, clientX: 30, clientY: 40 });
75
- fireEvent.pointerUp(node, { pointerId: 1, isPrimary: true, clientX: 30, clientY: 40 });
76
-
77
- expect(window.localStorage.getItem("kb:p1:fusion-plugin-dependency-graph:positions")).toContain('"A":{"x":20,"y":30}');
78
-
79
- unmount();
80
- render(<DependencyGraph tasks={[createTask("A")]} projectId="p1" />);
81
-
82
- expect(screen.getByTestId("graph-task-node-A").getAttribute("style")).toContain("left: 20px");
83
- expect(screen.getByTestId("graph-task-node-A").getAttribute("style")).toContain("top: 30px");
84
- });
85
-
86
- it("merges saved positions with auto-layout for new tasks", () => {
87
- window.localStorage.setItem("kb:p1:fusion-plugin-dependency-graph:positions", JSON.stringify({ A: { x: 25, y: 35 } }));
88
-
89
- render(<DependencyGraph tasks={[createTask("A"), createTask("B")]} projectId="p1" />);
90
-
91
- expect(screen.getByTestId("graph-task-node-A").getAttribute("style")).toContain("left: 25px");
92
- expect(screen.getByTestId("graph-task-node-B").getAttribute("style")).toContain("left: 200px");
93
- });
94
-
95
- it("fit to graph clears saved positions and reapplies auto-layout", () => {
96
- window.localStorage.setItem("kb:p1:fusion-plugin-dependency-graph:positions", JSON.stringify({ A: { x: 25, y: 35 } }));
97
-
98
- render(<DependencyGraph tasks={[createTask("A")]} projectId="p1" />);
99
- fireEvent.click(screen.getByRole("button", { name: "Fit to graph" }));
100
-
101
- expect(window.localStorage.getItem("kb:p1:fusion-plugin-dependency-graph:positions")).toBeNull();
102
- expect(screen.getByTestId("graph-task-node-A").getAttribute("style")).toContain("left: 0px");
103
- });
104
-
105
- it("switching projects loads project-scoped positions", () => {
106
- window.localStorage.setItem("kb:p1:fusion-plugin-dependency-graph:positions", JSON.stringify({ A: { x: 11, y: 22 } }));
107
- window.localStorage.setItem("kb:p2:fusion-plugin-dependency-graph:positions", JSON.stringify({ A: { x: 33, y: 44 } }));
108
-
109
- const { rerender } = render(<DependencyGraph tasks={[createTask("A")]} projectId="p1" />);
110
- expect(screen.getByTestId("graph-task-node-A").getAttribute("style")).toContain("left: 11px");
111
-
112
- rerender(<DependencyGraph tasks={[createTask("A")]} projectId="p2" />);
113
- expect(screen.getByTestId("graph-task-node-A").getAttribute("style")).toContain("left: 33px");
114
- });
115
- });
@@ -1,128 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
- import { cleanup, fireEvent, render, screen } from "@testing-library/react";
3
- import type { Task } from "@fusion/core";
4
- import { DependencyGraph } from "../DependencyGraph";
5
-
6
- const fitToGraph = vi.fn();
7
- const zoomIn = vi.fn();
8
- const zoomOut = vi.fn();
9
- const resetView = vi.fn();
10
- const handleKeyDown = vi.fn();
11
-
12
- vi.mock("@fusion/dashboard/app/components/TaskCard", () => ({
13
- TaskCard: ({ task, onOpenDetail, disableDrag }: { task: Task; onOpenDetail: (task: Task) => void; disableDrag?: boolean }) => (
14
- <button data-testid={`task-${task.id}`} draggable={!disableDrag} onClick={() => onOpenDetail(task)}>{task.id}</button>
15
- ),
16
- }));
17
-
18
- vi.mock("../useGraphInteraction", () => ({
19
- useGraphInteraction: () => ({
20
- transform: "translate(0px, 0px) scale(1)",
21
- zoom: 1,
22
- transitioning: false,
23
- zoomIn,
24
- zoomOut,
25
- resetView,
26
- fitToGraph,
27
- onPointerDown: vi.fn(),
28
- onPointerMove: vi.fn(),
29
- onPointerUp: vi.fn(),
30
- onWheelZoom: vi.fn(),
31
- handleKeyDown,
32
- }),
33
- }));
34
-
35
- function createTask(id: string, column: Task["column"], dependencies: string[] = []): Task {
36
- return { id, description: id, column, dependencies, steps: [], currentStep: 0, log: [] } as Task;
37
- }
38
-
39
- describe("DependencyGraph", () => {
40
- beforeEach(() => {
41
- fitToGraph.mockReset();
42
- zoomIn.mockReset();
43
- zoomOut.mockReset();
44
- resetView.mockReset();
45
- handleKeyDown.mockReset();
46
- });
47
-
48
- afterEach(() => {
49
- cleanup();
50
- });
51
-
52
- it("renders empty state for empty list", () => {
53
- render(<DependencyGraph tasks={[]} onOpenTaskDetail={vi.fn()} />);
54
- expect(screen.getByText(/No active tasks/i)).toBeTruthy();
55
- });
56
-
57
- it("renders only triage/todo/in-progress/in-review nodes from mixed columns", () => {
58
- render(
59
- <DependencyGraph
60
- tasks={[
61
- createTask("A", "triage"),
62
- createTask("B", "todo"),
63
- createTask("C", "in-progress"),
64
- createTask("D", "in-review"),
65
- createTask("E", "done"),
66
- createTask("F", "archived"),
67
- ]}
68
- onOpenTaskDetail={vi.fn()}
69
- />,
70
- );
71
-
72
- expect(screen.getByTestId("graph-task-node-A")).toBeTruthy();
73
- expect(screen.getByTestId("graph-task-node-B")).toBeTruthy();
74
- expect(screen.getByTestId("graph-task-node-C")).toBeTruthy();
75
- expect(screen.getByTestId("graph-task-node-D")).toBeTruthy();
76
- expect(screen.queryByTestId("graph-task-node-E")).toBeNull();
77
- expect(screen.queryByTestId("graph-task-node-F")).toBeNull();
78
- });
79
-
80
- it("auto-fits on initial load with active tasks", () => {
81
- render(<DependencyGraph tasks={[createTask("A", "todo")]} onOpenTaskDetail={vi.fn()} />);
82
- expect(fitToGraph).toHaveBeenCalled();
83
- });
84
-
85
- it("forwards keyboard events to interaction hook", () => {
86
- render(<DependencyGraph tasks={[createTask("A", "todo")]} onOpenTaskDetail={vi.fn()} />);
87
- const viewport = document.querySelector(".dependency-graph__viewport");
88
- if (!viewport) throw new Error("missing viewport");
89
- fireEvent.keyDown(viewport, { key: "=", ctrlKey: true });
90
- expect(handleKeyDown).toHaveBeenCalled();
91
- });
92
-
93
- it("sets viewport tabIndex for keyboard focus", () => {
94
- render(<DependencyGraph tasks={[createTask("A", "todo")]} onOpenTaskDetail={vi.fn()} />);
95
- const viewport = document.querySelector(".dependency-graph__viewport");
96
- expect(viewport?.getAttribute("tabindex")).toBe("0");
97
- });
98
-
99
- it("renders toolbar controls", () => {
100
- render(<DependencyGraph tasks={[createTask("A", "todo")]} onOpenTaskDetail={vi.fn()} />);
101
- expect(screen.getByRole("button", { name: "Zoom in" })).toBeTruthy();
102
- expect(screen.getByRole("button", { name: "Zoom out" })).toBeTruthy();
103
- expect(screen.getByRole("button", { name: "Fit to graph" })).toBeTruthy();
104
- expect(screen.getByRole("button", { name: "Reset view" })).toBeTruthy();
105
- expect(screen.getByText("100%")).toBeTruthy();
106
- });
107
-
108
- it("fit-to-graph button triggers fitToGraph", () => {
109
- render(<DependencyGraph tasks={[createTask("A", "todo")]} onOpenTaskDetail={vi.fn()} />);
110
- fireEvent.click(screen.getByRole("button", { name: "Fit to graph" }));
111
- expect(fitToGraph).toHaveBeenCalled();
112
- });
113
-
114
- it("clicking a card triggers onOpenDetail exactly once", () => {
115
- const onOpenDetail = vi.fn();
116
- render(<DependencyGraph tasks={[createTask("A", "in-progress")]} onOpenDetail={onOpenDetail} />);
117
- fireEvent.click(screen.getByTestId("task-A"));
118
- expect(onOpenDetail).toHaveBeenCalledTimes(1);
119
- expect(onOpenDetail).toHaveBeenCalledWith(expect.objectContaining({ id: "A" }));
120
- });
121
-
122
- it("falls back to onOpenTaskDetail when onOpenDetail is not provided", () => {
123
- const onOpenTaskDetail = vi.fn();
124
- render(<DependencyGraph tasks={[createTask("A", "in-progress")]} onOpenTaskDetail={onOpenTaskDetail} />);
125
- fireEvent.click(screen.getByTestId("task-A"));
126
- expect(onOpenTaskDetail).toHaveBeenCalledWith("A");
127
- });
128
- });
@@ -1,82 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from "vitest";
2
- import { cleanup, fireEvent, render, screen } from "@testing-library/react";
3
- import type React from "react";
4
- import type { Task } from "@fusion/core";
5
- import { GraphTaskNode } from "../GraphTaskNode";
6
-
7
- function task(id = "FN-1"): Task {
8
- return { id, description: id, column: "todo", dependencies: [], steps: [], currentStep: 0, log: [] } as Task;
9
- }
10
-
11
- function props(overrides: Partial<React.ComponentProps<typeof GraphTaskNode>> = {}): React.ComponentProps<typeof GraphTaskNode> {
12
- return {
13
- task: task(),
14
- projectId: "p1",
15
- position: { x: 0, y: 0 },
16
- scale: 1,
17
- isHighlighted: false,
18
- isDimmed: false,
19
- onNodePositionChange: vi.fn(),
20
- onNodeDragStateChange: vi.fn(),
21
- onOpenDetail: vi.fn(),
22
- addToast: vi.fn(),
23
- onUpdateTask: vi.fn(),
24
- onArchiveTask: vi.fn(),
25
- onUnarchiveTask: vi.fn(),
26
- onDeleteTask: vi.fn(),
27
- onRetryTask: vi.fn(),
28
- onOpenDetailWithTab: vi.fn(),
29
- onMoveTask: vi.fn(),
30
- onOpenMission: vi.fn(),
31
- taskStuckTimeoutMs: 1000,
32
- lastFetchTimeMs: Date.now(),
33
- workflowStepNameLookup: new Map<string, string>(),
34
- ...overrides,
35
- };
36
- }
37
-
38
- afterEach(() => {
39
- cleanup();
40
- });
41
-
42
- describe("GraphTaskNode drag", () => {
43
- it("does not open detail after drag threshold is exceeded", () => {
44
- const onOpenDetail = vi.fn();
45
- render(<GraphTaskNode {...props({ onOpenDetail })} />);
46
- const node = screen.getByTestId("graph-task-node-FN-1");
47
-
48
- fireEvent.pointerDown(node, { pointerId: 1, clientX: 10, clientY: 10, isPrimary: true });
49
- fireEvent.pointerMove(node, { pointerId: 1, clientX: 25, clientY: 25, isPrimary: true });
50
- fireEvent.pointerUp(node, { pointerId: 1, clientX: 25, clientY: 25, isPrimary: true });
51
- fireEvent.click(node);
52
-
53
- expect(onOpenDetail).not.toHaveBeenCalled();
54
- });
55
-
56
- it("applies dragging class only after threshold move", () => {
57
- const onNodePositionChange = vi.fn();
58
- render(<GraphTaskNode {...props({ onNodePositionChange })} />);
59
- const node = screen.getByTestId("graph-task-node-FN-1");
60
-
61
- fireEvent.pointerDown(node, { pointerId: 1, clientX: 10, clientY: 10, isPrimary: true });
62
- fireEvent.pointerMove(node, { pointerId: 1, clientX: 12, clientY: 12, isPrimary: true });
63
- expect(node.className).not.toContain("graph-node--dragging");
64
-
65
- fireEvent.pointerMove(node, { pointerId: 1, clientX: 20, clientY: 20, isPrimary: true });
66
- expect(node.className).toContain("graph-node--dragging");
67
- expect(onNodePositionChange).toHaveBeenCalled();
68
-
69
- fireEvent.pointerUp(node, { pointerId: 1, clientX: 20, clientY: 20, isPrimary: true });
70
- expect(node.className).not.toContain("graph-node--dragging");
71
- });
72
-
73
- it("composes highlight and dragging classes", () => {
74
- render(<GraphTaskNode {...props({ isHighlighted: true })} />);
75
- const node = screen.getByTestId("graph-task-node-FN-1");
76
- fireEvent.pointerDown(node, { pointerId: 1, clientX: 0, clientY: 0, isPrimary: true });
77
- fireEvent.pointerMove(node, { pointerId: 1, clientX: 10, clientY: 10, isPrimary: true });
78
-
79
- expect(node.className).toContain("graph-task-node--highlighted");
80
- expect(node.className).toContain("graph-node--dragging");
81
- });
82
- });