Now that we've added collaboration to our workflows, let's focus on the right sidebar component. The right sidebar component will have several purposes. The default one is the toolbar. The toolbar is going to display two categories of nodes, triggers and actions. Clicking on the node will simply add it to the canvas.
The second purpose of the right sidebar is the editor. Whenever a user selects a node from the canvas, we're going to automatically switch to the editor and display that node's information. From here, user will be able to modify the node data. To speed up development of this component, I've prepared a template in our github repository. The template only focuses on the UI and avoids adding any business logic.
Instead that's what we're going to focus this lesson being on. So we learn how to add the actual functionality to the component rather than you watching me type a thousand class names and copy after me. Once you're in the repository, you can either use the main branch or preferably go and find chapter 14 workflow toolbar and then go ahead and find the templates folder. In here, you will find a new component on top of the two existing ones that we've had called Write Sidebar. Now go inside of your existing features, workflows, components, Write sidebar and replace the entire content inside with the template.
As I've said, the template exclusively focuses on the UI. As you can see, writing this together honestly wouldn't be too educational. You would just have to pause the video all the time and then copy a bunch of these class names. They are nothing more than compositions of Shatsy and UI components. I've also written descriptive comments so we understand exactly what each of these components is doing, and we're going to go over the entire thing together.
But first, let's see what actually renders now. As you can see, our right side of our component now has the toolbar which reads from our node registry and displays the correct nodes. It also separates them in their correct categories. We also have the editor tab, which we are going to wire in so that when we click on a node, it's loaded here and we can change its data. We also have a run button at the top as well as a drop down with an option to delete the workflow.
None of these actions buttons do anything, but that's what we're going to focus on in this chapter, enabling the right sidebar component. Let's go over the source code of this component to understand what we are working with. The first function we have is the node icon. Node icon is used to display the icon inside of an accent color. So exactly what you see here in the start and open URL.
The actual icon as well as the accent is extracted from the node registry which matches the type which was passed for that node icon. So node registry is a very important component in this project because it's the manifest. It is the source of truth for all nodes. So later when we point our agent and tell it to create 50 new nodes, all it's going to have to do is modify the node registry and the right sidebar component, for example, will automatically load all of those nodes and their respective icons and their respective accent colors. Next, we have the section component.
The section component is this right here. So either toolbar or editor, but it also has the option to display an icon if needed for this scenario. When we select a node in the canvas. So then we will be able to display its name as well as its icon. So depending on the state, we can either use the section component to display the text toolbar and editor or to display an icon of the node the user has selected from the canvas.
Next, we have field input. Field input is this exact component right here, which is used to render the nodes data. Next, we have the inspector function, which is as the comment says, the editor tab. So this tab right here, which currently renders no node selected as you can see in this paragraph right here. So if there is no node it will render no node selected.
Otherwise, if we do have a node selected, we are once again going to extract its definition from the node registry. And then we will be able to render all of the fields that it has. You can see how we iterate over the fields and depending on the field we render the appropriate component. For example, in our node registry the start node has no fields so we will simply display no properties but if we click on the open URL node we will read hey we have one field with a placeholder youtube.com which is exactly what ends up being rendered here. Next, we have the toolbar tab.
So that is the default view right here. And in here we define the sections. You can think of sections as categories for our nodes. So we separate that into triggers and actions. Once again, we make sure that this is compatible with our node registry because each node needs a kind.
And so far we've only established two kinds, trigger and action. Which is also the type step note kind here which we make sure that we strictly type here so we it's not something like this cannot happen it has to be strictly typed that's why node registry is the source of truth Then we go ahead and we use the entire node registry export, which is this, so this exact object which holds all of our nodes and we create definitions for every single component. Then we use the palette and the palette basically renders the accordions by iterating over our sections. We define the sections here, which are basically categories and inside of each accordion which can be collapsed as you can see, it simply goes over all of the items inside or definitions which we extracted from that node registry. And we render a button for each of the node.
And we also render that node icon which we first saw at the top of the component. We then have the header component, which is basically this at the top, which has a drop-down menu, which simply renders a delete workflow button. And we also have a run button component right here. So I've purposely separated those two so it's easier for us to work with. And then we have the sidebar component itself, which is the whole thing, and all of these other things are plugged into it.
So actions menu, run button, and then we have the editor, the toolbar, the palette, the inspector. So now that we've established the source code and we know how it works, let's go ahead and make this right sidebar component functional. Before we implement the right sidebar functionality, I would actually recommend committing this file. The reason is that git diff right now is absolutely huge and now imagine trying to look at your agentic coding changes in this file on top of that. So to make this easier for us, go ahead and commit.
You don't have to push because we are still developing. And now every change we do will be clearly visible in the git diff. The first feature I want to add is the ability to add new nodes to the canvas when we click inside of the toolbar section. To understand how to implement this, we need to go into the right sidebar source code and find which function is responsible for rendering these two buttons. Instead of the right sidebar, you can find the palette function, which is the toolbar tab, which renders a button per node type that it adds to the canvas.
So once we render the sections, meaning the accordions, which are basically categories of nodes, inside of each category we render the definitions. Each definition is presented as a button with the node icon, which is exactly what we are seeing here. And right now we already have an onclick which calls the add function right here. The problem is this function currently doesn't do anything. Let's construct the following prompt.
This will be a prompt in three paragraphs so don't immediately execute it. We're going to additionally add more context about how we want this function to look like. First, the main functionality. When I click on a node in the palette in our right sidebar.tsx added to the canvas in the middle of the current view. Generate the node's ID with crypto.random.uuid That's the first paragraph.
Now let's add some more information about how this function should actually work. If I add several nodes of the same type, so if I have multiple agent nodes or multiple open URL nodes, we need to differentiate them. So number them, act 1, act 2, open URL 1, open URL 2, so they stay easy to tell apart. But only allow a single trigger node. So we are not going to allow workflows which are able to be executed in multiple trigger nodes.
So only one trigger node per workflow. So if one already exists, show an error with a sonnet host instead of adding another. And then let's add the last piece of information before we execute this. The palette lives in the sidebar component, which is outside of our canvas component. So let's actually tag that component as well.
So the two need to share one React flow store. So make sure to wrap the workflows ID page.tsx, which we could also tag directly. So dashboard workflows ID page.tsx in a React flow provider. So it sits above both the canvas and the sidebar. Now remember how we modified our agents.md to instruct it how to use React flow.
In theory, it should recognize that this is a scenario where it should load that documentation, but just in case we can slightly remind it, follow the agents or Claude Endy rule for using React flow. So this is our full prompt. Let's see what kind of function it's going to develop. Looks like the generation was successful. Looking at the summary, it's describing exactly what we requested in our prompt, but let's look at the source code to see exactly what was generated.
So the first thing I would expect is that we have a modification in our dashboard workflows ID page.tsx. So it imported react flow provider from xyflow react. And then inside of the room component it actually encapsulated my workflow shell within the React Flow provider. That is because the workflow shell is both responsible for rendering the canvas and for the right sidebar. So canvas and right sidebar are siblings, right?
So we needed to put them under the same context because right now the canvas has access to React flow using use live blocks flow. But right sidebar had no access to that component. They are siblings, right? So that's why we needed to add a React flow provider wrapping the workflow shell. So now both our canvas and our right sidebar have the same access.
Now let's take a look at the right sidebar, the actual component. So we have useReactFlow and useStore imported from xyFlowReact and we have toast imported from Sonar. Those are the import changes. Now we have no other changes besides the palette function which is perfect, exactly what we wanted. So using the useReact flow, we first give it a type, stepNodeType, so it matches our node registry.
From here we extract getNodes, getViewport and addNodes. So my agent decided to first calculate the width and the height of the canvas. So in the add function it can calculate where to position it, because I requested I wanted to position it in the center. But let's see what it does first. So inside of the add function it first reads the definition of the node I want to add by using the type from my add function here and then it extracts the definition using node registry type.
Then it loads all of the nodes that I currently have in my canvas and it goes through the definition. If the definition of the node I'm trying to add is a trigger and my nodes already have a trigger component, it throws an error and an early return. A workflow can only have one trigger. So that's the guardrail which we set in the prompt. In here, it defines the count of the nodes of that same type which I'm trying to add.
So it counts how many nodes do you have in the canvas right now. And then it constructs the title of this node which is going to add based on that count plus 1. So we then have open URL 1, open URL 2. Then as I said it uses the get viewport to get the coordinates and the relative zoom so it can calculate the position based on our canvas's width and height and whatever it needs to do to get to the center. I trust AI when it comes to math.
And then it uses add nodes which it extracted from the use react flow and it creates the ID using crypto random UUID, gives the type of step, that is basically the name of our custom step note component, gives it a position and then passes along the data, the type, the kind, which is mapped to definition kind, which can be action or trigger, the generated title and empty values. Looks very good. Now let's go ahead and actually see if it works. So I like how it didn't modify any files it didn't need to modify. So if I go ahead and first try to add another trigger node, there we go.
A workflow can only have one trigger. Let's click on the open URL and would you look at that? We can now add different nodes and look at how it increments the number of nodes. Beautiful! And since we already connected LiveBlocks, we actually have data persistence built in.
So if you do whatever you want to do with these nodes and then change your workflow and then go back to the one you were in, you will see the data is persisted. That's right. So LiveBlocks is currently the source of truth for our React flow data, but obviously we are later going to synchronize this with our schema graph field, but not right now. Brilliant! So, this is how the function should look like.
I hope yours is something similar. If it isn't, you can of course visit this exact branch. So go ahead and find a chapter 14 workflow toolbar and then go ahead and find, not inside of templates, right sidebar, but go inside of my features, workflows, components, and in here you will find right sidebar and you will find this exact palette functionality. So if you have this working of course it doesn't need to be the exact same code it can be slightly different as long as the functionality is the same and you can actually add nodes and they increment in numbers. So if you did this, I would recommend actually committing this so you once again have a clean slate so it's easier to track next changes.
So you can commit with something like add toolbar add function. Now that we've encapsulated our canvas component and our right sidebar component under the same umbrella of React flow provider, we can add another feature which makes it so that whenever a node is selected in the canvas component, its information is displayed in the editor tab. And we can achieve this in one line. What's important is that you have use store imported from xyflow react. Then scroll down to right sidebar function and replace the template version of the selected constant into the use store selector version, which has access to the nodes and has the ability to find the selected node.
And if you try it out, selecting any node in the canvas will properly render that node information in the editor. With the selected constant populated, all we do is pass it to the inspector component. The inspector component then matches its definition from our node registry. That's how it renders the necessary fields like URL and the input. And for start node, it doesn't render anything, because that's exactly what we have defined in our node registry.
The start node has no fields, but open URL has one field called URL, so that's exactly what's rendered. If you were to play around and add another field in the fields array of the open URL, it would immediately be displayed in the editor tab. If you've added any additional fields, make sure that you now remove them and then go ahead and stage and commit this change. Now let's make it so that the editor fields actually store the data which we type inside of these fields. Right now we can't even type inside because there is no value control in these fields.
So inside of the right sidebar find the inspector function and go ahead and add use react flow and give it step node type inside. And extract update node data. Now let's scroll down until we find field input on change and in here let's remove the template to do and let's actually use that extracted function update node data, target the correct node id and then update the values matching that field key which we are currently modifying which is currently rendered using definition fields and preserving all other values this node might have and assigning the value to that specific key. If you try typing into the field now, it's going to work and if you refresh, you will see that what we've entered is persisted, but it's currently only visible in the editor tab. It would be nice if it was also visible in the node component.
To achieve that, we have to go inside of our features, workflows components step node. Then, let's extract the values from the data so we can extract the fields from the definition of this node. And once we have the fields, all we have to do is iterate over their values and render them. We're going to render them right in between the closing tag for this div and this handle. So go ahead and add this snippet and save the file.
Once you go back to your canvas, your nodes are now going to display each field and its respective value. If you want to see how this snippet works in action with more fields, go inside of your node registry, find the OpenURL node, and add an additional field. Then go into the editor and add some value to that field. You will see that the value is immediately rendered in the StepNode component. After you've done testing, remove the additional field from the OpenURL node and stage and commit your changes.
Let's improve the user experience of our app so that whenever we click on a node in the canvas, we make the editor tab automatically open. Inside of the right sidebar function, let's add a new state, previousSelectedId. And let's give it a default value of selected.id. Make sure that you use the optional operator in case the selected is undefined. Then let's open an if clause which checks if we have any selected node and if the currently selected node is different from what we have previously stored.
Then let's modify this new field which we've registered and let's change the tab to editor. And if you try it out, every time you click on the node in the canvas, it's going to automatically open the editor. Now depending on the node, Sometimes we are going to need to render a simple input, but sometimes we are going to need to render a text area. So let's create a prompt which is going to enable the right sidebar to dynamically decide which component to render depending on the field property. We can achieve that with the following prompt.
Let a field opt into rendering as a larger multi-line text area instead of a single line input. First, in the node registry, add a flag to the field definition So any field can mark itself as multi-line. Then, in the right sidebar component, add a new field component that reads that flag and dynamically renders either the single line input which we currently render or a multi-line text area. Use it for the fields in the editor. Let's look at the output.
Node Registry now has an optional multi-line property, which is a boolean and a flag to the node field type. So any field can opt into a text area. The right sidebar has replaced our existing field input component with a new field component which dynamically renders either a text area if field multi-line is set, otherwise it renders the input. So now let's look at the actual code changes. So in your node registry, in the node field type, all you should have is a brand new property, multi-line, which is a boolean, and option.
In your right sidebar component, you should now have a new import for the text area. As per the field input component, it should be replaced with a new field component, which has the exact same props as before, but it dynamically renders either the text area or the input, depending if field is multi-line or not. And replace the actual field input to the new field component. If you're wondering which changes I have down here, it's just the auto switch to the editor tab we've developed previously. So go ahead and add another field to the open URL node, and this time give it a property multi-line set to true.
And if you go ahead and open it in the editor, you should now have rendered a text area instead of an input. Let's go back to the node registry and let's add one more property to the node field type. Right under our new property multi-line let's register another optional property called required and once we've added it here let's also add it in our URL property. We can now also get rid of the temporary multi-line field we've added. Then in the right sidebar find your inspector component and scroll down until you find the label and beneath the label go ahead and check if a field is required, and if it is, render an asterisk with a class name TextDestructive.
And now every required field will have an appropriate sign, So we know we need to populate this if we want to execute this workflow and use this node. This is a pretty good place for a checkpoint, so go ahead and stage your files and commit. Now let's enable the only item we have in our menu, which is to delete the workflow. So let's construct the flow to enable this. First, make the delete workflow item in the right sidebar, delete the whole workflow.
And then send me back to the homepage. So That's the overall goal that we want to achieve. Now let's get a bit more technical. It needs a server action to actually remove it from the database. It needs to scope it to the current organization.
The same action should also clean up the workflows' respective live blocks room, so we don't have any orphaned rooms, which uses the workflow ID as its room ID. Execute it from the menu item and disable that menu item while the delete is in flight or in progress. The sidebar doesn't know which workflow it's in yet. So we need to pass the workflow ID down to it as a prop from the workflow shell. So the action knows what to remove.
So what does that mean? Well take a look at the workflow shell component. We currently have a workflow ID destructured, but we don't use it. So we need to pass it to the right sidebar so the right sidebar is aware of which workflow it's in. And then let's go ahead and simply give it some pointers about where to create server actions and where to create database access and Let's end it with a skill, LiveBlocks best practices for a room removal API so it doesn't invent any new methods but uses the official instructions.
Looks like a very well constructed prompt so let's execute it. Let's look through the generated files one by one. First, I'm going to start with the data.ts inside of my features workflows. In here, the agent has added a new function, delete workflow, which follows the same convention as my create workflow, my get workflow, and my list workflows. Then let's take a look at the second file, actions.ts.
Once again, inside of features, workflows. So in here, it imports the LiveBlocks client and the newly generated delete workflow from the data. It generates a new server action called delete workflow action which accepts the ID which it needs to delete. It scopes the organization ID and throws an error if not available and then it calls the data delete workflow function which we just reviewed. In case the workflow is not found, it throws the error to let the user know that whichever workflow they are trying to delete doesn't exist or isn't available to the organization that they are currently in.
And in here it also performs live blocks delete room and passes the ID because the ID of the workflow is the same as the one of the room. Then it calls revalidate path and redirect, which are both from next cache and next navigation. So we revalidate the workflows endpoint and the layout. Perfect. So this is great.
Let's take a look at the workflow shell. So as expected, we have simply passed along this unused workflow ID to the right sidebar component. In the right sidebar component we have imported delete workflow action then let's scroll all the way down to find the right sidebar which now accepts a workflow ID and passes the workflow ID to the actions menu and the actions menu now accepts the workflow ID and has a new state isDeletingSetIsDeleting. It disables the drop-down menu item if isDeletingProp is set to true. It prevents the default on select, sets isDeleting to true and then calls delete workflow action.
Now sometimes my agent performs the pending state using the use transition hook. This time it chose not to do that. So if yours did it, it's perfectly fine. As long as the on select actually executes the delete workflow action, the code is perfectly fine. So now let's go ahead and actually test this out.
So I currently have 9 workflows and 7 rooms in live blocks. So I'm going to go ahead and refresh just in case and then I'm going to go ahead and go inside of my drop down and I'm going to click delete workflow and let's see what the error is about. Failed to delete workflow. So it appears that it actually deleted the workflow from here, but I'm assuming something went wrong with deleting the live blocks or maybe not, but we can verify by looking inside of here. So what actually happened?
So I deleted a couple of workflows and all of them have been successfully deleted both from the database and from the LiveBlocks dashboard. And I even tried going inside of my actions and commenting out LiveBlocks delete room to see if maybe that's the culprit. But regardless of what I did, the server action always seems to throw this toast error. So I'm beginning to think that perhaps this catch is incorrectly used here. So I'm going to highlight the usage of this server action and I'm going to write the following.
This dot catch is always throwing, but both the database deletion and the live blocks deletion work. Can you verify your implementation using Next.js node modules documentation for why this could be? So Next.js, if you don't remember, injects the documentation in its node modules so agents can read it. So let's see what it has to say about this. After some back and forth with the AI, I've come to the conclusion that what actually threw the error was the redirect.
So this specific redirect works by throwing an error. I actually remembered this by reading some documentation or some problem someone wrote about. So I questioned it about that and the first thing it did which I purposely skipped in the recording is it recommended I use some weird unstable feature. So then I told it the following. So if you're in the same situation as me, simply tell it, we already have create workflow action.
So that's what I told it. We are using the same redirect which you deemed problematic in the create workflow action. Look at how you handle its execution and apply the same. And that was the solution. So in the end, the code should look like this.
In the drop-down menu item, on select, simply go ahead and import use transition and get is pending and start the transition from here. And then wrap your await delete workflow action inside of start transition. If you want to see the exact git diff, let me show you. So import useTransition from React, import deleteWorkflowAction from features, workflows, actions, and then inside of the actions menu, execute the hook useTransition, Extract isPending and startTransition, set the disable to be controlled by isPending and then invoke the server action inside of the startTransition function. This way you won't be greeted with any errors.
That brings us to the end of this chapter so let's go ahead and stage, commit and push our changes.