) => {
@@ -160,7 +154,6 @@ function GraphCanvasInner({
edges={edges}
onNodesChange={isLocked ? undefined : onNodesChange}
onEdgesChange={onEdgesChange}
- onNodeClick={handleNodeClick}
onNodeDoubleClick={handleNodeDoubleClick}
nodeTypes={nodeTypes}
edgeTypes={edgeTypes}
diff --git a/apps/ui/src/components/views/graph-view/graph-view.tsx b/apps/ui/src/components/views/graph-view/graph-view.tsx
index 61b2db14..1f054c3a 100644
--- a/apps/ui/src/components/views/graph-view/graph-view.tsx
+++ b/apps/ui/src/components/views/graph-view/graph-view.tsx
@@ -2,6 +2,7 @@ import { useMemo, useCallback } from 'react';
import { Feature, useAppStore } from '@/store/app-store';
import { GraphCanvas } from './graph-canvas';
import { useBoardBackground } from '../board-view/hooks';
+import { NodeActionCallbacks } from './hooks';
interface GraphViewProps {
features: Feature[];
@@ -13,6 +14,9 @@ interface GraphViewProps {
onSearchQueryChange: (query: string) => void;
onEditFeature: (feature: Feature) => void;
onViewOutput: (feature: Feature) => void;
+ onStartTask?: (feature: Feature) => void;
+ onStopTask?: (feature: Feature) => void;
+ onResumeTask?: (feature: Feature) => void;
}
export function GraphView({
@@ -25,6 +29,9 @@ export function GraphView({
onSearchQueryChange,
onEditFeature,
onViewOutput,
+ onStartTask,
+ onStopTask,
+ onResumeTask,
}: GraphViewProps) {
const { currentProject } = useAppStore();
@@ -56,17 +63,6 @@ export function GraphView({
});
}, [features, currentWorktreePath, currentWorktreeBranch, projectPath]);
- // Handle node click - view details
- const handleNodeClick = useCallback(
- (featureId: string) => {
- const feature = features.find((f) => f.id === featureId);
- if (feature) {
- onViewOutput(feature);
- }
- },
- [features, onViewOutput]
- );
-
// Handle node double click - edit
const handleNodeDoubleClick = useCallback(
(featureId: string) => {
@@ -78,6 +74,44 @@ export function GraphView({
[features, onEditFeature]
);
+ // Node action callbacks for dropdown menu
+ const nodeActionCallbacks: NodeActionCallbacks = useMemo(
+ () => ({
+ onViewLogs: (featureId: string) => {
+ const feature = features.find((f) => f.id === featureId);
+ if (feature) {
+ onViewOutput(feature);
+ }
+ },
+ onStartTask: (featureId: string) => {
+ const feature = features.find((f) => f.id === featureId);
+ if (feature) {
+ onStartTask?.(feature);
+ }
+ },
+ onStopTask: (featureId: string) => {
+ const feature = features.find((f) => f.id === featureId);
+ if (feature) {
+ onStopTask?.(feature);
+ }
+ },
+ onResumeTask: (featureId: string) => {
+ const feature = features.find((f) => f.id === featureId);
+ if (feature) {
+ onResumeTask?.(feature);
+ }
+ },
+ onViewBranch: (featureId: string) => {
+ const feature = features.find((f) => f.id === featureId);
+ if (feature?.branchName) {
+ // TODO: Implement view branch action
+ console.log('View branch:', feature.branchName);
+ }
+ },
+ }),
+ [features, onViewOutput, onStartTask, onStopTask, onResumeTask]
+ );
+
return (
diff --git a/apps/ui/src/components/views/graph-view/hooks/index.ts b/apps/ui/src/components/views/graph-view/hooks/index.ts
index fd08d803..579f583e 100644
--- a/apps/ui/src/components/views/graph-view/hooks/index.ts
+++ b/apps/ui/src/components/views/graph-view/hooks/index.ts
@@ -3,6 +3,7 @@ export {
type TaskNode,
type DependencyEdge,
type TaskNodeData,
+ type NodeActionCallbacks,
} from './use-graph-nodes';
export { useGraphLayout } from './use-graph-layout';
export { useGraphFilter, type GraphFilterState, type GraphFilterResult } from './use-graph-filter';
diff --git a/apps/ui/src/components/views/graph-view/hooks/use-graph-nodes.ts b/apps/ui/src/components/views/graph-view/hooks/use-graph-nodes.ts
index 308bbe2e..5dc38b83 100644
--- a/apps/ui/src/components/views/graph-view/hooks/use-graph-nodes.ts
+++ b/apps/ui/src/components/views/graph-view/hooks/use-graph-nodes.ts
@@ -12,6 +12,12 @@ export interface TaskNodeData extends Feature {
isMatched?: boolean;
isHighlighted?: boolean;
isDimmed?: boolean;
+ // Action callbacks
+ onViewLogs?: () => void;
+ onStartTask?: () => void;
+ onStopTask?: () => void;
+ onResumeTask?: () => void;
+ onViewBranch?: () => void;
}
export type TaskNode = Node;
@@ -22,17 +28,31 @@ export type DependencyEdge = Edge<{
isDimmed?: boolean;
}>;
+export interface NodeActionCallbacks {
+ onViewLogs?: (featureId: string) => void;
+ onStartTask?: (featureId: string) => void;
+ onStopTask?: (featureId: string) => void;
+ onResumeTask?: (featureId: string) => void;
+ onViewBranch?: (featureId: string) => void;
+}
+
interface UseGraphNodesProps {
features: Feature[];
runningAutoTasks: string[];
filterResult?: GraphFilterResult;
+ actionCallbacks?: NodeActionCallbacks;
}
/**
* Transforms features into React Flow nodes and edges
* Creates dependency edges based on feature.dependencies array
*/
-export function useGraphNodes({ features, runningAutoTasks, filterResult }: UseGraphNodesProps) {
+export function useGraphNodes({
+ features,
+ runningAutoTasks,
+ filterResult,
+ actionCallbacks,
+}: UseGraphNodesProps) {
const { nodes, edges } = useMemo(() => {
const nodeList: TaskNode[] = [];
const edgeList: DependencyEdge[] = [];
@@ -70,6 +90,22 @@ export function useGraphNodes({ features, runningAutoTasks, filterResult }: UseG
isMatched,
isHighlighted,
isDimmed,
+ // Action callbacks (bound to this feature's ID)
+ onViewLogs: actionCallbacks?.onViewLogs
+ ? () => actionCallbacks.onViewLogs!(feature.id)
+ : undefined,
+ onStartTask: actionCallbacks?.onStartTask
+ ? () => actionCallbacks.onStartTask!(feature.id)
+ : undefined,
+ onStopTask: actionCallbacks?.onStopTask
+ ? () => actionCallbacks.onStopTask!(feature.id)
+ : undefined,
+ onResumeTask: actionCallbacks?.onResumeTask
+ ? () => actionCallbacks.onResumeTask!(feature.id)
+ : undefined,
+ onViewBranch: actionCallbacks?.onViewBranch
+ ? () => actionCallbacks.onViewBranch!(feature.id)
+ : undefined,
},
};
@@ -107,7 +143,7 @@ export function useGraphNodes({ features, runningAutoTasks, filterResult }: UseG
});
return { nodes: nodeList, edges: edgeList };
- }, [features, runningAutoTasks, filterResult]);
+ }, [features, runningAutoTasks, filterResult, actionCallbacks]);
return { nodes, edges };
}