Now that we've added all the nodes, let's go ahead and develop the logs panel. The logs panel's purpose is to display to the user exactly how each workflow went. We can see that more closely in this image canvas2.png. So each run will be inside of a batch with individual steps inside. And we're going to see exactly how long each step took before it was completed and we're going to see if any step is in progress or if it was skipped or simply not there yet and each run will be separated with some kind of divider.
The place where we have to start developing this feature is where we actually run the task. That is because in order to develop this component we need to add some crucial metadata to our tasks. For example, we need to have duration but right now inside of our run workflow task we don't calculate how long each step lasts. So this will be the first prompt we have to construct. We're going to need to add some more properties so this run workflow actually tracks duration.
You can find all of the prompts which I'm going to use in this chapter inside of this specifications folder inside of chapter 21 console panel. First, let's go ahead and install a new dependency called Pretty Milliseconds. So let's explain to the agent what we are trying to achieve and what we need to modify first. I want a console under the canvas that shows what each run did, a list of every run and its steps. And clicking on a step shows what that step produced or its error if it failed plus how long it took.
So that's the general idea of this logs panel. A display of every single step within a run as well as how long each step took. And clicking on any of the steps should split the logs panel in half and then in the other half display either the JSON formatted output or the error if it failed. If a step hasn't been reached yet, it should appear inactive like this. And if it's in progress, it should be spinning.
So in the first sentence, we simply give it a general idea of what we are building. And then we start with the first request, which is what we established that we need. We need to modify our run workflow task. Right now, our run step only records its node ID and status inside of run workflow. So there's nothing to show yet.
Basically, if you go inside of the run workflow right now, you can see that we are kind of storing something in the metadata here, but we are just storing the node ID and we are storing the status, right? Running, which is useful, but we explicitly developed this for the canvas, right? So in the canvas when a node is running it appears as loading, which is useful for the logs as well, but we are missing additional metadata like the duration and the output or the error. Those are the things we now want to add to the run workflow. So that's what we described.
Right now, only a few things are being recorded inside of the run workflow, so there's nothing to show yet. So make each step track everything the console will show. Which node it is so we can display its icon and title, its status as it moves from pending to running to done or failed, how long it took, whatever it output and its error if it threw. Basically exactly what we just described we need so we can properly develop that console panel. But right now we're just focusing on adding more things to our run workflow.
Once we have all of those new things, it's important that we expose all of that run data through our workflow runs provider. Because I don't know if you remember, but we don't ever explicitly directly use the trigger.dev hooks instead we developed the workflow runs provider which uses the use real-time runs with tag and this provider is where we keep track of everything in this value here, right? So we have available runs here and then I believe we also developed use latest run steps in which we kind of summarize if a step is live and we extract the metadata of those steps. So we also need to make sure that whichever new fields we add inside of the run workflow, we also add to the workflow runs provider. So I think this is a pretty good description of what we need.
And we also give it a little hint, don't build the UI yet, just get the data in place. Brilliant. And let's go ahead and now execute this prompt. Now let's review the output. Keep in mind that your outcome doesn't need to be exact as mine.
It doesn't need to be a line for line identical change, but I will show you some of the things you should look out for. So the most obvious change should start from the run step because the run step is essentially the type which describes what we are storing inside of a metadata of a run step. So previously we only had the status here and node id which was now extended with what we requested. We have a type, we have a title, we have duration in milliseconds, we have an output and we have the error. So if your agent didn't do this, it might be a good idea to tell it also make sure that our runs output the error, Make sure that our runs output the output or the duration in milliseconds if it forgot to do any of those.
And you can validate that simply by looking at the run step because this is kind of the source of truth. This is the main type of the run step. So if you have anything similar to these, the names doesn't have to be exact, it's probably correct. I also have some new imports, so node type from the node registry so I can give it the proper type so each run step can only be start, open URL, act, extract, observe, etc. Basically our nodes.
Let's take a look at the rest of the code here. So now when it iterates over the steps here, besides just getting the node, which was usually enough, it also makes sure to explicitly extract the node data type and the node data title. So previously status was enough with the node ID but to satisfy the new run step now it also adds the type and the title. And here's one thing that perhaps your agent didn't do at all. So my agent decided to save some time and create a helper function called publish steps.
So it replaces having to do metadata.set steps every time. It also casted steps as unknown as the serialized JSON from trigger.dev core. So this is a very unique thing. I doubt your agent did the exact same thing but don't be afraid if it didn't, it should work just as well. This isn't required.
As I can see, this is just a helper so it doesn't have to repeat the same thing all the time so now in all the places where we did metadata set my agent replaced this new function publish steps all of this is left unchanged and As you can see every time it does metadata set steps, it just calls publish the steps, right? We change it to running, now publish it. Let's scroll down. This is how it calculates how long my task, my step actually lasts. So it develops a new constant starter that using date.now and then inside of the let's find it here it is if it catches meaning if it's inside of an error it starts the calculation of the duration so because it failed so we need to calculate date now minus started at and it populates duration milliseconds it populates the error right that's what we wanted because previously the only thing we populated was the status but now we populate the duration the error and we publish the steps in the successful run here's what we do previously we just did a wait executor and we stored it inside of an ID in the outputs object.
But now we extract the output in its own variable and then we do the same thing as we did before. We store the output in the ID of the outputs object but we also added to the step.output so it can be published using the publish steps. So exactly what we described is happening. We are now sending to the frontend using the metadata inside of this step, the output, the duration, the error, the type, the title. All of that is what we need.
And here we have one more calculation of the duration millisecond. So this one happens if a step fails and this one happens if it successfully completes. And that is the entire change. So it looks like a lot of things have changed but when you dissect it, it's actually not that much. Now let's go inside of our run workflows run provider.
So in here what I expect is some kind of additional helpers maybe like this. I doubt it's going to be exactly the same And I promise you don't have to worry about your code being different because we are going to add more prompts to this conversation that we are having. So your agent will be aware of the changes it just created. For example, mine created a new hook called use console runs and in here it uses the use workflow runs extracts the runs and then it goes over the runs sort them by created at so the latest one appears at the top and then creates an object with an ID, status, created that, is live, and steps for a run. Your agent probably achieved something similar, but it doesn't need to be exactly like this, right?
But you should definitely see some changes in the workflow runs provider here. Great. So now let's go to the second step. So now that we've added the necessary outputs to the run workflow task and we have attached them to our workflow runs provider, let's build the UI. So build a console panel below the canvas that lists workflow runs.
Every run and below it, it's steps. Each step shows its nodes icon, its title and how long it took. Format the duration with pretty milliseconds which is a package we added. A Step spins while it's running, turns red if it failed and looks inactive if it never run. Clicking a step selects it, clicking again deselects.
The accent colored node icon already exists as node icon instead of the right sidebar. Reuse it instead of building another. So if you take a look at the right sidebar component, you will see that in here we actually have a function called node icon and I think it's perfect to reuse for what we need right here. I think I still have the picture open. There we go.
You can see that this icon is identical to the one we have in both in the step node, but also in the right sidebar. Perhaps the one in the right sidebar is even more perfect because it's smaller for this. So that's why I'm telling you can go inside of the right sidebar component and reuse that instead of creating a new one. And then I tell it build the run list as a logs panel and wrap it in a console panel that owns the selection. Both new components in the workflow features components folder.
Then mount the console panel inside of the workflow shell component where it currently shows a logs placeholder. So let's go ahead and simply visit that workflow shell component. So that's the resizable panel that we have, right? And the only thing we have right now is a text which says logs. So this is the place it needs to populate and develop that console panel and use our new hooks to display the run data inside.
So I think this is a pretty good description of what we want and this is how agentic coding is usually done. You focus on the outcome rather than knowing every single little thing that we are going to develop. Obviously in here I'm kind of nudging it into the name of the components that I want. So both you and I have the same named components because in the next prompts that we're going to do, we're probably going to reference those new components. So that's why I'm being very explicit with my naming.
But Usually when you are doing agent decoding, you wouldn't know the name of the component, you would let the agent name it whatever you want. But because I want to make this easier for you to follow and more deterministic, I'm being very explicit about the name of the components that I want. So once again, you can find all of these prompts inside of the specifications folder in our GitHub repository. Just look for chapter, this would be 21 console panel. And let's execute this prompt.
After the prompt finishes, the first thing you might see in your app is that all existing workflows are now breaking. And that is expected. In fact, this would have happened if you tried running your app after we run the first prompt and that is because we fundamentally changed the definition of what step outputs. So if you see this, don't worry, just go ahead and create a brand new workflow. So first let's test how this looks visually and then we'll look at the code.
So you can compare both your UI and functionality and you can compare your code to what my agent did. So the empty panel looks like this. It simply says no runs yet. I'm okay with this and what happens when I actually run something? Well, I did that here.
So it displays all of the steps which happened. It also shows that this open URL took 7 seconds for example and you can see that when I click on it, it stays selected, right? Unless I click again, then it's deselected. One bug I can see happening is which I think was actually introduced in our first prompt and that is because I told it to make sure that all nodes which weren't reached display as inactive and the start node is technically never reached because there's no executor. So maybe you have this bug as well where your start node looks disabled.
We can fix that later. And let me go ahead and do one more run so you can see how this happens. So right now it's executing, you can see the start and you can see how it's currently spinning the open URL. So this state in the canvas and this state right here are both synchronized. And once it finishes, you can see that both of them get in their finished state.
So that is the UI and the functionality. And hopefully yours looks somewhat similar to this. And now let's quickly take a peek at the code. So inside of the workflow shell component, you should now have replaced the previous logs to do temporary paragraph with a proper component called console panel. Now inside of that console panel, it could be different for you, but here's what my agent did.
So first of all, it's a client component because inside of the console panel, it maintains which step is currently selected. It also developed a toggle function here, whether yours did this or not doesn't really matter as long as the functionality works. And the only thing it returns is the logs panel and in here it sends the currently selected node and it also sends the onSelected step which is the toggle. And then inside of the new logs panel, this is where we have all of the functionality needed to display each step in a row. So this is mostly just UI and cosmetics about this, so isSelected, isInactive, isRunning, which changes the spinner.
That's kind of one thing I don't like. I would prefer if is running displayed the spinner in place of the icon but not by replacing the entire component. But that's okay. I want to focus on polishing those things later because for now I just wanna focus on the big picture stuff, okay? And you should see somewhere in here the pretty milliseconds or pretty MS import from the package which we installed.
And in here we have the actual logs panel which uses our hook use console runs. Now, whatever your agent modified in the workflow runs provider is probably what it's gonna be using here to display these runs. But essentially, as long as you can see all of those things which we attached to the run using the step metadata in the run workflow, you should now start seeing here, right. So far we've only had the ID but now we should also have step title, step duration, step type, right? Thing is like that.
As long as you see that, it's probably okay. And in here we have some calculated fields like is running, is failed, is inactive. So that is kind of the general gist of how this component should look like and I think at this point both you and I hopefully are getting deterministic changes. Obviously the UI can vary a bit depending on which agent you're using but because we have such a large code base right now chances are that the agent did this incorrectly because we first started with the run workflow, then we modified the workflow provider and only now we are doing the UI. So the agent really understands what we are trying to achieve.
So now let's go ahead and continue to step 3. So the last thing we need is that when we click on a step in the logs panel, some output shows in the other half of that console panel. So the workflow console below the canvas, which we just developed, the console panel.tsx, lists each runs steps and lets you select one. Add an output view showing the selected steps result. Its output as formatted JSON, its error if it failed, or a short note when there's nothing.
Build it as an inspector panel component in the workflow features components folder rendered inside the console panel next to the logs and only while a step is selected. Let's run this. So to test if your agent developed this correctly, go ahead and select any of your steps And what you should see now is an inspector panel component, which renders a formatted JSON of that node's output. And with this inspector panel, I can also debug what's actually happening with my start node, because all of them appear as inactive and looks like my fear was correct, we have a bug. So because the start node doesn't have an executor, it is never considered done.
So I assume this is the fault inside of the run workflow because when we append metadata to these steps, the step without the executor never gets assigned as running because we never even reach this whole code block. So that's why it probably always appears as skipped or not yet reached. So let's go through some other bugs that I have. Now you probably don't have the exact same look or functionality, but as long as you have a panel where you can see your runs and your steps and you can click on individual steps and you can see their outputs, it is good. And now what I'm going to teach you is how I approach fixing bugs when a gentic coding doesn't produce something exactly as I imagined.
So the first bug I have is this start node being skipped or considered skipped. The second bug I have is when I click run, my spinning node looks like this. But what I would rather prefer is that if it kept this green background and then if it spun inside rather than replacing the entire thing, I think it looks prettier. And another kind of a bug which I have is that these two components aren't used, aren't put together using resizable panels like the rest of my layout. So I can't resize them.
So now I'm going to show you how I would prompt to fix this small little bugs. So if you happen to have any similar bugs, you can follow along or if you have some other bugs in that case, you can adapt my prompts to your specific problems. But the overall functionality of what we wanted to achieve in this chapter is officially functional. I would also recommend testing this new console panel on a more complicated workflow like this one just to see if all nodes are correctly rendered here. So you can see how my agent renders all of the steps which are going to happen and keeps them inactive until they are either in progress or they are finished.
So this even more shows how this initial start node is a bug because it infinitely appears as inactive. But I think this looks super cool especially with how synchronized everything is and that is because we use our workflow runs provider both in the canvas and in here. And let's quickly take a look at the output. I think this is really cool and there we go. I can even see when it didn't manage to do something.
Perfect. So this is how useful this inspector panel is because then you can see when something goes wrong. This technically isn't an error. The agent simply didn't succeed at what we told it to do. So perhaps we could capture this success and if it's not true we could manually throw an error if you think that should be a failed node.
But keep in mind that then Trigger will retry that step which can waste a lot of tokens. So sometimes maybe the user simply prompted something that makes no sense. For example, maybe I forgot completely what I can see on this page right here and I couldn't extract this. And also make sure to test one invalid node simply to see how an error looks like. So I just put invalid URL in here and let's see what happens now.
There we go. Error cannot navigate to invalid URL and you can see how when it retries it actually resets the output. So the culprit behind the bug in which my start nodes are marked as not run yet is inside of the run workflow. Specifically, when we try to extract the executor from the node executor, I then do, if there is no executor, simply continue, meaning that it never reaches the status of running. We can fix that with a simple prompt.
In my run workflow, a node with no executor, for example, the start trigger hits if no executor continue while still pending, so it shows as skipped forever and its inspector says it hasn't run. Instead of skipping it, mark that step as done and publish before continuing. The trigger does no work and has no output, so it should be read as completed. And the fix was very easy. So instead of immediately continuing, what we now do is we set the steps status to done and we publish the steps and then we continue.
So if I try running this inside a brand new workflow, hopefully the start step will now appear as immediately executed and we even have the correct comment here, this step produced no output. Now I want to fix the running step. So I want to fix how this looks. So what I'm going to tell it to do is to extract node icon from the right sidebar component into its own file in the workflows components folder and then update the right sidebar, update the logs panel and the new inspector panel to all import it from there. Then add a new running prop to the node icon which we are now going to extract that shows a spinner inside the accent chip so it preserves the background color in place of the icon and use it in the logs list.
So running step spins inside its colored chip instead of as a bare spinner which is precisely the issue I have right now. So we've now extracted the component node icon from right sidebar into its own node icon component inside of features workflows and as instructed we now have a running prop which simply switches the icon for the spinner but preserves the entire colored accent background. So if we test it out on a new run, the spinner should now look exactly as in the canvas. The last problem I have is the fact that these two panels aren't resizable. So the culprit is inside of the console panel, because this is where we maintain the selected step.
So when a step is selected, all we do is we render a div which renders the inspector panel, and we maintain 50-50 layout using Flex. So now I'm going to prompt to replace this with resizable panels. So inside of the console panel component, replace the plain Flex split between logs and the inspector with a horizontal resizable panel group and the drag handle from our components UI resizable. Keep the inspector rendered only while a step is selected. And there we go.
I can now resize either my logs panel or my inspector panel and the console panel code now uses resizable components from Shazia and UI. So we established a new resizable panel group and then we render the logs panel inside of one resizable panel and inspector panel inside a dynamically rendered other resizable panel. So now let's go ahead and git stage, git commit and git push our changes.