From c1dea0e2ce4d0d92282ecb0e418607d94b18ab74 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 25 Feb 2026 23:02:37 -0800 Subject: [PATCH 1/6] feat(sidebar): add lock/unlock to workflow registry context menu --- .../[workspaceId]/w/[workflowId]/workflow.tsx | 10 +++++ .../components/context-menu/context-menu.tsx | 38 ++++++++++++++++++- .../workflow-item/workflow-item.tsx | 24 ++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx index 451e5a709f..02c6175c2f 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx @@ -2534,6 +2534,16 @@ const WorkflowContent = React.memo(() => { window.removeEventListener('remove-from-subflow', handleRemoveFromSubflow as EventListener) }, [blocks, edgesForDisplay, getNodeAbsolutePosition, collaborativeBatchUpdateParent]) + useEffect(() => { + const handleToggleWorkflowLock = (e: CustomEvent<{ blockIds: string[] }>) => { + collaborativeBatchToggleLocked(e.detail.blockIds) + } + + window.addEventListener('toggle-workflow-lock', handleToggleWorkflowLock as EventListener) + return () => + window.removeEventListener('toggle-workflow-lock', handleToggleWorkflowLock as EventListener) + }, [collaborativeBatchToggleLocked]) + /** * Updates container dimensions in displayNodes during drag or keyboard movement. */ diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/context-menu/context-menu.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/context-menu/context-menu.tsx index efb3e7eb0f..b20fea3d9c 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/context-menu/context-menu.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/context-menu/context-menu.tsx @@ -281,6 +281,24 @@ interface ContextMenuProps { * Set to true when user cannot leave (e.g., last admin) */ disableLeave?: boolean + /** + * Callback when lock/unlock is clicked + */ + onToggleLock?: () => void + /** + * Whether to show the lock option (default: false) + * Set to true for workflows that support locking + */ + showLock?: boolean + /** + * Whether the lock option is disabled (default: false) + * Set to true when user lacks permissions + */ + disableLock?: boolean + /** + * Whether the workflow is currently locked (all blocks locked) + */ + isLocked?: boolean } /** @@ -321,6 +339,10 @@ export function ContextMenu({ onLeave, showLeave = false, disableLeave = false, + onToggleLock, + showLock = false, + disableLock = false, + isLocked = false, }: ContextMenuProps) { const [hexInput, setHexInput] = useState(currentColor || '#ffffff') @@ -372,7 +394,8 @@ export function ContextMenu({ (showRename && onRename) || (showCreate && onCreate) || (showCreateFolder && onCreateFolder) || - (showColorChange && onColorChange) + (showColorChange && onColorChange) || + (showLock && onToggleLock) const hasCopySection = (showDuplicate && onDuplicate) || (showExport && onExport) return ( @@ -495,6 +518,19 @@ export function ContextMenu({ )} + {showLock && onToggleLock && ( + { + onToggleLock() + onClose() + }} + > + {isLocked ? 'Unlock' : 'Lock'} + + )} + {/* Copy and export actions */} {hasEditSection && hasCopySection && } {showDuplicate && onDuplicate && ( diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/workflow-item/workflow-item.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/workflow-item/workflow-item.tsx index 6963464d46..f658992f7f 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/workflow-item/workflow-item.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/workflow-item/workflow-item.tsx @@ -27,6 +27,7 @@ import { import { useFolderStore } from '@/stores/folders/store' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' import type { WorkflowMetadata } from '@/stores/workflows/registry/types' +import { useWorkflowStore } from '@/stores/workflows/workflow/store' interface WorkflowItemProps { workflow: WorkflowMetadata @@ -169,6 +170,25 @@ export function WorkflowItem({ [workflow.id, updateWorkflow] ) + const activeWorkflowId = useWorkflowRegistry((state) => state.activeWorkflowId) + const isActiveWorkflow = workflow.id === activeWorkflowId + + const allBlocksLocked = useWorkflowStore( + useCallback((state) => { + const blockValues = Object.values(state.blocks) + if (blockValues.length === 0) return false + return blockValues.every((block) => block.locked) + }, []) + ) + const isWorkflowLocked = isActiveWorkflow && allBlocksLocked + + const handleToggleLock = useCallback(() => { + if (!isActiveWorkflow) return + const blockIds = Object.keys(useWorkflowStore.getState().blocks) + if (blockIds.length === 0) return + window.dispatchEvent(new CustomEvent('toggle-workflow-lock', { detail: { blockIds } })) + }, [isActiveWorkflow]) + const isEditingRef = useRef(false) const { @@ -461,6 +481,10 @@ export function WorkflowItem({ disableExport={!userPermissions.canEdit} disableColorChange={!userPermissions.canEdit} disableDelete={!userPermissions.canEdit || !canDeleteSelection} + onToggleLock={handleToggleLock} + showLock={isActiveWorkflow && !isMixedSelection && selectedWorkflows.size <= 1} + disableLock={!userPermissions.canAdmin} + isLocked={isWorkflowLocked} /> Date: Wed, 25 Feb 2026 23:06:41 -0800 Subject: [PATCH 2/6] docs(tools): add manual descriptions to google_books and table --- apps/docs/content/docs/en/tools/google_books.mdx | 8 +++++++- apps/docs/content/docs/en/tools/table.mdx | 4 +++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/docs/content/docs/en/tools/google_books.mdx b/apps/docs/content/docs/en/tools/google_books.mdx index 2b370d1394..d23d424f8b 100644 --- a/apps/docs/content/docs/en/tools/google_books.mdx +++ b/apps/docs/content/docs/en/tools/google_books.mdx @@ -5,11 +5,17 @@ description: Search and retrieve book information import { BlockInfoCard } from "@/components/ui/block-info-card" - +{/* MANUAL-CONTENT-START:intro */} +[Google Books](https://books.google.com) is Google's comprehensive book discovery and metadata service, providing access to millions of books from publishers, libraries, and digitized collections worldwide. The Google Books API enables programmatic search and retrieval of detailed book information including titles, authors, descriptions, ratings, and publication details. + +In Sim, the Google Books integration allows your agents to search for books and retrieve volume details as part of automated workflows. This enables use cases such as content research, reading list curation, bibliographic data enrichment, ISBN lookups, and knowledge gathering from published works. By connecting Sim with Google Books, your agents can discover and analyze book metadata, filter by availability or format, and incorporate literary references into their outputs—all without manual research. +{/* MANUAL-CONTENT-END */} + ## Usage Instructions Search for books using the Google Books API. Find volumes by title, author, ISBN, or keywords, and retrieve detailed information about specific books including descriptions, ratings, and publication details. diff --git a/apps/docs/content/docs/en/tools/table.mdx b/apps/docs/content/docs/en/tools/table.mdx index acde8e300c..388005b772 100644 --- a/apps/docs/content/docs/en/tools/table.mdx +++ b/apps/docs/content/docs/en/tools/table.mdx @@ -5,11 +5,12 @@ description: User-defined data tables for storing and querying structured data import { BlockInfoCard } from "@/components/ui/block-info-card" - +{/* MANUAL-CONTENT-START:intro */} Tables allow you to create and manage custom data tables directly within Sim. Store, query, and manipulate structured data within your workflows without needing external database integrations. **Why Use Tables?** @@ -26,6 +27,7 @@ Tables allow you to create and manage custom data tables directly within Sim. St - Batch operations for bulk inserts - Bulk updates and deletes by filter - Up to 10,000 rows per table, 100 tables per workspace +{/* MANUAL-CONTENT-END */} ## Creating Tables From 8e5b8057b0c6c63d1696210885cb80f49f011d99 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 25 Feb 2026 23:09:41 -0800 Subject: [PATCH 3/6] docs(tools): add manual descriptions to google_bigquery and google_tasks --- apps/docs/content/docs/en/tools/google_bigquery.mdx | 8 +++++++- apps/docs/content/docs/en/tools/google_tasks.mdx | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/apps/docs/content/docs/en/tools/google_bigquery.mdx b/apps/docs/content/docs/en/tools/google_bigquery.mdx index 4218ff9f68..4b31096c7a 100644 --- a/apps/docs/content/docs/en/tools/google_bigquery.mdx +++ b/apps/docs/content/docs/en/tools/google_bigquery.mdx @@ -5,11 +5,17 @@ description: Query, list, and insert data in Google BigQuery import { BlockInfoCard } from "@/components/ui/block-info-card" - +{/* MANUAL-CONTENT-START:intro */} +[Google BigQuery](https://cloud.google.com/bigquery) is Google Cloud's fully managed, serverless data warehouse designed for large-scale data analytics. BigQuery lets you run fast SQL queries on massive datasets, making it ideal for business intelligence, data exploration, and machine learning pipelines. It supports standard SQL, streaming inserts, and integrates with the broader Google Cloud ecosystem. + +In Sim, the Google BigQuery integration allows your agents to query datasets, list tables, inspect schemas, and insert rows as part of automated workflows. This enables use cases such as automated reporting, data pipeline orchestration, real-time data ingestion, and analytics-driven decision making. By connecting Sim with BigQuery, your agents can pull insights from petabytes of data, write results back to tables, and keep your analytics workflows running without manual intervention. +{/* MANUAL-CONTENT-END */} + ## Usage Instructions Connect to Google BigQuery to run SQL queries, list datasets and tables, get table metadata, and insert rows. diff --git a/apps/docs/content/docs/en/tools/google_tasks.mdx b/apps/docs/content/docs/en/tools/google_tasks.mdx index 192a6dc37b..9743de3979 100644 --- a/apps/docs/content/docs/en/tools/google_tasks.mdx +++ b/apps/docs/content/docs/en/tools/google_tasks.mdx @@ -5,11 +5,17 @@ description: Manage Google Tasks import { BlockInfoCard } from "@/components/ui/block-info-card" - +{/* MANUAL-CONTENT-START:intro */} +[Google Tasks](https://support.google.com/tasks) is Google's lightweight task management service, integrated into Gmail, Google Calendar, and the standalone Google Tasks app. It provides a simple way to create, organize, and track to-do items with support for due dates, subtasks, and task lists. As part of Google Workspace, Google Tasks keeps your action items synchronized across all your devices. + +In Sim, the Google Tasks integration allows your agents to create, read, update, delete, and list tasks and task lists as part of automated workflows. This enables use cases such as automated task creation from incoming data, to-do list management based on workflow triggers, task status tracking, and deadline monitoring. By connecting Sim with Google Tasks, your agents can manage action items programmatically, keep teams organized, and ensure nothing falls through the cracks. +{/* MANUAL-CONTENT-END */} + ## Usage Instructions Integrate Google Tasks into your workflow. Create, read, update, delete, and list tasks and task lists. From 4db11e4ab591658d5f420472f8dc5accb68219e8 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 25 Feb 2026 23:18:35 -0800 Subject: [PATCH 4/6] fix(sidebar): avoid unnecessary store subscriptions and fix mixed lock state toggle --- .../workflow-item/workflow-item.tsx | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/workflow-item/workflow-item.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/workflow-item/workflow-item.tsx index f658992f7f..b59cf6cb52 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/workflow-item/workflow-item.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/workflow-item/workflow-item.tsx @@ -173,21 +173,36 @@ export function WorkflowItem({ const activeWorkflowId = useWorkflowRegistry((state) => state.activeWorkflowId) const isActiveWorkflow = workflow.id === activeWorkflowId - const allBlocksLocked = useWorkflowStore( - useCallback((state) => { - const blockValues = Object.values(state.blocks) - if (blockValues.length === 0) return false - return blockValues.every((block) => block.locked) - }, []) + const isWorkflowLocked = useWorkflowStore( + useCallback( + (state) => { + if (!isActiveWorkflow) return false + const blockValues = Object.values(state.blocks) + if (blockValues.length === 0) return false + return blockValues.every((block) => block.locked) + }, + [isActiveWorkflow] + ) ) - const isWorkflowLocked = isActiveWorkflow && allBlocksLocked const handleToggleLock = useCallback(() => { if (!isActiveWorkflow) return - const blockIds = Object.keys(useWorkflowStore.getState().blocks) + const blocks = useWorkflowStore.getState().blocks + const blockIds = Object.keys(blocks) if (blockIds.length === 0) return + // batchToggleLocked determines target state from the first block's locked value. + // Ensure the first ID is a block whose state matches our intent: + // when locking (not all locked), put an unlocked block first; + // when unlocking (all locked), put a locked block first. + const wantLocked = !isWorkflowLocked + const pivotId = blockIds.find((id) => Boolean(blocks[id].locked) !== wantLocked) + if (pivotId && pivotId !== blockIds[0]) { + const idx = blockIds.indexOf(pivotId) + blockIds[idx] = blockIds[0] + blockIds[0] = pivotId + } window.dispatchEvent(new CustomEvent('toggle-workflow-lock', { detail: { blockIds } })) - }, [isActiveWorkflow]) + }, [isActiveWorkflow, isWorkflowLocked]) const isEditingRef = useRef(false) From 6b9c3378dc8468a3bdf5630924b70f2798ca51df Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 25 Feb 2026 23:30:07 -0800 Subject: [PATCH 5/6] fix(sidebar): use getWorkflowLockToggleIds utility for lock toggle Replaces manual pivot-sorting logic with the existing utility function, which handles block ordering and no-op guards consistently. --- .../components/workflow-item/workflow-item.tsx | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/workflow-item/workflow-item.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/workflow-item/workflow-item.tsx index b59cf6cb52..39ad12852f 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/workflow-item/workflow-item.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/workflow-item/workflow-item.tsx @@ -24,6 +24,7 @@ import { useExportSelection, useExportWorkflow, } from '@/app/workspace/[workspaceId]/w/hooks' +import { getWorkflowLockToggleIds } from '@/app/workspace/[workspaceId]/w/[workflowId]/utils' import { useFolderStore } from '@/stores/folders/store' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' import type { WorkflowMetadata } from '@/stores/workflows/registry/types' @@ -188,19 +189,8 @@ export function WorkflowItem({ const handleToggleLock = useCallback(() => { if (!isActiveWorkflow) return const blocks = useWorkflowStore.getState().blocks - const blockIds = Object.keys(blocks) + const blockIds = getWorkflowLockToggleIds(blocks, !isWorkflowLocked) if (blockIds.length === 0) return - // batchToggleLocked determines target state from the first block's locked value. - // Ensure the first ID is a block whose state matches our intent: - // when locking (not all locked), put an unlocked block first; - // when unlocking (all locked), put a locked block first. - const wantLocked = !isWorkflowLocked - const pivotId = blockIds.find((id) => Boolean(blocks[id].locked) !== wantLocked) - if (pivotId && pivotId !== blockIds[0]) { - const idx = blockIds.indexOf(pivotId) - blockIds[idx] = blockIds[0] - blockIds[0] = pivotId - } window.dispatchEvent(new CustomEvent('toggle-workflow-lock', { detail: { blockIds } })) }, [isActiveWorkflow, isWorkflowLocked]) From 7db6f64a98b91c9410c75883e6f8e68642654118 Mon Sep 17 00:00:00 2001 From: waleed Date: Wed, 25 Feb 2026 23:32:50 -0800 Subject: [PATCH 6/6] lint --- .../workflow-list/components/workflow-item/workflow-item.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/workflow-item/workflow-item.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/workflow-item/workflow-item.tsx index 39ad12852f..6651f6880f 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/workflow-item/workflow-item.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/workflow-item/workflow-item.tsx @@ -6,6 +6,7 @@ import { MoreHorizontal } from 'lucide-react' import Link from 'next/link' import { useParams } from 'next/navigation' import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider' +import { getWorkflowLockToggleIds } from '@/app/workspace/[workspaceId]/w/[workflowId]/utils' import { ContextMenu } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/context-menu/context-menu' import { DeleteModal } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/delete-modal/delete-modal' import { Avatars } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/workflow-item/avatars/avatars' @@ -24,7 +25,6 @@ import { useExportSelection, useExportWorkflow, } from '@/app/workspace/[workspaceId]/w/hooks' -import { getWorkflowLockToggleIds } from '@/app/workspace/[workspaceId]/w/[workflowId]/utils' import { useFolderStore } from '@/stores/folders/store' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' import type { WorkflowMetadata } from '@/stores/workflows/registry/types'