Skip to content

Commit 0e6a131

Browse files
improvement: workflow loading, sidebar scrolling (#2322)
* improvement: workflow loading, sidebar scrolling * further optimizations * remove redundant perms calls * remove redundant api calls * use displayNodes local state to make dragging smooth even in larger workflows * improvement(logs): trace span output styling * fix(s-modal): sidebar overflow scrolling * fix(footer): guardrails link * improvement(loading): spinner * refactor(training-modal): changed file name * improvement(spinner): optimize spinner in background --------- Co-authored-by: Vikhyath Mondreti <[email protected]>
1 parent f0dc8e8 commit 0e6a131

File tree

18 files changed

+671
-739
lines changed

18 files changed

+671
-739
lines changed

apps/sim/app/(landing)/components/footer/consts.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export const FOOTER_BLOCKS = [
44
'Condition',
55
'Evaluator',
66
'Function',
7+
'Guardrails',
78
'Human In The Loop',
89
'Loop',
910
'Parallel',
@@ -30,7 +31,6 @@ export const FOOTER_TOOLS = [
3031
'GitHub',
3132
'Gmail',
3233
'Google Drive',
33-
'Guardrails',
3434
'HubSpot',
3535
'HuggingFace',
3636
'Hunter',

apps/sim/app/workspace/[workspaceId]/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export default function WorkspaceLayout({ children }: { children: React.ReactNod
1414
<ProviderModelsLoader />
1515
<GlobalCommandsProvider>
1616
<Tooltip.Provider delayDuration={600} skipDelayDuration={0}>
17-
<div className='flex h-screen w-full'>
17+
<div className='flex h-screen w-full bg-[var(--bg)]'>
1818
<WorkspacePermissionsProvider>
1919
<div className='shrink-0' suppressHydrationWarning>
2020
<Sidebar />

apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/trace-spans/trace-spans.tsx

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ function InputOutputSection({
398398
}, [data])
399399

400400
return (
401-
<div className='flex flex-col gap-[8px]'>
401+
<div className='flex min-w-0 flex-col gap-[8px] overflow-hidden'>
402402
<div
403403
className='group flex cursor-pointer items-center justify-between'
404404
onClick={() => onToggle(sectionKey)}
@@ -436,7 +436,7 @@ function InputOutputSection({
436436
<Code.Viewer
437437
code={jsonString}
438438
language='json'
439-
className='!bg-[var(--surface-3)] min-h-0 overflow-hidden rounded-[6px] border-0'
439+
className='!bg-[var(--surface-3)] min-h-0 max-w-full rounded-[6px] border-0 [word-break:break-all]'
440440
wrapText
441441
/>
442442
)}
@@ -477,7 +477,7 @@ function NestedBlockItem({
477477
const isChildrenExpanded = expandedChildren.has(spanId)
478478

479479
return (
480-
<div className='flex flex-col gap-[8px]'>
480+
<div className='flex min-w-0 flex-col gap-[8px] overflow-hidden'>
481481
<ExpandableRowHeader
482482
name={span.name}
483483
duration={span.duration || 0}
@@ -502,7 +502,7 @@ function NestedBlockItem({
502502

503503
{/* Nested children */}
504504
{hasChildren && isChildrenExpanded && (
505-
<div className='mt-[2px] flex flex-col gap-[10px] border-[var(--border)] border-l-2 pl-[10px]'>
505+
<div className='mt-[2px] flex min-w-0 flex-col gap-[10px] overflow-hidden border-[var(--border)] border-l-2 pl-[10px]'>
506506
{span.children!.map((child, childIndex) => (
507507
<NestedBlockItem
508508
key={child.id || `${spanId}-child-${childIndex}`}
@@ -617,7 +617,7 @@ function TraceSpanItem({
617617

618618
return (
619619
<>
620-
<div className='flex flex-col gap-[8px] rounded-[6px] bg-[var(--surface-1)] px-[10px] py-[8px]'>
620+
<div className='flex min-w-0 flex-col gap-[8px] overflow-hidden rounded-[6px] bg-[var(--surface-1)] px-[10px] py-[8px]'>
621621
<ExpandableRowHeader
622622
name={span.name}
623623
duration={duration}
@@ -642,7 +642,7 @@ function TraceSpanItem({
642642

643643
{/* For workflow blocks, keep children nested within the card (not as separate cards) */}
644644
{!isFirstSpan && isWorkflowBlock && inlineChildren.length > 0 && isCardExpanded && (
645-
<div className='mt-[2px] flex flex-col gap-[10px] border-[var(--border)] border-l-2 pl-[10px]'>
645+
<div className='mt-[2px] flex min-w-0 flex-col gap-[10px] overflow-hidden border-[var(--border)] border-l-2 pl-[10px]'>
646646
{inlineChildren.map((childSpan, index) => (
647647
<NestedBlockItem
648648
key={childSpan.id || `${spanId}-nested-${index}`}
@@ -662,7 +662,7 @@ function TraceSpanItem({
662662

663663
{/* For non-workflow blocks, render inline children/tool calls */}
664664
{!isFirstSpan && !isWorkflowBlock && isCardExpanded && (
665-
<div className='mt-[2px] flex flex-col gap-[10px] border-[var(--border)] border-l-2 pl-[10px]'>
665+
<div className='mt-[2px] flex min-w-0 flex-col gap-[10px] overflow-hidden border-[var(--border)] border-l-2 pl-[10px]'>
666666
{[...toolCallSpans, ...inlineChildren].map((childSpan, index) => {
667667
const childId = childSpan.id || `${spanId}-inline-${index}`
668668
const childIsError = childSpan.status === 'error'
@@ -677,7 +677,10 @@ function TraceSpanItem({
677677
)
678678

679679
return (
680-
<div key={`inline-${childId}`} className='flex flex-col gap-[8px]'>
680+
<div
681+
key={`inline-${childId}`}
682+
className='flex min-w-0 flex-col gap-[8px] overflow-hidden'
683+
>
681684
<ExpandableRowHeader
682685
name={childSpan.name}
683686
duration={childSpan.duration || 0}
@@ -727,7 +730,7 @@ function TraceSpanItem({
727730

728731
{/* Nested children */}
729732
{showChildrenInProgressBar && hasNestedChildren && isNestedExpanded && (
730-
<div className='mt-[2px] flex flex-col gap-[10px] border-[var(--border)] border-l-2 pl-[10px]'>
733+
<div className='mt-[2px] flex min-w-0 flex-col gap-[10px] overflow-hidden border-[var(--border)] border-l-2 pl-[10px]'>
731734
{childSpan.children!.map((nestedChild, nestedIndex) => (
732735
<NestedBlockItem
733736
key={nestedChild.id || `${childId}-nested-${nestedIndex}`}
@@ -809,9 +812,9 @@ export function TraceSpans({ traceSpans, totalDuration = 0 }: TraceSpansProps) {
809812
}
810813

811814
return (
812-
<div className='flex w-full flex-col gap-[6px] rounded-[6px] bg-[var(--surface-2)] px-[10px] py-[8px]'>
815+
<div className='flex w-full min-w-0 flex-col gap-[6px] overflow-hidden rounded-[6px] bg-[var(--surface-2)] px-[10px] py-[8px]'>
813816
<span className='font-medium text-[12px] text-[var(--text-tertiary)]'>Trace Span</span>
814-
<div className='flex flex-col gap-[8px]'>
817+
<div className='flex min-w-0 flex-col gap-[8px] overflow-hidden'>
815818
{normalizedSpans.map((span, index) => (
816819
<TraceSpanItem
817820
key={span.id || index}

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/training-controls/training-modal.tsx renamed to apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/training-modal/training-modal.tsx

File renamed without changes.

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-edge/workflow-edge.tsx

Lines changed: 55 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import { memo, useMemo } from 'react'
12
import { X } from 'lucide-react'
23
import { BaseEdge, EdgeLabelRenderer, type EdgeProps, getSmoothStepPath } from 'reactflow'
4+
import { useShallow } from 'zustand/react/shallow'
35
import type { EdgeDiffStatus } from '@/lib/workflows/diff/types'
46
import { useExecutionStore } from '@/stores/execution/store'
57
import { useWorkflowDiffStore } from '@/stores/workflow-diff'
@@ -9,7 +11,7 @@ interface WorkflowEdgeProps extends EdgeProps {
911
targetHandle?: string | null
1012
}
1113

12-
export const WorkflowEdge = ({
14+
const WorkflowEdgeComponent = ({
1315
id,
1416
sourceX,
1517
sourceY,
@@ -41,65 +43,64 @@ export const WorkflowEdge = ({
4143
const isInsideLoop = data?.isInsideLoop ?? false
4244
const parentLoopId = data?.parentLoopId
4345

44-
const diffAnalysis = useWorkflowDiffStore((state) => state.diffAnalysis)
45-
const isShowingDiff = useWorkflowDiffStore((state) => state.isShowingDiff)
46-
const isDiffReady = useWorkflowDiffStore((state) => state.isDiffReady)
46+
// Combined store subscription to reduce subscription overhead
47+
const { diffAnalysis, isShowingDiff, isDiffReady } = useWorkflowDiffStore(
48+
useShallow((state) => ({
49+
diffAnalysis: state.diffAnalysis,
50+
isShowingDiff: state.isShowingDiff,
51+
isDiffReady: state.isDiffReady,
52+
}))
53+
)
4754
const lastRunEdges = useExecutionStore((state) => state.lastRunEdges)
4855

49-
const generateEdgeIdentity = (
50-
sourceId: string,
51-
targetId: string,
52-
sourceHandle?: string | null,
53-
targetHandle?: string | null
54-
): string => {
55-
const actualSourceHandle = sourceHandle || 'source'
56-
const actualTargetHandle = targetHandle || 'target'
57-
return `${sourceId}-${actualSourceHandle}-${targetId}-${actualTargetHandle}`
58-
}
56+
const dataSourceHandle = (data as { sourceHandle?: string } | undefined)?.sourceHandle
57+
const isErrorEdge = (sourceHandle ?? dataSourceHandle) === 'error'
58+
const edgeRunStatus = lastRunEdges.get(id)
5959

60-
const edgeIdentifier = generateEdgeIdentity(source, target, sourceHandle, targetHandle)
60+
// Memoize diff status calculation to avoid recomputing on every render
61+
const edgeDiffStatus = useMemo((): EdgeDiffStatus => {
62+
if (data?.isDeleted) return 'deleted'
63+
if (!diffAnalysis?.edge_diff || !isDiffReady) return null
6164

62-
let edgeDiffStatus: EdgeDiffStatus = null
65+
const actualSourceHandle = sourceHandle || 'source'
66+
const actualTargetHandle = targetHandle || 'target'
67+
const edgeIdentifier = `${source}-${actualSourceHandle}-${target}-${actualTargetHandle}`
6368

64-
if (data?.isDeleted) {
65-
edgeDiffStatus = 'deleted'
66-
} else if (diffAnalysis?.edge_diff && edgeIdentifier && isDiffReady) {
6769
if (isShowingDiff) {
68-
if (diffAnalysis.edge_diff.new_edges.includes(edgeIdentifier)) {
69-
edgeDiffStatus = 'new'
70-
} else if (diffAnalysis.edge_diff.unchanged_edges.includes(edgeIdentifier)) {
71-
edgeDiffStatus = 'unchanged'
72-
}
70+
if (diffAnalysis.edge_diff.new_edges.includes(edgeIdentifier)) return 'new'
71+
if (diffAnalysis.edge_diff.unchanged_edges.includes(edgeIdentifier)) return 'unchanged'
7372
} else {
74-
if (diffAnalysis.edge_diff.deleted_edges.includes(edgeIdentifier)) {
75-
edgeDiffStatus = 'deleted'
76-
}
73+
if (diffAnalysis.edge_diff.deleted_edges.includes(edgeIdentifier)) return 'deleted'
7774
}
78-
}
79-
80-
const dataSourceHandle = (data as { sourceHandle?: string } | undefined)?.sourceHandle
81-
const isErrorEdge = (sourceHandle ?? dataSourceHandle) === 'error'
82-
83-
// Check if this edge was traversed during last execution
84-
const edgeRunStatus = lastRunEdges.get(id)
85-
86-
const getEdgeColor = () => {
87-
if (edgeDiffStatus === 'deleted') return 'var(--text-error)'
88-
if (isErrorEdge) return 'var(--text-error)'
89-
if (edgeDiffStatus === 'new') return 'var(--brand-tertiary)'
90-
// Show run path status if edge was traversed
91-
if (edgeRunStatus === 'success') return 'var(--border-success)'
92-
if (edgeRunStatus === 'error') return 'var(--text-error)'
93-
return 'var(--surface-12)'
94-
}
95-
96-
const edgeStyle = {
97-
...(style ?? {}),
98-
strokeWidth: edgeDiffStatus ? 3 : isSelected ? 2.5 : 2,
99-
stroke: getEdgeColor(),
100-
strokeDasharray: edgeDiffStatus === 'deleted' ? '10,5' : undefined,
101-
opacity: edgeDiffStatus === 'deleted' ? 0.7 : isSelected ? 0.5 : 1,
102-
}
75+
return null
76+
}, [
77+
data?.isDeleted,
78+
diffAnalysis,
79+
isDiffReady,
80+
isShowingDiff,
81+
source,
82+
target,
83+
sourceHandle,
84+
targetHandle,
85+
])
86+
87+
// Memoize edge style to prevent object recreation
88+
const edgeStyle = useMemo(() => {
89+
let color = 'var(--surface-12)'
90+
if (edgeDiffStatus === 'deleted') color = 'var(--text-error)'
91+
else if (isErrorEdge) color = 'var(--text-error)'
92+
else if (edgeDiffStatus === 'new') color = 'var(--brand-tertiary)'
93+
else if (edgeRunStatus === 'success') color = 'var(--border-success)'
94+
else if (edgeRunStatus === 'error') color = 'var(--text-error)'
95+
96+
return {
97+
...(style ?? {}),
98+
strokeWidth: edgeDiffStatus ? 3 : isSelected ? 2.5 : 2,
99+
stroke: color,
100+
strokeDasharray: edgeDiffStatus === 'deleted' ? '10,5' : undefined,
101+
opacity: edgeDiffStatus === 'deleted' ? 0.7 : isSelected ? 0.5 : 1,
102+
}
103+
}, [style, edgeDiffStatus, isSelected, isErrorEdge, edgeRunStatus])
103104

104105
return (
105106
<>
@@ -148,3 +149,5 @@ export const WorkflowEdge = ({
148149
</>
149150
)
150151
}
152+
153+
export const WorkflowEdge = memo(WorkflowEdgeComponent)

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-current-workflow.ts

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -43,26 +43,29 @@ export interface CurrentWorkflow {
4343
*/
4444
export function useCurrentWorkflow(): CurrentWorkflow {
4545
// Get normal workflow state - optimized with shallow comparison
46-
// This prevents re-renders when only subblock values change (not block structure)
4746
const normalWorkflow = useWorkflowStore(
48-
useShallow((state) => {
49-
const workflow = state.getWorkflowState()
50-
return {
51-
blocks: workflow.blocks,
52-
edges: workflow.edges,
53-
loops: workflow.loops,
54-
parallels: workflow.parallels,
55-
lastSaved: workflow.lastSaved,
56-
isDeployed: workflow.isDeployed,
57-
deployedAt: workflow.deployedAt,
58-
deploymentStatuses: workflow.deploymentStatuses,
59-
needsRedeployment: workflow.needsRedeployment,
60-
}
61-
})
47+
useShallow((state) => ({
48+
blocks: state.blocks,
49+
edges: state.edges,
50+
loops: state.loops,
51+
parallels: state.parallels,
52+
lastSaved: state.lastSaved,
53+
isDeployed: state.isDeployed,
54+
deployedAt: state.deployedAt,
55+
deploymentStatuses: state.deploymentStatuses,
56+
needsRedeployment: state.needsRedeployment,
57+
}))
6258
)
6359

64-
// Get diff state - now including isDiffReady
65-
const { isShowingDiff, isDiffReady, hasActiveDiff, baselineWorkflow } = useWorkflowDiffStore()
60+
// Get diff state - optimized with shallow comparison
61+
const { isShowingDiff, isDiffReady, hasActiveDiff, baselineWorkflow } = useWorkflowDiffStore(
62+
useShallow((state) => ({
63+
isShowingDiff: state.isShowingDiff,
64+
isDiffReady: state.isDiffReady,
65+
hasActiveDiff: state.hasActiveDiff,
66+
baselineWorkflow: state.baselineWorkflow,
67+
}))
68+
)
6669

6770
// Create the abstracted interface - optimized to prevent unnecessary re-renders
6871
const currentWorkflow = useMemo((): CurrentWorkflow => {

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { ErrorBoundary } from '@/app/workspace/[workspaceId]/w/[workflowId]/comp
22

33
export default function WorkflowLayout({ children }: { children: React.ReactNode }) {
44
return (
5-
<main className='flex h-full flex-1 flex-col overflow-hidden bg-muted/40'>
5+
<main className='flex h-full flex-1 flex-col overflow-hidden bg-[var(--bg)]'>
66
<ErrorBoundary>{children}</ErrorBoundary>
77
</main>
88
)

0 commit comments

Comments
 (0)