Skip to content

Running flows and reading results

Pressing ▶ RUN (or Ctrl+Enter in the editor) is the only way a flow leaves the page. This page documents what happens in code on the way through.

The five-step run loop

FlowpadApp.onRunFromCode (projects/matrix-3/packages/flowpad/src/FlowpadApp.ts:342-371) is the canonical entry. Pre-built examples take a slightly different path through onRunFlow (FlowpadApp.ts:373-412), which uses FlowBuilder to construct the same kind of plan from coded stage descriptors.

1. compile        compileSourceToCanonicalIR({ source, defaultTargetMount, modeHint, sourceFormatHint })
2. plan           emit executionPlanned { flowId, language, irKind, adapterPath }
3. execute        executeCanonicalIR(executionContext, ir)
4. complete       emit flowCompleted { flowId, result }
5. display        ResultsPanel adds a result child of the right viewer kind

Errors break out at step 3: flowError { flowId, error } is emitted, then re-thrown.

Step 1 — Compile to IR

compileSourceToCanonicalIR (runtime/queryPipeline.ts) detects the language and produces one of two IR shapes:

ts
type CanonicalIR = IQueryExecutionIR | IComponentSchemaIR;

IQueryExecutionIR carries the steps array; each step is remote-call, local-map, or local-filter. IComponentSchemaIR covers the five-representation parity case where the source is a component schema (HTML / Fluent / S-expr / Topics / JSON).

The detection precedence is:

  1. Explicit sourceFormatHint — matches flow/flow-dsl/flow-json/lisp/scheme/json/etc.
  2. modeHint (the editor's auto-detected mode).
  3. JSON parse — distinguishes schemas (accepts/emits/children), commands (op/operation), or payloads.
  4. Fall back to flow-dsl.

Step 2 — Status pane reflects the plan

ExecutionStatus (components/ExecutionStatus.ts) listens for executionPlanned and statusChanged. Its toolbar carries:

  • A status word (Idle, Running, OK, Error).
  • A topology tag — runtime <root> · authority <root> · target <kind> resolved by resolveExecutionTopology reading data-runtime-root, data-authority-root, and data-target-source off the host element (ExecutionStatus.ts:31-76).
  • A recovery-actions panel populated by resolveHostedRuntimeRecoveryPlan when the requested root is unavailable.
  • A mount tag (flowpad-page.status) so this status bar can itself be addressed over the bus.

Step 3 — Execute against actors

executeCanonicalIR walks the steps, sending request/reply calls to actor mounts and routing local stages through pure-function transforms. The execution context is a derive('_runner') of FlowpadApp's own context, so all calls inherit the same authority root and transport.

Per-stage timeout policy (FlowpadApp.ts:336-340):

ts
const isCrossRoot = mount.includes('/');
const timeoutMs = isCrossRoot
  ? 30000                  // cross-root federated calls
  : mount.includes('vlm')
    ? 10000                // VLM Lisp evaluation
    : 5000;                // default

Step 4 — Result becomes a child actor

ResultsPanel (components/ResultsPanel.ts) is the most interesting part of the pipeline. Each successful run becomes an addressable child of flowpad-page.results.result-N:

ts
// components/ResultsPanel.ts:55-66
export class ResultsPanel extends MatrixActor {
  static accepts = {
    addResult: { data: 'unknown', type: 'string?', label: 'string?', query: 'string?' },
    removeResult: { id: 'string' },
    clearResults: {},
  };

  static emits = {
    resultAdded: { id: 'string', mount: 'string', type: 'string', label: 'string?' },
    resultRemoved: { id: 'string' },
    resultsCleared: { count: 'number' },
  };
}

The viewer kind is auto-selected from the result shape:

ShapeViewerMount class
Array of records<table-viewer>TableResultActor (TableViewer accepts/emits)
Tree-shaped object (with children/accepts)<tree-viewer>TreeViewer
Error<error-viewer>ErrorResultActor
Anything else<data-viewer>DataResultActor

Because each result is a real Matrix child, you can address it like any other actor: ./results.result-0 accepts expandAll, collapseAll, sortBy, introspect, etc. The DSL examples 'Expand Result', 'Collapse Result', 'Sort Table', 'Introspect Result' (dsl/examples.ts) demonstrate this — a flow can talk to a previously rendered result the same way it talks to ./db.

Step 5 — Transport log captures the bus traffic

TransportLog (components/TransportLog.ts) lives in the dock. It subscribes to bus traffic and renders entries timestamped by direction (accepts / emits / lifecycle). Each entry shows:

  • topic
  • payload preview
  • colour-coded by category (blue for accepts, green for emits, amber for lifecycle).

This is your post-mortem when a flow misbehaves — every send, every reply, every $lifecycle event is captured here.

Reading the four viewers

ViewerUse it forNotable ops
DataViewerAny unstructured value (numbers, strings, generic objects)setData
TableViewerArrays of records (database query results)setRows, sortBy, setColumns
TreeViewerHierarchical actor schemas (output of introspect)setTree, expandAll, collapseAll
ErrorViewerStep failures and timeoutssetError

Their schemas live in @open-matrix/core/flow/viewers. ResultsPanel re-exports their accepts/emits onto its synthesised *ResultActor classes so that the result children declare exactly the same contract as the standalone viewers.

Clearing and replaying

The toolbar's Clear button calls FlowpadApp's clearResults — it both wipes the per-stage data map and tells ResultsPanel to drop every result child. Re-running an example refills the panel.

See also

Source: projects/matrix-3/packages/flowpad/src/FlowpadApp.ts:342-412 and projects/matrix-3/packages/flowpad/src/components/ResultsPanel.ts.