@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,15 +0,0 @@
1
- .graph-node--draggable {
2
- cursor: grab;
3
- touch-action: none;
4
- }
5
-
6
- .graph-node--dragging {
7
- z-index: 3;
8
- cursor: grabbing;
9
- box-shadow: var(--shadow-lg);
10
- transform: scale(1.02);
11
- transition:
12
- transform var(--transition-fast),
13
- box-shadow var(--transition-fast),
14
- z-index var(--transition-fast);
15
- }
@@ -1,21 +0,0 @@
1
- import type { Task } from "@fusion/core";
2
-
3
- export interface GraphPosition {
4
- x: number;
5
- y: number;
6
- }
7
-
8
- export interface GraphNode {
9
- task: Task;
10
- position?: GraphPosition;
11
- }
12
-
13
- export interface GraphEdge {
14
- source: string;
15
- target: string;
16
- }
17
-
18
- export interface GraphData {
19
- nodes: GraphNode[];
20
- edges: GraphEdge[];
21
- }
@@ -1,17 +0,0 @@
1
- import { useMemo } from "react";
2
- import type { Task } from "@fusion/core";
3
- import type { GraphData, GraphNode } from "./types";
4
-
5
- export function useGraphData(tasks: Task[]): GraphData {
6
- return useMemo(() => {
7
- const nodes: GraphNode[] = tasks.map((task) => ({ task }));
8
- const taskIds = new Set(tasks.map((task) => task.id));
9
- const edges = tasks.flatMap((task) =>
10
- (task.dependencies ?? [])
11
- .filter((dependencyId) => taskIds.has(dependencyId))
12
- .map((dependencyId) => ({ source: task.id, target: dependencyId })),
13
- );
14
-
15
- return { nodes, edges };
16
- }, [tasks]);
17
- }
@@ -1,292 +0,0 @@
1
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
- import type { KeyboardEvent as ReactKeyboardEvent } from "react";
3
- import type { LayoutOptions } from "./layout";
4
- import type { GraphPosition } from "./types";
5
-
6
- const MIN_ZOOM = 0.1;
7
- const MAX_ZOOM = 3;
8
- const FIT_PADDING = 40;
9
- const TRANSITION_TIMEOUT_MS = 200;
10
-
11
- interface PointerPoint {
12
- x: number;
13
- y: number;
14
- }
15
-
16
- interface PinchState {
17
- distance: number;
18
- zoom: number;
19
- pan: PointerPoint;
20
- midpoint: PointerPoint;
21
- }
22
-
23
- function clamp(value: number, min: number, max: number): number {
24
- return Math.min(max, Math.max(min, value));
25
- }
26
-
27
- function isEditableTarget(target: EventTarget | null): boolean {
28
- if (!(target instanceof HTMLElement)) return false;
29
- const tagName = target.tagName.toLowerCase();
30
- return target.isContentEditable || tagName === "input" || tagName === "textarea" || tagName === "select";
31
- }
32
-
33
- export function useGraphInteraction() {
34
- const [pan, setPan] = useState<PointerPoint>({ x: 0, y: 0 });
35
- const [zoom, setZoom] = useState(1);
36
- const [transitioning, setTransitioning] = useState(false);
37
-
38
- const panRef = useRef(pan);
39
- const zoomRef = useRef(zoom);
40
- const transitionTimerRef = useRef<number | null>(null);
41
- const dragStateRef = useRef<{ start: PointerPoint; panStart: PointerPoint } | null>(null);
42
- const pointersRef = useRef<Map<number, PointerPoint>>(new Map());
43
- const pinchRef = useRef<PinchState | null>(null);
44
-
45
- useEffect(() => {
46
- panRef.current = pan;
47
- }, [pan]);
48
-
49
- useEffect(() => {
50
- zoomRef.current = zoom;
51
- }, [zoom]);
52
-
53
- useEffect(() => () => {
54
- if (transitionTimerRef.current !== null) {
55
- window.clearTimeout(transitionTimerRef.current);
56
- }
57
- }, []);
58
-
59
- const transform = useMemo(() => `translate(${pan.x}px, ${pan.y}px) scale(${zoom})`, [pan.x, pan.y, zoom]);
60
- const zoomPercent = useMemo(() => Math.round(zoom * 100), [zoom]);
61
-
62
- const setAnimate = useCallback((enabled: boolean) => {
63
- if (!enabled) {
64
- if (transitionTimerRef.current !== null) {
65
- window.clearTimeout(transitionTimerRef.current);
66
- transitionTimerRef.current = null;
67
- }
68
- setTransitioning(false);
69
- return;
70
- }
71
-
72
- setTransitioning(true);
73
- if (transitionTimerRef.current !== null) {
74
- window.clearTimeout(transitionTimerRef.current);
75
- }
76
- transitionTimerRef.current = window.setTimeout(() => {
77
- setTransitioning(false);
78
- transitionTimerRef.current = null;
79
- }, TRANSITION_TIMEOUT_MS);
80
- }, []);
81
-
82
- const clampPan = useCallback((nextPan: PointerPoint, viewportWidth: number, viewportHeight: number) => ({
83
- x: clamp(nextPan.x, -viewportWidth, viewportWidth),
84
- y: clamp(nextPan.y, -viewportHeight, viewportHeight),
85
- }), []);
86
-
87
- const zoomAtPoint = useCallback((
88
- nextZoomRaw: number,
89
- anchor: PointerPoint,
90
- viewportWidth: number,
91
- viewportHeight: number,
92
- ) => {
93
- const currentZoom = zoomRef.current;
94
- const currentPan = panRef.current;
95
- const nextZoom = clamp(nextZoomRaw, MIN_ZOOM, MAX_ZOOM);
96
- const scaleRatio = nextZoom / currentZoom;
97
-
98
- const nextPan = clampPan({
99
- x: anchor.x - (anchor.x - currentPan.x) * scaleRatio,
100
- y: anchor.y - (anchor.y - currentPan.y) * scaleRatio,
101
- }, viewportWidth, viewportHeight);
102
-
103
- setZoom(nextZoom);
104
- setPan(nextPan);
105
- }, [clampPan]);
106
-
107
- const zoomByFactor = useCallback((factor: number, viewportWidth: number, viewportHeight: number, anchor?: PointerPoint) => {
108
- setAnimate(false);
109
- const point = anchor ?? { x: viewportWidth / 2, y: viewportHeight / 2 };
110
- zoomAtPoint(zoomRef.current * factor, point, viewportWidth, viewportHeight);
111
- }, [setAnimate, zoomAtPoint]);
112
-
113
- const zoomIn = useCallback((viewportWidth?: number, viewportHeight?: number) => {
114
- if (viewportWidth && viewportHeight) {
115
- zoomByFactor(1.2, viewportWidth, viewportHeight);
116
- return;
117
- }
118
- setZoom((current) => clamp(current + 0.1, MIN_ZOOM, MAX_ZOOM));
119
- }, [zoomByFactor]);
120
-
121
- const zoomOut = useCallback((viewportWidth?: number, viewportHeight?: number) => {
122
- if (viewportWidth && viewportHeight) {
123
- zoomByFactor(1 / 1.2, viewportWidth, viewportHeight);
124
- return;
125
- }
126
- setZoom((current) => clamp(current - 0.1, MIN_ZOOM, MAX_ZOOM));
127
- }, [zoomByFactor]);
128
-
129
- const resetView = useCallback(() => {
130
- setAnimate(true);
131
- setPan({ x: 0, y: 0 });
132
- setZoom(1);
133
- }, [setAnimate]);
134
-
135
- const fitToGraph = useCallback((
136
- positions: Map<string, GraphPosition>,
137
- viewportWidth: number,
138
- viewportHeight: number,
139
- layoutOptions?: LayoutOptions,
140
- ) => {
141
- setAnimate(true);
142
- if (positions.size === 0) {
143
- setPan({ x: 0, y: 0 });
144
- setZoom(1);
145
- return;
146
- }
147
-
148
- const nodeWidth = layoutOptions?.nodeWidth ?? 280;
149
- const nodeHeight = layoutOptions?.nodeHeight ?? 100;
150
-
151
- const entries = Array.from(positions.values());
152
- const minX = Math.min(...entries.map((p) => p.x));
153
- const minY = Math.min(...entries.map((p) => p.y));
154
- const maxX = Math.max(...entries.map((p) => p.x + nodeWidth));
155
- const maxY = Math.max(...entries.map((p) => p.y + nodeHeight));
156
-
157
- const graphWidth = Math.max(1, maxX - minX);
158
- const graphHeight = Math.max(1, maxY - minY);
159
- const availableWidth = Math.max(1, viewportWidth - FIT_PADDING * 2);
160
- const availableHeight = Math.max(1, viewportHeight - FIT_PADDING * 2);
161
- const nextZoom = clamp(Math.min(availableWidth / graphWidth, availableHeight / graphHeight), MIN_ZOOM, MAX_ZOOM);
162
-
163
- const panX = (viewportWidth - graphWidth * nextZoom) / 2 - minX * nextZoom;
164
- const panY = (viewportHeight - graphHeight * nextZoom) / 2 - minY * nextZoom;
165
-
166
- setZoom(nextZoom);
167
- setPan(clampPan({ x: panX, y: panY }, viewportWidth, viewportHeight));
168
- }, [clampPan, setAnimate]);
169
-
170
- const onPointerDown = useCallback((pointerId: number, point: PointerPoint) => {
171
- pointersRef.current.set(pointerId, point);
172
- if (pointersRef.current.size === 2) {
173
- const [a, b] = Array.from(pointersRef.current.values());
174
- pinchRef.current = {
175
- distance: Math.hypot(a.x - b.x, a.y - b.y),
176
- zoom: zoomRef.current,
177
- pan: panRef.current,
178
- midpoint: { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 },
179
- };
180
- dragStateRef.current = null;
181
- return;
182
- }
183
- dragStateRef.current = { start: point, panStart: panRef.current };
184
- }, []);
185
-
186
- const onPointerMove = useCallback((pointerId: number, point: PointerPoint, viewportWidth: number, viewportHeight: number) => {
187
- if (pointersRef.current.has(pointerId)) pointersRef.current.set(pointerId, point);
188
-
189
- if (pointersRef.current.size >= 2 && pinchRef.current) {
190
- setAnimate(false);
191
- const [a, b] = Array.from(pointersRef.current.values());
192
- const distance = Math.hypot(a.x - b.x, a.y - b.y);
193
- const factor = distance / Math.max(1, pinchRef.current.distance);
194
- const midpoint = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
195
- const nextZoom = clamp(pinchRef.current.zoom * factor, MIN_ZOOM, MAX_ZOOM);
196
- const ratio = nextZoom / pinchRef.current.zoom;
197
- const nextPan = clampPan({
198
- x: midpoint.x - (pinchRef.current.midpoint.x - pinchRef.current.pan.x) * ratio,
199
- y: midpoint.y - (pinchRef.current.midpoint.y - pinchRef.current.pan.y) * ratio,
200
- }, viewportWidth, viewportHeight);
201
- setZoom(nextZoom);
202
- setPan(nextPan);
203
- return;
204
- }
205
-
206
- const dragState = dragStateRef.current;
207
- if (!dragState) return;
208
- setAnimate(false);
209
- const nextPan = {
210
- x: dragState.panStart.x + (point.x - dragState.start.x),
211
- y: dragState.panStart.y + (point.y - dragState.start.y),
212
- };
213
- setPan(clampPan(nextPan, viewportWidth, viewportHeight));
214
- }, [clampPan, setAnimate]);
215
-
216
- const onPointerUp = useCallback((pointerId: number) => {
217
- pointersRef.current.delete(pointerId);
218
- if (pointersRef.current.size < 2) pinchRef.current = null;
219
- if (pointersRef.current.size === 0) dragStateRef.current = null;
220
- }, []);
221
-
222
- const onWheelZoom = useCallback((
223
- deltaY: number,
224
- point: PointerPoint,
225
- viewportWidth: number,
226
- viewportHeight: number,
227
- ) => {
228
- const factor = deltaY < 0 ? 1.1 : 0.9;
229
- setAnimate(false);
230
- zoomAtPoint(zoomRef.current * factor, point, viewportWidth, viewportHeight);
231
- }, [setAnimate, zoomAtPoint]);
232
-
233
- const handleKeyDown = useCallback((
234
- event: ReactKeyboardEvent,
235
- viewportWidth: number,
236
- viewportHeight: number,
237
- positions: Map<string, GraphPosition>,
238
- layoutOptions?: LayoutOptions,
239
- ) => {
240
- if (isEditableTarget(event.target)) return;
241
-
242
- const modifier = event.metaKey || event.ctrlKey;
243
- if (event.key === "Escape") {
244
- event.preventDefault();
245
- resetView();
246
- return;
247
- }
248
-
249
- if (!modifier) return;
250
-
251
- if (event.key === "=" || event.key === "+") {
252
- event.preventDefault();
253
- zoomByFactor(1.2, viewportWidth, viewportHeight);
254
- return;
255
- }
256
-
257
- if (event.key === "-") {
258
- event.preventDefault();
259
- zoomByFactor(1 / 1.2, viewportWidth, viewportHeight);
260
- return;
261
- }
262
-
263
- if (event.key === "0") {
264
- event.preventDefault();
265
- resetView();
266
- return;
267
- }
268
-
269
- if ((event.key === "f" || event.key === "F") && event.shiftKey) {
270
- event.preventDefault();
271
- fitToGraph(positions, viewportWidth, viewportHeight, layoutOptions);
272
- }
273
- }, [fitToGraph, resetView, zoomByFactor]);
274
-
275
- return {
276
- pan,
277
- zoom,
278
- zoomPercent,
279
- transform,
280
- transitioning,
281
- zoomIn,
282
- zoomOut,
283
- resetView,
284
- fitToGraph,
285
- setAnimate,
286
- onPointerDown,
287
- onPointerMove,
288
- onPointerUp,
289
- onWheelZoom,
290
- handleKeyDown,
291
- };
292
- }
@@ -1,65 +0,0 @@
1
- import { getScopedItem, removeScopedItem, setScopedItem } from "@fusion/dashboard/app/utils/projectStorage";
2
-
3
- export type NodePositions = Record<string, { x: number; y: number }>;
4
-
5
- const STORAGE_KEY = "fusion-plugin-dependency-graph:positions";
6
-
7
- function isPosition(value: unknown): value is { x: number; y: number } {
8
- if (!value || typeof value !== "object") return false;
9
- const candidate = value as { x?: unknown; y?: unknown };
10
- return typeof candidate.x === "number" && Number.isFinite(candidate.x) && typeof candidate.y === "number" && Number.isFinite(candidate.y);
11
- }
12
-
13
- export function loadPositions(projectId?: string): NodePositions {
14
- const raw = getScopedItem(STORAGE_KEY, projectId);
15
- if (!raw) return {};
16
-
17
- try {
18
- const parsed = JSON.parse(raw) as Record<string, unknown>;
19
- if (!parsed || typeof parsed !== "object") return {};
20
-
21
- const result: NodePositions = {};
22
- for (const [taskId, value] of Object.entries(parsed)) {
23
- if (isPosition(value)) {
24
- result[taskId] = value;
25
- }
26
- }
27
-
28
- return result;
29
- } catch {
30
- return {};
31
- }
32
- }
33
-
34
- export function savePositions(positions: NodePositions, visibleTaskIds: Set<string>, projectId?: string): void {
35
- const filtered: NodePositions = {};
36
- for (const [taskId, position] of Object.entries(positions)) {
37
- if (visibleTaskIds.has(taskId) && isPosition(position)) {
38
- filtered[taskId] = position;
39
- }
40
- }
41
-
42
- setScopedItem(STORAGE_KEY, JSON.stringify(filtered), projectId);
43
- }
44
-
45
- export function clearPositions(projectId?: string): void {
46
- removeScopedItem(STORAGE_KEY, projectId);
47
- }
48
-
49
- export function mergePositions(autoLayoutPositions: NodePositions, savedPositions: NodePositions, visibleTaskIds: Set<string>): NodePositions {
50
- const merged: NodePositions = {};
51
-
52
- for (const [taskId, position] of Object.entries(autoLayoutPositions)) {
53
- if (visibleTaskIds.has(taskId) && isPosition(position)) {
54
- merged[taskId] = position;
55
- }
56
- }
57
-
58
- for (const [taskId, position] of Object.entries(savedPositions)) {
59
- if (visibleTaskIds.has(taskId) && isPosition(position)) {
60
- merged[taskId] = position;
61
- }
62
- }
63
-
64
- return merged;
65
- }
@@ -1,101 +0,0 @@
1
- /* @vitest-environment jsdom */
2
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
- import {
4
- createRoadmap,
5
- createRoadmapFeature,
6
- createRoadmapMilestone,
7
- deleteRoadmap,
8
- deleteRoadmapFeature,
9
- deleteRoadmapMilestone,
10
- fetchRoadmap,
11
- fetchRoadmapHandoff,
12
- fetchRoadmaps,
13
- generateFeatureSuggestions,
14
- generateMilestoneSuggestions,
15
- moveRoadmapFeature,
16
- reorderRoadmapFeatures,
17
- reorderRoadmapMilestones,
18
- updateRoadmap,
19
- updateRoadmapFeature,
20
- updateRoadmapMilestone,
21
- } from "../dashboard/api";
22
-
23
- describe("roadmap dashboard api client", () => {
24
- const fetchMock = vi.fn();
25
-
26
- beforeEach(() => {
27
- vi.stubGlobal("fetch", fetchMock);
28
- fetchMock.mockResolvedValue({
29
- ok: true,
30
- status: 200,
31
- json: async () => ({ ok: true }),
32
- });
33
- });
34
-
35
- afterEach(() => {
36
- vi.unstubAllGlobals();
37
- vi.restoreAllMocks();
38
- });
39
-
40
- it("uses plugin namespace for roadmap CRUD wrappers", async () => {
41
- await fetchRoadmaps("proj-1");
42
- await fetchRoadmap("RM-1", "proj-1");
43
- await createRoadmap({ title: "A" }, "proj-1");
44
- await updateRoadmap("RM-1", { title: "B" }, "proj-1");
45
- await deleteRoadmap("RM-1", "proj-1");
46
-
47
- expect(fetchMock.mock.calls[0][0]).toBe("/api/plugins/roadmap-planner/roadmaps?projectId=proj-1");
48
- expect(fetchMock.mock.calls[1][0]).toBe("/api/plugins/roadmap-planner/roadmaps/RM-1?projectId=proj-1");
49
- expect(fetchMock.mock.calls[2][0]).toBe("/api/plugins/roadmap-planner/roadmaps?projectId=proj-1");
50
- expect(fetchMock.mock.calls[3][0]).toBe("/api/plugins/roadmap-planner/roadmaps/RM-1?projectId=proj-1");
51
- expect(fetchMock.mock.calls[4][0]).toBe("/api/plugins/roadmap-planner/roadmaps/RM-1?projectId=proj-1");
52
- });
53
-
54
- it("uses plugin namespace for milestone and feature reorder/move + suggestions + handoff", async () => {
55
- await createRoadmapMilestone("RM-1", { title: "M" }, "proj-1");
56
- await updateRoadmapMilestone("RMS-1", { title: "M2" }, "proj-1");
57
- await deleteRoadmapMilestone("RMS-1", "proj-1");
58
- await reorderRoadmapMilestones("RM-1", ["RMS-1", "RMS-2"], "proj-1");
59
-
60
- await createRoadmapFeature("RMS-1", { title: "F" }, "proj-1");
61
- await updateRoadmapFeature("RF-1", { title: "F2" }, "proj-1");
62
- await deleteRoadmapFeature("RF-1", "proj-1");
63
- await reorderRoadmapFeatures("RMS-1", ["RF-1", "RF-2"], "proj-1");
64
- await moveRoadmapFeature("RF-1", "RMS-2", 0, "proj-1");
65
-
66
- await generateMilestoneSuggestions("RM-1", "goal", 3, "proj-1");
67
- await generateFeatureSuggestions("RMS-1", { prompt: "p", count: 2 }, "proj-1");
68
- await fetchRoadmapHandoff("RM-1", "proj-1");
69
-
70
- const calledUrls = fetchMock.mock.calls.map((call) => String(call[0]));
71
- expect(calledUrls).toContain("/api/plugins/roadmap-planner/roadmaps/RM-1/milestones?projectId=proj-1");
72
- expect(calledUrls).toContain("/api/plugins/roadmap-planner/roadmaps/milestones/RMS-1?projectId=proj-1");
73
- expect(calledUrls).toContain("/api/plugins/roadmap-planner/roadmaps/RM-1/milestones/reorder?projectId=proj-1");
74
- expect(calledUrls).toContain("/api/plugins/roadmap-planner/roadmaps/milestones/RMS-1/features/reorder?projectId=proj-1");
75
- expect(calledUrls).toContain("/api/plugins/roadmap-planner/roadmaps/features/RF-1/move?projectId=proj-1");
76
- expect(calledUrls).toContain("/api/plugins/roadmap-planner/roadmaps/RM-1/suggestions/milestones?projectId=proj-1");
77
- expect(calledUrls).toContain("/api/plugins/roadmap-planner/roadmaps/milestones/RMS-1/suggestions/features?projectId=proj-1");
78
- expect(calledUrls).toContain("/api/plugins/roadmap-planner/roadmaps/RM-1/handoff?projectId=proj-1");
79
- });
80
-
81
- it("surfaces server error body when available", async () => {
82
- fetchMock.mockResolvedValueOnce({
83
- ok: false,
84
- status: 400,
85
- statusText: "Bad Request",
86
- json: async () => ({ error: "invalid payload" }),
87
- });
88
-
89
- await expect(fetchRoadmaps()).rejects.toThrow("invalid payload");
90
- });
91
-
92
- it("returns undefined for 204 responses", async () => {
93
- fetchMock.mockResolvedValueOnce({
94
- ok: true,
95
- status: 204,
96
- json: async () => ({}),
97
- });
98
-
99
- await expect(deleteRoadmap("RM-1")).resolves.toBeUndefined();
100
- });
101
- });
@@ -1,92 +0,0 @@
1
- import { mkdtempSync, readFileSync } from "node:fs";
2
- import { tmpdir } from "node:os";
3
- import { join, resolve } from "node:path";
4
- import { rm } from "node:fs/promises";
5
- import { Database } from "@fusion/core";
6
- import { afterEach, describe, expect, it } from "vitest";
7
- import plugin, {
8
- RoadmapStore,
9
- applyRoadmapFeatureReorder,
10
- applyRoadmapMilestoneReorder,
11
- mapAllFeaturesToTaskHandoffs,
12
- mapFeatureToTaskHandoff,
13
- mapRoadmapToMissionHandoff,
14
- mapRoadmapWithHierarchyToMissionHandoff,
15
- moveRoadmapFeature,
16
- normalizeRoadmapFeatureOrder,
17
- normalizeRoadmapMilestoneOrder,
18
- } from "../index.js";
19
-
20
- describe("roadmap-planner package surface", () => {
21
- const tmpDirs: string[] = [];
22
-
23
- afterEach(async () => {
24
- await Promise.all(tmpDirs.map((dir) => rm(dir, { recursive: true, force: true })));
25
- tmpDirs.length = 0;
26
- });
27
-
28
- it("keeps manifest and plugin entry metadata aligned", () => {
29
- const manifest = JSON.parse(readFileSync(resolve(process.cwd(), "manifest.json"), "utf8")) as {
30
- id: string;
31
- version: string;
32
- dashboardViews?: Array<{ viewId: string }>;
33
- };
34
-
35
- expect(plugin.manifest.id).toBe(manifest.id);
36
- expect(plugin.manifest.version).toBe(manifest.version);
37
- expect(plugin.dashboardViews?.[0]?.viewId).toBe(manifest.dashboardViews?.[0]?.viewId);
38
- });
39
-
40
- it("declares expected package exports", () => {
41
- const pkg = JSON.parse(readFileSync(resolve(process.cwd(), "package.json"), "utf8")) as {
42
- exports: Record<string, unknown>;
43
- };
44
-
45
- expect(pkg.exports).toHaveProperty(".");
46
- expect(pkg.exports).toHaveProperty("./server");
47
- expect(pkg.exports).toHaveProperty("./dashboard-view");
48
- });
49
-
50
- it("exports plugin manifest with roadmap id", () => {
51
- expect(plugin.manifest.id).toBe("roadmap-planner");
52
- });
53
-
54
- it("registers onSchemaInit hook that creates roadmap tables and indexes", () => {
55
- const tmpDir = mkdtempSync(join(tmpdir(), "roadmap-plugin-schema-test-"));
56
- tmpDirs.push(tmpDir);
57
-
58
- const db = new Database(join(tmpDir, ".fusion"), { inMemory: true });
59
- db.init();
60
-
61
- expect(plugin.hooks?.onSchemaInit).toBeTypeOf("function");
62
- plugin.hooks?.onSchemaInit?.(db);
63
-
64
- const tables = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table'").all() as Array<{ name: string }>;
65
- const indexes = db.prepare("SELECT name FROM sqlite_master WHERE type = 'index'").all() as Array<{ name: string }>;
66
-
67
- expect(tables.map((row) => row.name)).toEqual(expect.arrayContaining([
68
- "roadmaps",
69
- "roadmap_milestones",
70
- "roadmap_features",
71
- ]));
72
- expect(indexes.map((row) => row.name)).toEqual(expect.arrayContaining([
73
- "idxRoadmapMilestonesRoadmapOrder",
74
- "idxRoadmapFeaturesMilestoneOrder",
75
- ]));
76
-
77
- db.close();
78
- });
79
-
80
- it("re-exports roadmap domain symbols", () => {
81
- expect(typeof normalizeRoadmapMilestoneOrder).toBe("function");
82
- expect(typeof applyRoadmapMilestoneReorder).toBe("function");
83
- expect(typeof normalizeRoadmapFeatureOrder).toBe("function");
84
- expect(typeof applyRoadmapFeatureReorder).toBe("function");
85
- expect(typeof moveRoadmapFeature).toBe("function");
86
- expect(typeof mapFeatureToTaskHandoff).toBe("function");
87
- expect(typeof mapRoadmapToMissionHandoff).toBe("function");
88
- expect(typeof mapRoadmapWithHierarchyToMissionHandoff).toBe("function");
89
- expect(typeof mapAllFeaturesToTaskHandoffs).toBe("function");
90
- expect(typeof RoadmapStore).toBe("function");
91
- });
92
- });
@@ -1,48 +0,0 @@
1
- import { describe, it, expect, vi } from "vitest";
2
- import { createRoadmapPluginRoutes } from "../roadmap-routes.js";
3
-
4
- function createCtx() {
5
- return {
6
- pluginId: "roadmap-planner",
7
- taskStore: {
8
- getDatabase: () => ({}),
9
- getRootDir: () => "/tmp/project",
10
- getRoadmapStore: () => ({
11
- getRoadmap: vi.fn(() => ({ id: "RM-1", title: "R" })),
12
- getMilestone: vi.fn(() => ({ id: "MS-1", roadmapId: "RM-1", title: "M" })),
13
- listFeatures: vi.fn(() => []),
14
- }),
15
- },
16
- settings: {},
17
- logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
18
- emitEvent: vi.fn(),
19
- } as any;
20
- }
21
-
22
- describe("createRoadmapPluginRoutes", () => {
23
- it("includes PATCH roadmap routes", () => {
24
- const routes = createRoadmapPluginRoutes();
25
- expect(routes.some((r) => r.method === "PATCH" && r.path === "/roadmaps/:roadmapId")).toBe(true);
26
- });
27
-
28
- it("returns 400 for invalid milestone suggestions body", async () => {
29
- const route = createRoadmapPluginRoutes().find((r) => r.path === "/roadmaps/:roadmapId/suggestions/milestones");
30
- const result = await route!.handler({ params: { roadmapId: "RM-1" }, body: {} }, createCtx());
31
- expect(result).toMatchObject({ status: 400 });
32
- });
33
-
34
- it("returns 404 for milestone suggestions when roadmap is missing", async () => {
35
- const route = createRoadmapPluginRoutes().find((r) => r.path === "/roadmaps/:roadmapId/suggestions/milestones");
36
- const ctx = createCtx();
37
- const store = ctx.taskStore.getRoadmapStore();
38
- ctx.taskStore.getRoadmapStore = () => ({ ...store, getRoadmap: vi.fn(() => null) });
39
- const result = await route!.handler({ params: { roadmapId: "RM-404" }, body: { goalPrompt: "goal" } }, ctx);
40
- expect(result).toMatchObject({ status: 404 });
41
- });
42
-
43
- it("returns 503 for suggestions when createAiSession is unavailable", async () => {
44
- const route = createRoadmapPluginRoutes().find((r) => r.path === "/roadmaps/:roadmapId/suggestions/milestones");
45
- const result = await route!.handler({ params: { roadmapId: "RM-1" }, body: { goalPrompt: "goal" } }, createCtx());
46
- expect(result).toMatchObject({ status: 503 });
47
- });
48
- });
@@ -1,31 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from "vitest";
2
- import {
3
- __resetSuggestionState,
4
- __setCreateAiSessionFactory,
5
- generateMilestoneSuggestions,
6
- ServiceUnavailableError,
7
- } from "../roadmap-suggestions.js";
8
-
9
- describe("roadmap suggestion service", () => {
10
- beforeEach(() => {
11
- __resetSuggestionState();
12
- });
13
-
14
- it("throws when AI factory is unavailable", async () => {
15
- await expect(generateMilestoneSuggestions("goal", 1, "/tmp/project")).rejects.toBeInstanceOf(ServiceUnavailableError);
16
- });
17
-
18
- it("uses PluginContext createAiSession-compatible factory", async () => {
19
- const prompt = vi.fn().mockResolvedValue(undefined);
20
- __setCreateAiSessionFactory(async () => ({
21
- session: {
22
- prompt,
23
- state: { messages: [{ role: "assistant", content: '[{"title":"A"}]' }] },
24
- },
25
- }));
26
-
27
- const result = await generateMilestoneSuggestions("goal", 1, "/tmp/project");
28
- expect(prompt).toHaveBeenCalled();
29
- expect(result[0]?.title).toBe("A");
30
- });
31
- });