diff --git a/frontend/src/components/timeline/ExecutionInspector.tsx b/frontend/src/components/timeline/ExecutionInspector.tsx index fcb2afe3..0c63753a 100644 --- a/frontend/src/components/timeline/ExecutionInspector.tsx +++ b/frontend/src/components/timeline/ExecutionInspector.tsx @@ -30,7 +30,7 @@ import { useWorkflowUiStore } from '@/store/workflowUiStore'; import { useWorkflowStore } from '@/store/workflowStore'; import { useArtifactStore } from '@/store/artifactStore'; import { useToast } from '@/components/ui/use-toast'; -import { useRunStore } from '@/store/runStore'; +import { useRunStore, type ExecutionRun } from '@/store/runStore'; import { cn } from '@/lib/utils'; import type { ExecutionLog } from '@/schemas/execution'; import { RunArtifactsPanel } from '@/components/artifacts/RunArtifactsPanel'; @@ -40,6 +40,20 @@ import { NetworkPanel } from '@/components/timeline/NetworkPanel'; import { getTriggerDisplay } from '@/utils/triggerDisplay'; import { RunInfoDisplay } from '@/components/timeline/RunInfoDisplay'; +const TERMINAL_STATUSES: ExecutionRun['status'][] = [ + 'COMPLETED', + 'FAILED', + 'CANCELLED', + 'TERMINATED', + 'TIMED_OUT', +]; + +const isRunLive = (run?: ExecutionRun | null) => { + if (!run) return false; + if (run.isLive) return true; + return !TERMINAL_STATUSES.includes(run.status); +}; + const formatTime = (timestamp: string) => { const date = new Date(timestamp); return date.toLocaleTimeString(); @@ -307,6 +321,10 @@ export function ExecutionInspector({ onRerunRun }: ExecutionInspectorProps = {}) variant="outline" size="sm" className="h-7 px-3 gap-1.5" + disabled={isRunLive(selectedRun)} + title={ + isRunLive(selectedRun) ? 'Wait for run to complete' : 'Rerun this workflow' + } onClick={() => onRerunRun(selectedRun.id)} > diff --git a/frontend/src/components/timeline/RunSelector.tsx b/frontend/src/components/timeline/RunSelector.tsx index c703ae35..d5570da3 100644 --- a/frontend/src/components/timeline/RunSelector.tsx +++ b/frontend/src/components/timeline/RunSelector.tsx @@ -319,6 +319,8 @@ export function RunSelector({ onRerun }: RunSelectorProps = {}) { size="sm" variant="outline" className="h-7 px-3 gap-1.5" + disabled={isRunLive(run)} + title={isRunLive(run) ? 'Wait for run to complete' : 'Rerun this workflow'} onClick={(event) => { event.preventDefault(); event.stopPropagation(); diff --git a/frontend/src/components/ui/markdown.tsx b/frontend/src/components/ui/markdown.tsx index 0c005816..a1bd0091 100644 --- a/frontend/src/components/ui/markdown.tsx +++ b/frontend/src/components/ui/markdown.tsx @@ -203,6 +203,11 @@ export const MarkdownView = memo(function MarkdownView({ } }; + // Prevent wheel events from propagating to React Flow canvas (which would zoom instead of scroll) + const handleWheel = useCallback((e: React.WheelEvent) => { + e.stopPropagation(); + }, []); + return (
); diff --git a/frontend/src/components/workflow/Canvas.tsx b/frontend/src/components/workflow/Canvas.tsx index e3f20d70..588e4210 100644 --- a/frontend/src/components/workflow/Canvas.tsx +++ b/frontend/src/components/workflow/Canvas.tsx @@ -415,9 +415,14 @@ export function Canvas({ requestAnimationFrame(() => { if (!reactFlowInstance) return; - // Re-check workflow nodes (terminal nodes might have been added/removed) - // Use the nodes prop directly since we're inside the effect - const currentWorkflowNodes = nodes.filter((n: Node) => n.type !== 'terminal'); + // IMPORTANT: Get CURRENT nodes from ReactFlow instance, not from the stale closure. + // When mode switches, execution nodes are set asynchronously by useWorkflowExecutionLifecycle. + // The `nodes` variable captured in this closure may be stale (from before the mode switch). + // Using getNodes() ensures we get the most up-to-date node positions. + const currentNodes = reactFlowInstance.getNodes() as Node[]; + const currentWorkflowNodes = currentNodes.filter( + (n: Node) => n.type !== 'terminal', + ); if (currentWorkflowNodes.length === 0) return; try { diff --git a/frontend/src/components/workflow/ExecutionErrorView.tsx b/frontend/src/components/workflow/ExecutionErrorView.tsx index 14360919..12a76620 100644 --- a/frontend/src/components/workflow/ExecutionErrorView.tsx +++ b/frontend/src/components/workflow/ExecutionErrorView.tsx @@ -80,6 +80,8 @@ export const ExecutionErrorView: React.FC = ({ error, c config.border, className, )} + // Prevent wheel events from propagating to React Flow canvas (which would zoom instead of scroll) + onWheel={(e) => e.stopPropagation()} >
{/* Issue list */} -
+
e.stopPropagation()}> {hasIssues ? (
{validationIssues.map((issue, index) => ( @@ -259,6 +259,7 @@ export function ValidationDock({ nodes, edges, mode, onNodeClick }: ValidationDo shouldCollapse && !isExpanded && 'max-h-0', (!shouldCollapse || isExpanded) && 'max-h-[300px] overflow-y-auto', )} + onWheel={(e) => e.stopPropagation()} > {validationIssues.map((issue, index) => (