@parhelia/core 0.1.12350 → 0.1.12390

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 (252) hide show
  1. package/dist/agents-view/AgentsView.js +1 -1
  2. package/dist/agents-view/AgentsView.js.map +1 -1
  3. package/dist/components/ui/LanguageSelector.js +1 -3
  4. package/dist/components/ui/LanguageSelector.js.map +1 -1
  5. package/dist/components/ui/dialog.js +1 -1
  6. package/dist/components/ui/dialog.js.map +1 -1
  7. package/dist/config/config.js +56 -16
  8. package/dist/config/config.js.map +1 -1
  9. package/dist/config/notificationRoutes.js +10 -0
  10. package/dist/config/notificationRoutes.js.map +1 -1
  11. package/dist/config/types/workspace.d.ts +6 -0
  12. package/dist/config/types.d.ts +2 -5
  13. package/dist/editor/Editor.js +11 -4
  14. package/dist/editor/Editor.js.map +1 -1
  15. package/dist/editor/FieldListField.js +6 -4
  16. package/dist/editor/FieldListField.js.map +1 -1
  17. package/dist/editor/ai/AgentCostDisplay.d.ts +1 -0
  18. package/dist/editor/ai/AgentCostDisplay.js +1 -1
  19. package/dist/editor/ai/AgentCostDisplay.js.map +1 -1
  20. package/dist/editor/ai/AgentTerminal.js +144 -36
  21. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  22. package/dist/editor/ai/AgentTerminalStatusBar.d.ts +2 -0
  23. package/dist/editor/ai/AgentTerminalStatusBar.js +22 -37
  24. package/dist/editor/ai/AgentTerminalStatusBar.js.map +1 -1
  25. package/dist/editor/ai/AiResponseMessage.js +0 -1
  26. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  27. package/dist/editor/ai/ContentInspectorPopover.d.ts +17 -0
  28. package/dist/editor/ai/ContentInspectorPopover.js +136 -0
  29. package/dist/editor/ai/ContentInspectorPopover.js.map +1 -0
  30. package/dist/editor/ai/ContextInfoBar.js +55 -2
  31. package/dist/editor/ai/ContextInfoBar.js.map +1 -1
  32. package/dist/editor/ai/InlineAiDialog.js +1 -7
  33. package/dist/editor/ai/InlineAiDialog.js.map +1 -1
  34. package/dist/editor/ai/ToolCallDisplay.d.ts +4 -0
  35. package/dist/editor/ai/ToolCallDisplay.js +43 -8
  36. package/dist/editor/ai/ToolCallDisplay.js.map +1 -1
  37. package/dist/editor/ai/dialogs/AgentDialogHandler.js +46 -22
  38. package/dist/editor/ai/dialogs/AgentDialogHandler.js.map +1 -1
  39. package/dist/editor/client/EditorShell.js +69 -26
  40. package/dist/editor/client/EditorShell.js.map +1 -1
  41. package/dist/editor/client/editContext.d.ts +3 -2
  42. package/dist/editor/client/hooks/useQuota.d.ts +2 -1
  43. package/dist/editor/client/hooks/useQuota.js.map +1 -1
  44. package/dist/editor/client/hooks/useSocketMessageHandler.js +28 -0
  45. package/dist/editor/client/hooks/useSocketMessageHandler.js.map +1 -1
  46. package/dist/editor/client/operations.d.ts +1 -0
  47. package/dist/editor/client/operations.js +67 -15
  48. package/dist/editor/client/operations.js.map +1 -1
  49. package/dist/editor/client/waitForEditOperationTerminal.d.ts +11 -0
  50. package/dist/editor/client/waitForEditOperationTerminal.js +40 -0
  51. package/dist/editor/client/waitForEditOperationTerminal.js.map +1 -0
  52. package/dist/editor/commands/commands.d.ts +11 -1
  53. package/dist/editor/commands/commands.js +12 -1
  54. package/dist/editor/commands/commands.js.map +1 -1
  55. package/dist/editor/commands/customCommandConverter.d.ts +8 -1
  56. package/dist/editor/commands/customCommandConverter.js +33 -4
  57. package/dist/editor/commands/customCommandConverter.js.map +1 -1
  58. package/dist/editor/commands/handlers/uiActionHandlers.d.ts +6 -0
  59. package/dist/editor/commands/handlers/uiActionHandlers.js +84 -0
  60. package/dist/editor/commands/handlers/uiActionHandlers.js.map +1 -0
  61. package/dist/editor/commands/itemCommands.js +6 -2
  62. package/dist/editor/commands/itemCommands.js.map +1 -1
  63. package/dist/editor/commands/keyboardCommands.d.ts +10 -0
  64. package/dist/editor/commands/keyboardCommands.js +142 -0
  65. package/dist/editor/commands/keyboardCommands.js.map +1 -0
  66. package/dist/editor/commands/undo.d.ts +9 -15
  67. package/dist/editor/commands/undo.js +24 -0
  68. package/dist/editor/commands/undo.js.map +1 -1
  69. package/dist/editor/menubar/PageSelector.js +1 -3
  70. package/dist/editor/menubar/PageSelector.js.map +1 -1
  71. package/dist/editor/menubar/VersionSelector.js +1 -3
  72. package/dist/editor/menubar/VersionSelector.js.map +1 -1
  73. package/dist/editor/menubar/toolbar-sections/CustomCommandsToolbar.js +7 -36
  74. package/dist/editor/menubar/toolbar-sections/CustomCommandsToolbar.js.map +1 -1
  75. package/dist/editor/menubar/toolbar-sections/UtilityControls.js +10 -1
  76. package/dist/editor/menubar/toolbar-sections/UtilityControls.js.map +1 -1
  77. package/dist/editor/notifications/notificationRoutes.js +1 -0
  78. package/dist/editor/notifications/notificationRoutes.js.map +1 -1
  79. package/dist/editor/page-editor-chrome/InlineEditor.js +53 -36
  80. package/dist/editor/page-editor-chrome/InlineEditor.js.map +1 -1
  81. package/dist/editor/page-viewer/PageViewer.js +60 -6
  82. package/dist/editor/page-viewer/PageViewer.js.map +1 -1
  83. package/dist/editor/reviews/Comment.js +12 -10
  84. package/dist/editor/reviews/Comment.js.map +1 -1
  85. package/dist/editor/reviews/CommentDisplayPopover.js +1 -3
  86. package/dist/editor/reviews/CommentDisplayPopover.js.map +1 -1
  87. package/dist/editor/reviews/PreviewInfo.js +1 -4
  88. package/dist/editor/reviews/PreviewInfo.js.map +1 -1
  89. package/dist/editor/reviews/reviewCommands.js +4 -1
  90. package/dist/editor/reviews/reviewCommands.js.map +1 -1
  91. package/dist/editor/reviews/useReviews.d.ts +2 -2
  92. package/dist/editor/reviews/useReviews.js +12 -30
  93. package/dist/editor/reviews/useReviews.js.map +1 -1
  94. package/dist/editor/services/agentService.d.ts +26 -0
  95. package/dist/editor/services/agentService.js +41 -0
  96. package/dist/editor/services/agentService.js.map +1 -1
  97. package/dist/editor/services/aiService.d.ts +7 -1
  98. package/dist/editor/services/aiService.js +13 -1
  99. package/dist/editor/services/aiService.js.map +1 -1
  100. package/dist/editor/services/notificationService.d.ts +1 -0
  101. package/dist/editor/services/notificationService.js +1 -0
  102. package/dist/editor/services/notificationService.js.map +1 -1
  103. package/dist/editor/services/reviewsService.d.ts +2 -5
  104. package/dist/editor/services/reviewsService.js +0 -10
  105. package/dist/editor/services/reviewsService.js.map +1 -1
  106. package/dist/editor/services/systemService.d.ts +2 -1
  107. package/dist/editor/services/systemService.js +3 -0
  108. package/dist/editor/services/systemService.js.map +1 -1
  109. package/dist/editor/settings/QuotaInfo.js +15 -7
  110. package/dist/editor/settings/QuotaInfo.js.map +1 -1
  111. package/dist/editor/settings/index/useIndexStatus.js +1 -1
  112. package/dist/editor/settings/index/useIndexStatus.js.map +1 -1
  113. package/dist/editor/settings/panels/AgentProfileConfigPanel.d.ts +10 -0
  114. package/dist/editor/settings/panels/AgentProfileConfigPanel.js +61 -0
  115. package/dist/editor/settings/panels/AgentProfileConfigPanel.js.map +1 -0
  116. package/dist/editor/settings/panels/AgentsPanel.d.ts +0 -4
  117. package/dist/editor/settings/panels/AgentsPanel.js +101 -109
  118. package/dist/editor/settings/panels/AgentsPanel.js.map +1 -1
  119. package/dist/editor/settings/panels/CreateAgentProfileDialog.d.ts +7 -0
  120. package/dist/editor/settings/panels/CreateAgentProfileDialog.js +48 -0
  121. package/dist/editor/settings/panels/CreateAgentProfileDialog.js.map +1 -0
  122. package/dist/editor/settings/panels/GroupedFieldConfigPanel.d.ts +33 -0
  123. package/dist/editor/settings/panels/GroupedFieldConfigPanel.js +91 -0
  124. package/dist/editor/settings/panels/GroupedFieldConfigPanel.js.map +1 -0
  125. package/dist/editor/settings/panels/ModelConfigPanel.d.ts +10 -0
  126. package/dist/editor/settings/panels/ModelConfigPanel.js +51 -0
  127. package/dist/editor/settings/panels/ModelConfigPanel.js.map +1 -0
  128. package/dist/editor/settings/panels/ModelsPanel.js +201 -70
  129. package/dist/editor/settings/panels/ModelsPanel.js.map +1 -1
  130. package/dist/editor/settings/panels/ProjectTemplateAgentPanel.d.ts +10 -0
  131. package/dist/editor/settings/panels/ProjectTemplateAgentPanel.js +46 -0
  132. package/dist/editor/settings/panels/ProjectTemplateAgentPanel.js.map +1 -0
  133. package/dist/editor/settings/panels/ProjectTemplatesPanel.d.ts +2 -0
  134. package/dist/editor/settings/panels/ProjectTemplatesPanel.js +1340 -0
  135. package/dist/editor/settings/panels/ProjectTemplatesPanel.js.map +1 -0
  136. package/dist/editor/settings/panels/ProviderConfigPanel.d.ts +10 -0
  137. package/dist/editor/settings/panels/ProviderConfigPanel.js +32 -0
  138. package/dist/editor/settings/panels/ProviderConfigPanel.js.map +1 -0
  139. package/dist/editor/settings/panels/ProvidersPanel.js +46 -4
  140. package/dist/editor/settings/panels/ProvidersPanel.js.map +1 -1
  141. package/dist/editor/settings/panels/SearchConfigPanel.js +3 -3
  142. package/dist/editor/settings/panels/SearchConfigPanel.js.map +1 -1
  143. package/dist/editor/settings/panels/index.d.ts +1 -2
  144. package/dist/editor/settings/panels/index.js +1 -2
  145. package/dist/editor/settings/panels/index.js.map +1 -1
  146. package/dist/editor/sidebar/WorkspaceRailMobile.d.ts +1 -3
  147. package/dist/editor/sidebar/WorkspaceRailMobile.js +3 -5
  148. package/dist/editor/sidebar/WorkspaceRailMobile.js.map +1 -1
  149. package/dist/editor/ui/ItemNameDialogNew.js +15 -9
  150. package/dist/editor/ui/ItemNameDialogNew.js.map +1 -1
  151. package/dist/editor/utils/keyboardNavigation.d.ts +6 -20
  152. package/dist/editor/utils/keyboardNavigation.js +48 -139
  153. package/dist/editor/utils/keyboardNavigation.js.map +1 -1
  154. package/dist/revision.d.ts +2 -2
  155. package/dist/revision.js +2 -2
  156. package/dist/setup/services/setupWizardService.d.ts +8 -10
  157. package/dist/setup/services/setupWizardService.js +4 -17
  158. package/dist/setup/services/setupWizardService.js.map +1 -1
  159. package/dist/splash-screen/ModernSplashScreen.js +100 -18
  160. package/dist/splash-screen/ModernSplashScreen.js.map +1 -1
  161. package/dist/splash-screen/ParheliaAssistantChat.js +3 -22
  162. package/dist/splash-screen/ParheliaAssistantChat.js.map +1 -1
  163. package/dist/splash-screen/ParheliaLogo.js +87 -37
  164. package/dist/splash-screen/ParheliaLogo.js.map +1 -1
  165. package/dist/task-board/TaskBoardWorkspace.js +201 -66
  166. package/dist/task-board/TaskBoardWorkspace.js.map +1 -1
  167. package/dist/task-board/components/AssignAgentDialog.js +9 -4
  168. package/dist/task-board/components/AssignAgentDialog.js.map +1 -1
  169. package/dist/task-board/components/CreateProjectDialog.js +32 -34
  170. package/dist/task-board/components/CreateProjectDialog.js.map +1 -1
  171. package/dist/task-board/components/CreateTaskDialog.d.ts +2 -0
  172. package/dist/task-board/components/CreateTaskDialog.js +35 -11
  173. package/dist/task-board/components/CreateTaskDialog.js.map +1 -1
  174. package/dist/task-board/components/ProjectPropertiesPanel.js +7 -4
  175. package/dist/task-board/components/ProjectPropertiesPanel.js.map +1 -1
  176. package/dist/task-board/components/TaskAgentPanel.js +13 -4
  177. package/dist/task-board/components/TaskAgentPanel.js.map +1 -1
  178. package/dist/task-board/components/TaskAssigneePicker.d.ts +2 -2
  179. package/dist/task-board/components/TaskAssigneePicker.js +12 -4
  180. package/dist/task-board/components/TaskAssigneePicker.js.map +1 -1
  181. package/dist/task-board/components/TaskAttachmentsSection.js +1 -1
  182. package/dist/task-board/components/TaskAttachmentsSection.js.map +1 -1
  183. package/dist/task-board/components/TaskBoardProjectListSidebar.js.map +1 -1
  184. package/dist/task-board/components/TaskBoardTitlebar.js +1 -1
  185. package/dist/task-board/components/TaskBoardTitlebar.js.map +1 -1
  186. package/dist/task-board/components/TaskCard.js +2 -2
  187. package/dist/task-board/components/TaskCard.js.map +1 -1
  188. package/dist/task-board/components/TaskDetailPanel.js +2 -2
  189. package/dist/task-board/components/TaskDetailPanel.js.map +1 -1
  190. package/dist/task-board/components/TaskRow.js +2 -2
  191. package/dist/task-board/components/TaskRow.js.map +1 -1
  192. package/dist/task-board/components/WizardCommunicationCenter.d.ts +2 -0
  193. package/dist/task-board/components/WizardCommunicationCenter.js +19 -8
  194. package/dist/task-board/components/WizardCommunicationCenter.js.map +1 -1
  195. package/dist/task-board/components/WizardTaskDetailsPanel.js +1 -1
  196. package/dist/task-board/components/WizardTaskDetailsPanel.js.map +1 -1
  197. package/dist/task-board/index.d.ts +1 -0
  198. package/dist/task-board/index.js +1 -0
  199. package/dist/task-board/index.js.map +1 -1
  200. package/dist/task-board/services/taskService.d.ts +13 -2
  201. package/dist/task-board/services/taskService.js +26 -2
  202. package/dist/task-board/services/taskService.js.map +1 -1
  203. package/dist/task-board/types.d.ts +71 -5
  204. package/dist/task-board/views/DependencyGraphView.d.ts +39 -0
  205. package/dist/task-board/views/DependencyGraphView.js +702 -0
  206. package/dist/task-board/views/DependencyGraphView.js.map +1 -0
  207. package/dist/task-board/views/KanbanView.d.ts +0 -4
  208. package/dist/task-board/views/KanbanView.js +4 -10
  209. package/dist/task-board/views/KanbanView.js.map +1 -1
  210. package/dist/task-board/views/ListView.d.ts +0 -4
  211. package/dist/task-board/views/ListView.js +3 -9
  212. package/dist/task-board/views/ListView.js.map +1 -1
  213. package/dist/task-board/views/WizardView.js +4 -4
  214. package/dist/task-board/views/WizardView.js.map +1 -1
  215. package/dist/types.d.ts +23 -15
  216. package/package.json +11 -8
  217. package/dist/editor/settings/Setup.d.ts +0 -1
  218. package/dist/editor/settings/Setup.js +0 -211
  219. package/dist/editor/settings/Setup.js.map +0 -1
  220. package/dist/editor/settings/panels/DatabasePanel.d.ts +0 -6
  221. package/dist/editor/settings/panels/DatabasePanel.js +0 -50
  222. package/dist/editor/settings/panels/DatabasePanel.js.map +0 -1
  223. package/dist/editor/settings/setup-steps/AiSetupStep/EmbeddingsModelSection.d.ts +0 -2
  224. package/dist/editor/settings/setup-steps/AiSetupStep/EmbeddingsModelSection.js +0 -195
  225. package/dist/editor/settings/setup-steps/AiSetupStep/EmbeddingsModelSection.js.map +0 -1
  226. package/dist/editor/settings/setup-steps/AiSetupStep/index.d.ts +0 -2
  227. package/dist/editor/settings/setup-steps/AiSetupStep/index.js +0 -21
  228. package/dist/editor/settings/setup-steps/AiSetupStep/index.js.map +0 -1
  229. package/dist/editor/settings/setup-steps/AiSetupStep/provider/ProviderSection.d.ts +0 -1
  230. package/dist/editor/settings/setup-steps/AiSetupStep/provider/ProviderSection.js +0 -233
  231. package/dist/editor/settings/setup-steps/AiSetupStep/provider/ProviderSection.js.map +0 -1
  232. package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersList.d.ts +0 -15
  233. package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersList.js +0 -14
  234. package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersList.js.map +0 -1
  235. package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersSection.d.ts +0 -1
  236. package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersSection.js +0 -94
  237. package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersSection.js.map +0 -1
  238. package/dist/editor/settings/setup-steps/AiSetupStep/types.d.ts +0 -1
  239. package/dist/editor/settings/setup-steps/AiSetupStep/types.js +0 -2
  240. package/dist/editor/settings/setup-steps/AiSetupStep/types.js.map +0 -1
  241. package/dist/editor/settings/setup-steps/AiSetupStep/utils.d.ts +0 -5
  242. package/dist/editor/settings/setup-steps/AiSetupStep/utils.js +0 -44
  243. package/dist/editor/settings/setup-steps/AiSetupStep/utils.js.map +0 -1
  244. package/dist/editor/settings/setup-steps/IndexSetupStep.d.ts +0 -2
  245. package/dist/editor/settings/setup-steps/IndexSetupStep.js +0 -36
  246. package/dist/editor/settings/setup-steps/IndexSetupStep.js.map +0 -1
  247. package/dist/editor/settings/setup-steps/SettingsSetupStep.d.ts +0 -2
  248. package/dist/editor/settings/setup-steps/SettingsSetupStep.js +0 -111
  249. package/dist/editor/settings/setup-steps/SettingsSetupStep.js.map +0 -1
  250. package/dist/editor/settings/setup-steps/SetupOverview.d.ts +0 -14
  251. package/dist/editor/settings/setup-steps/SetupOverview.js +0 -38
  252. package/dist/editor/settings/setup-steps/SetupOverview.js.map +0 -1
@@ -0,0 +1,702 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useMemo, useRef } from "react";
3
+ import { ReactFlow, Controls, MiniMap, useNodesState, useEdgesState, Handle, Position, MarkerType, } from "@xyflow/react";
4
+ import Dagre from "@dagrejs/dagre";
5
+ import "@xyflow/react/dist/style.css";
6
+ import { toast } from "sonner";
7
+ import { normalizeTaskStatus, getTaskStatusLabel } from "../taskStatus";
8
+ import { getTaskExecutionDisplayFromTask } from "../taskExecutionStatus";
9
+ import { cn } from "../../lib/utils";
10
+ import { saveGraphLayout } from "../services/taskService";
11
+ import { Button } from "../../components/ui/button";
12
+ import { Badge } from "../../components/ui/badge";
13
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../../components/ui/tooltip";
14
+ import { Circle, PlayCircle, Clock3, Eye, CheckCircle2, ArrowUp, ArrowDown, Minus, Flame, GitBranch, RotateCcw, Loader2, Plus, Trash2, } from "lucide-react";
15
+ const NODE_WIDTH = 220;
16
+ const NODE_HEIGHT = 80;
17
+ const SAVE_DEBOUNCE_MS = 500;
18
+ const EDGE_BLOCKED_BY = "#f97316";
19
+ const EDGE_CHILD_TASK = "#0d9488";
20
+ const EDGE_RELATED = "#94a3b8";
21
+ const STATUS_STYLES = {
22
+ Todo: { bg: "bg-slate-100", text: "text-slate-600", icon: Circle },
23
+ InProgress: { bg: "bg-blue-100", text: "text-blue-700", icon: PlayCircle },
24
+ Waiting: { bg: "bg-amber-100", text: "text-amber-700", icon: Clock3 },
25
+ Review: { bg: "bg-purple-100", text: "text-purple-700", icon: Eye },
26
+ Done: { bg: "bg-green-100", text: "text-green-700", icon: CheckCircle2 },
27
+ };
28
+ const PRIORITY_CONFIG = {
29
+ Critical: { icon: Flame, color: "text-red-500", label: "Critical" },
30
+ High: { icon: ArrowUp, color: "text-orange-500", label: "High" },
31
+ Medium: { icon: Minus, color: "text-yellow-500", label: "Medium" },
32
+ Low: { icon: ArrowDown, color: "text-slate-400", label: "Low" },
33
+ };
34
+ const HANDLE_CLASS = "bg-slate-300! border-slate-400! w-2! h-2!";
35
+ function TaskGraphNode({ data }) {
36
+ const status = data.status;
37
+ const statusStyle = STATUS_STYLES[status] || STATUS_STYLES.Todo;
38
+ const StatusIcon = statusStyle.icon;
39
+ const executionDisplay = data.showExecutionStateBadge
40
+ ? getTaskExecutionDisplayFromTask({
41
+ taskStatus: status,
42
+ executionState: data.executionState,
43
+ isBlocked: data.isBlocked,
44
+ })
45
+ : null;
46
+ const priorityCfg = data.priority ? PRIORITY_CONFIG[data.priority] : null;
47
+ const PriorityIcon = priorityCfg?.icon;
48
+ const actionButtonClass = "nodrag nopan absolute z-20 flex h-5 w-5 items-center justify-center rounded-full border border-slate-200 bg-white text-slate-500 shadow-sm transition-colors hover:border-blue-200 hover:bg-blue-50 hover:text-blue-600";
49
+ return (_jsxs("div", { className: cn("relative cursor-pointer rounded-lg border bg-white px-3 py-2 shadow-sm transition-all", data.isSelected
50
+ ? "ring-primary border-primary/50 ring-2"
51
+ : "border-slate-200 hover:border-slate-300 hover:shadow-md", data.isBlocked && !data.isSelected && "border-red-200 bg-red-50/40"), style: { width: NODE_WIDTH }, "data-testid": `dependency-graph-node-${data.taskId}`, children: [_jsx(Handle, { type: "target", position: Position.Left, id: "left", className: HANDLE_CLASS, "data-testid": `dependency-graph-target-left-${data.taskId}` }), _jsx(Handle, { type: "target", position: Position.Top, id: "top", className: HANDLE_CLASS, "data-testid": `dependency-graph-target-top-${data.taskId}` }), _jsxs("div", { className: "flex items-start justify-between gap-1", children: [_jsxs("div", { className: "flex min-w-0 flex-1 items-center justify-between gap-1", children: [_jsx("span", { className: "truncate text-[10px] font-medium text-slate-400", children: data.taskKey || data.taskId.slice(0, 8).toUpperCase() }), PriorityIcon && (_jsx(PriorityIcon, { className: cn("h-3 w-3 shrink-0", priorityCfg.color) }))] }), data.onRemoveTask && (_jsx("button", { type: "button", className: "text-slate-400 hover:bg-red-50 hover:text-red-600 -mr-0.5 -mt-px shrink-0 rounded p-0.5 transition-colors", "aria-label": "Remove task", "data-testid": `dependency-graph-remove-task-${data.taskId}`, onClick: (event) => {
52
+ event.stopPropagation();
53
+ data.onRemoveTask?.(data.taskId);
54
+ }, children: _jsx(Trash2, { className: "h-2.5 w-2.5", strokeWidth: 1.5 }) }))] }), _jsx("div", { className: "mt-0.5 truncate text-xs font-medium text-slate-800", children: data.title }), _jsxs("div", { className: "mt-1 flex items-center gap-1.5", children: [executionDisplay ? (_jsxs(Badge, { variant: executionDisplay.variant, className: cn("h-5 max-w-full px-1.5 text-[10px] font-bold uppercase tracking-wider", executionDisplay.className), children: [executionDisplay.showSpinner && (_jsx(Loader2, { className: "mr-1 h-2.5 w-2.5 animate-spin" })), executionDisplay.label] })) : (_jsxs("span", { className: cn("inline-flex items-center gap-0.5 rounded-full px-1.5 py-0 text-[10px] font-medium", statusStyle.bg, statusStyle.text), children: [_jsx(StatusIcon, { className: "h-2.5 w-2.5" }), getTaskStatusLabel(status)] })), data.assigneeDisplayName && (_jsxs("span", { className: "truncate text-[10px] text-slate-400", children: ["@", data.assigneeDisplayName] }))] }), data.onAddDependentTaskFromNode && (_jsxs(Tooltip, { delayDuration: 250, children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", className: cn(actionButtonClass, "top-1/2 right-0 translate-x-[calc(100%+6px)] -translate-y-1/2"), "aria-label": "Add dependent task", "data-testid": `dependency-graph-add-dependent-task-${data.taskId}`, onClick: (event) => {
55
+ event.stopPropagation();
56
+ data.onSelect(data.taskId);
57
+ data.onAddDependentTaskFromNode?.(data.taskId);
58
+ }, onMouseDown: (event) => {
59
+ event.stopPropagation();
60
+ }, children: _jsx(Plus, { className: "h-3 w-3", strokeWidth: 1.75 }) }) }), _jsx(TooltipContent, { side: "right", sideOffset: 8, children: "Add dependent task" })] })), data.onAddChildTaskFromNode && (_jsxs(Tooltip, { delayDuration: 250, children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", className: cn(actionButtonClass, "bottom-0 left-1/2 -translate-x-1/2 translate-y-[calc(100%+6px)]"), "aria-label": "Add subtask", "data-testid": `dependency-graph-add-subtask-${data.taskId}`, onClick: (event) => {
61
+ event.stopPropagation();
62
+ data.onSelect(data.taskId);
63
+ data.onAddChildTaskFromNode?.(data.taskId);
64
+ }, onMouseDown: (event) => {
65
+ event.stopPropagation();
66
+ }, children: _jsx(Plus, { className: "h-3 w-3", strokeWidth: 1.75 }) }) }), _jsx(TooltipContent, { side: "bottom", sideOffset: 8, children: "Add subtask" })] })), _jsx(Handle, { type: "source", position: Position.Right, id: "right", className: HANDLE_CLASS, "data-testid": `dependency-graph-source-right-${data.taskId}` }), _jsx(Handle, { type: "source", position: Position.Bottom, id: "bottom", className: HANDLE_CLASS, "data-testid": `dependency-graph-source-bottom-${data.taskId}` })] }));
67
+ }
68
+ const nodeTypes = {
69
+ taskNode: TaskGraphNode,
70
+ };
71
+ const HIERARCHY_CHILD_INDENT = 24;
72
+ const HIERARCHY_CHILD_GAP = 32;
73
+ const HIERARCHY_SUBTREE_GAP = 80;
74
+ /**
75
+ * Compound layout for the hierarchy strategy: each parent–children group is
76
+ * laid out vertically (parent on top, children stacked below with a slight
77
+ * indent), and then the resulting subtree "super-nodes" are arranged
78
+ * left-to-right via Dagre using the dependency edges between them.
79
+ */
80
+ function layoutGraphHierarchy(nodes, edges) {
81
+ if (nodes.length === 0)
82
+ return { nodes, edges };
83
+ const childrenOf = new Map();
84
+ const parentOf = new Map();
85
+ for (const edge of edges) {
86
+ if (edge.data?.relationship !== "hierarchy")
87
+ continue;
88
+ parentOf.set(edge.target, edge.source);
89
+ const kids = childrenOf.get(edge.source) ?? [];
90
+ kids.push(edge.target);
91
+ childrenOf.set(edge.source, kids);
92
+ }
93
+ const roots = nodes.filter((n) => !parentOf.has(n.id));
94
+ function measureSubtree(nodeId, x, y, positions) {
95
+ positions.set(nodeId, { x, y });
96
+ const children = childrenOf.get(nodeId) ?? [];
97
+ if (children.length === 0) {
98
+ return { width: NODE_WIDTH, height: NODE_HEIGHT };
99
+ }
100
+ let childY = y + NODE_HEIGHT + HIERARCHY_CHILD_GAP;
101
+ let maxRight = x + NODE_WIDTH;
102
+ for (const childId of children) {
103
+ const result = measureSubtree(childId, x + HIERARCHY_CHILD_INDENT, childY, positions);
104
+ childY += result.height + HIERARCHY_CHILD_GAP;
105
+ maxRight = Math.max(maxRight, x + HIERARCHY_CHILD_INDENT + result.width);
106
+ }
107
+ return {
108
+ width: maxRight - x,
109
+ height: childY - HIERARCHY_CHILD_GAP - y,
110
+ };
111
+ }
112
+ const subtrees = new Map();
113
+ for (const root of roots) {
114
+ const positions = new Map();
115
+ const dims = measureSubtree(root.id, 0, 0, positions);
116
+ subtrees.set(root.id, { positions, ...dims });
117
+ }
118
+ const nodeToRoot = new Map();
119
+ for (const [rootId, data] of subtrees) {
120
+ for (const nodeId of data.positions.keys()) {
121
+ nodeToRoot.set(nodeId, rootId);
122
+ }
123
+ }
124
+ const g = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
125
+ g.setGraph({ rankdir: "LR", nodesep: 40, ranksep: HIERARCHY_SUBTREE_GAP });
126
+ for (const [rootId, data] of subtrees) {
127
+ g.setNode(rootId, { width: data.width, height: data.height });
128
+ }
129
+ const addedSuperEdges = new Set();
130
+ for (const edge of edges) {
131
+ if (edge.data?.relationship !== "dependency")
132
+ continue;
133
+ const srcRoot = nodeToRoot.get(edge.source);
134
+ const tgtRoot = nodeToRoot.get(edge.target);
135
+ if (srcRoot && tgtRoot && srcRoot !== tgtRoot) {
136
+ const key = `${srcRoot}->${tgtRoot}`;
137
+ if (!addedSuperEdges.has(key)) {
138
+ addedSuperEdges.add(key);
139
+ g.setEdge(srcRoot, tgtRoot);
140
+ }
141
+ }
142
+ }
143
+ Dagre.layout(g);
144
+ const finalPositions = new Map();
145
+ for (const [rootId, data] of subtrees) {
146
+ const superNode = g.node(rootId);
147
+ const offsetX = superNode.x - data.width / 2;
148
+ const offsetY = superNode.y - data.height / 2;
149
+ for (const [nodeId, localPos] of data.positions) {
150
+ finalPositions.set(nodeId, {
151
+ x: offsetX + localPos.x,
152
+ y: offsetY + localPos.y,
153
+ });
154
+ }
155
+ }
156
+ return {
157
+ nodes: nodes.map((node) => ({
158
+ ...node,
159
+ position: finalPositions.get(node.id) ?? node.position,
160
+ })),
161
+ edges,
162
+ };
163
+ }
164
+ function layoutGraph(nodes, edges, orientation, autoLayoutStrategy) {
165
+ if (autoLayoutStrategy === "hierarchy" &&
166
+ edges.some((e) => e.data?.relationship === "hierarchy")) {
167
+ return layoutGraphHierarchy(nodes, edges);
168
+ }
169
+ const g = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
170
+ g.setGraph({
171
+ rankdir: orientation === "horizontal" ? "LR" : "TB",
172
+ nodesep: 40,
173
+ ranksep: 60,
174
+ });
175
+ for (const node of nodes) {
176
+ g.setNode(node.id, { width: NODE_WIDTH, height: NODE_HEIGHT });
177
+ }
178
+ for (const edge of edges) {
179
+ g.setEdge(edge.source, edge.target);
180
+ }
181
+ Dagre.layout(g);
182
+ const layoutedNodes = nodes.map((node) => {
183
+ const dagNode = g.node(node.id);
184
+ return {
185
+ ...node,
186
+ position: {
187
+ x: dagNode.x - NODE_WIDTH / 2,
188
+ y: dagNode.y - NODE_HEIGHT / 2,
189
+ },
190
+ };
191
+ });
192
+ return { nodes: layoutedNodes, edges };
193
+ }
194
+ function buildGraph(tasks, dependencies, selectedTaskId, blockedTaskIds, orientation, showExecutionStateBadges, autoLayoutStrategy, onSelect, onAddDependentTaskFromNode, onAddChildTaskFromNode, onRemoveTask) {
195
+ const taskMap = new Map();
196
+ for (const t of tasks)
197
+ taskMap.set(t.taskId, t);
198
+ const visibleTasks = tasks;
199
+ const childRelationshipKeys = new Set(visibleTasks
200
+ .filter((task) => !!task.parentTaskId)
201
+ .map((task) => `${task.parentTaskId}->${task.taskId}`));
202
+ const nodes = visibleTasks.map((task) => ({
203
+ id: task.taskId,
204
+ type: "taskNode",
205
+ position: { x: 0, y: 0 },
206
+ data: {
207
+ taskId: task.taskId,
208
+ taskKey: task.taskKey ?? null,
209
+ title: task.title,
210
+ status: normalizeTaskStatus(task.status, task.executionState),
211
+ executionState: task.executionState,
212
+ showExecutionStateBadge: showExecutionStateBadges,
213
+ priority: task.priority ?? null,
214
+ assigneeDisplayName: task.assigneeDisplayName ?? null,
215
+ isSelected: task.taskId === selectedTaskId,
216
+ isBlocked: blockedTaskIds.has(task.taskId),
217
+ orientation,
218
+ onSelect,
219
+ onAddDependentTaskFromNode,
220
+ onAddChildTaskFromNode,
221
+ onRemoveTask,
222
+ },
223
+ }));
224
+ const depSourceHandle = orientation === "horizontal" ? "right" : "bottom";
225
+ const depTargetHandle = orientation === "horizontal" ? "left" : "top";
226
+ const hierSourceHandle = autoLayoutStrategy === "hierarchy" ? "bottom" : depSourceHandle;
227
+ const hierTargetHandle = autoLayoutStrategy === "hierarchy" ? "left" : depTargetHandle;
228
+ const dependencyEdges = dependencies
229
+ .filter((dep) => taskMap.has(dep.taskId) &&
230
+ taskMap.has(dep.dependsOnTaskId) &&
231
+ !childRelationshipKeys.has(`${dep.dependsOnTaskId}->${dep.taskId}`))
232
+ .map((dep) => {
233
+ const isBlockedBy = dep.dependencyType === "BlockedBy";
234
+ const strokeColor = isBlockedBy
235
+ ? EDGE_BLOCKED_BY
236
+ : EDGE_RELATED;
237
+ return {
238
+ id: dep.dependencyId,
239
+ source: dep.dependsOnTaskId,
240
+ target: dep.taskId,
241
+ sourceHandle: depSourceHandle,
242
+ targetHandle: depTargetHandle,
243
+ type: "smoothstep",
244
+ animated: false,
245
+ style: {
246
+ stroke: strokeColor,
247
+ strokeWidth: isBlockedBy ? 2 : 1.5,
248
+ },
249
+ markerEnd: {
250
+ type: MarkerType.ArrowClosed,
251
+ color: strokeColor,
252
+ width: 16,
253
+ height: 16,
254
+ },
255
+ label: isBlockedBy ? undefined : "related",
256
+ labelStyle: { fontSize: 10, fill: EDGE_RELATED },
257
+ data: {
258
+ relationship: "dependency",
259
+ },
260
+ };
261
+ });
262
+ const hierarchyEdges = visibleTasks
263
+ .filter((task) => !!task.parentTaskId &&
264
+ taskMap.has(task.parentTaskId))
265
+ .map((task) => ({
266
+ id: `parent:${task.parentTaskId}:${task.taskId}`,
267
+ source: task.parentTaskId,
268
+ target: task.taskId,
269
+ sourceHandle: hierSourceHandle,
270
+ targetHandle: hierTargetHandle,
271
+ type: "smoothstep",
272
+ style: {
273
+ stroke: "#3b82f6",
274
+ strokeWidth: 1.5,
275
+ },
276
+ markerEnd: {
277
+ type: MarkerType.ArrowClosed,
278
+ color: "#3b82f6",
279
+ width: 16,
280
+ height: 16,
281
+ },
282
+ data: {
283
+ relationship: "hierarchy",
284
+ },
285
+ }));
286
+ const edges = [...dependencyEdges, ...hierarchyEdges];
287
+ if (nodes.length === 0)
288
+ return { nodes: [], edges: [] };
289
+ return layoutGraph(nodes, edges, orientation, autoLayoutStrategy);
290
+ }
291
+ function applySavedLayout(nodes, savedLayout) {
292
+ if (!savedLayout?.nodes?.length) {
293
+ return nodes;
294
+ }
295
+ const positionsByTaskId = new Map(savedLayout.nodes.map((node) => [
296
+ node.taskId,
297
+ {
298
+ x: node.x,
299
+ y: node.y,
300
+ },
301
+ ]));
302
+ return nodes.map((node) => ({
303
+ ...node,
304
+ position: positionsByTaskId.get(node.id) ?? node.position,
305
+ }));
306
+ }
307
+ function preserveNodePositions(previousNodes, nextNodes) {
308
+ const previousPositions = new Map(previousNodes.map((node) => [node.id, node.position]));
309
+ return nextNodes.map((node) => ({
310
+ ...node,
311
+ position: previousPositions.get(node.id) ?? node.position,
312
+ }));
313
+ }
314
+ function buildChildRelationshipKeys(tasks) {
315
+ return new Set(tasks
316
+ .filter((task) => !!task.parentTaskId)
317
+ .map((task) => `${task.parentTaskId}->${task.taskId}`));
318
+ }
319
+ function isHierarchyConnectionValid(connection, tasks) {
320
+ const { source, sourceHandle, target, targetHandle } = connection;
321
+ if (!source || !target || source === target) {
322
+ return false;
323
+ }
324
+ if (sourceHandle !== "bottom") {
325
+ return false;
326
+ }
327
+ if (targetHandle && targetHandle !== "left" && targetHandle !== "top") {
328
+ return false;
329
+ }
330
+ const validTaskIds = new Set(tasks.map((task) => task.taskId));
331
+ if (!validTaskIds.has(source) || !validTaskIds.has(target)) {
332
+ return false;
333
+ }
334
+ const parentByTaskId = new Map(tasks.map((task) => [task.taskId, task.parentTaskId ?? null]));
335
+ let ancestorTaskId = parentByTaskId.get(source) ?? null;
336
+ while (ancestorTaskId) {
337
+ if (ancestorTaskId === target) {
338
+ return false;
339
+ }
340
+ ancestorTaskId = parentByTaskId.get(ancestorTaskId) ?? null;
341
+ }
342
+ return !buildChildRelationshipKeys(tasks).has(`${source}->${target}`);
343
+ }
344
+ function isDependencyConnectionValid(connection, tasks, dependencies) {
345
+ const { source, sourceHandle, target, targetHandle } = connection;
346
+ if (!source || !target || source === target) {
347
+ return false;
348
+ }
349
+ if (sourceHandle !== "right") {
350
+ return false;
351
+ }
352
+ if (targetHandle && targetHandle !== "left" && targetHandle !== "top") {
353
+ return false;
354
+ }
355
+ const validTaskIds = new Set(tasks.map((task) => task.taskId));
356
+ if (!validTaskIds.has(source) || !validTaskIds.has(target)) {
357
+ return false;
358
+ }
359
+ if (dependencies.some((dependency) => dependency.dependsOnTaskId === source && dependency.taskId === target)) {
360
+ return false;
361
+ }
362
+ return !buildChildRelationshipKeys(tasks).has(`${source}->${target}`);
363
+ }
364
+ function isGraphConnectionValid(connection, tasks, dependencies) {
365
+ if (connection.sourceHandle === "bottom") {
366
+ return isHierarchyConnectionValid(connection, tasks);
367
+ }
368
+ return isDependencyConnectionValid(connection, tasks, dependencies);
369
+ }
370
+ function createLayoutSignature(layoutKey, nodes, viewport) {
371
+ return JSON.stringify({
372
+ layoutKey,
373
+ nodes: nodes
374
+ .map((node) => ({
375
+ taskId: node.id,
376
+ x: node.position.x,
377
+ y: node.position.y,
378
+ }))
379
+ .sort((a, b) => a.taskId.localeCompare(b.taskId)),
380
+ viewport: viewport
381
+ ? {
382
+ x: viewport.x,
383
+ y: viewport.y,
384
+ zoom: viewport.zoom,
385
+ }
386
+ : null,
387
+ });
388
+ }
389
+ export function DependencyGraphView(props) {
390
+ const { projectId, layoutKey, tasks, dependencies, savedLayout, selectedTaskId, onSelectTask, permission, canPersistLayout: canPersistLayoutOverride, onPersistLayout, onLayoutSaved, highlightBlockedTasks = true, showExecutionStateBadges = true, emptyStateTitle = "No tasks to visualize", emptyStateDescription = "Create tasks in this project to see them in the dependency graph.", orientation = "vertical", autoLayoutStrategy = "allEdges", layoutSaveDebounceMs = SAVE_DEBOUNCE_MS, onAddDependentTaskFromNode, onAddChildTaskFromNode, onRemoveTask, allowDependencyConnect = false, onCreateDependency, onCreateChildRelationship, miniMapWidth, miniMapHeight, } = props;
391
+ const canPersistLayout = typeof canPersistLayoutOverride === "boolean"
392
+ ? canPersistLayoutOverride
393
+ : permission === "Owner" || permission === "Editor";
394
+ const effectiveLayoutKey = layoutKey ?? projectId ?? "graph";
395
+ const taskMap = useMemo(() => {
396
+ const m = new Map();
397
+ for (const t of tasks)
398
+ m.set(t.taskId, t);
399
+ return m;
400
+ }, [tasks]);
401
+ const blockedTaskIds = useMemo(() => {
402
+ if (!highlightBlockedTasks) {
403
+ return new Set();
404
+ }
405
+ const blocked = new Set();
406
+ for (const dep of dependencies) {
407
+ if (dep.dependencyType !== "BlockedBy")
408
+ continue;
409
+ const blocker = taskMap.get(dep.dependsOnTaskId);
410
+ if (!blocker ||
411
+ normalizeTaskStatus(blocker.status, blocker.executionState) !== "Done") {
412
+ blocked.add(dep.taskId);
413
+ }
414
+ }
415
+ return blocked;
416
+ }, [dependencies, highlightBlockedTasks, taskMap]);
417
+ const onSelect = useCallback((taskId) => onSelectTask(taskId), [onSelectTask]);
418
+ const { nodes: initialNodes, edges: initialEdges } = useMemo(() => buildGraph(tasks, dependencies, selectedTaskId, blockedTaskIds, orientation, showExecutionStateBadges, autoLayoutStrategy, onSelect, onAddDependentTaskFromNode, onAddChildTaskFromNode, onRemoveTask), [
419
+ tasks,
420
+ dependencies,
421
+ selectedTaskId,
422
+ blockedTaskIds,
423
+ orientation,
424
+ showExecutionStateBadges,
425
+ autoLayoutStrategy,
426
+ onSelect,
427
+ onAddDependentTaskFromNode,
428
+ onAddChildTaskFromNode,
429
+ onRemoveTask,
430
+ ]);
431
+ const [nodes, setNodes, onNodesChangeBase] = useNodesState(initialNodes);
432
+ const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
433
+ const nodesRef = useRef(nodes);
434
+ const onNodesChange = useCallback((changes) => {
435
+ onNodesChangeBase(changes);
436
+ const hasPositionChange = changes.some((c) => c.type === "position" && c.position);
437
+ if (hasPositionChange) {
438
+ nodesRef.current = nodesRef.current.map((n) => {
439
+ const change = changes.find((c) => c.type === "position" && c.id === n.id && c.position);
440
+ return change && change.type === "position" && change.position
441
+ ? { ...n, position: change.position }
442
+ : n;
443
+ });
444
+ }
445
+ }, [onNodesChangeBase]);
446
+ const reactFlowRef = useRef(null);
447
+ const saveTimeoutRef = useRef(null);
448
+ const pendingViewportRef = useRef(null);
449
+ const fitViewOnNextSyncRef = useRef(false);
450
+ const skipNextMoveEndSaveRef = useRef(false);
451
+ const saveAfterViewportRestoreRef = useRef(false);
452
+ const lastSavedSignatureRef = useRef(null);
453
+ const lastAppliedLayoutKeyRef = useRef(null);
454
+ const lastAppliedLayoutSignatureRef = useRef(null);
455
+ const savedLayoutSignature = useMemo(() => savedLayout
456
+ ? JSON.stringify({
457
+ updatedAt: savedLayout.updatedAt ?? null,
458
+ nodes: savedLayout.nodes ?? [],
459
+ viewport: savedLayout.viewport ?? null,
460
+ })
461
+ : "null", [savedLayout]);
462
+ const persistLayout = useCallback(async (nextNodes, viewport) => {
463
+ if (!canPersistLayout) {
464
+ return;
465
+ }
466
+ const signature = createLayoutSignature(effectiveLayoutKey, nextNodes, viewport);
467
+ if (signature === lastSavedSignatureRef.current) {
468
+ return;
469
+ }
470
+ try {
471
+ const layoutPayload = {
472
+ nodes: nextNodes.map((node) => ({
473
+ taskId: node.id,
474
+ x: node.position.x,
475
+ y: node.position.y,
476
+ })),
477
+ viewport: viewport == null
478
+ ? null
479
+ : {
480
+ x: viewport.x,
481
+ y: viewport.y,
482
+ zoom: viewport.zoom,
483
+ },
484
+ };
485
+ const nextLayout = onPersistLayout
486
+ ? await onPersistLayout(layoutPayload)
487
+ : projectId
488
+ ? await (async () => {
489
+ const result = await saveGraphLayout({
490
+ projectId,
491
+ nodes: layoutPayload.nodes,
492
+ viewport: layoutPayload.viewport,
493
+ });
494
+ if (result.type !== "success") {
495
+ toast.error(result.summary || "Failed to save graph layout");
496
+ return null;
497
+ }
498
+ return result.data ?? null;
499
+ })()
500
+ : layoutPayload;
501
+ lastSavedSignatureRef.current = signature;
502
+ if (nextLayout) {
503
+ const incomingSignature = JSON.stringify({
504
+ updatedAt: nextLayout.updatedAt ?? null,
505
+ nodes: nextLayout.nodes ?? [],
506
+ viewport: nextLayout.viewport ?? null,
507
+ });
508
+ lastAppliedLayoutSignatureRef.current = incomingSignature;
509
+ }
510
+ onLayoutSaved?.(nextLayout ?? null);
511
+ }
512
+ catch (error) {
513
+ toast.error(error instanceof Error
514
+ ? error.message
515
+ : "Failed to save graph layout");
516
+ }
517
+ }, [
518
+ canPersistLayout,
519
+ effectiveLayoutKey,
520
+ onLayoutSaved,
521
+ onPersistLayout,
522
+ projectId,
523
+ ]);
524
+ const queueLayoutSave = useCallback((nextNodes, viewport) => {
525
+ if (!canPersistLayout) {
526
+ return;
527
+ }
528
+ const signature = createLayoutSignature(effectiveLayoutKey, nextNodes, viewport);
529
+ if (signature === lastSavedSignatureRef.current) {
530
+ return;
531
+ }
532
+ if (saveTimeoutRef.current !== null) {
533
+ window.clearTimeout(saveTimeoutRef.current);
534
+ saveTimeoutRef.current = null;
535
+ }
536
+ if (layoutSaveDebounceMs === 0) {
537
+ void persistLayout(nextNodes, viewport);
538
+ return;
539
+ }
540
+ saveTimeoutRef.current = window.setTimeout(() => {
541
+ saveTimeoutRef.current = null;
542
+ void persistLayout(nextNodes, viewport);
543
+ }, layoutSaveDebounceMs);
544
+ }, [canPersistLayout, effectiveLayoutKey, layoutSaveDebounceMs, persistLayout]);
545
+ const restoreViewport = useCallback(() => {
546
+ const instance = reactFlowRef.current;
547
+ if (!instance || nodesRef.current.length === 0) {
548
+ return;
549
+ }
550
+ if (pendingViewportRef.current) {
551
+ const viewport = pendingViewportRef.current;
552
+ pendingViewportRef.current = null;
553
+ skipNextMoveEndSaveRef.current = true;
554
+ window.requestAnimationFrame(() => {
555
+ reactFlowRef.current?.setViewport(viewport, { duration: 0 });
556
+ lastSavedSignatureRef.current = createLayoutSignature(effectiveLayoutKey, nodesRef.current, viewport);
557
+ });
558
+ return;
559
+ }
560
+ if (!fitViewOnNextSyncRef.current) {
561
+ return;
562
+ }
563
+ fitViewOnNextSyncRef.current = false;
564
+ skipNextMoveEndSaveRef.current = true;
565
+ window.requestAnimationFrame(() => {
566
+ reactFlowRef.current?.fitView({ padding: 0.2 });
567
+ if (!saveAfterViewportRestoreRef.current) {
568
+ return;
569
+ }
570
+ saveAfterViewportRestoreRef.current = false;
571
+ const viewport = reactFlowRef.current?.getViewport() ?? null;
572
+ queueLayoutSave(nodesRef.current, viewport);
573
+ });
574
+ }, [effectiveLayoutKey, queueLayoutSave]);
575
+ useEffect(() => {
576
+ const shouldApplySavedLayout = lastAppliedLayoutKeyRef.current !== effectiveLayoutKey ||
577
+ lastAppliedLayoutSignatureRef.current !== savedLayoutSignature;
578
+ const nextNodes = shouldApplySavedLayout
579
+ ? applySavedLayout(initialNodes, savedLayout)
580
+ : preserveNodePositions(nodesRef.current, initialNodes);
581
+ setNodes(nextNodes);
582
+ setEdges(initialEdges);
583
+ nodesRef.current = nextNodes;
584
+ if (shouldApplySavedLayout) {
585
+ lastAppliedLayoutKeyRef.current = effectiveLayoutKey;
586
+ lastAppliedLayoutSignatureRef.current = savedLayoutSignature;
587
+ pendingViewportRef.current = savedLayout?.viewport ?? null;
588
+ fitViewOnNextSyncRef.current = !savedLayout?.viewport;
589
+ if (!savedLayout?.viewport) {
590
+ lastSavedSignatureRef.current = savedLayout?.nodes?.length
591
+ ? createLayoutSignature(effectiveLayoutKey, nextNodes, null)
592
+ : null;
593
+ }
594
+ restoreViewport();
595
+ }
596
+ }, [
597
+ initialEdges,
598
+ initialNodes,
599
+ effectiveLayoutKey,
600
+ restoreViewport,
601
+ savedLayout,
602
+ savedLayoutSignature,
603
+ setEdges,
604
+ setNodes,
605
+ ]);
606
+ useEffect(() => {
607
+ return () => {
608
+ if (saveTimeoutRef.current !== null) {
609
+ window.clearTimeout(saveTimeoutRef.current);
610
+ saveTimeoutRef.current = null;
611
+ }
612
+ };
613
+ }, []);
614
+ const hasVisibleNodes = initialNodes.length > 0;
615
+ const handleAutoLayout = useCallback(() => {
616
+ const autoLayout = buildGraph(tasks, dependencies, selectedTaskId, blockedTaskIds, orientation, showExecutionStateBadges, autoLayoutStrategy, onSelect, onAddDependentTaskFromNode, onAddChildTaskFromNode, onRemoveTask);
617
+ setNodes(autoLayout.nodes);
618
+ setEdges(autoLayout.edges);
619
+ nodesRef.current = autoLayout.nodes;
620
+ fitViewOnNextSyncRef.current = true;
621
+ saveAfterViewportRestoreRef.current = true;
622
+ restoreViewport();
623
+ }, [
624
+ blockedTaskIds,
625
+ dependencies,
626
+ onSelect,
627
+ orientation,
628
+ showExecutionStateBadges,
629
+ autoLayoutStrategy,
630
+ onRemoveTask,
631
+ onAddDependentTaskFromNode,
632
+ onAddChildTaskFromNode,
633
+ restoreViewport,
634
+ selectedTaskId,
635
+ setEdges,
636
+ setNodes,
637
+ tasks,
638
+ ]);
639
+ const isValidConnection = useCallback((connection) => allowDependencyConnect
640
+ ? isGraphConnectionValid(connection, tasks, dependencies)
641
+ : false, [allowDependencyConnect, dependencies, tasks]);
642
+ const handleConnect = useCallback((connection) => {
643
+ if (!allowDependencyConnect) {
644
+ return;
645
+ }
646
+ const { source, sourceHandle, target } = connection;
647
+ if (!source || !target) {
648
+ return;
649
+ }
650
+ if (!isGraphConnectionValid(connection, tasks, dependencies)) {
651
+ return;
652
+ }
653
+ onSelectTask(target);
654
+ if (sourceHandle === "bottom") {
655
+ onCreateChildRelationship?.(source, target);
656
+ return;
657
+ }
658
+ onCreateDependency?.(source, target);
659
+ }, [
660
+ allowDependencyConnect,
661
+ dependencies,
662
+ onCreateChildRelationship,
663
+ onCreateDependency,
664
+ onSelectTask,
665
+ tasks,
666
+ ]);
667
+ return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsxs("div", { className: "flex shrink-0 items-center justify-between gap-3 px-3 pt-2", children: [hasVisibleNodes && highlightBlockedTasks ? (_jsxs("div", { className: "flex items-center gap-2 text-[11px] text-slate-400", children: [_jsxs("span", { className: "inline-flex items-center gap-1", children: [_jsx("span", { className: "inline-block h-2 w-4 rounded-sm border border-orange-300 bg-orange-400" }), "Blocked by"] }), _jsxs("span", { className: "inline-flex items-center gap-1", children: [_jsx("span", { className: "inline-block h-0.5 w-4 rounded-sm bg-slate-400" }), "Related"] }), _jsxs("span", { className: "inline-flex items-center gap-1", children: [_jsx("span", { className: "inline-block h-0.5 w-4 rounded-sm bg-blue-500" }), "Child task"] })] })) : (_jsx("div", {})), hasVisibleNodes && canPersistLayout && (_jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "h-8 px-3", onClick: handleAutoLayout, children: [_jsx(RotateCcw, { className: "mr-1.5 h-4 w-4" }), "Auto layout"] }))] }), _jsx("div", { className: "min-h-0 flex-1", children: !hasVisibleNodes ? (_jsx("div", { className: "flex h-full items-center justify-center", children: _jsxs("div", { className: "flex max-w-sm flex-col items-center gap-3 text-center", children: [_jsx("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-slate-100", children: _jsx(GitBranch, { className: "h-6 w-6 text-slate-400" }) }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-slate-600", children: emptyStateTitle }), _jsx("p", { className: "mt-1 text-xs text-slate-400", children: emptyStateDescription })] })] }) })) : (_jsxs(ReactFlow, { nodes: nodes, edges: edges, onNodesChange: onNodesChange, onEdgesChange: onEdgesChange, onConnect: handleConnect, onNodeClick: (_event, node) => {
668
+ onSelectTask(node.id);
669
+ }, onNodeDragStart: (_event, node) => {
670
+ onSelectTask(node.id);
671
+ }, onInit: (instance) => {
672
+ reactFlowRef.current = instance;
673
+ restoreViewport();
674
+ }, onNodeDragStop: () => {
675
+ const allNodes = reactFlowRef.current?.getNodes() ?? nodesRef.current;
676
+ nodesRef.current = allNodes;
677
+ queueLayoutSave(allNodes, reactFlowRef.current?.getViewport() ?? null);
678
+ }, onMoveEnd: (_event, viewport) => {
679
+ if (skipNextMoveEndSaveRef.current) {
680
+ skipNextMoveEndSaveRef.current = false;
681
+ return;
682
+ }
683
+ queueLayoutSave(nodesRef.current, viewport);
684
+ }, nodeTypes: nodeTypes, minZoom: 0.2, maxZoom: 2, proOptions: { hideAttribution: true }, nodesDraggable: canPersistLayout, nodesConnectable: allowDependencyConnect, isValidConnection: isValidConnection, elementsSelectable: false, children: [_jsx(Controls, { showInteractive: false, className: "rounded-lg! border-slate-200! shadow-md!" }), _jsx(MiniMap, { ...(miniMapWidth != null && miniMapHeight != null
685
+ ? { width: miniMapWidth, height: miniMapHeight }
686
+ : {}), nodeColor: (node) => {
687
+ const status = node.data?.status;
688
+ switch (status) {
689
+ case "Done":
690
+ return "#86efac";
691
+ case "InProgress":
692
+ return "#93c5fd";
693
+ case "Review":
694
+ return "#c4b5fd";
695
+ case "Waiting":
696
+ return "#fcd34d";
697
+ default:
698
+ return "#cbd5e1";
699
+ }
700
+ }, maskColor: "rgba(0, 0, 0, 0.08)", className: "rounded-lg! border-slate-200! shadow-md!" })] })) })] }));
701
+ }
702
+ //# sourceMappingURL=DependencyGraphView.js.map