Solution · Workflows

Workflow graphs you can draw, check and embed.

Model approvals as typed nodes and edges, validate edits, and shape the graph with automatic layout, a canvas or a keyboard outline. Your app runs them.

Status: in development

Isometric workflow graph of pearl nodes, ports and dashed edges with a small inspector card

Edit an approval flow

Try this

  • Start the demo, then select a step or change its label.
  • Add a notification step, or arrange the graph from left to right.
  • Use Reset example to return to the starting approval flow.

Use Reset to return to the starting example.

Expense approval workflow preview
  • Submit expense Expense request Submitted to Manager review
  • Manager review Manager review Approved to Approved; Rejected to Rejected
  • Approved Outcome
  • Rejected Outcome

4 steps · 3 connections

Expense approval workflow loaded. Use the step list or action buttons to explore it.

Expense approval workflow graph
  • Submit expense Expense request Submitted to Manager review
  • Manager review Manager review Approved to Approved; Rejected to Rejected
  • Approved Outcome
  • Rejected Outcome

Workflow steps

Select a step. Use the up and down arrow keys to move through the list.

If the interactive demo cannot load, use the static preview above.

What a workflow graph can do

Stop invalid changes before they land.

Node types are declared up front. Each operation checks ports, directions, data types and the cycle policy. A refused operation changes nothing, so people and agents can correct a graph before it is stored.

  • Graph model
A three-node workflow graph with a glass check gate on one edge and a refused node sitting unused beside it

Place the graph predictably.

Deterministic layered layout places nodes left to right or top to bottom, including right-to-left writing. Apply its moves as ordinary operations.

  • Layout
The same four workflow nodes in a neat left-to-right layered layout, with a faint top-to-bottom stack behind

Edit visually. Keep control of history.

Build with a toolbar, palette, canvas, minimap, inspector and keyboard outline. Changes come back to your app; wire undo and redo through the history helper.

  • Author
A workflow editor with a toolbar, a palette of node cubes, a canvas graph and a compact inspector card

From typed graph to editable view.

  1. Model

    Define node types, then apply validated operations to build the graph.

    Package: @texuli/workflows

  2. Lay out

    Place nodes deterministically, left to right or top to bottom.

    Package: @texuli/workflows

  3. Edit

    Use the canvas or keyboard outline; wire undo and redo through workflow history.

    Package: @texuli/workflows

  4. Inspect

    Edit the selected node’s typed props in the inspector.

    Package: @texuli/workflows

  5. View

    Show the same graph as a read-only figure that lists where each node leads.

    Package: @texuli/workflows

Workflows is built from @texuli/workflows, on texuli, @texuli/tokens, @texuli/primitives, @texuli/core and @texuli/drag.

Solution

Workflows

Packages

  • L4@texuli/workflowsGraph model, layout, editor, viewer and inspectorSubpaths: ./model · ./layout · ./editor · ./viewer · ./inspector

Built on

  • L3texuli (Styled components)
  • L2@texuli/tokens (Theme tokens)
  • L1@texuli/primitives (Headless primitives)
  • L0@texuli/core (Texels and the URL policy)
  • L0@texuli/drag (Drag engine)
Five-beat isometric pipeline from typed node cubes through layered layout, an editor, an inspector and a read-only viewer

Build with Workflows

Start with a workflow graph

Install

pnpm add @texuli/workflows

Create a graph

import { applyWorkflowOps, createWorkflow, defineNodeTypes } from '@texuli/workflows/model';

const registry = defineNodeTypes([
  {
    "type": "request",
    "label": "Request",
    "category": "Start",
    "ports": [
      {
        "id": "out",
        "direction": "out",
        "dataType": "any"
      }
    ]
  },
  {
    "type": "approval",
    "label": "Approval",
    "category": "Review",
    "ports": [
      {
        "id": "in",
        "direction": "in",
        "dataType": "any"
      },
      {
        "id": "yes",
        "direction": "out",
        "dataType": "any",
        "label": "Approved"
      }
    ]
  },
  {
    "type": "end",
    "label": "End",
    "category": "Done",
    "ports": [
      {
        "id": "in",
        "direction": "in",
        "dataType": "any"
      }
    ]
  }
]);
const built = applyWorkflowOps(createWorkflow(), [
  {
    "op": "addNode",
    "node": {
      "id": "ask",
      "type": "request",
      "label": "Expense submitted",
      "position": {
        "x": 0,
        "y": 0
      }
    }
  },
  {
    "op": "addNode",
    "node": {
      "id": "lead",
      "type": "approval",
      "label": "Team lead",
      "position": {
        "x": 0,
        "y": 0
      }
    }
  },
  {
    "op": "addNode",
    "node": {
      "id": "paid",
      "type": "end",
      "label": "Paid",
      "position": {
        "x": 0,
        "y": 0
      }
    }
  },
  {
    "op": "connect",
    "edge": {
      "id": "e1",
      "from": {
        "node": "ask",
        "port": "out"
      },
      "to": {
        "node": "lead",
        "port": "in"
      }
    }
  },
  {
    "op": "connect",
    "edge": {
      "id": "e2",
      "from": {
        "node": "lead",
        "port": "yes"
      },
      "to": {
        "node": "paid",
        "port": "in"
      }
    }
  }
], { registry });

if (built.ok) {
  console.log(built.graph.nodes.map((node) => node.id));
}

Lay the graph out

import { layoutMoves, layoutWorkflow } from '@texuli/workflows/layout';
import { applyWorkflowOps, createWorkflow, defineNodeTypes } from '@texuli/workflows/model';

const registry = defineNodeTypes([
  {
    "type": "request",
    "label": "Request",
    "category": "Start",
    "ports": [
      {
        "id": "out",
        "direction": "out",
        "dataType": "any"
      }
    ]
  },
  {
    "type": "approval",
    "label": "Approval",
    "category": "Review",
    "ports": [
      {
        "id": "in",
        "direction": "in",
        "dataType": "any"
      },
      {
        "id": "yes",
        "direction": "out",
        "dataType": "any",
        "label": "Approved"
      }
    ]
  },
  {
    "type": "end",
    "label": "End",
    "category": "Done",
    "ports": [
      {
        "id": "in",
        "direction": "in",
        "dataType": "any"
      }
    ]
  }
]);
const built = applyWorkflowOps(createWorkflow(), [
  {
    "op": "addNode",
    "node": {
      "id": "ask",
      "type": "request",
      "label": "Expense submitted",
      "position": {
        "x": 0,
        "y": 0
      }
    }
  },
  {
    "op": "addNode",
    "node": {
      "id": "lead",
      "type": "approval",
      "label": "Team lead",
      "position": {
        "x": 0,
        "y": 0
      }
    }
  },
  {
    "op": "addNode",
    "node": {
      "id": "paid",
      "type": "end",
      "label": "Paid",
      "position": {
        "x": 0,
        "y": 0
      }
    }
  },
  {
    "op": "connect",
    "edge": {
      "id": "e1",
      "from": {
        "node": "ask",
        "port": "out"
      },
      "to": {
        "node": "lead",
        "port": "in"
      }
    }
  },
  {
    "op": "connect",
    "edge": {
      "id": "e2",
      "from": {
        "node": "lead",
        "port": "yes"
      },
      "to": {
        "node": "paid",
        "port": "in"
      }
    }
  }
], { registry });
if (built.ok) {
  const moves = layoutMoves(layoutWorkflow(built.graph, { direction: 'LR' }));
  const laid = applyWorkflowOps(built.graph, [{ op: 'moveNodes', moves }], { registry });
  if (laid.ok) {
    console.log(laid.graph.nodes.length);
  }
}

Edit with WorkflowEditor

<script lang="ts">
  import { WorkflowEditor } from '@texuli/workflows/editor';
  import { applyWorkflowOps, createWorkflow, defineNodeTypes } from '@texuli/workflows/model';
  import '@texuli/tokens/base.css';

  const registry = defineNodeTypes([{"type":"request","label":"Request","category":"Start","ports":[{"id":"out","direction":"out","dataType":"any"}]},{"type":"approval","label":"Approval","category":"Review","ports":[{"id":"in","direction":"in","dataType":"any"},{"id":"yes","direction":"out","dataType":"any","label":"Approved"}]},{"type":"end","label":"End","category":"Done","ports":[{"id":"in","direction":"in","dataType":"any"}]}]);
  const built = applyWorkflowOps(createWorkflow(), [{"op":"addNode","node":{"id":"ask","type":"request","label":"Expense submitted","position":{"x":0,"y":0}}},{"op":"addNode","node":{"id":"lead","type":"approval","label":"Team lead","position":{"x":0,"y":0}}},{"op":"addNode","node":{"id":"paid","type":"end","label":"Paid","position":{"x":0,"y":0}}},{"op":"connect","edge":{"id":"e1","from":{"node":"ask","port":"out"},"to":{"node":"lead","port":"in"}}},{"op":"connect","edge":{"id":"e2","from":{"node":"lead","port":"yes"},"to":{"node":"paid","port":"in"}}}], { registry });
  let graph = $state.raw(built.ok ? built.graph : createWorkflow());
</script>

<WorkflowEditor {graph} {registry} onchange={(next) => (graph = next)} />

Verified by apps/website/tests/solutions/workflows-quickstart.test.ts

Recipes

For agents

Agents emit a workflow-graph catalog node. Check it with catalog.validate or blueprint.validate. There is no MCP operation that runs a workflow. The packages are in development.

  • workflow-graphworkflow-graph: In development

    Catalog component for a typed graph, view or edit, painted by @texuli/workflows.

  • catalog.validatecatalog.validate: In development

    MCP operation: validate a catalog tree, including a workflow-graph node.

  • blueprint.validateblueprint.validate: In development

    MCP operation: the same check on a blueprint envelope.

  • skills/texuli/build-ui/references/recipes.mdskills/texuli/build-ui/references/recipes.md: In development

    Skill recipe for the workflow editor, viewer, inspector, model and layout.

Platforms

  • Web

    Web: In development

    Svelte 5 packages published on npm (pre-1.0).

  • Compose Desktop

    Compose Desktop: Planned

    Kotlin Multiplatform renderer on the JVM, built in this repository.

  • Android

    Android: Planned

    Native renderer target.

  • iOS

    iOS: Planned

    Native renderer target.

How the native renderer works

Start building with Workflows

Author and view workflow graphs inside your app.

A low horizon of stacked cubes rising to one glowing pearl cube under a wide open skyA low horizon of stacked cubes rising to one glowing pearl cube under a wide open sky