A visual flow builder for constructing conditional node graphs and exporting them as JSON. Built with React 18 + TypeScript + React Flow v12 + Zustand.
npm install
npm run devOpen http://localhost:5173 (or whichever port Vite assigns — check your terminal).
| Command | Description |
|---|---|
npm run dev |
Start the development server |
npm run build |
Type-check and build for production |
npm run preview |
Preview the production build locally |
npm run test |
Run unit tests (Vitest) |
| Action | How |
|---|---|
| Add a node | Click + Add Node in the toolbar or the canvas button |
| Connect nodes | Drag from a node's bottom handle to another's top handle |
| Edit a node | Click any node — the sidebar opens |
| Set start node | Click ⚑ on the node card, or use Set as Start in the sidebar |
| Delete a node | Click ✕ on the node card, or use Delete Node in the sidebar (both prompt inline confirmation) |
| Delete a node (keyboard) | Click a node to select it, then press Delete |
| Delete an edge | Select the edge on the canvas and press Delete, or remove it via the sidebar edge card |
| Clear everything | Clear All in the toolbar → confirm |
| Import JSON | JSON Preview panel → ↑ Import → paste → Import & Reconstruct |
| Copy JSON | JSON Preview panel → ⎘ Copy |
| Download JSON | JSON Preview panel → ↓ Download |
| Screen size | Layout |
|---|---|
| Desktop ≥ 1024px | Full 3-panel layout: Sidebar · Canvas · JSON Preview |
| Tablet 640–1023px | Sidebar + Canvas; toggle JSON panel via the {} JSON button in the toolbar |
| Mobile < 640px | Single panel at a time; switch between Canvas / Edit / JSON using the bottom tab bar |
{
"start_node_id": "greeting",
"nodes": [
{
"id": "greeting",
"description": "Initial greeting step",
"prompt": "Hello! How can I help you today?",
"edges": [
{
"to_node_id": "collect_info",
"condition": "user_responds",
"parameters": { "timeout": "30s" }
}
]
},
{
"id": "collect_info",
"description": "Collect user information",
"prompt": "Could you please share your name and issue?",
"edges": []
}
]
}src/
├── __tests__/
│ └── validation.test.ts — Unit tests for validation logic (Vitest)
├── components/
│ ├── Canvas/
│ │ ├── FlowCanvas.tsx — React Flow canvas wrapper + provider
│ │ ├── CustomNode.tsx — Draggable node with start/error/selected states
│ │ └── CustomEdge.tsx — Bezier edge with condition label + delete button
│ ├── Sidebar/
│ │ └── NodeSidebar.tsx — Node editor panel (ID, description, prompt, edges)
│ ├── JsonPreview/
│ │ └── JsonPreview.tsx — Live JSON output with import / copy / download
│ ├── Toolbar/
│ │ └── Toolbar.tsx — Top bar with node count, validation status, responsive controls
│ └── ErrorBoundary.tsx — React error boundary for graceful crash recovery
├── hooks/
│ └── useFlowStore.ts — Zustand store with localStorage persistence
├── types/
│ └── flow.types.ts — TypeScript interfaces for nodes, edges, schema
├── utils/
│ ├── validation.ts — Live validation rules (uniqueness, required fields, connectivity)
│ └── schema.ts — Schema builder utility
├── App.tsx — Layout + responsive breakpoint logic
├── main.tsx
└── index.css
| Area | Choice | Reason |
|---|---|---|
| Flow library | @xyflow/react v12 |
Most mature React flow library — full TS support, custom nodes/edges, built-in drag & pan |
| State | Zustand | Single store keeps Canvas, Sidebar, and JSON Preview in sync without prop-drilling |
| Persistence | Zustand persist middleware |
Flow state is automatically saved to localStorage and restored on page reload |
| Validation | Live on every mutation | Errors appear instantly in the sidebar (inline), on the node (red border), and in the JSON panel |
| Schema source of truth | flowNodes[] array |
React Flow nodes/edges are derived visual state; the schema is always derived from flowNodes |
| Confirmations | Inline UI | Delete/Clear actions use in-component confirm steps instead of window.confirm (which is blocked by some browsers) |
| Responsive layout | React breakpoint hook | Panel visibility is controlled via useBreakpoint() + inline style={{ display }} rather than pure CSS, avoiding React Flow sizing conflicts |
| Edge labels | EdgeLabelRenderer |
Enables interactive labels and a delete button directly on the edge |
| ID uniqueness | Enforced on blur | Prevents rename collisions without blocking typing mid-word; reverts to original on invalid blur |
| Import | Full schema reconstruction | Parses JSON and rebuilds both flowNodes and the visual RF nodes with an auto-layout grid |
| Error handling | React Error Boundary | Catches rendering errors gracefully and shows a recovery UI |
Errors (block valid JSON badge):
- Start node must be designated and must exist in the graph
- Node IDs must be unique and non-empty
- Description and prompt fields are required on every node
- Edge target node must be selected and must exist in the graph
- Edge condition text is required
Warnings:
- Nodes with no incoming edges (other than the start node) are flagged as disconnected
# Build
npm run build
# Vercel
npx vercel --prod