# Vue Flow Process Builder System Guide
## ๐ System Overview
This is a **Vue 3 + Nuxt 3** application that provides a visual process builder using **Vue Flow** library. The system allows users to create, edit, and manage business processes through a drag-and-drop interface with custom nodes and edges.
### ๐๏ธ **Core Architecture**
- **Frontend**: Vue 3 with Composition API + Nuxt 3
- **Flow Engine**: Vue Flow (Vue 3 compatible fork of React Flow)
- **Styling**: Tailwind CSS + Custom SCSS
- **State Management**: Pinia stores
- **Database**: Prisma ORM
- **UI Components**: Custom component library + FormKit
---
## ๐ File Structure & Key Locations
### **๐ฏ Main Process Builder**
```
pages/process-builder/
โโโ index.vue # Main process builder page
โโโ manage.vue # Process management page
```
### **๐ง Core Vue Flow Components**
```
components/process-flow/
โโโ ProcessFlowCanvas.vue # Main Vue Flow canvas component
โโโ ArrowEdge.vue # Custom edge component
โโโ custom/ # Custom node components
โ โโโ StartNode.vue # Process start node
โ โโโ EndNode.vue # Process end node
โ โโโ FormNode.vue # Form task node
โ โโโ ApiNode.vue # API call node
โ โโโ ScriptNode.vue # Script execution node
โ โโโ BusinessRuleNode.vue # Business rule node
โ โโโ NotificationNode.vue # Notification node
โ โโโ HtmlNode.vue # HTML content node
โ โโโ SubprocessNode.vue # Subprocess node
โ โโโ GatewayNode.vue # Decision/gateway node
โ โโโ TextAnnotation.vue # Text annotation node
โ โโโ ProcessGroup.vue # Process grouping node
โโโ notification/ # Notification system components
โ โโโ NotificationManager.vue
โ โโโ NotificationQueue.vue
โ โโโ NotificationLogs.vue
โโโ GatewayConditionManager.vue # Gateway condition configuration UI
โโโ GatewayConditionManagerModal.vue # Modal for gateway setup
โโโ [25+ other process flow files]
```
### **๐ Configuration & Data**
```
composables/
โโโ processFlowNodes.js # Node type definitions & configurations
โโโ nodeStyles.js # Global CSS styles for all nodes
โโโ codemirrorThemes.js # Code editor themes
โโโ themeList.js # UI theme configurations
docs/json/process-builder/
โโโ processDefinition.json # Process structure schema
โโโ processVariables.json # Process variable definitions
stores/
โโโ processBuilder.js # Pinia store for process builder state
โโโ formBuilder.js # Form builder state
โโโ layout.js # Layout configurations
```
### **๐จ Styling System**
```
assets/style/
โโโ css/ # Compiled CSS
โ โโโ tailwind.css
โ โโโ component/ # Component-specific styles
โโโ scss/
โ โโโ main.scss # Main SCSS entry point
โ โโโ custom/ # Custom styling
โ โโโ library/ # Third-party library styles
โ โ โโโ _dropdown.scss
โ โ โโโ _formkit.scss
โ โ โโโ _floatingvue.scss
โ โโโ transition/ # Animation styles
โ โโโ fade.scss
โ โโโ page.scss
plugins/
โโโ process-flow-styles.client.js # Vue Flow styling injection
โโโ vue-codemirror.js # Code editor plugin
```
### **โ๏ธ Server API**
```
server/api/
โโโ process/
โ โโโ [id].get.js # Get process definition
โ โโโ [id].put.js # Update process
โ โโโ [id].delete.js # Delete process
โ โโโ [id]/
โ โ โโโ publish.post.js # Publish process
โ โ โโโ duplicate.post.js # Duplicate process
โ โ โโโ history.get.js # Process history
โ โโโ dashboard/
โ โโโ summary.get.js # Dashboard summary
โโโ forms/ # Form-related APIs
โโโ [id].get.js
โโโ [id].put.js
โโโ [id]/history.get.js
```
---
## ๐ How the System Works
### **1. Vue Flow Integration**
#### **Main Canvas Component** (`ProcessFlowCanvas.vue`)
```vue
```
#### **Custom Node Structure**
Every custom node follows this pattern:
```vue
```
### **2. Node Shape System**
#### **Shape Configuration**
Nodes support multiple shapes through CSS `clip-path`:
```javascript
// Available shapes
const shapes = [
'rectangle', // Default rectangle
'rounded-rectangle', // Rounded corners
'circle', // Circle shape
'diamond', // Diamond (gateway nodes)
'hexagon', // Hexagon shape
'trapezoid', // Trapezoid shape
'parallelogram' // Parallelogram shape
]
```
#### **Shape CSS Implementation** (`nodeStyles.js`)
```css
/* Rectangle (default) */
.custom-node.shape-rectangle {
border-radius: 4px;
}
/* Hexagon shape */
.custom-node.shape-hexagon {
background: none !important;
border: none !important;
border-radius: 0 !important;
}
.custom-node.shape-hexagon::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: var(--node-bg-color, white);
border: 2px solid var(--node-border-color, #ddd);
clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
z-index: 0;
}
/* Node-type specific colors for shapes */
.node-form.shape-hexagon::before {
border-left: 4px solid #9333ea; /* Purple */
}
```
### **3. Node Types & Configurations**
#### **Node Type Registry** (`processFlowNodes.js`)
```javascript
export const nodeTypes = {
'start': {
label: 'Start',
icon: 'play_circle',
color: '#10b981',
allowedShapes: ['circle', 'rectangle'],
defaultData: {
label: 'Start',
backgroundColor: '#dcfce7',
borderColor: '#10b981'
}
},
'form': {
label: 'Form',
icon: 'description',
color: '#9333ea',
allowedShapes: ['rectangle', 'rounded-rectangle', 'hexagon', 'trapezoid'],
defaultData: {
label: 'Form Task',
formId: null,
required: true
}
},
// ... other node types
}
```
### **4. Process Builder State Management**
#### **Pinia Store** (`stores/processBuilder.js`)
```javascript
export const useProcessBuilderStore = defineStore('processBuilder', () => {
// State
const nodes = ref([])
const edges = ref([])
const selectedNode = ref(null)
const processDefinition = ref({})
// Actions
const addNode = (nodeData) => {
const newNode = {
id: generateId(),
type: nodeData.type,
position: nodeData.position,
data: { ...nodeTypes[nodeData.type].defaultData, ...nodeData.data }
}
nodes.value.push(newNode)
}
const updateNode = (nodeId, updates) => {
const nodeIndex = nodes.value.findIndex(n => n.id === nodeId)
if (nodeIndex > -1) {
nodes.value[nodeIndex] = { ...nodes.value[nodeIndex], ...updates }
}
}
return {
nodes, edges, selectedNode, processDefinition,
addNode, updateNode
}
})
```
---
## ๐ ๏ธ Implementation Patterns
### **1. Adding a New Node Type**
#### **Step 1: Create Node Component**
```bash
# Create new component file
touch components/process-flow/custom/YourNewNode.vue
```
```vue
your_icon
{{ data?.label || 'Your Node' }}
```
#### **Step 2: Register in ProcessFlowCanvas.vue**
```javascript
// Import the component
import YourNewNode from './custom/YourNewNode.vue'
// Add to customNodeTypes
const customNodeTypes = {
// ... existing types
'yournew': markRaw(YourNewNode),
}
```
#### **Step 3: Add Node Configuration**
```javascript
// In processFlowNodes.js
export const nodeTypes = {
// ... existing types
'yournew': {
label: 'Your New Node',
icon: 'your_icon',
color: '#your-color',
allowedShapes: ['rectangle', 'circle'],
defaultData: {
label: 'Your New Node',
yourProperty: 'default-value'
}
}
}
```
#### **Step 4: Add Styling**
```css
/* In nodeStyles.js */
.node-yournew {
background: white;
border: 1px solid #ddd;
border-left: 4px solid #your-color;
}
```
### **2. Adding Shape Support**
#### **Step 1: Define Shape CSS**
```css
/* In nodeStyles.js */
.custom-node.shape-yourshape {
background: none !important;
border: none !important;
border-radius: 0 !important;
}
.custom-node.shape-yourshape::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: var(--node-bg-color, white);
border: 2px solid var(--node-border-color, #ddd);
clip-path: polygon(/* your shape points */);
z-index: 0;
}
```
#### **Step 2: Add to Shape Lists**
```javascript
// In processFlowNodes.js or relevant config
const availableShapes = [
// ... existing shapes
'yourshape'
]
// Add to node type configurations
'form': {
allowedShapes: ['rectangle', 'hexagon', 'yourshape']
}
```
### **3. Customizing Node Properties**
#### **Properties Panel Integration**
```vue
*
{{ data?.label || 'Default Label' }}
```
### **4. Gateway Decision Logic**
#### **Gateway Configuration UI Components**
```vue
No Decision Paths Defined
Your process will always follow the default path
```
#### **Condition Evaluation Process**
```javascript
// Workflow execution evaluates gateway conditions
function evaluateConditionGroup(conditionGroup, variables) {
if (!conditionGroup.conditions || conditionGroup.conditions.length === 0) {
return false;
}
// If only one condition, evaluate it directly
if (conditionGroup.conditions.length === 1) {
return evaluateCondition(conditionGroup.conditions[0], variables);
}
// For multiple conditions, evaluate based on logical operators
let result = evaluateCondition(conditionGroup.conditions[0], variables);
for (let i = 1; i < conditionGroup.conditions.length; i++) {
const condition = conditionGroup.conditions[i];
const conditionResult = evaluateCondition(condition, variables);
const operator = condition.logicalOperator || 'and';
if (operator === 'and') {
result = result && conditionResult;
} else if (operator === 'or') {
result = result || conditionResult;
}
}
return result;
}
// Single condition evaluation
function evaluateCondition(condition, variables) {
const { variable, operator, value, valueType } = condition;
const variableValue = variables[variable];
// Handle boolean type conversions
let compareValue = value;
if (valueType === 'boolean') {
if (typeof value === 'string') {
compareValue = value.toLowerCase() === 'true';
} else {
compareValue = Boolean(value);
}
}
// Evaluate based on operator
switch (operator) {
case 'eq':
return variableValue == compareValue;
case 'gt':
return Number(variableValue) > Number(compareValue);
case 'is_true':
return Boolean(variableValue) === true;
// ... other operators
}
}
```
#### **Decision Path Selection**
```javascript
// Determine which path to follow based on conditions
function getNextNodeIdForDecision(currentNodeId) {
const currentNodeObj = workflowData.nodes.find(n => n.id === currentNodeId);
const outgoingEdges = getOutgoingEdges(currentNodeId);
if (!currentNodeObj || !outgoingEdges.length) return null;
const { conditions = [] } = currentNodeObj.data || {};
// Evaluate condition groups (each group represents a path)
for (const conditionGroup of conditions) {
if (evaluateConditionGroup(conditionGroup, variables)) {
// Find the edge that matches this condition group's output label
const edge = outgoingEdges.find(e => e.label === conditionGroup.output);
if (edge) return edge.target;
}
}
// If no conditions match, use default path
const defaultEdge = outgoingEdges.find(e => e.data?.isDefault);
if (defaultEdge) return defaultEdge.target;
// Fallback to first edge
return outgoingEdges[0]?.target || null;
}
```
---
## ๐จ Styling System
### **CSS Architecture**
1. **Global Styles** (`nodeStyles.js`) - Injected globally via plugin
2. **Component Styles** - Scoped styles in individual `.vue` files
3. **Tailwind Classes** - Utility classes for layout and spacing
4. **CSS Variables** - Dynamic theming support
### **CSS Variable System**
```css
/* Node supports dynamic styling via CSS variables */
.custom-node {
background: var(--node-bg-color, white);
border-color: var(--node-border-color, #ddd);
color: var(--node-text-color, black);
}
```
```javascript
// Set in component
const nodeStyle = computed(() => ({
'--node-bg-color': props.data?.backgroundColor || 'white',
'--node-border-color': props.data?.borderColor || '#ddd',
'--node-text-color': props.data?.textColor || 'black'
}))
```
### **Shape Override Pattern**
```css
/* Base node styles */
.node-form {
background: white;
border: 1px solid #ddd;
border-radius: 4px;
}
/* Shape overrides */
.node-form.shape-hexagon {
background: none !important;
border: none !important;
border-radius: 0 !important;
}
```
---
## ๐ Plugin System
### **Vue Flow Styles Plugin** (`process-flow-styles.client.js`)
```javascript
export default defineNuxtPlugin(() => {
// Inject global styles for Vue Flow nodes
if (process.client) {
const { injectGlobalStyles } = useNodeStyles()
injectGlobalStyles()
}
})
```
### **CodeMirror Integration** (`vue-codemirror.js`)
```javascript
export default defineNuxtPlugin(() => {
// Register CodeMirror for script editing
// Used in ScriptNode and ApiNode components
})
```
---
## ๐ Data Flow
### **Process Definition Structure**
```json
{
"id": "process-uuid",
"name": "Process Name",
"version": "1.0.0",
"nodes": [
{
"id": "node-1",
"type": "start",
"position": { "x": 100, "y": 100 },
"data": {
"label": "Start Process",
"backgroundColor": "#dcfce7",
"shape": "circle"
}
}
],
"edges": [
{
"id": "edge-1",
"source": "node-1",
"target": "node-2",
"type": "default"
}
],
"variables": {},
"settings": {}
}
```
### **Node Data Structure**
```javascript
// Standard node data properties
{
id: 'unique-node-id',
type: 'form|api|script|gateway|etc',
position: { x: number, y: number },
data: {
// Universal properties
label: 'Node Label',
shape: 'rectangle|circle|hexagon|etc',
backgroundColor: '#ffffff',
borderColor: '#dddddd',
textColor: '#000000',
// Type-specific properties
formId: 'form-uuid', // For form nodes
apiEndpoint: 'url', // For API nodes
scriptCode: 'javascript', // For script nodes
conditions: [], // For gateway nodes (see Gateway Conditions section)
// Custom properties
customProperties: {}
}
}
```
### **Gateway Conditions Structure**
```javascript
// Gateway node conditions format (group-based)
{
id: 'gateway-node-id',
type: 'gateway',
data: {
label: 'Decision Point',
conditions: [
{
id: 'condition-group-1',
output: 'Path Label', // Label for this decision path
conditions: [ // Array of conditions for this path
{
id: 'condition-1',
variable: 'processVariable', // Process variable to evaluate
operator: 'eq', // Comparison operator
value: 'expectedValue', // Value to compare against
valueType: 'string', // Data type (string, boolean, number, etc.)
logicalOperator: 'and' // How to combine with next condition (and/or)
}
// ... more conditions for this path
]
}
// ... more condition groups (paths)
],
defaultPath: 'Default' // Default path if no conditions match
}
}
```
### **Supported Condition Operators**
```javascript
// String operators
'eq', 'equals', '==' // Equal to
'neq', 'not_equals', '!=' // Not equal to
'contains' // Contains substring
'not_contains' // Does not contain
'starts_with' // Starts with
'ends_with' // Ends with
'empty' // Is empty/null
'not_empty' // Is not empty/null
'regex' // Matches regex pattern
// Numeric operators
'gt', 'greater_than', '>' // Greater than
'lt', 'less_than', '<' // Less than
'gte', 'greater_than_or_equal', '>=' // Greater than or equal
'lte', 'less_than_or_equal', '<=' // Less than or equal
'between' // Between two values
'not_between' // Not between two values
// Boolean operators
'is_true' // Is true
'is_false' // Is false
// Date operators
'today' // Is today
'this_week' // Is this week
'this_month' // Is this month
'this_year' // Is this year
// Object operators
'has_property' // Has specific property
'property_equals' // Property equals value
```
---
## ๐ Common Issues & Solutions
### **1. Nodes Not Displaying in Production**
**Problem**: Custom nodes show as empty boxes in production build.
**Solution**: Use file-based components with `markRaw()` wrapper.
```javascript
// โ Wrong - breaks in production
const nodeTypes = {
'form': {
template: '
Form Node
'
}
}
// โ
Correct - works in production
import FormNode from './custom/FormNode.vue'
const nodeTypes = {
'form': markRaw(FormNode)
}
```
### **2. Shapes Not Displaying**
**Problem**: Node shapes remain rectangular despite shape selection.
**Solution**: Ensure shape CSS overrides base styles with `!important`.
```css
/* โ Wrong - base styles override */
.custom-node.shape-hexagon {
border-radius: 0;
background: none;
}
/* โ
Correct - overrides base styles */
.custom-node.shape-hexagon {
border-radius: 0 !important;
background: none !important;
border: none !important;
}
```
### **3. Handle Connection Issues**
**Problem**: Nodes can't connect to each other.
**Solution**: Ensure handles have proper `type` and `position`.
```vue
```
---
## ๐ Deployment Checklist
### **Production Readiness**
- [ ] All custom nodes are file-based components (not inline)
- [ ] Node types registered with `markRaw()` wrapper
- [ ] Global styles injected via plugin system
- [ ] CSS variables used for dynamic theming
- [ ] Shape overrides use `!important` declarations
- [ ] Handle connections properly configured
- [ ] Error boundaries implemented for Vue Flow errors
- [ ] Process validation before save/publish
### **Performance Optimization**
- [ ] Large node sets use virtual scrolling
- [ ] Debounced auto-save functionality
- [ ] Optimized re-renders with `computed` properties
- [ ] Proper Vue 3 reactivity patterns
- [ ] Memory leak prevention with proper cleanup
---
## ๐ Key Dependencies
```json
{
"@vue-flow/core": "^1.x.x",
"@vue-flow/background": "^1.x.x",
"@vue-flow/controls": "^1.x.x",
"@vue-flow/minimap": "^1.x.x",
"vue": "^3.x.x",
"nuxt": "^3.x.x",
"@pinia/nuxt": "^0.x.x",
"@formkit/nuxt": "^1.x.x",
"tailwindcss": "^3.x.x"
}
```
---
## ๐ Reference Links
- **Vue Flow Documentation**: https://vueflow.dev/
- **Vue 3 Composition API**: https://vuejs.org/guide/
- **Nuxt 3**: https://nuxt.com/docs
- **Pinia Store**: https://pinia.vuejs.org/
- **Tailwind CSS**: https://tailwindcss.com/docs
---
**๐ Last Updated**: Current as of the Vue Flow custom nodes migration and shape system implementation.
**๐ Version**: 1.0 - Comprehensive system documentation
**๐ฅ For New Team Members**: This document contains everything needed to understand and work with the Vue Flow Process Builder system. Start with the "File Structure" section and follow the implementation patterns for any new features.
---
## ๐งช Testing Gateway Decisions
### **Form Field Integration**
To test gateway decisions, add form fields that map to process variables:
```json
// Form component with gateway test field
{
"type": "checkbox",
"props": {
"name": "todoStatus",
"label": "Test Gateway Decision",
"help": "Toggle to test gateway paths"
}
}
```
### **Process Variable Mapping**
Map form fields to process variables for gateway testing:
```json
// Form node output mappings
{
"outputMappings": [
{ "formField": "todoStatus", "processVariable": "todoStatus" }
]
}
```
### **Gateway Testing Scenarios**
1. **"Completed" Path**: Check the test field โ `todoStatus = true` โ Follow "Completed" path
2. **"Not Completed" Path**: Leave test field unchecked โ `todoStatus = false` โ Follow "Not Completed" path
### **Workflow Execution**
The workflow execution engine (`pages/workflow/[id].vue`) automatically:
- Evaluates gateway conditions based on current process variables
- Shows visual feedback of which conditions are true/false
- Follows the first matching path
- Falls back to default path if no conditions match