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 (