diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/note-block/note-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/note-block/note-block.tsx index 24c17c80c6..24af88f93e 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/note-block/note-block.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/note-block/note-block.tsx @@ -35,33 +35,47 @@ const NoteMarkdown = memo(function NoteMarkdown({ content }: { content: string }

{children}

, + p: ({ children }) => ( +

{children}

+ ), h1: ({ children }) => ( -

{children}

+

+ {children} +

), h2: ({ children }) => ( -

{children}

+

+ {children} +

), h3: ({ children }) => ( -

{children}

+

+ {children} +

), h4: ({ children }) => ( -

{children}

+

+ {children} +

), ul: ({ children }) => ( - + ), ol: ({ children }) => ( -
    {children}
+
    + {children} +
), - li: ({ children }) =>
  • {children}
  • , + li: ({ children }) =>
  • {children}
  • , code: ({ inline, children }: any) => inline ? ( - + {children} ) : ( - + {children} ), @@ -78,7 +92,7 @@ const NoteMarkdown = memo(function NoteMarkdown({ content }: { content: string } strong: ({ children }) => {children}, em: ({ children }) => {children}, blockquote: ({ children }) => ( -
    +
    {children}
    ), @@ -187,9 +201,7 @@ export const NoteBlock = memo(function NoteBlock({ id, data }: NodeProps ) : ( -

    - {content} -

    +

    {content}

    )} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/copilot/components/user-input/hooks/use-mention-data.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/copilot/components/user-input/hooks/use-mention-data.ts index ee40d2671d..4a0bb28ca6 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/copilot/components/user-input/hooks/use-mention-data.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/copilot/components/user-input/hooks/use-mention-data.ts @@ -1,6 +1,7 @@ 'use client' import { useCallback, useEffect, useState } from 'react' +import { shallow } from 'zustand/shallow' import { createLogger } from '@/lib/logs/console/logger' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' import { useWorkflowStore } from '@/stores/workflows/workflow/store' @@ -109,7 +110,11 @@ export function useMentionData(props: UseMentionDataProps) { const [workflowBlocks, setWorkflowBlocks] = useState([]) const [isLoadingWorkflowBlocks, setIsLoadingWorkflowBlocks] = useState(false) - const workflowStoreBlocks = useWorkflowStore((state) => state.blocks) + // Only subscribe to block keys to avoid re-rendering on position updates + const blockKeys = useWorkflowStore( + useCallback((state) => Object.keys(state.blocks), []), + shallow + ) // Use workflow registry as source of truth for workflows const registryWorkflows = useWorkflowRegistry((state) => state.workflows) @@ -139,15 +144,19 @@ export function useMentionData(props: UseMentionDataProps) { /** * Syncs workflow blocks from store + * Only re-runs when blocks are added/removed (not on position updates) */ useEffect(() => { const syncWorkflowBlocks = async () => { - if (!workflowId || !workflowStoreBlocks || Object.keys(workflowStoreBlocks).length === 0) { + if (!workflowId || blockKeys.length === 0) { setWorkflowBlocks([]) return } try { + // Fetch current blocks from store + const workflowStoreBlocks = useWorkflowStore.getState().blocks + const { registry: blockRegistry } = await import('@/blocks/registry') const mapped = Object.values(workflowStoreBlocks).map((b: any) => { const reg = (blockRegistry as any)[b.type] @@ -169,7 +178,7 @@ export function useMentionData(props: UseMentionDataProps) { } syncWorkflowBlocks() - }, [workflowStoreBlocks, workflowId]) + }, [blockKeys, workflowId]) /** * Ensures past chats are loaded @@ -323,10 +332,10 @@ export function useMentionData(props: UseMentionDataProps) { if (!workflowId) return logger.debug('ensureWorkflowBlocksLoaded called', { workflowId, - storeBlocksCount: Object.keys(workflowStoreBlocks || {}).length, + storeBlocksCount: blockKeys.length, workflowBlocksCount: workflowBlocks.length, }) - }, [workflowId, workflowStoreBlocks, workflowBlocks.length]) + }, [workflowId, blockKeys.length, workflowBlocks.length]) return { // State diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/editor.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/editor.tsx index 05eed70d54..5d32d3305e 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/editor.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/editor.tsx @@ -210,9 +210,14 @@ export function Editor() { /> ) : (

    { + if (e.detail === 2) { + e.preventDefault() + } + }} > {title}

    diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-node-utilities.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-node-utilities.ts index f8c2e5456c..ac6bf6170e 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-node-utilities.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-node-utilities.ts @@ -11,7 +11,7 @@ const DEFAULT_CONTAINER_HEIGHT = 300 * Hook providing utilities for node position, hierarchy, and dimension calculations */ export function useNodeUtilities(blocks: Record) { - const { getNodes, project } = useReactFlow() + const { getNodes } = useReactFlow() /** * Check if a block is a container type (loop, parallel, or subflow) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx index 23c81bb648..f47e7732dd 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx @@ -110,7 +110,7 @@ const WorkflowContent = React.memo(() => { // Hooks const params = useParams() const router = useRouter() - const { project, getNodes, fitView } = useReactFlow() + const { screenToFlowPosition, getNodes, fitView } = useReactFlow() const { emitCursorUpdate } = useSocket() // Get workspace ID from the params @@ -170,7 +170,7 @@ const WorkflowContent = React.memo(() => { // Get diff analysis for edge reconstruction const { diffAnalysis, isShowingDiff, isDiffReady } = useWorkflowDiffStore() - // Reconstruct deleted edges when viewing original workflow and filter trigger edges + // Reconstruct deleted edges when viewing original workflow and filter out invalid edges const edgesForDisplay = useMemo(() => { let edgesToFilter = edges @@ -237,7 +237,21 @@ const WorkflowContent = React.memo(() => { // Combine existing edges with reconstructed deleted edges edgesToFilter = [...edges, ...reconstructedEdges] } - return edgesToFilter + + // Filter out edges that connect to/from annotation-only blocks (note blocks) + // These blocks don't have handles and shouldn't have connections + return edgesToFilter.filter((edge) => { + const sourceBlock = blocks[edge.source] + const targetBlock = blocks[edge.target] + + // Remove edge if either source or target is an annotation-only block + if (!sourceBlock || !targetBlock) return false + if (isAnnotationOnlyBlock(sourceBlock.type) || isAnnotationOnlyBlock(targetBlock.type)) { + return false + } + + return true + }) }, [edges, isShowingDiff, isDiffReady, diffAnalysis, blocks]) // User permissions - get current user's specific permissions from context @@ -680,7 +694,11 @@ const WorkflowContent = React.memo(() => { // Auto-connect logic for blocks inside containers const isAutoConnectEnabled = useGeneralStore.getState().isAutoConnectEnabled let autoConnectEdge - if (isAutoConnectEnabled && data.type !== 'starter') { + if ( + isAutoConnectEnabled && + data.type !== 'starter' && + !isAnnotationOnlyBlock(data.type) + ) { if (existingChildBlocks.length > 0) { // Connect to the nearest existing child block within the container const closestBlock = existingChildBlocks @@ -694,7 +712,7 @@ const WorkflowContent = React.memo(() => { .sort((a, b) => a.distance - b.distance)[0]?.block if (closestBlock) { - // Don't create edges into trigger blocks + // Don't create edges into trigger blocks or annotation blocks const targetBlockConfig = getBlock(data.type) const isTargetTrigger = data.enableTriggerMode === true || targetBlockConfig?.category === 'triggers' @@ -769,10 +787,14 @@ const WorkflowContent = React.memo(() => { // Regular auto-connect logic const isAutoConnectEnabled = useGeneralStore.getState().isAutoConnectEnabled let autoConnectEdge - if (isAutoConnectEnabled && data.type !== 'starter') { + if ( + isAutoConnectEnabled && + data.type !== 'starter' && + !isAnnotationOnlyBlock(data.type) + ) { const closestBlock = findClosestOutput(position) if (closestBlock) { - // Don't create edges into trigger blocks + // Don't create edges into trigger blocks or annotation blocks const targetBlockConfig = getBlock(data.type) const isTargetTrigger = data.enableTriggerMode === true || targetBlockConfig?.category === 'triggers' @@ -842,7 +864,7 @@ const WorkflowContent = React.memo(() => { const baseName = type === 'loop' ? 'Loop' : 'Parallel' const name = getUniqueBlockName(baseName, blocks) - const centerPosition = project({ + const centerPosition = screenToFlowPosition({ x: window.innerWidth / 2, y: window.innerHeight / 2, }) @@ -891,7 +913,7 @@ const WorkflowContent = React.memo(() => { } // Calculate the center position of the viewport - const centerPosition = project({ + const centerPosition = screenToFlowPosition({ x: window.innerWidth / 2, y: window.innerHeight / 2, }) @@ -906,11 +928,11 @@ const WorkflowContent = React.memo(() => { // Auto-connect logic const isAutoConnectEnabled = useGeneralStore.getState().isAutoConnectEnabled let autoConnectEdge - if (isAutoConnectEnabled && type !== 'starter') { + if (isAutoConnectEnabled && type !== 'starter' && !isAnnotationOnlyBlock(type)) { const closestBlock = findClosestOutput(centerPosition) logger.info('Closest block found:', closestBlock) if (closestBlock) { - // Don't create edges into trigger blocks + // Don't create edges into trigger blocks or annotation blocks const targetBlockConfig = blockConfig const isTargetTrigger = enableTriggerMode || targetBlockConfig?.category === 'triggers' @@ -977,7 +999,7 @@ const WorkflowContent = React.memo(() => { ) } }, [ - project, + screenToFlowPosition, blocks, addBlock, addEdge, @@ -1014,7 +1036,7 @@ const WorkflowContent = React.memo(() => { } const bounds = canvasElement.getBoundingClientRect() - const position = project({ + const position = screenToFlowPosition({ x: detail.clientX - bounds.left, y: detail.clientY - bounds.top, }) @@ -1041,7 +1063,7 @@ const WorkflowContent = React.memo(() => { 'toolbar-drop-on-empty-workflow-overlay', handleOverlayToolbarDrop as EventListener ) - }, [project, handleToolbarDrop]) + }, [screenToFlowPosition, handleToolbarDrop]) /** * Recenter canvas when diff appears @@ -1090,7 +1112,7 @@ const WorkflowContent = React.memo(() => { if (!data?.type) return const reactFlowBounds = event.currentTarget.getBoundingClientRect() - const position = project({ + const position = screenToFlowPosition({ x: event.clientX - reactFlowBounds.left, y: event.clientY - reactFlowBounds.top, }) @@ -1106,7 +1128,7 @@ const WorkflowContent = React.memo(() => { logger.error('Error dropping block on ReactFlow canvas:', { err }) } }, - [project, handleToolbarDrop] + [screenToFlowPosition, handleToolbarDrop] ) const handleCanvasPointerMove = useCallback( @@ -1114,14 +1136,14 @@ const WorkflowContent = React.memo(() => { const target = event.currentTarget as HTMLElement const bounds = target.getBoundingClientRect() - const position = project({ + const position = screenToFlowPosition({ x: event.clientX - bounds.left, y: event.clientY - bounds.top, }) emitCursorUpdate(position) }, - [project, emitCursorUpdate] + [screenToFlowPosition, emitCursorUpdate] ) const handleCanvasPointerLeave = useCallback(() => { @@ -1144,7 +1166,7 @@ const WorkflowContent = React.memo(() => { try { const reactFlowBounds = event.currentTarget.getBoundingClientRect() - const position = project({ + const position = screenToFlowPosition({ x: event.clientX - reactFlowBounds.left, y: event.clientY - reactFlowBounds.top, }) @@ -1188,7 +1210,7 @@ const WorkflowContent = React.memo(() => { logger.error('Error in onDragOver', { err }) } }, - [project, isPointInLoopNode, getNodes] + [screenToFlowPosition, isPointInLoopNode, getNodes] ) // Initialize workflow when it exists in registry and isn't active @@ -1584,8 +1606,8 @@ const WorkflowContent = React.memo(() => { // Store currently dragged node ID setDraggedNodeId(node.id) - // Emit collaborative position update during drag for smooth real-time movement - collaborativeUpdateBlockPosition(node.id, node.position, false) + // Note: We don't emit position updates during drag to avoid flooding socket events. + // The final position is sent in onNodeDragStop for collaborative updates. // Get the current parent ID of the node being dragged const currentParentId = blocks[node.id]?.data?.parentId || null @@ -1721,14 +1743,7 @@ const WorkflowContent = React.memo(() => { } } }, - [ - getNodes, - potentialParentId, - blocks, - getNodeAbsolutePosition, - getNodeDepth, - collaborativeUpdateBlockPosition, - ] + [getNodes, potentialParentId, blocks, getNodeAbsolutePosition, getNodeDepth] ) // Add in a nodeDrag start event to set the dragStartParentId @@ -1855,7 +1870,8 @@ const WorkflowContent = React.memo(() => { // Auto-connect when moving an existing block into a container const isAutoConnectEnabled = useGeneralStore.getState().isAutoConnectEnabled - if (isAutoConnectEnabled) { + // Don't auto-connect annotation blocks (like note blocks) + if (isAutoConnectEnabled && !isAnnotationOnlyBlock(node.data?.type)) { // Existing children in the target container (excluding the moved node) const existingChildBlocks = Object.values(blocks).filter( (b) => b.data?.parentId === potentialParentId && b.id !== node.id