fix(layout): layout canvas onInit after node sizes are calculated, remove legacy history code and legacy marking of dirty workflows (#1645)

This commit is contained in:
Waleed
2025-10-15 15:35:32 -07:00
committed by GitHub
parent b2009fe467
commit 2ca9044bc6
10 changed files with 23 additions and 602 deletions
@@ -38,7 +38,7 @@ export default function StructuredData() {
url: 'https://sim.ai',
name: 'Sim - AI Agent Workflow Builder',
description:
'Open-source AI agent workflow builder. 30,000+ developers build and deploy agentic workflows. SOC2 and HIPAA compliant.',
'Open-source AI agent workflow builder. 50,000+ developers build and deploy agentic workflows. SOC2 and HIPAA compliant.',
publisher: {
'@id': 'https://sim.ai/#organization',
},
@@ -98,7 +98,7 @@ export default function StructuredData() {
'@id': 'https://sim.ai/#software',
name: 'Sim - AI Agent Workflow Builder',
description:
'Open-source AI agent workflow builder used by 30,000+ developers. Build agentic workflows with visual drag-and-drop interface. SOC2 and HIPAA compliant. Integrate with 100+ apps.',
'Open-source AI agent workflow builder used by 50,000+ developers. Build agentic workflows with visual drag-and-drop interface. SOC2 and HIPAA compliant. Integrate with 100+ apps.',
applicationCategory: 'DeveloperApplication',
applicationSubCategory: 'AI Development Tools',
operatingSystem: 'Web, Windows, macOS, Linux',
@@ -173,7 +173,7 @@ export default function StructuredData() {
'Visual workflow builder',
'Drag-and-drop interface',
'100+ integrations',
'AI model support (OpenAI, Anthropic, Google)',
'AI model support (OpenAI, Anthropic, Google, xAI, Mistral, Perplexity)',
'Real-time collaboration',
'Version control',
'API access',
@@ -198,7 +198,7 @@ export default function StructuredData() {
name: 'What is Sim?',
acceptedAnswer: {
'@type': 'Answer',
text: 'Sim is an open-source AI agent workflow builder used by 30,000+ developers at trail-blazing startups to Fortune 500 companies. It provides a visual drag-and-drop interface for building and deploying agentic workflows. Sim is SOC2 and HIPAA compliant.',
text: 'Sim is an open-source AI agent workflow builder used by 50,000+ developers at trail-blazing startups to Fortune 500 companies. It provides a visual drag-and-drop interface for building and deploying agentic workflows. Sim is SOC2 and HIPAA compliant.',
},
},
{
@@ -206,7 +206,7 @@ export default function StructuredData() {
name: 'Which AI models does Sim support?',
acceptedAnswer: {
'@type': 'Answer',
text: 'Sim supports all major AI models including OpenAI (GPT-4, GPT-3.5), Anthropic (Claude), Google (Gemini), Mistral, Perplexity, and many more. You can also connect to open-source models via Ollama.',
text: 'Sim supports all major AI models including OpenAI (GPT-5, GPT-4o), Anthropic (Claude), Google (Gemini), xAI (Grok), Mistral, Perplexity, and many more. You can also connect to open-source models via Ollama.',
},
},
{
-1
View File
@@ -38,7 +38,6 @@ export default function manifest(): MetadataRoute.Manifest {
short_name: 'New',
description: 'Create a new AI workflow',
url: '/workspace',
icons: [{ src: '/icons/new-workflow.png', sizes: '192x192' }],
},
],
lang: 'en-US',
+3 -3
View File
@@ -4,7 +4,7 @@ import Landing from '@/app/(landing)/landing'
export const metadata: Metadata = {
title: 'Sim - AI Agent Workflow Builder | Open Source Platform',
description:
'Open-source AI agent workflow builder used by 30,000+ developers. Build and deploy agentic workflows with visual drag-and-drop interface. Connect 100+ apps. SOC2 and HIPAA compliant. Used by startups to Fortune 500 companies.',
'Open-source AI agent workflow builder used by 50,000+ developers. Build and deploy agentic workflows with visual drag-and-drop interface. Connect 100+ apps. SOC2 and HIPAA compliant. Used by startups to Fortune 500 companies.',
keywords:
'AI agent workflow builder, agentic workflows, open source AI, visual workflow builder, AI automation, LLM workflows, AI agents, workflow automation, no-code AI, SOC2 compliant, HIPAA compliant, enterprise AI',
authors: [{ name: 'Sim Studio' }],
@@ -18,7 +18,7 @@ export const metadata: Metadata = {
openGraph: {
title: 'Sim - AI Agent Workflow Builder | Open Source',
description:
'Open-source platform used by 30,000+ developers. Build and deploy agentic workflows with drag-and-drop interface. SOC2 & HIPAA compliant. Connect 100+ apps.',
'Open-source platform used by 50,000+ developers. Build and deploy agentic workflows with drag-and-drop interface. SOC2 & HIPAA compliant. Connect 100+ apps.',
type: 'website',
url: 'https://sim.ai',
siteName: 'Sim',
@@ -45,7 +45,7 @@ export const metadata: Metadata = {
creator: '@simdotai',
title: 'Sim - AI Agent Workflow Builder | Open Source',
description:
'Open-source platform for agentic workflows. 30,000+ developers. Visual builder. 100+ integrations. SOC2 & HIPAA compliant.',
'Open-source platform for agentic workflows. 50,000+ developers. Visual builder. 100+ integrations. SOC2 & HIPAA compliant.',
images: {
url: '/social/twitter-image.png',
alt: 'Sim - Visual AI Workflow Builder',
@@ -86,8 +86,7 @@ export function ControlBar({ hasValidationErrors = false }: ControlBarProps) {
const workspaceId = params.workspaceId as string
// Store hooks
const { history, revertToHistoryState, lastSaved, setNeedsRedeploymentFlag, blocks } =
useWorkflowStore()
const { lastSaved, setNeedsRedeploymentFlag, blocks } = useWorkflowStore()
const {
workflows,
updateWorkflow,
@@ -92,7 +92,8 @@ const WorkflowContent = React.memo(() => {
const [draggedNodeId, setDraggedNodeId] = useState<string | null>(null)
const [potentialParentId, setPotentialParentId] = useState<string | null>(null)
// State for tracking validation errors
const [nestedSubflowErrors, setNestedSubflowErrors] = useState<Set<string>>(new Set())
// Use a function initializer to ensure the Set is only created once
const [nestedSubflowErrors, setNestedSubflowErrors] = useState<Set<string>>(() => new Set())
// Enhanced edge selection with parent context and unique identifier
const [selectedEdgeInfo, setSelectedEdgeInfo] = useState<SelectedEdgeInfo | null>(null)
@@ -1292,8 +1293,6 @@ const WorkflowContent = React.memo(() => {
// Include dynamic dimensions for container resizing calculations
width: block.isWide ? 450 : 350, // Standard width based on isWide state
height: Math.max(block.height || 100, 100), // Use actual height with minimum
// Explicitly set measured to prevent ReactFlow from recalculating
measured: { width: block.isWide ? 450 : 350, height: Math.max(block.height || 100, 100) },
})
})
@@ -1967,7 +1966,13 @@ const WorkflowContent = React.memo(() => {
edgeTypes={edgeTypes}
onDrop={effectivePermissions.canEdit ? onDrop : undefined}
onDragOver={effectivePermissions.canEdit ? onDragOver : undefined}
fitView
onInit={(instance) => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
instance.fitView({ padding: 0.3 })
})
})
}}
minZoom={0.1}
maxZoom={1.3}
panOnScroll
-265
View File
@@ -1,265 +0,0 @@
import type { StateCreator } from 'zustand'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
import type { WorkflowState, WorkflowStore } from '@/stores/workflows/workflow/types'
interface HistoryEntry {
state: WorkflowState
timestamp: number
action: string
subblockValues: Record<string, Record<string, any>>
}
interface WorkflowHistory {
past: HistoryEntry[]
present: HistoryEntry
future: HistoryEntry[]
}
interface HistoryActions {
undo: () => void
redo: () => void
canUndo: () => boolean
canRedo: () => boolean
revertToHistoryState: (index: number) => void
}
// MAX for each individual workflow
const MAX_HISTORY_LENGTH = 20
// Default empty state for consistent initialization
const EMPTY_WORKFLOW_STATE = {
blocks: {},
edges: [] as any[],
loops: {},
parallels: {},
}
// Types for workflow store with history management capabilities
export interface WorkflowStoreWithHistory extends WorkflowStore, HistoryActions {
history: WorkflowHistory
revertToDeployedState: (deployedState: WorkflowState) => void
}
// Higher-order store middleware that adds undo/redo functionality
export const withHistory = (
config: StateCreator<WorkflowStoreWithHistory>
): StateCreator<WorkflowStoreWithHistory> => {
return (set, get, api) => {
// Initialize store with history tracking
const initialState = config(set, get, api)
const initialHistoryEntry: HistoryEntry = {
state: {
blocks: initialState.blocks,
edges: initialState.edges,
loops: initialState.loops,
parallels: initialState.parallels,
},
timestamp: Date.now(),
action: 'Initial state',
subblockValues: {}, // Add storage for subblock values
}
return {
...initialState,
history: {
past: [],
present: initialHistoryEntry,
future: [],
},
// Check if undo operation is available
canUndo: () => get().history.past.length > 0,
// Check if redo operation is available
canRedo: () => get().history.future.length > 0,
// Restore previous state from history
undo: () => {
const { history, ...state } = get()
if (history.past.length === 0) return
const previous = history.past[history.past.length - 1]
const newPast = history.past.slice(0, history.past.length - 1)
// Get active workflow ID for subblock handling
const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId
if (!activeWorkflowId) return
// Apply the state change
set({
...state,
...previous.state,
history: {
past: newPast,
present: previous,
future: [history.present, ...history.future],
},
lastSaved: Date.now(),
})
// Restore subblock values from the previous state's snapshot
if (previous.subblockValues && activeWorkflowId) {
// Update the subblock store with the saved values
useSubBlockStore.setState({
workflowValues: {
...useSubBlockStore.getState().workflowValues,
[activeWorkflowId]: previous.subblockValues,
},
})
}
},
// Restore next state from history
redo: () => {
const { history, ...state } = get()
if (history.future.length === 0) return
const next = history.future[0]
const newFuture = history.future.slice(1)
// Get active workflow ID for subblock handling
const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId
if (!activeWorkflowId) return
// Apply the state change
set({
...state,
...next.state,
history: {
past: [...history.past, history.present],
present: next,
future: newFuture,
},
lastSaved: Date.now(),
})
// Restore subblock values from the next state's snapshot
if (next.subblockValues && activeWorkflowId) {
// Update the subblock store with the saved values
useSubBlockStore.setState({
workflowValues: {
...useSubBlockStore.getState().workflowValues,
[activeWorkflowId]: next.subblockValues,
},
})
}
},
// Reset workflow to empty state
clear: () => {
const newState = {
...EMPTY_WORKFLOW_STATE,
history: {
past: [],
present: {
state: { ...EMPTY_WORKFLOW_STATE },
timestamp: Date.now(),
action: 'Clear workflow',
subblockValues: {},
},
future: [],
},
lastSaved: Date.now(),
}
set(newState)
return newState
},
// Jump to specific point in history
revertToHistoryState: (index: number) => {
const { history, ...state } = get()
const allStates = [...history.past, history.present, ...history.future]
const targetState = allStates[index]
if (!targetState) return
// Get active workflow ID for subblock handling
const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId
if (!activeWorkflowId) return
const newPast = allStates.slice(0, index)
const newFuture = allStates.slice(index + 1)
set({
...state,
...targetState.state,
history: {
past: newPast,
present: targetState,
future: newFuture,
},
lastSaved: Date.now(),
})
// Restore subblock values from the target state's snapshot
if (targetState.subblockValues && activeWorkflowId) {
// Update the subblock store with the saved values
useSubBlockStore.setState({
workflowValues: {
...useSubBlockStore.getState().workflowValues,
[activeWorkflowId]: targetState.subblockValues,
},
})
}
},
}
}
}
// Create a new history entry with current state snapshot
export const createHistoryEntry = (state: WorkflowState, action: string): HistoryEntry => {
// Get active workflow ID for subblock handling
const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId
// Create a deep copy of the state
const stateCopy = {
blocks: { ...state.blocks },
edges: [...state.edges],
loops: { ...state.loops },
parallels: { ...state.parallels },
}
// Capture the current subblock values for this workflow
let subblockValues = {}
if (activeWorkflowId) {
// Get the current subblock values from the store
const currentValues = useSubBlockStore.getState().workflowValues[activeWorkflowId] || {}
// Create a deep copy to ensure we don't have reference issues
subblockValues = JSON.parse(JSON.stringify(currentValues))
}
return {
state: stateCopy,
timestamp: Date.now(),
action,
subblockValues,
}
}
// Add new entry to history and maintain history size limit
export const pushHistory = (
set: (
partial:
| Partial<WorkflowStoreWithHistory>
| ((state: WorkflowStoreWithHistory) => Partial<WorkflowStoreWithHistory>),
replace?: boolean
) => void,
get: () => WorkflowStoreWithHistory,
newState: WorkflowState,
action: string
) => {
const { history } = get()
const newEntry = createHistoryEntry(newState, action)
set({
history: {
past: [...history.past, history.present].slice(-MAX_HISTORY_LENGTH),
present: newEntry,
future: [],
},
lastSaved: Date.now(),
})
}
+1 -177
View File
@@ -90,7 +90,6 @@ async function fetchWorkflowsFromDB(workspaceId?: string): Promise<void> {
name,
description,
color,
state,
variables,
createdAt,
marketplaceData,
@@ -186,26 +185,10 @@ function resetWorkflowStores() {
blocks: {},
edges: [],
loops: {},
parallels: {},
isDeployed: false,
deployedAt: undefined,
deploymentStatuses: {}, // Reset deployment statuses map
history: {
past: [],
present: {
state: {
blocks: {},
edges: [],
loops: {},
parallels: {},
isDeployed: false,
deployedAt: undefined,
},
timestamp: Date.now(),
action: 'Initial state',
subblockValues: {},
},
future: [],
},
lastSaved: Date.now(),
})
@@ -438,13 +421,6 @@ export const useWorkflowRegistry = create<WorkflowRegistry>()(
logger.info(`Switching to workflow ${id}`)
// First, sync the current workflow before switching (if there is one)
if (activeWorkflowId && activeWorkflowId !== id) {
// Mark current workflow as dirty and sync (fire and forget)
useWorkflowStore.getState().sync.markDirty()
useWorkflowStore.getState().sync.forceSync()
}
// Fetch workflow state from database
const response = await fetch(`/api/workflows/${id}`, { method: 'GET' })
const workflowData = response.ok ? (await response.json()).data : null
@@ -464,25 +440,6 @@ export const useWorkflowRegistry = create<WorkflowRegistry>()(
lastSaved: Date.now(),
marketplaceData: workflowData.marketplaceData || null,
deploymentStatuses: {},
history: {
past: [],
present: {
state: {
blocks: workflowData.state.blocks || {},
edges: workflowData.state.edges || [],
loops: workflowData.state.loops || {},
parallels: workflowData.state.parallels || {},
isDeployed: workflowData.isDeployed || false,
deployedAt: workflowData.deployedAt
? new Date(workflowData.deployedAt)
: undefined,
},
timestamp: Date.now(),
action: 'Loaded from database (normalized tables)',
subblockValues: {},
},
future: [],
},
}
} else {
// If no state in DB, use empty state - server should have created start block
@@ -494,23 +451,6 @@ export const useWorkflowRegistry = create<WorkflowRegistry>()(
isDeployed: false,
deployedAt: undefined,
deploymentStatuses: {},
history: {
past: [],
present: {
state: {
blocks: {},
edges: [],
loops: {},
parallels: {},
isDeployed: false,
deployedAt: undefined,
},
timestamp: Date.now(),
action: 'Empty initial state - server should provide start block',
subblockValues: {},
},
future: [],
},
lastSaved: Date.now(),
}
@@ -604,72 +544,8 @@ export const useWorkflowRegistry = create<WorkflowRegistry>()(
folderId: createdWorkflow.folderId,
}
let initialState: any
// If this is a marketplace import with existing state
if (options.marketplaceId && options.marketplaceState) {
initialState = {
blocks: options.marketplaceState.blocks || {},
edges: options.marketplaceState.edges || [],
loops: options.marketplaceState.loops || {},
parallels: options.marketplaceState.parallels || {},
isDeployed: false,
deployedAt: undefined,
deploymentStatuses: {}, // Initialize empty deployment statuses map
workspaceId, // Include workspace ID in the state object
history: {
past: [],
present: {
state: {
blocks: options.marketplaceState.blocks || {},
edges: options.marketplaceState.edges || [],
loops: options.marketplaceState.loops || {},
parallels: options.marketplaceState.parallels || {},
isDeployed: false,
deployedAt: undefined,
workspaceId, // Include workspace ID in history
},
timestamp: Date.now(),
action: 'Imported from marketplace',
subblockValues: {},
},
future: [],
},
lastSaved: Date.now(),
}
logger.info(`Created workflow from marketplace: ${options.marketplaceId}`)
} else {
// Create empty workflow (no default blocks)
initialState = {
blocks: {},
edges: [],
loops: {},
parallels: {},
isDeployed: false,
deployedAt: undefined,
deploymentStatuses: {}, // Initialize empty deployment statuses map
workspaceId, // Include workspace ID in the state object
history: {
past: [],
present: {
state: {
blocks: {},
edges: [],
loops: {},
parallels: {},
isDeployed: false,
deployedAt: undefined,
workspaceId, // Include workspace ID in history
},
timestamp: Date.now(),
action: 'Initial state',
subblockValues: {},
},
future: [],
},
lastSaved: Date.now(),
}
}
// Add workflow to registry with server-generated ID
@@ -747,23 +623,6 @@ export const useWorkflowRegistry = create<WorkflowRegistry>()(
parallels: state.parallels || {},
isDeployed: false,
deployedAt: undefined,
history: {
past: [],
present: {
state: {
blocks: state.blocks || {},
edges: state.edges || [],
loops: state.loops || {},
parallels: state.parallels || {},
isDeployed: false,
deployedAt: undefined,
},
timestamp: Date.now(),
action: 'Imported from marketplace',
subblockValues: {},
},
future: [],
},
lastSaved: Date.now(),
}
@@ -1027,24 +886,6 @@ export const useWorkflowRegistry = create<WorkflowRegistry>()(
deployedAt: undefined,
workspaceId,
deploymentStatuses: {},
history: {
past: [],
present: {
state: {
blocks: sourceState.blocks,
edges: sourceState.edges,
loops: sourceState.loops,
parallels: sourceState.parallels,
isDeployed: false,
deployedAt: undefined,
workspaceId,
},
timestamp: Date.now(),
action: 'Duplicated workflow',
subblockValues: {},
},
future: [],
},
lastSaved: Date.now(),
}
@@ -1156,23 +997,6 @@ export const useWorkflowRegistry = create<WorkflowRegistry>()(
parallels: {},
isDeployed: false,
deployedAt: undefined,
history: {
past: [],
present: {
state: {
blocks: {},
edges: [],
loops: {},
parallels: {},
isDeployed: false,
deployedAt: undefined,
},
timestamp: Date.now(),
action: 'Workflow deleted',
subblockValues: {},
},
future: [],
},
lastSaved: Date.now(),
})
@@ -253,44 +253,6 @@ describe('workflow store', () => {
expect(state.parallels.parallel1).toBeDefined()
expect(state.parallels.parallel1.parallelType).toBe('count')
})
it('should save to history when updating parallel properties', () => {
const { addBlock, updateParallelCollection, updateParallelCount, updateParallelType } =
useWorkflowStore.getState()
// Add a parallel block
addBlock(
'parallel1',
'parallel',
'Test Parallel',
{ x: 0, y: 0 },
{
count: 3,
collection: '',
}
)
// Get initial history length
const initialHistoryLength = useWorkflowStore.getState().history.past.length
// Update collection
updateParallelCollection('parallel1', '["a", "b", "c"]')
let state = useWorkflowStore.getState()
expect(state.history.past.length).toBe(initialHistoryLength + 1)
// Update count
updateParallelCount('parallel1', 5)
state = useWorkflowStore.getState()
expect(state.history.past.length).toBe(initialHistoryLength + 2)
// Update parallel type
updateParallelType('parallel1', 'count')
state = useWorkflowStore.getState()
expect(state.history.past.length).toBe(initialHistoryLength + 3)
})
})
describe('mode switching', () => {
+4 -91
View File
@@ -5,11 +5,6 @@ import { createLogger } from '@/lib/logs/console/logger'
import { getBlockOutputs } from '@/lib/workflows/block-outputs'
import { getBlock } from '@/blocks'
import { resolveOutputType } from '@/blocks/utils'
import {
pushHistory,
type WorkflowStoreWithHistory,
withHistory,
} from '@/stores/workflows/middleware'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
import {
@@ -20,8 +15,8 @@ import {
import type {
Position,
SubBlockState,
SyncControl,
WorkflowState,
WorkflowStore,
} from '@/stores/workflows/workflow/types'
import { generateLoopBlocks, generateParallelBlocks } from '@/stores/workflows/workflow/utils'
@@ -39,54 +34,12 @@ const initialState = {
// New field for per-workflow deployment tracking
deploymentStatuses: {},
needsRedeployment: false,
history: {
past: [],
present: {
state: {
blocks: {},
edges: [],
loops: {},
parallels: {},
isDeployed: false,
isPublished: false,
},
timestamp: 0,
action: 'Initial state',
subblockValues: {},
},
future: [],
},
}
// Create a consolidated sync control implementation
/**
* Socket-based SyncControl implementation (replaces HTTP sync)
*/
const createSyncControl = (): SyncControl => ({
markDirty: () => {
// No-op: Socket-based sync handles this automatically
},
isDirty: () => {
// Always return false since socket sync is real-time
return false
},
forceSync: () => {
// No-op: Socket-based sync is always in sync
},
})
export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
export const useWorkflowStore = create<WorkflowStore>()(
devtools(
withHistory((set, get) => ({
(set, get) => ({
...initialState,
undo: () => {},
redo: () => {},
canUndo: () => false,
canRedo: () => false,
revertToHistoryState: () => {},
// Implement sync control interface
sync: createSyncControl(),
setNeedsRedeploymentFlag: (needsRedeployment: boolean) => {
set({ needsRedeployment })
@@ -143,9 +96,7 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
}
set(newState)
pushHistory(set, get, newState, `Add ${type} node`)
get().updateLastSaved()
// get().sync.markDirty() // Disabled: Using socket-based sync
return
}
@@ -199,9 +150,7 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
}
set(newState)
pushHistory(set, get, newState, `Add ${type} block`)
get().updateLastSaved()
// get().sync.markDirty() // Disabled: Using socket-based sync
},
updateBlockPosition: (id: string, position: Position) => {
@@ -310,12 +259,6 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
})
set(newState)
pushHistory(
set,
get,
newState,
parentId ? `Set parent for ${block.name}` : `Remove parent for ${block.name}`
)
get().updateLastSaved()
// Note: Socket.IO handles real-time sync automatically
},
@@ -386,7 +329,6 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
})
set(newState)
pushHistory(set, get, newState, 'Remove block and children')
get().updateLastSaved()
// Note: Socket.IO handles real-time sync automatically
},
@@ -426,9 +368,7 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
}
set(newState)
pushHistory(set, get, newState, 'Add connection')
get().updateLastSaved()
// get().sync.markDirty() // Disabled: Using socket-based sync
},
removeEdge: (edgeId: string) => {
@@ -449,7 +389,6 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
}
set(newState)
pushHistory(set, get, newState, 'Remove connection')
get().updateLastSaved()
},
@@ -459,26 +398,7 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
edges: [],
loops: {},
parallels: {},
history: {
past: [],
present: {
state: {
blocks: {},
edges: [],
loops: {},
parallels: {},
isDeployed: false,
isPublished: false,
},
timestamp: Date.now(),
action: 'Initial state',
subblockValues: {},
},
future: [],
},
lastSaved: Date.now(),
isDeployed: false,
isPublished: false,
}
set(newState)
// Note: Socket.IO handles real-time sync automatically
@@ -585,7 +505,6 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
}
set(newState)
pushHistory(set, get, newState, `Duplicate ${block.type} block`)
get().updateLastSaved()
// Note: Socket.IO handles real-time sync automatically
},
@@ -723,7 +642,6 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
}
set(newState)
pushHistory(set, get, newState, `${name} block name updated`)
get().updateLastSaved()
// Note: Socket.IO handles real-time sync automatically
@@ -958,7 +876,6 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
},
})
pushHistory(set, get, newState, 'Reverted to deployed state')
get().updateLastSaved()
// Call API to persist the revert to normalized tables
@@ -1042,7 +959,6 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
}
set(newState)
pushHistory(set, get, newState, `Toggle trigger mode for ${block.type} block`)
get().updateLastSaved()
// Handle webhook enable/disable when toggling trigger mode
@@ -1111,7 +1027,6 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
}
set(newState)
pushHistory(set, get, newState, `Update parallel count`)
get().updateLastSaved()
// Note: Socket.IO handles real-time sync automatically
},
@@ -1139,7 +1054,6 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
}
set(newState)
pushHistory(set, get, newState, `Update parallel collection`)
get().updateLastSaved()
// Note: Socket.IO handles real-time sync automatically
},
@@ -1167,7 +1081,6 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
}
set(newState)
pushHistory(set, get, newState, `Update parallel type`)
get().updateLastSaved()
// Note: Socket.IO handles real-time sync automatically
},
@@ -1184,7 +1097,7 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
getDragStartPosition: () => {
return get().dragStartPosition || null
},
})),
}),
{ name: 'workflow-store' }
)
)
@@ -26,7 +26,6 @@ export interface ParallelConfig {
parallelType?: 'count' | 'collection'
}
// Generic subflow interface
export interface Subflow {
id: string
workflowId: string
@@ -158,16 +157,6 @@ export interface WorkflowState {
dragStartPosition?: DragStartPosition | null
}
// New interface for sync control
export interface SyncControl {
// Mark the workflow as changed, requiring sync
markDirty: () => void
// Check if the workflow has unsaved changes
isDirty: () => boolean
// Immediately trigger a sync
forceSync: () => void
}
export interface WorkflowActions {
addBlock: (
id: string,
@@ -218,11 +207,6 @@ export interface WorkflowActions {
toggleBlockTriggerMode: (id: string) => void
setDragStartPosition: (position: DragStartPosition | null) => void
getDragStartPosition: () => DragStartPosition | null
// Add the sync control methods to the WorkflowActions interface
sync: SyncControl
// Add method to get current workflow state (eliminates duplication in diff store)
getWorkflowState: () => WorkflowState
}