Architecture Overview

How Floww works internally — the rendering model, execution engine, and file format.

High-Level Architecture

Floww is a desktop-first node-based workflow engine. Its architecture is composed of four cooperating layers:

Layer Technology Responsibility
Shell Electron / Tauri Window management, native menus, file-system access, IPC bridge
Renderer WebKit2GTK (Linux), Chromium (Electron) HTML/CSS/JS rendering, DOM management
Canvas Engine Custom HTML5 Canvas + DOM hybrid Wire rendering, node layout, viewport transforms, hit-testing
Execution Runtime JavaScript (sandboxed worker) Node execution, data propagation, variable resolution, error handling

The shell process launches the renderer process and communicates with it over an IPC channel. The renderer hosts both the canvas engine (which draws the visual graph) and the execution runtime (which runs workflows). These two subsystems are decoupled: you can execute a workflow headlessly without the canvas engine, which is how the localFloww SDK works.

Cross-platform note
On Linux, Floww uses a Tauri shell with WebKit2GTK for a lighter footprint. On macOS and Windows, an Electron shell provides broader compatibility. The internal APIs are identical regardless of shell.

The .floww File Format

A .floww file is a JSON document that describes a directed acyclic graph (DAG) of nodes and edges, along with workflow-level variables and metadata. Here is a minimal example:

{
  "version": "1.0",
  "metadata": {
    "name": "My Workflow",
    "description": "A simple two-node pipeline",
    "author": "you",
    "created": "2025-06-15T10:00:00Z",
    "modified": "2025-06-15T12:30:00Z"
  },
  "variables": {
    "apiKey": { "type": "string", "value": "", "sensitive": true },
    "retryCount": { "type": "number", "value": 3, "sensitive": false }
  },
  "nodes": [
    {
      "id": "node-001",
      "type": "http-request",
      "position": { "x": 120, "y": 200 },
      "config": {
        "method": "GET",
        "url": "https://api.example.com/data"
      }
    },
    {
      "id": "node-002",
      "type": "json-parse",
      "position": { "x": 420, "y": 200 },
      "config": {}
    }
  ],
  "edges": [
    {
      "id": "edge-001",
      "source": { "nodeId": "node-001", "port": "response" },
      "target": { "nodeId": "node-002", "port": "input" }
    }
  ]
}

Schema rules

  • version — currently "1.0". Floww will migrate older files automatically on open.
  • nodes — each node has a unique id, a type that maps to a registered node definition, a canvas position, and an arbitrary config object shaped by the node's configuration schema.
  • edges — each edge connects an output port on a source node to an input port on a target node. Cycles are rejected at parse time.
  • variables — workflow-level key-value pairs accessible to any node at execution time. Variables marked sensitive are encrypted at rest.

Node Execution Lifecycle

Every node follows a strict state machine during execution:

  ┌──────────┐
  │   IDLE   │
  └────┬─────┘
       │ trigger
       ▼
  ┌──────────┐
  │   INIT   │  ← resolve config, bind variables
  └────┬─────┘
       │
       ▼
  ┌──────────┐
  │ VALIDATE │  ← check inputs match expected types
  └────┬─────┘
       │ pass
       ▼
  ┌──────────┐
  │ EXECUTE  │  ← run async execute() function
  └────┬─────┘
       │
   ┌───┴───┐
   ▼       ▼
┌────────┐ ┌───────┐
│COMPLETE│ │ ERROR │
└────────┘ └───────┘
  1. IDLE — the node is waiting. It has not been triggered yet.
  2. INIT — the runtime resolves configuration values, substitutes variable references ({{variableName}}), and prepares the execution context.
  3. VALIDATE — input data from upstream nodes is checked against the port type declarations. If validation fails, the node transitions directly to ERROR.
  4. EXECUTE — the node's execute() function runs asynchronously. It receives inputs and config, and must return an outputs object.
  5. COMPLETE — execution finished successfully. Output data is propagated to downstream nodes.
  6. ERROR — something went wrong. The error is captured, and the workflow engine decides whether to halt or continue depending on the error handling strategy.
Error handling strategies
Workflows support three error modes: stop (halt the entire workflow), skip (skip the failed node and continue downstream with null), and retry (retry the node up to N times with exponential backoff).

Canvas Rendering Model

Floww uses a hybrid rendering approach that balances performance with interactivity:

  • Wires — drawn on an HTML5 <canvas> element using Bézier curves. The canvas layer sits behind the node DOM and is redrawn on every frame during panning, zooming, or dragging.
  • Nodes — rendered as regular DOM elements positioned with CSS transforms. This means nodes support standard text selection, form inputs, and accessibility features that would be difficult in a pure-canvas approach.
  • Virtual viewport — the visible area is tracked as a viewport rectangle. Only nodes within or near the viewport are mounted in the DOM (a technique similar to virtual scrolling in list UIs). This allows Floww to handle workflows with thousands of nodes without degrading frame rate.
  • Culling — wires whose start and end are both off-screen are skipped during the canvas draw pass. A spatial index (R-tree) accelerates these visibility checks.

Coordinate system

Positions in the .floww file are in world coordinates. The canvas engine maintains a transform matrix that maps world coordinates to screen coordinates based on the current pan offset and zoom level:

screenX = (worldX - panX) * zoom
screenY = (worldY - panY) * zoom

Event System

Floww uses an internal publish/subscribe event bus for communication between the canvas engine, execution runtime, and UI layer. This is also the primary extension point for plugins.

Core events

Event Emitted when
node:addedA node is placed on the canvas
node:removedA node is deleted
node:executedA node finishes execution (success or error)
edge:connectedTwo ports are wired together
edge:disconnectedA wire is removed
workflow:startedA workflow execution begins
workflow:completedAll nodes have finished
workflow:errorA workflow-level error occurs
canvas:viewport-changedThe user pans or zooms
variable:changedA workflow variable is updated

Subscribing from a plugin

// Listen for node execution completions
floww.events.on('node:executed', (event) => {
  console.log(`Node ${event.nodeId} finished in ${event.duration}ms`);
  console.log('Outputs:', event.outputs);
});

// Emit a custom event
floww.events.emit('myplugin:data-ready', { rows: 42 });

Custom events should be namespaced with your plugin name to avoid collisions (e.g., myplugin:event-name).