Skip to main content

πŸ”§ CRITICAL WORKFLOW STUDIO BUGS - FIX PLAN

Date: October 25, 2025
Status: 🚨 URGENT FIXES NEEDED
Priority: P0 - Blocking production use


πŸ› BUGS IDENTIFIED​

1. Nodes/Modules Don't Move on Canvas ⚠️ CRITICAL​

Symptom: Drag cursor appears but nodes don't move
Root Cause: ReactFlow missing nodesDraggable={true} prop
Location: WorkflowCanvas.tsx line ~605

2. ExecModules Toolbar Width 🎨 UX​

Symptom: Internal scrolling div not 100% width
Root Cause: .modules-list needs width: 100%
Location: FloatingExecModulesToolbar.css

3. Connector Handle Animation 🎨 UX​

Symptom: Handles bounce up/down, not mushroom-shaped
Root Cause: Animation uses translateY, needs scale instead
Location: WorkflowCanvas.tsx + CSS for handles

4. ExecModule Drop to Task ⚠️ CRITICAL​

Symptom: task.addModule() may not persist correctly
Root Cause: Need robust RTK Query mutation with error handling
Location: ConsolidatedWorkflowStudio.tsx handleDropExecModule

5. Button Group Readability 🎨 UX​

Symptom: Test/Run/Save/Load buttons have light text (unreadable)
Root Cause: Button styling needs dark text
Location: Control panel buttons

6. Test/Run System Enhancement πŸš€ FEATURE​

Symptom: No real-time visualization, no WebSocket updates
Root Cause: Missing WebSocket integration + 3D viewer animation
Location: Multiple files (WebSocket client, Redux, 3D viewer)


🎯 FIX IMPLEMENTATION ORDER​

Phase 1: Critical Bugs (30 mins)​

  1. Fix node dragging (1 line change)
  2. Fix toolbar width (1 CSS change)
  3. Fix button readability (CSS change)

Phase 2: UX Polish (1 hour)​

  1. Enhance handle animation (mushroom shape + scale)
  2. Robust drop-to-task with error handling

Phase 3: Test/Run Enhancement (3-4 hours)​

  1. WebSocket integration
  2. Redux state for execution
  3. 3D viewer real-time animation

πŸ“ DETAILED FIXES​

Fix 1: Enable Node Dragging βœ…β€‹

File: WorkflowCanvas.tsx
Line: ~605

// BEFORE:
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
// ... missing nodesDraggable
>

// AFTER:
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
nodesDraggable={true} // βœ… ENABLE DRAGGING
elementsSelectable={true}
selectNodesOnDrag={false}
panOnDrag={[1, 2]} // Pan with middle/right mouse
>

Fix 2: Toolbar Width βœ…β€‹

File: FloatingExecModulesToolbar.css
Line: ~85

/* BEFORE: */
.modules-list {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 14px 18px;
/* width missing */
}

/* AFTER: */
.modules-list {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 14px 18px;
width: 100%; /* βœ… FULL WIDTH */
box-sizing: border-box;
}

Fix 3: Mushroom Handle Animation βœ…β€‹

File: WorkflowCanvas.tsx
Function: buildHandleStyle

// Add hover animation via CSS class instead of inline styles
const buildHandleStyle = (
accent: string | undefined,
orientation: HandleOrientation,
variant: "source" | "target" = "source"
): React.CSSProperties => {
const normalizedAccent = normalizeHexColor(accent);
const highlight = lightenColor(normalizedAccent, 0.35);
const baseFill = hexToRgba(
normalizedAccent,
variant === "source" ? 0.95 : 0.9
);
const depth = darkenColor(normalizedAccent, 0.25);
const glow = hexToRgba(normalizedAccent, variant === "source" ? 0.3 : 0.22);

const style: React.CSSProperties = {
width: HANDLE_DIAMETER,
height: HANDLE_DIAMETER,
borderRadius: HANDLE_DIAMETER / 2,
border: `3px solid ${normalizedAccent}`,
background: `radial-gradient(circle at 32% 32%, ${highlight} 0%, ${baseFill} 55%, ${depth} 100%)`,
boxShadow: `0 14px 30px rgba(15, 23, 42, 0.55), 0 0 0 12px ${glow}`,
cursor: "grab",
touchAction: "none",
zIndex: 5,
transition: "transform 0.2s ease, box-shadow 0.2s ease", // βœ… ADD TRANSITION
};

// Mushroom shape: wider at top/bottom
if (orientation === "top" || orientation === "bottom") {
style.width = HANDLE_DIAMETER * 1.2; // βœ… WIDER
style.borderRadius = `${HANDLE_DIAMETER * 0.6}px / ${
HANDLE_DIAMETER * 0.5
}px`; // βœ… ELLIPSE
}

// ... position logic
return style;
};

CSS Addition:

/* Add to WorkflowCanvas.css or global styles */
.react-flow__handle:hover {
transform: scale(1.15) !important; /* βœ… BOUNCE-EXPAND */
box-shadow: 0 0 0 16px rgba(56, 189, 248, 0.25) !important;
}

.react-flow__handle:active {
transform: scale(0.95) !important;
cursor: grabbing !important;
}

Fix 4: Robust Drop-to-Task βœ…β€‹

File: ConsolidatedWorkflowStudio.tsx
Function: handleDropExecModule

const handleDropExecModule = React.useCallback(
async (
payload: {
className: string;
adapter?: any;
label?: string;
displayName?: string;
defaultModuleData?: string;
moduleType?: string;
accentColor?: string;
},
position: { x: number; y: number }
) => {
appendLog(
`πŸ“¦ Dropping ExecModule: ${payload.displayName || payload.className}`
);

try {
// 1. Find target node at drop position
const targetNode = nodes.find((n) => {
const nodeEl = document.querySelector(`[data-id="${n.id}"]`);
if (!nodeEl) return false;
const rect = nodeEl.getBoundingClientRect();
const canvasRect = canvasRef.current?.getBoundingClientRect();
if (!canvasRect) return false;

const relX = position.x - canvasRect.left;
const relY = position.y - canvasRect.top;

return (
relX >= rect.left - canvasRect.left &&
relX <= rect.right - canvasRect.left &&
relY >= rect.top - canvasRect.top &&
relY <= rect.bottom - canvasRect.top
);
});

if (targetNode && targetNode.type === "task") {
// βœ… DROP ON TASK - ADD MODULE TO TASK
appendLog(`🎯 Dropping module on task: ${targetNode.data.label}`);

// 2. Create ExecModule object
const newModule: Partial<ExecModule> = {
name:
payload.displayName ||
payload.label ||
payload.className.split(".").pop(),
className: payload.className,
moduleType: payload.moduleType || "INTEGRATION",
moduleData:
payload.defaultModuleData || payload.adapter
? JSON.stringify(payload.adapter)
: "{}",
status: "DRAFT",
notes: `Dropped at ${new Date().toISOString()}`,
};

// 3. Save to backend via RTK Query
appendLog("πŸ’Ύ Saving module to backend...");
const saveResult = await dispatchRequest(
requestAsync(postExecModule({ execModule: newModule }))
);

if (!saveResult?.value?.body?.id) {
throw new Error("Failed to save module - no ID returned");
}

const savedModule = saveResult.value.body;
appendLog(`βœ… Module saved with ID: ${savedModule.id}`);

// 4. Associate module with task (if Task has modules array in backend)
// TODO: Implement task.addModule() API endpoint
appendLog(
`πŸ”— Associating module ${savedModule.id} with task ${targetNode.id}`
);

// 5. Update node data to show module
setNodes((nds) =>
nds.map((n) =>
n.id === targetNode.id
? {
...n,
data: {
...n.data,
modules: [...(n.data.modules || []), savedModule],
},
}
: n
)
);

appendLog("πŸŽ‰ Module successfully added to task!");
} else {
// βœ… DROP ON CANVAS - CREATE STANDALONE TASK WITH MODULE
appendLog("πŸ“ Dropping module on canvas (creating new task)");
handleDropNewNode("task", position, {
label: payload.displayName || payload.label || "Task",
execModule: payload.className,
modules: [
{
name: payload.displayName || payload.label,
className: payload.className,
moduleType: payload.moduleType,
moduleData: payload.defaultModuleData,
},
],
});
}
} catch (error) {
appendLog(`❌ ERROR dropping module: ${(error as Error).message}`);
console.error("handleDropExecModule error:", error);
// TODO: Show user-friendly error toast
}
},
[appendLog, handleDropNewNode, nodes, dispatchRequest]
);

Fix 5: Button Readability βœ…β€‹

File: Find control panel CSS or add inline styles

// In ConsolidatedWorkflowStudio.tsx or control panel component:
<ButtonGroup>
<CoolButton
variant="dark"
style={{
color: "#1e293b", // βœ… DARK TEXT
backgroundColor: "#f8fafc",
fontWeight: 600,
}}
>
Test
</CoolButton>
<CoolButton
variant="dark"
style={{ color: "#1e293b", backgroundColor: "#f8fafc", fontWeight: 600 }}
>
Run
</CoolButton>
{/* ... etc */}
</ButtonGroup>

Fix 6: WebSocket + 3D Real-Time βœ…β€‹

This is a major feature requiring multiple files. See separate implementation plan below.


πŸš€ WEBSOCKET INTEGRATION ARCHITECTURE​

Step 1: WebSocket Client Hook​

File: src/hooks/useWorkflowWebSocket.ts (NEW)

import { useEffect, useRef, useState } from "react";
import { useAppDispatch } from "../redux/hooks";
import {
updateTaskStatus,
updateWorkflowProgress,
} from "../redux/features/workflows/workflowCanvasSlice";

interface WorkflowExecutionUpdate {
workflowId: string;
taskId?: string;
status: "RUNNING" | "COMPLETED" | "FAILED";
progress?: number;
output?: any;
timestamp: string;
}

export const useWorkflowWebSocket = (workflowId: string | null) => {
const dispatch = useAppDispatch();
const wsRef = useRef<WebSocket | null>(null);
const [isConnected, setIsConnected] = useState(false);

useEffect(() => {
if (!workflowId) return;

const ws = new WebSocket(
`ws://localhost:8080/v1/vaiworkflow/subscribe/${workflowId}`
);
wsRef.current = ws;

ws.onopen = () => {
console.log("πŸ”Œ WebSocket connected for workflow:", workflowId);
setIsConnected(true);
};

ws.onmessage = (event) => {
try {
const update: WorkflowExecutionUpdate = JSON.parse(event.data);
console.log("πŸ“¨ Workflow update:", update);

// Update Redux state
if (update.taskId) {
dispatch(
updateTaskStatus({
taskId: update.taskId,
status: update.status,
output: update.output,
})
);
}

dispatch(
updateWorkflowProgress({
workflowId: update.workflowId,
progress: update.progress || 0,
})
);
} catch (error) {
console.error("Failed to parse WebSocket message:", error);
}
};

ws.onerror = (error) => {
console.error("❌ WebSocket error:", error);
setIsConnected(false);
};

ws.onclose = () => {
console.log("πŸ”Œ WebSocket disconnected");
setIsConnected(false);
};

return () => {
ws.close();
};
}, [workflowId, dispatch]);

return { isConnected };
};

Step 2: Redux State Updates​

File: redux/features/workflows/workflowCanvasSlice.ts

// Add to existing slice:
interface TaskExecutionState {
taskId: string;
status: 'IDLE' | 'RUNNING' | 'COMPLETED' | 'FAILED';
startTime?: number;
endTime?: number;
output?: any;
error?: string;
}

interface WorkflowCanvasState {
// ... existing state
executionStates: Record<string, TaskExecutionState>;
currentProgress: number;
isExecuting: boolean;
}

// Add reducers:
updateTaskStatus: (state, action: PayloadAction<{
taskId: string;
status: TaskExecutionState['status'];
output?: any;
error?: string;
}>) => {
state.executionStates[action.payload.taskId] = {
...state.executionStates[action.payload.taskId],
taskId: action.payload.taskId,
status: action.payload.status,
output: action.payload.output,
error: action.payload.error,
...(action.payload.status === 'RUNNING' && { startTime: Date.now() }),
...(action.payload.status === 'COMPLETED' || action.payload.status === 'FAILED') && { endTime: Date.now() },
};
},

updateWorkflowProgress: (state, action: PayloadAction<{
workflowId: string;
progress: number;
}>) => {
state.currentProgress = action.payload.progress;
state.isExecuting = action.payload.progress < 100;
},

Step 3: 3D Viewer Animation​

File: Workflow3DViewer.tsx

// Add animation for active tasks:
const TaskMesh = ({ task, isActive, isCompleted }) => {
const meshRef = useRef();

useFrame((state) => {
if (!meshRef.current) return;

if (isActive) {
// βœ… PULSE ANIMATION for running task
meshRef.current.scale.setScalar(
1 + Math.sin(state.clock.elapsedTime * 3) * 0.1
);
meshRef.current.material.emissive.setHex(0x00ff00);
meshRef.current.material.emissiveIntensity =
0.5 + Math.sin(state.clock.elapsedTime * 2) * 0.3;
} else if (isCompleted) {
// βœ… COMPLETED GLOW
meshRef.current.material.emissive.setHex(0x00aaff);
meshRef.current.material.emissiveIntensity = 0.3;
} else {
// IDLE
meshRef.current.scale.setScalar(1);
meshRef.current.material.emissiveIntensity = 0;
}
});

return (
<mesh ref={meshRef} position={[task.x, task.y, 0]}>
<boxGeometry args={[1, 1, 0.2]} />
<meshStandardMaterial color="#6366f1" />
</mesh>
);
};

Step 4: Integration​

File: ConsolidatedWorkflowStudio.tsx

import { useWorkflowWebSocket } from '../../hooks/useWorkflowWebSocket';

export const ConsolidatedWorkflowStudio = () => {
const [currentWorkflowId, setCurrentWorkflowId] = useState<string | null>(null);
const { isConnected } = useWorkflowWebSocket(currentWorkflowId);

const handleRunWorkflow = async () => {
appendLog("▢️ Starting workflow execution...");

try {
// 1. Save workflow if not saved
if (!currentWorkflowId) {
const result = await saveWorkflowToDatabase();
setCurrentWorkflowId(result.id);
}

// 2. Trigger execution
const response = await fetch(`/v1/vaiworkflow/${currentWorkflowId}/execute`, {
method: 'POST',
});

if (!response.ok) {
throw new Error('Failed to start workflow');
}

appendLog("βœ… Workflow execution started!");
appendLog(`πŸ”Œ WebSocket ${isConnected ? 'connected' : 'connecting'}...`);

} catch (error) {
appendLog(`❌ ERROR: ${(error as Error).message}`);
}
};

return (
// ... JSX with Run button calling handleRunWorkflow
);
};

βœ… TESTING CHECKLIST​

Phase 1 Tests​

  • Nodes drag and move freely on canvas
  • ExecModules toolbar shows full width scroll area
  • Button text is readable (dark text on light background)

Phase 2 Tests​

  • Connector handles are mushroom-shaped
  • Handles scale up on hover (not bounce up/down)
  • Dropping ExecModule on task adds module correctly
  • Error handling works (console shows errors)
  • Module persists after refresh

Phase 3 Tests​

  • WebSocket connects when running workflow
  • Redux state updates in real-time
  • 3D viewer shows pulse animation on active tasks
  • Completed tasks show blue glow
  • Progress bar updates smoothly
  • Console shows real-time execution logs

🎯 SUCCESS METRICS​

  • βœ… All drag operations work smoothly
  • βœ… UI is readable and professional
  • βœ… Drop-to-task is 100% reliable
  • βœ… Real-time execution visualization works
  • βœ… Zero console errors
  • βœ… Users say "This is the best workflow editor I've ever used"

Status: πŸ“‹ Plan ready - awaiting implementation
Estimated Time: 5-6 hours total
Priority Order: Fix 1-3 β†’ Fix 4-5 β†’ Fix 6

LET'S MAKE THIS THE BEST WORKFLOW STUDIO ON THE PLANET! πŸš€