fancy-flow
@particle-academy/fancy-flow
Headless workflow engine + an optional React Flow editor — six node kits, topological executor, and a FlowRunnerUx flow→UX bridge. The editor designs graphs; `/engine` runs them with zero React on a server, worker, or CLI (PHP twin: fancy-flow-php).
$
npm install @particle-academy/fancy-flowAuthoring surface
Humans compose it
Controlled value + onChange, JSON-friendly props, terse defaults — so people (and agents) build it fast.
Inhabited surface
Agents drive it over MCP
A bridge in @particle-academy/agent-integrations exposes typed tools — agents read and mutate state through stable handles, never DOM scraping.
flow_add_nodeflow_connectflow_run
Why
Building a workflow editor from scratch on React Flow means wiring nodes, handles, edges, and pan/zoom by hand — it's a superb low-level engine, but a steep ramp before you have anything that runs. The alternative is reaching for a heavy closed tool like n8n or Zapier and inheriting someone else's runtime, hosting, and lock-in.
fancy-flow sits in the middle: a batteries-included editor and topological runner you drop into your own React app, owning your graph and your executors. And because it's built for Human+ UX, the editor is bridgeable — the flow_* MCP bridge lets an agent read and mutate the graph through stable node and port ids, never by scraping the DOM — so humans and agents edit the same workflow together.What
fancy-flow ships a complete editor surface: <FlowCanvas> for the raw canvas, <FlowEditor> as the full editor shell, plus <NodePalette> and <NodeConfigPanel> for adding and configuring nodes. Out of the box you get 27 built-in node kinds — three triggers (manual_trigger / webhook_trigger / schedule_trigger), control flow (branch, switch_case, merge, for_each, wait), data (transform, variable, memory_store, data_store), I/O (api_request, webhook_out, notify, log, output), human-in-the-loop (user_input, rich_user_input, human_approval — each pauses the run for a form), AI (llm_call, llm_router, tool_use, embed_search), and structure (subflow, lane, note). Anything beyond that comes from the node marketplace via npx fancy-cli add node <kind>, which checks the node runs on the runtimes your project actually executes on. On AI, fancy-flow is a shuttle, not an engine: core declares a registerLlmClient contract and never imports a provider SDK, with opt-in adapters on the ./llm/vercel-ai subpath (optional ai peer) and ./llm/prism (no SDK at all — it POSTs to a route on your own server that answers with Prism). Custom nodes are authored with defineNode and <NodePort>, an API that hands you ports, sides, and source/target handles without ever importing from @xyflow/react. That matters because React Flow is bundled and hidden inside the dist (its stylesheet included in ours) — there's zero peer install and the underlying engine stays swappable. The runtime is real, not just a picture: runFlow does a topological walk firing each node once its upstream ports resolve, and the useFlowRun hook wraps it in React state with per-node status events cycling through idle / queued / running / done / error. State is controlled via useFlowState, and the whole graph is a plain JSON-serializable FlowGraph of nodes and edges — easy to persist, diff, or emit from an agent. Power users who want to drop to the metal can still consult the React Flow API reference for the engine underneath.How
Install the package and import its stylesheet (React Flow's own CSS rides along inside it):
npm install @particle-academy/fancy-flow, then import "@particle-academy/fancy-flow/styles.css". The smallest render is a controlled FlowGraph on a canvas: const flow = useFlowState(initial) wired into <FlowCanvas nodes={flow.nodes} edges={flow.edges} onConnect={flow.onConnect} />. To author a custom node, reach for defineNode + <NodePort> — e.g. const ThresholdNode = defineNode(({ data }) => (<div className="node"><NodePort side="left" type="target" id="in" /><div>≥ {data.threshold}</div><NodePort side="right" type="source" id="pass" /></div>)) — no @xyflow/react import in sight. To make the graph run, supply an ExecutorRegistry keyed by node kind and drive it with useFlowRun, then paint live per-node status as each executor resolves. For the concepts beneath the authoring API — handles, connections, viewport — React Flow's concepts guide is worthwhile further reading.Components
5 components · click any tile for a full demo, source, and install snippet.
useFlowStateOpen →
hook · controlled graph
const [graph, setGraph] =
useFlowState(initial)
setGraph.addNode({…})
Controlled state hook.
useFlowRunOpen →
hook · React runtime
const run = useFlowRun(graph)
run.start()
run.status → "running"
Executor hook.
