In this chapter, we're going to develop the live run status. So far, we can successfully execute our nodes, we can even interpolate their values, but we don't have an indicator as to which node is currently running, nor do we have an indicator if a node fails. So just like in the previous chapter, I've prepared a specification with all of the prompts which we are going to run in this chapter. So go into our GitHub repository either on the main branch or go ahead and find chapter 18 live run status and in the specifications folder you will find the new prompts. So the live run status needs to start from somewhere.
And in our case, the answer is from the run workflow task. This is essentially the trigger.dev environment. And inside of this environment, we have the option to store the current status of a node, whether it's executing or whether it failed using trigger metadata. And then we somehow have to send that to the UI So Trigger real-time hooks can accept that and display it accordingly. So let's go through the first prompt together, in which we are going to stream the step status from the Trigger.dev run.
So instead of our run workflow task, Stream each node's live status so the canvas can show progress. Before running anything, build a list of the steps we are about to run. Each just a node ID and status starting at pending. And then publish it to the runs metadata under steps value. Export the step type and call it run step.
So we are here establishing the types and the default state we need to maintain each node's progress. As we go, set a node's status to running before its executor. So before a node reaches the executor, let's go ahead and label it as running. And then after the executor is finished, mark it as done. Or failed if it throws and at that point also stop the run, so no going forward.
And then republish the metadata on every change. Right after marking a node running, force the metadata to flush. Otherwise, that state gets overwritten by done before it's ever pushed and we'd never see the spinner. So this is something that I've come to find out after a few unsuccessful implementations of this. So when working with Trigger.dev's metadata, you have to flush it after certain events, otherwise it simply won't reach the canvas and it would appear as broken.
So that's why I have explicitly added it to this prompt. Also flush right after marking a node as failed, before the run stops. A thrown run reaches no output, so the flushed metadata is the only way that failed state ever reaches the canvas. Also return the final steps from the task so a successful run's finished state is guaranteed. Keep the existing interpolation and execution as is, and finally, use the trigger.dev tasks skill.
So let's go ahead and implement this prompt. Let's take a look at the change. So we now have metadata imported from trigger.dev SDK. We have a new exported type called run step with node ID and the status which can either be pending, running, done, or failed. Then in here, after we topologically sort our nodes, so we have a new constant called order, we go ahead and we construct all of the steps from that order and we make sure that each one starts with pending and then we add those to the metadata under the steps property.
So now we replaced the for const loop to a regular for let I loop, I'm assuming so we can keep track of the index here. So now we manually have to maintain the id and then we have to construct the step using these steps and then accessing it via the index. So before we actually await the executor, we go ahead and set this status to running and we modify the metadata once again and then we do metadata flush so it actually appears on the canvas. We now execute the executor instead of a try and catch because that is the only way we can capture if it failed and if it did, we modify the step status to failed, we once again change the metadata steps and then we do metadata flush, we do an early stage hand close and we also throw an error. Otherwise, if it doesn't get into the catch block, we do step.status done and metadata set steps steps.
And we have simplified the return output. Brilliant. So very clean and surgical change. So now that our trigger.dev task is actually outputting the status, we need to subscribe to that workflows runs. So our next prompt is going to be the following.
I want one shared subscription to this workflows runs that any component on the canvas can read. Give me a client provider that subscribes to workflows runs in real time by their tag, which is the workflow ID. So remember when we added that inside of our actions.ts. If you scroll down you can find run workflow action. That is why we added a tag.
I told you that it's going to come in handy later. So make sure that you actually have this tag right here. Using a public access token passed in as a prop. Call it workflow runs provider. So we are creating a new provider.
So we're going to wrap our entire LiveBlocks room, our canvas inside of it, and then any component in there will be able to access which node is currently running and all other metadata we decide to pass through. Expose a use latest run steps hook that returns the most recent run steps plus whether it's still live, preferring the runs initial output steps and falling back to the live metadata steps. Live means the run is queued or executing. So we're going to develop a hook called use latest run steps which will either return the metadata values or the current live values. So there is a difference.
That is because Trigger.dev outputs values using their real-time hooks of the currently running task, but also preserves all the values of the previous tasks which have completed. So we're gonna need to create a hook which checks if the live one is active so it displays the most up-to-date data or falls back to the data which is now in the metadata which we stored ourselves. You're going to see in the code, it's easier that way. And put it in the workflow features components. Use trigger.dev real-time and frontend skill.
So let's go ahead and run this prompt inside of the same conversation which has context of what we're building. You should now have a new component inside of features workflows components called workflow runs provider. So in here it's marked as use client and it uses trigger.dev React hooks, specifically use real-time runs with a tag. And in here it does exactly what we described it. And it uses the access token.
So that's the next thing which we are going to have to create a proper consolidation and cascading of that access token which we are going to hand down as a prop. Beautiful. So use real-time runs with tag and this is how we connect to whatever our run workflow is outputting, right? So we are currently outputting some things using metadata set and metadata flush, right? But the only way we can subscribe to that is by using the correct tag which is workflow, workflow ID because that is what we have set inside of our actions.ts.
In here we just have a normal use memo for the value, so we have the runs, we have the error, perfect. This is simple enough. This isn't really a complicated function at all. And in here we have a hook, use workflow runs. This is simply so we can access the value of this provider inside of any component we might need.
And then we have this, which we requested use latest run steps. So in here, it created a hook, which will always produce the latest run, right? So it maintains whether we are live, which are the metadata steps and the steps. And you can see how it falls back in between the output or the metadata steps. So that's what I was talking about.
Your code, your agent should have generated something similar because Trigger.dev outputs multiple variations of their real-time status. And for the nodes which have already been finished, we need to fall back to some other value. Brilliant. So if yours is anything like this, it doesn't need to be line for line, but it should be a workflow runs provider, and it should use use real-time runs with tag. Now let's actually wire this up.
In the workflow page, wire up the real-time subscription. Mint a read-only public token scoped in this workflow's run tag, good for about an hour, and wrap the canvas shell in the workflow runs provider we just made. So that component we just generated. Passing in the workflow ID and that token. Use trigger dev real-time and frontend skill.
Perfect. So in that same conversation let's go ahead and run this prompt. So let's take a look at the modification it did to page.tsx. So so far you should have three changes, one in the run workflow which outputs the live status, another in the workflow runs provider, which consumes what our task is outputting, and then one change in the page inside of the workflow ID, which is responsible for creating the access token to pass to the workflow run provider so we can correctly communicate with the running task. And the reason this needs to be authenticated using the access token is so that someone, some third party cannot maliciously look at our running tasks, of course.
So we import auth as trigger auth from trigger.dev SDK. We use an alias because we already have auth from clerk next.js server and we use our new workflow runs provider. Then we generate a runs token using trigger auth create public token. We go ahead and give it a read access and we tag it to the workflow ID and we give it an expiration time of one hour. And then we simply wrap the workflow shell inside of the workflow runs provider passing along the workflow ID and the access token.
Perfect. Very simple, very precise. There's only one more thing we have to do and that is to paint the nodes. So we have to modify our step node component and make each node reflect the latest run. So we have to look up this node's status using our hook UseLatestRunSteps by matching the node ID.
When it's running, show a spinner in place of the node's icon and give it a blue border. When it failed, give it a destructive border. Only treat a node as running while the run is actually live. If the run has ended a node left marked running should stop spinning. So let's go ahead in the same conversation and completely finalize this feature.
Let's take a look at the changes in StepNodeComponent. So instead of the component, we now import UseLatestRunSteps from our Workflows run provider. We import the Spinner component from Components UI Spinner. We pass along the ID to the prop in the StepNode component, we destruct steps and whether it is live using useLatestRunSteps, we extract the current status of our node by filtering or finding our exact ID of the node. We define isRunning if status is running and if we extract isLive from useLatestRunSteps.
And we define isFailed if status is failed. And then very simply we have modified the most outer div to change the border blue 500 if it's running and if it's failed, border destructive. And if it's running we don't render the icon but instead we render the spinner and then we fall back on the icon otherwise. Beautiful. So once again, very clean, simple surgical change.
Now let's test it out. If you try to enter any of your existing workflows, you will probably see an error like this. Steps don't find is not a function. That is because those are existing workflows which have different metadata in them. And as you can see, we actually change the steps from being a number to being an array with object inside.
So that is why all of the previous workflows are going to fail. Let's go inside of our Neon database, let's choose our project, tables, and in here let's clean up everything we have in the workflows. So delete all records. I would also recommend doing the same inside of your LiveBlocks dashboard, simply so you don't have any orphaned rooms. Then go ahead and construct a new workflow.
Start node, connect it to open URL, and make sure that this one has a working URL and then connect it to the second node and in here make sure you add an invalid URL. We want to test if this node actually fails and now let's go ahead and click run and let's see there we go so this node is currently in progress and let's see what happens once we reach the open URL to node to which we purposely put an incorrect URL and as expected this node then fails. Keep in mind that trigger.dev has built in retries, so what it's going to do is it's going to retry I think about three times. Let's see, yes, it's going to attempt a maximum of three attempts. So you will probably see this repeating and that's another benefit of using a trigger because otherwise this would have been a very complicated feature to implement ourselves.
Amazing. And now after three attempts it is stuck here. Beautiful. So we are pretty much finished with our main functionality. Now go ahead and stage, commit and push your changes.