Skip to content

feat(sdk) Variable enhancement - #76

Open
szymon-t-sc wants to merge 33 commits into
mainfrom
feat/WB-139-variables
Open

feat(sdk) Variable enhancement #76
szymon-t-sc wants to merge 33 commits into
mainfrom
feat/WB-139-variables

Conversation

@szymon-t-sc

Copy link
Copy Markdown
Member

Workflow Builder introduced variable support some time ago, allowing sidebar controls to use variables from previous nodes or global variables.

This PR enhances the feature with the missing logic to make variable usage more robust and flexible.

Main changes

Variables provided by nodes to downstream nodes can now depend on:

  • selected type in the sidebar - for example, selecting a time-based trigger returns schedule information, while selecting an event-based trigger returns information about the event type
  • sourceHandle - if a node has success and error source handles, it can provide the actual node output to the success branch and the error output to the error-handling branch
Nagranie.z.ekranu.2026-08-14.o.18.32.10.mov

Node variables store:

Previously, variables from previous nodes were calculated when the control was mounted. We traversed the graph and calculated their values at the same time - now we keep nodes variables in the store and only collect them:

  • The list of variables further down the flow is calculated faster, as we only collect the variables that are available
  • It's now easier to check which variables are available from a node, and we can easily add a plugin to display them
obraz

New single variable control (previously used in conditions)

Nagranie.z.ekranu.2026-08-14.o.18.28.48.mov

It shows a date picker with {} when date variables are available.

Fixes

  • When an edge was added or removed, we sometimes had incorrect highlighting of values in controls. This is now fixed thanks to totalVariables
  • Global variables cannot be added in read-only mode

It's worth noting that the majority of the changes are encapsulated in packages/sdk/src/features/variables/. So while reviewing, we can identify what is used externally as a useful public API and note the core functions that could be valuable to expose.

Comment thread apps/demo/src/app/data/nodes/action/action.ts Outdated
@szymon-t-sc
szymon-t-sc marked this pull request as draft August 27, 2026 09:46
@szymon-t-sc
szymon-t-sc marked this pull request as ready for review August 28, 2026 10:09
@szymon-t-sc szymon-t-sc added the enhancement New feature or request label Aug 28, 2026
Comment thread packages/sdk/src/index.ts
Comment thread .changeset/sdk-variables-json-schema.md Outdated
Comment thread apps/docs/src/content/docs/node-schemas/form-controls.md
// Variables can’t change while the modal containing them is in use, so we only need to refresh them when they change.
// .length is critical here for performance.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [nodeId, edges.length, nodes.length]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The nodes.length / edges.length deps (and the key={totalVariables} remount in VariableDynamic) are a documented perf trade-off, and the sidebar remount masks most cases. Remaining gaps: rewiring an edge to a different source (same counts) and the 100 ms indexing race after addNode. A cheap fix later: subscribe to the suggestions-store state identity (one new object per refresh) instead of counts.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix for 100ms
Good catch with the 100ms indexing race. It didn't appear while using the app normally because we would have to connect something and then click the node within 100ms. I've added lastUpdateTimestamp, which should solve race-condition issues like this (suggestions store force refresh when any suggestion is changed) now hook dependecy array looks like this [lastUpdateTimestamp, nodeId, excludeTypes, includeTypes, edges.length, nodes.length]);.

Not cheap fix

A cheap fix later: subscribe to the suggestions-store state identity (one new object per refresh) instead of counts.

For clarity, suggestion store caches suggestions produced by the node, divided by its sourceHandles.

In useNodeVariables (here), we collect those suggestions by determining the previous nodes connected to our nodeId. This is calculated live for the hook when nodes.length / edges.length changes.

Traversing the graph is expensive, previous implementation also did that. The difference is that it now collects cached suggestions from the store instead of building them live (earlier version) - which is good because building them for different handles and conditions is more complex now.

And since we can't clearly determine which edge changes impact a nodeId's variables, watching nodes.length and edges.length makes sense.

Why I believe my approach is the right one
The question 'which variables are available for a given nodeId?' is, in my mind, a business-logic question. We shouldn't calculate the answer by relying on the React and Zustand hooks lifecycle, as it's less clear and can't be used outside React components.

We already have a dedicated getAvailableVariablesByNodeId function that can be called outside. Relying fully on hooks here would duplicate the logic.

Keeping getAvailableVariablesByNodeId as the basis of the implementation is better than splitting it. getAvailableVariablesByNodeId is used by the validation plugin and can currently be called when imported. If we only had a hook-based solution, we would need to entangle the validation function with the React component lifecycle, which is unnecessary. Keeping these functions independent like this allows us to easily adapt them to run on the server - we just need to pass the store JSON to them.

Regarding the totalVariables key
key={totalVariables} is mainly a result of the library we are using for the variable picker. Changing the suggestions property works, but the control behaves more reliably when we don't do it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants