Skip to main content

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}`);
}
}
}

Need Help?