Workflow Editor Specification
Visual workflow editor for creating and editing DAG-based workflow templates.
Status: Planned (not yet implemented) Source: Architecture Advisory Section 12.2 Related Modules: Workflow Engine, Workflow Templates Sprint: 111_004 Workflow Editor
Overview
The workflow editor provides a visual graph editor for creating and editing workflow templates. It supports drag-and-drop node placement, connection creation, real-time run visualization, and bidirectional YAML synchronization.
Graph Editor Component
Editor State
interface WorkflowEditorState {
template: WorkflowTemplate;
selectedNode: string | null;
selectedEdge: string | null;
zoom: number;
pan: { x: number; y: number };
mode: "select" | "pan" | "connect";
clipboard: StepNode[] | null;
undoStack: WorkflowTemplate[];
redoStack: WorkflowTemplate[];
}
interface WorkflowEditorProps {
template: WorkflowTemplate;
stepTypes: StepType[];
readOnly: boolean;
onSave: (template: WorkflowTemplate) => void;
onValidate: (template: WorkflowTemplate) => ValidationResult;
}
Node Renderer
interface NodeRendererProps {
node: StepNode;
stepType: StepType;
status?: StepRunStatus; // For run visualization
selected: boolean;
onSelect: () => void;
onMove: (position: Position) => void;
onConnect: (sourceHandle: string) => void;
}
const NodeRenderer: React.FC<NodeRendererProps> = ({
node, stepType, status, selected
}) => {
const statusColor = getStatusColor(status);
return (
<div className={`workflow-node ${selected ? 'selected' : ''}`}
style={{ borderColor: statusColor }}>
{/* Node header */}
<div className="node-header" style={{ backgroundColor: stepType.color }}>
<Icon name={stepType.icon} />
<span className="node-name">{node.name}</span>
{status && <StatusBadge status={status} />}
</div>
{/* Node body */}
<div className="node-body">
<span className="node-type">{stepType.name}</span>
{node.timeout && <span className="node-timeout">T {node.timeout}s</span>}
</div>
{/* Connection handles */}
<Handle type="target" position="top" />
<Handle type="source" position="bottom" />
{/* Conditional indicator */}
{node.condition && (
<div className="condition-badge" title={node.condition}>
<Icon name="condition" />
</div>
)}
</div>
);
};
Run Visualization Overlay
Real-Time Execution Display
interface RunVisualizationProps {
template: WorkflowTemplate;
workflowRun: WorkflowRun;
stepRuns: StepRun[];
onNodeClick: (nodeId: string) => void;
}
const RunVisualization: React.FC<RunVisualizationProps> = ({
template, workflowRun, stepRuns, onNodeClick
}) => {
// WebSocket for real-time updates
const { subscribe, unsubscribe } = useWorkflowStream(workflowRun.id);
useEffect(() => {
const handlers = {
'step_started': (data) => updateStepStatus(data.nodeId, 'running'),
'step_completed': (data) => updateStepStatus(data.nodeId, data.status),
'step_log': (data) => appendLog(data.nodeId, data.line),
};
subscribe(handlers);
return () => unsubscribe();
}, [workflowRun.id]);
return (
<div className="run-visualization">
{/* Workflow graph with status overlay */}
<WorkflowGraph
template={template}
nodeRenderer={(node) => (
<NodeRenderer
node={node}
stepType={getStepType(node.type)}
status={getStepRunStatus(node.id)}
selected={selectedNode === node.id}
onSelect={() => setSelectedNode(node.id)}
/>
)}
edgeRenderer={(edge) => (
<EdgeRenderer
edge={edge}
animated={isEdgeActive(edge)}
/>
)}
/>
{/* Log panel */}
{selectedNode && (
<LogPanel
stepRun={getStepRun(selectedNode)}
streaming={isStepRunning(selectedNode)}
/>
)}
{/* Progress bar */}
<ProgressBar
completed={completedSteps}
total={totalSteps}
status={workflowRun.status}
/>
</div>
);
};
Status Indicators
| Status | Visual |
|---|---|
| Pending | Gray circle |
| Running | Blue spinner |
| Success | Green checkmark |
| Failed | Red X |
| Skipped | Yellow dash |
Canvas Operations
Drag and Drop
- Drag steps from palette to canvas
- Drop creates new node at position
- Connect nodes by dragging from source to target handle
- Multi-select with Shift+click or box selection
Validation
The editor performs real-time validation:
- DAG Cycle Detection: Prevent circular dependencies
- Orphan Node Detection: Warn about unconnected nodes
- Required Inputs: Highlight missing required configuration
- Type Compatibility: Validate edge connections between compatible types
Zoom and Pan
| Action | Control |
|---|---|
| Zoom In | Ctrl + Mouse Wheel Up |
| Zoom Out | Ctrl + Mouse Wheel Down |
| Fit View | Ctrl + 0 |
| Pan | Middle Mouse Drag / Space + Drag |
| Reset | Ctrl + R |
YAML Editor Mode
Monaco Editor Integration
The editor supports a bidirectional YAML mode for power users:
interface YAMLEditorProps {
template: WorkflowTemplate;
onChange: (template: WorkflowTemplate) => void;
onValidate: (yaml: string) => ValidationResult;
}
const YAMLEditor: React.FC<YAMLEditorProps> = ({ template, onChange, onValidate }) => {
const [yaml, setYaml] = useState(templateToYaml(template));
return (
<MonacoEditor
language="yaml"
value={yaml}
onChange={(value) => {
setYaml(value);
const result = onValidate(value);
if (result.valid) {
onChange(yamlToTemplate(value));
}
}}
options={{
minimap: { enabled: false },
lineNumbers: 'on',
scrollBeyondLastLine: false,
}}
/>
);
};
Bidirectional Sync
Changes in either view (graph or YAML) are synchronized:
- Graph changes update YAML immediately
- Valid YAML changes update graph
- Invalid YAML shows error markers without updating graph
Step Palette
Available Step Types
The palette shows all available step types from core and plugins:
interface StepPaletteProps {
stepTypes: StepType[];
onDragStart: (stepType: string) => void;
filter: string;
}
const categories = [
{ name: "Deployment", types: ["deploy", "rollback"] },
{ name: "Gates", types: ["security-gate", "approval", "freeze-window-gate"] },
{ name: "Utility", types: ["script", "wait", "notify"] },
{ name: "Plugins", types: [] }, // Dynamically loaded
];
Keyboard Shortcuts
| Shortcut | Action |
|---|---|
| Ctrl + S | Save template |
| Ctrl + Z | Undo |
| Ctrl + Shift + Z | Redo |
| Delete | Delete selected |
| Ctrl + C | Copy selected |
| Ctrl + V | Paste |
| Ctrl + A | Select all |
| Escape | Deselect / Cancel |
