Anatomy
The data model, shown next to what it draws — never in isolation. Each block
below is one thing (a node, a connector tag, a connector state) rendered live,
with its TypeScript (the render call) and its JSON (the tracer's output)
one tab away. Flip the Preview / Source / Data tabs to line the three up.
For the prose reference of the same shapes, see Pipeline Graph JSON.
The node card
A node is one stage — a @ls.udf — as a 156 × 72 card. Everything on it comes
straight from the node JSON; nothing is styled by hand.
| On the card | JSON field | Notes |
|---|---|---|
| Kind dot (top-left square) | kind | Tints by category — source green, transform blue, model/review purple, filter amber, merge grey, sink red. |
| Title | title | The UDF name. id is the stable key (may differ on fan-out: semantic_match_2). |
Meta line transform · 1→1 | kind · inputs.length→outputs.length | The → is port counts, not columns. |
| Left dots | inputs | One input port per UDF parameter. |
| Right dot | outputs | A UDF returns one table → a single ["out"] port. A sink has none ([]). |
| Status dot + label (footer) | status | idle here; drives tint + the pulse when running. |
| (not drawn on the card) | columns | The result schema — boxes, classes, confidence. Surfaced in the source inspector, not as ports. |
Ports vs columns is the one thing to internalise: inputs are ports (one
per parameter), outputs is a single port (the whole result table), and the
return column names live in columns — a schema, not more ports.
Connector tags
An edge carries exactly one structural tag, kind, decided by the tracer. It's
the only style an edge stores; everything else about an edge is
derived from status.
data — the value flows through (solid)
The default. The source's result table is consumed by the target.
mask — the source gates the target (dashed)
A gate, not data flow. The source (a review veto or a confidence/consensus mask)
only decides which rows of the target survive. In Python this is
labels[consensus] — a boolean selector — so it reads as a gate without adding a
filter node. Rendered dashed and slightly fainter in the settled states.
| Tag | Meaning | Look | Produced by |
|---|---|---|---|
data | source result flows into target | solid | passing a UDF result as an argument |
mask | source only filters/gates target | dashed, fainter | a boolean mask used as a selector (labels[mask]) |
Connector states
An edge stores no colour, width, or animation. Its flow — how it looks
right now — is derived at render time from the status of its two endpoint
nodes, via one shared function:
Six states, one derived value. Below, each swatch is the real
FLOW[state] styling, next to the status pair that produces it (this board is
built straight from the exported edgeFlow + FLOW, so it can't drift from the
component):
| Flow | Derived when | Look |
|---|---|---|
running | src running, or (src ok & dst running) | blue, marching dashes |
queued | src ok, dst still idle | grey, slow drift |
stalled | src stale | amber, gentle breath |
error | either endpoint errored | red, tight dashes |
ok | src ok & dst ok | green, solid |
idle | anything else | faint, solid |
Note the spelling: a node's status is stale, but the edge flow it derives
is stalled. Because flow is derived, you never store or diff it — keep node
status live (via statusById) and every edge restyles itself. That single
source of truth is what makes the live runner
cheap.
Next: Pipeline Graph JSON is the full data-model reference · Architecture & Roadmap covers the internals and what's planned.