@majdibo/flow-visualizer 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +339 -0
- package/dist/flow-visualizer.css +1 -0
- package/dist/flow-visualizer.es.js +5686 -0
- package/dist/flow-visualizer.umd.js +6 -0
- package/dist/logo.svg +24 -0
- package/package.json +60 -0
- package/public/logo.svg +24 -0
- package/src/FlowVisualizer.tsx +257 -0
- package/src/NodeDetailModal.tsx +63 -0
- package/src/index.css +119 -0
- package/src/index.ts +26 -0
- package/src/layoutEngine.ts +141 -0
- package/src/modal.css +153 -0
- package/src/theme.css +225 -0
- package/src/vite-env.d.ts +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
# Flow Visualizer
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="./public/logo.svg" alt="Flow Visualizer Logo" width="120" height="120">
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>Beautiful React components for visualizing execution flows and timelines</strong>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://www.npmjs.com/package/@majdibo/flow-visualizer">
|
|
13
|
+
<img src="https://img.shields.io/npm/v/@majdibo/flow-visualizer.svg" alt="npm version">
|
|
14
|
+
</a>
|
|
15
|
+
<a href="https://github.com/majdibo/flow-visualizer/blob/main/LICENSE">
|
|
16
|
+
<img src="https://img.shields.io/npm/l/@majdibo/flow-visualizer.svg" alt="license">
|
|
17
|
+
</a>
|
|
18
|
+
<a href="https://www.npmjs.com/package/@majdibo/flow-visualizer">
|
|
19
|
+
<img src="https://img.shields.io/npm/dm/@majdibo/flow-visualizer.svg" alt="downloads">
|
|
20
|
+
</a>
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
<p align="center">
|
|
24
|
+
<a href="https://majdibo.github.io/flow-visualizer">🎮 Try the Interactive Playground</a>
|
|
25
|
+
</p>
|
|
26
|
+
|
|
27
|
+
## Why Flow Visualizer?
|
|
28
|
+
|
|
29
|
+
Visualizing complex execution flows shouldn't require building a custom graph renderer from scratch. Flow Visualizer gives you production-ready React components to display:
|
|
30
|
+
|
|
31
|
+
- **Workflow executions** - Show users how their automations run
|
|
32
|
+
- **State machines** - Visualize state transitions and flows
|
|
33
|
+
- **Process traces** - Debug and monitor system processes
|
|
34
|
+
- **AI agent reasoning** - Display agent decision trees and tool usage
|
|
35
|
+
- **Build pipelines** - Show CI/CD execution flows
|
|
36
|
+
- **Any trace-based execution** - Generic enough for any sequential process
|
|
37
|
+
|
|
38
|
+
Built on top of [`@majdibo/flow-engine`](https://github.com/majdibo/flow-engine), it handles the hard parts (graph layout, state management, timeline tracking) so you can focus on your application.
|
|
39
|
+
|
|
40
|
+
## ✨ Features
|
|
41
|
+
|
|
42
|
+
- 🎨 **Beautiful by default** - Modern UI with light/dark mode support
|
|
43
|
+
- ⚛️ **React native** - Drop-in components, fully typed with TypeScript
|
|
44
|
+
- 🖱️ **Interactive** - Zoom, pan, and explore with smooth animations
|
|
45
|
+
- 📊 **Auto-layout** - Powered by Dagre for perfect graph positioning
|
|
46
|
+
- ⏱️ **Timeline support** - Replay executions step-by-step
|
|
47
|
+
- 🎯 **Customizable** - Override styles and customize node displays
|
|
48
|
+
- 📦 **Zero config** - Works out of the box with sensible defaults
|
|
49
|
+
|
|
50
|
+
## 🚀 Installation
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm install @majdibo/flow-visualizer
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Or with yarn:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
yarn add @majdibo/flow-visualizer
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
> **Note:** `@majdibo/flow-engine` is a required peer dependency for graph construction and state management.
|
|
63
|
+
|
|
64
|
+
## 📖 Quick Start
|
|
65
|
+
|
|
66
|
+
### Basic Example
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
import { FlowVisualizer, computeLayout } from '@majdibo/flow-visualizer';
|
|
70
|
+
import { buildGraphFromTraces, computeVisualState } from '@majdibo/flow-engine';
|
|
71
|
+
import { useState } from 'react';
|
|
72
|
+
|
|
73
|
+
function MyFlowApp() {
|
|
74
|
+
// Your execution traces
|
|
75
|
+
const traces = [
|
|
76
|
+
{ step: 'start', nodeType: 'action', data: { msg: 'Starting' }, timestamp: Date.now() },
|
|
77
|
+
{ step: 'process', nodeType: 'action', data: { msg: 'Processing' }, timestamp: Date.now() },
|
|
78
|
+
{ step: 'end', nodeType: 'action', data: { msg: 'Done' }, timestamp: Date.now() }
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
const [currentIndex, setCurrentIndex] = useState(0);
|
|
82
|
+
|
|
83
|
+
// Build graph from traces
|
|
84
|
+
const graph = buildGraphFromTraces(traces);
|
|
85
|
+
const timeline = traces.map(t => t.step);
|
|
86
|
+
|
|
87
|
+
// Compute layout
|
|
88
|
+
const { nodes, edges } = computeLayout(graph);
|
|
89
|
+
|
|
90
|
+
// Compute visual state
|
|
91
|
+
const visualState = computeVisualState(graph, timeline, currentIndex);
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<div>
|
|
95
|
+
<FlowVisualizer
|
|
96
|
+
nodes={nodes}
|
|
97
|
+
edges={edges}
|
|
98
|
+
state={visualState}
|
|
99
|
+
onNodeClick={(nodeId) => console.log('Clicked:', nodeId)}
|
|
100
|
+
/>
|
|
101
|
+
|
|
102
|
+
{/* Timeline controls */}
|
|
103
|
+
<button onClick={() => setCurrentIndex(Math.max(0, currentIndex - 1))}>
|
|
104
|
+
Previous
|
|
105
|
+
</button>
|
|
106
|
+
<button onClick={() => setCurrentIndex(Math.min(timeline.length - 1, currentIndex + 1))}>
|
|
107
|
+
Next
|
|
108
|
+
</button>
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### With Custom Node Details
|
|
115
|
+
|
|
116
|
+
Show detailed information when users click on nodes:
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
import { FlowVisualizer, NodeDetailModal } from '@majdibo/flow-visualizer';
|
|
120
|
+
import { useState } from 'react';
|
|
121
|
+
|
|
122
|
+
function MyApp() {
|
|
123
|
+
const [selectedNode, setSelectedNode] = useState<string | null>(null);
|
|
124
|
+
|
|
125
|
+
// Customize how trace details are displayed
|
|
126
|
+
const renderTraceDetails = (trace: any, traceIndex: number) => (
|
|
127
|
+
<div className="custom-trace-card">
|
|
128
|
+
<h4>Step {traceIndex + 1}</h4>
|
|
129
|
+
<p>{trace.step}</p>
|
|
130
|
+
<pre>{JSON.stringify(trace.data, null, 2)}</pre>
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<>
|
|
136
|
+
<FlowVisualizer
|
|
137
|
+
nodes={nodes}
|
|
138
|
+
edges={edges}
|
|
139
|
+
state={visualState}
|
|
140
|
+
onNodeClick={setSelectedNode}
|
|
141
|
+
/>
|
|
142
|
+
|
|
143
|
+
{selectedNode && (
|
|
144
|
+
<NodeDetailModal
|
|
145
|
+
nodeId={selectedNode}
|
|
146
|
+
nodes={nodes}
|
|
147
|
+
graph={graph}
|
|
148
|
+
traces={traces}
|
|
149
|
+
visualState={visualState}
|
|
150
|
+
icons={{}}
|
|
151
|
+
onClose={() => setSelectedNode(null)}
|
|
152
|
+
renderTraceDetails={renderTraceDetails}
|
|
153
|
+
/>
|
|
154
|
+
)}
|
|
155
|
+
</>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Custom Layout Options
|
|
161
|
+
|
|
162
|
+
Control how your graph is laid out:
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
const { nodes, edges } = computeLayout(graph, {
|
|
166
|
+
direction: 'LR', // Left-to-right layout
|
|
167
|
+
nodeSep: 100, // Horizontal spacing
|
|
168
|
+
rankSep: 150, // Vertical spacing
|
|
169
|
+
edgeStyle: 'orthogonal' // Right-angle edges
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## 📚 API Reference
|
|
174
|
+
|
|
175
|
+
### `<FlowVisualizer />`
|
|
176
|
+
|
|
177
|
+
The main component for rendering interactive flow diagrams.
|
|
178
|
+
|
|
179
|
+
| Prop | Type | Description |
|
|
180
|
+
|------|------|-------------|
|
|
181
|
+
| `nodes` | `VisualNode[]` | Array of nodes with positions (from `computeLayout`) |
|
|
182
|
+
| `edges` | `VisualEdge[]` | Array of edges with paths (from `computeLayout`) |
|
|
183
|
+
| `state` | `VisualState` | Current state (from `computeVisualState`) |
|
|
184
|
+
| `onNodeClick?` | `(nodeId: string) => void` | Called when a node is clicked |
|
|
185
|
+
|
|
186
|
+
**Interactions:**
|
|
187
|
+
- **Mouse wheel** - Zoom in/out
|
|
188
|
+
- **Click + drag** - Pan around the graph
|
|
189
|
+
- **Double-click** - Reset zoom and position
|
|
190
|
+
- **Click node** - Trigger `onNodeClick` callback
|
|
191
|
+
|
|
192
|
+
### `<NodeDetailModal />`
|
|
193
|
+
|
|
194
|
+
Display detailed information about a selected node in a modal.
|
|
195
|
+
|
|
196
|
+
| Prop | Type | Description |
|
|
197
|
+
|------|------|-------------|
|
|
198
|
+
| `nodeId` | `string` | ID of the node to display |
|
|
199
|
+
| `nodes` | `VisualNode[]` | Array of visual nodes |
|
|
200
|
+
| `graph` | `FlowGraph` | The flow graph |
|
|
201
|
+
| `traces` | `Trace[]` | Array of execution traces |
|
|
202
|
+
| `visualState` | `VisualState` | Current visual state |
|
|
203
|
+
| `icons` | `Record<string, ReactNode>` | Icon mapping for node types |
|
|
204
|
+
| `onClose` | `() => void` | Called when modal is closed |
|
|
205
|
+
| `renderTraceDetails?` | `(trace, index) => ReactNode` | Custom trace renderer |
|
|
206
|
+
|
|
207
|
+
### `computeLayout(graph, options?)`
|
|
208
|
+
|
|
209
|
+
Compute positions for nodes and paths for edges using automatic layout.
|
|
210
|
+
|
|
211
|
+
**Parameters:**
|
|
212
|
+
- `graph` - FlowGraph from `buildGraphFromTraces`
|
|
213
|
+
- `options?` - Layout configuration:
|
|
214
|
+
- `direction` - Layout direction: `'TB'` (top-bottom), `'LR'` (left-right), `'BT'` (bottom-top), `'RL'` (right-left). Default: `'TB'`
|
|
215
|
+
- `nodeSep` - Horizontal spacing between nodes. Default: `80`
|
|
216
|
+
- `rankSep` - Vertical spacing between ranks. Default: `120`
|
|
217
|
+
- `edgeStyle` - Edge routing style: `'polyline'` or `'orthogonal'`. Default: `'polyline'`
|
|
218
|
+
|
|
219
|
+
**Returns:** `{ nodes: VisualNode[], edges: VisualEdge[] }`
|
|
220
|
+
|
|
221
|
+
## 🎨 Styling & Theming
|
|
222
|
+
|
|
223
|
+
Flow Visualizer includes beautiful default styles and automatically adapts to light/dark mode based on user preferences (`prefers-color-scheme`).
|
|
224
|
+
|
|
225
|
+
### Customizing Colors
|
|
226
|
+
|
|
227
|
+
Override CSS variables to match your brand:
|
|
228
|
+
|
|
229
|
+
```css
|
|
230
|
+
:root {
|
|
231
|
+
/* Light mode */
|
|
232
|
+
--flow-node-current-stroke: #171717;
|
|
233
|
+
--flow-node-traversed-fill: #f5f5f5;
|
|
234
|
+
--flow-edge-current: #171717;
|
|
235
|
+
--flow-text-primary: #171717;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
@media (prefers-color-scheme: dark) {
|
|
239
|
+
:root {
|
|
240
|
+
/* Dark mode */
|
|
241
|
+
--flow-node-current-stroke: #f5f5f5;
|
|
242
|
+
--flow-node-traversed-fill: #262626;
|
|
243
|
+
--flow-edge-current: #f5f5f5;
|
|
244
|
+
--flow-text-primary: #f5f5f5;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Available CSS Variables
|
|
250
|
+
|
|
251
|
+
| Variable | Description |
|
|
252
|
+
|----------|-------------|
|
|
253
|
+
| `--flow-bg` | Background color |
|
|
254
|
+
| `--flow-node-current-fill` | Current node background |
|
|
255
|
+
| `--flow-node-current-stroke` | Current node border |
|
|
256
|
+
| `--flow-node-traversed-fill` | Completed node background |
|
|
257
|
+
| `--flow-node-untraversed-fill` | Pending node background |
|
|
258
|
+
| `--flow-edge-current` | Current edge color |
|
|
259
|
+
| `--flow-edge-traversed` | Completed edge color |
|
|
260
|
+
| `--flow-text-primary` | Primary text color |
|
|
261
|
+
| `--flow-text-secondary` | Secondary text color |
|
|
262
|
+
| `--flow-gradient-start` | Gradient start color |
|
|
263
|
+
| `--flow-gradient-end` | Gradient end color |
|
|
264
|
+
|
|
265
|
+
## 🔧 TypeScript Support
|
|
266
|
+
|
|
267
|
+
Flow Visualizer is written in TypeScript and includes full type definitions:
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
import type {
|
|
271
|
+
VisualNode,
|
|
272
|
+
VisualEdge,
|
|
273
|
+
LayoutOptions,
|
|
274
|
+
LayoutResult,
|
|
275
|
+
FlowVisualizerProps,
|
|
276
|
+
NodeDetailModalProps
|
|
277
|
+
} from '@majdibo/flow-visualizer';
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
All components and functions are fully typed for the best developer experience.
|
|
281
|
+
|
|
282
|
+
## 🤝 Contributing
|
|
283
|
+
|
|
284
|
+
We welcome contributions! Here's how you can help:
|
|
285
|
+
|
|
286
|
+
1. **Fork the repository**
|
|
287
|
+
2. **Create a feature branch** (`git checkout -b feature/amazing-feature`)
|
|
288
|
+
3. **Commit your changes** (`git commit -m 'Add amazing feature'`)
|
|
289
|
+
4. **Push to the branch** (`git push origin feature/amazing-feature`)
|
|
290
|
+
5. **Open a Pull Request**
|
|
291
|
+
|
|
292
|
+
### Development Setup
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
# Clone the repo
|
|
296
|
+
git clone https://github.com/majdibo/flow-visualizer.git
|
|
297
|
+
cd flow-visualizer
|
|
298
|
+
|
|
299
|
+
# Install dependencies
|
|
300
|
+
npm install
|
|
301
|
+
|
|
302
|
+
# Start development server
|
|
303
|
+
npm run dev
|
|
304
|
+
|
|
305
|
+
# Build the library
|
|
306
|
+
npm run build
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Reporting Issues
|
|
310
|
+
|
|
311
|
+
Found a bug or have a feature request? [Open an issue](https://github.com/majdibo/flow-visualizer/issues) on GitHub.
|
|
312
|
+
|
|
313
|
+
## 📦 Related Projects
|
|
314
|
+
|
|
315
|
+
- [`@majdibo/flow-engine`](https://github.com/majdibo/flow-engine) - Core graph engine (required dependency)
|
|
316
|
+
|
|
317
|
+
## 🙏 Acknowledgments
|
|
318
|
+
|
|
319
|
+
Built with:
|
|
320
|
+
- [React](https://react.dev/) - UI library
|
|
321
|
+
- [Dagre](https://github.com/dagrejs/dagre) - Graph layout
|
|
322
|
+
- [TypeScript](https://www.typescriptlang.org/) - Type safety
|
|
323
|
+
- [Tailwind CSS](https://tailwindcss.com/) - Styling
|
|
324
|
+
|
|
325
|
+
## 📄 License
|
|
326
|
+
|
|
327
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
<p align="center">
|
|
332
|
+
Made with ❤️ by <a href="https://github.com/majdibo">majdibo</a>
|
|
333
|
+
</p>
|
|
334
|
+
|
|
335
|
+
<p align="center">
|
|
336
|
+
<a href="https://github.com/majdibo/flow-visualizer">GitHub</a> •
|
|
337
|
+
<a href="https://www.npmjs.com/package/@majdibo/flow-visualizer">npm</a> •
|
|
338
|
+
<a href="https://github.com/majdibo/flow-visualizer/issues">Issues</a>
|
|
339
|
+
</p>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--flow-bg: transparent;--flow-node-current-fill: url(#gradient-current);--flow-node-current-stroke: #0F766E;--flow-node-current-shadow: drop-shadow(0 4px 16px rgba(42, 157, 143, .4)) drop-shadow(0 0 24px rgba(42, 157, 143, .2));--flow-node-traversed-fill: #F1F5F9;--flow-node-traversed-stroke: #CBD5E1;--flow-node-traversed-shadow: drop-shadow(0 2px 4px rgba(15, 23, 42, .08));--flow-node-untraversed-fill: #FFFFFF;--flow-node-untraversed-stroke: #E2E8F0;--flow-node-untraversed-shadow: drop-shadow(0 1px 3px rgba(15, 23, 42, .06));--flow-edge-current: #14B8A6;--flow-edge-current-glow: rgba(20, 184, 166, .3);--flow-edge-traversed: #94A3B8;--flow-edge-untraversed: #E2E8F0;--flow-text-primary: #0F172A;--flow-text-secondary: #64748B;--flow-text-current: #FFFFFF;--flow-gradient-start: #14B8A6;--flow-gradient-end: #0D9488;--flow-node-action: #2A9D8F;--flow-node-decision: #457B9D;--flow-node-tool: #E76F51;--flow-node-memory: #F4A261}.dark{--flow-node-current-fill: url(#gradient-current);--flow-node-current-stroke: #5EEAD4;--flow-node-current-shadow: drop-shadow(0 4px 20px rgba(61, 186, 162, .5)) drop-shadow(0 0 32px rgba(61, 186, 162, .3));--flow-node-traversed-fill: #334155;--flow-node-traversed-stroke: #475569;--flow-node-traversed-shadow: drop-shadow(0 2px 6px rgba(0, 0, 0, .4));--flow-node-untraversed-fill: #1E293B;--flow-node-untraversed-stroke: #334155;--flow-node-untraversed-shadow: drop-shadow(0 1px 3px rgba(0, 0, 0, .5));--flow-edge-current: #5EEAD4;--flow-edge-current-glow: rgba(94, 234, 212, .4);--flow-edge-traversed: #64748B;--flow-edge-untraversed: #334155;--flow-text-primary: #F1F5F9;--flow-text-secondary: #94A3B8;--flow-text-current: #0F172A;--flow-gradient-start: #5EEAD4;--flow-gradient-end: #2DD4BF;--flow-node-action: #3DBAA2;--flow-node-decision: #5A9FBF;--flow-node-tool: #F4A261;--flow-node-memory: #FFD166}@media(prefers-color-scheme:dark){:root{--flow-node-current-fill: url(#gradient-current);--flow-node-current-stroke: #5EEAD4;--flow-node-current-shadow: drop-shadow(0 4px 20px rgba(61, 186, 162, .5)) drop-shadow(0 0 32px rgba(61, 186, 162, .3));--flow-node-traversed-fill: #334155;--flow-node-traversed-stroke: #475569;--flow-node-traversed-shadow: drop-shadow(0 2px 6px rgba(0, 0, 0, .4));--flow-node-untraversed-fill: #1E293B;--flow-node-untraversed-stroke: #334155;--flow-node-untraversed-shadow: drop-shadow(0 1px 3px rgba(0, 0, 0, .5));--flow-edge-current: #5EEAD4;--flow-edge-current-glow: rgba(94, 234, 212, .4);--flow-edge-traversed: #64748B;--flow-edge-untraversed: #334155;--flow-text-primary: #F1F5F9;--flow-text-secondary: #94A3B8;--flow-text-current: #0F172A;--flow-gradient-start: #5EEAD4;--flow-gradient-end: #2DD4BF;--flow-node-action: #3DBAA2;--flow-node-decision: #5A9FBF;--flow-node-tool: #F4A261;--flow-node-memory: #FFD166}}*{transition:fill .3s ease,stroke .3s ease,filter .3s ease}.flow-node{cursor:pointer;transition:all .2s ease}.flow-node:hover{filter:brightness(1.05);transform:scale(1.02)}.flow-node-current{animation:pulse-node 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes pulse-node{0%,to{filter:drop-shadow(0 4px 16px rgba(42,157,143,.4)) drop-shadow(0 0 24px rgba(42,157,143,.2))}50%{filter:drop-shadow(0 4px 20px rgba(42,157,143,.6)) drop-shadow(0 0 32px rgba(42,157,143,.4))}}.dark .flow-node-current{animation:pulse-node-dark 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes pulse-node-dark{0%,to{filter:drop-shadow(0 4px 20px rgba(61,186,162,.5)) drop-shadow(0 0 32px rgba(61,186,162,.3))}50%{filter:drop-shadow(0 4px 24px rgba(61,186,162,.7)) drop-shadow(0 0 40px rgba(61,186,162,.5))}}.flow-edge-current{stroke-width:2.5;filter:drop-shadow(0 0 8px var(--flow-edge-current-glow));animation:flow-pulse 2s ease-in-out infinite}@keyframes flow-pulse{0%,to{opacity:1}50%{opacity:.7}}.flow-edge-traversed{stroke-width:2;stroke-dasharray:none}.flow-edge-untraversed{stroke-width:1.5;stroke-dasharray:5,5;opacity:.5}.flow-arrow-current{fill:var(--flow-edge-current);filter:drop-shadow(0 0 4px var(--flow-edge-current-glow))}.flow-arrow-traversed{fill:var(--flow-edge-traversed)}.flow-arrow-untraversed{fill:var(--flow-edge-untraversed);opacity:.5}.flow-node-label{font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;font-weight:600;font-size:13px;letter-spacing:-.01em}.flow-node-sublabel{font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;font-weight:500;font-size:11px;opacity:.7}.flow-node-badge{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em}.flow-modal-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;display:flex;align-items:center;justify-content:center;z-index:9999}.flow-modal-backdrop{position:absolute;top:0;left:0;width:100%;height:100%;z-index:1;background-color:#00000080;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.flow-modal-container{position:relative;z-index:2;display:flex;align-items:center;justify-content:center;padding:1rem;max-width:100%;max-height:100%}.flow-modal-container>div{background:#fff;border-radius:.75rem;box-shadow:0 20px 25px -5px #0000001a,0 10px 10px -5px #0000000a;max-width:600px;width:100%;max-height:80vh;overflow-y:auto}@media(prefers-color-scheme:dark){.flow-modal-container>div{background:#0f172a;box-shadow:0 20px 25px -5px #0000004d,0 10px 10px -5px #0003}}.flow-modal-content{display:flex;flex-direction:column;max-height:80vh;overflow:hidden}.flow-modal-header{display:flex;align-items:center;justify-content:space-between}.flow-modal-title{display:flex;align-items:center;gap:.75rem}.flow-modal-close{cursor:pointer;border:none;background:none;font-size:1.5rem;line-height:1;padding:.5rem}.flow-modal-body{flex:1;overflow-y:auto}.flow-modal-section{margin-bottom:1rem}.flow-modal-section-title{font-weight:600;margin-bottom:.5rem}.flow-modal-section-content{display:flex;align-items:center;gap:.5rem}.flow-modal-state-indicator{width:.5rem;height:.5rem;border-radius:50%;display:inline-block}.flow-modal-traces{display:flex;flex-direction:column;gap:.75rem}.flow-modal-trace{border:1px solid #e5e7eb;border-radius:.5rem;padding:.75rem}.flow-modal-trace-header{display:flex;justify-content:space-between;margin-bottom:.5rem;font-size:.875rem}.flow-modal-trace-content{margin-bottom:.5rem}.flow-modal-trace-details summary{cursor:pointer;font-size:.875rem}.flow-modal-trace-data{margin-top:.5rem;padding:.5rem;font-size:.75rem;overflow-x:auto;border-radius:.25rem}.flow-modal-empty{text-align:center;padding:2rem;color:#9ca3af}
|