From 1f5ebc3e7312cb8711f5ab414cf6338ef23a5d6b Mon Sep 17 00:00:00 2001 From: waleed Date: Wed, 15 Oct 2025 15:15:01 -0700 Subject: [PATCH] fix(layout): layout canvas onInit after node sizes are calculated, remove legacy history code and legacy marking of dirty workflows --- .../(landing)/components/structured-data.tsx | 10 +- apps/sim/app/manifest.ts | 1 - apps/sim/app/page.tsx | 6 +- .../components/control-bar/control-bar.tsx | 3 +- .../[workspaceId]/w/[workflowId]/workflow.tsx | 13 +- apps/sim/stores/workflows/middleware.ts | 265 ------------------ apps/sim/stores/workflows/registry/store.ts | 178 +----------- .../stores/workflows/workflow/store.test.ts | 38 --- apps/sim/stores/workflows/workflow/store.ts | 95 +------ apps/sim/stores/workflows/workflow/types.ts | 16 -- 10 files changed, 23 insertions(+), 602 deletions(-) delete mode 100644 apps/sim/stores/workflows/middleware.ts diff --git a/apps/sim/app/(landing)/components/structured-data.tsx b/apps/sim/app/(landing)/components/structured-data.tsx index 632757bc99..e83f81e02d 100644 --- a/apps/sim/app/(landing)/components/structured-data.tsx +++ b/apps/sim/app/(landing)/components/structured-data.tsx @@ -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.', }, }, { diff --git a/apps/sim/app/manifest.ts b/apps/sim/app/manifest.ts index 8de0b14e8e..ddb651a838 100644 --- a/apps/sim/app/manifest.ts +++ b/apps/sim/app/manifest.ts @@ -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', diff --git a/apps/sim/app/page.tsx b/apps/sim/app/page.tsx index c168082303..90e63c67bc 100644 --- a/apps/sim/app/page.tsx +++ b/apps/sim/app/page.tsx @@ -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', diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/control-bar.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/control-bar.tsx index 77a24ae23f..fb90e938fc 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/control-bar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/control-bar.tsx @@ -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, diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx index 3486b0732e..adc3d0bdcc 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx @@ -92,7 +92,8 @@ const WorkflowContent = React.memo(() => { const [draggedNodeId, setDraggedNodeId] = useState(null) const [potentialParentId, setPotentialParentId] = useState(null) // State for tracking validation errors - const [nestedSubflowErrors, setNestedSubflowErrors] = useState>(new Set()) + // Use a function initializer to ensure the Set is only created once + const [nestedSubflowErrors, setNestedSubflowErrors] = useState>(() => new Set()) // Enhanced edge selection with parent context and unique identifier const [selectedEdgeInfo, setSelectedEdgeInfo] = useState(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 diff --git a/apps/sim/stores/workflows/middleware.ts b/apps/sim/stores/workflows/middleware.ts deleted file mode 100644 index e8751bc1df..0000000000 --- a/apps/sim/stores/workflows/middleware.ts +++ /dev/null @@ -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> -} - -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 -): StateCreator => { - 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 - | ((state: WorkflowStoreWithHistory) => Partial), - 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(), - }) -} diff --git a/apps/sim/stores/workflows/registry/store.ts b/apps/sim/stores/workflows/registry/store.ts index bf6e616968..e83dd0bd4a 100644 --- a/apps/sim/stores/workflows/registry/store.ts +++ b/apps/sim/stores/workflows/registry/store.ts @@ -90,7 +90,6 @@ async function fetchWorkflowsFromDB(workspaceId?: string): Promise { 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()( 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()( 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()( 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()( 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()( 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()( 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()( 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(), }) diff --git a/apps/sim/stores/workflows/workflow/store.test.ts b/apps/sim/stores/workflows/workflow/store.test.ts index e942c87e7f..e10176fa9e 100644 --- a/apps/sim/stores/workflows/workflow/store.test.ts +++ b/apps/sim/stores/workflows/workflow/store.test.ts @@ -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', () => { diff --git a/apps/sim/stores/workflows/workflow/store.ts b/apps/sim/stores/workflows/workflow/store.ts index e5159fdb58..a8b93deb09 100644 --- a/apps/sim/stores/workflows/workflow/store.ts +++ b/apps/sim/stores/workflows/workflow/store.ts @@ -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()( +export const useWorkflowStore = create()( 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()( } 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()( } 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()( }) 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()( }) 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()( } 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()( } set(newState) - pushHistory(set, get, newState, 'Remove connection') get().updateLastSaved() }, @@ -459,26 +398,7 @@ export const useWorkflowStore = create()( 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()( } 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()( } 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()( }, }) - 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()( } 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()( } 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()( } 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()( } 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()( getDragStartPosition: () => { return get().dragStartPosition || null }, - })), + }), { name: 'workflow-store' } ) ) diff --git a/apps/sim/stores/workflows/workflow/types.ts b/apps/sim/stores/workflows/workflow/types.ts index 3a1c6a0b5b..05cb67d572 100644 --- a/apps/sim/stores/workflows/workflow/types.ts +++ b/apps/sim/stores/workflows/workflow/types.ts @@ -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 }