@parhelia/core 0.1.12477 → 0.1.12485

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 (162) hide show
  1. package/dist/config/config.js +22 -3
  2. package/dist/config/config.js.map +1 -1
  3. package/dist/config/types.d.ts +2 -0
  4. package/dist/config/types.js.map +1 -1
  5. package/dist/editor/ConfirmationDialog.js +20 -4
  6. package/dist/editor/ConfirmationDialog.js.map +1 -1
  7. package/dist/editor/ContentTree.js +19 -7
  8. package/dist/editor/ContentTree.js.map +1 -1
  9. package/dist/editor/Editor.js.map +1 -1
  10. package/dist/editor/PictureCropper.js +45 -41
  11. package/dist/editor/PictureCropper.js.map +1 -1
  12. package/dist/editor/ai/AgentTerminal.js +97 -8
  13. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  14. package/dist/editor/ai/AgentTerminalStatusBar.js +65 -0
  15. package/dist/editor/ai/AgentTerminalStatusBar.js.map +1 -1
  16. package/dist/editor/ai/Agents.js +19 -0
  17. package/dist/editor/ai/Agents.js.map +1 -1
  18. package/dist/editor/ai/AiResponseMessage.js +5 -0
  19. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  20. package/dist/editor/ai/ContentInspectorPopover.d.ts +1 -0
  21. package/dist/editor/ai/ContentInspectorPopover.js +22 -8
  22. package/dist/editor/ai/ContentInspectorPopover.js.map +1 -1
  23. package/dist/editor/ai/ToolCallDisplay.d.ts +2 -0
  24. package/dist/editor/ai/ToolCallDisplay.js +54 -11
  25. package/dist/editor/ai/ToolCallDisplay.js.map +1 -1
  26. package/dist/editor/ai/dialogs/AgentDialogHandler.js +32 -3
  27. package/dist/editor/ai/dialogs/AgentDialogHandler.js.map +1 -1
  28. package/dist/editor/ai/dialogs/QuestionnaireInline.js +55 -20
  29. package/dist/editor/ai/dialogs/QuestionnaireInline.js.map +1 -1
  30. package/dist/editor/ai/dialogs/agentDialogTypes.d.ts +7 -4
  31. package/dist/editor/ai/dialogs/agentDialogTypes.js.map +1 -1
  32. package/dist/editor/ai/types.d.ts +2 -0
  33. package/dist/editor/client/EditorShell.js +55 -10
  34. package/dist/editor/client/EditorShell.js.map +1 -1
  35. package/dist/editor/client/editContext.d.ts +2 -0
  36. package/dist/editor/client/editContext.js.map +1 -1
  37. package/dist/editor/client/ui/EditorChrome.d.ts +0 -2
  38. package/dist/editor/client/ui/EditorChrome.js +2 -13
  39. package/dist/editor/client/ui/EditorChrome.js.map +1 -1
  40. package/dist/editor/page-viewer/EditorForm.js +8 -1
  41. package/dist/editor/page-viewer/EditorForm.js.map +1 -1
  42. package/dist/editor/page-viewer/PageViewer.d.ts +3 -1
  43. package/dist/editor/page-viewer/PageViewer.js +23 -4
  44. package/dist/editor/page-viewer/PageViewer.js.map +1 -1
  45. package/dist/editor/page-viewer/PageViewerFrame.d.ts +2 -1
  46. package/dist/editor/page-viewer/PageViewerFrame.js +3 -4
  47. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  48. package/dist/editor/services/agentService.d.ts +27 -0
  49. package/dist/editor/services/agentService.js +11 -2
  50. package/dist/editor/services/agentService.js.map +1 -1
  51. package/dist/editor/services/aiService.js +54 -3
  52. package/dist/editor/services/aiService.js.map +1 -1
  53. package/dist/editor/services/serviceHelper.js +5 -2
  54. package/dist/editor/services/serviceHelper.js.map +1 -1
  55. package/dist/editor/settings/About.js +40 -4
  56. package/dist/editor/settings/About.js.map +1 -1
  57. package/dist/editor/settings/panels/PersistentLogsPanel.d.ts +2 -0
  58. package/dist/editor/settings/panels/PersistentLogsPanel.js +244 -0
  59. package/dist/editor/settings/panels/PersistentLogsPanel.js.map +1 -0
  60. package/dist/editor/settings/panels/ProjectTemplateAgentPanel.d.ts +7 -1
  61. package/dist/editor/settings/panels/ProjectTemplateAgentPanel.js +3 -3
  62. package/dist/editor/settings/panels/ProjectTemplateAgentPanel.js.map +1 -1
  63. package/dist/editor/settings/panels/ProjectTemplatesPanel.js +165 -84
  64. package/dist/editor/settings/panels/ProjectTemplatesPanel.js.map +1 -1
  65. package/dist/editor/settings/panels/index.d.ts +1 -0
  66. package/dist/editor/settings/panels/index.js +1 -0
  67. package/dist/editor/settings/panels/index.js.map +1 -1
  68. package/dist/editor/sidebar/ComponentTree.d.ts +8 -1
  69. package/dist/editor/sidebar/ComponentTree.js +23 -15
  70. package/dist/editor/sidebar/ComponentTree.js.map +1 -1
  71. package/dist/editor/sidebar/NavigationPanelItem.js +1 -1
  72. package/dist/editor/sidebar/NavigationPanelItem.js.map +1 -1
  73. package/dist/editor/sidebar/WorkspaceButton.js +1 -1
  74. package/dist/editor/sidebar/WorkspaceButton.js.map +1 -1
  75. package/dist/editor/tree-indicators/GutterColumns.js +24 -4
  76. package/dist/editor/tree-indicators/GutterColumns.js.map +1 -1
  77. package/dist/editor/tree-indicators/types.d.ts +10 -0
  78. package/dist/editor/ui/Splitter.d.ts +1 -0
  79. package/dist/editor/ui/Splitter.js +7 -1
  80. package/dist/editor/ui/Splitter.js.map +1 -1
  81. package/dist/editor/utils.js +16 -4
  82. package/dist/editor/utils.js.map +1 -1
  83. package/dist/editor/views/CompareView.d.ts +3 -1
  84. package/dist/editor/views/CompareView.js +3 -3
  85. package/dist/editor/views/CompareView.js.map +1 -1
  86. package/dist/editor/views/EditorSlot.js +7 -6
  87. package/dist/editor/views/EditorSlot.js.map +1 -1
  88. package/dist/editor/views/SingleEditView.d.ts +2 -1
  89. package/dist/editor/views/SingleEditView.js +8 -8
  90. package/dist/editor/views/SingleEditView.js.map +1 -1
  91. package/dist/licensing/LicenseContext.js +40 -4
  92. package/dist/licensing/LicenseContext.js.map +1 -1
  93. package/dist/licensing/LicenseOverlay.js +1 -1
  94. package/dist/licensing/LicenseOverlay.js.map +1 -1
  95. package/dist/licensing/types.d.ts +3 -1
  96. package/dist/licensing/types.js.map +1 -1
  97. package/dist/revision.d.ts +2 -2
  98. package/dist/revision.js +2 -2
  99. package/dist/task-board/TaskBoardWorkspace.js +165 -354
  100. package/dist/task-board/TaskBoardWorkspace.js.map +1 -1
  101. package/dist/task-board/assigneeDisplay.js +1 -3
  102. package/dist/task-board/assigneeDisplay.js.map +1 -1
  103. package/dist/task-board/components/CreateProjectDialog.js +2 -1
  104. package/dist/task-board/components/CreateProjectDialog.js.map +1 -1
  105. package/dist/task-board/components/ProjectDashboard.js +2 -1
  106. package/dist/task-board/components/ProjectDashboard.js.map +1 -1
  107. package/dist/task-board/components/ProjectExecutionUserPicker.d.ts +14 -0
  108. package/dist/task-board/components/ProjectExecutionUserPicker.js +65 -0
  109. package/dist/task-board/components/ProjectExecutionUserPicker.js.map +1 -0
  110. package/dist/task-board/components/ProjectSettingsDialog.d.ts +1 -0
  111. package/dist/task-board/components/ProjectSettingsDialog.js +146 -12
  112. package/dist/task-board/components/ProjectSettingsDialog.js.map +1 -1
  113. package/dist/task-board/components/TaskAgentPanel.d.ts +1 -0
  114. package/dist/task-board/components/TaskAgentPanel.js +2 -2
  115. package/dist/task-board/components/TaskAgentPanel.js.map +1 -1
  116. package/dist/task-board/components/TaskAssigneePicker.js +2 -2
  117. package/dist/task-board/components/TaskAssigneePicker.js.map +1 -1
  118. package/dist/task-board/components/TaskBoardMyTasksSidebar.js +1 -1
  119. package/dist/task-board/components/TaskBoardMyTasksSidebar.js.map +1 -1
  120. package/dist/task-board/components/TaskDetailDialog.d.ts +1 -0
  121. package/dist/task-board/components/TaskDetailDialog.js +2 -2
  122. package/dist/task-board/components/TaskDetailDialog.js.map +1 -1
  123. package/dist/task-board/components/TaskDetailPanel.d.ts +1 -0
  124. package/dist/task-board/components/TaskDetailPanel.js +23 -8
  125. package/dist/task-board/components/TaskDetailPanel.js.map +1 -1
  126. package/dist/task-board/components/TaskRow.js +3 -2
  127. package/dist/task-board/components/TaskRow.js.map +1 -1
  128. package/dist/task-board/components/TaskboardPersistentLogPanel.d.ts +11 -0
  129. package/dist/task-board/components/TaskboardPersistentLogPanel.js +141 -0
  130. package/dist/task-board/components/TaskboardPersistentLogPanel.js.map +1 -0
  131. package/dist/task-board/components/WizardTaskDetailsPanel.js +2 -1
  132. package/dist/task-board/components/WizardTaskDetailsPanel.js.map +1 -1
  133. package/dist/task-board/persistentLogCopy.d.ts +7 -0
  134. package/dist/task-board/persistentLogCopy.js +80 -0
  135. package/dist/task-board/persistentLogCopy.js.map +1 -0
  136. package/dist/task-board/persistentLogLabels.d.ts +38 -0
  137. package/dist/task-board/persistentLogLabels.js +189 -0
  138. package/dist/task-board/persistentLogLabels.js.map +1 -0
  139. package/dist/task-board/services/taskService.d.ts +17 -1
  140. package/dist/task-board/services/taskService.js +56 -0
  141. package/dist/task-board/services/taskService.js.map +1 -1
  142. package/dist/task-board/taskExecutionRecords.js +2 -0
  143. package/dist/task-board/taskExecutionRecords.js.map +1 -1
  144. package/dist/task-board/types.d.ts +78 -1
  145. package/dist/task-board/useTaskBoardAgentPanelState.d.ts +34 -0
  146. package/dist/task-board/useTaskBoardAgentPanelState.js +283 -0
  147. package/dist/task-board/useTaskBoardAgentPanelState.js.map +1 -0
  148. package/dist/task-board/utils/taskDependencyOrdering.d.ts +6 -0
  149. package/dist/task-board/utils/taskDependencyOrdering.js +138 -1
  150. package/dist/task-board/utils/taskDependencyOrdering.js.map +1 -1
  151. package/dist/task-board/views/DependencyGraphView.d.ts +5 -2
  152. package/dist/task-board/views/DependencyGraphView.js +261 -69
  153. package/dist/task-board/views/DependencyGraphView.js.map +1 -1
  154. package/dist/task-board/views/KanbanView.js +8 -1
  155. package/dist/task-board/views/KanbanView.js.map +1 -1
  156. package/dist/task-board/views/ListView.js +1 -1
  157. package/dist/task-board/views/ListView.js.map +1 -1
  158. package/dist/task-board/views/WizardView.js +30 -24
  159. package/dist/task-board/views/WizardView.js.map +1 -1
  160. package/dist/tour/Tour.js +8 -2
  161. package/dist/tour/Tour.js.map +1 -1
  162. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useMemo, useRef } from "react";
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
3
  import { ReactFlow, Controls, MiniMap, useNodesState, useEdgesState, Handle, Position, MarkerType, } from "@xyflow/react";
4
4
  import Dagre from "@dagrejs/dagre";
5
5
  import "@xyflow/react/dist/style.css";
@@ -10,7 +10,7 @@ import { cn } from "../../lib/utils";
10
10
  import { saveGraphLayout } from "../services/taskService";
11
11
  import { Button } from "../../components/ui/button";
12
12
  import { Badge } from "../../components/ui/badge";
13
- import { Tooltip, TooltipContent, TooltipTrigger } from "../../components/ui/tooltip";
13
+ import { Tooltip, TooltipContent, TooltipTrigger, } from "../../components/ui/tooltip";
14
14
  import { Circle, PlayCircle, Clock3, Eye, CheckCircle2, ArrowUp, ArrowDown, Minus, Flame, GitBranch, RotateCcw, Loader2, Plus, Trash2, } from "lucide-react";
15
15
  const NODE_WIDTH = 220;
16
16
  const NODE_HEIGHT = 80;
@@ -46,24 +46,37 @@ function TaskGraphNode({ data }) {
46
46
  const priorityCfg = data.priority ? PRIORITY_CONFIG[data.priority] : null;
47
47
  const PriorityIcon = priorityCfg?.icon;
48
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) => {
49
+ const dependentButtonClass = data.dependencySourceHandle === "right"
50
+ ? "top-1/2 right-0 translate-x-[calc(100%+6px)] -translate-y-1/2"
51
+ : "bottom-0 left-1/2 -translate-x-1/2 translate-y-[calc(100%+6px)]";
52
+ const dependentTooltipSide = data.dependencySourceHandle === "right" ? "right" : "bottom";
53
+ const subtaskButtonClass = data.hierarchySourceHandle === "right"
54
+ ? "top-1/2 right-0 translate-x-[calc(100%+6px)] -translate-y-1/2"
55
+ : "bottom-0 left-1/2 -translate-x-1/2 translate-y-[calc(100%+6px)]";
56
+ const subtaskTooltipSide = data.hierarchySourceHandle === "right" ? "right" : "bottom";
57
+ const nodeSurfaceClass = data.isSelected
58
+ ? "ring-primary border-primary/50 ring-2 bg-white"
59
+ : data.isBlocked
60
+ ? "border-red-200 bg-red-50/70"
61
+ : data.status === "Done"
62
+ ? "border-emerald-200 bg-emerald-50/80"
63
+ : "border-slate-200 bg-white hover:border-slate-300 hover:shadow-md";
64
+ return (_jsxs("div", { className: cn("relative cursor-pointer rounded-lg border px-3 py-2 shadow-sm transition-all", nodeSurfaceClass), 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: "-mt-px -mr-0.5 shrink-0 rounded p-0.5 text-slate-400 transition-colors hover:bg-red-50 hover:text-red-600", "aria-label": "Remove task", "data-testid": `dependency-graph-remove-task-${data.taskId}`, onClick: (event) => {
52
65
  event.stopPropagation();
53
66
  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) => {
67
+ }, 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 tracking-wider uppercase", 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, dependentButtonClass), "aria-label": "Add dependent task", "data-testid": `dependency-graph-add-dependent-task-${data.taskId}`, onClick: (event) => {
55
68
  event.stopPropagation();
56
69
  data.onSelect(data.taskId);
57
70
  data.onAddDependentTaskFromNode?.(data.taskId);
58
71
  }, onMouseDown: (event) => {
59
72
  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) => {
73
+ }, children: _jsx(Plus, { className: "h-3 w-3", strokeWidth: 1.75 }) }) }), _jsx(TooltipContent, { side: dependentTooltipSide, 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, subtaskButtonClass), "aria-label": "Add subtask", "data-testid": `dependency-graph-add-subtask-${data.taskId}`, onClick: (event) => {
61
74
  event.stopPropagation();
62
75
  data.onSelect(data.taskId);
63
76
  data.onAddChildTaskFromNode?.(data.taskId);
64
77
  }, onMouseDown: (event) => {
65
78
  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}` })] }));
79
+ }, children: _jsx(Plus, { className: "h-3 w-3", strokeWidth: 1.75 }) }) }), _jsx(TooltipContent, { side: subtaskTooltipSide, 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
80
  }
68
81
  const nodeTypes = {
69
82
  taskNode: TaskGraphNode,
@@ -71,13 +84,30 @@ const nodeTypes = {
71
84
  const HIERARCHY_CHILD_INDENT = 24;
72
85
  const HIERARCHY_CHILD_GAP = 32;
73
86
  const HIERARCHY_SUBTREE_GAP = 80;
87
+ function normalizeGraphOrientation(orientation) {
88
+ return orientation === "vertical" ? "vertical" : "horizontal";
89
+ }
90
+ function getDependencyHandles(orientation) {
91
+ return orientation === "horizontal"
92
+ ? { source: "right", target: "left" }
93
+ : { source: "bottom", target: "top" };
94
+ }
95
+ function getHierarchyHandles(orientation, autoLayoutStrategy) {
96
+ if (autoLayoutStrategy !== "hierarchy") {
97
+ return getDependencyHandles(orientation);
98
+ }
99
+ return orientation === "horizontal"
100
+ ? { source: "bottom", target: "left" }
101
+ : { source: "right", target: "left" };
102
+ }
74
103
  /**
75
104
  * Compound layout for the hierarchy strategy: each parent–children group is
76
105
  * laid out vertically (parent on top, children stacked below with a slight
77
106
  * indent), and then the resulting subtree "super-nodes" are arranged
78
- * left-to-right via Dagre using the dependency edges between them.
107
+ * in the selected orientation via Dagre using the dependency edges between
108
+ * them.
79
109
  */
80
- function layoutGraphHierarchy(nodes, edges) {
110
+ function layoutGraphHierarchy(nodes, edges, orientation) {
81
111
  if (nodes.length === 0)
82
112
  return { nodes, edges };
83
113
  const childrenOf = new Map();
@@ -122,7 +152,11 @@ function layoutGraphHierarchy(nodes, edges) {
122
152
  }
123
153
  }
124
154
  const g = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
125
- g.setGraph({ rankdir: "LR", nodesep: 40, ranksep: HIERARCHY_SUBTREE_GAP });
155
+ g.setGraph({
156
+ rankdir: orientation === "horizontal" ? "LR" : "TB",
157
+ nodesep: 40,
158
+ ranksep: HIERARCHY_SUBTREE_GAP,
159
+ });
126
160
  for (const [rootId, data] of subtrees) {
127
161
  g.setNode(rootId, { width: data.width, height: data.height });
128
162
  }
@@ -164,7 +198,7 @@ function layoutGraphHierarchy(nodes, edges) {
164
198
  function layoutGraph(nodes, edges, orientation, autoLayoutStrategy) {
165
199
  if (autoLayoutStrategy === "hierarchy" &&
166
200
  edges.some((e) => e.data?.relationship === "hierarchy")) {
167
- return layoutGraphHierarchy(nodes, edges);
201
+ return layoutGraphHierarchy(nodes, edges, orientation);
168
202
  }
169
203
  const g = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
170
204
  g.setGraph({
@@ -199,6 +233,8 @@ function buildGraph(tasks, dependencies, selectedTaskId, selectedDependencyId, b
199
233
  const childRelationshipKeys = new Set(visibleTasks
200
234
  .filter((task) => !!task.parentTaskId)
201
235
  .map((task) => `${task.parentTaskId}->${task.taskId}`));
236
+ const dependencyHandles = getDependencyHandles(orientation);
237
+ const hierarchyHandles = getHierarchyHandles(orientation, autoLayoutStrategy);
202
238
  const nodes = visibleTasks.map((task) => ({
203
239
  id: task.taskId,
204
240
  type: "taskNode",
@@ -215,26 +251,27 @@ function buildGraph(tasks, dependencies, selectedTaskId, selectedDependencyId, b
215
251
  isSelected: task.taskId === selectedTaskId,
216
252
  isBlocked: blockedTaskIds.has(task.taskId),
217
253
  orientation,
254
+ dependencySourceHandle: dependencyHandles.source,
255
+ hierarchySourceHandle: hierarchyHandles.source,
218
256
  onSelect,
219
257
  onAddDependentTaskFromNode,
220
258
  onAddChildTaskFromNode,
221
259
  onRemoveTask,
222
260
  },
223
261
  }));
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;
262
+ const depSourceHandle = dependencyHandles.source;
263
+ const depTargetHandle = dependencyHandles.target;
264
+ const hierSourceHandle = hierarchyHandles.source;
265
+ const hierTargetHandle = hierarchyHandles.target;
228
266
  const dependencyEdges = dependencies
229
267
  .filter((dep) => taskMap.has(dep.taskId) &&
230
268
  taskMap.has(dep.dependsOnTaskId) &&
231
269
  !childRelationshipKeys.has(`${dep.dependsOnTaskId}->${dep.taskId}`))
232
270
  .map((dep) => {
233
271
  const isBlockedBy = dep.dependencyType === "BlockedBy";
234
- const strokeColor = isBlockedBy
235
- ? EDGE_BLOCKED_BY
236
- : EDGE_RELATED;
237
- const isSelected = dep.taskId === selectedTaskId && dep.dependsOnTaskId === selectedDependencyId;
272
+ const strokeColor = isBlockedBy ? EDGE_BLOCKED_BY : EDGE_RELATED;
273
+ const isSelected = dep.taskId === selectedTaskId &&
274
+ dep.dependsOnTaskId === selectedDependencyId;
238
275
  return {
239
276
  id: dep.dependencyId,
240
277
  source: dep.dependsOnTaskId,
@@ -264,8 +301,7 @@ function buildGraph(tasks, dependencies, selectedTaskId, selectedDependencyId, b
264
301
  };
265
302
  });
266
303
  const hierarchyEdges = visibleTasks
267
- .filter((task) => !!task.parentTaskId &&
268
- taskMap.has(task.parentTaskId))
304
+ .filter((task) => !!task.parentTaskId && taskMap.has(task.parentTaskId))
269
305
  .map((task) => ({
270
306
  id: `parent:${task.parentTaskId}:${task.taskId}`,
271
307
  source: task.parentTaskId,
@@ -320,12 +356,15 @@ function buildChildRelationshipKeys(tasks) {
320
356
  .filter((task) => !!task.parentTaskId)
321
357
  .map((task) => `${task.parentTaskId}->${task.taskId}`));
322
358
  }
323
- function isHierarchyConnectionValid(connection, tasks) {
359
+ function isHierarchyConnectionValid(connection, tasks, orientation, autoLayoutStrategy) {
360
+ if (autoLayoutStrategy !== "hierarchy") {
361
+ return false;
362
+ }
324
363
  const { source, sourceHandle, target, targetHandle } = connection;
325
364
  if (!source || !target || source === target) {
326
365
  return false;
327
366
  }
328
- if (sourceHandle !== "bottom") {
367
+ if (sourceHandle !== getHierarchyHandles(orientation, autoLayoutStrategy).source) {
329
368
  return false;
330
369
  }
331
370
  if (targetHandle && targetHandle !== "left" && targetHandle !== "top") {
@@ -345,12 +384,12 @@ function isHierarchyConnectionValid(connection, tasks) {
345
384
  }
346
385
  return !buildChildRelationshipKeys(tasks).has(`${source}->${target}`);
347
386
  }
348
- function isDependencyConnectionValid(connection, tasks, dependencies) {
387
+ function isDependencyConnectionValid(connection, tasks, dependencies, orientation) {
349
388
  const { source, sourceHandle, target, targetHandle } = connection;
350
389
  if (!source || !target || source === target) {
351
390
  return false;
352
391
  }
353
- if (sourceHandle !== "right") {
392
+ if (sourceHandle !== getDependencyHandles(orientation).source) {
354
393
  return false;
355
394
  }
356
395
  if (targetHandle && targetHandle !== "left" && targetHandle !== "top") {
@@ -365,15 +404,18 @@ function isDependencyConnectionValid(connection, tasks, dependencies) {
365
404
  }
366
405
  return !buildChildRelationshipKeys(tasks).has(`${source}->${target}`);
367
406
  }
368
- function isGraphConnectionValid(connection, tasks, dependencies) {
369
- if (connection.sourceHandle === "bottom") {
370
- return isHierarchyConnectionValid(connection, tasks);
407
+ function isGraphConnectionValid(connection, tasks, dependencies, orientation, autoLayoutStrategy) {
408
+ const hierarchySourceHandle = getHierarchyHandles(orientation, "hierarchy").source;
409
+ if (autoLayoutStrategy === "hierarchy" &&
410
+ connection.sourceHandle === hierarchySourceHandle) {
411
+ return isHierarchyConnectionValid(connection, tasks, orientation, autoLayoutStrategy);
371
412
  }
372
- return isDependencyConnectionValid(connection, tasks, dependencies);
413
+ return isDependencyConnectionValid(connection, tasks, dependencies, orientation);
373
414
  }
374
- function createLayoutSignature(layoutKey, nodes, viewport) {
415
+ function createLayoutSignature(layoutKey, nodes, viewport, orientation) {
375
416
  return JSON.stringify({
376
417
  layoutKey,
418
+ orientation,
377
419
  nodes: nodes
378
420
  .map((node) => ({
379
421
  taskId: node.id,
@@ -390,12 +432,43 @@ function createLayoutSignature(layoutKey, nodes, viewport) {
390
432
  : null,
391
433
  });
392
434
  }
435
+ function createGraphSyncSignature(nodes, edges) {
436
+ return JSON.stringify({
437
+ nodes: nodes
438
+ .map((node) => ({
439
+ id: node.id,
440
+ x: node.position.x,
441
+ y: node.position.y,
442
+ selected: node.data.isSelected,
443
+ status: node.data.status,
444
+ executionState: node.data.executionState ?? null,
445
+ blocked: node.data.isBlocked,
446
+ assignee: node.data.assigneeDisplayName ?? null,
447
+ }))
448
+ .sort((a, b) => a.id.localeCompare(b.id)),
449
+ edges: edges
450
+ .map((edge) => ({
451
+ id: edge.id,
452
+ source: edge.source,
453
+ target: edge.target,
454
+ sourceHandle: edge.sourceHandle ?? null,
455
+ targetHandle: edge.targetHandle ?? null,
456
+ }))
457
+ .sort((a, b) => a.id.localeCompare(b.id)),
458
+ });
459
+ }
393
460
  export function DependencyGraphView(props) {
394
- const { projectId, layoutKey, tasks, dependencies, savedLayout, selectedTaskId, selectedDependencyId = null, onSelectTask, onSelectDependency, onClearDependencySelection, 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;
461
+ const { projectId, layoutKey, tasks, dependencies, savedLayout, selectedTaskId, selectedDependencyId = null, onSelectTask, onSelectDependency, onClearDependencySelection, 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", showOrientationToggle = false, layoutSaveDebounceMs = SAVE_DEBOUNCE_MS, onAddDependentTaskFromNode, onAddChildTaskFromNode, onRemoveTask, allowDependencyConnect = false, onCreateDependency, onCreateChildRelationship, miniMapWidth, miniMapHeight, showMiniMap = true, } = props;
395
462
  const canPersistLayout = typeof canPersistLayoutOverride === "boolean"
396
463
  ? canPersistLayoutOverride
397
464
  : permission === "Owner" || permission === "Editor";
398
465
  const effectiveLayoutKey = layoutKey ?? projectId ?? "graph";
466
+ const fallbackOrientation = normalizeGraphOrientation(orientation);
467
+ const savedLayoutOrientation = savedLayout
468
+ ? normalizeGraphOrientation(savedLayout.orientation ?? fallbackOrientation)
469
+ : null;
470
+ const [transientOrientation, setTransientOrientation] = useState(null);
471
+ const effectiveOrientation = transientOrientation ?? savedLayoutOrientation ?? fallbackOrientation;
399
472
  const taskMap = useMemo(() => {
400
473
  const m = new Map();
401
474
  for (const t of tasks)
@@ -407,6 +480,11 @@ export function DependencyGraphView(props) {
407
480
  return new Set();
408
481
  }
409
482
  const blocked = new Set();
483
+ for (const task of tasks) {
484
+ if ((task.executionBlockedByProjectIds?.length ?? 0) > 0) {
485
+ blocked.add(task.taskId);
486
+ }
487
+ }
410
488
  for (const dep of dependencies) {
411
489
  if (dep.dependencyType !== "BlockedBy")
412
490
  continue;
@@ -419,13 +497,13 @@ export function DependencyGraphView(props) {
419
497
  return blocked;
420
498
  }, [dependencies, highlightBlockedTasks, taskMap]);
421
499
  const onSelect = useCallback((taskId) => onSelectTask(taskId), [onSelectTask]);
422
- const { nodes: initialNodes, edges: initialEdges } = useMemo(() => buildGraph(tasks, dependencies, selectedTaskId, selectedDependencyId, blockedTaskIds, orientation, showExecutionStateBadges, autoLayoutStrategy, onSelect, onAddDependentTaskFromNode, onAddChildTaskFromNode, onRemoveTask), [
500
+ const { nodes: initialNodes, edges: initialEdges } = useMemo(() => buildGraph(tasks, dependencies, selectedTaskId, selectedDependencyId, blockedTaskIds, effectiveOrientation, showExecutionStateBadges, autoLayoutStrategy, onSelect, onAddDependentTaskFromNode, onAddChildTaskFromNode, onRemoveTask), [
423
501
  tasks,
424
502
  dependencies,
425
503
  selectedTaskId,
426
504
  selectedDependencyId,
427
505
  blockedTaskIds,
428
- orientation,
506
+ effectiveOrientation,
429
507
  showExecutionStateBadges,
430
508
  autoLayoutStrategy,
431
509
  onSelect,
@@ -436,6 +514,7 @@ export function DependencyGraphView(props) {
436
514
  const [nodes, setNodes, onNodesChangeBase] = useNodesState(initialNodes);
437
515
  const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
438
516
  const nodesRef = useRef(nodes);
517
+ const suppressNodeSelectionUntilRef = useRef(0);
439
518
  const onNodesChange = useCallback((changes) => {
440
519
  onNodesChangeBase(changes);
441
520
  const hasPositionChange = changes.some((c) => c.type === "position" && c.position);
@@ -453,22 +532,36 @@ export function DependencyGraphView(props) {
453
532
  const pendingViewportRef = useRef(null);
454
533
  const fitViewOnNextSyncRef = useRef(false);
455
534
  const skipNextMoveEndSaveRef = useRef(false);
456
- const saveAfterViewportRestoreRef = useRef(false);
535
+ const pendingSaveOrientationRef = useRef(null);
457
536
  const lastSavedSignatureRef = useRef(null);
458
537
  const lastAppliedLayoutKeyRef = useRef(null);
459
538
  const lastAppliedLayoutSignatureRef = useRef(null);
539
+ const lastAppliedGraphSignatureRef = useRef(null);
540
+ const lastAppliedNodeIdSignatureRef = useRef(null);
460
541
  const savedLayoutSignature = useMemo(() => savedLayout
461
542
  ? JSON.stringify({
543
+ orientation: savedLayoutOrientation,
462
544
  updatedAt: savedLayout.updatedAt ?? null,
463
545
  nodes: savedLayout.nodes ?? [],
464
546
  viewport: savedLayout.viewport ?? null,
465
547
  })
466
- : "null", [savedLayout]);
467
- const persistLayout = useCallback(async (nextNodes, viewport) => {
548
+ : "null", [savedLayout, savedLayoutOrientation]);
549
+ useEffect(() => {
550
+ setTransientOrientation(null);
551
+ pendingSaveOrientationRef.current = null;
552
+ }, [effectiveLayoutKey]);
553
+ useEffect(() => {
554
+ if (transientOrientation &&
555
+ savedLayoutOrientation &&
556
+ transientOrientation === savedLayoutOrientation) {
557
+ setTransientOrientation(null);
558
+ }
559
+ }, [savedLayoutOrientation, transientOrientation]);
560
+ const persistLayout = useCallback(async (nextNodes, viewport, nextOrientation) => {
468
561
  if (!canPersistLayout) {
469
562
  return;
470
563
  }
471
- const signature = createLayoutSignature(effectiveLayoutKey, nextNodes, viewport);
564
+ const signature = createLayoutSignature(effectiveLayoutKey, nextNodes, viewport, nextOrientation);
472
565
  if (signature === lastSavedSignatureRef.current) {
473
566
  return;
474
567
  }
@@ -479,6 +572,7 @@ export function DependencyGraphView(props) {
479
572
  x: node.position.x,
480
573
  y: node.position.y,
481
574
  })),
575
+ orientation: nextOrientation,
482
576
  viewport: viewport == null
483
577
  ? null
484
578
  : {
@@ -495,6 +589,7 @@ export function DependencyGraphView(props) {
495
589
  projectId,
496
590
  nodes: layoutPayload.nodes,
497
591
  viewport: layoutPayload.viewport,
592
+ orientation: layoutPayload.orientation,
498
593
  });
499
594
  if (result.type !== "success") {
500
595
  toast.error(result.summary || "Failed to save graph layout");
@@ -506,6 +601,7 @@ export function DependencyGraphView(props) {
506
601
  lastSavedSignatureRef.current = signature;
507
602
  if (nextLayout) {
508
603
  const incomingSignature = JSON.stringify({
604
+ orientation: normalizeGraphOrientation(nextLayout.orientation ?? nextOrientation),
509
605
  updatedAt: nextLayout.updatedAt ?? null,
510
606
  nodes: nextLayout.nodes ?? [],
511
607
  viewport: nextLayout.viewport ?? null,
@@ -522,15 +618,16 @@ export function DependencyGraphView(props) {
522
618
  }, [
523
619
  canPersistLayout,
524
620
  effectiveLayoutKey,
621
+ effectiveOrientation,
525
622
  onLayoutSaved,
526
623
  onPersistLayout,
527
624
  projectId,
528
625
  ]);
529
- const queueLayoutSave = useCallback((nextNodes, viewport) => {
626
+ const queueLayoutSave = useCallback((nextNodes, viewport, nextOrientation = effectiveOrientation) => {
530
627
  if (!canPersistLayout) {
531
628
  return;
532
629
  }
533
- const signature = createLayoutSignature(effectiveLayoutKey, nextNodes, viewport);
630
+ const signature = createLayoutSignature(effectiveLayoutKey, nextNodes, viewport, nextOrientation);
534
631
  if (signature === lastSavedSignatureRef.current) {
535
632
  return;
536
633
  }
@@ -539,14 +636,20 @@ export function DependencyGraphView(props) {
539
636
  saveTimeoutRef.current = null;
540
637
  }
541
638
  if (layoutSaveDebounceMs === 0) {
542
- void persistLayout(nextNodes, viewport);
639
+ void persistLayout(nextNodes, viewport, nextOrientation);
543
640
  return;
544
641
  }
545
642
  saveTimeoutRef.current = window.setTimeout(() => {
546
643
  saveTimeoutRef.current = null;
547
- void persistLayout(nextNodes, viewport);
644
+ void persistLayout(nextNodes, viewport, nextOrientation);
548
645
  }, layoutSaveDebounceMs);
549
- }, [canPersistLayout, effectiveLayoutKey, layoutSaveDebounceMs, persistLayout]);
646
+ }, [
647
+ canPersistLayout,
648
+ effectiveLayoutKey,
649
+ effectiveOrientation,
650
+ layoutSaveDebounceMs,
651
+ persistLayout,
652
+ ]);
550
653
  const restoreViewport = useCallback(() => {
551
654
  const instance = reactFlowRef.current;
552
655
  if (!instance || nodesRef.current.length === 0) {
@@ -558,7 +661,7 @@ export function DependencyGraphView(props) {
558
661
  skipNextMoveEndSaveRef.current = true;
559
662
  window.requestAnimationFrame(() => {
560
663
  reactFlowRef.current?.setViewport(viewport, { duration: 0 });
561
- lastSavedSignatureRef.current = createLayoutSignature(effectiveLayoutKey, nodesRef.current, viewport);
664
+ lastSavedSignatureRef.current = createLayoutSignature(effectiveLayoutKey, nodesRef.current, viewport, effectiveOrientation);
562
665
  });
563
666
  return;
564
667
  }
@@ -569,39 +672,73 @@ export function DependencyGraphView(props) {
569
672
  skipNextMoveEndSaveRef.current = true;
570
673
  window.requestAnimationFrame(() => {
571
674
  reactFlowRef.current?.fitView({ padding: 0.2 });
572
- if (!saveAfterViewportRestoreRef.current) {
675
+ if (!pendingSaveOrientationRef.current) {
573
676
  return;
574
677
  }
575
- saveAfterViewportRestoreRef.current = false;
678
+ const nextOrientation = pendingSaveOrientationRef.current;
679
+ pendingSaveOrientationRef.current = null;
576
680
  const viewport = reactFlowRef.current?.getViewport() ?? null;
577
- queueLayoutSave(nodesRef.current, viewport);
681
+ queueLayoutSave(nodesRef.current, viewport, nextOrientation);
578
682
  });
579
- }, [effectiveLayoutKey, queueLayoutSave]);
683
+ }, [effectiveLayoutKey, effectiveOrientation, queueLayoutSave]);
580
684
  useEffect(() => {
581
685
  const shouldApplySavedLayout = lastAppliedLayoutKeyRef.current !== effectiveLayoutKey ||
582
686
  lastAppliedLayoutSignatureRef.current !== savedLayoutSignature;
687
+ const nextNodeIdSignature = initialNodes.map((node) => node.id).join("|");
688
+ const savedLayoutNodeCount = savedLayout?.nodes?.length ?? 0;
689
+ const shouldAutoLayoutForHierarchy = autoLayoutStrategy === "hierarchy" &&
690
+ (savedLayoutNodeCount < initialNodes.length ||
691
+ (lastAppliedNodeIdSignatureRef.current !== null &&
692
+ lastAppliedNodeIdSignatureRef.current !== nextNodeIdSignature));
583
693
  const nextNodes = shouldApplySavedLayout
584
- ? applySavedLayout(initialNodes, savedLayout)
585
- : preserveNodePositions(nodesRef.current, initialNodes);
694
+ ? shouldAutoLayoutForHierarchy
695
+ ? initialNodes
696
+ : applySavedLayout(initialNodes, savedLayout)
697
+ : shouldAutoLayoutForHierarchy
698
+ ? initialNodes
699
+ : preserveNodePositions(nodesRef.current, initialNodes);
700
+ const nextGraphSignature = createGraphSyncSignature(nextNodes, initialEdges);
701
+ if (!shouldApplySavedLayout &&
702
+ !shouldAutoLayoutForHierarchy &&
703
+ nextGraphSignature === lastAppliedGraphSignatureRef.current) {
704
+ return;
705
+ }
586
706
  setNodes(nextNodes);
587
707
  setEdges(initialEdges);
588
708
  nodesRef.current = nextNodes;
709
+ lastAppliedGraphSignatureRef.current = nextGraphSignature;
710
+ lastAppliedNodeIdSignatureRef.current = nextNodeIdSignature;
589
711
  if (shouldApplySavedLayout) {
590
712
  lastAppliedLayoutKeyRef.current = effectiveLayoutKey;
591
713
  lastAppliedLayoutSignatureRef.current = savedLayoutSignature;
592
- pendingViewportRef.current = savedLayout?.viewport ?? null;
593
- fitViewOnNextSyncRef.current = !savedLayout?.viewport;
594
- if (!savedLayout?.viewport) {
714
+ pendingViewportRef.current =
715
+ shouldAutoLayoutForHierarchy ? null : (savedLayout?.viewport ?? null);
716
+ fitViewOnNextSyncRef.current =
717
+ shouldAutoLayoutForHierarchy || !savedLayout?.viewport;
718
+ if (!savedLayout?.viewport || shouldAutoLayoutForHierarchy) {
595
719
  lastSavedSignatureRef.current = savedLayout?.nodes?.length
596
- ? createLayoutSignature(effectiveLayoutKey, nextNodes, null)
720
+ ? createLayoutSignature(effectiveLayoutKey, nextNodes, null, effectiveOrientation)
597
721
  : null;
598
722
  }
723
+ if (shouldAutoLayoutForHierarchy) {
724
+ pendingSaveOrientationRef.current = effectiveOrientation;
725
+ }
726
+ restoreViewport();
727
+ return;
728
+ }
729
+ if (shouldAutoLayoutForHierarchy) {
730
+ pendingViewportRef.current = null;
731
+ fitViewOnNextSyncRef.current = true;
732
+ pendingSaveOrientationRef.current = effectiveOrientation;
599
733
  restoreViewport();
600
734
  }
601
735
  }, [
736
+ autoLayoutStrategy,
602
737
  initialEdges,
603
738
  initialNodes,
604
739
  effectiveLayoutKey,
740
+ effectiveOrientation,
741
+ projectId,
605
742
  restoreViewport,
606
743
  savedLayout,
607
744
  savedLayoutSignature,
@@ -618,18 +755,18 @@ export function DependencyGraphView(props) {
618
755
  }, []);
619
756
  const hasVisibleNodes = initialNodes.length > 0;
620
757
  const handleAutoLayout = useCallback(() => {
621
- const autoLayout = buildGraph(tasks, dependencies, selectedTaskId, selectedDependencyId, blockedTaskIds, orientation, showExecutionStateBadges, autoLayoutStrategy, onSelect, onAddDependentTaskFromNode, onAddChildTaskFromNode, onRemoveTask);
758
+ const autoLayout = buildGraph(tasks, dependencies, selectedTaskId, selectedDependencyId, blockedTaskIds, effectiveOrientation, showExecutionStateBadges, autoLayoutStrategy, onSelect, onAddDependentTaskFromNode, onAddChildTaskFromNode, onRemoveTask);
622
759
  setNodes(autoLayout.nodes);
623
760
  setEdges(autoLayout.edges);
624
761
  nodesRef.current = autoLayout.nodes;
625
762
  fitViewOnNextSyncRef.current = true;
626
- saveAfterViewportRestoreRef.current = true;
763
+ pendingSaveOrientationRef.current = effectiveOrientation;
627
764
  restoreViewport();
628
765
  }, [
629
766
  blockedTaskIds,
630
767
  dependencies,
768
+ effectiveOrientation,
631
769
  onSelect,
632
- orientation,
633
770
  showExecutionStateBadges,
634
771
  autoLayoutStrategy,
635
772
  onRemoveTask,
@@ -643,8 +780,14 @@ export function DependencyGraphView(props) {
643
780
  tasks,
644
781
  ]);
645
782
  const isValidConnection = useCallback((connection) => allowDependencyConnect
646
- ? isGraphConnectionValid(connection, tasks, dependencies)
647
- : false, [allowDependencyConnect, dependencies, tasks]);
783
+ ? isGraphConnectionValid(connection, tasks, dependencies, effectiveOrientation, autoLayoutStrategy)
784
+ : false, [
785
+ allowDependencyConnect,
786
+ autoLayoutStrategy,
787
+ dependencies,
788
+ effectiveOrientation,
789
+ tasks,
790
+ ]);
648
791
  const handleConnect = useCallback((connection) => {
649
792
  if (!allowDependencyConnect) {
650
793
  return;
@@ -653,24 +796,64 @@ export function DependencyGraphView(props) {
653
796
  if (!source || !target) {
654
797
  return;
655
798
  }
656
- if (!isGraphConnectionValid(connection, tasks, dependencies)) {
799
+ if (!isGraphConnectionValid(connection, tasks, dependencies, effectiveOrientation, autoLayoutStrategy)) {
657
800
  return;
658
801
  }
659
- onSelectTask(target);
660
- if (sourceHandle === "bottom") {
802
+ suppressNodeSelectionUntilRef.current = performance.now() + 150;
803
+ if (autoLayoutStrategy === "hierarchy" &&
804
+ sourceHandle === getHierarchyHandles(effectiveOrientation, "hierarchy").source) {
661
805
  onCreateChildRelationship?.(source, target);
662
- return;
663
806
  }
664
- onCreateDependency?.(source, target);
807
+ else {
808
+ onCreateDependency?.(source, target);
809
+ }
810
+ window.requestAnimationFrame(() => {
811
+ onSelectTask(target);
812
+ });
665
813
  }, [
666
814
  allowDependencyConnect,
815
+ autoLayoutStrategy,
667
816
  dependencies,
817
+ effectiveOrientation,
668
818
  onCreateChildRelationship,
669
819
  onCreateDependency,
670
820
  onSelectTask,
671
821
  tasks,
672
822
  ]);
673
- 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, onPaneClick: () => {
823
+ const handleOrientationChange = useCallback((nextOrientation) => {
824
+ if (nextOrientation === effectiveOrientation) {
825
+ return;
826
+ }
827
+ setTransientOrientation(nextOrientation);
828
+ const autoLayout = buildGraph(tasks, dependencies, selectedTaskId, selectedDependencyId, blockedTaskIds, nextOrientation, showExecutionStateBadges, autoLayoutStrategy, onSelect, onAddDependentTaskFromNode, onAddChildTaskFromNode, onRemoveTask);
829
+ setNodes(autoLayout.nodes);
830
+ setEdges(autoLayout.edges);
831
+ nodesRef.current = autoLayout.nodes;
832
+ fitViewOnNextSyncRef.current = true;
833
+ pendingSaveOrientationRef.current = nextOrientation;
834
+ restoreViewport();
835
+ }, [
836
+ autoLayoutStrategy,
837
+ blockedTaskIds,
838
+ dependencies,
839
+ effectiveOrientation,
840
+ onAddChildTaskFromNode,
841
+ onAddDependentTaskFromNode,
842
+ onRemoveTask,
843
+ onSelect,
844
+ restoreViewport,
845
+ selectedDependencyId,
846
+ selectedTaskId,
847
+ setEdges,
848
+ setNodes,
849
+ showExecutionStateBadges,
850
+ tasks,
851
+ ]);
852
+ 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-0.5 w-4 rounded-sm 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 && (_jsxs("div", { className: "flex items-center gap-2", children: [showOrientationToggle && (_jsxs("div", { className: "inline-flex rounded-md border border-slate-200 bg-white p-0.5", "data-testid": "dependency-graph-orientation-toggle", children: [_jsx("button", { type: "button", className: cn("rounded px-2.5 py-1 text-xs font-medium transition-colors", effectiveOrientation === "horizontal"
853
+ ? "bg-slate-900 text-white"
854
+ : "text-slate-500 hover:bg-slate-100 hover:text-slate-700"), "data-testid": "dependency-graph-orientation-horizontal", onClick: () => handleOrientationChange("horizontal"), children: "Horizontal" }), _jsx("button", { type: "button", className: cn("rounded px-2.5 py-1 text-xs font-medium transition-colors", effectiveOrientation === "vertical"
855
+ ? "bg-slate-900 text-white"
856
+ : "text-slate-500 hover:bg-slate-100 hover:text-slate-700"), "data-testid": "dependency-graph-orientation-vertical", onClick: () => handleOrientationChange("vertical"), children: "Vertical" })] })), 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, onPaneClick: () => {
674
857
  onClearDependencySelection?.();
675
858
  }, onEdgeClick: (_event, edge) => {
676
859
  if (edge.data?.relationship === "dependency" &&
@@ -679,9 +862,15 @@ export function DependencyGraphView(props) {
679
862
  onSelectDependency?.(edge.data.taskId, edge.data.dependsOnTaskId);
680
863
  }
681
864
  }, onNodeClick: (_event, node) => {
865
+ if (performance.now() < suppressNodeSelectionUntilRef.current) {
866
+ return;
867
+ }
682
868
  onClearDependencySelection?.();
683
869
  onSelectTask(node.id);
684
870
  }, onNodeDragStart: (_event, node) => {
871
+ if (performance.now() < suppressNodeSelectionUntilRef.current) {
872
+ return;
873
+ }
685
874
  onClearDependencySelection?.();
686
875
  onSelectTask(node.id);
687
876
  }, onInit: (instance) => {
@@ -697,13 +886,16 @@ export function DependencyGraphView(props) {
697
886
  return;
698
887
  }
699
888
  queueLayoutSave(nodesRef.current, viewport);
700
- }, nodeTypes: nodeTypes, minZoom: 0.2, maxZoom: 2, proOptions: { hideAttribution: true }, nodesDraggable: canPersistLayout, nodesConnectable: allowDependencyConnect, isValidConnection: isValidConnection, elementsSelectable: true, children: [_jsx(Controls, { showInteractive: false, className: "rounded-lg! border-slate-200! shadow-md!" }), _jsx(MiniMap, { ...(miniMapWidth != null && miniMapHeight != null
889
+ }, nodeTypes: nodeTypes, minZoom: 0.2, maxZoom: 2, proOptions: { hideAttribution: true }, nodesDraggable: canPersistLayout, nodesConnectable: allowDependencyConnect, isValidConnection: isValidConnection, elementsSelectable: true, children: [_jsx(Controls, { showInteractive: false, className: "rounded-lg! border-slate-200! shadow-md!" }), showMiniMap ? (_jsx(MiniMap, { ...(miniMapWidth != null && miniMapHeight != null
701
890
  ? { width: miniMapWidth, height: miniMapHeight }
702
891
  : {}), nodeColor: (node) => {
703
- const status = node.data?.status;
892
+ const d = node.data;
893
+ if (d?.isBlocked)
894
+ return "#fecaca";
895
+ const status = d?.status;
704
896
  switch (status) {
705
897
  case "Done":
706
- return "#86efac";
898
+ return "#bbf7d0";
707
899
  case "InProgress":
708
900
  return "#93c5fd";
709
901
  case "Review":
@@ -713,6 +905,6 @@ export function DependencyGraphView(props) {
713
905
  default:
714
906
  return "#cbd5e1";
715
907
  }
716
- }, maskColor: "rgba(0, 0, 0, 0.08)", className: "rounded-lg! border-slate-200! shadow-md!" })] })) })] }));
908
+ }, maskColor: "rgba(0, 0, 0, 0.08)", className: "rounded-lg! border-slate-200! shadow-md!" })) : null] })) })] }));
717
909
  }
718
910
  //# sourceMappingURL=DependencyGraphView.js.map