<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Piotr Błaszczyk</title>
    <description>The latest articles on DEV Community by Piotr Błaszczyk (@piotr_baszczyk_779d91aca).</description>
    <link>https://dev.to/piotr_baszczyk_779d91aca</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3753335%2F18a5119f-53f3-4c53-8f7c-51a958561c61.jpg</url>
      <title>DEV Community: Piotr Błaszczyk</title>
      <link>https://dev.to/piotr_baszczyk_779d91aca</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/piotr_baszczyk_779d91aca"/>
    <language>en</language>
    <item>
      <title>Parameters change everything: passing data between workflow nodes</title>
      <dc:creator>Piotr Błaszczyk</dc:creator>
      <pubDate>Tue, 21 Jul 2026 12:34:45 +0000</pubDate>
      <link>https://dev.to/workflowbuilder/parameters-change-everything-passing-data-between-workflow-nodes-9mb</link>
      <guid>https://dev.to/workflowbuilder/parameters-change-everything-passing-data-between-workflow-nodes-9mb</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; &lt;a href="https://www.workflowbuilder.io/" rel="noopener noreferrer"&gt;Workflow Builder&lt;/a&gt; is an open-source (Apache 2.0) React SDK for embedding a visual workflow editor in your own product. It is built on React Flow: the canvas comes from React Flow, Workflow Builder is the layer above it (nodes as JSON schemas, properties panel, validation, execution). This article is about one feature, the Variable Picker, which lets one node read another node's output. Type &lt;code&gt;{{&lt;/code&gt; into a field and the editor offers only the data that exists at that point in the graph, already type-checked. Underneath it took output schemas in JSON, a graph reachability walk for scoping, and a strict template resolver. Real code included.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I am the creator of the Workflow Builder SDK and have been building it since the first commit. Keep that in mind while reading.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article first appeared on the &lt;a href="https://www.workflowbuilder.io/blog/variable-picker-passing-data-between-workflow-nodes" rel="noopener noreferrer"&gt;Workflow Builder blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Drop a few boxes on a canvas, connect them with arrows, and you have a workflow. Do this, then this, then that. It runs, top to bottom, each box doing its one fixed thing.&lt;/p&gt;

&lt;p&gt;It works, but it is a trivial program. Nothing one box produces can reach the next, so no step can build on what another did. It only becomes a real program once one node can use the output of another.&lt;/p&gt;

&lt;h2&gt;
  
  
  A node is a function
&lt;/h2&gt;

&lt;p&gt;Here is why nothing flows between the boxes. A node is a function, and right now it receives no inputs. Think about how that works in code. A function with no arguments is rarely interesting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;sendEmail&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What does it send? To whom? It is a black box with a label.&lt;/p&gt;

&lt;p&gt;Now give it parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;sendEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suddenly it is a tool. The same function serves a thousand different cases, because the caller decides what flows in. The signature is a contract. It says "give me these three things and I will do the rest."&lt;/p&gt;

&lt;p&gt;A node in &lt;a href="https://www.workflowbuilder.io/" rel="noopener noreferrer"&gt;Workflow Builder&lt;/a&gt; is exactly this. An empty action node is &lt;code&gt;sendEmail()&lt;/code&gt;. A configured one is &lt;code&gt;sendEmail(to, subject, body)&lt;/code&gt;. The whole value of the editor lives in that gap.&lt;/p&gt;

&lt;p&gt;So the interesting problem in building a workflow editor is not the canvas. It is this: how does a node get its inputs?&lt;/p&gt;

&lt;h2&gt;
  
  
  Where do the arguments come from
&lt;/h2&gt;

&lt;p&gt;An argument gets its value from one of two sources. The first is a literal. You type the value in by hand. &lt;code&gt;subject = "Welcome aboard"&lt;/code&gt;. This is fine for constants, and useless for anything that depends on what happened earlier in the run.&lt;/p&gt;

&lt;p&gt;The second is a reference. You point at the output of a step that already ran, and let the real value be filled in when the workflow executes. The email subject is not a string you typed. It is "whatever the Classify step decided this request was about."&lt;/p&gt;

&lt;p&gt;That second source is the Variable Picker. Say an upstream Classify node has just tagged an incoming support ticket as &lt;code&gt;Billing&lt;/code&gt;. In the email node's Subject field you type &lt;code&gt;{{&lt;/code&gt;, and instead of guessing at variable names, you get a panel of everything that is actually available at that point in the graph. You pick one. The field shows a friendly chip, &lt;code&gt;{{ Classify · Category }}&lt;/code&gt;, while the diagram quietly stores the durable form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;classify-1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;category&lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the same idea as wiring one function's return value into another function's argument. We made the wiring visual, and we made it impossible to wire to something that is not there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Types, because data has a shape
&lt;/h2&gt;

&lt;p&gt;As soon as data flows between steps, it has a shape, and shapes can be wrong.&lt;/p&gt;

&lt;p&gt;A Decision node comparing "is greater than" only makes sense for numbers and dates. Feeding it a boolean is not a clever edge case. It is a bug the user did not mean to write. So every output a node can emit declares its type, drawn from a small, deliberate set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;VariableTypePrimitive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;boolean&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;datetime&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;VariableType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;VariableTypePrimitive&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;array&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The picker reads those types and filters itself. When a field expects a number, the panel only offers numbers. When it expects a date, it also offers datetimes, because a datetime is a date with extra precision. The available comparison operators shift with the type too. Strings get "contains." Numbers get "greater than." Dates get "before" and "after." The interface never lets the user assemble a comparison that cannot mean anything.&lt;/p&gt;

&lt;p&gt;This is types-as-validation, except the user never sees a red error. They simply are not offered the wrong choice in the first place.&lt;/p&gt;

&lt;p&gt;The filter is strict but not naive. User input arrives as strings, so a few cross-type pairings are allowed on purpose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A number can fill a &lt;code&gt;string&lt;/code&gt; field.&lt;/li&gt;
&lt;li&gt;A valid date string satisfies a &lt;code&gt;date&lt;/code&gt; field, and &lt;code&gt;date&lt;/code&gt; and &lt;code&gt;datetime&lt;/code&gt; are interchangeable.&lt;/li&gt;
&lt;li&gt;The only boolean literals it accepts are &lt;code&gt;''&lt;/code&gt;, &lt;code&gt;'true'&lt;/code&gt;, and &lt;code&gt;'false'&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything else is refused. The picker coerces where the meaning is unambiguous and refuses where it is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  A node can only see what runs before it
&lt;/h2&gt;

&lt;p&gt;In a programming language, a variable has a scope. You cannot read a value that has not been assigned yet, and you cannot reach into a block that never ran. A workflow has exactly the same rule, drawn in two dimensions on a canvas, and a serious editor has to respect it.&lt;/p&gt;

&lt;p&gt;Some variables are global. Secrets, tenant configuration, an API base URL. Available everywhere, from the first node to the last.&lt;/p&gt;

&lt;p&gt;But most variables are earned. A node can only reference the output of a step that genuinely runs before it. Concretely, that means a step somewhere upstream, reachable by walking the arrows backward from the current node. The picker computes this directly. It runs a breadth-first search back through the edges to collect every ancestor of the selected node, and offers their outputs and nothing else:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// BFS backward through edges to find all ancestor nodes&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ancestors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;nodeId&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;edge&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ancestors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;ancestors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The consequence is exactly the scoping rule you would want. A node sees what flows into it. It cannot see a sibling on a parallel branch that never reaches it. It cannot see a node downstream of itself either, because that data does not exist yet when this step runs. The graph topology &lt;em&gt;is&lt;/em&gt; the scope. We did not invent a new mental model. We took the one every programmer already has and rendered it on the canvas.&lt;/p&gt;

&lt;p&gt;One honest note on that code. This is really a reachability walk, not a meaningful breadth-first one. Order does not matter, because we collect the whole set of ancestors, so depth-first would behave the same. The inner loop rescans every edge on each step, so it runs in O(ancestors × edges). That is fine at workflow scale. It would not be fine in a general graph library.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simple when it works, loud when it fails
&lt;/h2&gt;

&lt;p&gt;Everything above is plumbing. For the end user it collapses into one gesture: focus a field, type &lt;code&gt;{{&lt;/code&gt;, pick from a panel grouped by upstream step, and keep typing around the chip. The editor only ever offers things that work, and the hard part disappears into a two-character keystroke.&lt;/p&gt;

&lt;p&gt;We took one more deliberate stance for the moment things go wrong. A reference is strict by default. If a path cannot be resolved at run time, the run fails loudly with &lt;code&gt;Unresolved template reference&lt;/code&gt;, rather than silently substituting an empty string and shipping it to an LLM. A typo in a prompt should break on the first run, not three weeks later in production. When absence is genuinely expected, the user opts into a fallback on purpose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;classify-1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;category?}}&lt;span class="w"&gt;                          &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;resolves&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;missing&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;{{nodes.classify-1.category&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;'unclassified'&lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;  // resolves to a default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Strictness needs one more distinction to be safe. The resolver parses in two stages. An outer pass catches anything that looks like a reference: a &lt;code&gt;{{namespace.path}}&lt;/code&gt; block with a dot, the marker that the author meant a reference and not literal text. An inner pass checks that body against the real grammar. So a token has three fates, not two:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;{{nodes.foo?bar}}&lt;/code&gt; looks like a reference but breaks the grammar. It throws &lt;code&gt;Malformed&lt;/code&gt; instead of leaking a broken token into a prompt.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;{{total}}&lt;/code&gt; has no namespace dot, so the outer pass never claims it. It stays literal text.&lt;/li&gt;
&lt;li&gt;A well-formed path that points at nothing throws &lt;code&gt;Unresolved&lt;/code&gt;. Only the opt-in &lt;code&gt;?&lt;/code&gt; and &lt;code&gt;default:&lt;/code&gt; quiet this last case.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Strict by default, lenient on request. The safe thing is automatic, the risky thing is a choice you make with your eyes open.&lt;/p&gt;

&lt;h2&gt;
  
  
  Because in Workflow Builder, everything is data
&lt;/h2&gt;

&lt;p&gt;None of this would be possible if a node were just a React component with some props hardcoded in its source.&lt;/p&gt;

&lt;p&gt;A node in Workflow Builder is a data definition. It always carried a &lt;code&gt;schema&lt;/code&gt;, the JSON description of its own configurable inputs, the part that says "I am a function with this signature." Building the Variable Picker meant the other half of that contract had to exist as data too. So we extended the definition with an &lt;code&gt;outputSchema&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;OutputProperty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;VariableType&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;description&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole trick. The inputs of a node are data. The outputs of a node are now data. And because both halves of the contract are just JSON, the picker can read them, the type filter can reason over them, and the ancestor search can connect one node's outputs to another node's inputs. The SDK stops there. It stores the wiring as text and runs nothing itself. A backend resolves the references when the workflow runs. Workflow Builder ships a reference backend so you can watch the whole loop work, then swap in your own. A node that wants to participate adds an &lt;a href="https://www.workflowbuilder.io/docs/guides/use-variable-picker/" rel="noopener noreferrer"&gt;&lt;code&gt;outputSchema&lt;/code&gt;&lt;/a&gt;. A node that does not, simply never shows up in any downstream picker. No special cases. No component knows about any other component. The graph and the schemas carry all the meaning.&lt;/p&gt;

&lt;p&gt;This is how we tend to think on this project. When we want a new capability, the first question is rarely "what component do we build." It is "what does this need to become data, and what can read that data." Make the contract explicit and make it JSON. After that, a new capability is mostly a question of what each node reads and what it exposes.&lt;/p&gt;

&lt;p&gt;The Variable Picker looks like a small convenience: two braces and a dropdown. Underneath, it is the feature that finally lets one box pass a value to the next. That is the line between "do this, then this" and a real program. Cross it and you have a tool. Stop short and you have a diagram.&lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
