const WorkflowViewer = ({ onNav }) => { const nodes = [ { id: 'trigger', x: 60, y: 260, title: 'Weekly Schedule', type: 'trigger', badge: 'CRON', icon: 'clock', tone: '#22C55E', inputs: [], outputs: [{k: 'timestamp', t: 'ISO8601'}, {k: 'runId', t: 'string'}] }, { id: 'fetch', x: 320, y: 140, title: 'Fetch Accounts', type: 'database.query', badge: 'DB', icon: 'database', tone: '#3B82F6', inputs: [{k: 'segment', t: 'string'}], outputs: [{k: 'accounts', t: 'Account[]'}, {k: 'count', t: 'number'}] }, { id: 'events', x: 320, y: 380, title: 'Pull Events', type: 'segment.events', badge: 'API', icon: 'bolt', tone: '#F59E0B', inputs: [{k: 'since', t: 'ISO8601'}], outputs: [{k: 'events', t: 'Event[]'}] }, { id: 'summarize', x: 600, y: 260, title: 'Summarize with Claude', type: 'ai.completion', badge: 'AI', icon: 'spark', tone: '#8B5CF6', inputs: [{k: 'accounts', t: 'Account[]'}, {k: 'events', t: 'Event[]'}, {k: 'prompt', t: 'string'}], outputs: [{k: 'digest', t: 'Digest'}, {k: 'tokens', t: 'number'}] }, { id: 'filter', x: 880, y: 160, title: 'Filter — priority ≥ 2', type: 'logic.filter', badge: 'IF', icon: 'filter', tone: '#64748B', inputs: [{k: 'digest', t: 'Digest'}], outputs: [{k: 'items', t: 'Item[]'}] }, { id: 'slack', x: 1140, y: 100, title: 'Post to #revenue', type: 'slack.message', badge: 'OUT', icon: 'slack', tone: '#EC4899', inputs: [{k: 'channel', t: 'string'}, {k: 'blocks', t: 'Block[]'}], outputs: [{k: 'ts', t: 'string'}] }, { id: 'mail', x: 1140, y: 260, title: 'Email Digest', type: 'mail.send', badge: 'OUT', icon: 'mail', tone: '#6366F1', inputs: [{k: 'to', t: 'string[]'}, {k: 'subject', t: 'string'}, {k: 'html', t: 'string'}], outputs: [{k: 'messageId', t: 'string'}] }, { id: 'log', x: 880, y: 400, title: 'Log run metadata', type: 'arcrun.log', badge: 'LOG', icon: 'terminal', tone: '#475569', inputs: [{k: 'runId', t: 'string'}, {k: 'stats', t: 'Stats'}], outputs: [] }, ]; const edges = [ ['trigger', 'fetch'], ['trigger', 'events'], ['fetch', 'summarize'], ['events', 'summarize'], ['summarize', 'filter'], ['summarize', 'log'], ['filter', 'slack'], ['filter', 'mail'], ]; const [selectedId, setSelectedId] = React.useState('summarize'); const [title, setTitle] = React.useState('digest/weekly'); const [zoom, setZoom] = React.useState(100); const selected = nodes.find(n => n.id === selectedId); // Edit triplet inline (for the summarize node's prompt config) const [triplet, setTriplet] = React.useState({ model: 'claude-haiku-4-5', temperature: '0.3', prompt: 'Summarize this week\'s account activity for the revenue team.', }); // Measure node widths for edge endpoint accuracy const nodeRefs = React.useRef({}); const [sizes, setSizes] = React.useState({}); React.useEffect(() => { const ns = {}; for (const n of nodes) { const el = nodeRefs.current[n.id]; if (el) ns[n.id] = { w: el.offsetWidth, h: el.offsetHeight }; } setSizes(ns); }, []); const getPort = (id, side) => { const n = nodes.find(x => x.id === id); const sz = sizes[id] || { w: 200, h: 60 }; return { x: side === 'out' ? n.x + sz.w : n.x, y: n.y + sz.h / 2, }; }; return (
onNav('dashboard')} title="Back to dashboard">
onNav('landing')} />
onNav('dashboard')}>Workflows setTitle(e.target.value)} />
Saved · 2m ago
{edges.map(([a, b], i) => { const p1 = getPort(a, 'out'); const p2 = getPort(b, 'in'); const dx = Math.max(40, (p2.x - p1.x) * 0.5); const d = `M ${p1.x} ${p1.y} C ${p1.x + dx} ${p1.y}, ${p2.x - dx} ${p2.y}, ${p2.x - 2} ${p2.y}`; const highlight = a === selectedId || b === selectedId; return ( ); })}
{nodes.map(n => (
(nodeRefs.current[n.id] = el)} className={`wf-node ${selectedId === n.id ? 'selected' : ''}`} style={{left: n.x, top: n.y}} onClick={() => setSelectedId(n.id)}> {n.inputs.length > 0 && } {n.outputs.length > 0 && }
{n.title} {n.badge}
{n.type}
))}
{/* Detail panel */} {selected && (

{selected.title}

{selected.type}

Input schema

{selected.inputs.length === 0 ? (
No inputs — this is a trigger.
) : selected.inputs.map(f => (
{f.k} {f.t}
))}

Output schema

{selected.outputs.length === 0 ? (
No outputs — terminal node.
) : selected.outputs.map(f => (
{f.k} {f.t}
))}
{selected.id === 'summarize' && (

Configuration

model
setTriplet({...triplet, model: e.target.value})} />
temp
setTriplet({...triplet, temperature: e.target.value})} />
prompt
setTriplet({...triplet, prompt: e.target.value})} />
)}

Last run

Duration
2.4s
Status
success
)} {/* Minimap */}
Overview
{nodes.map(n => { const sz = sizes[n.id] || {w: 180, h: 60}; return (
); })}
{/* Zoom controls */}
{zoom}%
); }; window.WorkflowViewer = WorkflowViewer;