@runfusion/fusion 0.22.0 → 0.24.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 (206) hide show
  1. package/dist/bin.js +30071 -20735
  2. package/dist/client/assets/AgentDetailView-BwJaLqZh.css +1 -0
  3. package/dist/client/assets/AgentDetailView-gy_5SUj2.js +18 -0
  4. package/dist/client/assets/AgentsView-BkB9FiMT.js +29 -0
  5. package/dist/client/assets/AgentsView-CV3vm7Qk.css +1 -0
  6. package/dist/client/assets/ChatView-B_-B8fqu.js +1 -0
  7. package/dist/client/assets/ChatView-DwJAd5G1.css +1 -0
  8. package/dist/client/assets/{DevServerView-l8RCyL2k.js → DevServerView-BkvtjZBa.js} +1 -1
  9. package/dist/client/assets/{DirectoryPicker-CS1dwqcC.js → DirectoryPicker-BK-KbnhP.js} +1 -1
  10. package/dist/client/assets/{DocumentsView-DmthQWDZ.js → DocumentsView-BEg1CQAk.js} +1 -1
  11. package/dist/client/assets/{DocumentsView-BrhyOdeE.css → DocumentsView-gv4zG3aT.css} +1 -1
  12. package/dist/client/assets/EvalsView-Berf9bQm.js +1 -0
  13. package/dist/client/assets/EvalsView-CUNJ1TLc.css +1 -0
  14. package/dist/client/assets/{agentSkills-DDHJnrkn.css → ExperimentalAgentOnboardingModal-B-APN_lM.css} +1 -1
  15. package/dist/client/assets/ExperimentalAgentOnboardingModal-jcInE50G.js +499 -0
  16. package/dist/client/assets/InsightsView-B0J4mhzV.css +1 -0
  17. package/dist/client/assets/InsightsView-BX5bSF1J.js +11 -0
  18. package/dist/client/assets/{MemoryView-CPwlKnUI.js → MemoryView-CKElJY_3.js} +2 -2
  19. package/dist/client/assets/NodesView-DLUOBLf6.js +14 -0
  20. package/dist/client/assets/NodesView-DT4pXowv.css +1 -0
  21. package/dist/client/assets/{PiExtensionsManager-j8rPXqmB.js → PiExtensionsManager-COlJf0Kx.js} +2 -2
  22. package/dist/client/assets/PluginManager-CfW55BF4.js +1 -0
  23. package/dist/client/assets/PluginManager-DtRQXia5.css +1 -0
  24. package/dist/client/assets/{ResearchView-D9DNJYDq.js → ResearchView-B256Lr8I.js} +1 -1
  25. package/dist/client/assets/SettingsModal-BeA_nQtW.js +31 -0
  26. package/dist/client/assets/SettingsModal-DzsLquBu.css +1 -0
  27. package/dist/client/assets/{SettingsModal-fxvTFLtR.js → SettingsModal-yRqM4DV8.js} +1 -1
  28. package/dist/client/assets/SetupWizardModal-uUZk3TKT.js +1 -0
  29. package/dist/client/assets/{SkillsView-Ddf0YL8z.js → SkillsView-CP8JX0P_.js} +1 -1
  30. package/dist/client/assets/TodoView-Cx9cVhq7.css +1 -0
  31. package/dist/client/assets/TodoView-DCRIkDZ-.js +6 -0
  32. package/dist/client/assets/createLucideIcon-BazL2hk5.js +21 -0
  33. package/dist/client/assets/dashboard-view-BkTMSZYn.css +1 -0
  34. package/dist/client/assets/dashboard-view-CyWN-d02.js +63 -0
  35. package/dist/client/assets/dashboard-view-lR7YYmSC.js +21 -0
  36. package/dist/client/assets/{folder-open-BiJpmnaT.js → folder-open-DHjELt8-.js} +1 -1
  37. package/dist/client/assets/index-CQyVRLOb.js +692 -0
  38. package/dist/client/assets/index-CxA2Nn0_.css +1 -0
  39. package/dist/client/assets/projectDetection-G3XuxD2X.js +1 -0
  40. package/dist/client/assets/{star-BwRZmiuZ.js → star-DYesq1AV.js} +1 -1
  41. package/dist/client/assets/{upload-D4NwZhPp.js → upload-DTWF3Db5.js} +1 -1
  42. package/dist/client/assets/{users-DNISDtI1.js → users--syrel4l.js} +1 -1
  43. package/dist/client/index.html +12 -20
  44. package/dist/client/theme-data.css +106 -0
  45. package/dist/client/version.json +1 -1
  46. package/dist/droid-cli/package.json +1 -1
  47. package/dist/extension.js +17072 -9627
  48. package/dist/pi-claude-cli/package.json +1 -1
  49. package/dist/plugins/fusion-plugin-cursor-runtime/bundled.js +218 -0
  50. package/dist/plugins/fusion-plugin-cursor-runtime/manifest.json +6 -0
  51. package/dist/plugins/fusion-plugin-cursor-runtime/package.json +11 -0
  52. package/dist/plugins/fusion-plugin-dependency-graph/manifest.json +1 -1
  53. package/dist/plugins/fusion-plugin-dependency-graph/package.json +6 -4
  54. package/dist/plugins/fusion-plugin-dependency-graph/src/DependencyGraph.css +58 -0
  55. package/dist/plugins/fusion-plugin-dependency-graph/src/DependencyGraph.tsx +301 -0
  56. package/dist/plugins/fusion-plugin-dependency-graph/src/GraphHighlight.css +27 -0
  57. package/dist/plugins/fusion-plugin-dependency-graph/src/GraphTaskNode.css +157 -0
  58. package/dist/plugins/fusion-plugin-dependency-graph/src/GraphTaskNode.tsx +126 -0
  59. package/dist/plugins/fusion-plugin-dependency-graph/src/GraphToolbar.css +35 -0
  60. package/dist/plugins/fusion-plugin-dependency-graph/src/GraphToolbar.tsx +36 -0
  61. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/DependencyGraph.highlighting.test.tsx +112 -0
  62. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/DependencyGraph.persistence.test.tsx +115 -0
  63. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/DependencyGraph.test.tsx +128 -0
  64. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/GraphTaskNode.drag.test.tsx +82 -0
  65. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/GraphTaskNode.test.tsx +307 -0
  66. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/GraphToolbar.test.tsx +60 -0
  67. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/edges.test.tsx +75 -0
  68. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/filtering.test.tsx +62 -0
  69. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/filters.test.ts +78 -0
  70. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/graphPositionStorage.test.ts +95 -0
  71. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/host-integration.test.ts +74 -0
  72. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/index.test.ts +58 -0
  73. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/interactions.test.tsx +121 -0
  74. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/layout.test.ts +70 -0
  75. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/persistence.test.tsx +89 -0
  76. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/useGraphData.test.ts +86 -0
  77. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/useGraphInteraction.test.ts +167 -0
  78. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/useGraphPositions.test.ts +66 -0
  79. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/useNodeDrag.test.ts +81 -0
  80. package/dist/plugins/fusion-plugin-dependency-graph/src/dashboard-interop.d.ts +35 -0
  81. package/dist/plugins/fusion-plugin-dependency-graph/src/dashboard-view.tsx +19 -0
  82. package/dist/plugins/fusion-plugin-dependency-graph/src/edges.tsx +70 -0
  83. package/dist/plugins/fusion-plugin-dependency-graph/src/filters.ts +8 -0
  84. package/dist/plugins/fusion-plugin-dependency-graph/src/hooks/__tests__/useDependencyChain.test.ts +53 -0
  85. package/dist/plugins/fusion-plugin-dependency-graph/src/hooks/useDependencyChain.ts +60 -0
  86. package/dist/plugins/fusion-plugin-dependency-graph/src/hooks/useGraphPositions.ts +45 -0
  87. package/dist/plugins/fusion-plugin-dependency-graph/src/hooks/useNodeDrag.ts +114 -0
  88. package/dist/plugins/fusion-plugin-dependency-graph/src/index.ts +1 -2
  89. package/dist/plugins/fusion-plugin-dependency-graph/src/layout.ts +91 -0
  90. package/dist/plugins/fusion-plugin-dependency-graph/src/styles/drag.css +15 -0
  91. package/dist/plugins/fusion-plugin-dependency-graph/src/types.ts +21 -0
  92. package/dist/plugins/fusion-plugin-dependency-graph/src/useGraphData.ts +17 -0
  93. package/dist/plugins/fusion-plugin-dependency-graph/src/useGraphInteraction.ts +292 -0
  94. package/dist/plugins/fusion-plugin-dependency-graph/src/utils/graphPositionStorage.ts +65 -0
  95. package/dist/plugins/fusion-plugin-droid-runtime/bundled.js +136680 -0
  96. package/dist/plugins/fusion-plugin-droid-runtime/manifest.json +13 -0
  97. package/dist/plugins/fusion-plugin-droid-runtime/mcp-schema-server.cjs +49 -0
  98. package/dist/plugins/fusion-plugin-droid-runtime/package.json +11 -0
  99. package/dist/plugins/fusion-plugin-hermes-runtime/bundled.js +176 -7
  100. package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
  101. package/dist/plugins/fusion-plugin-openclaw-runtime/bundled.js +93 -6
  102. package/dist/plugins/fusion-plugin-openclaw-runtime/mcp-schema-server.cjs +59 -0
  103. package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
  104. package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
  105. package/dist/plugins/fusion-plugin-reports/manifest.json +33 -0
  106. package/dist/plugins/fusion-plugin-reports/package.json +26 -0
  107. package/dist/plugins/fusion-plugin-reports/src/__tests__/manifest.test.ts +51 -0
  108. package/dist/plugins/fusion-plugin-reports/src/__tests__/review-panel.test.ts +166 -0
  109. package/dist/plugins/fusion-plugin-reports/src/__tests__/settings.test.ts +157 -0
  110. package/dist/plugins/fusion-plugin-reports/src/index.ts +41 -0
  111. package/dist/plugins/fusion-plugin-reports/src/review-panel.ts +294 -0
  112. package/dist/plugins/fusion-plugin-reports/src/review-types.ts +75 -0
  113. package/dist/plugins/fusion-plugin-reports/src/settings.ts +105 -0
  114. package/dist/plugins/fusion-plugin-roadmap/manifest.json +16 -0
  115. package/dist/plugins/fusion-plugin-roadmap/package.json +48 -0
  116. package/dist/plugins/fusion-plugin-roadmap/src/__tests__/api-client.test.ts +101 -0
  117. package/dist/plugins/fusion-plugin-roadmap/src/__tests__/index.test.ts +92 -0
  118. package/dist/plugins/fusion-plugin-roadmap/src/__tests__/roadmap-routes.test.ts +48 -0
  119. package/dist/plugins/fusion-plugin-roadmap/src/__tests__/roadmap-suggestions.test.ts +31 -0
  120. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/RoadmapsView.css +1299 -0
  121. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/RoadmapsView.tsx +2559 -0
  122. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/__tests__/RoadmapsView.test.tsx +1144 -0
  123. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/__tests__/useRoadmaps.test.ts +1756 -0
  124. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/api.ts +70 -0
  125. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/test-setup.ts +7 -0
  126. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/types.ts +1 -0
  127. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/useConfirm.ts +8 -0
  128. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/useRoadmaps.ts +1188 -0
  129. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/useViewportMode.ts +20 -0
  130. package/dist/plugins/fusion-plugin-roadmap/src/dashboard-view.tsx +6 -0
  131. package/dist/plugins/fusion-plugin-roadmap/src/index.ts +74 -0
  132. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-routes.ts +1 -0
  133. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-schema.ts +41 -0
  134. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-suggestions.d.ts +15 -0
  135. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-suggestions.ts +15 -0
  136. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.d.ts +283 -0
  137. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.d.ts.map +1 -0
  138. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.js +21 -0
  139. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.js.map +1 -0
  140. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.ts +310 -0
  141. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.d.ts +5 -0
  142. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.d.ts.map +1 -0
  143. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.js +361 -0
  144. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.js.map +1 -0
  145. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.ts +408 -0
  146. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.d.ts +68 -0
  147. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.d.ts.map +1 -0
  148. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.js +300 -0
  149. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.js.map +1 -0
  150. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.ts +381 -0
  151. package/dist/plugins/fusion-plugin-roadmap/src/server/index.d.ts +3 -0
  152. package/dist/plugins/fusion-plugin-roadmap/src/server/index.ts +1 -0
  153. package/dist/plugins/fusion-plugin-roadmap/src/store/__tests__/roadmap-handoff.test.ts +445 -0
  154. package/dist/plugins/fusion-plugin-roadmap/src/store/__tests__/roadmap-ordering.test.ts +334 -0
  155. package/dist/plugins/fusion-plugin-roadmap/src/store/__tests__/roadmap-store.test.ts +1318 -0
  156. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-handoff.ts +163 -0
  157. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.d.ts +37 -0
  158. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.d.ts.map +1 -0
  159. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.js +188 -0
  160. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.js.map +1 -0
  161. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.ts +311 -0
  162. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.d.ts +299 -0
  163. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.d.ts.map +1 -0
  164. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.js +765 -0
  165. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.js.map +1 -0
  166. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.ts +1001 -0
  167. package/dist/plugins/fusion-plugin-whatsapp-chat/manifest.json +8 -0
  168. package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +34 -0
  169. package/dist/plugins/fusion-plugin-whatsapp-chat/src/__tests__/auth-state.test.ts +99 -0
  170. package/dist/plugins/fusion-plugin-whatsapp-chat/src/__tests__/connection.test.ts +145 -0
  171. package/dist/plugins/fusion-plugin-whatsapp-chat/src/__tests__/index.test.ts +216 -0
  172. package/dist/plugins/fusion-plugin-whatsapp-chat/src/__tests__/reply.test.ts +52 -0
  173. package/dist/plugins/fusion-plugin-whatsapp-chat/src/auth-state.ts +89 -0
  174. package/dist/plugins/fusion-plugin-whatsapp-chat/src/connection.ts +253 -0
  175. package/dist/plugins/fusion-plugin-whatsapp-chat/src/index.ts +262 -0
  176. package/dist/plugins/fusion-plugin-whatsapp-chat/src/qrcode.d.ts +1 -0
  177. package/dist/plugins/fusion-plugin-whatsapp-chat/src/reply.ts +37 -0
  178. package/package.json +2 -2
  179. package/skill/fusion/SKILL.md +2 -2
  180. package/skill/fusion/references/engine-tools.md +8 -2
  181. package/skill/fusion/references/extension-tools.md +39 -0
  182. package/skill/fusion/references/fusion-capabilities.md +3 -0
  183. package/dist/client/assets/AgentDetailView-BKKpbp1S.js +0 -18
  184. package/dist/client/assets/AgentDetailView-CeO_1MK7.css +0 -1
  185. package/dist/client/assets/AgentsView-BRXFmrcJ.js +0 -527
  186. package/dist/client/assets/AgentsView-Bs03ptrd.css +0 -1
  187. package/dist/client/assets/ChatView-D7L2e_qu.js +0 -1
  188. package/dist/client/assets/InsightsView-AWo5o_81.css +0 -1
  189. package/dist/client/assets/InsightsView-DvXpMKmH.js +0 -11
  190. package/dist/client/assets/NodesView-BLlfUfsy.js +0 -14
  191. package/dist/client/assets/NodesView-fXqDk9ur.css +0 -1
  192. package/dist/client/assets/PluginManager-DA_T0GHn.css +0 -1
  193. package/dist/client/assets/PluginManager-pW6RMz5z.js +0 -1
  194. package/dist/client/assets/RoadmapsView-Djc_X35v.js +0 -6
  195. package/dist/client/assets/SettingsModal-BWe0KrGY.css +0 -1
  196. package/dist/client/assets/SettingsModal-WGCF_pk8.js +0 -31
  197. package/dist/client/assets/SetupWizardModal-tG_MF_nA.js +0 -1
  198. package/dist/client/assets/agentSkills-EwIwBlG8.js +0 -1
  199. package/dist/client/assets/index-D6ebxTPF.css +0 -1
  200. package/dist/client/assets/index-DYDLmOcK.js +0 -694
  201. package/dist/plugins/fusion-plugin-dependency-graph/src/DependencyGraphView.css +0 -132
  202. package/dist/plugins/fusion-plugin-dependency-graph/src/DependencyGraphView.tsx +0 -428
  203. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/DependencyGraphView.test.tsx +0 -261
  204. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/storage.test.ts +0 -31
  205. package/dist/plugins/fusion-plugin-dependency-graph/src/storage.ts +0 -23
  206. /package/dist/client/assets/{RoadmapsView-DdGlfuu-.css → dashboard-view-DdGlfuu-.css} +0 -0
@@ -1,132 +0,0 @@
1
- .dependency-graph-view {
2
- --dependency-graph-canvas-min-height: calc(var(--space-2xl) * 10);
3
- --dependency-graph-canvas-min-height-mobile: calc(var(--space-2xl) * 8);
4
- --dependency-graph-edge-width: var(--btn-border-width);
5
- --dependency-graph-node-max-width-mobile: calc(var(--space-2xl) * 10);
6
-
7
- display: flex;
8
- flex-direction: column;
9
- gap: var(--space-md);
10
- padding: var(--space-lg);
11
- }
12
-
13
- .dependency-graph-controls {
14
- display: flex;
15
- flex-wrap: wrap;
16
- gap: var(--space-sm);
17
- }
18
-
19
- .dependency-graph-canvas {
20
- overflow: auto;
21
- border: var(--btn-border-width) solid var(--border);
22
- border-radius: var(--radius-md);
23
- background: var(--surface);
24
- min-height: var(--dependency-graph-canvas-min-height);
25
- cursor: grab;
26
- touch-action: none;
27
- }
28
-
29
- .dependency-graph-canvas:active {
30
- cursor: grabbing;
31
- }
32
-
33
- .dependency-graph-scene {
34
- position: relative;
35
- transition: transform var(--transition-fast);
36
- }
37
-
38
- .dependency-graph-edges {
39
- position: absolute;
40
- inset: 0;
41
- width: 100%;
42
- height: 100%;
43
- pointer-events: none;
44
- }
45
-
46
- .dependency-graph-edge {
47
- stroke: var(--border);
48
- stroke-width: var(--dependency-graph-edge-width);
49
- transition: stroke var(--transition-fast), opacity var(--transition-fast);
50
- }
51
-
52
- .dependency-graph-edge.is-related {
53
- stroke: var(--todo);
54
- }
55
-
56
- .dependency-graph-edge.is-dimmed {
57
- opacity: 0.4;
58
- }
59
-
60
- .dependency-graph-node {
61
- position: absolute;
62
- top: 0;
63
- left: 0;
64
- cursor: grab;
65
- transition: opacity var(--transition-fast), filter var(--transition-fast), box-shadow var(--transition-fast);
66
- }
67
-
68
- .dependency-graph-node:active {
69
- cursor: grabbing;
70
- }
71
-
72
- .dependency-graph-node .card {
73
- height: 100%;
74
- }
75
-
76
- .dependency-graph-node.is-selected .card {
77
- box-shadow: var(--focus-ring-strong);
78
- border-color: var(--todo);
79
- }
80
-
81
- .dependency-graph-node.is-related:not(.is-selected) .card {
82
- border-color: var(--in-progress);
83
- }
84
-
85
- .dependency-graph-node.is-dimmed {
86
- opacity: 0.5;
87
- filter: saturate(0.8);
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
-
101
- @media (max-width: 768px) {
102
- .dependency-graph-view {
103
- padding: var(--space-md);
104
- }
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
-
119
- .dependency-graph-canvas {
120
- flex: 1;
121
- min-height: 0;
122
- min-height: var(--dependency-graph-canvas-min-height-mobile);
123
- }
124
-
125
- .dependency-graph-node {
126
- width: min(100%, var(--dependency-graph-node-max-width-mobile)) !important;
127
- }
128
-
129
- .dependency-graph-empty {
130
- min-height: var(--dependency-graph-canvas-min-height-mobile);
131
- }
132
- }
@@ -1,428 +0,0 @@
1
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
- import type { PointerEvent as ReactPointerEvent, ReactNode, WheelEvent as ReactWheelEvent } from "react";
3
- import type { Task } from "@fusion/core";
4
- import { loadPositions, savePositions } from "./storage";
5
- import "./DependencyGraphView.css";
6
-
7
- const ACTIVE_COLUMNS = new Set(["triage", "todo", "in-progress", "in-review"]);
8
- const NODE_WIDTH_REM = 18;
9
- const NODE_HEIGHT_REM = 9;
10
- const GRID_GAP_X_REM = 3;
11
- const GRID_GAP_Y_REM = 4;
12
- const DRAG_THRESHOLD_REM = 0.5;
13
- const SCENE_PADDING_REM = 2;
14
- const FIT_PADDING_REM = 2;
15
- const MIN_SCALE = 0.4;
16
- const MAX_SCALE = 2;
17
- const WHEEL_ZOOM_FACTOR = 0.002;
18
- const MOBILE_BREAKPOINT = 768;
19
-
20
- export interface DependencyGraphHostContext {
21
- projectId?: string;
22
- tasks: Task[];
23
- openTaskDetail: (task: Task) => void;
24
- renderTaskCard: (task: Task) => ReactNode;
25
- }
26
-
27
- export interface PluginDashboardViewComponentProps {
28
- context: DependencyGraphHostContext;
29
- }
30
-
31
- type Position = { x: number; y: number };
32
-
33
- function getDistance(a: Position, b: Position): number {
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;
40
- }
41
-
42
- export function DependencyGraphView({ context }: PluginDashboardViewComponentProps) {
43
- const [scale, setScale] = useState(1);
44
- const [pan, setPan] = useState<Position>({ x: 0, y: 0 });
45
- const [nodeOverrides, setNodeOverrides] = useState<Record<string, Position>>({});
46
- const [selectedTaskId, setSelectedTaskId] = useState<string | null>(null);
47
- const [hoveredTaskId, setHoveredTaskId] = useState<string | null>(null);
48
- const persisted = useMemo(() => loadPositions(context.projectId), [context.projectId]);
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());
53
- const interactionRef = useRef<
54
- | { kind: "node"; taskId: string; startPointer: Position; startNode: Position; moved: boolean }
55
- | { kind: "pan"; startPointer: Position; startPan: Position; moved: boolean }
56
- | null
57
- >(null);
58
- const pinchRef = useRef<{ startDistance: number; startScale: number } | null>(null);
59
- const autoFitDoneRef = useRef(false);
60
-
61
- const tasks = useMemo(
62
- () => context.tasks.filter((task) => ACTIVE_COLUMNS.has(task.column)),
63
- [context.tasks],
64
- );
65
-
66
- const positioned = useMemo(() => {
67
- return tasks.map((task, index) => {
68
- const saved = nodeOverrides[task.id] ?? persisted[task.id];
69
- return {
70
- task,
71
- x: saved?.x ?? (index % 4) * (NODE_WIDTH_REM + GRID_GAP_X_REM),
72
- y: saved?.y ?? Math.floor(index / 4) * (NODE_HEIGHT_REM + GRID_GAP_Y_REM),
73
- };
74
- });
75
- }, [nodeOverrides, persisted, tasks]);
76
-
77
- const map = useMemo(() => new Map(positioned.map((node) => [node.task.id, node])), [positioned]);
78
-
79
- const edges = useMemo(() => {
80
- const lines: Array<{ from: string; to: string; x1: number; y1: number; x2: number; y2: number }> = [];
81
- positioned.forEach((node) => {
82
- (node.task.dependencies ?? []).forEach((dependencyId) => {
83
- const dependency = map.get(dependencyId);
84
- if (!dependency) return;
85
- lines.push({
86
- from: dependencyId,
87
- to: node.task.id,
88
- x1: dependency.x + NODE_WIDTH_REM,
89
- y1: dependency.y + NODE_HEIGHT_REM / 2,
90
- x2: node.x,
91
- y2: node.y + NODE_HEIGHT_REM / 2,
92
- });
93
- });
94
- });
95
- return lines;
96
- }, [map, positioned]);
97
-
98
- const bounds = useMemo(() => {
99
- if (positioned.length === 0) {
100
- return { minX: 0, minY: 0, width: NODE_WIDTH_REM * 2, height: NODE_HEIGHT_REM * 2 };
101
- }
102
-
103
- const minX = Math.min(...positioned.map((node) => node.x)) - SCENE_PADDING_REM;
104
- const minY = Math.min(...positioned.map((node) => node.y)) - SCENE_PADDING_REM;
105
- const maxX = Math.max(...positioned.map((node) => node.x + NODE_WIDTH_REM)) + SCENE_PADDING_REM;
106
- const maxY = Math.max(...positioned.map((node) => node.y + NODE_HEIGHT_REM)) + SCENE_PADDING_REM;
107
-
108
- return {
109
- minX,
110
- minY,
111
- width: Math.max(NODE_WIDTH_REM * 2, maxX - minX),
112
- height: Math.max(NODE_HEIGHT_REM * 2, maxY - minY),
113
- };
114
- }, [positioned]);
115
-
116
- const positionedForRender = useMemo(
117
- () =>
118
- positioned.map((node) => ({
119
- ...node,
120
- renderX: node.x - bounds.minX,
121
- renderY: node.y - bounds.minY,
122
- })),
123
- [bounds.minX, bounds.minY, positioned],
124
- );
125
-
126
- const edgesForRender = useMemo(
127
- () =>
128
- edges.map((edge) => ({
129
- ...edge,
130
- renderX1: edge.x1 - bounds.minX,
131
- renderY1: edge.y1 - bounds.minY,
132
- renderX2: edge.x2 - bounds.minX,
133
- renderY2: edge.y2 - bounds.minY,
134
- })),
135
- [bounds.minX, bounds.minY, edges],
136
- );
137
-
138
- const dependencyGraph = useMemo(() => {
139
- const downstream = new Map<string, Set<string>>();
140
- const upstream = new Map<string, Set<string>>();
141
-
142
- edges.forEach((edge) => {
143
- downstream.set(edge.from, (downstream.get(edge.from) ?? new Set<string>()).add(edge.to));
144
- upstream.set(edge.to, (upstream.get(edge.to) ?? new Set<string>()).add(edge.from));
145
- });
146
-
147
- return { downstream, upstream };
148
- }, [edges]);
149
-
150
- const focusTaskId = hoveredTaskId ?? selectedTaskId;
151
-
152
- const relatedTaskIds = useMemo(() => {
153
- if (!focusTaskId) return null;
154
-
155
- const related = new Set<string>([focusTaskId]);
156
- const walk = (seed: string, map: Map<string, Set<string>>) => {
157
- const queue = [seed];
158
- while (queue.length > 0) {
159
- const current = queue.shift();
160
- if (!current) continue;
161
- (map.get(current) ?? new Set<string>()).forEach((next) => {
162
- if (related.has(next)) return;
163
- related.add(next);
164
- queue.push(next);
165
- });
166
- }
167
- };
168
-
169
- walk(focusTaskId, dependencyGraph.downstream);
170
- walk(focusTaskId, dependencyGraph.upstream);
171
-
172
- return related;
173
- }, [dependencyGraph.downstream, dependencyGraph.upstream, focusTaskId]);
174
-
175
- const fitToGraph = useCallback(() => {
176
- const canvas = canvasRef.current;
177
- if (!canvas) return;
178
-
179
- const rootFontSize = Number.parseFloat(globalThis.getComputedStyle(document.documentElement).fontSize) || 16;
180
- const widthPx = bounds.width * rootFontSize;
181
- const heightPx = bounds.height * rootFontSize;
182
- const paddingPx = FIT_PADDING_REM * rootFontSize;
183
- const availableWidth = Math.max(1, canvas.clientWidth - paddingPx * 2);
184
- const availableHeight = Math.max(1, canvas.clientHeight - paddingPx * 2);
185
-
186
- const nextScale = Math.min(MAX_SCALE, Math.max(MIN_SCALE, Math.min(availableWidth / widthPx, availableHeight / heightPx)));
187
- const centeredPanX = (canvas.clientWidth - widthPx * nextScale) / (2 * rootFontSize * nextScale);
188
- const centeredPanY = (canvas.clientHeight - heightPx * nextScale) / (2 * rootFontSize * nextScale);
189
-
190
- setScale(nextScale);
191
- setPan({ x: centeredPanX, y: centeredPanY });
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]);
212
-
213
- const persistPosition = (taskId: string, next: Position) => {
214
- setNodeOverrides((current) => ({ ...current, [taskId]: next }));
215
- savePositions(context.projectId, { ...persisted, ...nodeOverrides, [taskId]: next });
216
- };
217
-
218
- const handlePointerDownOnNode = (taskId: string, event: ReactPointerEvent<HTMLDivElement>) => {
219
- setSelectedTaskId((current) => (current === taskId ? null : taskId));
220
- if (event.button !== 0) return;
221
- event.preventDefault();
222
- event.stopPropagation();
223
- const hit = map.get(taskId);
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
-
229
- interactionRef.current = {
230
- kind: "node",
231
- taskId,
232
- startPointer: { x: event.clientX, y: event.clientY },
233
- startNode: { x: hit.x, y: hit.y },
234
- moved: false,
235
- };
236
- };
237
-
238
- const handleCanvasPointerDown = (event: ReactPointerEvent<HTMLDivElement>) => {
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
-
254
- interactionRef.current = {
255
- kind: "pan",
256
- startPointer: { x: event.clientX, y: event.clientY },
257
- startPan: pan,
258
- moved: false,
259
- };
260
- };
261
-
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
-
278
- const current = interactionRef.current;
279
- if (!current) return;
280
-
281
- const delta = {
282
- x: (event.clientX - current.startPointer.x) / 16,
283
- y: (event.clientY - current.startPointer.y) / 16,
284
- };
285
-
286
- if (current.kind === "node") {
287
- const moved = getDistance({ x: 0, y: 0 }, delta) > DRAG_THRESHOLD_REM;
288
- if (moved && !current.moved) current.moved = true;
289
- if (!current.moved) return;
290
- setNodeOverrides((existing) => ({
291
- ...existing,
292
- [current.taskId]: { x: current.startNode.x + delta.x / scale, y: current.startNode.y + delta.y / scale },
293
- }));
294
- return;
295
- }
296
-
297
- const moved = getDistance({ x: 0, y: 0 }, delta) > DRAG_THRESHOLD_REM;
298
- if (moved && !current.moved) current.moved = true;
299
- if (!current.moved) return;
300
- setPan({ x: current.startPan.x + delta.x, y: current.startPan.y + delta.y });
301
- };
302
-
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
-
314
- const current = interactionRef.current;
315
- interactionRef.current = null;
316
- if (!current) return;
317
-
318
- if (current.kind === "node") {
319
- const hit = map.get(current.taskId);
320
- if (!hit) return;
321
-
322
- if (!current.moved) {
323
- context.openTaskDetail(hit.task);
324
- return;
325
- }
326
-
327
- persistPosition(current.taskId, { x: hit.x, y: hit.y });
328
- }
329
- };
330
-
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
- }));
361
- };
362
-
363
- return (
364
- <section className="dependency-graph-view">
365
- <div className="dependency-graph-controls">
366
- <button className="btn btn-sm" onClick={() => setScale((value) => Math.min(value + 0.1, MAX_SCALE))}>Zoom In</button>
367
- <button className="btn btn-sm" onClick={() => setScale((value) => Math.max(value - 0.1, MIN_SCALE))}>Zoom Out</button>
368
- <button className="btn btn-sm" onClick={fitToGraph}>Fit</button>
369
- </div>
370
-
371
- <div
372
- className="dependency-graph-canvas"
373
- ref={canvasRef}
374
- onPointerDown={handleCanvasPointerDown}
375
- onPointerMove={handlePointerMove}
376
- onPointerUp={handlePointerUp}
377
- onPointerCancel={handlePointerCancel}
378
- onWheel={handleWheel}
379
- >
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>
422
- ))}
423
- </div>
424
- )}
425
- </div>
426
- </section>
427
- );
428
- }