Visual Designer Guide
Complete guide to using Axon OS's visual workflow designer, including interface overview, design patterns, and advanced features.
Overview
The Axon OS Visual Designer is an intuitive drag-and-drop interface for creating, editing, and managing workflows. It provides a comprehensive set of tools for building complex automation workflows without writing code.
Key Features
- Drag-and-Drop Interface: Intuitive node placement and connection
- Real-time Validation: Instant feedback on workflow structure
- Smart Suggestions: AI-powered node recommendations
- Collaborative Editing: Multi-user workflow development
- Version Control: Built-in workflow versioning
- Testing Tools: Integrated workflow testing and debugging
Interface Overview
Main Components
1. Canvas Area
The central workspace where workflows are designed:
interface CanvasConfig {
// Grid settings
gridSize: number;
snapToGrid: boolean;
showGrid: boolean;
// Zoom settings
minZoom: number;
maxZoom: number;
defaultZoom: number;
// Layout options
autoLayout: boolean;
layoutDirection: 'horizontal' | 'vertical';
nodeSpacing: number;
}
Canvas Features:
- Infinite Canvas: Unlimited workspace for large workflows
- Grid Snapping: Precise node alignment
- Zoom Controls: Detailed view and overview modes
- Mini-map: Navigation for complex workflows
- Pan and Zoom: Mouse and keyboard navigation
2. Node Library Panel
Categorized collection of available nodes:
interface NodeCategory {
id: string;
name: string;
icon: string;
description: string;
nodes: NodeDefinition[];
collapsed?: boolean;
}
interface NodeDefinition {
id: string;
name: string;
type: string;
category: string;
icon: string;
description: string;
inputs: InputPort[];
outputs: OutputPort[];
configuration: ConfigurationSchema;
}
Node Categories:
- 📊 Data Processing: Transform, filter, aggregate data
- 🌐 API & Web: HTTP requests, webhooks, REST APIs
- 📧 Communication: Email, SMS, notifications
- 🗄️ Database: SQL queries, database operations
- 🔄 Control Flow: Conditions, loops, switches
- 🛠️ Utilities: Math, text, date operations
- 🔌 Integrations: Third-party service connectors
- 🎯 Custom: User-defined nodes
3. Properties Panel
Configuration interface for selected nodes:
interface PropertyPanel {
nodeId: string;
sections: PropertySection[];
validation: ValidationResult[];
helpText?: string;
}
interface PropertySection {
title: string;
collapsed: boolean;
properties: Property[];
}
interface Property {
key: string;
type: 'string' | 'number' | 'boolean' | 'select' | 'json' | 'code';
label: string;
description?: string;
required: boolean;
value: any;
validation?: ValidationRule[];
dependencies?: PropertyDependency[];
}
4. Toolbar
Quick access to common actions:
interface ToolbarAction {
id: string;
label: string;
icon: string;
shortcut?: string;
action: () => void;
disabled?: boolean;
group?: string;
}
const toolbarActions: ToolbarAction[] = [
// File operations
{ id: 'new', label: 'New Workflow', icon: 'plus', shortcut: 'Ctrl+N' },
{ id: 'open', label: 'Open', icon: 'folder-open', shortcut: 'Ctrl+O' },
{ id: 'save', label: 'Save', icon: 'save', shortcut: 'Ctrl+S' },
// Edit operations
{ id: 'undo', label: 'Undo', icon: 'undo', shortcut: 'Ctrl+Z' },
{ id: 'redo', label: 'Redo', icon: 'redo', shortcut: 'Ctrl+Y' },
{ id: 'copy', label: 'Copy', icon: 'copy', shortcut: 'Ctrl+C' },
{ id: 'paste', label: 'Paste', icon: 'paste', shortcut: 'Ctrl+V' },
// View operations
{ id: 'zoom-in', label: 'Zoom In', icon: 'zoom-in', shortcut: 'Ctrl++' },
{ id: 'zoom-out', label: 'Zoom Out', icon: 'zoom-out', shortcut: 'Ctrl+-' },
{ id: 'fit-view', label: 'Fit to View', icon: 'expand', shortcut: 'Ctrl+0' },
// Workflow operations
{ id: 'validate', label: 'Validate', icon: 'check-circle', shortcut: 'F7' },
{ id: 'test', label: 'Test Run', icon: 'play', shortcut: 'F5' },
{ id: 'deploy', label: 'Deploy', icon: 'upload', shortcut: 'Ctrl+D' }
];
Workflow Design Process
1. Creating a New Workflow
Basic Setup
interface WorkflowMetadata {
name: string;
description: string;
version: string;
tags: string[];
category: string;
author: string;
createdAt: Date;
updatedAt: Date;
}
// Example workflow creation
const newWorkflow: WorkflowMetadata = {
name: "Data Processing Pipeline",
description: "Processes incoming data and sends notifications",
version: "1.0.0",
tags: ["data", "processing", "notifications"],
category: "data-processing",
author: "user@example.com",
createdAt: new Date(),
updatedAt: new Date()
};
Template Selection
The designer offers pre-built templates for common use cases:
- 📊 Data ETL Pipeline: Extract, transform, load operations
- 🔄 API Integration: Connect multiple services
- 📧 Notification Workflow: Multi-channel notifications
- 🗄️ Database Sync: Keep databases synchronized
- 🎯 Custom Logic: Blank canvas for custom workflows
2. Adding and Configuring Nodes
Node Placement
interface NodePlacement {
position: { x: number; y: number };
autoConnect: boolean;
insertBetween?: { sourceId: string; targetId: string };
}
// Add node with automatic positioning
function addNode(nodeType: string, placement: NodePlacement): void {
const node = createNodeInstance(nodeType);
if (placement.autoConnect) {
// Connect to previously selected node
connectToSelected(node);
}
if (placement.insertBetween) {
// Insert between existing nodes
insertBetweenNodes(node, placement.insertBetween);
}
canvas.addNode(node, placement.position);
}
Node Configuration
Each node has a configuration schema that defines its properties:
interface NodeConfigurationSchema {
properties: {
[key: string]: {
type: string;
title: string;
description?: string;
default?: any;
enum?: any[];
required?: boolean;
validation?: ValidationRule[];
};
};
required: string[];
dependencies?: ConfigDependencies;
}
// Example: HTTP Request node configuration
const httpRequestSchema: NodeConfigurationSchema = {
properties: {
url: {
type: "string",
title: "Request URL",
description: "The URL to send the request to",
required: true,
validation: [{ type: "url" }]
},
method: {
type: "string",
title: "HTTP Method",
enum: ["GET", "POST", "PUT", "DELETE", "PATCH"],
default: "GET"
},
headers: {
type: "object",
title: "Request Headers",
description: "Additional headers to include"
},
timeout: {
type: "number",
title: "Timeout (seconds)",
default: 30,
validation: [{ type: "range", min: 1, max: 300 }]
}
},
required: ["url", "method"]
};
3. Connecting Nodes
Connection Types
interface Connection {
id: string;
sourceNodeId: string;
sourcePortId: string;
targetNodeId: string;
targetPortId: string;
type: ConnectionType;
conditions?: ConnectionCondition[];
}
enum ConnectionType {
DATA = 'data', // Data flow connection
CONTROL = 'control', // Execution flow connection
ERROR = 'error', // Error handling connection
CONDITIONAL = 'conditional' // Conditional execution
}
interface ConnectionCondition {
field: string;
operator: 'equals' | 'not_equals' | 'greater_than' | 'less_than' | 'contains';
value: any;
}
Smart Connection Features
class ConnectionManager {
// Automatic type validation
validateConnection(source: Port, target: Port): ValidationResult {
// Check data type compatibility
if (!this.areTypesCompatible(source.dataType, target.dataType)) {
return {
valid: false,
error: `Cannot connect ${source.dataType} to ${target.dataType}`
};
}
// Check for circular dependencies
if (this.wouldCreateCycle(source.nodeId, target.nodeId)) {
return {
valid: false,
error: "Connection would create a circular dependency"
};
}
return { valid: true };
}
// Auto-suggest transformations
suggestTransformations(source: Port, target: Port): TransformationSuggestion[] {
const suggestions: TransformationSuggestion[] = [];
if (source.dataType === 'string' && target.dataType === 'number') {
suggestions.push({
type: 'parse_number',
description: 'Convert string to number',
confidence: 0.9
});
}
return suggestions;
}
// Connection optimization
optimizeConnections(): void {
// Remove unnecessary intermediate nodes
// Combine compatible sequential operations
// Suggest batching opportunities
}
}
Advanced Design Features
1. Subworkflows and Components
Creating Reusable Components
interface WorkflowComponent {
id: string;
name: string;
description: string;
inputs: ComponentInput[];
outputs: ComponentOutput[];
workflow: WorkflowDefinition;
version: string;
}
interface ComponentInput {
name: string;
type: string;
required: boolean;
description?: string;
defaultValue?: any;
}
// Example: Email notification component
const emailNotificationComponent: WorkflowComponent = {
id: 'email-notification',
name: 'Email Notification',
description: 'Sends formatted email notifications',
inputs: [
{
name: 'recipient',
type: 'string',
required: true,
description: 'Email recipient address'
},
{
name: 'subject',
type: 'string',
required: true,
description: 'Email subject line'
},
{
name: 'data',
type: 'object',
required: true,
description: 'Data to include in email'
}
],
outputs: [
{
name: 'success',
type: 'boolean',
description: 'Whether email was sent successfully'
},
{
name: 'messageId',
type: 'string',
description: 'Email message ID if successful'
}
],
workflow: {
// Internal workflow definition
},
version: '1.0.0'
};
Component Library Management
class ComponentLibrary {
private components: Map<string, WorkflowComponent> = new Map();
async publishComponent(component: WorkflowComponent): Promise<void> {
// Validate component
const validation = await this.validateComponent(component);
if (!validation.valid) {
throw new Error(`Component validation failed: ${validation.errors.join(', ')}`);
}
// Version management
const existingComponent = this.components.get(component.id);
if (existingComponent) {
component.version = this.incrementVersion(existingComponent.version);
}
// Store component
this.components.set(component.id, component);
// Update library index
await this.updateLibraryIndex();
}
searchComponents(query: string, category?: string): WorkflowComponent[] {
return Array.from(this.components.values())
.filter(component => {
const matchesQuery = component.name.toLowerCase().includes(query.toLowerCase()) ||
component.description.toLowerCase().includes(query.toLowerCase());
const matchesCategory = !category || component.category === category;
return matchesQuery && matchesCategory;
});
}
}
2. Conditional Logic and Branching
Switch Node Configuration
interface SwitchNodeConfig {
expression: string;
cases: SwitchCase[];
defaultCase?: string;
evaluationMode: 'javascript' | 'json_path' | 'simple';
}
interface SwitchCase {
id: string;
condition: string;
description?: string;
outputPort: string;
}
// Example: Status-based routing
const statusSwitchConfig: SwitchNodeConfig = {
expression: "data.status",
evaluationMode: "json_path",
cases: [
{
id: "success",
condition: "success",
description: "Route to success handler",
outputPort: "success"
},
{
id: "error",
condition: "error",
description: "Route to error handler",
outputPort: "error"
},
{
id: "pending",
condition: "pending",
description: "Route to retry handler",
outputPort: "pending"
}
],
defaultCase: "unknown"
};
Loop Constructs
interface LoopNodeConfig {
type: 'for_each' | 'while' | 'do_while' | 'count';
condition?: string;
maxIterations?: number;
batchSize?: number;
parallelism?: number;
}
// Example: Batch processing loop
const batchProcessingLoop: LoopNodeConfig = {
type: 'for_each',
batchSize: 10,
parallelism: 3,
maxIterations: 1000
};
3. Error Handling Design
Error Boundaries
interface ErrorBoundary {
id: string;
name: string;
scope: 'node' | 'branch' | 'workflow';
catchTypes: ErrorType[];
retryPolicy?: RetryPolicy;
fallbackAction?: FallbackAction;
}
interface RetryPolicy {
maxAttempts: number;
delay: number;
backoffMultiplier: number;
maxDelay: number;
}
// Example: Resilient API call with retries
const apiErrorBoundary: ErrorBoundary = {
id: 'api-error-boundary',
name: 'API Error Handler',
scope: 'branch',
catchTypes: ['network_error', 'timeout_error', 'rate_limit_error'],
retryPolicy: {
maxAttempts: 3,
delay: 1000,
backoffMultiplier: 2,
maxDelay: 10000
},
fallbackAction: {
type: 'default_value',
value: { status: 'unavailable' }
}
};
4. Data Transformation Patterns
Visual Data Mapping
interface DataMapping {
sourceField: string;
targetField: string;
transformation?: DataTransformation;
condition?: string;
}
interface DataTransformation {
type: 'function' | 'expression' | 'lookup';
operation: string;
parameters?: any;
}
// Example: Complex data mapping
const userDataMapping: DataMapping[] = [
{
sourceField: "user.firstName",
targetField: "name.first",
transformation: {
type: "function",
operation: "capitalize"
}
},
{
sourceField: "user.email",
targetField: "contact.email",
transformation: {
type: "function",
operation: "toLowerCase"
}
},
{
sourceField: "user.birthDate",
targetField: "demographics.age",
transformation: {
type: "expression",
operation: "Math.floor((Date.now() - new Date(value)) / (1000 * 60 * 60 * 24 * 365))"
}
}
];
Collaborative Features
1. Real-time Collaboration
Multi-user Editing
interface CollaborationSession {
workflowId: string;
participants: Participant[];
changes: Change[];
version: number;
}
interface Participant {
userId: string;
username: string;
cursor: { x: number; y: number };
selection: string[];
color: string;
lastActivity: Date;
}
interface Change {
id: string;
type: 'add_node' | 'remove_node' | 'update_node' | 'add_connection' | 'remove_connection';
userId: string;
timestamp: Date;
data: any;
conflict?: boolean;
}
Conflict Resolution
class ConflictResolver {
resolveConflicts(changes: Change[]): ResolvedChange[] {
const resolved: ResolvedChange[] = [];
const conflictGroups = this.groupConflictingChanges(changes);
for (const group of conflictGroups) {
if (group.length === 1) {
// No conflict
resolved.push({ change: group[0], resolution: 'accept' });
} else {
// Apply conflict resolution strategy
const resolution = this.applyResolutionStrategy(group);
resolved.push(...resolution);
}
}
return resolved;
}
private applyResolutionStrategy(conflicts: Change[]): ResolvedChange[] {
// Last-write-wins for simple properties
// Manual resolution for complex conflicts
// Automatic merging for compatible changes
return conflicts.map(change => ({
change,
resolution: 'manual_review_required'
}));
}
}
2. Comments and Annotations
Node Comments
interface NodeComment {
id: string;
nodeId: string;
author: string;
content: string;
timestamp: Date;
resolved: boolean;
replies: CommentReply[];
position: 'top' | 'bottom' | 'left' | 'right';
}
interface CommentReply {
id: string;
author: string;
content: string;
timestamp: Date;
}
Testing and Debugging
1. Interactive Testing
Test Data Management
interface TestCase {
id: string;
name: string;
description: string;
inputs: TestInput[];
expectedOutputs: TestOutput[];
assertions: TestAssertion[];
}
interface TestInput {
nodeId: string;
portId: string;
data: any;
}
interface TestAssertion {
type: 'equals' | 'contains' | 'greater_than' | 'matches_schema';
field: string;
expected: any;
description: string;
}
// Example: API workflow test case
const apiTestCase: TestCase = {
id: 'api-success-case',
name: 'API Success Response',
description: 'Test successful API call and data processing',
inputs: [
{
nodeId: 'trigger',
portId: 'input',
data: { userId: 123, action: 'get_profile' }
}
],
expectedOutputs: [
{
nodeId: 'output',
portId: 'result',
data: { status: 'success', user: { id: 123, name: 'John Doe' } }
}
],
assertions: [
{
type: 'equals',
field: 'status',
expected: 'success',
description: 'Response should indicate success'
},
{
type: 'contains',
field: 'user',
expected: { id: 123 },
description: 'User object should contain correct ID'
}
]
};
Debug Mode
interface DebugSession {
workflowId: string;
executionId: string;
breakpoints: Breakpoint[];
watchedVariables: WatchedVariable[];
stepMode: 'step_over' | 'step_into' | 'step_out' | 'continue';
}
interface Breakpoint {
nodeId: string;
condition?: string;
enabled: boolean;
}
interface WatchedVariable {
expression: string;
value: any;
type: string;
}
class DebugManager {
async startDebugSession(workflowId: string, testInput: any): Promise<DebugSession> {
const session: DebugSession = {
workflowId,
executionId: this.generateExecutionId(),
breakpoints: [],
watchedVariables: [],
stepMode: 'step_over'
};
// Start workflow execution in debug mode
await this.executeWithDebugging(session, testInput);
return session;
}
async stepExecution(sessionId: string): Promise<ExecutionStep> {
// Execute next step
// Evaluate watched variables
// Check breakpoint conditions
// Return current state
}
}
2. Performance Analysis
Execution Profiling
interface ExecutionProfile {
workflowId: string;
executionId: string;
totalDuration: number;
nodePerformance: NodePerformance[];
resourceUsage: ResourceUsage;
bottlenecks: PerformanceBottleneck[];
}
interface NodePerformance {
nodeId: string;
nodeName: string;
duration: number;
memoryUsage: number;
cpuUsage: number;
ioOperations: number;
retries: number;
}
interface PerformanceBottleneck {
type: 'slow_node' | 'memory_leak' | 'excessive_retries' | 'blocking_operation';
description: string;
nodeId: string;
impact: 'low' | 'medium' | 'high';
suggestions: string[];
}
Design Best Practices
1. Workflow Organization
Naming Conventions
const namingStandards = {
workflows: {
format: "PascalCase",
examples: ["UserRegistrationFlow", "DataProcessingPipeline"],
maxLength: 50
},
nodes: {
format: "camelCase with descriptive action",
examples: ["validateUserInput", "sendWelcomeEmail", "updateDatabase"],
maxLength: 30
},
variables: {
format: "camelCase",
examples: ["userData", "apiResponse", "errorMessage"],
maxLength: 25
}
};
Visual Layout Guidelines
interface LayoutGuidelines {
nodeSpacing: {
horizontal: number; // 200px
vertical: number; // 150px
};
flowDirection: 'left-to-right' | 'top-to-bottom';
connectionRouting: {
style: 'orthogonal' | 'curved' | 'straight';
avoidOverlaps: boolean;
minimizeCrossings: boolean;
};
grouping: {
useColors: boolean;
useBackgrounds: boolean;
labelGroups: boolean;
};
}
2. Error Prevention
Design-time Validation
class WorkflowValidator {
validate(workflow: WorkflowDefinition): ValidationResult {
const errors: ValidationError[] = [];
const warnings: ValidationWarning[] = [];
// Check for disconnected nodes
const disconnectedNodes = this.findDisconnectedNodes(workflow);
if (disconnectedNodes.length > 0) {
warnings.push({
type: 'disconnected_nodes',
message: `Found ${disconnectedNodes.length} disconnected nodes`,
nodeIds: disconnectedNodes
});
}
// Check for infinite loops
const cycles = this.detectCycles(workflow);
if (cycles.length > 0) {
errors.push({
type: 'infinite_loop',
message: 'Workflow contains cycles that could cause infinite loops',
nodeIds: cycles.flat()
});
}
// Check for missing required configurations
const missingConfigs = this.findMissingConfigurations(workflow);
errors.push(...missingConfigs);
// Check data type compatibility
const typeErrors = this.validateDataTypes(workflow);
errors.push(...typeErrors);
return {
valid: errors.length === 0,
errors,
warnings
};
}
}
Keyboard Shortcuts and Accessibility
Keyboard Shortcuts
const keyboardShortcuts = {
// File operations
'Ctrl+N': 'New workflow',
'Ctrl+O': 'Open workflow',
'Ctrl+S': 'Save workflow',
'Ctrl+Shift+S': 'Save as',
// Edit operations
'Ctrl+Z': 'Undo',
'Ctrl+Y': 'Redo',
'Ctrl+C': 'Copy selection',
'Ctrl+V': 'Paste',
'Ctrl+X': 'Cut selection',
'Delete': 'Delete selection',
// Navigation
'Space': 'Pan mode toggle',
'Ctrl+0': 'Fit to view',
'Ctrl++': 'Zoom in',
'Ctrl+-': 'Zoom out',
// Node operations
'Ctrl+A': 'Select all',
'Ctrl+D': 'Duplicate selection',
'Ctrl+G': 'Group selection',
'Ctrl+Shift+G': 'Ungroup selection',
// Workflow operations
'F5': 'Test run',
'F7': 'Validate workflow',
'Ctrl+R': 'Run workflow',
'Esc': 'Cancel current operation'
};
Accessibility Features
interface AccessibilityConfig {
screenReader: {
enabled: boolean;
announceActions: boolean;
announceSelection: boolean;
};
keyboard: {
tabNavigation: boolean;
focusIndicators: boolean;
skipLinks: boolean;
};
visual: {
highContrast: boolean;
largeText: boolean;
colorBlindFriendly: boolean;
};
motor: {
clickTimeout: number;
dragThreshold: number;
alternativeInputs: boolean;
};
}
Performance Optimization
Canvas Rendering
class CanvasRenderer {
private viewport: Viewport;
private culling: boolean = true;
private levelOfDetail: boolean = true;
render(workflow: WorkflowDefinition): void {
// Frustum culling - only render visible elements
const visibleNodes = this.culling ?
this.getVisibleNodes(workflow.nodes) :
workflow.nodes;
// Level of detail - simplify distant objects
const renderLevel = this.calculateRenderLevel();
// Batch render operations
this.batchRender(visibleNodes, renderLevel);
}
private getVisibleNodes(nodes: Node[]): Node[] {
return nodes.filter(node =>
this.viewport.intersects(node.bounds)
);
}
private calculateRenderLevel(): RenderLevel {
const zoom = this.viewport.zoom;
if (zoom < 0.25) return RenderLevel.MINIMAL;
if (zoom < 0.5) return RenderLevel.SIMPLIFIED;
if (zoom < 1.5) return RenderLevel.NORMAL;
return RenderLevel.DETAILED;
}
}
Integration and Export
Export Formats
interface ExportOptions {
format: 'json' | 'yaml' | 'xml' | 'image' | 'pdf';
includeMetadata: boolean;
minifyOutput: boolean;
imageOptions?: {
resolution: number;
format: 'png' | 'svg' | 'jpeg';
backgroundColor: string;
};
}
class WorkflowExporter {
async export(workflow: WorkflowDefinition, options: ExportOptions): Promise<Blob> {
switch (options.format) {
case 'json':
return this.exportJSON(workflow, options);
case 'yaml':
return this.exportYAML(workflow, options);
case 'image':
return this.exportImage(workflow, options);
case 'pdf':
return this.exportPDF(workflow, options);
default:
throw new Error(`Unsupported export format: ${options.format}`);
}
}
}