fix(notes): fix notes, tighten spacing, update deprecated zustand function, update use mention data to ignore block positon (#2002)

This commit is contained in:
Waleed
2025-11-14 22:12:01 -08:00
committed by GitHub
parent ad2a375358
commit 8bd75debc1
5 changed files with 94 additions and 52 deletions

View File

@@ -35,33 +35,47 @@ const NoteMarkdown = memo(function NoteMarkdown({ content }: { content: string }
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
p: ({ children }) => <p className='mb-0 text-[#E5E5E5] text-sm'>{children}</p>,
p: ({ children }) => (
<p className='!mt-0 !-mb-4 text-[#E5E5E5] text-sm leading-tight'>{children}</p>
),
h1: ({ children }) => (
<h1 className='mt-0 mb-[-2px] font-semibold text-[#E5E5E5] text-lg'>{children}</h1>
<h1 className='!mt-0 !-mb-4 font-semibold text-[#E5E5E5] text-lg leading-tight'>
{children}
</h1>
),
h2: ({ children }) => (
<h2 className='mt-0 mb-[-2px] font-semibold text-[#E5E5E5] text-base'>{children}</h2>
<h2 className='!mt-0 !-mb-4 font-semibold text-[#E5E5E5] text-base leading-tight'>
{children}
</h2>
),
h3: ({ children }) => (
<h3 className='mt-0 mb-[-2px] font-semibold text-[#E5E5E5] text-sm'>{children}</h3>
<h3 className='!mt-0 !-mb-4 font-semibold text-[#E5E5E5] text-sm leading-tight'>
{children}
</h3>
),
h4: ({ children }) => (
<h4 className='mt-0 mb-[-2px] font-semibold text-[#E5E5E5] text-xs'>{children}</h4>
<h4 className='!mt-0 !-mb-4 font-semibold text-[#E5E5E5] text-xs leading-tight'>
{children}
</h4>
),
ul: ({ children }) => (
<ul className='-mt-[2px] mb-0 list-disc pl-4 text-[#E5E5E5] text-sm'>{children}</ul>
<ul className='!-mt-4 !-mb-4 [&_li>ul]:!mt-0 [&_li>ul]:!mb-0 [&_li>ol]:!mt-0 [&_li>ol]:!mb-0 list-disc pl-4 text-[#E5E5E5] text-sm leading-tight'>
{children}
</ul>
),
ol: ({ children }) => (
<ol className='-mt-[2px] mb-0 list-decimal pl-4 text-[#E5E5E5] text-sm'>{children}</ol>
<ol className='!-mt-4 !-mb-4 [&_li>ul]:!mt-0 [&_li>ul]:!mb-0 [&_li>ol]:!mt-0 [&_li>ol]:!mb-0 list-decimal pl-4 text-[#E5E5E5] text-sm leading-tight'>
{children}
</ol>
),
li: ({ children }) => <li className='mb-0'>{children}</li>,
li: ({ children }) => <li className='!mb-0 leading-tight'>{children}</li>,
code: ({ inline, children }: any) =>
inline ? (
<code className='rounded bg-[var(--divider)] px-1 py-0.5 text-[#F59E0B] text-xs'>
<code className='break-words rounded bg-[var(--divider)] px-1 py-0.5 text-[#F59E0B] text-xs'>
{children}
</code>
) : (
<code className='block rounded bg-[#1A1A1A] p-2 text-[#E5E5E5] text-xs'>
<code className='block whitespace-pre-wrap break-words rounded bg-[#1A1A1A] p-2 text-[#E5E5E5] text-xs'>
{children}
</code>
),
@@ -78,7 +92,7 @@ const NoteMarkdown = memo(function NoteMarkdown({ content }: { content: string }
strong: ({ children }) => <strong className='font-semibold text-white'>{children}</strong>,
em: ({ children }) => <em className='text-[#B8B8B8]'>{children}</em>,
blockquote: ({ children }) => (
<blockquote className='m-0 border-[#F59E0B] border-l-2 pl-3 text-[#B8B8B8] italic'>
<blockquote className='!mt-0 !-mb-4 border-[#F59E0B] border-l-2 pl-3 text-[#B8B8B8] italic'>
{children}
</blockquote>
),
@@ -187,9 +201,7 @@ export const NoteBlock = memo(function NoteBlock({ id, data }: NodeProps<NoteBlo
) : showMarkdown ? (
<NoteMarkdown content={content} />
) : (
<p className='whitespace-pre-wrap text-[#E5E5E5] text-sm leading-relaxed'>
{content}
</p>
<p className='whitespace-pre-wrap text-[#E5E5E5] text-sm leading-snug'>{content}</p>
)}
</div>
</div>

View File

@@ -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<WorkflowBlockItem[]>([])
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

View File

@@ -210,9 +210,14 @@ export function Editor() {
/>
) : (
<h2
className='min-w-0 flex-1 cursor-pointer truncate pr-[8px] font-medium text-[14px] text-[var(--white)] dark:text-[var(--white)]'
className='min-w-0 flex-1 cursor-pointer select-none truncate pr-[8px] font-medium text-[14px] text-[var(--white)] dark:text-[var(--white)]'
title={title}
onDoubleClick={handleStartRename}
onMouseDown={(e) => {
if (e.detail === 2) {
e.preventDefault()
}
}}
>
{title}
</h2>

View File

@@ -11,7 +11,7 @@ const DEFAULT_CONTAINER_HEIGHT = 300
* Hook providing utilities for node position, hierarchy, and dimension calculations
*/
export function useNodeUtilities(blocks: Record<string, any>) {
const { getNodes, project } = useReactFlow()
const { getNodes } = useReactFlow()
/**
* Check if a block is a container type (loop, parallel, or subflow)

View File

@@ -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