Skip to content

Conversation

@waleedlatif1
Copy link
Collaborator

@waleedlatif1 waleedlatif1 commented Jan 12, 2026

Summary

  • added the ability to export workflow
  • added animation for importing icon
  • standardized all hooks to use stable reference instead of getters, updated callers

Type of Change

  • New feature

Testing

Tested manually

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel
Copy link

vercel bot commented Jan 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
docs Ready Ready Preview, Comment Jan 12, 2026 5:47am

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 12, 2026

Greptile Overview

Greptile Summary

This PR adds workflow export functionality and refactors all hooks to use stable references instead of getter functions. While most of the refactoring is sound, there is a critical bug in workflow-item.tsx that will break duplicate and export operations for workflows.

Key Changes

New Functionality:

  • Added useExportFolder hook for exporting folders and their nested workflows as ZIP files
  • New animated Download icon component for import loading states
  • Folder export UI integration with proper workflow validation

Hook Refactoring:

  • Changed from getter pattern getWorkflowIds: () => string[] to stable reference pattern workflowIds: string[]
  • Applied across useExportWorkflow, useDeleteWorkflow, useDuplicateWorkflow, useDeleteFolder, useDuplicateFolder
  • Moved useImportWorkflow instantiation from WorkflowList to parent Sidebar component
  • Improved import loading state UI with Loader component

Critical Issues Found

1. BROKEN: Workflow duplicate/export in workflow-item.tsx 🔴

Location: Lines 88-97 in workflow-item.tsx

The refactoring introduced a critical bug. The hooks are initialized with:

workflowIds: capturedSelectionRef.current?.workflowIds || []

At render time, capturedSelectionRef.current is null, so both hooks are initialized with an empty array []. React hooks capture their dependencies at initialization - even when capturedSelectionRef.current is later updated via captureSelectionState(), the hooks' callbacks still have the stale empty array.

Impact: Users cannot duplicate or export workflows from the context menu. Operations will silently fail or do nothing.

Previous implementation (correct): Used getter functions that were called at execution time, not render time.

2. Missing dependency in useExportFolder 🟡

Location: Line 230 in use-export-folder.ts

The handleExportFolder callback uses folderStore.folders (line 120) but doesn't include folders in its dependency array. This could cause stale closure issues when folders change.

Positive Aspects

  • Filename deduplication logic added to prevent ZIP conflicts when exporting multiple workflows with same names
  • Clean separation of concerns with useExportFolder implementation
  • Proper loading states and error handling throughout
  • Animation support for download icon enhances UX during imports
  • Most hook refactorings are correctly implemented (folder hooks, delete workflow, etc.)

Recommendation

Do not merge until the critical bug in workflow-item.tsx is fixed. The old getter function pattern should be restored for these two specific hooks, or the hooks need to be refactored to accept workflowIds as parameters to their handler functions rather than to the hook itself.

Confidence Score: 1/5

  • This PR has a critical bug that breaks core workflow operations (duplicate/export). Not safe to merge.
  • Score of 1 reflects a critical logic error in workflow-item.tsx that will cause duplicate and export operations to fail silently for all users. The bug was introduced during the refactoring from getter functions to stable references. While the new folder export feature is well-implemented and most other refactorings are correct, this single bug breaks existing functionality that users rely on daily.
  • Critical attention required for workflow-item.tsx (lines 88-97). Also review use-export-folder.ts for missing dependency.

Important Files Changed

File Analysis

Filename Score Overview
apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/workflow-item/workflow-item.tsx 1/5 CRITICAL BUG: Hooks initialized with empty array instead of capturing ref at execution time. Duplicate and export operations will fail.
apps/sim/app/workspace/[workspaceId]/w/hooks/use-export-folder.ts 2/5 New file adds folder export functionality. Missing folders dependency in useCallback hook.
apps/sim/app/workspace/[workspaceId]/w/hooks/use-export-workflow.ts 4/5 Refactored from getter function to stable reference pattern. Adds filename deduplication logic for ZIP exports.
apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx 4/5 Moved useImportWorkflow hook instantiation from WorkflowList to Sidebar. Improved loading state UI with Loader component.
apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/folder-item/folder-item.tsx 4/5 Added folder export functionality with proper hasWorkflows check. Refactored hooks to use stable references.

Sequence Diagram

sequenceDiagram
    participant User
    participant WorkflowItem
    participant ContextMenu
    participant useDuplicateWorkflow
    participant useExportWorkflow
    participant API

    Note over WorkflowItem: Component renders with<br/>capturedSelectionRef.current = null
    
    WorkflowItem->>useDuplicateWorkflow: Initialize hook with<br/>workflowIds = [] (empty!)
    WorkflowItem->>useExportWorkflow: Initialize hook with<br/>workflowIds = [] (empty!)
    
    Note over useDuplicateWorkflow,useExportWorkflow: Hooks capture empty array<br/>in their closures
    
    User->>WorkflowItem: Right-click on workflow
    WorkflowItem->>WorkflowItem: captureSelectionState()
    WorkflowItem->>WorkflowItem: Updates capturedSelectionRef.current<br/>with selected workflow IDs
    
    WorkflowItem->>ContextMenu: Show context menu
    
    User->>ContextMenu: Click "Duplicate"
    ContextMenu->>useDuplicateWorkflow: handleDuplicateWorkflow()
    
    Note over useDuplicateWorkflow: BUG: Hook callback still has<br/>empty array [] from initialization!
    
    useDuplicateWorkflow->>useDuplicateWorkflow: Check if workflowIds exists
    useDuplicateWorkflow-->>ContextMenu: Early return (no workflows to duplicate)
    
    Note over User: Operation fails silently.<br/>Nothing happens!
    
    rect rgb(255, 240, 240)
        Note over WorkflowItem,API: SAME BUG AFFECTS EXPORT
        User->>ContextMenu: Click "Export"
        ContextMenu->>useExportWorkflow: handleExportWorkflow()
        useExportWorkflow->>useExportWorkflow: Check if workflowIds exists
        useExportWorkflow-->>ContextMenu: Early return (no workflows to export)
    end
    
    Note over User,API: FIX NEEDED: Use getter functions<br/>or pass IDs to handler at call time
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

@waleedlatif1
Copy link
Collaborator Author

@greptile

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6 files reviewed, 6 comments

Edit Code Review Agent Settings | Greptile

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 12, 2026

Additional Comments (1)

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/folder-item/folder-item.tsx
The duplicate functionality is disabled when a folder has no workflows (!hasWorkflows), but the export functionality also has this same condition (!hasWorkflows). This means:

  1. Both operations require workflows to exist (correct for export)
  2. However, disabling duplicate for empty folders may not be intended behavior

Looking at useDuplicateFolder.ts, the mutation will still work for empty folders (it duplicates the folder structure). The hasWorkflows check is coming from useExportFolder which makes sense for export, but applying it to duplicate seems incorrect.

Consider whether duplicate should really be disabled for empty folders, or if this is conflating two different concerns.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/folder-item/folder-item.tsx
Line: 383:383

Comment:
The duplicate functionality is disabled when a folder has no workflows (`!hasWorkflows`), but the export functionality also has this same condition (`!hasWorkflows`). This means:

1. Both operations require workflows to exist (correct for export)
2. However, disabling duplicate for empty folders may not be intended behavior

Looking at `useDuplicateFolder.ts`, the mutation will still work for empty folders (it duplicates the folder structure). The `hasWorkflows` check is coming from `useExportFolder` which makes sense for export, but applying it to duplicate seems incorrect. 

Consider whether duplicate should really be disabled for empty folders, or if this is conflating two different concerns.

How can I resolve this? If you propose a fix, please make it concise.

@waleedlatif1
Copy link
Collaborator Author

@greptile

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines 88 to 97
const { handleDuplicateWorkflow } = useDuplicateWorkflow({
workspaceId,
getWorkflowIds: () => {
// Use the selection captured at right-click time
return capturedSelectionRef.current?.workflowIds || []
},
workflowIds: capturedSelectionRef.current?.workflowIds || [],
})

// Export workflow hook
// Export workflow hook (uses captured selection from right-click)
const { handleExportWorkflow } = useExportWorkflow({
workspaceId,
getWorkflowIds: () => {
// Use the selection captured at right-click time
return capturedSelectionRef.current?.workflowIds || []
},
workflowIds: capturedSelectionRef.current?.workflowIds || [],
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical Bug: Hooks initialized with stale empty array

The useDuplicateWorkflow and useExportWorkflow hooks are being called with capturedSelectionRef.current?.workflowIds || [] at component render time. When the component first renders, capturedSelectionRef.current is null (initialized on line 65), so both hooks are initialized with an empty array [].

React hooks capture their prop values at initialization. Even though capturedSelectionRef.current is later updated in captureSelectionState() (line 174), the hooks don't see this update because they were already initialized with the empty array.

Result: When users try to duplicate or export workflows, the operations will fail silently or do nothing because the hooks have an empty workflowIds array.

The old implementation was correct - it used getter functions getWorkflowIds: () => capturedSelectionRef.current?.workflowIds || [] which were called at execution time, not render time.

Fix: Either:

  1. Revert to using getter functions for these two hooks, OR
  2. Move these hook calls inside the handler functions (which would require refactoring the hooks to not be hooks), OR
  3. Pass the workflowIds directly to the handler functions instead of to the hooks
Suggested change
const { handleDuplicateWorkflow } = useDuplicateWorkflow({
workspaceId,
getWorkflowIds: () => {
// Use the selection captured at right-click time
return capturedSelectionRef.current?.workflowIds || []
},
workflowIds: capturedSelectionRef.current?.workflowIds || [],
})
// Export workflow hook
// Export workflow hook (uses captured selection from right-click)
const { handleExportWorkflow } = useExportWorkflow({
workspaceId,
getWorkflowIds: () => {
// Use the selection captured at right-click time
return capturedSelectionRef.current?.workflowIds || []
},
workflowIds: capturedSelectionRef.current?.workflowIds || [],
})
// Duplicate workflow hook (uses captured selection from right-click)
const { handleDuplicateWorkflow: duplicateWorkflow } = useDuplicateWorkflow({
workspaceId,
workflowIds: [], // Pass empty array initially, will be overridden in handler
})
// Wrapper that passes the actual workflowIds at execution time
const handleDuplicateWorkflow = useCallback(() => {
const workflowIds = capturedSelectionRef.current?.workflowIds || []
if (workflowIds.length === 0) return
// Call the underlying duplicate function with fresh workflowIds
duplicateWorkflow(workflowIds)
}, [duplicateWorkflow])
// Export workflow hook (uses captured selection from right-click)
const { handleExportWorkflow: exportWorkflow } = useExportWorkflow({
workspaceId,
workflowIds: [], // Pass empty array initially, will be overridden in handler
})
// Wrapper that passes the actual workflowIds at execution time
const handleExportWorkflow = useCallback(() => {
const workflowIds = capturedSelectionRef.current?.workflowIds || []
if (workflowIds.length === 0) return
// Call the underlying export function with fresh workflowIds
exportWorkflow(workflowIds)
}, [exportWorkflow])
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/workflow-item/workflow-item.tsx
Line: 88:97

Comment:
**Critical Bug: Hooks initialized with stale empty array**

The `useDuplicateWorkflow` and `useExportWorkflow` hooks are being called with `capturedSelectionRef.current?.workflowIds || []` at component render time. When the component first renders, `capturedSelectionRef.current` is `null` (initialized on line 65), so both hooks are initialized with an empty array `[]`.

React hooks capture their prop values at initialization. Even though `capturedSelectionRef.current` is later updated in `captureSelectionState()` (line 174), the hooks don't see this update because they were already initialized with the empty array.

**Result**: When users try to duplicate or export workflows, the operations will fail silently or do nothing because the hooks have an empty workflowIds array.

**The old implementation was correct** - it used getter functions `getWorkflowIds: () => capturedSelectionRef.current?.workflowIds || []` which were called at execution time, not render time.

**Fix**: Either:
1. Revert to using getter functions for these two hooks, OR
2. Move these hook calls inside the handler functions (which would require refactoring the hooks to not be hooks), OR  
3. Pass the workflowIds directly to the handler functions instead of to the hooks

```suggestion
  // Duplicate workflow hook (uses captured selection from right-click)
  const { handleDuplicateWorkflow: duplicateWorkflow } = useDuplicateWorkflow({
    workspaceId,
    workflowIds: [], // Pass empty array initially, will be overridden in handler
  })

  // Wrapper that passes the actual workflowIds at execution time
  const handleDuplicateWorkflow = useCallback(() => {
    const workflowIds = capturedSelectionRef.current?.workflowIds || []
    if (workflowIds.length === 0) return
    // Call the underlying duplicate function with fresh workflowIds
    duplicateWorkflow(workflowIds)
  }, [duplicateWorkflow])

  // Export workflow hook (uses captured selection from right-click)
  const { handleExportWorkflow: exportWorkflow } = useExportWorkflow({
    workspaceId,
    workflowIds: [], // Pass empty array initially, will be overridden in handler
  })

  // Wrapper that passes the actual workflowIds at execution time  
  const handleExportWorkflow = useCallback(() => {
    const workflowIds = capturedSelectionRef.current?.workflowIds || []
    if (workflowIds.length === 0) return
    // Call the underlying export function with fresh workflowIds
    exportWorkflow(workflowIds)
  }, [exportWorkflow])
```

How can I resolve this? If you propose a fix, please make it concise.

} finally {
setIsExporting(false)
}
}, [folderId, isExporting, workflows, onSuccess])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing folders dependency in useCallback

The handleExportFolder callback uses folderStore.folders on line 120 (collectWorkflowsInFolder(folderId, workflows, folderStore.folders)), but folders is not included in the dependency array.

The folders state comes from useFolderStore() on line 67 and should be included in the dependencies to ensure the callback is recreated when folders change.

Suggested change
}, [folderId, isExporting, workflows, onSuccess])
}, [folderId, isExporting, workflows, folders, onSuccess])
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/sim/app/workspace/[workspaceId]/w/hooks/use-export-folder.ts
Line: 230:230

Comment:
**Missing `folders` dependency in useCallback**

The `handleExportFolder` callback uses `folderStore.folders` on line 120 (`collectWorkflowsInFolder(folderId, workflows, folderStore.folders)`), but `folders` is not included in the dependency array.

The `folders` state comes from `useFolderStore()` on line 67 and should be included in the dependencies to ensure the callback is recreated when folders change.

```suggestion
  }, [folderId, isExporting, workflows, folders, onSuccess])
```

How can I resolve this? If you propose a fix, please make it concise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants