Files
automaker/apps/ui/src/components/views/graph-view/hooks/use-graph-nodes.ts
Shirone 5c335641fa chore: Fix all 246 TypeScript errors in UI
- Extended SetupAPI interface with 20+ missing methods for Cursor, Codex,
  OpenCode, Gemini, and Copilot CLI integrations
- Fixed WorktreeInfo type to include isCurrent and hasWorktree fields
- Added null checks for optional API properties across all hooks
- Fixed Feature type conflicts between @automaker/types and local definitions
- Added missing CLI status hooks for all providers
- Fixed type mismatches in mutation callbacks and event handlers
- Removed dead code referencing non-existent GlobalSettings properties
- Updated mock implementations in electron.ts for all new API methods

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 18:36:47 +01:00

196 lines
6.8 KiB
TypeScript

import { useMemo } from 'react';
import { Node, Edge } from '@xyflow/react';
import { Feature } from '@/store/app-store';
import { createFeatureMap, getBlockingDependenciesFromMap } from '@automaker/dependency-resolver';
import { GRAPH_RENDER_MODE_FULL, type GraphRenderMode } from '../constants';
import { GraphFilterResult } from './use-graph-filter';
export interface TaskNodeData extends Feature {
// Re-declare properties from BaseFeature that have index signature issues
priority?: number;
error?: string;
branchName?: string;
dependencies?: string[];
// Task node specific properties
isBlocked: boolean;
isRunning: boolean;
blockingDependencies: string[];
// Filter highlight states
isMatched?: boolean;
isHighlighted?: boolean;
isDimmed?: boolean;
// Background/theme settings
cardOpacity?: number;
cardGlassmorphism?: boolean;
cardBorderEnabled?: boolean;
cardBorderOpacity?: number;
// Action callbacks
onViewLogs?: () => void;
onViewDetails?: () => void;
onStartTask?: () => void;
onStopTask?: () => void;
onResumeTask?: () => void;
onSpawnTask?: () => void;
onDeleteTask?: () => void;
renderMode?: GraphRenderMode;
}
export type TaskNode = Node<TaskNodeData, 'task'>;
export type DependencyEdge = Edge<{
sourceStatus: Feature['status'];
targetStatus: Feature['status'];
isHighlighted?: boolean;
isDimmed?: boolean;
onDeleteDependency?: (sourceId: string, targetId: string) => void;
renderMode?: GraphRenderMode;
}>;
export interface NodeActionCallbacks {
onViewLogs?: (featureId: string) => void;
onViewDetails?: (featureId: string) => void;
onStartTask?: (featureId: string) => void;
onStopTask?: (featureId: string) => void;
onResumeTask?: (featureId: string) => void;
onSpawnTask?: (featureId: string) => void;
onDeleteTask?: (featureId: string) => void;
onDeleteDependency?: (sourceId: string, targetId: string) => void;
}
interface BackgroundSettings {
cardOpacity: number;
cardGlassmorphism: boolean;
cardBorderEnabled: boolean;
cardBorderOpacity: number;
}
interface UseGraphNodesProps {
features: Feature[];
runningAutoTasks: string[];
filterResult?: GraphFilterResult;
actionCallbacks?: NodeActionCallbacks;
backgroundSettings?: BackgroundSettings;
renderMode?: GraphRenderMode;
enableEdgeAnimations?: boolean;
}
/**
* Transforms features into React Flow nodes and edges
* Creates dependency edges based on feature.dependencies array
*/
export function useGraphNodes({
features,
runningAutoTasks,
filterResult,
actionCallbacks,
backgroundSettings,
renderMode = GRAPH_RENDER_MODE_FULL,
enableEdgeAnimations = true,
}: UseGraphNodesProps) {
const { nodes, edges } = useMemo(() => {
const nodeList: TaskNode[] = [];
const edgeList: DependencyEdge[] = [];
const featureMap = createFeatureMap(features);
const runningTaskIds = new Set(runningAutoTasks);
// Extract filter state
const hasActiveFilter = filterResult?.hasActiveFilter ?? false;
const matchedNodeIds = filterResult?.matchedNodeIds ?? new Set<string>();
const highlightedNodeIds = filterResult?.highlightedNodeIds ?? new Set<string>();
const highlightedEdgeIds = filterResult?.highlightedEdgeIds ?? new Set<string>();
// Create nodes
features.forEach((feature) => {
const isRunning = runningTaskIds.has(feature.id);
const blockingDeps = getBlockingDependenciesFromMap(feature, featureMap);
// Calculate filter highlight states
const isMatched = hasActiveFilter && matchedNodeIds.has(feature.id);
const isHighlighted = hasActiveFilter && highlightedNodeIds.has(feature.id);
const isDimmed = hasActiveFilter && !highlightedNodeIds.has(feature.id);
const node: TaskNode = {
id: feature.id,
type: 'task',
position: { x: 0, y: 0 }, // Will be set by layout
data: {
...feature,
isBlocked: blockingDeps.length > 0,
isRunning,
blockingDependencies: blockingDeps,
// Filter states
isMatched,
isHighlighted,
isDimmed,
// Background/theme settings
cardOpacity: backgroundSettings?.cardOpacity,
cardGlassmorphism: backgroundSettings?.cardGlassmorphism,
cardBorderEnabled: backgroundSettings?.cardBorderEnabled,
cardBorderOpacity: backgroundSettings?.cardBorderOpacity,
renderMode,
// Action callbacks (bound to this feature's ID)
onViewLogs: actionCallbacks?.onViewLogs
? () => actionCallbacks.onViewLogs!(feature.id)
: undefined,
onViewDetails: actionCallbacks?.onViewDetails
? () => actionCallbacks.onViewDetails!(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,
onSpawnTask: actionCallbacks?.onSpawnTask
? () => actionCallbacks.onSpawnTask!(feature.id)
: undefined,
onDeleteTask: actionCallbacks?.onDeleteTask
? () => actionCallbacks.onDeleteTask!(feature.id)
: undefined,
},
};
nodeList.push(node);
// Create edges for dependencies
const deps = feature.dependencies as string[] | undefined;
if (deps && deps.length > 0) {
deps.forEach((depId: string) => {
// Only create edge if the dependency exists in current view
if (featureMap.has(depId)) {
const sourceFeature = featureMap.get(depId)!;
const edgeId = `${depId}->${feature.id}`;
// Calculate edge highlight states
const edgeIsHighlighted = hasActiveFilter && highlightedEdgeIds.has(edgeId);
const edgeIsDimmed = hasActiveFilter && !highlightedEdgeIds.has(edgeId);
const edge: DependencyEdge = {
id: edgeId,
source: depId,
target: feature.id,
type: 'dependency',
animated: enableEdgeAnimations && (isRunning || runningTaskIds.has(depId)),
data: {
sourceStatus: sourceFeature.status as Feature['status'],
targetStatus: feature.status,
isHighlighted: edgeIsHighlighted,
isDimmed: edgeIsDimmed,
onDeleteDependency: actionCallbacks?.onDeleteDependency,
renderMode,
},
};
edgeList.push(edge);
}
});
}
});
return { nodes: nodeList, edges: edgeList };
}, [features, runningAutoTasks, filterResult, actionCallbacks, backgroundSettings]);
return { nodes, edges };
}