Now that we have the basic execution logic in place, it's time to start developing the canvas component. And for that, We're going to be using React Flow, which is the fact the standard for developing workflows and it's a fully open source library. Since React Flow doesn't have an official quick start using a gentic coding, nor does it maintain any official skills in the skills SH registry, what we can use instead is their LLMs endpoint. So simply append LLMs.txt on the React flow domain and that's going to give you this markdown file which is basically an instruction file for agents on how to find relevant documentation for React flow. So we can use this instead of skills or MCP servers.
Let's go ahead and once again revisit our workflow shell component. And just like we've separated the right sidebar in its own component, let's do the same for the canvas. So either use the line selection functionality, if you have that, you can see that I have three lines selected here. But if that's not working for you then go ahead and tell it something like this. Separate the canvas div inside workflow shell.tsx in its own component just like right sidebar.
Do not alter the actual content of the new component. I just want to add one guardrail so it doesn't go off the rails and I don't know what it wants to implement. Let's go ahead and simply do that. After that you should have a separate canvas component in place where this was written in line moments ago. The actual look of your app shouldn't change because we explicitly told it that the new canvas component should preserve the content that was there before.
But now we have a separate canvas component which we can target for upcoming prompts. So let's go ahead and do the following. Modify the canvas.tsx component and set up a basic example of React flow using the documentation. And then in here, go ahead and paste this LLMs.txt documentation. There we go.
So here's what the agent produced. It set up a basic React flow example in our new canvas.tsx component. It also installed a new dependency xyflow react which is react flow 12. It also verified that there was an old package called react flow So that is deprecated and we can also verify that by very quickly visiting their documentation. And yes, we can see that this is the package it's supposed to use.
It replaced the placeholder content within our canvas component with a working flow. So we now have two connected nodes and some logic like use nodes state, use edges state on connect and some components like background and controls. It also turned this into a client component because it's interactive and it uses hooks so it's mandatory. Oh, sorry, no. And it added the mandatory style.css import.
It also added the size full so it fills the resizable panel from workflow shell. That sounds pretty basic, exactly what we wanted. So don't worry if yours looks somewhat different. This is, I'm pretty sure, exactly what's written here. I'm pretty sure that's it.
Just maybe slightly modified so it matches our export function canvas and in here it's default function app. So slight modification here. Great. So in here I can see one weird thing and that is that we are skipping the second parameter. Oh yeah, because we don't need set nodes.
So if you're added set nodes, it will just be unused I guess. So that's why this is skipped and that's why it caught my eye. I would prefer we do like set nodes but yeah that's going to throw a warning so I'm just going to leave it like this. We also have a very simple onConnect here and then div class name size full so it takes the whole space and a very simple React flow with background and controls. All that simple enough.
Let me take a look at all the changes here to verify. So yes, xyflow react package was added inside of workflow shell. We just rendered a canvas component and in here we have the entire new canvas component which isn't really complicated simple enough and if I go ahead and refresh we should see a very cool component right here called the canvas In dark mode it's currently broken but if you toggle it should look fine. Don't worry we're obviously going to fix this and adjust it so that it works in dark mode as well. But you should have this working right now.
I think there LLMs.txt instructs the agent to do exactly this which is exactly what we need at this point. So great. Now let's go ahead and modify the styles of this so it matches it a bit closely to what we expect in the canvas picture right here. So let's go step by step and turn the existing canvas from this into what we would expect to see. So the first thing that bothers me besides the obvious dark mode failure is this connection right here.
I would rather it looks like a step than this current curve that we have. So let's go ahead and from XY flow react import the connection line type. Then go ahead and find the React flow component and add the following props. Connection line type and choose smooth step, set the proper color of the stroke so it uses our border variable and default edge options type smooth step style and once again use the same color for the stroke. And once you save that it should get closer to what we expect which is this kind of step connection rather than the weird curve that it was before.
Let's go ahead and add some more style props which will change the look. So after these connection lines and default edge options, go ahead and add a style prop which is an object. Make sure to open double square brackets and then change these three variables and then add a typecast as react.css properties. So this should a tiny bit more modify the values. You can't really see right now, but the border has kind of changed.
This line has become thicker and some other small changes. And the last prop you should have here is max zoom. Make sure it's set to one. Now let's go ahead and add the dark mode support to our canvas. We can do this in the existing conversation but even if you add a new conversation, it will work equally.
So this is the prompt. Add light and dark mode support to the canvas component by syncing React flow's color mode with the app theme. Use the use theme hook from next themes package to read the current theme and pass it to react flow color mode prop so the canvas edges and controls switch between light and dark along with the rest of the app. Let's take a look at the output. The canvas now syncs react flow's color mode with the app theme.
Imported used theme from next themes and color mode from XY flow react. Read the current theme and passed it to the color mode prop, falling back to system during hydration before theme resolves. React flows color mode accepts light, dark or system, the same values Next Themes produces so the canvas, background edges, and controls now switch with the rest of the app. Since your background edge connection colors use the CSS variables, which we've added on purpose so we use background and border, those already follow the theme as well. So let's actually take a look at these changes here.
So we have now theme from use theme hook which was imported from next themes. We also now have type color mode imported from XY flow react. And let's see, so we now have color mode prop which adds the theme here as a prop from here. It casts it as color mode so it matches the type that XY flow expects and it falls back to system if it cannot read the value which can happen during hydration. And just like that we now have pretty cool looking dark mode and switching to light mode seems to work as well.
I'm just curious what happens during a refresh. Okay, so this is what I was worried about. During refresh the hydration appears to fail. So let me confirm this a few more times. Yes, so I'm guessing yes.
Okay. When I refresh it's broken but if I toggle from this state, then it works fine. So we implemented it, but it looks like there is some sync which we have to implement here because we currently have an error. So the fix for this is to add a sync external store hook which is going to resolve the theme first and fall back to some other mode during server side rendering and hydration. Since the prompt for that is a little bit longer and I don't think it makes sense for you to watch and copy this whole text, you can simply go inside of the specifications folder in the GitHub repository either in the main branch or in this chapters branch.
And then let's go ahead and in the same conversation where we implemented the canvas theme, let's go ahead and say implement canvas theme dot MD. So these are the changes that it did. It imported use sync external store from react in our canvas component and then it created a function called useMounted. This function useMounted simply returns the useSyncExternalStore and uses a no-op function here. It also uses some dummy unmounts here.
Then in the actual canvas component we replaced the usage of theme with resolved theme value from used theme. We then grabbed the mounted value from our use mounted hook and then we calculated the color mode by the value of mounted. So if we have successfully mounted, meaning we have hydrated, we fall back to resolved theme. Otherwise, we fall back to light theme. And if we don't have mounted at all, we go ahead and fall back to light as well.
And down here we simply swap the theme value and the fallback to the system to that color mode value. So the component looks like this in the end. Let me show you once again. We import useSyncExternalStore. We create a no-op function, empty subscribe.
We create a function useMounted which returns useSyncExternalStore, uses that no-op function, returns true and false respectively. And then instead of extracting theme from used theme, we extract resolved theme and then the color mode is now calculated by using that mounted value from used mounted hook. If we are mounted, we attempt to use the resolved theme which we cast as color mode here or we fall back to light and if we are not mounted we fall back to light mode as well. And then we simply replace the color mode here to use that calculated value instead of the previous one which we had which fall back to system. And if you take a look now and refresh, it works.
You can toggle, you can refresh and everything will persist. Now as you can see we have some white flash here. So if you want to get rid of that and if you're certain that your app will be in dark mode by default you can change this light to dark value and then there we go there is no flash anymore but then if you want to use your app in light mode, there will be a quite more noticeable flash in the other direction. So choose whichever value you want to use here. So depending on your preference, you can either leave or remove the background.
I personally think it looks cleaner if we get rid of the background component but you can choose for yourself. This way it looks empty. I like it that way. I feel like it's cleaner but if you prefer having those little dots feel free to leave the background component there. And now let's go ahead and stage, commit and push our changes so we have a checkpoint and so that we can close this chapter.