diff --git a/apps/sim/app/api/workflows/[id]/route.ts b/apps/sim/app/api/workflows/[id]/route.ts
index 0172f6d68..802e4e447 100644
--- a/apps/sim/app/api/workflows/[id]/route.ts
+++ b/apps/sim/app/api/workflows/[id]/route.ts
@@ -133,9 +133,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
const finalWorkflowData = {
...workflowData,
state: {
- // Default values for expected properties
deploymentStatuses: {},
- // Data from normalized tables
blocks: normalizedData.blocks,
edges: normalizedData.edges,
loops: normalizedData.loops,
@@ -143,8 +141,11 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
lastSaved: Date.now(),
isDeployed: workflowData.isDeployed || false,
deployedAt: workflowData.deployedAt,
+ metadata: {
+ name: workflowData.name,
+ description: workflowData.description,
+ },
},
- // Include workflow variables
variables: workflowData.variables || {},
}
@@ -166,6 +167,10 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
lastSaved: Date.now(),
isDeployed: workflowData.isDeployed || false,
deployedAt: workflowData.deployedAt,
+ metadata: {
+ name: workflowData.name,
+ description: workflowData.description,
+ },
},
variables: workflowData.variables || {},
}
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-editor.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-editor.tsx
index 8e4839ef9..d5e5aba12 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-editor.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-editor.tsx
@@ -4,11 +4,14 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import {
ArrowDown,
ArrowUp,
+ Check,
ChevronDown as ChevronDownIcon,
ChevronUp,
+ Clipboard,
ExternalLink,
Maximize2,
RepeatIcon,
+ Search,
SplitIcon,
X,
} from 'lucide-react'
@@ -813,6 +816,13 @@ function PreviewEditorContent({
} = useContextMenu()
const [contextMenuData, setContextMenuData] = useState({ content: '', copyOnly: false })
+ const [copiedSection, setCopiedSection] = useState<'input' | 'output' | null>(null)
+
+ const handleCopySection = useCallback((content: string, section: 'input' | 'output') => {
+ navigator.clipboard.writeText(content)
+ setCopiedSection(section)
+ setTimeout(() => setCopiedSection(null), 1500)
+ }, [])
const openContextMenu = useCallback(
(e: React.MouseEvent, content: string, copyOnly: boolean) => {
@@ -862,9 +872,6 @@ function PreviewEditorContent({
}
}, [contextMenuData.content])
- /**
- * Handles mouse down event on the resize handle to initiate resizing
- */
const handleConnectionsResizeMouseDown = useCallback(
(e: React.MouseEvent) => {
setIsResizing(true)
@@ -874,18 +881,12 @@ function PreviewEditorContent({
[connectionsHeight]
)
- /**
- * Toggle connections collapsed state
- */
const toggleConnectionsCollapsed = useCallback(() => {
setConnectionsHeight((prev) =>
prev <= MIN_CONNECTIONS_HEIGHT ? DEFAULT_CONNECTIONS_HEIGHT : MIN_CONNECTIONS_HEIGHT
)
}, [])
- /**
- * Sets up resize event listeners during resize operations
- */
useEffect(() => {
if (!isResizing) return
@@ -1205,7 +1206,11 @@ function PreviewEditorContent({
}
emptyMessage='No input data'
>
-
+
+ {/* Action buttons overlay */}
+ {!isSearchActive && (
+
+
+
+
+
+
+ {copiedSection === 'input' ? 'Copied' : 'Copy'}
+
+
+
+
+
+
+ Search
+
+
+ )}
)}
@@ -1231,7 +1279,7 @@ function PreviewEditorContent({
emptyMessage='No output data'
isError={executionData.status === 'error'}
>
-
+
+ {/* Action buttons overlay */}
+ {!isSearchActive && (
+
+
+
+
+
+
+ {copiedSection === 'output' ? 'Copied' : 'Copy'}
+
+
+
+
+
+
+ Search
+
+
+ )}
)}
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/preview/preview.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/preview/preview.tsx
index da6bafe03..47df78e53 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/components/preview/preview.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/components/preview/preview.tsx
@@ -35,6 +35,7 @@ interface WorkflowStackEntry {
workflowState: WorkflowState
traceSpans: TraceSpan[]
blockExecutions: Record
+ workflowName: string
}
/**
@@ -144,7 +145,6 @@ export function Preview({
initialSelectedBlockId,
autoSelectLeftmost = true,
}: PreviewProps) {
- /** Initialize pinnedBlockId synchronously to ensure sidebar is present from first render */
const [pinnedBlockId, setPinnedBlockId] = useState(() => {
if (initialSelectedBlockId) return initialSelectedBlockId
if (autoSelectLeftmost) {
@@ -153,17 +153,14 @@ export function Preview({
return null
})
- /** Stack for nested workflow navigation. Empty means we're at the root level. */
const [workflowStack, setWorkflowStack] = useState([])
- /** Block executions for the root level */
const rootBlockExecutions = useMemo(() => {
if (providedBlockExecutions) return providedBlockExecutions
if (!rootTraceSpans || !Array.isArray(rootTraceSpans)) return {}
return buildBlockExecutions(rootTraceSpans)
}, [providedBlockExecutions, rootTraceSpans])
- /** Current block executions - either from stack or root */
const blockExecutions = useMemo(() => {
if (workflowStack.length > 0) {
return workflowStack[workflowStack.length - 1].blockExecutions
@@ -171,7 +168,6 @@ export function Preview({
return rootBlockExecutions
}, [workflowStack, rootBlockExecutions])
- /** Current workflow state - either from stack or root */
const workflowState = useMemo(() => {
if (workflowStack.length > 0) {
return workflowStack[workflowStack.length - 1].workflowState
@@ -179,41 +175,39 @@ export function Preview({
return rootWorkflowState
}, [workflowStack, rootWorkflowState])
- /** Whether we're in execution mode (have trace spans/block executions) */
const isExecutionMode = useMemo(() => {
return Object.keys(blockExecutions).length > 0
}, [blockExecutions])
- /** Handler to drill down into a nested workflow block */
const handleDrillDown = useCallback(
(blockId: string, childWorkflowState: WorkflowState) => {
const blockExecution = blockExecutions[blockId]
const childTraceSpans = extractChildTraceSpans(blockExecution)
const childBlockExecutions = buildBlockExecutions(childTraceSpans)
+ const workflowName = childWorkflowState.metadata?.name || 'Nested Workflow'
+
setWorkflowStack((prev) => [
...prev,
{
workflowState: childWorkflowState,
traceSpans: childTraceSpans,
blockExecutions: childBlockExecutions,
+ workflowName,
},
])
- /** Set pinned block synchronously to avoid double fitView from sidebar resize */
const leftmostId = getLeftmostBlockId(childWorkflowState)
setPinnedBlockId(leftmostId)
},
[blockExecutions]
)
- /** Handler to go back up the stack */
const handleGoBack = useCallback(() => {
setWorkflowStack((prev) => prev.slice(0, -1))
setPinnedBlockId(null)
}, [])
- /** Handlers for node interactions - memoized to prevent unnecessary re-renders */
const handleNodeClick = useCallback((blockId: string) => {
setPinnedBlockId(blockId)
}, [])
@@ -232,6 +226,8 @@ export function Preview({
const isNested = workflowStack.length > 0
+ const currentWorkflowName = isNested ? workflowStack[workflowStack.length - 1].workflowName : null
+
return (
{isNested && (
-
+
Go back to parent workflow
+ {currentWorkflowName && (
+
+
+ {currentWorkflowName}
+
+
+ )}
)}