From b537f9f534bbaae6352326e944f6a7b2f391b499 Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 29 Jun 2026 13:24:19 -0700 Subject: [PATCH 01/30] chore(workflow-renderer): declare @sim/emcn dep + wire the package into docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the missing @sim/emcn peer/dev dependency to @sim/workflow-renderer (it imports @sim/emcn in every View but resolved only via workspace hoisting). Wires apps/docs to consume @sim/workflow-renderer (dependency, transpilePackages, Tailwind @source) and adds remark-breaks (pulled transitively via the barrel's NoteBlockView export) — mirroring the @sim/emcn integration. Foundation for migrating the docs workflow-preview fork onto the shared Views. Build resolves the package/@source/remark-breaks cleanly. --- apps/docs/app/global.css | 1 + apps/docs/next.config.ts | 2 +- apps/docs/package.json | 2 ++ bun.lock | 4 ++++ packages/workflow-renderer/package.json | 2 ++ 5 files changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/docs/app/global.css b/apps/docs/app/global.css index 91d2433c7c6..e2b162e0f56 100644 --- a/apps/docs/app/global.css +++ b/apps/docs/app/global.css @@ -3,6 +3,7 @@ @import "fumadocs-ui/css/preset.css"; @import "fumadocs-openapi/css/preset.css"; @source "../../../packages/emcn/src"; +@source "../../../packages/workflow-renderer/src"; /* Prevent overscroll bounce effect on the page */ html, diff --git a/apps/docs/next.config.ts b/apps/docs/next.config.ts index 1993869f285..a322697fbd4 100644 --- a/apps/docs/next.config.ts +++ b/apps/docs/next.config.ts @@ -5,7 +5,7 @@ const withMDX = createMDX() const config: NextConfig = { reactStrictMode: true, - transpilePackages: ['@sim/emcn'], + transpilePackages: ['@sim/emcn', '@sim/workflow-renderer'], images: { unoptimized: true, }, diff --git a/apps/docs/package.json b/apps/docs/package.json index e1bf0b4ebb6..9f8d622f6cc 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -19,6 +19,7 @@ "@ai-sdk/react": "2.0.205", "@sim/db": "workspace:*", "@sim/emcn": "workspace:*", + "@sim/workflow-renderer": "workspace:*", "@vercel/og": "^0.6.5", "ai": "5.0.203", "class-variance-authority": "^0.7.1", @@ -34,6 +35,7 @@ "postgres": "^3.4.5", "react": "19.2.4", "react-dom": "19.2.4", + "remark-breaks": "^4.0.0", "shiki": "4.0.0", "streamdown": "2.5.0", "tailwind-merge": "^3.0.2", diff --git a/bun.lock b/bun.lock index 1d0973d9c00..a3d604a4fe1 100644 --- a/bun.lock +++ b/bun.lock @@ -26,6 +26,7 @@ "@ai-sdk/react": "2.0.205", "@sim/db": "workspace:*", "@sim/emcn": "workspace:*", + "@sim/workflow-renderer": "workspace:*", "@vercel/og": "^0.6.5", "ai": "5.0.203", "class-variance-authority": "^0.7.1", @@ -43,6 +44,7 @@ "react": "19.2.4", "react-dom": "19.2.4", "reactflow": "^11.11.4", + "remark-breaks": "^4.0.0", "shiki": "4.0.0", "streamdown": "2.5.0", "tailwind-merge": "^3.0.2", @@ -532,6 +534,7 @@ "name": "@sim/workflow-renderer", "version": "0.1.0", "devDependencies": { + "@sim/emcn": "workspace:*", "@sim/tsconfig": "workspace:*", "@types/react": "^19", "lucide-react": "^0.479.0", @@ -542,6 +545,7 @@ "typescript": "^5.7.3", }, "peerDependencies": { + "@sim/emcn": "workspace:*", "lucide-react": ">=0.479.0", "react": "^19", "reactflow": "^11.11.4", diff --git a/packages/workflow-renderer/package.json b/packages/workflow-renderer/package.json index 65274c95eb0..64f08ccb7f7 100644 --- a/packages/workflow-renderer/package.json +++ b/packages/workflow-renderer/package.json @@ -26,6 +26,7 @@ "format:check": "biome format ." }, "peerDependencies": { + "@sim/emcn": "workspace:*", "lucide-react": ">=0.479.0", "react": "^19", "reactflow": "^11.11.4", @@ -33,6 +34,7 @@ "streamdown": ">=2.5.0" }, "devDependencies": { + "@sim/emcn": "workspace:*", "@sim/tsconfig": "workspace:*", "@types/react": "^19", "lucide-react": "^0.479.0", From 0ba9203e8249aa13e54ec1aad89e844d5fd195e3 Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 29 Jun 2026 13:32:00 -0700 Subject: [PATCH 02/30] feat(docs): render loop/parallel containers with the shared SubflowNodeView MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the forked PreviewContainerNode with a thin DocsContainerNode that maps the static preview data to SubflowNodeView's read-only (isPreview) props — no stores or hooks. Adds the block size to the preview node data so the view can size itself, and corrects the parallel example's start-edge handle id to 'parallel-start-source' (the view derives the handle id from kind). Deletes preview-container-node.tsx. Container colors/icons are now owned by the shared view (loop=blue, parallel=yellow). --- .../workflow-preview/docs-container-node.tsx | 42 +++++++++ .../components/workflow-preview/examples.ts | 7 +- .../preview-container-node.tsx | 94 ------------------- .../workflow-preview/workflow-data.ts | 1 + .../workflow-preview/workflow-preview.tsx | 4 +- 5 files changed, 51 insertions(+), 97 deletions(-) create mode 100644 apps/docs/components/workflow-preview/docs-container-node.tsx delete mode 100644 apps/docs/components/workflow-preview/preview-container-node.tsx diff --git a/apps/docs/components/workflow-preview/docs-container-node.tsx b/apps/docs/components/workflow-preview/docs-container-node.tsx new file mode 100644 index 00000000000..85d098979d3 --- /dev/null +++ b/apps/docs/components/workflow-preview/docs-container-node.tsx @@ -0,0 +1,42 @@ +'use client' + +import { memo } from 'react' +import { type SubflowNodeData, SubflowNodeView } from '@sim/workflow-renderer' +import type { NodeProps } from 'reactflow' + +interface DocsContainerData { + name: string + blockType: string + size?: { width: number; height: number } +} + +/** + * Docs adapter for loop/parallel container blocks: maps the static preview data + * to {@link SubflowNodeView}'s read-only `isPreview` shape. Carries no stores, + * hooks, or queries — it only reshapes data into View props. + */ +export const DocsContainerNode = memo(function DocsContainerNode({ + id, + data, +}: NodeProps) { + const subflowData: SubflowNodeData = { + kind: data.blockType === 'parallel' ? 'parallel' : 'loop', + name: data.name, + width: data.size?.width, + height: data.size?.height, + isPreview: true, + } + + return ( + {}} + /> + ) +}) diff --git a/apps/docs/components/workflow-preview/examples.ts b/apps/docs/components/workflow-preview/examples.ts index 3691e72424b..98202e02145 100644 --- a/apps/docs/components/workflow-preview/examples.ts +++ b/apps/docs/components/workflow-preview/examples.ts @@ -1581,7 +1581,12 @@ export const PARALLEL_WORKFLOW: PreviewWorkflow = { ], edges: [ { id: 'start-parallel', source: 'start', target: 'parallel' }, - { id: 'parallel-call', source: 'parallel', target: 'call', sourceHandle: 'loop-start-source' }, + { + id: 'parallel-call', + source: 'parallel', + target: 'call', + sourceHandle: 'parallel-start-source', + }, { id: 'parallel-aggregate', source: 'parallel', target: 'aggregate' }, ], } diff --git a/apps/docs/components/workflow-preview/preview-container-node.tsx b/apps/docs/components/workflow-preview/preview-container-node.tsx deleted file mode 100644 index 332a6e1e5ed..00000000000 --- a/apps/docs/components/workflow-preview/preview-container-node.tsx +++ /dev/null @@ -1,94 +0,0 @@ -'use client' - -import { memo } from 'react' -import { Handle, type NodeProps, Position } from 'reactflow' -import { BLOCK_ICONS } from '@/components/workflow-preview/block-icons' - -interface PreviewContainerData { - name: string - blockType: string - bgColor: string - rows: Array<{ title: string; value: string }> - hideTargetHandle?: boolean - hideSourceHandle?: boolean -} - -const HANDLE_BASE = '!z-[10] !border-none !bg-[var(--wp-edge)]' -const HANDLE_LEFT = `${HANDLE_BASE} !left-[-8px] !h-5 !w-[7px] !rounded-r-none !rounded-l-[2px]` -const HANDLE_RIGHT = `${HANDLE_BASE} !right-[-8px] !h-5 !w-[7px] !rounded-l-none !rounded-r-[2px]` -const HANDLE_START = `${HANDLE_BASE} !right-[-8px] !h-4 !w-[7px] !rounded-l-none !rounded-r-[2px]` - -/** Legible icon class for the header swatch — dark glyph on a light bg, white on a dark one. */ -function iconClassFor(bg: string): string { - const hex = bg.replace('#', '') - if (hex.length !== 6) return 'text-white' - const r = Number.parseInt(hex.slice(0, 2), 16) - const g = Number.parseInt(hex.slice(2, 4), 16) - const b = Number.parseInt(hex.slice(4, 6), 16) - const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255 - return luminance > 0.6 ? 'text-[#1c1c1c]' : 'text-white' -} - -/** - * Container node for Loop / Parallel blocks, mirroring the app's subflow node: - * a solid-bordered box with a header (icon + name), an internal "Start" pill - * whose right handle feeds the first nested block, and target/source handles at - * the vertical center. React Flow positions the child blocks inside via parentNode. - */ -export const PreviewContainerNode = memo(function PreviewContainerNode({ - data, -}: NodeProps) { - const { name, blockType, bgColor, hideTargetHandle, hideSourceHandle } = data - const Icon = BLOCK_ICONS[blockType] - - return ( -
- {!hideTargetHandle && ( - - )} - -
-
- {Icon && } -
- {name} -
- -
- Start - -
- - {!hideSourceHandle && ( - - )} -
- ) -}) diff --git a/apps/docs/components/workflow-preview/workflow-data.ts b/apps/docs/components/workflow-preview/workflow-data.ts index da2b3543401..ad3b0d44cba 100644 --- a/apps/docs/components/workflow-preview/workflow-data.ts +++ b/apps/docs/components/workflow-preview/workflow-data.ts @@ -105,6 +105,7 @@ export function toReactFlowElements( tools: block.tools, hideTargetHandle: block.hideTargetHandle, hideSourceHandle: block.hideSourceHandle, + size: block.size, index, animate, isHighlighted: highlightBlock === block.id || selectedBlock === block.id, diff --git a/apps/docs/components/workflow-preview/workflow-preview.tsx b/apps/docs/components/workflow-preview/workflow-preview.tsx index e3363a533b2..b5c4a222471 100644 --- a/apps/docs/components/workflow-preview/workflow-preview.tsx +++ b/apps/docs/components/workflow-preview/workflow-preview.tsx @@ -20,8 +20,8 @@ import ReactFlow, { } from 'reactflow' import 'reactflow/dist/style.css' import { BlockInspector } from '@/components/workflow-preview/block-inspector' +import { DocsContainerNode } from '@/components/workflow-preview/docs-container-node' import { PreviewBlockNode } from '@/components/workflow-preview/preview-block-node' -import { PreviewContainerNode } from '@/components/workflow-preview/preview-container-node' import { EASE_OUT, type PreviewBlock, @@ -90,7 +90,7 @@ function PreviewEdge({ const NODE_TYPES: NodeTypes = { previewBlock: PreviewBlockNode, - previewContainer: PreviewContainerNode, + previewContainer: DocsContainerNode, } const EDGE_TYPES: EdgeTypes = { previewEdge: PreviewEdge } const PRO_OPTIONS = { hideAttribution: true } From 76b44bf3187041a8c5d35a3a4af8e4a9566d0cd4 Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 29 Jun 2026 13:45:54 -0700 Subject: [PATCH 03/30] feat(docs): render block nodes with the shared WorkflowBlockView MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the forked PreviewBlockNode with a thin DocsBlockNode that maps the static preview data to WorkflowBlockView's props — store-free, builds the subblock rows (condition/router Context+routes/default + tools + error) via SubBlockRowView, strips branch-id prefixes so the view's regenerated handle ids match, remaps router->router_v2, and keeps the framer-motion dim/stagger wrapper. Promotes resolveIcon into block-icons.tsx, adds the --workflow-edge token to docs global.css, deletes preview-block-node.tsx. The canvas diagrams now render with the real editor's view. --- apps/docs/app/global.css | 2 + .../workflow-preview/block-icons.tsx | 12 +- .../workflow-preview/docs-block-node.tsx | 174 ++++++++++++++ .../components/workflow-preview/examples.ts | 49 ---- .../workflow-preview/preview-block-node.tsx | 215 ------------------ .../workflow-preview/workflow-data.ts | 5 - .../workflow-preview/workflow-preview.tsx | 4 +- 7 files changed, 189 insertions(+), 272 deletions(-) create mode 100644 apps/docs/components/workflow-preview/docs-block-node.tsx delete mode 100644 apps/docs/components/workflow-preview/preview-block-node.tsx diff --git a/apps/docs/app/global.css b/apps/docs/app/global.css index e2b162e0f56..993bb832a54 100644 --- a/apps/docs/app/global.css +++ b/apps/docs/app/global.css @@ -43,6 +43,7 @@ body { */ :root { --bg: #fefefe; + --workflow-edge: #e0e0e0; --surface-1: #fbfbfb; --surface-2: #ffffff; --surface-3: #f7f7f7; @@ -92,6 +93,7 @@ body { .dark { --bg: #1b1b1b; + --workflow-edge: #454545; --surface-1: #1e1e1e; --surface-2: #232323; --surface-3: #242424; diff --git a/apps/docs/components/workflow-preview/block-icons.tsx b/apps/docs/components/workflow-preview/block-icons.tsx index 94bbfbd0352..b759375a56b 100644 --- a/apps/docs/components/workflow-preview/block-icons.tsx +++ b/apps/docs/components/workflow-preview/block-icons.tsx @@ -1,4 +1,4 @@ -import type { SVGProps } from 'react' +import type { ComponentType, SVGProps } from 'react' import { Clock, Database, Layers, Repeat, Table } from 'lucide-react' import { ApiIcon, @@ -16,6 +16,7 @@ import { WebhookIcon, WorkflowIcon, } from '@/components/icons' +import { blockTypeToIconMap } from '@/components/ui/icon-mapping' /** * The two Sim-specific block glyphs we need, ported verbatim from @@ -101,3 +102,12 @@ export const BLOCK_ICONS: Record | null { + return BLOCK_ICONS[type] ?? blockTypeToIconMap[type] ?? null +} diff --git a/apps/docs/components/workflow-preview/docs-block-node.tsx b/apps/docs/components/workflow-preview/docs-block-node.tsx new file mode 100644 index 00000000000..de2df68031a --- /dev/null +++ b/apps/docs/components/workflow-preview/docs-block-node.tsx @@ -0,0 +1,174 @@ +'use client' + +import { type ComponentType, memo } from 'react' +import { SubBlockRowView, WorkflowBlockView } from '@sim/workflow-renderer' +import { m } from 'framer-motion' +import type { NodeProps } from 'reactflow' +import { resolveIcon } from '@/components/workflow-preview/block-icons' +import { + BLOCK_STAGGER, + EASE_OUT, + type PreviewTool, +} from '@/components/workflow-preview/workflow-data' + +/** Renders the colored square with no glyph when a block type has no registered icon. */ +const EMPTY_ICON: ComponentType<{ className?: string }> = () => null + +const RING_STYLES = 'ring-2 ring-[var(--brand-secondary)]' + +interface DocsBlockData { + name: string + blockType: string + bgColor: string + rows: Array<{ title: string; value: string }> + branches?: Array<{ id: string; label: string; value?: string }> + tools?: PreviewTool[] + hideTargetHandle?: boolean + index?: number + animate?: boolean + isHighlighted?: boolean + isDimmed?: boolean +} + +/** + * Docs adapter for workflow block nodes: maps the static preview data to the + * shared {@link WorkflowBlockView}'s props. Carries no stores, hooks, or + * queries — it only reshapes data into View props and wraps the result in the + * dim/stagger motion used by the rest of the diagram (the parent + * `WorkflowPreview` provides the `LazyMotion` feature set). The block's ring is + * driven by `hasRing`/`ringStyles` inside the View. + */ +export const DocsBlockNode = memo(function DocsBlockNode({ id, data }: NodeProps) { + const { + name, + blockType, + bgColor, + rows: dataRows, + branches, + tools, + hideTargetHandle = false, + index = 0, + animate = false, + isHighlighted = false, + isDimmed = false, + } = data + + /** The View gates router handle topology on `type === 'router_v2'`. */ + const type = blockType === 'router' ? 'router_v2' : blockType + + const Icon = resolveIcon(blockType) ?? EMPTY_ICON + const delay = animate ? index * BLOCK_STAGGER : 0 + + const hasBranches = Boolean(branches && branches.length > 0) + const hasTools = Boolean(tools && tools.length > 0) + + /** The View renders the default target/source/error handles (and the error row) for non-trigger blocks; mirror that gate. */ + const shouldShowDefaultHandles = !hideTargetHandle + const hasContentBelowHeader = + dataRows.length > 0 || hasBranches || hasTools || shouldShowDefaultHandles + + /** + * Strip the app's `condition-`/`router-` handle prefixes — the View + * regenerates them, so passing them through would double-prefix the handle id. + */ + const conditionRows = + type === 'condition' + ? (branches ?? []).map((branch) => ({ + id: branch.id.replace(/^condition-/, ''), + title: branch.label, + value: branch.value ?? '', + })) + : [] + const routerRows = + type === 'router_v2' + ? (branches ?? []).map((branch) => ({ + id: branch.id.replace(/^router-/, ''), + value: branch.value ?? '', + })) + : [] + + /** + * Replicate the editor's row order so the View's absolute condition/router + * handle offsets line up: branch rows first (router adds a leading Context + * row); other blocks render their subblock rows, a Tools row, then the error + * row when default handles are shown. + */ + const rows = + type === 'condition' ? ( + (branches ?? []).map((branch) => ( + + )) + ) : type === 'router_v2' ? ( + <> + + {(branches ?? []).map((branch, routeIndex) => ( + + ))} + + ) : ( + <> + {dataRows.map((row) => ( + + ))} + {hasTools && ( + tool.name).join(', ')} + /> + )} + {shouldShowDefaultHandles && } + + ) + + return ( + + false} + isWorkflowSelector={false} + childWorkflowId={undefined} + childIsDeployed={null} + childNeedsRedeploy={false} + isDeploying={false} + canAdmin={false} + onDeployChild={() => {}} + shouldShowScheduleBadge={false} + scheduleIsDisabled={false} + onReactivateSchedule={() => {}} + showWebhookIndicator={false} + webhookProvider={undefined} + webhookPath={undefined} + webhookProviderName={undefined} + isWebhookConfigured={false} + isWebhookDisabled={false} + webhookId={undefined} + onReactivateWebhook={() => {}} + onSelect={() => {}} + rows={rows} + /> + + ) +}) diff --git a/apps/docs/components/workflow-preview/examples.ts b/apps/docs/components/workflow-preview/examples.ts index 98202e02145..a1648033684 100644 --- a/apps/docs/components/workflow-preview/examples.ts +++ b/apps/docs/components/workflow-preview/examples.ts @@ -24,7 +24,6 @@ export const CLASSIFY_WORKFLOW: PreviewWorkflow = { type: 'agent', bgColor: '#33C482', position: { x: 340, y: 0 }, - hideSourceHandle: true, rows: [ { title: 'Model', value: 'claude-sonnet-4-6' }, { title: 'Messages', value: 'Classify ' }, @@ -69,7 +68,6 @@ export const CLASSIFY_REPLY_WORKFLOW: PreviewWorkflow = { type: 'agent', bgColor: '#33C482', position: { x: 660, y: 0 }, - hideSourceHandle: true, rows: [ { title: 'Model', value: 'claude-sonnet-4-6' }, { title: 'Messages', value: 'Draft a reply for ' }, @@ -116,7 +114,6 @@ export const SUPPORT_KB_WORKFLOW: PreviewWorkflow = { type: 'agent', bgColor: '#33C482', position: { x: 660, y: 0 }, - hideSourceHandle: true, rows: [ { title: 'Model', value: 'claude-sonnet-4-6' }, { title: 'Messages', value: 'Answer from ' }, @@ -168,7 +165,6 @@ export const TABLE_ENRICH_WORKFLOW: PreviewWorkflow = { type: 'table', bgColor: '#10B981', position: { x: 660, y: 0 }, - hideSourceHandle: true, rows: [ { title: 'Operation', value: 'Update Rows' }, { title: 'Set', value: 'status = qualified' }, @@ -215,7 +211,6 @@ export const API_FETCH_WORKFLOW: PreviewWorkflow = { type: 'agent', bgColor: '#33C482', position: { x: 660, y: 0 }, - hideSourceHandle: true, rows: [ { title: 'Model', value: 'claude-sonnet-4-6' }, { title: 'Messages', value: 'Summarize ' }, @@ -263,7 +258,6 @@ export const CONDITION_ROUTE_WORKFLOW: PreviewWorkflow = { type: 'agent', bgColor: '#33C482', position: { x: 700, y: 0 }, - hideSourceHandle: true, rows: [{ title: 'Messages', value: 'Escalate this ticket' }], }, { @@ -272,7 +266,6 @@ export const CONDITION_ROUTE_WORKFLOW: PreviewWorkflow = { type: 'agent', bgColor: '#33C482', position: { x: 700, y: 130 }, - hideSourceHandle: true, rows: [{ title: 'Messages', value: 'Draft a standard reply' }], }, ], @@ -320,7 +313,6 @@ export const CONDITION_MODERATE_WORKFLOW: PreviewWorkflow = { type: 'function', bgColor: '#FF402F', position: { x: 700, y: 0 }, - hideSourceHandle: true, rows: [{ title: 'Code', value: "throw 'blocked'" }], }, { @@ -329,7 +321,6 @@ export const CONDITION_MODERATE_WORKFLOW: PreviewWorkflow = { type: 'api', bgColor: '#2F55FF', position: { x: 700, y: 130 }, - hideSourceHandle: true, rows: [{ title: 'Method', value: 'POST' }], }, ], @@ -377,7 +368,6 @@ export const CONDITION_ONBOARD_WORKFLOW: PreviewWorkflow = { type: 'agent', bgColor: '#33C482', position: { x: 700, y: 0 }, - hideSourceHandle: true, rows: [{ title: 'Messages', value: 'Walk through SSO and SCIM' }], }, { @@ -386,7 +376,6 @@ export const CONDITION_ONBOARD_WORKFLOW: PreviewWorkflow = { type: 'agent', bgColor: '#33C482', position: { x: 700, y: 130 }, - hideSourceHandle: true, rows: [{ title: 'Messages', value: 'Send the 2-minute setup' }], }, ], @@ -433,7 +422,6 @@ export const FUNCTION_RESHAPE_WORKFLOW: PreviewWorkflow = { type: 'function', bgColor: '#FF402F', position: { x: 640, y: 0 }, - hideSourceHandle: true, rows: [ { title: 'Language', value: 'JavaScript' }, { title: 'Code', value: 'return .profile' }, @@ -477,7 +465,6 @@ export const FUNCTION_VALIDATE_WORKFLOW: PreviewWorkflow = { type: 'api', bgColor: '#2F55FF', position: { x: 640, y: 0 }, - hideSourceHandle: true, rows: [ { title: 'Method', value: 'POST' }, { title: 'Body', value: '' }, @@ -526,7 +513,6 @@ export const ROUTER_TRIAGE_WORKFLOW: PreviewWorkflow = { type: 'agent', bgColor: '#33C482', position: { x: 700, y: 0 }, - hideSourceHandle: true, rows: [{ title: 'Messages', value: 'Answer the pricing question' }], }, { @@ -535,7 +521,6 @@ export const ROUTER_TRIAGE_WORKFLOW: PreviewWorkflow = { type: 'agent', bgColor: '#33C482', position: { x: 700, y: 95 }, - hideSourceHandle: true, rows: [{ title: 'Messages', value: 'Help with the issue' }], }, { @@ -544,7 +529,6 @@ export const ROUTER_TRIAGE_WORKFLOW: PreviewWorkflow = { type: 'agent', bgColor: '#33C482', position: { x: 700, y: 190 }, - hideSourceHandle: true, rows: [{ title: 'Messages', value: 'Resolve the billing question' }], }, ], @@ -584,7 +568,6 @@ export const RESPONSE_API_WORKFLOW: PreviewWorkflow = { type: 'response', bgColor: '#2F55FF', position: { x: 640, y: 0 }, - hideSourceHandle: true, rows: [ { title: 'Data', value: '{ "answer": }' }, { title: 'Status', value: '200' }, @@ -629,7 +612,6 @@ export const ROUTER_CLASSIFY_WORKFLOW: PreviewWorkflow = { type: 'workflow', bgColor: '#6366F1', position: { x: 700, y: 0 }, - hideSourceHandle: true, rows: [{ title: 'Workflow', value: 'product-intake' }], }, { @@ -638,7 +620,6 @@ export const ROUTER_CLASSIFY_WORKFLOW: PreviewWorkflow = { type: 'workflow', bgColor: '#6366F1', position: { x: 700, y: 110 }, - hideSourceHandle: true, rows: [{ title: 'Workflow', value: 'bug-triage' }], }, ], @@ -681,7 +662,6 @@ export const ROUTER_LEAD_WORKFLOW: PreviewWorkflow = { type: 'agent', bgColor: '#33C482', position: { x: 700, y: 0 }, - hideSourceHandle: true, rows: [{ title: 'Messages', value: 'Book a sales call' }], }, { @@ -690,7 +670,6 @@ export const ROUTER_LEAD_WORKFLOW: PreviewWorkflow = { type: 'workflow', bgColor: '#6366F1', position: { x: 700, y: 110 }, - hideSourceHandle: true, rows: [{ title: 'Workflow', value: 'onboarding' }], }, ], @@ -739,7 +718,6 @@ export const RESPONSE_WEBHOOK_WORKFLOW: PreviewWorkflow = { type: 'response', bgColor: '#2F55FF', position: { x: 640, y: 0 }, - hideSourceHandle: true, rows: [ { title: 'Data', value: '{ "received": true }' }, { title: 'Status', value: '200' }, @@ -784,7 +762,6 @@ export const RESPONSE_ERROR_WORKFLOW: PreviewWorkflow = { type: 'response', bgColor: '#2F55FF', position: { x: 700, y: 0 }, - hideSourceHandle: true, rows: [{ title: 'Status', value: '200' }], }, { @@ -793,7 +770,6 @@ export const RESPONSE_ERROR_WORKFLOW: PreviewWorkflow = { type: 'response', bgColor: '#2F55FF', position: { x: 700, y: 130 }, - hideSourceHandle: true, rows: [{ title: 'Status', value: '400' }], }, ], @@ -832,7 +808,6 @@ export const VARIABLES_RETRY_WORKFLOW: PreviewWorkflow = { type: 'condition', bgColor: '#FF752F', position: { x: 640, y: 0 }, - hideSourceHandle: true, rows: [], branches: [ { id: 'condition-if', label: 'If', value: ' < 3' }, @@ -874,7 +849,6 @@ export const VARIABLES_CONFIG_WORKFLOW: PreviewWorkflow = { type: 'agent', bgColor: '#33C482', position: { x: 640, y: 0 }, - hideSourceHandle: true, rows: [{ title: 'Messages', value: 'Personalize for ' }], }, ], @@ -915,7 +889,6 @@ export const WAIT_RATELIMIT_WORKFLOW: PreviewWorkflow = { type: 'api', bgColor: '#2F55FF', position: { x: 640, y: 0 }, - hideSourceHandle: true, rows: [{ title: 'Method', value: 'GET' }], }, ], @@ -956,7 +929,6 @@ export const WAIT_FOLLOWUP_WORKFLOW: PreviewWorkflow = { type: 'agent', bgColor: '#33C482', position: { x: 640, y: 0 }, - hideSourceHandle: true, rows: [{ title: 'Messages', value: 'Send a check-in' }], }, ], @@ -997,7 +969,6 @@ export const EVALUATOR_GATE_WORKFLOW: PreviewWorkflow = { type: 'condition', bgColor: '#FF752F', position: { x: 640, y: 0 }, - hideSourceHandle: true, rows: [], branches: [ { id: 'condition-if', label: 'If', value: ' >= 4' }, @@ -1034,7 +1005,6 @@ export const CREDENTIAL_SHARE_WORKFLOW: PreviewWorkflow = { type: 'gmail', bgColor: '#FFFFFF', position: { x: 380, y: 0 }, - hideSourceHandle: true, rows: [{ title: 'Account', value: '' }], }, { @@ -1043,7 +1013,6 @@ export const CREDENTIAL_SHARE_WORKFLOW: PreviewWorkflow = { type: 'google_drive', bgColor: '#FFFFFF', position: { x: 380, y: 100 }, - hideSourceHandle: true, rows: [{ title: 'Account', value: '' }], }, { @@ -1052,7 +1021,6 @@ export const CREDENTIAL_SHARE_WORKFLOW: PreviewWorkflow = { type: 'google_calendar', bgColor: '#FFFFFF', position: { x: 380, y: 200 }, - hideSourceHandle: true, rows: [{ title: 'Account', value: '' }], }, ], @@ -1095,7 +1063,6 @@ export const CREDENTIAL_ROUTE_WORKFLOW: PreviewWorkflow = { type: 'credential', bgColor: '#6366F1', position: { x: 700, y: 0 }, - hideSourceHandle: true, rows: [{ title: 'Account', value: 'Prod Slack' }], }, { @@ -1104,7 +1071,6 @@ export const CREDENTIAL_ROUTE_WORKFLOW: PreviewWorkflow = { type: 'credential', bgColor: '#6366F1', position: { x: 700, y: 130 }, - hideSourceHandle: true, rows: [{ title: 'Account', value: 'Staging Slack' }], }, ], @@ -1135,7 +1101,6 @@ const GUARDRAILS_GATE = { type: 'condition', bgColor: '#FF752F', position: { x: 640, y: 0 }, - hideSourceHandle: true, } as const /** Guardrails example: validate JSON before parsing it. */ @@ -1270,7 +1235,6 @@ export const HITL_APPROVAL_WORKFLOW: PreviewWorkflow = { type: 'api', bgColor: '#2F55FF', position: { x: 640, y: 0 }, - hideSourceHandle: true, rows: [{ title: 'Method', value: 'POST' }], }, ], @@ -1316,7 +1280,6 @@ export const HITL_MULTISTAGE_WORKFLOW: PreviewWorkflow = { type: 'function', bgColor: '#FF402F', position: { x: 880, y: 0 }, - hideSourceHandle: true, rows: [{ title: 'Code', value: 'apply()' }], }, ], @@ -1355,7 +1318,6 @@ export const HITL_VALIDATE_WORKFLOW: PreviewWorkflow = { type: 'function', bgColor: '#FF402F', position: { x: 640, y: 0 }, - hideSourceHandle: true, rows: [{ title: 'Code', value: 'process()' }], }, ], @@ -1393,7 +1355,6 @@ export const WEBHOOK_NOTIFY_WORKFLOW: PreviewWorkflow = { type: 'webhook', bgColor: '#10B981', position: { x: 640, y: 0 }, - hideSourceHandle: true, rows: [{ title: 'URL', value: 'hooks.slack.com/…' }], }, ], @@ -1435,7 +1396,6 @@ export const WEBHOOK_TRIGGER_WORKFLOW: PreviewWorkflow = { type: 'webhook', bgColor: '#10B981', position: { x: 640, y: 0 }, - hideSourceHandle: true, rows: [{ title: 'URL', value: 'api.partner.com/hook' }], }, ], @@ -1476,7 +1436,6 @@ export const WORKFLOW_CALL_WORKFLOW: PreviewWorkflow = { type: 'agent', bgColor: '#33C482', position: { x: 640, y: 0 }, - hideSourceHandle: true, rows: [{ title: 'Messages', value: 'Summarize ' }], }, ], @@ -1516,7 +1475,6 @@ export const LOOP_WORKFLOW: PreviewWorkflow = { bgColor: '#33C482', position: { x: 150, y: 62 }, parentId: 'loop', - hideSourceHandle: true, rows: [{ title: 'Messages', value: 'Rate ' }], }, { @@ -1525,7 +1483,6 @@ export const LOOP_WORKFLOW: PreviewWorkflow = { type: 'agent', bgColor: '#33C482', position: { x: 860, y: 95 }, - hideSourceHandle: true, rows: [{ title: 'Messages', value: 'Summarize ' }], }, ], @@ -1566,7 +1523,6 @@ export const PARALLEL_WORKFLOW: PreviewWorkflow = { bgColor: '#2F55FF', position: { x: 150, y: 62 }, parentId: 'parallel', - hideSourceHandle: true, rows: [{ title: 'URL', value: '/tasks/' }], }, { @@ -1575,7 +1531,6 @@ export const PARALLEL_WORKFLOW: PreviewWorkflow = { type: 'function', bgColor: '#FF402F', position: { x: 860, y: 95 }, - hideSourceHandle: true, rows: [{ title: 'Code', value: 'merge()' }], }, ], @@ -1626,7 +1581,6 @@ export const BUILD_AGENT_WORKFLOW: PreviewWorkflow = { type: 'response', bgColor: '#2F55FF', position: { x: 680, y: 0 }, - hideSourceHandle: true, rows: [{ title: 'Data', value: '{ "score": }' }], }, ], @@ -1670,7 +1624,6 @@ export const FILE_SUMMARY_WORKFLOW: PreviewWorkflow = { type: 'file', bgColor: '#40916C', position: { x: 660, y: 0 }, - hideSourceHandle: true, rows: [ { title: 'Operation', value: 'Write' }, { title: 'File Name', value: 'summary.md' }, @@ -1714,7 +1667,6 @@ export const TABLE_ROUNDTRIP_WORKFLOW: PreviewWorkflow = { type: 'table', bgColor: '#10B981', position: { x: 680, y: 0 }, - hideSourceHandle: true, rows: [ { title: 'Operation', value: 'Update Rows' }, { title: 'Set', value: "category, status = 'qualified'" }, @@ -1784,7 +1736,6 @@ export const LEAD_SCORER_WORKFLOW: PreviewWorkflow = { type: 'google_sheets', bgColor: '#FFFFFF', position: { x: 1200, y: 0 }, - hideSourceHandle: true, rows: [{ title: 'Operation', value: 'Append' }], }, ], diff --git a/apps/docs/components/workflow-preview/preview-block-node.tsx b/apps/docs/components/workflow-preview/preview-block-node.tsx deleted file mode 100644 index ccfe7abf999..00000000000 --- a/apps/docs/components/workflow-preview/preview-block-node.tsx +++ /dev/null @@ -1,215 +0,0 @@ -'use client' - -import { memo } from 'react' -import { domAnimation, LazyMotion, m } from 'framer-motion' -import { Handle, type NodeProps, Position } from 'reactflow' -import { blockTypeToIconMap } from '@/components/ui/icon-mapping' -import { BLOCK_ICONS } from '@/components/workflow-preview/block-icons' -import { - BLOCK_STAGGER, - EASE_OUT, - type PreviewTool, -} from '@/components/workflow-preview/workflow-data' - -/** Core-block glyph first, then the integration icon map so diagrams can show tools too. */ -function resolveIcon(type: string) { - return BLOCK_ICONS[type] ?? blockTypeToIconMap[type] ?? null -} - -interface PreviewBlockData { - name: string - blockType: string - bgColor: string - rows: Array<{ title: string; value: string }> - branches?: Array<{ id: string; label: string; value?: string }> - showError?: boolean - tools?: PreviewTool[] - hideTargetHandle?: boolean - hideSourceHandle?: boolean - index?: number - animate?: boolean - isHighlighted?: boolean - isDimmed?: boolean -} - -/** - * Handle styling matching the real WorkflowBlock handles (--wp-edge mirrors - * the app's --workflow-edge in both modes). - */ -const HANDLE_BASE = '!z-[10] !border-none !bg-[var(--wp-edge)]' -const HANDLE_LEFT = `${HANDLE_BASE} !left-[-8px] !h-5 !w-[7px] !rounded-r-none !rounded-l-[2px]` -const HANDLE_RIGHT = `${HANDLE_BASE} !right-[-8px] !h-5 !w-[7px] !rounded-l-none !rounded-r-[2px]` -const HANDLE_BRANCH = - '!z-[10] !border-none !right-[-16px] !h-5 !w-[7px] !rounded-l-none !rounded-r-[2px] !bg-[var(--wp-edge)]' -const HANDLE_ERROR = - '!z-[10] !border-none !right-[-16px] !h-5 !w-[7px] !rounded-l-none !rounded-r-[2px] !bg-[var(--text-error)]' - -/** - * Static preview block node matching the real WorkflowBlock styling. - * Renders a header (icon + name), sub-block rows, and tool chips. - * - * Colors come from preview-theme.module.css, mirroring the app's tokens in - * both light and dark mode. - */ -export const PreviewBlockNode = memo(function PreviewBlockNode({ - data, -}: NodeProps) { - const { - name, - blockType, - bgColor, - rows, - branches, - showError, - tools, - hideTargetHandle, - hideSourceHandle, - index = 0, - animate = false, - isHighlighted = false, - isDimmed = false, - } = data - const Icon = resolveIcon(blockType) - const delay = animate ? index * BLOCK_STAGGER : 0 - const hasBranches = Boolean(branches && branches.length > 0) - const hasContent = - rows.length > 0 || hasBranches || Boolean(showError) || (tools && tools.length > 0) - /* A block with branch handles emits from them, not from the header handle. */ - const showHeaderSource = !hideSourceHandle && !hasBranches - - return ( - - -
- {isHighlighted && ( -
- )} - {!hideTargetHandle && ( - - )} - -
-
-
- {Icon && } -
- {name} -
-
- - {hasContent && ( -
- {rows.map((row) => ( -
- - {row.title} - - {row.value && ( - - {row.value} - - )} -
- ))} - - {branches?.map((branch) => ( -
- - {branch.label} - - - {branch.value ?? '-'} - - -
- ))} - - {showError && ( -
- - error - - -
- )} - - {tools && tools.length > 0 && ( -
- - Tools - -
- {tools.map((tool) => { - const ToolIcon = resolveIcon(tool.type) - return ( -
-
- {ToolIcon && } -
- - {tool.name} - -
- ) - })} -
-
- )} -
- )} - - {showHeaderSource && ( - - )} -
- - - ) -}) diff --git a/apps/docs/components/workflow-preview/workflow-data.ts b/apps/docs/components/workflow-preview/workflow-data.ts index ad3b0d44cba..19180af088d 100644 --- a/apps/docs/components/workflow-preview/workflow-data.ts +++ b/apps/docs/components/workflow-preview/workflow-data.ts @@ -26,12 +26,9 @@ export interface PreviewBlock { * blocks, `router-` on Routers. */ branches?: Array<{ id: string; label: string; value?: string }> - /** Render an error row with a red source handle (id `error`). */ - showError?: boolean tools?: PreviewTool[] position: { x: number; y: number } hideTargetHandle?: boolean - hideSourceHandle?: boolean /** When set, the block renders as a Loop/Parallel container sized to hold its children. */ size?: { width: number; height: number } /** Id of the container block this block sits inside. Its position is relative to the container. */ @@ -101,10 +98,8 @@ export function toReactFlowElements( bgColor: block.bgColor, rows: block.rows, branches: block.branches, - showError: block.showError, tools: block.tools, hideTargetHandle: block.hideTargetHandle, - hideSourceHandle: block.hideSourceHandle, size: block.size, index, animate, diff --git a/apps/docs/components/workflow-preview/workflow-preview.tsx b/apps/docs/components/workflow-preview/workflow-preview.tsx index b5c4a222471..c90e3277170 100644 --- a/apps/docs/components/workflow-preview/workflow-preview.tsx +++ b/apps/docs/components/workflow-preview/workflow-preview.tsx @@ -20,8 +20,8 @@ import ReactFlow, { } from 'reactflow' import 'reactflow/dist/style.css' import { BlockInspector } from '@/components/workflow-preview/block-inspector' +import { DocsBlockNode } from '@/components/workflow-preview/docs-block-node' import { DocsContainerNode } from '@/components/workflow-preview/docs-container-node' -import { PreviewBlockNode } from '@/components/workflow-preview/preview-block-node' import { EASE_OUT, type PreviewBlock, @@ -89,7 +89,7 @@ function PreviewEdge({ } const NODE_TYPES: NodeTypes = { - previewBlock: PreviewBlockNode, + previewBlock: DocsBlockNode, previewContainer: DocsContainerNode, } const EDGE_TYPES: EdgeTypes = { previewEdge: PreviewEdge } From 8a133ce7fbc678520f93befbec19a31622665b22 Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 29 Jun 2026 14:03:07 -0700 Subject: [PATCH 04/30] refactor(workflow-renderer): make editor-only WorkflowBlockView props optional The child-deploy, schedule, and webhook badge props (and their callbacks) only matter in the editor. Mark them optional and optional-chain the three callbacks so read-only consumers (docs, academy) can omit the whole group instead of passing ~18 explicit off-values. The editor still passes them, so its behavior is byte-identical (verified: apps/sim type-check clean). DocsBlockNode drops the off-props. --- .../workflow-preview/docs-block-node.tsx | 18 --------- .../workflow-block/workflow-block-view.tsx | 38 +++++++++---------- 2 files changed, 19 insertions(+), 37 deletions(-) diff --git a/apps/docs/components/workflow-preview/docs-block-node.tsx b/apps/docs/components/workflow-preview/docs-block-node.tsx index de2df68031a..ee70dcd984c 100644 --- a/apps/docs/components/workflow-preview/docs-block-node.tsx +++ b/apps/docs/components/workflow-preview/docs-block-node.tsx @@ -148,24 +148,6 @@ export const DocsBlockNode = memo(function DocsBlockNode({ id, data }: NodeProps conditionRows={conditionRows} routerRows={routerRows} wouldCreateConnectionCycle={() => false} - isWorkflowSelector={false} - childWorkflowId={undefined} - childIsDeployed={null} - childNeedsRedeploy={false} - isDeploying={false} - canAdmin={false} - onDeployChild={() => {}} - shouldShowScheduleBadge={false} - scheduleIsDisabled={false} - onReactivateSchedule={() => {}} - showWebhookIndicator={false} - webhookProvider={undefined} - webhookPath={undefined} - webhookProviderName={undefined} - isWebhookConfigured={false} - isWebhookDisabled={false} - webhookId={undefined} - onReactivateWebhook={() => {}} onSelect={() => {}} rows={rows} /> diff --git a/packages/workflow-renderer/src/workflow-block/workflow-block-view.tsx b/packages/workflow-renderer/src/workflow-block/workflow-block-view.tsx index e56254322bb..142846d189f 100644 --- a/packages/workflow-renderer/src/workflow-block/workflow-block-view.tsx +++ b/packages/workflow-renderer/src/workflow-block/workflow-block-view.tsx @@ -65,29 +65,29 @@ export interface WorkflowBlockViewProps { /** Connection-cycle guard; reads fresh edge state on every call. */ wouldCreateConnectionCycle: (source: string, target: string) => boolean - /** Child-workflow deploy badge state. */ - isWorkflowSelector: boolean + /** Child-workflow deploy badge state — editor-only; omit in read-only contexts. */ + isWorkflowSelector?: boolean childWorkflowId?: string - childIsDeployed: boolean | null - childNeedsRedeploy: boolean - isDeploying: boolean - canAdmin: boolean - onDeployChild: () => void + childIsDeployed?: boolean | null + childNeedsRedeploy?: boolean + isDeploying?: boolean + canAdmin?: boolean + onDeployChild?: () => void - /** Schedule badge state. */ - shouldShowScheduleBadge: boolean - scheduleIsDisabled: boolean - onReactivateSchedule: () => void + /** Schedule badge state — editor-only; omit in read-only contexts. */ + shouldShowScheduleBadge?: boolean + scheduleIsDisabled?: boolean + onReactivateSchedule?: () => void - /** Webhook badge state. */ - showWebhookIndicator: boolean + /** Webhook badge state — editor-only; omit in read-only contexts. */ + showWebhookIndicator?: boolean webhookProvider?: string webhookPath?: string webhookProviderName?: string - isWebhookConfigured: boolean - isWebhookDisabled: boolean + isWebhookConfigured?: boolean + isWebhookDisabled?: boolean webhookId?: string - onReactivateWebhook: () => void + onReactivateWebhook?: () => void /** Selects this block in the editor panel. */ onSelect: () => void @@ -221,7 +221,7 @@ export function WorkflowBlockView({ dot onClick={(e) => { e.stopPropagation() - onDeployChild() + onDeployChild?.() }} > {isDeploying ? 'Deploying...' : !childIsDeployed ? 'undeployed' : 'redeploy'} @@ -250,7 +250,7 @@ export function WorkflowBlockView({ dot onClick={(e) => { e.stopPropagation() - onReactivateSchedule() + onReactivateSchedule?.() }} > disabled @@ -293,7 +293,7 @@ export function WorkflowBlockView({ dot onClick={(e) => { e.stopPropagation() - onReactivateWebhook() + onReactivateWebhook?.() }} > disabled From 0c0957da30d5cc05524f88a7105a49e4b26ddc8d Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 29 Jun 2026 14:11:15 -0700 Subject: [PATCH 05/30] feat(docs): replace how-it-runs static diagrams with live WorkflowPreview Swaps the four static PNGs on the how-it-runs page for live, app-styled WorkflowPreview diagrams (concurrency, combination, condition+router branching, error path). Adds the four example workflows and renders error-port edges red to match the editor. The English page only; the translated execution/basics pages keep the PNGs. --- .../components/workflow-preview/examples.ts | 220 ++++++++++++++++++ .../docs/components/workflow-preview/index.ts | 4 + .../workflow-preview/workflow-data.ts | 5 +- .../content/docs/en/workflows/how-it-runs.mdx | 16 +- 4 files changed, 239 insertions(+), 6 deletions(-) diff --git a/apps/docs/components/workflow-preview/examples.ts b/apps/docs/components/workflow-preview/examples.ts index a1648033684..28fb82323eb 100644 --- a/apps/docs/components/workflow-preview/examples.ts +++ b/apps/docs/components/workflow-preview/examples.ts @@ -1746,3 +1746,223 @@ export const LEAD_SCORER_WORKFLOW: PreviewWorkflow = { { id: 'score-log', source: 'score', target: 'log' }, ], } + +/** + * The "Blocks run as soon as they can" diagram on the how-it-runs page: two + * agents that each depend only on Start, so they run concurrently. + */ +export const CONCURRENCY_WORKFLOW: PreviewWorkflow = { + id: 'concurrency', + name: 'Run in parallel', + blocks: [ + { + id: 'start', + name: 'Start', + type: 'start_trigger', + bgColor: '#2FB3FF', + position: { x: 0, y: 80 }, + hideTargetHandle: true, + rows: [{ title: 'Input', value: 'Ticket' }], + }, + { + id: 'support', + name: 'Customer Support', + type: 'agent', + bgColor: '#33C482', + position: { x: 360, y: 0 }, + rows: [{ title: 'Model', value: 'claude-sonnet-4-6' }], + }, + { + id: 'research', + name: 'Deep Researcher', + type: 'agent', + bgColor: '#33C482', + position: { x: 360, y: 150 }, + rows: [{ title: 'Model', value: 'claude-sonnet-4-6' }], + }, + ], + edges: [ + { id: 'start-support', source: 'start', target: 'support' }, + { id: 'start-research', source: 'start', target: 'research' }, + ], +} + +/** + * The "A block waits for all its inputs" diagram: a Function that runs only + * after both agents finish, reading both outputs. + */ +export const COMBINATION_WORKFLOW: PreviewWorkflow = { + id: 'combination', + name: 'Wait for all inputs', + blocks: [ + { + id: 'start', + name: 'Start', + type: 'start_trigger', + bgColor: '#2FB3FF', + position: { x: 0, y: 80 }, + hideTargetHandle: true, + rows: [{ title: 'Input', value: 'Ticket' }], + }, + { + id: 'support', + name: 'Customer Support', + type: 'agent', + bgColor: '#33C482', + position: { x: 360, y: 0 }, + rows: [{ title: 'Model', value: 'claude-sonnet-4-6' }], + }, + { + id: 'research', + name: 'Deep Researcher', + type: 'agent', + bgColor: '#33C482', + position: { x: 360, y: 150 }, + rows: [{ title: 'Model', value: 'claude-sonnet-4-6' }], + }, + { + id: 'combine', + name: 'Combine', + type: 'function', + bgColor: '#FF402F', + position: { x: 720, y: 75 }, + rows: [ + { title: 'Language', value: 'JavaScript' }, + { title: 'Code', value: 'merge(, )' }, + ], + }, + ], + edges: [ + { id: 'start-support', source: 'start', target: 'support' }, + { id: 'start-research', source: 'start', target: 'research' }, + { id: 'support-combine', source: 'support', target: 'combine' }, + { id: 'research-combine', source: 'research', target: 'combine' }, + ], +} + +/** + * The "Branches follow one path" diagram: a Condition splits on an explicit + * rule, and on one branch a Router lets a model choose among paths. + */ +export const ROUTING_WORKFLOW: PreviewWorkflow = { + id: 'routing', + name: 'Branch by condition and router', + blocks: [ + { + id: 'start', + name: 'Start', + type: 'start_trigger', + bgColor: '#2FB3FF', + position: { x: 0, y: 140 }, + hideTargetHandle: true, + rows: [{ title: 'Input', value: 'Message' }], + }, + { + id: 'condition', + name: 'Condition', + type: 'condition', + bgColor: '#FF752F', + position: { x: 340, y: 140 }, + rows: [], + branches: [ + { id: 'condition-if', label: 'If', value: " === 'lead'" }, + { id: 'condition-else', label: 'else' }, + ], + }, + { + id: 'router', + name: 'Router', + type: 'router', + bgColor: '#28C43F', + position: { x: 740, y: 0 }, + rows: [], + branches: [ + { id: 'router-sales', label: 'Sales' }, + { id: 'router-support', label: 'Support' }, + ], + }, + { + id: 'reply', + name: 'Reply', + type: 'agent', + bgColor: '#33C482', + position: { x: 740, y: 280 }, + rows: [{ title: 'Model', value: 'claude-sonnet-4-6' }], + }, + { + id: 'sales', + name: 'Sales', + type: 'agent', + bgColor: '#33C482', + position: { x: 1120, y: -60 }, + rows: [{ title: 'Model', value: 'claude-sonnet-4-6' }], + }, + { + id: 'support', + name: 'Support', + type: 'agent', + bgColor: '#33C482', + position: { x: 1120, y: 90 }, + rows: [{ title: 'Model', value: 'claude-sonnet-4-6' }], + }, + ], + edges: [ + { id: 'start-condition', source: 'start', target: 'condition' }, + { id: 'condition-router', source: 'condition', target: 'router', sourceHandle: 'condition-if' }, + { id: 'condition-reply', source: 'condition', target: 'reply', sourceHandle: 'condition-else' }, + { id: 'router-sales', source: 'router', target: 'sales', sourceHandle: 'router-sales' }, + { id: 'router-support', source: 'router', target: 'support', sourceHandle: 'router-support' }, + ], +} + +/** + * The "When a block fails" diagram: a Function fails and the run leaves through + * its red error port to a handler, while the normal-path block never runs. + */ +export const ERROR_PATH_WORKFLOW: PreviewWorkflow = { + id: 'error-path', + name: 'Handle a failure', + blocks: [ + { + id: 'start', + name: 'Start', + type: 'start_trigger', + bgColor: '#2FB3FF', + position: { x: 0, y: 80 }, + hideTargetHandle: true, + rows: [{ title: 'Input', value: 'Order' }], + }, + { + id: 'throwError', + name: 'throwError', + type: 'function', + bgColor: '#FF402F', + position: { x: 340, y: 80 }, + rows: [ + { title: 'Language', value: 'JavaScript' }, + { title: 'Code', value: 'throw new Error("failed")' }, + ], + }, + { + id: 'handleSuccess', + name: 'handleSuccess', + type: 'function', + bgColor: '#FF402F', + position: { x: 720, y: 0 }, + rows: [{ title: 'Code', value: 'return ok' }], + }, + { + id: 'handleError', + name: 'handleError', + type: 'function', + bgColor: '#FF402F', + position: { x: 720, y: 170 }, + rows: [{ title: 'Code', value: 'return recovered' }], + }, + ], + edges: [ + { id: 'start-throw', source: 'start', target: 'throwError' }, + { id: 'throw-success', source: 'throwError', target: 'handleSuccess' }, + { id: 'throw-error', source: 'throwError', target: 'handleError', sourceHandle: 'error' }, + ], +} diff --git a/apps/docs/components/workflow-preview/index.ts b/apps/docs/components/workflow-preview/index.ts index 6c35e3738e0..a6ee359ef63 100644 --- a/apps/docs/components/workflow-preview/index.ts +++ b/apps/docs/components/workflow-preview/index.ts @@ -4,11 +4,14 @@ export { BUILD_AGENT_WORKFLOW, CLASSIFY_REPLY_WORKFLOW, CLASSIFY_WORKFLOW, + COMBINATION_WORKFLOW, + CONCURRENCY_WORKFLOW, CONDITION_MODERATE_WORKFLOW, CONDITION_ONBOARD_WORKFLOW, CONDITION_ROUTE_WORKFLOW, CREDENTIAL_ROUTE_WORKFLOW, CREDENTIAL_SHARE_WORKFLOW, + ERROR_PATH_WORKFLOW, EVALUATOR_GATE_WORKFLOW, FILE_SUMMARY_WORKFLOW, FUNCTION_RESHAPE_WORKFLOW, @@ -28,6 +31,7 @@ export { ROUTER_CLASSIFY_WORKFLOW, ROUTER_LEAD_WORKFLOW, ROUTER_TRIAGE_WORKFLOW, + ROUTING_WORKFLOW, SUPPORT_KB_WORKFLOW, TABLE_ENRICH_WORKFLOW, TABLE_ROUNDTRIP_WORKFLOW, diff --git a/apps/docs/components/workflow-preview/workflow-data.ts b/apps/docs/components/workflow-preview/workflow-data.ts index 19180af088d..703850df6bd 100644 --- a/apps/docs/components/workflow-preview/workflow-data.ts +++ b/apps/docs/components/workflow-preview/workflow-data.ts @@ -50,6 +50,8 @@ export const EASE_OUT: [number, number, number, number] = [0.16, 1, 0.3, 1] const EDGE_STYLE = { stroke: 'var(--wp-edge)', strokeWidth: 1.5 } as const const EDGE_STYLE_HIGHLIGHT = { stroke: 'var(--wp-highlight)', strokeWidth: 2.5 } as const +/** Edges leaving a block's error port render red, matching the editor. */ +const EDGE_STYLE_ERROR = { stroke: 'var(--text-error)', strokeWidth: 1.5 } as const /** Optional emphasis: light one block or one edge and dim everything else. */ export interface HighlightOptions { @@ -118,6 +120,7 @@ export function toReactFlowElements( const sourceIndex = blockIndexMap.get(e.source) ?? 0 const isEdgeHighlight = highlightEdge === e.id const dimmed = hasHighlight && !isEdgeHighlight + const isErrorEdge = e.sourceHandle === 'error' return { id: e.id, source: e.source, @@ -125,7 +128,7 @@ export function toReactFlowElements( type: 'previewEdge', animated: false, style: { - ...(isEdgeHighlight ? EDGE_STYLE_HIGHLIGHT : EDGE_STYLE), + ...(isEdgeHighlight ? EDGE_STYLE_HIGHLIGHT : isErrorEdge ? EDGE_STYLE_ERROR : EDGE_STYLE), opacity: dimmed ? 0.35 : 1, }, sourceHandle: e.sourceHandle ?? 'source', diff --git a/apps/docs/content/docs/en/workflows/how-it-runs.mdx b/apps/docs/content/docs/en/workflows/how-it-runs.mdx index 2a808c2cde3..c437fe04051 100644 --- a/apps/docs/content/docs/en/workflows/how-it-runs.mdx +++ b/apps/docs/content/docs/en/workflows/how-it-runs.mdx @@ -6,7 +6,13 @@ pageType: concept import { Callout } from 'fumadocs-ui/components/callout' import { Card, Cards } from 'fumadocs-ui/components/card' -import { Image } from '@/components/ui/image' +import { + COMBINATION_WORKFLOW, + CONCURRENCY_WORKFLOW, + ERROR_PATH_WORKFLOW, + ROUTING_WORKFLOW, + WorkflowPreview, +} from '@/components/workflow-preview' When you run a workflow, Sim works out the order from the [connections](/workflows/connections): a block runs as soon as the blocks it depends on have finished. @@ -14,7 +20,7 @@ When you run a workflow, Sim works out the order from the [connections](/workflo Multiple blocks in a workflow can be executing at the same time. A block starts the moment its dependencies finish, and it waits on nothing else. -Two agent blocks running after the Start trigger + Here the Customer Support and Deep Researcher agents each depend only on Start, so neither waits for the other. @@ -22,7 +28,7 @@ Here the Customer Support and Deep Researcher agents each depend only on Start, When several blocks feed into one, that block waits for every feeder that is going to run, then runs once with each of their outputs available to read. A feeder on a branch that wasn't taken doesn't hold it up. You don't merge the outputs yourself. -A Function block running after two agent blocks complete + The Function block here runs after both agents complete, with both of their outputs ready. @@ -30,7 +36,7 @@ The Function block here runs after both agents complete, with both of their outp A workflow can split. A [Condition](/workflows/blocks/condition) block branches on an explicit rule; a [Router](/workflows/blocks/router) block lets a model choose the path. Only the branch that is taken runs. A block on a branch that didn't run produces no output, which is why a [connection tag](/workflows/connections) pointing at it comes back empty. -A workflow branching by condition and by router + To repeat work, a [Loop](/workflows/blocks/loop) block runs its inner blocks over a list, a count, or while a condition holds, and a [Parallel](/workflows/blocks/parallel) block runs them for several items at once. @@ -42,7 +48,7 @@ A workflow can call other workflows, through a Workflow block, an MCP tool, or a A block that errors fails the run: blocks already running finish, and nothing new starts. To handle the failure instead, connect the block's **error port** — the run follows the [error path](/workflows/connections) and continues. -A Function block whose error port routes to a handler block, with the normal path continuing to a success block + Here `throwError` fails, so the run leaves through its red error port to `handleError`; `handleSuccess` on the normal path never runs. From 975a675637395229e5c4ddb0694a253ac4dbea40 Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 29 Jun 2026 15:00:58 -0700 Subject: [PATCH 06/30] refactor(workflow-renderer): the view owns condition/router/error rows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both the editor container and the docs adapter hand-built the condition/router/error summary rows in an order that had to stay in lockstep with the view's absolute handle-offset math — a three-way coupling with nothing enforcing it. The view now renders those rows itself from the conditionRows/routerRows it already receives (plus a routerContextValue prop for the router's Context row), so row order and handle geometry live together in one place. Both containers pass only data and their non-branch rows. Editor is byte-identical: getDisplayValue moves to where conditionRows/routerRows are built; the no-subBlock SubBlockRow path is already an exact SubBlockRowView(title, value) passthrough; the error row stays gated on shouldShowDefaultHandles. Verified apps/sim type-check clean. Docs now also renders the error row on condition/router blocks, which the real editor already did (shouldShowDefaultHandles is true for them) — an alignment fix. --- .../workflow-preview/docs-block-node.tsx | 25 ++-------- .../workflow-block/workflow-block.tsx | 46 +++++++------------ .../workflow-block/workflow-block-view.tsx | 34 +++++++++++++- 3 files changed, 52 insertions(+), 53 deletions(-) diff --git a/apps/docs/components/workflow-preview/docs-block-node.tsx b/apps/docs/components/workflow-preview/docs-block-node.tsx index ee70dcd984c..e31386e74b7 100644 --- a/apps/docs/components/workflow-preview/docs-block-node.tsx +++ b/apps/docs/components/workflow-preview/docs-block-node.tsx @@ -88,28 +88,12 @@ export const DocsBlockNode = memo(function DocsBlockNode({ id, data }: NodeProps : [] /** - * Replicate the editor's row order so the View's absolute condition/router - * handle offsets line up: branch rows first (router adds a leading Context - * row); other blocks render their subblock rows, a Tools row, then the error - * row when default handles are shown. + * Non-branch content only — the View renders condition/router/error rows from + * the conditionRows/routerRows it receives, so their order stays locked to its + * handle geometry in one place. */ const rows = - type === 'condition' ? ( - (branches ?? []).map((branch) => ( - - )) - ) : type === 'router_v2' ? ( - <> - - {(branches ?? []).map((branch, routeIndex) => ( - - ))} - - ) : ( + type === 'condition' || type === 'router_v2' ? null : ( <> {dataRows.map((row) => ( @@ -120,7 +104,6 @@ export const DocsBlockNode = memo(function DocsBlockNode({ id, data }: NodeProps displayValue={tools?.map((tool) => tool.name).join(', ')} /> )} - {shouldShowDefaultHandles && } ) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx index 0976cdde23d..6c7976327e5 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx @@ -617,7 +617,10 @@ export const WorkflowBlock = memo(function WorkflowBlock({ */ const conditionRows = useMemo(() => { if (type !== 'condition') return [] as { id: string; title: string; value: string }[] - return getConditionRows(id, topologySubBlocks.conditions?.value) + return getConditionRows(id, topologySubBlocks.conditions?.value).map((cond) => ({ + ...cond, + value: getDisplayValue(cond.value), + })) }, [type, topologySubBlocks, id]) /** @@ -627,7 +630,10 @@ export const WorkflowBlock = memo(function WorkflowBlock({ */ const routerRows = useMemo(() => { if (type !== 'router_v2') return [] as { id: string; value: string }[] - return getRouterRows(id, topologySubBlocks.routes?.value) + return getRouterRows(id, topologySubBlocks.routes?.value).map((route) => ({ + ...route, + value: getDisplayValue(route.value), + })) }, [type, topologySubBlocks, id]) /** @@ -699,29 +705,10 @@ export const WorkflowBlock = memo(function WorkflowBlock({ const webhookProviderName = webhookProvider ? getProviderName(webhookProvider) : undefined - const rows = ( - <> - {type === 'condition' ? ( - conditionRows.map((cond) => ( - - )) - ) : type === 'router_v2' ? ( - <> - - {routerRows.map((route, index) => ( - - ))} - - ) : ( - subBlockRows.map((row, rowIndex) => + const rows = + type === 'condition' || type === 'router_v2' ? null : ( + <> + {subBlockRows.map((row, rowIndex) => row.flatMap((subBlock) => { const rawValue = subBlockState[subBlock.id]?.value if (subBlock.type === 'mcp-dynamic-args') { @@ -761,11 +748,9 @@ export const WorkflowBlock = memo(function WorkflowBlock({ />, ] }) - ) - )} - {shouldShowDefaultHandles && } - - ) + )} + + ) return ( boolean @@ -95,7 +98,11 @@ export interface WorkflowBlockViewProps { contentRef?: Ref /** Editor-only action bar; omit in read-only / preview contexts. */ actionBar?: ReactNode - /** Collapsed subblock summary rows, built by the container. */ + /** + * Non-branch collapsed subblock summary rows, built by the container. + * Condition/router/error rows are rendered by the view itself from + * conditionRows/routerRows. + */ rows: ReactNode } @@ -121,6 +128,7 @@ export function WorkflowBlockView({ hasContentBelowHeader, conditionRows, routerRows, + routerContextValue, wouldCreateConnectionCycle, isWorkflowSelector, childWorkflowId, @@ -315,7 +323,29 @@ export function WorkflowBlockView({
- {hasContentBelowHeader &&
{rows}
} + {hasContentBelowHeader && ( +
+ {type === 'condition' ? ( + conditionRows.map((cond) => ( + + )) + ) : type === 'router_v2' ? ( + <> + + {routerRows.map((route, index) => ( + + ))} + + ) : ( + rows + )} + {shouldShowDefaultHandles && } +
+ )} {type === 'condition' && ( <> From 0b03846318bcc879e7d54f7b5fe4f3db5c4042f9 Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 29 Jun 2026 15:14:30 -0700 Subject: [PATCH 07/30] refactor(docs): drop the parallel --wp-* token layer for the app/emcn tokens The workflow-preview ran a 25-token --wp-* mirror (22 were pure aliases of app tokens docs already defines) plus a .wp-scope wrapper class. Replaces every var(--wp-X) with its canonical app/emcn token (--wp-edge->--workflow-edge, --wp-highlight->--brand-secondary, badges->--badge-*, etc.), adds the one missing token (--divider), and deletes the .wp-scope blocks + class. Visually identical (aliases resolve to the same values); the preview now inherits the same design tokens as the shared views and the rest of the app instead of a hand-rolled parallel set. --- apps/docs/app/global.css | 52 +------------------ .../workflow-preview/block-inspector.tsx | 50 +++++++++--------- .../workflow-preview/block-preview.tsx | 24 ++++----- .../workflow-preview/output-bundle.tsx | 42 +++++++-------- .../workflow-preview/workflow-data.ts | 4 +- .../workflow-preview/workflow-preview.tsx | 16 +++--- 6 files changed, 70 insertions(+), 118 deletions(-) diff --git a/apps/docs/app/global.css b/apps/docs/app/global.css index 993bb832a54..063f5fa9a3d 100644 --- a/apps/docs/app/global.css +++ b/apps/docs/app/global.css @@ -53,6 +53,7 @@ body { --surface-active: #ececec; --border: #dedede; --border-1: #e0e0e0; + --divider: #ededed; --text-primary: #1a1a1a; --text-secondary: #525252; --text-tertiary: #5c5c5c; @@ -103,6 +104,7 @@ body { --surface-active: #2c2c2c; --border: #333333; --border-1: #3d3d3d; + --divider: #393939; --text-primary: #e6e6e6; --text-secondary: #cccccc; --text-tertiary: #b3b3b3; @@ -1664,56 +1666,6 @@ main article blockquote { box-shadow: none !important; } -/* Workflow-preview theme scope. Values mirror the app's tokens in - apps/sim/app/_styles/globals.css (light from :root/.light, dark from .dark) - so the docs previews match the OG repository in both modes. */ -.wp-scope { - /* surfaces */ - --wp-canvas: var(--bg); - --wp-panel: var(--surface-1); - --wp-surface: var(--surface-2); - --wp-header: var(--surface-3); - --wp-btn: var(--surface-4); - --wp-control: var(--surface-5); - --wp-active: var(--surface-active); - --wp-container-fill: rgba(0, 0, 0, 0.02); - - /* borders */ - --wp-border: var(--border); - --wp-border-1: var(--border-1); - --wp-chip-bg: var(--surface-5); /* ChipTag surface (light) */ - --wp-chip-text: var(--text-body); - --wp-divider: #ededed; /* --divider */ - --wp-edge: #e0e0e0; /* --workflow-edge */ - --wp-highlight: #33b4ff; /* traced edge / highlighted output node */ - - /* text */ - --wp-text: var(--text-primary); - --wp-text-2: var(--text-secondary); - --wp-text-3: var(--text-tertiary); - --wp-text-muted: var(--text-muted); - --wp-text-subtle: var(--text-subtle); - - /* type badges (output inspector) */ - --wp-badge-success-bg: var(--badge-success-bg); - --wp-badge-success-text: var(--badge-success-text); - --wp-badge-blue-bg: var(--badge-blue-bg); - --wp-badge-blue-text: var(--badge-blue-text); - --wp-badge-orange-bg: var(--badge-orange-bg); - --wp-badge-orange-text: var(--badge-orange-text); - --wp-badge-purple-bg: var(--badge-purple-bg); - --wp-badge-purple-text: var(--badge-purple-text); - --wp-badge-gray-bg: var(--badge-gray-bg); - --wp-badge-gray-text: var(--badge-gray-text); -} - -.dark .wp-scope { - --wp-container-fill: rgba(255, 255, 255, 0.02); - --wp-chip-bg: var(--surface-4); /* ChipTag surface (dark) */ - --wp-divider: #393939; - --wp-edge: #454545; -} - /* Tailwind v4 content sources */ @source '../app/**/*.{js,ts,jsx,tsx,mdx}'; @source '../components/**/*.{js,ts,jsx,tsx,mdx}'; diff --git a/apps/docs/components/workflow-preview/block-inspector.tsx b/apps/docs/components/workflow-preview/block-inspector.tsx index 6b073f70eec..070054ef0e1 100644 --- a/apps/docs/components/workflow-preview/block-inspector.tsx +++ b/apps/docs/components/workflow-preview/block-inspector.tsx @@ -48,18 +48,18 @@ function resolveIcon(type: string) { } const CONTROL = - 'flex w-full items-center justify-between gap-2 rounded-[10px] bg-[var(--wp-control)] px-3 py-2.5 text-[13px]' + 'flex w-full items-center justify-between gap-2 rounded-[10px] bg-[var(--surface-5)] px-3 py-2.5 text-[13px]' function FieldControl({ field }: { field: InspectorField }) { const kind = field.kind ?? 'input' const hasValue = field.value !== undefined && field.value !== '' - const textColor = hasValue ? 'var(--wp-text)' : 'var(--wp-text-muted)' + const textColor = hasValue ? 'var(--text-primary)' : 'var(--text-muted)' const text = hasValue ? field.value : (field.placeholder ?? '—') if (kind === 'textarea' || kind === 'code') { return (
- {on ? 'On' : 'Off'} + {on ? 'On' : 'Off'}
) } @@ -92,7 +92,7 @@ function FieldControl({ field }: { field: InspectorField }) { const percent = field.percent ?? 50 return (
-
+
- {field.value} + {field.value}
) } @@ -113,7 +113,7 @@ function FieldControl({ field }: { field: InspectorField }) { {text} {kind === 'select' && ( - + )}
) @@ -139,19 +139,19 @@ export function BlockInspector({
-
+
{Icon && }
- {name} - + {name} + @@ -162,11 +162,11 @@ export function BlockInspector({
0 ? { borderTop: '1px dashed var(--wp-divider)' } : undefined} + style={i > 0 ? { borderTop: '1px dashed var(--divider)' } : undefined} > - + {field.label} - {field.required && *} + {field.required && *}
@@ -175,16 +175,16 @@ export function BlockInspector({ {tools && tools.length > 0 && (
0 ? { borderTop: '1px dashed var(--wp-divider)' } : undefined} + style={fields.length > 0 ? { borderTop: '1px dashed var(--divider)' } : undefined} > - Tools + Tools
{tools.map((tool) => { const TIcon = resolveIcon(tool.type) return (
{TIcon && }
- {tool.name} + {tool.name}
) })} @@ -202,8 +202,8 @@ export function BlockInspector({
{connections && connections.length > 0 && ( -
-
+
+
Connections
@@ -213,12 +213,12 @@ export function BlockInspector({
{CIcon && }
- {c.name} - + {c.name} +
) })} diff --git a/apps/docs/components/workflow-preview/block-preview.tsx b/apps/docs/components/workflow-preview/block-preview.tsx index 3151f54f293..fa2e7a15708 100644 --- a/apps/docs/components/workflow-preview/block-preview.tsx +++ b/apps/docs/components/workflow-preview/block-preview.tsx @@ -22,16 +22,16 @@ function BlockCard({ spec }: { spec: BlockDisplaySpec }) { const hasContent = spec.rows.length > 0 || branches.length > 0 || Boolean(spec.showError) return ( -
+
{!spec.hideTargetHandle && ( - + )} {!spec.hideSourceHandle && ( - + )}
{Icon && }
- + {spec.name}
@@ -50,11 +50,11 @@ function BlockCard({ spec }: { spec: BlockDisplaySpec }) {
{spec.rows.map((row) => (
- + {row.title} {row.value && ( - + {row.value} )} @@ -63,17 +63,17 @@ function BlockCard({ spec }: { spec: BlockDisplaySpec }) { {branches.map((branch) => (
- + {branch} - - - + - +
))} {spec.showError && (
- + error @@ -100,7 +100,7 @@ export function BlockPreview({ type }: BlockPreviewProps) { if (!spec) return null return ( -
+
diff --git a/apps/docs/components/workflow-preview/output-bundle.tsx b/apps/docs/components/workflow-preview/output-bundle.tsx index ff7923d8702..90c950934fb 100644 --- a/apps/docs/components/workflow-preview/output-bundle.tsx +++ b/apps/docs/components/workflow-preview/output-bundle.tsx @@ -8,12 +8,12 @@ type ValueType = 'string' | 'number' | 'boolean' | 'object' | 'array' | 'null' /** Dark-theme equivalents of the app's type badges (string=green, number=blue, …). */ const BADGE_COLORS: Record = { - string: { bg: 'var(--wp-badge-success-bg)', text: 'var(--wp-badge-success-text)' }, - number: { bg: 'var(--wp-badge-blue-bg)', text: 'var(--wp-badge-blue-text)' }, - boolean: { bg: 'var(--wp-badge-orange-bg)', text: 'var(--wp-badge-orange-text)' }, - array: { bg: 'var(--wp-badge-purple-bg)', text: 'var(--wp-badge-purple-text)' }, - object: { bg: 'var(--wp-badge-gray-bg)', text: 'var(--wp-badge-gray-text)' }, - null: { bg: 'var(--wp-badge-gray-bg)', text: 'var(--wp-badge-gray-text)' }, + string: { bg: 'var(--badge-success-bg)', text: 'var(--badge-success-text)' }, + number: { bg: 'var(--badge-blue-bg)', text: 'var(--badge-blue-text)' }, + boolean: { bg: 'var(--badge-orange-bg)', text: 'var(--badge-orange-text)' }, + array: { bg: 'var(--badge-purple-bg)', text: 'var(--badge-purple-text)' }, + object: { bg: 'var(--badge-gray-bg)', text: 'var(--badge-gray-text)' }, + null: { bg: 'var(--badge-gray-bg)', text: 'var(--badge-gray-text)' }, } interface OutputNode { @@ -73,24 +73,24 @@ function TreeNode({ node, depth = 0 }: { node: OutputNode; depth?: number }) {
{node.key}
{expanded && (node.children || node.value !== undefined) && ( -
+
{node.children ? node.children.map((child) => ( )) : node.value !== undefined && ( -
{node.value}
+
{node.value}
)}
)} @@ -117,27 +117,27 @@ export function OutputBundle({ ] return ( -
-
-
-
Logs
+
+
+
+
Logs
{logRows.map((row) => { const Icon = row.type ? resolveIcon(row.type) : null return (
{Icon && }
- {row.name} + {row.name} {row.duration && ( - + {row.duration} )} @@ -148,9 +148,9 @@ export function OutputBundle({
- Output - Input - + Output + Input + diff --git a/apps/docs/components/workflow-preview/workflow-data.ts b/apps/docs/components/workflow-preview/workflow-data.ts index 703850df6bd..a26f24206eb 100644 --- a/apps/docs/components/workflow-preview/workflow-data.ts +++ b/apps/docs/components/workflow-preview/workflow-data.ts @@ -48,8 +48,8 @@ export interface PreviewWorkflow { export const BLOCK_STAGGER = 0.12 export const EASE_OUT: [number, number, number, number] = [0.16, 1, 0.3, 1] -const EDGE_STYLE = { stroke: 'var(--wp-edge)', strokeWidth: 1.5 } as const -const EDGE_STYLE_HIGHLIGHT = { stroke: 'var(--wp-highlight)', strokeWidth: 2.5 } as const +const EDGE_STYLE = { stroke: 'var(--workflow-edge)', strokeWidth: 1.5 } as const +const EDGE_STYLE_HIGHLIGHT = { stroke: 'var(--brand-secondary)', strokeWidth: 2.5 } as const /** Edges leaving a block's error port render red, matching the editor. */ const EDGE_STYLE_ERROR = { stroke: 'var(--text-error)', strokeWidth: 1.5 } as const diff --git a/apps/docs/components/workflow-preview/workflow-preview.tsx b/apps/docs/components/workflow-preview/workflow-preview.tsx index c90e3277170..7259d050ad5 100644 --- a/apps/docs/components/workflow-preview/workflow-preview.tsx +++ b/apps/docs/components/workflow-preview/workflow-preview.tsx @@ -196,7 +196,7 @@ function PreviewFlow({ fitViewOptions={interactive ? LIGHTBOX_FIT_VIEW_OPTIONS : FIT_VIEW_OPTIONS} className='h-full w-full' > - + ) } @@ -254,7 +254,7 @@ export function WorkflowPreview({ return (
@@ -270,7 +270,7 @@ export function WorkflowPreview({ type='button' aria-label='Expand workflow preview' onClick={() => openWith(null)} - className='absolute top-2 right-2 z-10 flex size-[28px] items-center justify-center rounded-[6px] border border-[var(--wp-border-1)] bg-[var(--wp-btn)] text-[var(--wp-text-muted)] opacity-0 transition-opacity duration-150 hover:text-[var(--wp-text)] group-hover:opacity-100' + className='absolute top-2 right-2 z-10 flex size-[28px] items-center justify-center rounded-[6px] border border-[var(--border-1)] bg-[var(--surface-4)] text-[var(--text-muted)] opacity-0 transition-opacity duration-150 hover:text-[var(--text-primary)] group-hover:opacity-100' > @@ -284,19 +284,19 @@ export function WorkflowPreview({ role='presentation' >
e.stopPropagation()} onKeyDown={() => {}} role='presentation' >
- {workflow.name} + {workflow.name} @@ -313,7 +313,7 @@ export function WorkflowPreview({
-
+
{selectedBlock ? ( ) : ( -
+
Select a block to see its full configuration
)} From 186903e239d586e829586c1cfd3c5bff889f8bf4 Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 29 Jun 2026 15:18:13 -0700 Subject: [PATCH 08/30] refactor(docs): adopt emcn Badge + dedup resolveIcon in workflow-preview output-bundle's hand-rolled type badge (BADGE_COLORS + a styled span) becomes the emcn Badge (its green/blue/orange/purple/gray variants use the identical --badge-* tokens). resolveIcon, which had three copies, is now imported once from block-icons by output-bundle and block-inspector. --- .../workflow-preview/block-inspector.tsx | 7 +--- .../workflow-preview/output-bundle.tsx | 34 +++++++------------ 2 files changed, 14 insertions(+), 27 deletions(-) diff --git a/apps/docs/components/workflow-preview/block-inspector.tsx b/apps/docs/components/workflow-preview/block-inspector.tsx index 070054ef0e1..03d5ab0b9d3 100644 --- a/apps/docs/components/workflow-preview/block-inspector.tsx +++ b/apps/docs/components/workflow-preview/block-inspector.tsx @@ -1,8 +1,7 @@ 'use client' import { BookOpen, ChevronDown, ChevronRight, Pencil } from 'lucide-react' -import { blockTypeToIconMap } from '@/components/ui/icon-mapping' -import { BLOCK_ICONS } from '@/components/workflow-preview/block-icons' +import { resolveIcon } from '@/components/workflow-preview/block-icons' type FieldKind = 'select' | 'input' | 'textarea' | 'code' | 'slider' | 'toggle' @@ -43,10 +42,6 @@ interface BlockInspectorProps { embedded?: boolean } -function resolveIcon(type: string) { - return BLOCK_ICONS[type] ?? blockTypeToIconMap[type] ?? null -} - const CONTROL = 'flex w-full items-center justify-between gap-2 rounded-[10px] bg-[var(--surface-5)] px-3 py-2.5 text-[13px]' diff --git a/apps/docs/components/workflow-preview/output-bundle.tsx b/apps/docs/components/workflow-preview/output-bundle.tsx index 90c950934fb..0c6ffd5a5f0 100644 --- a/apps/docs/components/workflow-preview/output-bundle.tsx +++ b/apps/docs/components/workflow-preview/output-bundle.tsx @@ -1,20 +1,20 @@ 'use client' +import { Badge } from '@sim/emcn' import { ChevronDown, Clipboard, Download, Search } from 'lucide-react' -import { blockTypeToIconMap } from '@/components/ui/icon-mapping' -import { BLOCK_ICONS } from '@/components/workflow-preview/block-icons' +import { resolveIcon } from '@/components/workflow-preview/block-icons' type ValueType = 'string' | 'number' | 'boolean' | 'object' | 'array' | 'null' -/** Dark-theme equivalents of the app's type badges (string=green, number=blue, …). */ -const BADGE_COLORS: Record = { - string: { bg: 'var(--badge-success-bg)', text: 'var(--badge-success-text)' }, - number: { bg: 'var(--badge-blue-bg)', text: 'var(--badge-blue-text)' }, - boolean: { bg: 'var(--badge-orange-bg)', text: 'var(--badge-orange-text)' }, - array: { bg: 'var(--badge-purple-bg)', text: 'var(--badge-purple-text)' }, - object: { bg: 'var(--badge-gray-bg)', text: 'var(--badge-gray-text)' }, - null: { bg: 'var(--badge-gray-bg)', text: 'var(--badge-gray-text)' }, -} +/** Output value type → emcn Badge color variant. */ +const TYPE_VARIANT = { + string: 'green', + number: 'blue', + boolean: 'orange', + array: 'purple', + object: 'gray', + null: 'gray', +} as const interface OutputNode { key: string @@ -49,19 +49,11 @@ interface OutputBundleProps { values: OutputNode[] } -function resolveIcon(type: string) { - return BLOCK_ICONS[type] ?? blockTypeToIconMap[type] ?? null -} - function TypeBadge({ type }: { type: ValueType }) { - const c = BADGE_COLORS[type] return ( - + {type} - + ) } From 85b2ed0fb0c792c474202587309bdbb67dce9411 Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 29 Jun 2026 17:13:20 -0700 Subject: [PATCH 09/30] refactor(docs): rebuild the preview inspector on emcn chip primitives MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The lightbox inspector was a hand-rolled facsimile (raw divs + a CONTROL class string + inline dashed borders). It now composes from the same @sim/emcn primitives the live editor's sub-block controls wrap — ChipSelect/ChipInput/ChipTextarea(viewOnly)/ChipSwitch/ChipTag/FieldDivider/Label — so it reads as the real editor panel, fed example data (read-only, full opacity via readOnly/viewOnly, not greyed). Slider stays minimal (no emcn equivalent) but on app tokens. Props API and embedded/standalone modes unchanged. --- .../workflow-preview/block-inspector.tsx | 173 ++++++++++-------- 1 file changed, 95 insertions(+), 78 deletions(-) diff --git a/apps/docs/components/workflow-preview/block-inspector.tsx b/apps/docs/components/workflow-preview/block-inspector.tsx index 03d5ab0b9d3..23287f25b8c 100644 --- a/apps/docs/components/workflow-preview/block-inspector.tsx +++ b/apps/docs/components/workflow-preview/block-inspector.tsx @@ -1,5 +1,15 @@ 'use client' +import { + ChipInput, + ChipSelect, + ChipSwitch, + ChipTag, + ChipTextarea, + cn, + FieldDivider, + Label, +} from '@sim/emcn' import { BookOpen, ChevronDown, ChevronRight, Pencil } from 'lucide-react' import { resolveIcon } from '@/components/workflow-preview/block-icons' @@ -42,44 +52,56 @@ interface BlockInspectorProps { embedded?: boolean } -const CONTROL = - 'flex w-full items-center justify-between gap-2 rounded-[10px] bg-[var(--surface-5)] px-3 py-2.5 text-[13px]' +const NOOP = () => {} +/** + * Read-only facsimile of one configuration field, composed from the same emcn + * chip primitives the live editor wraps: `select`→{@link ChipSelect}, + * `input`→{@link ChipInput}, `textarea`/`code`→{@link ChipTextarea} (read-only at + * full opacity, not greyed out), `toggle`→{@link ChipSwitch}. `slider` has no + * chip equivalent and stays a minimal app-token bar. + */ function FieldControl({ field }: { field: InspectorField }) { const kind = field.kind ?? 'input' - const hasValue = field.value !== undefined && field.value !== '' - const textColor = hasValue ? 'var(--text-primary)' : 'var(--text-muted)' - const text = hasValue ? field.value : (field.placeholder ?? '—') + const value = field.value ?? '' + const placeholder = field.placeholder ?? '—' + + if (kind === 'select') { + return ( + + ) + } if (kind === 'textarea' || kind === 'code') { return ( -
- {text} -
+ ) } if (kind === 'toggle') { const on = field.value === 'on' return ( -
-
-
-
- {on ? 'On' : 'Off'} -
+ ) } @@ -87,13 +109,13 @@ function FieldControl({ field }: { field: InspectorField }) { const percent = field.percent ?? 50 return (
-
+
@@ -102,22 +124,25 @@ function FieldControl({ field }: { field: InspectorField }) { ) } + return +} + +function InspectorFieldRow({ field }: { field: InspectorField }) { return ( -
- - {text} - - {kind === 'select' && ( - - )} +
+ +
) } /** * A read-only facsimile of the editor's right-hand block inspector: the block - * header, its configuration fields as static controls, and its connections. - * Hand-authored per usage, like {@link WorkflowPreview} examples. + * header, its configuration fields as static chip controls, and its + * connections. Hand-authored per usage, like {@link WorkflowPreview} examples. */ export function BlockInspector({ name, @@ -129,18 +154,20 @@ export function BlockInspector({ embedded = false, }: BlockInspectorProps) { const Icon = resolveIcon(type) + const hasTools = Boolean(tools && tools.length > 0) return (
{Icon && } @@ -152,45 +179,35 @@ export function BlockInspector({
-
+
{fields.map((field, i) => ( -
0 ? { borderTop: '1px dashed var(--divider)' } : undefined} - > - - {field.label} - {field.required && *} - - +
+ {i > 0 && } +
))} - {tools && tools.length > 0 && ( -
0 ? { borderTop: '1px dashed var(--divider)' } : undefined} - > - Tools -
- {tools.map((tool) => { - const TIcon = resolveIcon(tool.type) - return ( -
-
- {TIcon && } -
- {tool.name} -
- ) - })} + {hasTools && ( +
+ {fields.length > 0 && } +
+ +
+ {tools?.map((tool) => { + const TIcon = resolveIcon(tool.type) + return ( + + + {TIcon && } + + {tool.name} + + ) + })} +
)} From 7fef83166a1ba4e475d26eff394fa96bc3f40d7b Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 29 Jun 2026 17:25:38 -0700 Subject: [PATCH 10/30] refactor(docs): render the block-reference hero through the shared View Retires the hand-rolled BlockCard (a parallel reimplementation of WorkflowBlockView) and the BlockDisplaySpec data model. Each block hero is now a single-block PreviewWorkflow (block-display-workflows.ts) rendered through the same toReactFlowElements -> DocsBlockNode -> WorkflowBlockView pipeline as the diagrams, mounted in a minimal fitView ReactFlow (maxZoom 1.3, no canvas chrome). A single block can no longer drift from the canvas. --- .../workflow-preview/block-display-specs.ts | 220 ----------- .../block-display-workflows.ts | 361 ++++++++++++++++++ .../workflow-preview/block-preview.tsx | 145 +++---- 3 files changed, 413 insertions(+), 313 deletions(-) delete mode 100644 apps/docs/components/workflow-preview/block-display-specs.ts create mode 100644 apps/docs/components/workflow-preview/block-display-workflows.ts diff --git a/apps/docs/components/workflow-preview/block-display-specs.ts b/apps/docs/components/workflow-preview/block-display-specs.ts deleted file mode 100644 index 16f36e5f0fe..00000000000 --- a/apps/docs/components/workflow-preview/block-display-specs.ts +++ /dev/null @@ -1,220 +0,0 @@ -/** - * A hand-authored block, described as what the builder canvas displays. - * - * - `rows` are the visible sub-block rows; use `'-'` for an empty/unset field (the canvas - * shows a dash), or a representative value where the field has a default. - * - `branches` render one output handle per entry (Condition's if/else-if/else, Router's - * routes); when set, also set `hideSourceHandle: true` so the single output is replaced. - * - `showError: true` adds the bottom `Error` row + red handle (action blocks, not triggers). - * - `hideTargetHandle: true` for triggers (entry points — no input). - * - `bgColor` is the resolved hex; the Agent uses Sim green `#33C482` (`var(--brand)`). - */ -export interface BlockDisplaySpec { - name: string - /** Block type — drives the header icon (see `BLOCK_ICONS`). */ - type: string - /** Resolved brand color (hex). */ - bgColor: string - rows: Array<{ title: string; value: string }> - branches?: string[] - showError?: boolean - hideTargetHandle?: boolean - hideSourceHandle?: boolean -} - -/** - * Display specs for the block reference previews — one per block, edited to match exactly - * what the builder canvas shows. Source of truth for the `` heroes. - */ - -export const BLOCK_DISPLAY_SPECS: Record = { - agent: { - name: 'Agent', - type: 'agent', - bgColor: '#33C482', - showError: true, - rows: [ - { title: 'Messages', value: '-' }, - { title: 'Model', value: 'claude-sonnet-4-6' }, - { title: 'Files', value: '-' }, - { title: 'Tools', value: '-' }, - { title: 'Skills', value: '-' }, - { title: 'Memory', value: 'None' }, - { title: 'Response Format', value: '-' }, - ], - }, - api: { - name: 'API', - type: 'api', - bgColor: '#2F55FF', - showError: true, - rows: [ - { title: 'URL', value: '-' }, - { title: 'Method', value: 'GET' }, - { title: 'Query Params', value: '-' }, - { title: 'Headers', value: '-' }, - { title: 'Body', value: '-' }, - ], - }, - condition: { - name: 'Condition', - type: 'condition', - bgColor: '#FF752F', - showError: true, - hideSourceHandle: true, - rows: [], - branches: ['if', 'else if', 'else'], - }, - credential: { - name: 'Credential', - type: 'credential', - bgColor: '#6366F1', - showError: true, - rows: [ - { title: 'Operation', value: 'Select Credential' }, - { title: 'Credential', value: '-' }, - ], - }, - evaluator: { - name: 'Evaluator', - type: 'evaluator', - bgColor: '#4D5FFF', - showError: true, - rows: [ - { title: 'Evaluation Metrics', value: '-' }, - { title: 'Content', value: '-' }, - { title: 'Model', value: 'claude-sonnet-4-6' }, - ], - }, - function: { - name: 'Function', - type: 'function', - bgColor: '#FF402F', - showError: true, - rows: [ - { title: 'Language', value: 'JavaScript' }, - { title: 'Code', value: '-' }, - ], - }, - guardrails: { - name: 'Guardrails', - type: 'guardrails', - bgColor: '#3D642D', - showError: true, - rows: [ - { title: 'Content to Validate', value: '-' }, - { title: 'Validation Type', value: 'Valid JSON' }, - ], - }, - response: { - name: 'Response', - type: 'response', - bgColor: '#2F55FF', - showError: true, - hideSourceHandle: true, - rows: [ - { title: 'Response Data Mode', value: 'Builder' }, - { title: 'Response Structure', value: '-' }, - { title: 'Status Code', value: '-' }, - { title: 'Response Headers', value: '-' }, - ], - }, - router: { - name: 'Router', - type: 'router', - bgColor: '#28C43F', - showError: true, - hideSourceHandle: true, - rows: [{ title: 'Context', value: '-' }], - branches: ['route 1'], - }, - variables: { - name: 'Variables', - type: 'variables', - bgColor: '#8B5CF6', - showError: true, - rows: [{ title: 'Variable Assignments', value: '-' }], - }, - wait: { - name: 'Wait', - type: 'wait', - bgColor: '#F59E0B', - showError: true, - rows: [ - { title: 'Wait Amount', value: '-' }, - { title: 'Unit', value: 'Seconds' }, - ], - }, - webhook: { - name: 'Webhook', - type: 'webhook', - bgColor: '#10B981', - showError: true, - rows: [ - { title: 'Webhook URL', value: '-' }, - { title: 'Payload', value: '-' }, - { title: 'Signing Secret', value: '-' }, - { title: 'Additional Headers', value: '-' }, - ], - }, - workflow: { - name: 'Workflow', - type: 'workflow', - bgColor: '#6366F1', - showError: true, - rows: [ - { title: 'Select Workflow', value: '-' }, - { title: 'Input Variable', value: '-' }, - ], - }, - human_in_the_loop: { - name: 'Human in the Loop', - type: 'human_in_the_loop', - bgColor: '#10B981', - showError: true, - rows: [ - { title: 'Display Data', value: '-' }, - { title: 'Notification', value: '-' }, - { title: 'Resume Form', value: '-' }, - ], - }, - schedule: { - name: 'Schedule', - type: 'schedule', - bgColor: '#6366F1', - hideTargetHandle: true, - rows: [ - { title: 'Run frequency', value: 'Every X Minutes' }, - { title: 'Interval (minutes)', value: '-' }, - ], - }, - rss: { - name: 'RSS Feed', - type: 'rss', - bgColor: '#F97316', - hideTargetHandle: true, - rows: [{ title: 'Feed URL', value: '-' }], - }, - webhook_trigger: { - name: 'Webhook', - type: 'webhook', - bgColor: '#10B981', - hideTargetHandle: true, - rows: [ - { title: 'Webhook URL', value: '-' }, - { title: 'Require Authentication', value: '-' }, - { title: 'Input Format', value: '-' }, - ], - }, - table: { - name: 'Table', - type: 'table', - bgColor: '#10B981', - hideTargetHandle: true, - rows: [ - { title: 'Table', value: '-' }, - { title: 'Event type', value: 'Row updated' }, - { title: 'Watch columns', value: '-' }, - ], - }, -} diff --git a/apps/docs/components/workflow-preview/block-display-workflows.ts b/apps/docs/components/workflow-preview/block-display-workflows.ts new file mode 100644 index 00000000000..3d55b6ae491 --- /dev/null +++ b/apps/docs/components/workflow-preview/block-display-workflows.ts @@ -0,0 +1,361 @@ +import type { PreviewWorkflow } from '@/components/workflow-preview/workflow-data' + +/** + * Single-block preview workflows for the block reference heroes — one per block, + * authored to match exactly what the builder canvas shows. Source of truth for + * ``. + * + * Each entry is a one-block {@link PreviewWorkflow} rendered through the shared + * {@link WorkflowBlockView} (via `DocsBlockNode`), so the hero stays pixel-faithful + * to the canvas. Authoring notes: + * + * - `rows` are the visible sub-block rows; use `'-'` for an empty/unset field (the + * canvas shows a dash), or a representative value where the field has a default. + * - `branches` render one output handle per entry (Condition's if/else-if/else, + * Router's routes). The View regenerates handle topology, so the label doubles as + * the branch id. + * - `hideTargetHandle: true` for triggers (entry points — no input). The View derives + * the default target/source handles and the bottom `Error` row from this gate, so + * there is no separate `showError`/`hideSourceHandle` flag. + * - `bgColor` is the resolved hex; the Agent uses Sim green `#33C482` (`var(--brand)`). + */ +export const BLOCK_DISPLAY_WORKFLOWS: Record = { + agent: { + id: 'agent', + name: 'Agent', + blocks: [ + { + id: 'agent', + name: 'Agent', + type: 'agent', + bgColor: '#33C482', + position: { x: 0, y: 0 }, + rows: [ + { title: 'Messages', value: '-' }, + { title: 'Model', value: 'claude-sonnet-4-6' }, + { title: 'Files', value: '-' }, + { title: 'Tools', value: '-' }, + { title: 'Skills', value: '-' }, + { title: 'Memory', value: 'None' }, + { title: 'Response Format', value: '-' }, + ], + }, + ], + edges: [], + }, + api: { + id: 'api', + name: 'API', + blocks: [ + { + id: 'api', + name: 'API', + type: 'api', + bgColor: '#2F55FF', + position: { x: 0, y: 0 }, + rows: [ + { title: 'URL', value: '-' }, + { title: 'Method', value: 'GET' }, + { title: 'Query Params', value: '-' }, + { title: 'Headers', value: '-' }, + { title: 'Body', value: '-' }, + ], + }, + ], + edges: [], + }, + condition: { + id: 'condition', + name: 'Condition', + blocks: [ + { + id: 'condition', + name: 'Condition', + type: 'condition', + bgColor: '#FF752F', + position: { x: 0, y: 0 }, + rows: [], + branches: [ + { id: 'if', label: 'if' }, + { id: 'else if', label: 'else if' }, + { id: 'else', label: 'else' }, + ], + }, + ], + edges: [], + }, + credential: { + id: 'credential', + name: 'Credential', + blocks: [ + { + id: 'credential', + name: 'Credential', + type: 'credential', + bgColor: '#6366F1', + position: { x: 0, y: 0 }, + rows: [ + { title: 'Operation', value: 'Select Credential' }, + { title: 'Credential', value: '-' }, + ], + }, + ], + edges: [], + }, + evaluator: { + id: 'evaluator', + name: 'Evaluator', + blocks: [ + { + id: 'evaluator', + name: 'Evaluator', + type: 'evaluator', + bgColor: '#4D5FFF', + position: { x: 0, y: 0 }, + rows: [ + { title: 'Evaluation Metrics', value: '-' }, + { title: 'Content', value: '-' }, + { title: 'Model', value: 'claude-sonnet-4-6' }, + ], + }, + ], + edges: [], + }, + function: { + id: 'function', + name: 'Function', + blocks: [ + { + id: 'function', + name: 'Function', + type: 'function', + bgColor: '#FF402F', + position: { x: 0, y: 0 }, + rows: [ + { title: 'Language', value: 'JavaScript' }, + { title: 'Code', value: '-' }, + ], + }, + ], + edges: [], + }, + guardrails: { + id: 'guardrails', + name: 'Guardrails', + blocks: [ + { + id: 'guardrails', + name: 'Guardrails', + type: 'guardrails', + bgColor: '#3D642D', + position: { x: 0, y: 0 }, + rows: [ + { title: 'Content to Validate', value: '-' }, + { title: 'Validation Type', value: 'Valid JSON' }, + ], + }, + ], + edges: [], + }, + response: { + id: 'response', + name: 'Response', + blocks: [ + { + id: 'response', + name: 'Response', + type: 'response', + bgColor: '#2F55FF', + position: { x: 0, y: 0 }, + rows: [ + { title: 'Response Data Mode', value: 'Builder' }, + { title: 'Response Structure', value: '-' }, + { title: 'Status Code', value: '-' }, + { title: 'Response Headers', value: '-' }, + ], + }, + ], + edges: [], + }, + router: { + id: 'router', + name: 'Router', + blocks: [ + { + id: 'router', + name: 'Router', + type: 'router', + bgColor: '#28C43F', + position: { x: 0, y: 0 }, + rows: [{ title: 'Context', value: '-' }], + branches: [{ id: 'route 1', label: 'route 1' }], + }, + ], + edges: [], + }, + variables: { + id: 'variables', + name: 'Variables', + blocks: [ + { + id: 'variables', + name: 'Variables', + type: 'variables', + bgColor: '#8B5CF6', + position: { x: 0, y: 0 }, + rows: [{ title: 'Variable Assignments', value: '-' }], + }, + ], + edges: [], + }, + wait: { + id: 'wait', + name: 'Wait', + blocks: [ + { + id: 'wait', + name: 'Wait', + type: 'wait', + bgColor: '#F59E0B', + position: { x: 0, y: 0 }, + rows: [ + { title: 'Wait Amount', value: '-' }, + { title: 'Unit', value: 'Seconds' }, + ], + }, + ], + edges: [], + }, + webhook: { + id: 'webhook', + name: 'Webhook', + blocks: [ + { + id: 'webhook', + name: 'Webhook', + type: 'webhook', + bgColor: '#10B981', + position: { x: 0, y: 0 }, + rows: [ + { title: 'Webhook URL', value: '-' }, + { title: 'Payload', value: '-' }, + { title: 'Signing Secret', value: '-' }, + { title: 'Additional Headers', value: '-' }, + ], + }, + ], + edges: [], + }, + workflow: { + id: 'workflow', + name: 'Workflow', + blocks: [ + { + id: 'workflow', + name: 'Workflow', + type: 'workflow', + bgColor: '#6366F1', + position: { x: 0, y: 0 }, + rows: [ + { title: 'Select Workflow', value: '-' }, + { title: 'Input Variable', value: '-' }, + ], + }, + ], + edges: [], + }, + human_in_the_loop: { + id: 'human_in_the_loop', + name: 'Human in the Loop', + blocks: [ + { + id: 'human_in_the_loop', + name: 'Human in the Loop', + type: 'human_in_the_loop', + bgColor: '#10B981', + position: { x: 0, y: 0 }, + rows: [ + { title: 'Display Data', value: '-' }, + { title: 'Notification', value: '-' }, + { title: 'Resume Form', value: '-' }, + ], + }, + ], + edges: [], + }, + schedule: { + id: 'schedule', + name: 'Schedule', + blocks: [ + { + id: 'schedule', + name: 'Schedule', + type: 'schedule', + bgColor: '#6366F1', + position: { x: 0, y: 0 }, + hideTargetHandle: true, + rows: [ + { title: 'Run frequency', value: 'Every X Minutes' }, + { title: 'Interval (minutes)', value: '-' }, + ], + }, + ], + edges: [], + }, + rss: { + id: 'rss', + name: 'RSS Feed', + blocks: [ + { + id: 'rss', + name: 'RSS Feed', + type: 'rss', + bgColor: '#F97316', + position: { x: 0, y: 0 }, + hideTargetHandle: true, + rows: [{ title: 'Feed URL', value: '-' }], + }, + ], + edges: [], + }, + webhook_trigger: { + id: 'webhook_trigger', + name: 'Webhook', + blocks: [ + { + id: 'webhook_trigger', + name: 'Webhook', + type: 'webhook', + bgColor: '#10B981', + position: { x: 0, y: 0 }, + hideTargetHandle: true, + rows: [ + { title: 'Webhook URL', value: '-' }, + { title: 'Require Authentication', value: '-' }, + { title: 'Input Format', value: '-' }, + ], + }, + ], + edges: [], + }, + table: { + id: 'table', + name: 'Table', + blocks: [ + { + id: 'table', + name: 'Table', + type: 'table', + bgColor: '#10B981', + position: { x: 0, y: 0 }, + hideTargetHandle: true, + rows: [ + { title: 'Table', value: '-' }, + { title: 'Event type', value: 'Row updated' }, + { title: 'Watch columns', value: '-' }, + ], + }, + ], + edges: [], + }, +} diff --git a/apps/docs/components/workflow-preview/block-preview.tsx b/apps/docs/components/workflow-preview/block-preview.tsx index fa2e7a15708..42a8c9c3aba 100644 --- a/apps/docs/components/workflow-preview/block-preview.tsx +++ b/apps/docs/components/workflow-preview/block-preview.tsx @@ -1,109 +1,68 @@ 'use client' -import { - BLOCK_DISPLAY_SPECS, - type BlockDisplaySpec, -} from '@/components/workflow-preview/block-display-specs' -import { BLOCK_ICONS } from '@/components/workflow-preview/block-icons' +import { useMemo } from 'react' +import { domAnimation, LazyMotion } from 'framer-motion' +import ReactFlow, { type NodeTypes, ReactFlowProvider } from 'reactflow' +import 'reactflow/dist/style.css' +import { BLOCK_DISPLAY_WORKFLOWS } from '@/components/workflow-preview/block-display-workflows' +import { DocsBlockNode } from '@/components/workflow-preview/docs-block-node' +import { toReactFlowElements } from '@/components/workflow-preview/workflow-data' -/** Display scale for the hero — bump this one number to resize. */ -const SCALE = 1.3 - -const DOT = '-translate-y-1/2 absolute top-1/2 h-5 w-[7px]' -const HEADER_DOT = '-translate-y-1/2 absolute top-[20px] h-5 w-[7px]' - -/** - * Static, app-styled block card — the same visual as the canvas WorkflowBlock, but - * with non-interactive decorative handles (no ReactFlow, so it can't be panned/dragged). - */ -function BlockCard({ spec }: { spec: BlockDisplaySpec }) { - const Icon = BLOCK_ICONS[spec.type] - const branches = spec.branches ?? [] - const hasContent = spec.rows.length > 0 || branches.length > 0 || Boolean(spec.showError) - - return ( -
- {!spec.hideTargetHandle && ( - - )} - {!spec.hideSourceHandle && ( - - )} - -
-
-
- {Icon && } -
- - {spec.name} - -
-
- - {hasContent && ( -
- {spec.rows.map((row) => ( -
- - {row.title} - - {row.value && ( - - {row.value} - - )} -
- ))} - - {branches.map((branch) => ( -
- - {branch} - - - - -
- ))} - - {spec.showError && ( -
- - error - - -
- )} -
- )} -
- ) -} +/** The hero mounts the same node type the canvas uses, so it can never drift. */ +const NODE_TYPES: NodeTypes = { previewBlock: DocsBlockNode } +const PRO_OPTIONS = { hideAttribution: true } +/** `maxZoom` mirrors the previous hand-rolled hero's 1.3 scale. */ +const FIT_VIEW_OPTIONS = { padding: 0.2, maxZoom: 1.3 } as const interface BlockPreviewProps { - /** Block key from {@link BLOCK_DISPLAY_SPECS} (e.g. `agent`, `condition`, `webhook_trigger`). */ + /** Block key from {@link BLOCK_DISPLAY_WORKFLOWS} (e.g. `agent`, `condition`, `webhook_trigger`). */ type: string } /** - * Renders a single block exactly as it appears on the builder canvas, from its - * hand-authored display spec — static (no canvas) and scaled up. Use as the hero on a - * block reference page: ``. Edit specs in `block-display-specs.ts`. + * Renders a single block exactly as it appears on the builder canvas, drawn by the + * shared {@link WorkflowBlockView} (via `DocsBlockNode`) through the same ReactFlow + * machinery as the multi-block diagrams — never a parallel hand-rolled card. Static + * and non-interactive (no pan/zoom), centered in a bordered container. Use as the hero + * on a block reference page: ``. Edit the source data in + * `block-display-workflows.ts`. */ export function BlockPreview({ type }: BlockPreviewProps) { - const spec = BLOCK_DISPLAY_SPECS[type] - if (!spec) return null + const workflow = BLOCK_DISPLAY_WORKFLOWS[type] + + const elements = useMemo(() => (workflow ? toReactFlowElements(workflow) : null), [workflow]) + + if (!workflow || !elements) return null return ( -
-
- -
+
+ + + + +
) } From 4d2581afb8eca500691541177fae279843041ddf Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 29 Jun 2026 17:25:39 -0700 Subject: [PATCH 11/30] fix(docs): define sim's type scale + align the preview inspector to the editor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docs Tailwind v4 never defined sim's custom font sizes (text-small/caption/md/micro), so emcn components (Label, Badge, the shared views) fell back to inherited sizes — the inspector labels rendered huge. Adds the type scale to the docs @theme. Also aligns the inspector header to the real editor panel (surface-4 bar, size-[18px] rounded-sm icon, text-sm name) and removes the Connections section (and its now-dead prop/wiring). --- apps/docs/app/global.css | 9 +++ .../workflow-preview/block-inspector.tsx | 58 +++++-------------- .../workflow-preview/workflow-preview.tsx | 11 ---- 3 files changed, 23 insertions(+), 55 deletions(-) diff --git a/apps/docs/app/global.css b/apps/docs/app/global.css index 063f5fa9a3d..9dc81f81481 100644 --- a/apps/docs/app/global.css +++ b/apps/docs/app/global.css @@ -20,6 +20,15 @@ body { @theme { --color-fd-primary: var(--color-fd-foreground); + /* Sim's custom type scale — emcn components (Label, Badge, the shared block + views) use these names, so they must resolve here or text falls back to the + inherited size. Mirrors apps/sim/tailwind.config.ts. */ + --text-micro: 10px; + --text-xs: 11px; + --text-caption: 12px; + --text-small: 13px; + --text-base: 15px; + --text-md: 16px; } /* Pure white light mode background */ diff --git a/apps/docs/components/workflow-preview/block-inspector.tsx b/apps/docs/components/workflow-preview/block-inspector.tsx index 23287f25b8c..0e82d841ccc 100644 --- a/apps/docs/components/workflow-preview/block-inspector.tsx +++ b/apps/docs/components/workflow-preview/block-inspector.tsx @@ -10,7 +10,7 @@ import { FieldDivider, Label, } from '@sim/emcn' -import { BookOpen, ChevronDown, ChevronRight, Pencil } from 'lucide-react' +import { BookOpen, Pencil } from 'lucide-react' import { resolveIcon } from '@/components/workflow-preview/block-icons' type FieldKind = 'select' | 'input' | 'textarea' | 'code' | 'slider' | 'toggle' @@ -27,12 +27,6 @@ interface InspectorField { percent?: number } -interface InspectorConnection { - name: string - type?: string - color?: string -} - interface InspectorTool { type: string name: string @@ -47,7 +41,6 @@ interface BlockInspectorProps { color?: string fields: InspectorField[] tools?: InspectorTool[] - connections?: InspectorConnection[] /** Render as a borderless panel filling its parent (the lightbox sidebar). */ embedded?: boolean } @@ -150,7 +143,6 @@ export function BlockInspector({ color = '#33C482', fields, tools, - connections, embedded = false, }: BlockInspectorProps) { const Icon = resolveIcon(type) @@ -165,18 +157,20 @@ export function BlockInspector({ : 'not-prose my-6 w-full max-w-[380px] overflow-hidden rounded-xl border border-[var(--border)]' )} > -
-
- {Icon && } +
+
+
+ {Icon && } +
+ {name} +
+
+ +
- {name} - - - -
@@ -212,30 +206,6 @@ export function BlockInspector({
)}
- - {connections && connections.length > 0 && ( -
-
- - Connections -
- {connections.map((c) => { - const CIcon = c.type ? resolveIcon(c.type) : null - return ( -
-
- {CIcon && } -
- {c.name} - -
- ) - })} -
- )}
) } diff --git a/apps/docs/components/workflow-preview/workflow-preview.tsx b/apps/docs/components/workflow-preview/workflow-preview.tsx index 7259d050ad5..5cb01c531c0 100644 --- a/apps/docs/components/workflow-preview/workflow-preview.tsx +++ b/apps/docs/components/workflow-preview/workflow-preview.tsx @@ -239,12 +239,6 @@ export function WorkflowPreview({ const selectedBlock = selectedId ? (workflow.blocks.find((b) => b.id === selectedId) ?? null) : null - const incoming = selectedBlock - ? workflow.edges - .filter((e) => e.target === selectedBlock.id) - .map((e) => workflow.blocks.find((b) => b.id === e.source)) - .filter((b): b is PreviewBlock => Boolean(b)) - : [] const openWith = (blockId: string | null) => { setSelectedId(blockId) @@ -322,11 +316,6 @@ export function WorkflowPreview({ color={selectedBlock.bgColor} fields={inspectorFieldsFor(selectedBlock)} tools={selectedBlock.tools} - connections={incoming.map((b) => ({ - name: b.name, - type: b.type, - color: b.bgColor, - }))} /> ) : (
From f8d98bceb061a101e57f0f4f851d2503b9be8278 Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 29 Jun 2026 17:33:30 -0700 Subject: [PATCH 12/30] fix(docs): inspector shows the full field list + dragged positions persist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inspector: shows the block type's full field list (from the reference data) with the example's values overlaid, so it reads like the editor panel instead of only the canvas summary rows. Drag: selecting another block no longer relayouts the canvas — node positions the viewer dragged are preserved across highlight/selection changes (only a different workflow relayouts). --- .../workflow-preview/workflow-preview.tsx | 41 ++++++++++++++----- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/apps/docs/components/workflow-preview/workflow-preview.tsx b/apps/docs/components/workflow-preview/workflow-preview.tsx index 5cb01c531c0..b42f4922b08 100644 --- a/apps/docs/components/workflow-preview/workflow-preview.tsx +++ b/apps/docs/components/workflow-preview/workflow-preview.tsx @@ -19,6 +19,7 @@ import ReactFlow, { ReactFlowProvider, } from 'reactflow' import 'reactflow/dist/style.css' +import { BLOCK_DISPLAY_WORKFLOWS } from '@/components/workflow-preview/block-display-workflows' import { BlockInspector } from '@/components/workflow-preview/block-inspector' import { DocsBlockNode } from '@/components/workflow-preview/docs-block-node' import { DocsContainerNode } from '@/components/workflow-preview/docs-container-node' @@ -116,16 +117,24 @@ const SELECT_TITLES = new Set([ ]) function inspectorFieldsFor(block: PreviewBlock) { - const rowFields = block.rows.map((row) => ({ - label: row.title, - kind: - TEXTAREA_TITLES.has(row.title) || row.value.length > 40 - ? ('textarea' as const) - : SELECT_TITLES.has(row.title) - ? ('select' as const) - : ('input' as const), - value: row.value, - })) + // Show the block type's full field list (from the reference data) with this + // block's example values overlaid, so the inspector reads like the editor's + // panel — every field — not just the summary rows shown on the canvas node. + const exampleByTitle = new Map(block.rows.map((row) => [row.title, row.value])) + const fullRows = BLOCK_DISPLAY_WORKFLOWS[block.type]?.blocks[0]?.rows ?? block.rows + const rowFields = fullRows.map((row) => { + const value = exampleByTitle.get(row.title) ?? row.value + return { + label: row.title, + kind: + TEXTAREA_TITLES.has(row.title) || value.length > 40 + ? ('textarea' as const) + : SELECT_TITLES.has(row.title) + ? ('select' as const) + : ('input' as const), + value, + } + }) const branchFields = (block.branches ?? []).map((branch) => ({ label: branch.label, kind: 'code' as const, @@ -156,8 +165,18 @@ function PreviewFlow({ const [nodes, setNodes] = useState(initialNodes) const [edges, setEdges] = useState(initialEdges) + /** + * Apply data changes (highlight/selection) without discarding positions the + * viewer has dragged — only a different workflow should relayout the canvas. + */ useEffect(() => { - setNodes(initialNodes) + setNodes((prev) => { + const positions = new Map(prev.map((node) => [node.id, node.position])) + return initialNodes.map((node) => { + const position = positions.get(node.id) + return position ? { ...node, position } : node + }) + }) setEdges(initialEdges) }, [initialNodes, initialEdges]) From 9bebf7eb5def0563631a682b76c99080ff9da678 Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 29 Jun 2026 17:43:51 -0700 Subject: [PATCH 13/30] feat(docs): highlight <> references + env vars; hide Ask AI over the lightbox; respace blocks Inspector text fields render the value with <...> block references and {{...}} environment variables highlighted in brand-secondary (a lean read-only port of the editor's formatDisplayText), in the canonical chip field chrome. The floating Ask AI widget is hidden while a preview lightbox is open. Plus the example-data respacing so the editor-faithful Error row no longer makes stacked blocks overlap. --- apps/docs/app/global.css | 5 ++ .../workflow-preview/block-inspector.tsx | 54 ++++++++++++------- .../components/workflow-preview/examples.ts | 22 ++++---- .../workflow-preview/format-references.tsx | 27 ++++++++++ .../workflow-preview/workflow-preview.tsx | 2 + 5 files changed, 79 insertions(+), 31 deletions(-) create mode 100644 apps/docs/components/workflow-preview/format-references.tsx diff --git a/apps/docs/app/global.css b/apps/docs/app/global.css index 9dc81f81481..f46587678fc 100644 --- a/apps/docs/app/global.css +++ b/apps/docs/app/global.css @@ -31,6 +31,11 @@ body { --text-md: 16px; } +/* Hide the floating Ask AI widget while a workflow-preview lightbox is open. */ +body.wp-lightbox-open [aria-label="Ask AI"] { + display: none; +} + /* Pure white light mode background */ :root:not(.dark) { --color-fd-background: hsl(0, 0%, 100%) !important; diff --git a/apps/docs/components/workflow-preview/block-inspector.tsx b/apps/docs/components/workflow-preview/block-inspector.tsx index 0e82d841ccc..4e2646fe8d3 100644 --- a/apps/docs/components/workflow-preview/block-inspector.tsx +++ b/apps/docs/components/workflow-preview/block-inspector.tsx @@ -1,17 +1,18 @@ 'use client' import { - ChipInput, ChipSelect, ChipSwitch, ChipTag, - ChipTextarea, + chipFieldSurfaceClass, + chipFieldTextClass, cn, FieldDivider, Label, } from '@sim/emcn' import { BookOpen, Pencil } from 'lucide-react' import { resolveIcon } from '@/components/workflow-preview/block-icons' +import { formatReferences } from '@/components/workflow-preview/format-references' type FieldKind = 'select' | 'input' | 'textarea' | 'code' | 'slider' | 'toggle' @@ -48,11 +49,11 @@ interface BlockInspectorProps { const NOOP = () => {} /** - * Read-only facsimile of one configuration field, composed from the same emcn - * chip primitives the live editor wraps: `select`→{@link ChipSelect}, - * `input`→{@link ChipInput}, `textarea`/`code`→{@link ChipTextarea} (read-only at - * full opacity, not greyed out), `toggle`→{@link ChipSwitch}. `slider` has no - * chip equivalent and stays a minimal app-token bar. + * Read-only facsimile of one configuration field, composed from emcn chip + * chrome: `select`→{@link ChipSelect}, `toggle`→{@link ChipSwitch}; text fields + * (`input`/`textarea`/`code`) render the value with `<...>`/`{{...}}` references + * highlighted via {@link formatReferences} in the canonical chip field surface. + * `slider` has no chip equivalent and stays a minimal app-token bar. */ function FieldControl({ field }: { field: InspectorField }) { const kind = field.kind ?? 'input' @@ -71,18 +72,6 @@ function FieldControl({ field }: { field: InspectorField }) { ) } - if (kind === 'textarea' || kind === 'code') { - return ( - - ) - } - if (kind === 'toggle') { const on = field.value === 'on' return ( @@ -117,7 +106,32 @@ function FieldControl({ field }: { field: InspectorField }) { ) } - return + // input / textarea / code: read-only value with `<...>` block references and + // `{{...}}` environment variables highlighted, in the canonical chip chrome. + const content = value ? ( + formatReferences(value) + ) : ( + {placeholder} + ) + if (kind === 'textarea' || kind === 'code') { + return ( +
+ {content} +
+ ) + } + return ( +
+ {content} +
+ ) } function InspectorFieldRow({ field }: { field: InspectorField }) { diff --git a/apps/docs/components/workflow-preview/examples.ts b/apps/docs/components/workflow-preview/examples.ts index 28fb82323eb..639de68608e 100644 --- a/apps/docs/components/workflow-preview/examples.ts +++ b/apps/docs/components/workflow-preview/examples.ts @@ -265,7 +265,7 @@ export const CONDITION_ROUTE_WORKFLOW: PreviewWorkflow = { name: 'Reply', type: 'agent', bgColor: '#33C482', - position: { x: 700, y: 130 }, + position: { x: 700, y: 140 }, rows: [{ title: 'Messages', value: 'Draft a standard reply' }], }, ], @@ -320,7 +320,7 @@ export const CONDITION_MODERATE_WORKFLOW: PreviewWorkflow = { name: 'Publish', type: 'api', bgColor: '#2F55FF', - position: { x: 700, y: 130 }, + position: { x: 700, y: 140 }, rows: [{ title: 'Method', value: 'POST' }], }, ], @@ -375,7 +375,7 @@ export const CONDITION_ONBOARD_WORKFLOW: PreviewWorkflow = { name: 'Quick start', type: 'agent', bgColor: '#33C482', - position: { x: 700, y: 130 }, + position: { x: 700, y: 140 }, rows: [{ title: 'Messages', value: 'Send the 2-minute setup' }], }, ], @@ -520,7 +520,7 @@ export const ROUTER_TRIAGE_WORKFLOW: PreviewWorkflow = { name: 'Support', type: 'agent', bgColor: '#33C482', - position: { x: 700, y: 95 }, + position: { x: 700, y: 140 }, rows: [{ title: 'Messages', value: 'Help with the issue' }], }, { @@ -528,7 +528,7 @@ export const ROUTER_TRIAGE_WORKFLOW: PreviewWorkflow = { name: 'Billing', type: 'agent', bgColor: '#33C482', - position: { x: 700, y: 190 }, + position: { x: 700, y: 280 }, rows: [{ title: 'Messages', value: 'Resolve the billing question' }], }, ], @@ -619,7 +619,7 @@ export const ROUTER_CLASSIFY_WORKFLOW: PreviewWorkflow = { name: 'Bug report', type: 'workflow', bgColor: '#6366F1', - position: { x: 700, y: 110 }, + position: { x: 700, y: 140 }, rows: [{ title: 'Workflow', value: 'bug-triage' }], }, ], @@ -669,7 +669,7 @@ export const ROUTER_LEAD_WORKFLOW: PreviewWorkflow = { name: 'Self-serve', type: 'workflow', bgColor: '#6366F1', - position: { x: 700, y: 110 }, + position: { x: 700, y: 140 }, rows: [{ title: 'Workflow', value: 'onboarding' }], }, ], @@ -769,7 +769,7 @@ export const RESPONSE_ERROR_WORKFLOW: PreviewWorkflow = { name: 'Response', type: 'response', bgColor: '#2F55FF', - position: { x: 700, y: 130 }, + position: { x: 700, y: 140 }, rows: [{ title: 'Status', value: '400' }], }, ], @@ -1012,7 +1012,7 @@ export const CREDENTIAL_SHARE_WORKFLOW: PreviewWorkflow = { name: 'Drive', type: 'google_drive', bgColor: '#FFFFFF', - position: { x: 380, y: 100 }, + position: { x: 380, y: 140 }, rows: [{ title: 'Account', value: '' }], }, { @@ -1020,7 +1020,7 @@ export const CREDENTIAL_SHARE_WORKFLOW: PreviewWorkflow = { name: 'Calendar', type: 'google_calendar', bgColor: '#FFFFFF', - position: { x: 380, y: 200 }, + position: { x: 380, y: 280 }, rows: [{ title: 'Account', value: '' }], }, ], @@ -1070,7 +1070,7 @@ export const CREDENTIAL_ROUTE_WORKFLOW: PreviewWorkflow = { name: 'Credential', type: 'credential', bgColor: '#6366F1', - position: { x: 700, y: 130 }, + position: { x: 700, y: 140 }, rows: [{ title: 'Account', value: 'Staging Slack' }], }, ], diff --git a/apps/docs/components/workflow-preview/format-references.tsx b/apps/docs/components/workflow-preview/format-references.tsx new file mode 100644 index 00000000000..6a3c01ae21e --- /dev/null +++ b/apps/docs/components/workflow-preview/format-references.tsx @@ -0,0 +1,27 @@ +import type { ReactNode } from 'react' + +/** Block references `` and environment variables `{{VAR}}`. */ +const REFERENCE_PATTERN = /(<[^<>]+>|\{\{[^{}]+\}\})/g + +/** + * Highlights `<...>` block references and `{{...}}` environment variables in + * brand-secondary, mirroring the editor's `formatDisplayText`. Read-only and + * static — no validation or tag interactivity, since docs has no workflow state. + */ +export function formatReferences(text: string): ReactNode[] { + if (!text) return [] + return text.split(REFERENCE_PATTERN).map((part, index) => { + if (!part) return null + const isReference = + (part.startsWith('<') && part.endsWith('>')) || (part.startsWith('{{') && part.endsWith('}}')) + return isReference ? ( + // biome-ignore lint/suspicious/noArrayIndexKey: static, never reordered + + {part} + + ) : ( + // biome-ignore lint/suspicious/noArrayIndexKey: static, never reordered + {part} + ) + }) +} diff --git a/apps/docs/components/workflow-preview/workflow-preview.tsx b/apps/docs/components/workflow-preview/workflow-preview.tsx index b42f4922b08..c7a1580a1a7 100644 --- a/apps/docs/components/workflow-preview/workflow-preview.tsx +++ b/apps/docs/components/workflow-preview/workflow-preview.tsx @@ -249,9 +249,11 @@ export function WorkflowPreview({ document.addEventListener('keydown', onKey) const previousOverflow = document.body.style.overflow document.body.style.overflow = 'hidden' + document.body.classList.add('wp-lightbox-open') return () => { document.removeEventListener('keydown', onKey) document.body.style.overflow = previousOverflow + document.body.classList.remove('wp-lightbox-open') } }, [expanded]) From a301a94f8b8cad846b73c5ab3066a28ddfbcbba7 Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 29 Jun 2026 18:07:11 -0700 Subject: [PATCH 14/30] fix(docs): make per-type field templates match the real block registry Audited every block type's field list (the source the inspector + block-reference heroes render) against apps/sim/blocks/blocks/*. Corrected drift to the registry's default-visible fields, titles, and order: agent gains Temperature; router gains Model; wait gains Async; schedule rewritten (default is Daily, not minutes); webhook_trigger expanded to its real default-visible set; human_in_the_loop notification title fixed. Provider-credential and advanced-mode fields stay hidden, matching the editor. Canvas diagrams keep their clean curated rows; the inspector now shows the full, real field list per the chosen clean-canvas/full-inspector split. --- .../block-display-workflows.ts | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/apps/docs/components/workflow-preview/block-display-workflows.ts b/apps/docs/components/workflow-preview/block-display-workflows.ts index 3d55b6ae491..aa4e2cf326c 100644 --- a/apps/docs/components/workflow-preview/block-display-workflows.ts +++ b/apps/docs/components/workflow-preview/block-display-workflows.ts @@ -37,6 +37,7 @@ export const BLOCK_DISPLAY_WORKFLOWS: Record = { { title: 'Tools', value: '-' }, { title: 'Skills', value: '-' }, { title: 'Memory', value: 'None' }, + { title: 'Temperature', value: '0.7' }, { title: 'Response Format', value: '-' }, ], }, @@ -187,7 +188,10 @@ export const BLOCK_DISPLAY_WORKFLOWS: Record = { type: 'router', bgColor: '#28C43F', position: { x: 0, y: 0 }, - rows: [{ title: 'Context', value: '-' }], + rows: [ + { title: 'Context', value: '-' }, + { title: 'Model', value: 'claude-sonnet-4-6' }, + ], branches: [{ id: 'route 1', label: 'route 1' }], }, ], @@ -219,8 +223,9 @@ export const BLOCK_DISPLAY_WORKFLOWS: Record = { bgColor: '#F59E0B', position: { x: 0, y: 0 }, rows: [ - { title: 'Wait Amount', value: '-' }, + { title: 'Wait Amount', value: '10' }, { title: 'Unit', value: 'Seconds' }, + { title: 'Async', value: '-' }, ], }, ], @@ -276,7 +281,7 @@ export const BLOCK_DISPLAY_WORKFLOWS: Record = { position: { x: 0, y: 0 }, rows: [ { title: 'Display Data', value: '-' }, - { title: 'Notification', value: '-' }, + { title: 'Notification (Send URL)', value: '-' }, { title: 'Resume Form', value: '-' }, ], }, @@ -295,8 +300,9 @@ export const BLOCK_DISPLAY_WORKFLOWS: Record = { position: { x: 0, y: 0 }, hideTargetHandle: true, rows: [ - { title: 'Run frequency', value: 'Every X Minutes' }, - { title: 'Interval (minutes)', value: '-' }, + { title: 'Run frequency', value: 'Daily' }, + { title: 'Time', value: '-' }, + { title: 'Timezone', value: '-' }, ], }, ], @@ -331,7 +337,12 @@ export const BLOCK_DISPLAY_WORKFLOWS: Record = { hideTargetHandle: true, rows: [ { title: 'Webhook URL', value: '-' }, - { title: 'Require Authentication', value: '-' }, + { title: 'Require Authentication', value: 'On' }, + { title: 'Authentication Token', value: '-' }, + { title: 'Secret Header Name (Optional)', value: '-' }, + { title: 'Deduplication Field (Optional)', value: '-' }, + { title: 'Acknowledgement', value: 'Default' }, + { title: 'Verify Test Events', value: '-' }, { title: 'Input Format', value: '-' }, ], }, From 24facdd79aa2feb7aa03b6a17b531e593178b28c Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 29 Jun 2026 18:13:58 -0700 Subject: [PATCH 15/30] improvement(docs): taller default preview height so respaced diagrams aren't shrunk Bumps the default WorkflowPreview height 260->300 (the respaced, editor-faithful blocks are taller, so fitView was shrinking diagrams that relied on the default). The tall how-it-runs routing diagram gets 400. --- apps/docs/components/workflow-preview/workflow-preview.tsx | 2 +- apps/docs/content/docs/en/workflows/how-it-runs.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/docs/components/workflow-preview/workflow-preview.tsx b/apps/docs/components/workflow-preview/workflow-preview.tsx index c7a1580a1a7..4b43fec1689 100644 --- a/apps/docs/components/workflow-preview/workflow-preview.tsx +++ b/apps/docs/components/workflow-preview/workflow-preview.tsx @@ -233,7 +233,7 @@ function PreviewFlow({ */ export function WorkflowPreview({ workflow, - height = 260, + height = 300, animate = false, highlightBlock, highlightEdge, diff --git a/apps/docs/content/docs/en/workflows/how-it-runs.mdx b/apps/docs/content/docs/en/workflows/how-it-runs.mdx index c437fe04051..9bc42f226da 100644 --- a/apps/docs/content/docs/en/workflows/how-it-runs.mdx +++ b/apps/docs/content/docs/en/workflows/how-it-runs.mdx @@ -36,7 +36,7 @@ The Function block here runs after both agents complete, with both of their outp A workflow can split. A [Condition](/workflows/blocks/condition) block branches on an explicit rule; a [Router](/workflows/blocks/router) block lets a model choose the path. Only the branch that is taken runs. A block on a branch that didn't run produces no output, which is why a [connection tag](/workflows/connections) pointing at it comes back empty. - + To repeat work, a [Loop](/workflows/blocks/loop) block runs its inner blocks over a list, a count, or while a condition holds, and a [Parallel](/workflows/blocks/parallel) block runs them for several items at once. From 184cf413b77aa479b5b917986c66e784708e2f42 Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 29 Jun 2026 18:19:04 -0700 Subject: [PATCH 16/30] improvement(docs): zoomable inline preview + taller default + themed controls The inline preview is now zoomable outside the lightbox: adds react-flow zoom/fit Controls (themed to the dark canvas chrome) and enables pinch-zoom, while keeping scroll-zoom off so the page still scrolls over the diagram. Pan-drag and click-block-to-inspect already worked. Default height 300->340. --- apps/docs/app/global.css | 25 +++++++++++++++++++ .../workflow-preview/workflow-preview.tsx | 6 +++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/apps/docs/app/global.css b/apps/docs/app/global.css index f46587678fc..a6d231dd9fc 100644 --- a/apps/docs/app/global.css +++ b/apps/docs/app/global.css @@ -36,6 +36,31 @@ body.wp-lightbox-open [aria-label="Ask AI"] { display: none; } +/* Workflow-preview zoom controls — match the canvas chrome (the default + react-flow controls are light-themed). */ +.react-flow__controls { + box-shadow: var(--shadow-subtle); + border: 1px solid var(--border-1); + border-radius: 8px; + overflow: hidden; +} +.react-flow__controls-button { + width: 26px; + height: 26px; + background: var(--surface-4); + border-bottom: 1px solid var(--border-1); + color: var(--text-icon); +} +.react-flow__controls-button:hover { + background: var(--surface-active); + color: var(--text-primary); +} +.react-flow__controls-button svg { + max-width: 12px; + max-height: 12px; + fill: currentColor; +} + /* Pure white light mode background */ :root:not(.dark) { --color-fd-background: hsl(0, 0%, 100%) !important; diff --git a/apps/docs/components/workflow-preview/workflow-preview.tsx b/apps/docs/components/workflow-preview/workflow-preview.tsx index 4b43fec1689..d99ae8f989e 100644 --- a/apps/docs/components/workflow-preview/workflow-preview.tsx +++ b/apps/docs/components/workflow-preview/workflow-preview.tsx @@ -8,6 +8,7 @@ import ReactFlow, { applyNodeChanges, Background, BackgroundVariant, + Controls, type Edge, type EdgeProps, type EdgeTypes, @@ -205,7 +206,7 @@ function PreviewFlow({ zoomOnScroll={interactive} zoomOnDoubleClick={interactive} panOnScroll={false} - zoomOnPinch={interactive} + zoomOnPinch panOnDrag preventScrolling={interactive} autoPanOnNodeDrag={false} @@ -216,6 +217,7 @@ function PreviewFlow({ className='h-full w-full' > + ) } @@ -233,7 +235,7 @@ function PreviewFlow({ */ export function WorkflowPreview({ workflow, - height = 300, + height = 340, animate = false, highlightBlock, highlightEdge, From 25522d2722d4615c7abfb7b11d1520482f8d3a97 Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 29 Jun 2026 18:20:20 -0700 Subject: [PATCH 17/30] improvement(docs): click canvas to expand; click empty lightbox to deselect Clicking the inline preview canvas opens the full lightbox; clicking empty space in the lightbox clears the selection, matching the real editor. --- apps/docs/components/workflow-preview/workflow-preview.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/docs/components/workflow-preview/workflow-preview.tsx b/apps/docs/components/workflow-preview/workflow-preview.tsx index d99ae8f989e..c31825435df 100644 --- a/apps/docs/components/workflow-preview/workflow-preview.tsx +++ b/apps/docs/components/workflow-preview/workflow-preview.tsx @@ -153,10 +153,12 @@ function PreviewFlow({ selectedBlock, interactive = false, onNodeClick, + onPaneClick, }: WorkflowPreviewProps & { selectedBlock?: string interactive?: boolean onNodeClick?: (blockId: string) => void + onPaneClick?: () => void }) { const { nodes: initialNodes, edges: initialEdges } = useMemo( () => toReactFlowElements(workflow, animate, { highlightBlock, highlightEdge, selectedBlock }), @@ -197,6 +199,7 @@ function PreviewFlow({ onNodesChange={onNodesChange} onEdgesChange={onEdgesChange} onNodeClick={onNodeClick ? (_, node) => onNodeClick(node.id) : undefined} + onPaneClick={onPaneClick} nodeTypes={NODE_TYPES} edgeTypes={EDGE_TYPES} defaultEdgeOptions={{ type: 'previewEdge' }} @@ -281,6 +284,7 @@ export function WorkflowPreview({ highlightBlock={highlightBlock} highlightEdge={highlightEdge} onNodeClick={(id) => openWith(id)} + onPaneClick={() => openWith(null)} />
From a3c089e82a96ea92e339b068bf5d29c4e971f85c Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 29 Jun 2026 18:21:57 -0700 Subject: [PATCH 18/30] improvement(docs): reveal inline zoom controls on hover only The always-visible zoom controls felt heavy on the inline preview; they now fade in on hover (matching the expand button) and stay visible in the lightbox. --- apps/docs/app/global.css | 9 +++++++++ .../components/workflow-preview/workflow-preview.tsx | 6 +++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/docs/app/global.css b/apps/docs/app/global.css index a6d231dd9fc..87c90ef7278 100644 --- a/apps/docs/app/global.css +++ b/apps/docs/app/global.css @@ -60,6 +60,15 @@ body.wp-lightbox-open [aria-label="Ask AI"] { max-height: 12px; fill: currentColor; } +/* On the inline preview, reveal the zoom controls only on hover (the .group is + the preview wrapper); in the lightbox they stay visible. */ +.wp-controls-subtle { + opacity: 0; + transition: opacity 150ms ease; +} +.group:hover .wp-controls-subtle { + opacity: 1; +} /* Pure white light mode background */ :root:not(.dark) { diff --git a/apps/docs/components/workflow-preview/workflow-preview.tsx b/apps/docs/components/workflow-preview/workflow-preview.tsx index c31825435df..4ee2c5cf2b9 100644 --- a/apps/docs/components/workflow-preview/workflow-preview.tsx +++ b/apps/docs/components/workflow-preview/workflow-preview.tsx @@ -220,7 +220,11 @@ function PreviewFlow({ className='h-full w-full' > - + ) } From 1eaa3f5dacd9de2e08185490403cfe97a9eeb276 Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 29 Jun 2026 18:23:33 -0700 Subject: [PATCH 19/30] improvement(docs): drop zoom controls on the inline preview Inline preview keeps pinch-zoom, pan, drag, and click-to-expand; zoom buttons stay in the lightbox only. --- apps/docs/app/global.css | 9 --------- .../components/workflow-preview/workflow-preview.tsx | 6 +----- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/apps/docs/app/global.css b/apps/docs/app/global.css index 87c90ef7278..a6d231dd9fc 100644 --- a/apps/docs/app/global.css +++ b/apps/docs/app/global.css @@ -60,15 +60,6 @@ body.wp-lightbox-open [aria-label="Ask AI"] { max-height: 12px; fill: currentColor; } -/* On the inline preview, reveal the zoom controls only on hover (the .group is - the preview wrapper); in the lightbox they stay visible. */ -.wp-controls-subtle { - opacity: 0; - transition: opacity 150ms ease; -} -.group:hover .wp-controls-subtle { - opacity: 1; -} /* Pure white light mode background */ :root:not(.dark) { diff --git a/apps/docs/components/workflow-preview/workflow-preview.tsx b/apps/docs/components/workflow-preview/workflow-preview.tsx index 4ee2c5cf2b9..6285cb6ba0e 100644 --- a/apps/docs/components/workflow-preview/workflow-preview.tsx +++ b/apps/docs/components/workflow-preview/workflow-preview.tsx @@ -220,11 +220,7 @@ function PreviewFlow({ className='h-full w-full' > - + {interactive && } ) } From c7947140ba1dcfa60fc6a9242a830ae5f57338dc Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 29 Jun 2026 18:27:34 -0700 Subject: [PATCH 20/30] improvement(docs): remove zoom controls from the lightbox too Both previews zoom via scroll/pinch and pan via drag; no on-canvas zoom buttons. Drops the Controls import and its theming CSS. --- apps/docs/app/global.css | 25 ------------------- .../workflow-preview/workflow-preview.tsx | 2 -- 2 files changed, 27 deletions(-) diff --git a/apps/docs/app/global.css b/apps/docs/app/global.css index a6d231dd9fc..f46587678fc 100644 --- a/apps/docs/app/global.css +++ b/apps/docs/app/global.css @@ -36,31 +36,6 @@ body.wp-lightbox-open [aria-label="Ask AI"] { display: none; } -/* Workflow-preview zoom controls — match the canvas chrome (the default - react-flow controls are light-themed). */ -.react-flow__controls { - box-shadow: var(--shadow-subtle); - border: 1px solid var(--border-1); - border-radius: 8px; - overflow: hidden; -} -.react-flow__controls-button { - width: 26px; - height: 26px; - background: var(--surface-4); - border-bottom: 1px solid var(--border-1); - color: var(--text-icon); -} -.react-flow__controls-button:hover { - background: var(--surface-active); - color: var(--text-primary); -} -.react-flow__controls-button svg { - max-width: 12px; - max-height: 12px; - fill: currentColor; -} - /* Pure white light mode background */ :root:not(.dark) { --color-fd-background: hsl(0, 0%, 100%) !important; diff --git a/apps/docs/components/workflow-preview/workflow-preview.tsx b/apps/docs/components/workflow-preview/workflow-preview.tsx index 6285cb6ba0e..fcce50fb4f5 100644 --- a/apps/docs/components/workflow-preview/workflow-preview.tsx +++ b/apps/docs/components/workflow-preview/workflow-preview.tsx @@ -8,7 +8,6 @@ import ReactFlow, { applyNodeChanges, Background, BackgroundVariant, - Controls, type Edge, type EdgeProps, type EdgeTypes, @@ -220,7 +219,6 @@ function PreviewFlow({ className='h-full w-full' > - {interactive && } ) } From dc688b0d1452c3aa3a860b15466a74096df760e3 Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 29 Jun 2026 18:35:30 -0700 Subject: [PATCH 21/30] =?UTF-8?q?improvement(docs):=20match=20the=20real?= =?UTF-8?q?=20canvas=20=E2=80=94=20flat=20background=20+=20editor=20edge?= =?UTF-8?q?=20geometry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the last faithfulness gaps the audit found: removes the dot grid (the real editor hides its background — flat bg), aligns PreviewEdge to the editor's smoothstep math (borderRadius 8, offset 30) and 2px stroke (default + error edges), the selection ring to 1.75px, and minZoom to 0.1. Structural parity (blocks/handles/containers/colors/tokens) was already shared. Kept PreviewEdge rather than swapping to WorkflowEdgeView, which would clobber the docs-only highlight/dim/animate for no visual gain. --- .../components/workflow-preview/docs-block-node.tsx | 2 +- apps/docs/components/workflow-preview/workflow-data.ts | 4 ++-- .../components/workflow-preview/workflow-preview.tsx | 10 ++++------ 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/apps/docs/components/workflow-preview/docs-block-node.tsx b/apps/docs/components/workflow-preview/docs-block-node.tsx index e31386e74b7..0ea73af8512 100644 --- a/apps/docs/components/workflow-preview/docs-block-node.tsx +++ b/apps/docs/components/workflow-preview/docs-block-node.tsx @@ -14,7 +14,7 @@ import { /** Renders the colored square with no glyph when a block type has no registered icon. */ const EMPTY_ICON: ComponentType<{ className?: string }> = () => null -const RING_STYLES = 'ring-2 ring-[var(--brand-secondary)]' +const RING_STYLES = 'ring-[1.75px] ring-[var(--brand-secondary)]' interface DocsBlockData { name: string diff --git a/apps/docs/components/workflow-preview/workflow-data.ts b/apps/docs/components/workflow-preview/workflow-data.ts index a26f24206eb..330ecd0fa33 100644 --- a/apps/docs/components/workflow-preview/workflow-data.ts +++ b/apps/docs/components/workflow-preview/workflow-data.ts @@ -48,10 +48,10 @@ export interface PreviewWorkflow { export const BLOCK_STAGGER = 0.12 export const EASE_OUT: [number, number, number, number] = [0.16, 1, 0.3, 1] -const EDGE_STYLE = { stroke: 'var(--workflow-edge)', strokeWidth: 1.5 } as const +const EDGE_STYLE = { stroke: 'var(--workflow-edge)', strokeWidth: 2 } as const const EDGE_STYLE_HIGHLIGHT = { stroke: 'var(--brand-secondary)', strokeWidth: 2.5 } as const /** Edges leaving a block's error port render red, matching the editor. */ -const EDGE_STYLE_ERROR = { stroke: 'var(--text-error)', strokeWidth: 1.5 } as const +const EDGE_STYLE_ERROR = { stroke: 'var(--text-error)', strokeWidth: 2 } as const /** Optional emphasis: light one block or one edge and dim everything else. */ export interface HighlightOptions { diff --git a/apps/docs/components/workflow-preview/workflow-preview.tsx b/apps/docs/components/workflow-preview/workflow-preview.tsx index fcce50fb4f5..ee6002beb51 100644 --- a/apps/docs/components/workflow-preview/workflow-preview.tsx +++ b/apps/docs/components/workflow-preview/workflow-preview.tsx @@ -6,8 +6,6 @@ import { Maximize2, X } from 'lucide-react' import ReactFlow, { applyEdgeChanges, applyNodeChanges, - Background, - BackgroundVariant, type Edge, type EdgeProps, type EdgeTypes, @@ -60,6 +58,8 @@ function PreviewEdge({ targetY, sourcePosition, targetPosition, + borderRadius: 8, + offset: 30, }) if (data?.animate) { @@ -213,13 +213,11 @@ function PreviewFlow({ preventScrolling={interactive} autoPanOnNodeDrag={false} proOptions={PRO_OPTIONS} - minZoom={0.2} + minZoom={0.1} fitView fitViewOptions={interactive ? LIGHTBOX_FIT_VIEW_OPTIONS : FIT_VIEW_OPTIONS} className='h-full w-full' - > - - + /> ) } From b83b0fb6dc59c0183f808684d5f02ab25b561d0c Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 29 Jun 2026 18:45:01 -0700 Subject: [PATCH 22/30] improvement(docs): rebrand the docs assistant as 'Ask Sim', styled like the real chat input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renames the floating assistant from 'Ask AI' to 'Ask Sim' (matching the platform's voice — you talk to Sim) and restyles the composer to mirror the home chat input: a rounded-2xl bordered field with the toolbar inside, and the same 28px circular send/stop button (the home's exact active/disabled colors + white/black arrow). Updates the lightbox hide-selector to the new label. --- apps/docs/app/global.css | 4 +- apps/docs/components/ai/ask-ai.tsx | 84 ++++++++++++++++-------------- 2 files changed, 47 insertions(+), 41 deletions(-) diff --git a/apps/docs/app/global.css b/apps/docs/app/global.css index f46587678fc..cbfd2ef2639 100644 --- a/apps/docs/app/global.css +++ b/apps/docs/app/global.css @@ -31,8 +31,8 @@ body { --text-md: 16px; } -/* Hide the floating Ask AI widget while a workflow-preview lightbox is open. */ -body.wp-lightbox-open [aria-label="Ask AI"] { +/* Hide the floating Ask Sim widget while a workflow-preview lightbox is open. */ +body.wp-lightbox-open [aria-label="Ask Sim"] { display: none; } diff --git a/apps/docs/components/ai/ask-ai.tsx b/apps/docs/components/ai/ask-ai.tsx index dca3886305f..97b84dd4bd6 100644 --- a/apps/docs/components/ai/ask-ai.tsx +++ b/apps/docs/components/ai/ask-ai.tsx @@ -82,12 +82,12 @@ export function AskAI({ locale }: AskAIProps) { {!open && ( )} @@ -96,7 +96,7 @@ export function AskAI({ locale }: AskAIProps) {
- Ask AI + Ask Sim