Full transcript
— Introduction
0:00It's 2026 and most of the senior
0:02engineers I know aren't really writing
0:05code anymore. They design the systems
0:07and let AI handle the implementation.
0:09And the gap between developers who can
0:11do that and developers who can't is
0:14dividing the industry right now. This
0:16app is what that looks like when you do
0:18it well. Real-time multiplayer SaaS, AI
0:21agents running in the background, full
0:23production code, and I didn't write a
0:26single line of it. An agent built the
0:28whole thing. And by the end of this
0:30video, you'll have built it, too. The
0:32same app, the same stack, the same way I
0:36did. And here's what makes this
0:37different from other AI build tutorials.
0:40I built this app using the exact
0:42methodology the app itself is designed
0:45to teach. Specs first, architecture
0:48defined, every feature planned before we
0:51start building. I've been doing this
0:52architecture work by hand before every
0:55serious project for years. And at some
0:58point, it hit me. I'm a developer. Why
1:01am I doing all of this manually when I
1:03could build the tool that does it with
1:05me? So, I did. This is Ghost AI, a
1:09real-time collaborative workspace where
1:11you describe a system in plain English
1:14and then AI agent maps it onto a shared
1:17canvas live. Your team edits the design
1:20together and when it's ready, the app
1:23generates a complete technical
1:24specification you can build from.
1:27Now, if you've tried building anything
1:29serious like this with AI, you already
1:31know the wall.
1:33The first few hours feel incredible and
1:35then a week later, the agent has
1:37forgotten every decision you've made,
1:39one new feature breaks three others, and
1:42the code base you were excited about
1:44just starts fighting you.
1:45That's not an AI problem. It's an
1:48architecture problem. And it's the same
1:50wall all of you are hitting in your
1:52careers where the senior dev advice
1:54online sounds great as long as you're
1:57already senior. The developers who win
2:00in this market aren't avoiding AI, and
2:03they're not handing everything over to
2:05it, either.
2:06They're learning to think like senior
2:08engineers, and then using AI to build at
2:11the speed of one. And that's what this
2:13video teaches. By the end, you'll know
2:16how to design a system before writing
2:19any code, how to use the six-file
2:21context system I write before every
2:24project, and how a senior engineer
2:27actually thinks when working with AI.
2:29Oh, and that six-file context system I
2:31just mentioned, I've packaged it into a
2:34free guide you can grab and use on any
2:36of your upcoming projects, not just this
2:38one. The link is down in the
2:39description. Next, the stack is Next.js,
2:43React 19, Liveblocks for real-time
2:46collaboration with agents, Trigger.dev
2:49for background work with AI agents,
2:51Clerk for auth and user management,
2:54Prisma and Postgres for data, Vercel
2:57Blob for storage, all production grade
3:00and deployed by the end of the video.
3:03So, one person with the right system can
3:06now build what used to take a team. And
3:09by the end of the next few hours, that
3:11person is you.
3:12So,
3:13let's build it.
— Crash Course
3:17Before we open up a single tool, I want
3:19to spend the next 10 minutes on the part
3:22of the video that I think is most
3:24important, and that decides whether what
3:27you're building ships or falls apart in
3:30the third week. There's going to be no
3:32syntax and no code in this section.
3:35Rather, what I'm teaching you is the way
3:37that I think before I write a single
3:40prompt, and the way I plan a build
3:43before I touch the agent, and the system
3:46that I use to keep AI from drifting
3:48halfway through a project.
3:50So that by the end, you'll know the
3:52conversations to have before you build
3:55the six-file context system that turns
3:57an AI agent from a guesser into a
4:00developer who already knows your
4:02codebase,
4:03and how to break any project into units
4:06the agent can ship cleanly one at a
4:08time. So, if you've been frustrated by
4:11AI breaking your code,
4:13then this is the video that fixes it.
4:14The intro touched on something I want to
4:17sit with for a minute.
4:19You heard me say senior engineers a lot.
4:22And if you're early in your career, that
4:24framing probably hit a nerve. So, let me
4:27be direct about what I mean.
4:29The job market for developers right now
4:32is harder than it was 2 years ago.
4:35Some entry-level work has been
4:37automated. Clients who used to hire
4:39freelancers for straightforward projects
4:42are now doing it themselves. And a lot
4:44of people who learn to prompt without
4:47learning how to think are now flooding
4:49the market. So, if you're worried about
4:51that, you're not wrong to be worried.
4:54But the developers getting squeezed
4:56aren't the ones who learned deeply.
4:59They're the ones who learned just enough
5:01to execute without understanding the
5:03system behind it. That was always a
5:06fragile place to be.
5:08And AI just made that fragility visible
5:11faster.
5:12So, the way through is to learn the kind
5:15of thinking that AI cannot replace. The
5:18architectural thinking, the
5:20systems-level judgment, and then use AI
5:23to build at the speed of someone twice
5:25your experience. The clearer your
5:28understanding of what you're building,
5:30the better the AI output. Which means
5:32that learning properly isn't a waste of
5:35time in the AI era. It's the best
5:37investment that makes everything else
5:39possible.
5:41So, when I say things like think like a
5:44senior engineer in this video, I don't
5:46mean you need 10 years of experience to
5:48apply this.
5:50These are learnable habits and you can
5:52start building them today on this
5:54project. And here's something most
5:56developers outside of Big Tech don't
5:58really know. At Google, Amazon, and
6:01Netflix, before any serious project
6:04starts, engineers spend weeks writing
6:07documents and sometimes months.
6:10Design docs, one-pagers, RFCs, the
6:14format changes by company, but the
6:16principle is the same.
6:18Figure out what you're building before
6:21you build it.
6:22And senior engineers at these companies
6:24sometimes go months without writing
6:26production code. They're designing
6:28systems, making architectural decisions,
6:31reviewing what other engineers ship. So,
6:34software engineering has never really
6:36been about typing the most lines per
6:38day.
6:39It's always been about thinking clearly
6:41about what should exist before you build
6:44it.
6:45And AI didn't invent that discipline. It
6:48just made it the most important skill in
6:49the room. And that's the whole
6:51foundation for the spec-driven agentic
6:54development course that I've been
6:55developing for a long time now. So, if
6:58you want to stay up-to-date on how
7:00that's going and receive an occasional
7:01email where I share my thoughts at the
7:03current state of the industry, I'll
7:05leave the link down below so you can
7:06subscribe for the newsletter. But, let
7:08me immediately make it concrete in this
7:10video.
7:11Here are two different prompts that two
7:14different developers might write to
7:16build the same feature.
7:18The first one says, "Build me a SaaS app
7:21with authentication and a real-time
7:23canvas."
7:24And the second one says,
7:25"I'm adding a LiveBlocks room provider
7:28to the workspace route.
7:29Auth is already handled by Clerk
7:31middleware. The canvas uses React Flow.
7:35Room tokens should be issued only after
7:38verifying project membership. Wire the
7:41provider into the existing workspace
7:43layout without touching the sidebar or
7:45navbar. Both developers want roughly the
7:49same thing.
7:50But the second prompt reveals a
7:52developer who knows their auth layer,
7:54understands their component boundaries,
7:57and knows what should and shouldn't be
7:58touched. They've thought about the
8:00system, so they're communicating
8:02decisions and not wishes.
8:04So, the AI isn't smarter when it reads
8:07the second prompt. The developer is.
8:10And that is the difference between vibe
8:12coding and what I'm going to teach you,
8:14which is spec-driven development, which
8:17is also the premise behind that
8:19spec-driven agentic development course
8:21that I'm actively developing. Vibe
8:23coding focuses on the outcome.
8:26You describe what you want, let the
8:28agent run, and react to whatever comes
8:31out.
8:32For a weekend prototype, that's fine.
8:35But for anything you're going to
8:36maintain, it just collapses.
8:39New features break the old ones, the
8:42code base starts contradicting itself,
8:44and you spend more time untangling AI
8:46mistakes than actually building.
8:49Spec-driven keeps the thinking with you
8:52and gives the agent a system to execute
8:55against.
8:56You stay the architect and the agent
8:59becomes the implementation engine. So,
9:01how do you actually build that system?
9:03Because that's the part that nobody
9:05really teaches. It starts before you
9:07open up any AI coding tool, with a
9:10conversation. When I get an idea for
9:12something I want to build, I open up a
9:15planning AI, ChatGPT, Claude, or Gemini,
9:18whichever's on hand, and I talk through
9:20it. What does this thing actually do?
9:22Who uses it? What are the core flows?
9:25Where are the complex patterns, and what
9:26could go wrong?
9:28I push back on the answers and let AI
9:31pressure test my thinking until the
9:33system becomes clear in my head. This
9:36conversation is the work. It's what
9:38senior engineers do before they build,
9:41except they usually do it in their head
9:43or on a whiteboard. But, doing it with
9:45AI externalizes it and makes it faster.
9:49When the system is clear, you write it
9:51down. And that's where the six-file
9:53context system comes from.
9:54Not from sitting at a blank page trying
9:56to write documentation, but from taking
9:59the output of the architectural
10:01conversation and organizing it into
10:03documents that travel with the project
10:06for its entire life. For Ghost AI, I
10:09organized everything into one folder
10:11called context.
10:13Six files, and what matters is that
10:15before your AI agent writes anything, it
10:18already knows what you're building, how
10:20it fits together, what the rules are,
10:22and where things stand right now. That's
10:24what you're going to learn about in this
10:26video. But, very quickly, here's what
10:28each one of them does at a glance.
10:31The project overview covers what the
10:34product is, who is it for, the core
10:36flows, and what's deliberately out of
10:39scope.
10:40The architecture file defines the tech
10:42stack, the boundaries between layers,
10:45and the invariants the codebase must
10:47never break. The code standards keeps
10:50the agent consistent across every unit
10:52of the build with shared TypeScript and
10:55Next.js conventions. The AI workflow
10:58rules keep the agent disciplined,
11:00defining how to scope work and what to
11:02do when something needs a decision.
11:05The UI context holds design tokens and
11:08component conventions, so the UI stays
11:11coherent across every page the agent
11:13ships.
11:14And the progress tracker, which is the
11:16one most developers skip and most need,
11:19holds the current phase, what's in
11:21progress, what's complete, and the
11:23architectural decisions made along the
11:25way. It's the only file that actually
11:27updates constantly throughout the build.
11:30And it's how the agent picks up exactly
11:32where you left off in a single prompt.
11:34These six files are what make the
11:37difference an AI agent that drifts and
11:40one that executes. And you don't have to
11:42build them from scratch for every
11:44project. I've put together a free blank
11:47template of all six files with
11:49step-by-step instructions on how I
11:51generate them using AI for whichever
11:54project I'm working on. And it's not
11:56specific to Ghost AI. It works for any
11:58application. Click the link down in the
12:00description to get it so you can apply
12:02this methodology to your own project
12:04starting today.
12:06We'll open up each one of these and I'll
12:08walk you through what's actually inside
12:10in the next lesson when we set up the
12:12project architecture for real. But yeah,
12:14once these six files exist, the build
12:17runs in units. We're going to break down
12:19the project into specific scoped pieces
12:22before we start. Not vague phases like
12:25build a dashboard, but concrete units
12:28small enough to build in a single focus
12:31session with clear conditions for what
12:33done looks like. That means that
12:35together for Ghost AI, we'll map out the
12:38entire build. Each lesson will have its
12:41own spec file. The spec defines the
12:44goal, the design decisions, the
12:46implementation details, dependencies,
12:48and a checklist of what has to be true
12:51before the unit is complete.
12:53You write the spec in the same way you
12:55write the context file through a
12:57conversation with the planning AI.
12:59Then you give the spec to your coding
13:01agent in one prompt. Make sure to read
13:04that spec. Mark that unit as in progress
13:07in the progress tracker. Implement it
13:09exactly as specified without going
13:12beyond scope. The agent will read your
13:14spec, read your context file, and build
13:17against a defined system instead of
13:20guessing.
13:21You review it against the checklist and
13:24if it passes, you close the unit, push
13:26the code, and move to the next spec. If
13:29something's off, you write a focused
13:32corrective prompt, exactly what's wrong,
13:35exactly what you expect, fix that
13:36specific thing, and move on.
13:39That's the entire workflow, the same one
13:41I used to build Ghost AI, and the same
13:44one you're about to use to build it with
13:47me. Oh, and last thing before we begin.
13:49These files take time to write. The
13:52conversation, the architectural
13:54decisions, the unit planning, all of it
13:57is real upfront work. And a lot of
13:59developers skip it because they want to
14:01feel productive immediately. I mean,
14:04I've done that as well. So, you open up
14:06the agent, type a prompt, and start
14:09watching code appear.
14:10But, that's the trap. The time you save
14:14by skipping this is the time you lose in
14:17week three debugging AI output that's
14:20drifted away from anything coherent. So,
14:23do this work once, and you'll do it
14:25faster every project after. And finally,
14:28it's time to build.
— Project Setup
14:32Okay, enough talk, and let's build.
14:36Open up your desktop and create a new
14:38folder. Call it something like Ghost AI,
14:42and then simply drag and drop it into
14:44your favorite code editor. For this
14:46video, I'll be using VS Code as we have
14:49the file explorer on the left side, we
14:51have the code in the middle, and then we
14:53have the chat window which you can open
14:55up by pressing command shift P, and then
14:58just search for chat. Or, I think it's
15:00just command shift I to open it as well.
15:03No matter which agent you're using, is
15:05it Copilot, Claude, Codex, or whatever,
15:07the interface is more or less the same.
15:10This is how it looks like on Codex, and
15:12this is just general chat.
15:15So, this video is completely agentic
15:17tool agnostic. The thinking and the
15:20specs are what matters. The tool is
15:23completely your call. I'll personally be
15:25using Claude code as that's what most
15:27people are using and what's we're using
15:29internally within JSM. If you want the
15:31budget option, you can go with Codex.
15:34There's the Go plan, which is super
15:35cheap, the plus plan, which is also
15:37cheap, and I think they're also offering
15:39a month for free. And if you're already
15:41paying for something like Cursor, Win
15:43serve, or any other AI agent, just use
15:46that. I'll make sure you don't spend a
15:48lot of tokens, no matter what you're
15:49using, and that you learn a ton. Okay,
15:52so let's get started.
15:54We'll start with a fresh Next.js
15:56project. I'll do this manually with no
15:58prompts and agents.
16:00It's just a single command and it takes
16:02a couple of seconds and that'll give us
16:04a clean and predictable foundation to
16:06build everything else in top of. So,
16:08simply open up your integrated terminal
16:11and run MPX create next app at latest
16:15dot, which is going to create it in the
16:16current repository. It'll ask you
16:18whether you want to install the Next.js
16:20installer, so you can just say Y, yes,
16:22and proceed.
16:23And I also want you to know that you
16:25don't necessarily have to follow along
16:27by using Next.js.
16:29Of course, the majority of the context
16:31for AI agents we'll be working with is
16:33going to be featured around Next.js, but
16:36if you want to build this in TanStack,
16:38Angular, Vue, or anything else, the
16:40concepts will still be as valuable.
16:43So, let's just say Y for now. It's going
16:45to ask you whether you would like to use
16:46the defaults, so just press enter,
16:49which is going to install React,
16:51TypeScript, ESLint, Tailwind CSS, and
16:54the app router. Let's give it a moment
16:56until it finishes. Once the installation
16:58finishes, we need to clear out the
17:00default boilerplate.
17:01That's going to be within app and then
17:04page. Next.js ships with a lot of
17:06placeholder content that, honestly, we
17:08don't really need. Now, you could go
17:11ahead and clean it up manually by
17:13opening up your globals.css
17:15and cleaning everything besides the
17:17Tailwind CSS directives, deleting some
17:20SVGs and other stuff within the public
17:22folder, and replacing the page.tsx with
17:25a minimal component.
17:27Or we can use this as our first
17:29interaction with the coding agent.
17:33So, go ahead and open up your agent of
17:35choice. I'll just press command shift I
17:38to open up my chat sessions. And what I
17:40love about VS Code is that it's not
17:43shying away from the agnostic approach
17:45to agents.
17:46You can use their agents right here,
17:48such as Copilot CLI,
17:51or if you head over to extensions and
17:54then install an extension like Claude
17:56Code,
17:58which has like 12 million downloads, or
18:01something like CodeX, which has 8
18:03million downloads,
18:05and I installed both, you can actually
18:07just navigate over to them by selecting
18:10additional views,
18:11and then choosing the extension you
18:13want. Of course, alternatively, you can
18:15also just run all of these agents within
18:17the CLI by typing Claude, and you're in.
18:21For the longest time, I've been using
18:23Claude CLI, but the Claude VS Code
18:26extension recently got so much better,
18:28and honestly, it's working the same way
18:30as the CLI does, but with a bit of a
18:32nicer UI and a graphical interface that
18:35allows us to use it in an easier way.
18:37So, that's what I'll be proceeding with.
18:39I'll expand it so we have more space to
18:41work with, and you can notice that
18:43there's a little microphone button right
18:44here. So, you can either tap it or hold
18:47the command D key to speak into it. With
18:50CodeX and other agents, you can either
18:52type it manually,
18:53or what you can do is use a tool like
18:57Whisper Flow, not sponsored by the way,
18:59which is what I use to write prompts
19:02when I speak with agents. Speaking is
19:04just much faster. If you install it, you
19:06can just press the command key
19:09and start speaking into it. Like, clean
19:12up this Next.js boilerplate. Strip
19:15globals.css down to just the Tailwind
19:17directives. Delete all SVGs in the
19:20public folder, but keep the favicon.
19:23Remove page.module.css.
19:26Replace page.tsx with a minimal
19:28component that just renders a centered
19:31div saying Ghost AI.
19:34And I can stop it right here, and you
19:36can immediately see the output. Cool
19:38stuff, right? Go ahead and write
19:40something like this, and let's see how
19:42well Agent handles it.
19:43Also, just so we don't have to give it
19:46permissions every single time whenever
19:47we're doing something, for now I will
19:49say edit manually. Oh, and of course, we
19:52have to talk about the actual models
19:54we'll be using. In this case, we're
19:56using the default model, which is Opus 4
19:587 with a million token context, which is
20:00amazing, but you're going to hit the
20:02limits very soon. Instead, you'll be
20:04able to follow along this entire build
20:06with using Sonnet 4 6. It's fast, it's
20:10inexpensive, and it sometimes get lost
20:13unless you have the context files, which
20:16I'll teach you how to build so you guide
20:17it in a bit of a better way, and make it
20:20work much more like Opus does. So,
20:23that's what I'm going to select. If
20:24you're working with Codex, you can also
20:26go ahead and choose any model you'd
20:27like, like 5 4, 5 3, or anything else.
20:31Okay, let's give it a shot. It's going
20:33to think for a couple of seconds, read
20:35all the necessary files, and apply all
20:38four changes.
20:39Globals, just the import. Page, minimal
20:42centered Ghost AI component. Deleted
20:44five SVGs, but kept the favicon. And
20:47this file that I tried to trick it with
20:48doesn't really exist, so there's nothing
20:50to delete.
20:51You can see all the changes in the diff
20:53right here, and you can also see which
20:55files have been modified on the left
20:56side.
20:58There we go, just simple Ghost AI, and
21:01just the import for Tailwind. Which
21:03means that these classes right here
21:04should work to center the div. Before we
21:06run it, let's actually head over to
21:08package.json.
21:10Make sure that the project name is set
21:12to Ghost AI, and let's run it by running
21:15npm run dev.
21:18It'll run it on localhost:3000. So, if
21:21you open it up, you should be able to
21:23see something that looks like this.
21:25This means that our project is
21:26initialized, it's cleaned up, and now we
21:29are ready to move to the part that
21:31actually determines just how well
21:33everything goes. Setting up our context
21:36files and planning the build before the
21:38agent writes a single feature. So, let's
21:42do that next.
— Preparing Context
21:45Not that long ago, I talked about how
21:47senior engineers spend weeks designing
21:50systems before anyone writes a line of
21:53code.
21:54They write design docs, they define
21:56boundaries, and they make decisions
21:59early on so that everything else becomes
22:01predictable.
22:02So, let me show you how we are going to
22:04approach that.
22:06We're going to create a new folder right
22:08here in the root of our application,
22:10which we're going to call context.
22:12Everything inside of it is what your
22:15coding agent will read before it does
22:17anything. This is how it knows your
22:19project, and this is how it stays
22:21consistent across every session, commit,
22:24or unit of the build.
22:26So, you can switch multiple agents and
22:28share the context file with another
22:30developer so he can continue working
22:32with his agent from there. That's the
22:34catch. We're going to have a couple of
22:36different files within the context
22:37folder. Now, I don't want you to do a
22:40lot of copy-pasting, so I'll provide you
22:42with the final zipped context folder so
22:44we can easily review everything
22:46together. So, delete the context folder
22:48you just created. In the video kit link
22:51down in the description, get the zipped
22:53version of the folder, unzip it, and
22:55then just drag and drop it in.
22:57And here, you'll see six, well, seven
23:00different files with this agents.md.
23:02Might seem scary at first, but don't
23:05worry as I walk you through every single
23:07one of these files and show you how you
23:09can create them for yourself in the
23:10future. Let's start with the project
23:13overview. And let me walk you through
23:16why it's structured the way it is
23:18because understanding this is more
23:20valuable than just copying the file.
23:23Currently, we're looking at the markdown
23:25version of the file, but in VS Code, I
23:28believe this is done by default, you can
23:30also open the preview to the side by
23:32pressing command K and then V or just
23:34pressing this icon at the top and then
23:36you can close the actual MD and just see
23:38the formatted version. I think it's a
23:40bit easier to read this way. I typically
23:42open up these project overview documents
23:45with a one paragraph summary and then a
23:48numbered list of goals. This gives the
23:51agent the big picture immediately. So,
23:54when a requirement gets ambiguous 3
23:56weeks into the build and it will, this
23:59is where you resolve it. The agent uses
24:02this constantly to understand intent
24:05when a spec isn't specific enough.
24:08Notice the goals are concrete and
24:10measurable, not build a good canvas.
24:13Instead, let authenticated users create
24:16and manage architecture projects or let
24:19AI generate an initial architecture from
24:21a natural language prompt. The agent
24:24knows exactly what success looks like.
24:27And yeah, this is useful for us while we
24:29are going through the process of
24:30building the app. Just so you know what
24:32we are building. Just so everybody in
24:34the team, in this case me teaching you
24:36and you following along and you building
24:38it with me, are all on the same page. Go
24:42stay eye is a real-time collaborative
24:44system design workspace. Users describe
24:47a system in plain English and then AI
24:49agent maps that system onto a shared
24:51canvas. Collaborators refine the
24:54architecture and the app generates a
24:56technical specification document from
24:58the resulting graph.
25:00Okay, great. We have the overview, we
25:03have the goals, and then we have the
25:05core user flow. So, let me zoom this in
25:08and let's go through this together
25:10because this sequence matters. The agent
25:13can sometimes lose track of how features
25:16connect to each other.
25:17By defining a full flow from sign in to
25:21the spec generation, we make the logical
25:23sequence explicit. So, the agent won't
25:26try to build the spec generation feature
25:28on the login page because it knows the
25:30user has to create a project and the
25:32design architecture first. In the
25:34features, we get specific about
25:37technologies. And I want to take a
25:39second on this because the tools I chose
25:41for Ghost AI weren't random. I took a
25:44lot of time to choose what actually
25:46makes sense. Starting with
25:47authentication. For user sign in, route
25:50protection, project creation, ownership,
25:53and collaborator access, we're using
25:55Clerk. Could you have gone and created
25:57the full auth from scratch? I mean,
25:59sure, but it would take you a couple of
26:01days up to a couple of weeks to do it
26:03properly, even with AI.
26:06And when you do it, it's never going to
26:07be as secure as something like Clerk.
26:10And nowadays, the speed of shipping
26:12matters more than anything. If you have
26:15a specific product you want to push, you
26:17want to get it in front of potential
26:18users as soon as possible. And that's
26:20why for many of these agentic builds,
26:22it's just a no-brainer to plug and play
26:25Clerk into it. Especially considering
26:27just how well Clerk works with your
26:30agents. You can either just copy the
26:32prompt or use Clerk skills with
26:36whichever agent you're using.
26:38That way, it'll immediately become a
26:40professional developer at using Clerk
26:42and it'll be able to implement it within
26:45any project. And there's also the MCP,
26:48which is a server that allows AI agents
26:51like Claude,
26:52or others
26:54to access Clerk as the case snippets and
26:56implementation patterns, and basically
26:58implement everything for you. Oh, and
27:00not to mention that Clerk CLI is also
27:03being worked on. You'll just be able to
27:06ask your agent to use the Clerk CLI to
27:08add auth to your app, allowing you to
27:10not even have to leave your terminal or
27:12copy and paste any API keys. Clerk right
27:14now really is the leader in agentic
27:17development. And with a completely free
27:19pricing of up to 50,000 monthly
27:23recurring users, it's a no-brainer for
27:25me to build all of my applications with
27:27it. So, while we're here, I'll leave the
27:30link down in the description. You can
27:31click it, and then sign up so that we
27:33can very soon more easily get started
27:35with building. Okay, on top of auth, the
27:38most important part of our application
27:40is the collaborative canvas.
27:42And in this case, I wanted to make it
27:45real-time. So, for any kind of canvas,
27:47it makes sense to use React Flow. And if
27:50you want to make any part of your
27:51application live with cursors, so you
27:54can see what other people are doing,
27:55presence indicators, and node or edge
27:58editing, I mean, it just makes sense to
28:00use Liveblocks. Liveblocks is the leader
28:03for anything multiplayer, but not only
28:05apps, agents as well. Let me show you
28:08what I mean.
28:09I mean, this is the app without
28:11Liveblocks, and then if you add it, you
28:13immediately get live avatars. You can
28:16get the AI chat to generate something,
28:19and you can also leave comments and
28:21track what people are doing within your
28:22app, which is super useful for the
28:24collaborative canvas we're building.
28:26But, what's even cooler is interacting
28:29directly with AI assistants to do
28:32something within our application. I
28:33mean, if you try to build all of this
28:35from scratch, it would definitely take
28:37some time. But, with their AI assistants
28:39feature, you can just watch an AI do it
28:42for you. Oh, and this whole React Flow
28:44thing you're seeing, that got released
28:46recently, which means that in this
28:48video, we're building the latest stuff
28:49out there. Oh, and like Clerk and the
28:52many other amazing dev tools that are
28:54adapting to agentic development,
28:56Liveblocks also offers the agent skills,
28:59which you just have to install, and your
29:01agent will immediately know what it has
29:02to do to make the Liveblocks integration
29:05work. I'll teach you about all of that
29:07as we continue with the build. After the
29:09canvas, we have the starter system
29:11design, which I decided to have because
29:13it's very difficult for people to start
29:16with a blank canvas. So, I want to have
29:18some kind of a curated library of
29:20pre-built system design templates, where
29:23users can import a starter template into
29:25the canvas at any point during editing.
29:27But also, if the template doesn't do
29:29what you want it to do, we're going to
29:31allow our users to generate a system
29:33design from a prompt with AI. Then, that
29:37output will be structured as canvas
29:39nodes, and then we'll write it onto the
29:41canvas. And finally, we'll take a look
29:43at everything that is on the canvas, and
29:45we'll convert it into a technical
29:47specification in a markdown format. And
29:49then users will be able to view and
29:51download the generated specs. Oh, and an
29:54additional thing that I want to teach
29:55you is while we're generating stuff with
29:58AI, that'll obviously take time. As the
30:00user will likely provide a long idea of
30:03how they want to architect their app,
30:05and then the AI output is going to take
30:07potentially a minute or two. And running
30:10a more than 60-second AI generation call
30:13inside a Next.js API route will just
30:16time out in production. So, that's why
30:18I'll also teach you how to use
30:19Trigger.dev. It'll allow us to run
30:21background tasks that can run as long as
30:24they need to, with the retry logic and
30:26status tracking built-in. The way in
30:28which we'll combine Liveblocks and
30:30Trigger.dev is pretty amazing. So, we'll
30:33have to be very clear in letting our AI
30:35agent understand that it doesn't have to
30:37invent a custom web socket
30:39implementation when Liveblocks is
30:41already in stack, Or it won't try to run
30:44AI generation inside a request handler
30:47when trigger dev is defined as the layer
30:49for that work. Naming the tools we're
30:51going to use for the project in advance
30:53is crucial. Oh, and trigger allows you
30:55to do so much more. Recently, they've
30:57been diving deeper into allowing you to
30:59build and deploy AI agents, which is
31:01something we can explore in an
31:02additional video. But in this one, we'll
31:05also focus on many of its features,
31:07specifically running background tasks
31:09and reporting back to the front end. So
31:11while a long task is happening, we can
31:13keep the user updated on what's
31:15happening behind the scenes and allow
31:17the user to continue doing whatever
31:19they're doing on the application because
31:20we're no longer blocking the front end
31:22side while handling a specific task. So
31:24I'll leave a special link pointing to
31:26trigger as well as live blocks down in
31:29the description so you can create your
31:31accounts and then we'll be able to
31:32immediately dive into the development.
31:34Then we have the scope part which
31:36contains the in scope and out of scope
31:38features. And it's maybe the most
31:41important section for keeping your build
31:43focused. The out of scope section is
31:45doing serious work right here.
31:48Billing and subscription systems,
31:50enterprise permissions, version
31:51specification history and so on. This is
31:54telling the agent, don't even think
31:56about them. But we can add them later on
31:59after we build the base of our
32:00application. This is great because it
32:02keeps every session focused on what we
32:04are actually building.
32:06And finally, there's success criteria.
32:09Here, we define what actually matters.
32:12These are the benchmarks that your agent
32:14and you can verify against after each
32:17major feature lands. Not, does it look
32:19right? But, can a signed in user create
32:22and open up a project? Can multiple
32:24users collaborate? Can the graph be
32:26converted into a persisted markdown
32:28specification?
32:30Very simple, yet concrete.
32:33So that's our project overview. Written
32:35in a way that makes sense to agents, but
32:38also other team members working on the
32:40project. Next, let's take a look at the
32:42AI workflow rules. This file is
32:45different from others in a way that it's
32:48not about what we're building. It's
32:50about how the agent behaves when
32:52building it. And the most important rule
32:54right here is to work on one feature
32:57unit or subsystem at a time. We don't
33:00want to combine unrelated system
33:01boundaries in a single implementation
33:03step. And that single rule prevents most
33:06failures that the agents cause.
33:08Basically, all of these rules right here
33:10tell the agent, "Stay in your lane.
33:13Focus on one part of what you're doing,
33:15and then move on to the next one." After
33:17that, we have the code standards. So,
33:20you can open up that, and let's quickly
33:23take a look. This file is what keeps the
33:25code base consistent from our first all
33:28the way to the last chapter. Without it,
33:31the agent would drift away. Like,
33:33specific patterns that it uses for API
33:35routes when implementing feature five
33:37might look different from feature 16. Or
33:40maybe it's going to change some things
33:41regarding the TypeScript types, or how
33:43it uses Next.js, how it styles things.
33:46But here, we add that consistency. Like,
33:49we tell it, "Make sure to have strict
33:52mode enabled, and avoid using any." Or
33:54we're telling it to add use client only
33:57when the component needs browser
33:59interactivity. Or for styling, we're
34:01telling it, "No raw Tailwind color
34:03classes. Reference the tokens through
34:06the Tailwind utility names." I think you
34:08get the point. We want to stay
34:09consistent. Then, we have the UI
34:12context, which, as you can guess, dives
34:14a bit deeper into theming. Like, when I
34:17was initially coming up with a design
34:18for this application, I wanted to have
34:20something that is simple, yet functional
34:23and modern. And I spoke with AI a bit to
34:26generate this theme. Dark mode, no light
34:29mode, Uh, all colors are already
34:31defined, and we're telling the AI to use
34:34these colors.
34:35And again, if you're wondering how
34:37exactly I created all six of these
34:40documents by speaking with different AI
34:42agents, that's something that I'll cover
34:45in much more detail within the
34:47spec-driven agentic development course.
34:49So, you can join the waitlist in the
34:51description. But, I think you get the
34:52idea that I didn't just sit down and
34:54handpick every color in the file. I
34:56described the aesthetic that I wanted to
34:58AI, dark, technical, precise, something
35:01that feels like an engineering tool, and
35:04then I went back and forth on the
35:06palette, um, the token names, and this
35:08is what it came up with. Also, some
35:10font, border radiuses, and so on. That's
35:13exactly how you should approach your own
35:15UI. You don't need to be a designer, but
35:18you need to know the feel you're going
35:20for, and then AI can help you get there.
35:22Oh, and while the project overview gave
35:25some information about the project, the
35:28architecture context will give you the
35:30blueprint on how to build it. Here, I
35:32specified the tech stack,
35:35like every technology that we want to
35:36use, alongside the role that it has
35:38within the application. For auth, we're
35:40using Clerk for user identity and route
35:42protection. For databases, it's going to
35:44be Prisma and Postgres. For canvas, it's
35:47going to be Liveblocks and React Flow
35:50for real-time collaborative canvas.
35:52For background tasks, it's going to be
35:54Trigger.dev, and for storage, we're
35:55going to use Vercel Blobs. We also
35:57defined some system boundaries, like
35:59where we're going to put the request
36:01handlers. Trigger is what we're going to
36:03use for the long-running background
36:05jobs, and some other folders that we're
36:07going to use.
36:08Then, we defined the storage model,
36:10where and how we're going to save
36:12something. And specifically here, I
36:14decided to use a hybrid storage model,
36:16which is another senior-level decision.
36:18We aren't going to be stuffing massive
36:21JSON blobs or three-page markdown files
36:23into our database. Instead, we're going
36:25to use Postgres only for metadata and
36:28Vercel blob for the actual files. And
36:31this keeps our database lean. And this
36:33is important. We also need to tell it
36:35how different tools are working
36:37together.
36:38Since we're using Clerk with Liveblocks,
36:40we need to set a strict rule that
36:42project memberships must be verified
36:45before a Liveblocks token is ever
36:47issued. This ensures that our Ghost AI
36:50isn't just collaborative, but secure.
36:52And finally, invariants are the rules
36:55that the system must never violate. For
36:57example, request handlers do not run
37:00long-lived AI works. That belongs in
37:02background tasks through trigger.dev.
37:05Metadata and large artifacts are stored
37:07in separate layers. Auth and ownership
37:10are enforced at every mutation boundary.
37:12Client components only used when needed.
37:15And the canvas schema must remain
37:17consistent. Of course, we'll dive much
37:19deeper into this when we actually dive
37:21into building the application. But I
37:23wanted you to have a good idea of what
37:24it is that we're building. And finally,
37:26there's the progress tracker. This is
37:29the only file in the context folder that
37:31will look completely different by the
37:33end of this video.
37:35Right now, it is completely empty.
37:37Intentionally, because it reflects the
37:40actual state of the project. And right
37:42now, nothing has been built yet. So, as
37:45we complete each lesson, we're going to
37:47update this file. The current phase,
37:49what's in progress, what's complete, and
37:51what's coming next. And remember what I
37:53said in the beginning about agents
37:55having no memory between sessions.
37:57This file is the solution to that
37:59problem.
38:00At the start of every new session,
38:02whether that's tomorrow, next week, or 6
38:04months from now, one prompt is all it
38:06takes to restore full context. Our agent
38:10will read the progress tracker,
38:11understand exactly where the project
38:13stands, and pick up exactly where you
38:15left off. So, you don't have to
38:17re-explain yourself. Even though it's
38:19going to be a small file, it does more
38:21work than any other file in the project.
38:23Oh, and finally, there's the agents.md
38:26file. Within it, we're going to wire
38:29everything together. When you installed
38:30Next.js,
38:32that file was already created for you
38:34automatically at the root. This is the
38:36entry point file, and every major coding
38:38agent has one. They just name it
38:40differently.
38:41Claude calls it claude.md, Cursor, Win
38:44Surf, and others use different names,
38:46but the idea is always the same. It's
38:48the first thing that the agent reads at
38:50the start of every session.
38:52So, Next.js has already added the first
38:54section for you, and it tells the agent
38:56that this is a recent version, and its
38:58training data might be outdated, so read
39:00the install documentation before writing
39:02any code. Good default, and we can leave
39:04it, but now we can add our own part
39:06below it.
39:08So, copy your agents.md, delete it from
39:10the context, and instead move it right
39:13here. It's not long, so let's see what
39:16do we have within it. We're basically
39:17instructing the agent to read all six
39:20context files in order before
39:22implementing anything, and then to
39:25update the progress tracker after each
39:27change. And you might think, "Isn't this
39:29going to waste context and tokens?"
39:32Well, I mean, sure, it has to read
39:33through the files, but that's not even
39:351/10 of how many tokens you're going to
39:37save because there's going to be less
39:39back and forth, and less mistakes and
39:41bug fixing and correcting while you're
39:44implementing the features in the first
39:45place. So, that's the system. And now
39:48that you understand it, let's start
39:50building with it.
— UI Primitive Components
39:53Now that the context files are ready,
39:55before we build a single feature,
39:57there's one more thing we need to set
39:59up, and skipping this one is the most
40:01common mistake I see in AI-assisted
40:04projects.
40:05And that is the globals.css
40:08file.
40:09This one right here. We've defined all
40:11of our color tokens within our UI
40:13context right here. But, if we don't
40:16translate those tokens into actual CSS
40:18custom properties within the globals.css
40:21file, well, it'll just write the inline
40:23colors. So, instead of maybe saying
40:25something like text faint, it'll just
40:28write #505060.
40:31And the moment you want to change it,
40:32you have to change it across all places.
40:35So, let's set it up correctly. And this
40:36is also the first real demonstration of
40:39the spec-driven workflow in action. So,
40:42let's do it properly. Create a new
40:44folder inside side of the context folder
40:47and call it feature specs, like this.
40:51And then inside of it, create the first
40:53spec file.
40:5501
40:56design system.
40:59md. And then within it, we want to tell
41:01it something like read the agents file
41:04before starting.
41:06Then, we need to tell it what we're
41:07doing here, like adding the design
41:09system and the UI components.
41:12Then, we're telling it to install and
41:15configure shadcn-ui.
41:17And then we want it to add the following
41:20components.
41:21I figured we're going to use these
41:23across the rest of the application.
41:24Whenever we wanted to modify these files
41:27after the installation, so we can
41:29specify that.
41:30We can also ask it to install
41:32lucid-react for icons
41:35and create a lib-utils.ts
41:38with a reusable class names helper for
41:40merging tailwind class names. Although,
41:42I think it would do this by default,
41:44it's good to mention it.
41:46Finally, we want to ensure that all of
41:49the components match the existing dark
41:51theme within globals.css.
41:54And then, we want to apply some checks
41:56when it is done.
41:58For example, we want to make sure that
42:00all components import without errors,
42:02that the cn works properly, and that no
42:05default light styling appears. This is
42:08what we call a feature specification or
42:10a feature spec.
42:12Every unit we build from here on has
42:14one.
42:15The spec tells the agent exactly what to
42:18do, exactly what not to touch, and how
42:21to verify when it's done. No guessing.
42:24So, let's open up our agent by pressing
42:26command shift I. As I said, you can use
42:29anyone. I'll use Claude code.
42:31I'll start a new chat, and you can even
42:34open up that file, which will
42:35automatically put it within its context.
42:37And then, you can tell it something like
42:39read
42:41add 01 design system update
42:45the progress tracker MD file to mark
42:49this as in progress.
42:51And then, implement exactly as
42:55specified. This is the same template
42:57we'll use often, but the only thing
42:58that's going to change is going to be
43:00the spec of the feature we're
43:01developing. So, let's go ahead and run
43:04it, and notice what will happen before
43:06the agent writes a single line of code.
43:08It'll read the specification, and then
43:11update the progress tracker. You can see
43:14how it's going through all the files we
43:16prepared for it, and only once it has
43:18enough context, it'll write and save the
43:20plan, and then execute it. And only when
43:23it has a full plan, it will update the
43:25progress tracker.md, and then start
43:27doing it. So, we can already take a look
43:30at the progress tracker,
43:32and I'll open it up so we can see what
43:34it is doing. And you can see that the
43:35current phase is feature 01 system
43:38design, the current goal to install and
43:40configure shadcn with dark theme, and
43:43that is currently in progress with the
43:45feature 02 to be done. And it's also
43:48adding the architectural decisions
43:50needed for every step, as well as the
43:52session notes. It's going to ask us
43:54whether we want to run this command to
43:55install shadcn, so I'll say yes, go
43:58ahead. And after it installs everything,
44:00it'll verify it with TypeScript. It
44:03looks like it completed with zero
44:04errors, and finally, it needs to update
44:06the progress tracker to complete it.
44:09So, we can already open it up right here
44:12under progress tracker, and it should
44:14move it from current phase over to
44:16completed.
44:18So, now at any point in our application,
44:20if we come back 2 months later, it'll
44:23know that it's using shadcn, Tailwind V4
44:26with the following components, only
44:27using dark theme,
44:29and all the additional helpers,
44:31as well as the architectural decisions.
44:33So, once it finishes, the agent will
44:35have configured shadcn, installed the
44:38components, and verified that it all
44:39works. So, what do you say that we test
44:41it out?
44:42Back on localhost 3000, the first good
44:45sign should be that we're now officially
44:47in dark mode. And if you head over to
44:49the home page, that's going to be within
44:51app page, we can try to use a shadcn
44:54button component coming from components
44:57UI button, and we can try to make it say
44:59something like click me. You can see
45:01it's coming from there,
45:03and it follows our UI theme. That's it.
45:06Our first feature, being the theming and
45:09shadcn setup, is done. And that's the
45:11spectrum workflow I was telling you
45:13about. We define the specifications, we
45:16run the prompt, we verify the output,
45:19and then we move on. But just before we
45:21move on to our second feature, I want to
45:24add just one little extra step to this
45:26whole workflow that's going to make our
45:28codebase even more scalable,
45:30predictable, and less error-prone. And
45:33that is whenever we implement a specific
45:35feature, let's also review it with Code
45:37Rabbit. There's been a report that says
45:40that AI code creates 1.7 times more
45:42problems, which means that even though
45:44we're faster, we're producing more
45:46issues per PR. Also, the code becomes
45:50less readable, naturally, and the error
45:52handling isn't being done properly.
45:54Security also suffers. So, let's add
45:57that one additional line of defense.
45:59Let's review every single feature that
46:01we add to our project in the same way
46:03that biggest teams, such as the
46:04developers over at Nvidia, are doing it.
46:07I'll leave the link down in the
46:08description, so you can create your free
46:10account and follow along.
46:12You can log in with GitHub, and once
46:14you're in, you'll be able to see that I
46:15already gave it access to many of my
46:17repos. But first, we got to push our
46:19project over to GitHub. So, head over to
46:22github.com/new
46:24and create a new repo. You can call it
46:26Ghost AI.
46:30And just create it. Next, you can copy
46:32these commands one by one, or you can
46:35just copy all of them together,
46:37open up your agent, and paste it. Then,
46:39press enter. It's going to ask us
46:41whether we're going to allow it to stage
46:43all the changes. So, I'll say, "Yeah, go
46:45ahead." And it'll also allow all future
46:47commits, as that's going to help us
46:49speed up the workflow, as well as adding
46:51a remote, and finally push. You can see
46:54that the changes have been pushed
46:55successfully. So, if you come back and
46:57reload,
46:59you'll be able to see the current list
47:00of changes over on the repo. It's always
47:03good to make your project descriptive,
47:05so you can remove the releases,
47:07deployments, and packages, and add a
47:09short description,
47:10such as Ghost AI
47:13is an interactive systems
47:16architecture
47:19builder.
47:20We can route to the deployed website.
47:21For now, I'll just route it to
47:23jsmastery.com, and here we can put the
47:25different topics, such as Next.js,
47:28React, My Blocks, Clerk, Trigger.dev,
47:33and even Code Rabbit, which we'll be
47:34using for reviewing your code. And I
47:37like how AI always adds nice commit
47:39messages and not random gibberish that
47:41I'm used to. But yeah, now back within
47:44Code Rabbit, you can head over to add
47:46repositories,
47:47sign in, and give it access to all your
47:49repos. And then, when you're back, you
47:52can just find your project right here.
47:58Which means that it's automatically
47:59being tracked. So, as soon as we start
48:01adding the real features to the app,
48:03we'll be able to open up a PR for every
48:05new feature as we're developing within a
48:07large organization and then get it
48:09reviewed and only when Code Rabbit gives
48:11it a green light,
48:12merge it over to main.
— Layout Setup
48:16Now that our colors are in and the
48:18initial Shards and components are in as
48:20well, we are ready to start building the
48:22actual application. And the first thing
48:24that we need is the editor, including
48:27the top navbar and the left sidebar
48:29because they're the foundation of every
48:30feature we're going to build from here
48:32on. We're not yet touching off and we're
48:35not building this project creation. We
48:37just need to establish the layout so
48:39when these features come, they have
48:41somewhere to go. So, inside of feature
48:43specs folder, create a new file called
48:4602 editor.md.
48:49And within it, we're going to follow a
48:50similar structure we followed before. We
48:53want to start with explaining what we
48:55need,
48:56such as the base chrome components that
48:58frame every editor screen, the top
49:00navbar and the left sidebar shell. These
49:04will be reused and extended in every
49:06chapter that follows. Then, we need to
49:08focus on what are we actually creating?
49:11That's going to be the editor navbar.
49:14So, we wanted to create a new component
49:16within the editor editor navbar file
49:19and then we want to specify some
49:20requirements that follow. We want this
49:23navbar to be of a fixed height at the
49:25top. We want it to have both left,
49:28center, and right sections, which you
49:30can see right here. On the left, we open
49:32up the sidebar. In the middle, we have
49:34the name and on the right side, we have
49:36additional actions. That's exactly what
49:38we explained right here.
49:41And of course, it's going to be of a
49:42dark background with a subtle bottom
49:44border. Next, we want to explain what we
49:47want to do with the project sidebar. So,
49:49right below, we can say create a project
49:52sidebar component.
49:54It should float above the editor canvas.
49:57Opening it should not push the page
49:59content. So, you can see it remains
50:01where it is.
50:03And slides in from the left. It has the
50:05header that says projects and title and
50:08a close button right here. It can use
50:10the Shazian tabs component. And both
50:13tabs show empty placeholder state, full
50:16width new project button at the bottom
50:18with the plus icon. So, something like
50:20this. Oh, and finally, when we click
50:23create new project, we need this kind of
50:25a dialogue. A new pop-up that shows.
50:29So, for that, we can say something along
50:31the lines of
50:33dialogue pattern. Use the existing color
50:36tokens from globals for dialogue
50:37styling. It supports title, description,
50:40and footer action, but don't build any
50:42dialogues yet.
50:44We just want to create the component for
50:46it. Finally, to check whether it is
50:48done, we can check whether new
50:50components compile without TypeScript
50:52errors, no lint errors, and diagram
50:54pattern is ready for future use. So,
50:57hopefully, you were typing this out with
50:59me, or maybe you paused the screen and
51:01typed it with your own words. But, this
51:03course isn't at all about typing. It is
51:06about understanding what we're doing
51:08right here. So, if you don't really feel
51:10like typing, nor you should, in the
51:12video kit link down in the description,
51:14I'll provide you with all the prompts
51:17that we're going to use throughout the
51:18rest of this course. So, if at any point
51:20I'm going too fast when explaining them,
51:23and you just want to have it on your end
51:25and listen along, you can totally do
51:27that. But, yeah, let me quickly walk you
51:29through the structure because this is
51:31the template that every spec file in
51:33this project will follow. We start with
51:35a goal. One or two sentences, what does
51:38this unit produce when it is done? Then,
51:41we go over some specific design
51:42decisions. Is it visual or structural or
51:46something specific to the component like
51:48layout behavior, responsiveness, and
51:51this is where we can refer to that UI
51:53context file so that the agent isn't
51:55guessing the colors.
51:56Then we go over into implementation, and
51:59in this case, I decided to separate it
52:01into sections. So, we have the editor
52:03navbar, the project sidebar, and the
52:06dialog pattern, and finally the
52:08verification checklist. So, let's open
52:10up our agent. Whenever you're building a
52:12new feature, always open up a new chat
52:15and don't use one of the older chats.
52:17That's because we don't want some stale
52:19context lingering around. Only remain
52:21within the same chat when what you're
52:23about to do next is related to what
52:25you've done before, such as when you
52:27want to fix specific issues. You can see
52:29that it already has access to the editor
52:31file.
52:33So, I'll say, "Read this file and update
52:37the tasks on the progress
52:40tracker and then
52:43implement it
52:45exactly as specified."
52:48And press enter. And in about a minute,
52:51it's all done.
52:52It developed the editor navbar, the
52:54sidebar, and also the dialog pattern.
52:58TypeScript and ESLint both pass clean.
53:00Progress tracker also got updated. So,
53:03let's check the progress tracker first.
53:05It'll be right here within the progress
53:07tracker.
53:08So, if you check feature two completed,
53:12editor navbar and project sidebar both
53:15implemented. You can see them right here
53:17under editor, editor navbar, and project
53:20sidebar.
53:21We just created them so far, but they're
53:23not yet being used within the layout.
53:25And even though this wasn't part of the
53:27checks,
53:28I want to actually be able to see them.
53:30I want to tell it to use the navbar and
53:33the sidebar right now within the
53:34project. So, I'll open up the chat in
53:37the same context window and tell it to
53:39use these two components within a
53:41layout. And within a minute it put it to
53:44use. It even created a placeholder page
53:46so we can navigate over to the editor
53:49and check it out. So, head over to
53:50localhost:3000/editor,
53:53and you can see a canvas coming soon,
53:55but there is a top bar and a left
53:58sidebar, which opens up the projects.
54:01So, you can see that what we requested
54:03indeed got implemented. Since we're not
54:05yet at the point where we need that
54:06because we don't even yet have the
54:08editor, I actually want to show you how
54:10you can undo the changes, at least right
54:12here in Claude code. The only thing you
54:14have to do is press this back arrow on
54:16the message that you used to create
54:18these components, and then say rewind
54:20code to here. Which is going to bring us
54:22back and only give us what the
54:24specification wanted, and that is the
54:27components that we can then use and that
54:29compile, but we're not using them quite
54:32yet. Perfect. So, now that we have the
54:33navbar and a togglable sidebar, let's
54:36quickly check them out. The editor
54:38navbar is pretty straightforward, and
54:40the sidebar accepts some props such as
54:42is open and on close, and uses the
54:44Shadsy and tabs to modify what's being
54:47shown. But, when you're writing code
54:48yourself, you have a natural
54:50understanding of every decision you
54:52made. Heck, you wrote it.
54:54You know why a function is structured in
54:57a specific way or why you used specific
54:59props.
55:01But, AI-generated code doesn't come with
55:04that context.
55:05The agent made decisions that that were
55:08reasonable most of the time, but you
55:11didn't make them. And that means that
55:13reviewing AI output isn't optional. It's
55:16the step that keeps you in control of
55:17your own codebase. So, that's the
55:19perfect use case to test out the Code
55:21Rabbit edition that we added in the last
55:23lesson. I'll open up the chat and tell
55:25it to push all the current changes to a
55:29new branch called development.
55:31Typically, in real production databases,
55:33you often have multiple branches such as
55:36dev, staging, and only then main.
55:40So, for now, we want to push this over
55:41to the development branch, get it
55:43reviewed, and only if it's good, merge
55:45it over to main. So, by giving it this
55:47quick message, it'll run a couple of Git
55:50commands,
55:51figure out that we're currently on main,
55:53that we need to switch over and push to
55:55that new branch. And that's a little pro
55:57tip. Uh-huh, I mean, sure you could run
56:00these commands through the terminal, but
56:02I find it super easy to just stay in
56:04flow and tell the agent to push the code
56:07for me, which you can see it just did.
56:09So, if you head back over to your repo,
56:11you'll see that the development branch
56:13had recent pushes 3 seconds ago.
56:15So, let's go ahead and compare them and
56:17open up the pull request. We have 333
56:20new lines of code across five different
56:23files. So, Code Rabbit immediately
56:25hooked itself onto the PR, and we'll see
56:28whether it'll be able to pull out some
56:29bugs out of the hat.
56:31Let's give it a minute, and then I'll be
56:33right back. And we got back the
56:35walk-through, where it says exactly what
56:38we introduced in this project, such as
56:40two new React components for an editor
56:42UI Chrome, an editor navbar, and a
56:45project sidebar. These are super simple,
56:47as this was a simple review. Later on,
56:50these are going to get much more
56:51detailed, but yeah, let's check whether
56:54we have some potential issues even on a
56:56simple PR, such as this one. There's one
56:58major issue that says, "Hide the
57:01off-screen sidebar from focus order and
57:03assistive tech when closed." So,
57:05specifically, this is an accessibility
57:07fix, which is definitely a good
57:09implementation.
57:10Since we haven't yet utilized this
57:12component in our app, I'll leave this so
57:13we can add it later. There's also a
57:15minor issue, where the spec lists only
57:18the is open for the sidebar, but the
57:21implementation also requires on close.
57:23So, actually, it's suggesting to change
57:25the specification. This is interesting,
57:27because sometimes you're going to miss
57:29some stuff from the spec, and it's okay
57:31if AI tries to fix it or add some stuff,
57:35but it's equally as important for Code
57:37Rabbit to flag it. Because the whole
57:39reason why we're writing specs in the
57:41first place is so we can have predictive
57:43output. So basically, you can just copy
57:45this part right here, go back to your
57:47code base where we're saying accepts
57:50and then it's going to be is open prop.
57:52But we're going to say accepts both is
57:55open and on close props. Little change,
57:59I know, but now our code base is
58:01consistent with the spec. And that's it
58:03for this simple PR. As we continue
58:05developing more components, the reviews
58:07are going to get significantly more
58:09detailed. So for now, I'm going to go
58:11ahead and merge it, which means that we
58:13are ready to continue developing the
58:15next component.
— Authentication
58:18For this type of application, we don't
58:21really need a traditional homepage.
58:23Most tools like this drop you straight
58:26into the editor.
58:27You sign in from a simple sign in page
58:30and you manage everything from the
58:32canvas. And that's exactly what we'll do
58:34within our app.
58:36But we need auth, sign in, sign up, and
58:39other similar pages where you explain
58:41how your application works
58:43and then allow users to sign back in. So
58:46to get that set up, click the Clerk link
58:48down in the description and sign in. You
58:50can sign in with Google or GitHub. And
58:53once you're in, you can head over to
58:54applications and create a new app on the
58:57dashboard. You can give it a name such
58:59as Ghost AI and choose the sign in
59:02options
59:03such as email, Google, and since this is
59:06a development website, we can also do
59:08GitHub. Then click create application.
59:11We're building on Next.js, so what you
59:13can do is just install @clerk/nextjs
59:17by copying this command and paste it
59:20straight into the terminal.
59:22And while that is being installed, you
59:24can also set up your Clerk API keys by
59:27copying them from here
59:29and creating a new file called
59:31.env.local
59:34and then paste them right here.
59:35Now, before we hand anything over to the
59:38agent, there are two things you need to
59:40know about Clerk and Next.js 16
59:42specifically, because if you skip this,
59:45the agent will get it wrong. And it says
59:47it right here. If you're using Next.js
59:4915 or lower, name your file
59:52middleware.ts instead of proxy.ts.
59:56The code itself remains the same, only
59:58the file name changes. But, because most
1:00:00agents were trained on Next.js 14 and 15
1:00:03code bases, it'll almost certainly
1:00:06create a middleware.ts file by default.
1:00:09So, we'll have to specify proxy.ts
1:00:11explicitly in our spec so it doesn't
1:00:13have to guess. Oh, and another important
1:00:15thing is that just by adding middleware,
1:00:18it doesn't automatically protect all
1:00:20routes. As you can see right here, by
1:00:22default, it leaves all the routes
1:00:24public. And this catches a lot of
1:00:26developers. You have to explicitly
1:00:28define which routes are protected and
1:00:31which are public, which means that we
1:00:32have to configure everything
1:00:34intentionally. And this is exactly why
1:00:36reading the updated documentation before
1:00:38building with AI matters. The agent
1:00:41knows Clerk, not necessarily the version
1:00:43of Clerk you just installed or the
1:00:45version of Next.js you're running on.
1:00:46The spec bridges that gap. Oh, but we
1:00:49can also use agent skills.
1:00:52As I told you at the start, most major
1:00:54frameworks and libraries now publish
1:00:56official skill packages specifically for
1:00:58this problem. It gives your agent
1:01:00up-to-date knowledge, current APIs, and
1:01:03patterns, and the best practices of the
1:01:05library you're using. So, if you search
1:01:07on Google or within the docs, Clerk
1:01:09agent skills, you'll be redirected to
1:01:11this page. Then, simply copy the
1:01:14installation command,
1:01:15head back over to your code base and
1:01:17paste it in your terminal. npx skills
1:01:20add clerk skills. Press enter.
1:01:24You might need to install the skills
1:01:25package by saying Y and then enter. And
1:01:28then, by pressing the arrow up and down
1:01:30keys and the space key, you can select
1:01:33specific packages, such as core clerk.
1:01:36You can either select some additional
1:01:38clerk features or some additional clerk
1:01:40frameworks. Like in this case, I'm going
1:01:42to go with clerk next js patterns. And
1:01:46press enter.
1:01:48Then, you can select additional agents
1:01:50that you want to add it to. By default,
1:01:52it's going to work on codex, cursor, and
1:01:54anti-gravity. But, if you want to add
1:01:56Claude code, you have to select it here
1:01:58and press enter.
1:02:00And we can install it in project scope
1:02:02via symlink. That's the recommended way.
1:02:05So, just proceed with installation.
1:02:07Perfect. Our skill is installed and
1:02:09we'll be able to invoke it later on once
1:02:12we focus on implementing the auth
1:02:13functionality.
1:02:15So, now we are ready to write the spec.
1:02:19Open up your context feature specs and
1:02:21create a new file called 03 auth.md.
1:02:25And once again, the full feature specs
1:02:28files are linked in the description in
1:02:30case you want to just copy them and then
1:02:31follow along. Or, you can slowly type it
1:02:34out with me.
1:02:35Let's start by telling it that clerk is
1:02:38already installed and connected. So, we
1:02:40just need to wire it into the next js
1:02:42app, provider, auth pages, redirects,
1:02:46route protections, and the user menu.
1:02:49When it comes to the design, we just
1:02:52want to use the clerk's dark theme from
1:02:54the @clerk/ui themes as the base, and
1:02:57then we want to override the clerk
1:03:00appearance variables using the app's
1:03:02existing CSS variables.
1:03:05With no hardcoded colors. For the sign
1:03:08up and sign in pages, this is what we
1:03:11want to develop. On large screens, we
1:03:14just want to have a simple two-panel
1:03:16layout. On the left side, a logo, a
1:03:19tagline, and text-only features. On the
1:03:22right side, a centered Clerk form, and
1:03:25on small screens, forms only.
1:03:27We don't want to have any kind of
1:03:29gradients, as that's going to seem
1:03:30AI-ish. No oversized hero sections,
1:03:33cards, or scroll-heavy layouts. Keep the
1:03:36layout minimal and professional.
1:03:38And then we can dive into a bit more
1:03:40details on the full implementation.
1:03:43So, we can say
1:03:46wrap the root layout with a Clerk
1:03:48provider using the Clerk's dark theme.
1:03:52Create sign-in and sign-up pages using
1:03:54Clerk components. And as I told you
1:03:56before, we have to be specific in
1:03:58telling it that it should use the
1:04:00proxy.ts file name at the project root
1:04:03instead of the middleware.ts.
1:04:06And then, we have to define public
1:04:07routes using the existing sign-in and
1:04:10sign-up environment variables. Protect
1:04:12everything else by default. Which means
1:04:15that we have to update our home page so
1:04:18that when authenticated users visit it,
1:04:21we redirect them to the editor. Or when
1:04:24unauthenticated users visit it, we
1:04:26redirect them to the sign-in page.
1:04:29We also want to implement Clerk's
1:04:32built-in user button to the editor
1:04:35navbar right section for profile
1:04:37settings and log out. We want to keep
1:04:39Clerk's default user menu and profile
1:04:41flows intact and not rebuild heavily
1:04:44custom- ized Clerk internals, and want
1:04:46to use existing Clerk environment
1:04:48variables without renaming or inventing
1:04:51new ones. Finally, we can specify
1:04:54additional dependencies, such as Clerk
1:04:56UI, if we need to install it. And then,
1:04:59when it's done, we want to check that
1:05:00proxy file is there, that all routes are
1:05:03protected except public auth routes,
1:05:06auth pages use CSS variables with no
1:05:09hardcoded colors, Clerk provider wraps
1:05:12the layout and the build passes. This is
1:05:15our complete authentication
1:05:17implementation. Now, you know the drill.
1:05:20Go ahead and open up your agent,
1:05:22give it access to this file, and tell it
1:05:25to read this file and update the
1:05:28progress tracker.md file accordingly.
1:05:31Then, implement the auth feature exactly
1:05:34as specified in the auth.md file. Okay?
1:05:38You can see that the built-in microphone
1:05:40feature right here still isn't perfect.
1:05:42Let me try to do the same thing with
1:05:43Whisper flow specified in auth.md file.
1:05:50There we go. That's a bit better, and
1:05:52specifically it's 03.auth.md.
1:05:55Perfect. Let's go ahead and run it.
1:05:58First, it's asking me to run some bash
1:06:00commands to check whether Clerk has been
1:06:02properly installed, and we'll say,
1:06:04"Yeah, go ahead, you can run it." It
1:06:06took him about 2 minutes or so to go
1:06:09through all the context we shared, and
1:06:11only then it'll start to implement
1:06:13everything in parallel.
1:06:15And this is much better than if it
1:06:17started right away and then just ended
1:06:20up with a bunch of mistakes. So, the
1:06:21list of updates that it put right here
1:06:23is to update the .env.local with Clerk
1:06:26sign-in and sign-up URL vars, create a
1:06:29proxy with Clerk middleware route
1:06:31protection, update the app layout with a
1:06:33Clerk provider and dark theme, create
1:06:36sign-in and sign-up pages with a
1:06:38two-panel layout,
1:06:39update the app page to redirect based on
1:06:42the auth state, add the user button to
1:06:44editor navbar,
1:06:46and create editor page, and then update
1:06:48the progress tracker, and run npm run
1:06:50build to verify. It's going to ask me
1:06:53whether it has the ability to create
1:06:55some directories, and I'll tell it,
1:06:57"Yeah, go ahead."
1:06:58And in the future we can give it some
1:07:00more permissions so it can do things a
1:07:02bit more freely. And after it came up
1:07:04with the initial plan, it actually built
1:07:07out everything pretty quickly. Maybe
1:07:09even in less time than it used to think
1:07:12how to approach it in the first place,
1:07:13which shows you just how important the
1:07:16initial context and a proper task are.
1:07:19And there we go. The build passes, and
1:07:21here's the summary of everything that
1:07:23was implemented. I think all of these
1:07:25things right here shouldn't come as a
1:07:27surprise because we initially specified
1:07:30them in the spec. Then it updated the
1:07:33to-do's, and now it just did it. And
1:07:36yeah, it's pretty interesting that it
1:07:37even notes right here that it would have
1:07:39gotten confused about this middleware
1:07:41proxy thing if we didn't specify it
1:07:43properly. But thankfully, it did it in
1:07:46the right way thanks to the research
1:07:47that we've done at the start.
1:07:49So, what do you say that we take it for
1:07:51a spin? The application is running on
1:07:53localhost 3000, but whenever I make some
1:07:55bigger changes, I like to rerun it. But
1:07:58initially, if you head over to
1:07:59localhost, you might get redirected to
1:08:02this clerk handshake part, which is
1:08:04going to lead to a broken page. But
1:08:06after you reload, it should properly
1:08:09redirect you back to the homepage. And
1:08:11after that, it should just work.
1:08:14This is something that we can polish and
1:08:16fix up later on.
1:08:18But yeah, this is looking interesting.
1:08:20Definitely not quite as nice as the
1:08:23original application that I've showed
1:08:25you that's deployed.
1:08:26So, what are some of the things that we
1:08:28can do to make it look more similar to
1:08:31that one? Well, step one is to search
1:08:34some kind of design websites online like
1:08:36Dribbble or award-winning websites, and
1:08:39then just take a screenshot and try to
1:08:41get it to match to that closer. Or in
1:08:43this case, you can head over to the
1:08:45deployed version of this application,
1:08:48take a screenshot of this whole UI that
1:08:51you can see right here,
1:08:52and then we can feed it over into our
1:08:55chat. So, go ahead and open up the chat.
1:08:57Keep in mind that we can still stay
1:08:59within the authentication window that we
1:09:01worked on
1:09:02because now we're fixing some parts
1:09:04about that specific implementation. So,
1:09:06what you can do is just click this plus
1:09:08right here and upload from computer or
1:09:10just drag and drop the screenshot. Then,
1:09:13select it and then we can further point
1:09:15out to some things that could be
1:09:17improved, not just the layout. It seems
1:09:19like it didn't properly read the fonts,
1:09:22that the right side of the screen is
1:09:24taking a larger portion, so maybe we can
1:09:26split them 50/50, same as it is right
1:09:28here, and make the font size a bit
1:09:31larger. So, it's not just the layout. We
1:09:33needed to review the screenshot and
1:09:35update the UI of our current application
1:09:38to look more like the one on the
1:09:39screenshot. Means 50/50 left and right
1:09:43side layout with some kind of a color on
1:09:45the left side to differentiate it from a
1:09:47dark background,
1:09:49as well as we need to fix the fonts so
1:09:51that it uses the ones outlined in our UI
1:09:55guidelines. I think for now this is
1:09:56going to be enough for it to get closer
1:09:59to the design we want to get. So, press
1:10:01enter and let's see how it handles it.
1:10:03And there we go, the build passes, it
1:10:06updated the globals from circular font
1:10:08reference to the one that should be
1:10:10correct right now. The body was pointing
1:10:12at itself, so this font was never
1:10:15actually applied to anything. Same fix
1:10:17for the heading and it also implemented
1:10:19some other fixes. Let's check it out.
1:10:21The design now looks much closer to the
1:10:23finished product. The fonts are being
1:10:26properly applied and that makes a big
1:10:28difference. So does the increase in font
1:10:30size and this shift between the two
1:10:33different background colors. Of course,
1:10:35later on we can come up with a unique
1:10:37logo that we can put right here, but for
1:10:39now this is looking great.
1:10:41And believe it or not, we have a fully
1:10:44functional authentication system built
1:10:46in right here. So, what do you say that
1:10:48we go ahead and test it out? You can
1:10:50head over to sign up to see whether that
1:10:52works and it does. Later on, if you want
1:10:55to, you can modify the contents on the
1:10:57left side depending on whether you're in
1:10:59sign-in or sign-up page. And then let's
1:11:01use something like GitHub to sign in.
1:11:04I'll authorize it and the redirect
1:11:06redirects us to a page that currently
1:11:09breaks. So, I'll try to head back over
1:11:12to localhost:3000 one more time. And now
1:11:15when we get back, it actually redirects
1:11:17to the editor properly. You can see that
1:11:20we have the sidebar, we have a space for
1:11:23the canvas that's about to come in the
1:11:25future. And then on top right, we see
1:11:27all the information about our currently
1:11:29logged-in account, which is beautiful. I
1:11:31mean, we get complete user management
1:11:34within a single prompt that we've done.
1:11:36Let's also try to sign out for now, and
1:11:38we do get one issue when we click that
1:11:40button saying there's an unexpected
1:11:43response received from the server. So,
1:11:45if we head back over here and open up
1:11:47the terminal, we can see an unhandled
1:11:49rejection. Unexpected response was
1:11:51received from the server. And then in
1:11:53the terminal, we see something like
1:11:55this, which doesn't really tell us much.
1:11:58Now, what we could do is just copy this,
1:12:00open up the chat window one more time by
1:12:03heading over here, and then just pasting
1:12:06this error and telling it to fix it.
1:12:08But, I'm actually glad that this error
1:12:10happened because I can teach you a bit
1:12:12better and more precise way to handle
1:12:14these issues and so that when you try to
1:12:16fix them, you don't cause new ones.
1:12:19So, instead of pasting this right into
1:12:21the chat, we're actually going to open
1:12:22up a new file right here within context
1:12:26and call it current issues.md.
1:12:31You can even do it within feature specs.
1:12:33Either way works. Then, you can paste
1:12:35any kind of errors that you have and
1:12:37explain what's happening. So, I'll
1:12:39explain that when I click the logout
1:12:43button,
1:12:46the following error appears. And now I
1:12:49can paste this error message. And we can
1:12:51also specify that sometimes when we log
1:12:54in, we get redirected to this weird long
1:12:57URL, which doesn't show anything on the
1:12:59page.
1:13:00And then we can just paste this URL
1:13:03right here. Again, the errors on your
1:13:05end might be a bit different from what
1:13:08I'm seeing right here. Whatever they
1:13:10are, I don't want to teach you how to
1:13:12copy and paste. I want to teach you how
1:13:14to solve the problems for yourself.
1:13:16Explore the current issues file and
1:13:19deeply analyze the problem.
1:13:22Only when you have the analysis, give it
1:13:24back to me with the idea of how you're
1:13:26planning to solve it, and then wait for
1:13:28me to give it the green light to execute
1:13:31it. So yeah, writing something like this
1:13:33makes sense because that way it doesn't
1:13:35go into the spiral of trying to fix its
1:13:37own bugs while breaking 10 other things.
1:13:39You provided the error, it's going to
1:13:41come back with the analysis. You're the
1:13:43one deciding whether that analysis makes
1:13:45sense and whether it can actually
1:13:47execute it. So, let's run it and see
1:13:50what it comes back with. And after some
1:13:52thinking, it's back with the analysis.
1:13:55And this is so much better and so much
1:13:58more detailed than if we just told it to
1:14:00fix it immediately. Now, it actually
1:14:03tried it out and has a deep idea of
1:14:05what's happening. The server log is the
1:14:07key clue. Proxy took 373 milliseconds
1:14:11out of a half a second total request.
1:14:13Almost all time is spent in the proxy.
1:14:16So, that already points it in the right
1:14:18direction.
1:14:19As a human person, I would never figure
1:14:22this out on my own. At least not from
1:14:24such a vague error message. But yeah,
1:14:27here it figures out that this button
1:14:29needs an after sign out URL. So, it's
1:14:32just going to add it.
1:14:33And a similar thing is happening with
1:14:35the handshake URL. It basically needs to
1:14:38configure that after sign up URL, and
1:14:41that way it's going to route it
1:14:42properly. So, it provided a two fix
1:14:44plan. So, this looks plausible to me,
1:14:47looks good. So, let's just tell it to
1:14:50execute the plan and fix the issues.
1:14:52There we go. So, the fix now seems very
1:14:55apparent. We just needed this after sign
1:14:57out URL. And if you want to learn more
1:14:59tips and tricks just like this one about
1:15:02actually analyzing and fixing the errors
1:15:04and how I approach building these
1:15:06production level applications,
1:15:08definitely check out the spec driven
1:15:10agentic development course. It's not out
1:15:12yet as it's going to be super detailed
1:15:14and it's going to follow the best
1:15:15practices from the strongest developer
1:15:17teams out there. But yeah, it'll be out
1:15:19soon, but I'm still super glad that
1:15:21while I'm developing that, you can still
1:15:24learn how I develop these applications
1:15:26with the agentic ways with this new
1:15:28video that you're watching right now.
1:15:30And you can let me know down in the
1:15:32comments how you like this new type of
1:15:34video. I get that it's completely
1:15:36different from manual coding, but I
1:15:38still think there's so much to learn and
1:15:41we can have a predictable development
1:15:44workflow even with AI doing the writing
1:15:47for us. So, let's see whether this
1:15:49actually fixes it. And the build passes.
1:15:51The after sign out URL prop was removed
1:15:54from the user button as it belongs in
1:15:56clerk options and is set via environment
1:15:58variables. So, we'll soon be able to
1:16:00verify whether that actually fixes it.
1:16:03But before, I want to open up my
1:16:05terminal,
1:16:06stop it from running and then rerun it
1:16:08again on localhost 3000 because we
1:16:10changed the ENVs, so we want to make
1:16:12sure that they're read by the browser.
1:16:14So now, heading back over to localhost
1:16:163000, maybe you're signed in already,
1:16:18which is fine. You can simply sign out
1:16:20now and that brings us to another error,
1:16:23which is the same one we've had before.
1:16:25And another thing you can do is head
1:16:27over to inspect element, switch over to
1:16:29the application tab, and then clear all
1:16:32the cookies. So, find the cookies for
1:16:34localhost 3000, clear them, and then
1:16:36reload the page.
1:16:38You'll be redirected back to the home
1:16:40page and now we can retry with a clean
1:16:42slate.
1:16:43So, I'll sign in using GitHub the same
1:16:46way I did before. That works. I
1:16:48automatically get redirected over to the
1:16:50editor, which is great. And now, I'll
1:16:52sign out, and that worked. So, now head
1:16:55back over within your terminal,
1:16:57run git add dot, git commit {dash} m,
1:17:01implement auth, and then git push. This
1:17:04is going to push all the changes to
1:17:06origin development, allowing us to open
1:17:08up a pull request over to the main
1:17:10branch. Then, you can open up a PR, and
1:17:14let's wait for CodeRabbit to review it.
1:17:16This time, we had many changes, but not
1:17:19many of them are directly related to
1:17:21what we did in the code. We just added
1:17:23these agent skills so that everybody
1:17:25else's agents working on this code base
1:17:28also, well, become smart in the
1:17:30technologies that we're using for the
1:17:32project. And then, yeah, of course, we
1:17:34implemented a couple of different files
1:17:36that CodeRabbit will verify. But,
1:17:38primarily, what we've done is right here
1:17:41within app, sign in and sign up pages,
1:17:45we have added some features that would
1:17:46display on the left side. And then, the
1:17:48sign-in page, where on the right side,
1:17:51we just render the sign-in component
1:17:54coming from Clerk.
1:17:56Similar thing happens over to the
1:17:58sign-up page. So, right here, we're just
1:18:01rendering the sign-up UI.
1:18:03Then,
1:18:04over in the layout, we are wrapping
1:18:07everything with a Clerk provider,
1:18:09setting the theme to dark, and setting
1:18:11some custom variables. That's it.
1:18:14Everything else remains the same. And
1:18:15then, within the editor page, we are
1:18:18simply showing the nav bar and the
1:18:20sidebar, as well as the rest of the
1:18:22content.
1:18:23In the nav bar, we display the Clerk
1:18:26user button, allowing us to see more
1:18:27info about the user, and allowing us to
1:18:30log us out. Pretty straightforward so
1:18:32far. As our components and features get
1:18:34more detailed, we're going to do deeper
1:18:36dives into the code base, but so far so
1:18:39good. Let's wait for the review. And
1:18:41quickly we're back with a full
1:18:43walk-through. This pull request
1:18:45integrates Clerk auth into a Next.js
1:18:47application and establishes
1:18:49comprehensive agent skills for Clerk
1:18:52integration across multiple frameworks.
1:18:54It also adds auth middleware, sign-in
1:18:57and sign-up pages, Clerk provider setup,
1:18:59and introduces five new skill
1:19:01definitions for supporting scripts,
1:19:03documentation, and so on. So, obviously
1:19:06a lot of the checks right here from
1:19:08CodeRabbit are going to be about the
1:19:09skills that we set up, but we can skip
1:19:12those for now and focus on the ones
1:19:14about the actual files we implemented.
1:19:17In this case, it looks like there's one
1:19:19critical issue, and that is within the
1:19:21context current issues.md.
1:19:24Well, you never want to publish current
1:19:26issues to GitHub anyways, because you
1:19:28want people to see your code and not
1:19:30your mistakes. Um what we're doing here
1:19:33is even worse. We're exposing the
1:19:36handshake or the JWT token from the
1:19:38track docs. So, this is a real security
1:19:42issue. So, what we need to do is delete
1:19:44this file. Obviously, this won't delete
1:19:46it from the Git history, but that's fine
1:19:49for us because this JWT is no longer in
1:19:51use.
1:19:53So, right here over to getignore, I'm
1:19:54going to add our {forward-slash} context
1:19:58{forward-slash}, and that's going to be
1:20:00the current issues
1:20:03.md file.
1:20:05And I'll also remove everything that is
1:20:07within it.
1:20:09Not that it matters right now, because
1:20:10we're adding it to getignore anyway. So,
1:20:13let's go ahead and push those changes by
1:20:14saying get add {dot} get commit update
1:20:18{dot} getignore.
1:20:20And get push. Immediately the changes
1:20:23will be recognized, which means that we
1:20:24can merge this over to the main branch.
1:20:27And while we're here, we can also head
1:20:29over into context on the main branch and
1:20:32remove the current issues file right
1:20:34here from GitHub by simply deleting the
1:20:36file.
1:20:38And committing the changes.
1:20:40We can also do the same thing on the
1:20:41development branch by heading over to
1:20:43context
1:20:45and heading over to current issues, and
1:20:48just removing the file
1:20:50so that it's no longer here, and it's
1:20:51not going to be pushed to GitHub any
1:20:53longer because we added it to get
1:20:55ignore.
1:20:56Perfect. With that in mind, we've
1:20:58successfully implemented the full UI and
1:21:01functionality for the authentication
1:21:03within our application.
— Project Dialogues
1:21:07Now that our authentication is done, and
1:21:09we can actually sign into our
1:21:11application, let's make the sidebar
1:21:14actually do something. As right now, it
1:21:16is just static. Before we hook up any
1:21:19real data, we need the UI in place.
1:21:22The create, rename, and delete
1:21:25dialogues, plus the editor home state.
1:21:27So, we're keeping this prompt focused on
1:21:29UI only with no API calls yet. Head over
1:21:33into context, feature specs, and add a
1:21:36new file called 04 project dialogues.
1:21:40Within it, we can specify that the goal
1:21:43is to build the editor home screen and
1:21:45add the project dialogues and sidebar
1:21:47actions with no API calls yet. So, what
1:21:51does this specifically mean?
1:21:52Well, it means that on the home page, we
1:21:54want to reuse the existing editor layout
1:21:58without modifying the navbar or sidebar
1:22:00behavior, but in the center of the page,
1:22:02we want to add a heading, create a
1:22:04project, or open up an existing one, and
1:22:07a description. Start a new architecture
1:22:09workspace or choose a project from the
1:22:11sidebar, and a new project button with a
1:22:14plus icon. Keeping the layout minimal
1:22:16without wrapping this content in cards.
1:22:19And then clicking new project should
1:22:21open up the create project dialogue.
1:22:23Let me actually show you what I mean by
1:22:26all of this by heading over to the
1:22:27finished version of the application, and
1:22:30quickly signing in. Notice how right
1:22:32here in the middle we have some text to
1:22:34greeting us, even though the canvas is
1:22:36empty, allowing us to create a new
1:22:38project, which then opens up this
1:22:39dialogue. That's exactly what we want to
1:22:42achieve.
1:22:43So, we'll have a dialogue for creating a
1:22:45new project, as well as for editing one,
1:22:48and deleting it. So, let's specify these
1:22:51three dialogues below.
1:22:53We're going to have one for creating a
1:22:54project that takes in the project name
1:22:57input, the live slug preview based on
1:23:00the name, and preview updates as the
1:23:02user types. Then, the one for the rename
1:23:05with pre-filled project name input, and
1:23:08the one for delete. Finally, on the
1:23:10sidebar, we want to add the following
1:23:13project item actions, rename and delete,
1:23:16show actions only for the projects that
1:23:18we own, and hide actions for the shared
1:23:21or projects belonging to collaborators,
1:23:23and on mobile, tapping outside the
1:23:25sidebar closes it, and want to add some
1:23:27kind of a backdrop scrim. Finally, let's
1:23:29specify the implementation right here by
1:23:32telling it to create a dedicated hook to
1:23:35manage the dialogue state, the form
1:23:37state, and the loading state, that way
1:23:39we can reuse the functionality.
1:23:41And then we want to wire the editor home
1:23:44new project to create dialogue, the
1:23:47sidebar create button to create dialogue
1:23:49as well, sidebar rename to rename
1:23:52dialogue, and sidebar delete to delete
1:23:54dialogue. For now, we only want to use
1:23:56mock project data with no API calls.
1:24:00And to check when it is done, we need to
1:24:03check that sidebar actions are wired,
1:24:05that slug preview works, that no
1:24:07TypeScript errors exist, and there's
1:24:09also no linting errors.
1:24:11So, let's open up Claude Code or your AI
1:24:14agent of choice,
1:24:16and then we can tell it to read this
1:24:19file, update the progress-tracker.md
1:24:22to mark this as in progress and then
1:24:24implement it exactly as specified. And
1:24:28let's run it.
1:24:29And in about a minute or so, feature
1:24:31four is done.
1:24:33Here's what's built. New file hooks use
1:24:36project dialogues, which centralizes the
1:24:38dialogue type, form, including the name
1:24:41and the slug, the loading state and mock
1:24:44data, which exposes these different
1:24:46functions to deal with the dialogues.
1:24:48Then there's the dialogue component, as
1:24:50well as within the project sidebar, we
1:24:53also call the dialogues when needed.
1:24:56So, let's quickly check this hooks file
1:24:58that it implemented. You can see that it
1:25:00is strictly typed right here with the
1:25:02project interface, the dialogue type, to
1:25:05slug, which takes in the name and turns
1:25:07it into a human readable ID,
1:25:09and it even created some mock three
1:25:11projects that we can verify.
1:25:13It came up with a lot of different use
1:25:15states, keeping track of all the
1:25:17projects, the dialogue type, the
1:25:20selected project, the name, slug, and
1:25:22loading.
1:25:23And all of these are going to be used
1:25:25within the dialogues themselves. Then it
1:25:27returns the data, so we can actually use
1:25:29them. Let's check where the dialogue is
1:25:31being used. Most often, it is right here
1:25:33within project dialogues,
1:25:35where we have the actual code for how
1:25:37the dialogue looks like. If the dialogue
1:25:40is of a type create, then we show this
1:25:42one. If it's of a type rename, we show
1:25:44this one. And if it's delete, we show
1:25:47the one below. Before we test it out,
1:25:49let's check out the progress tracker
1:25:51that says that the current phase is the
1:25:53feature four dialogues.
1:25:55It has the goal to do it. And it has
1:25:58actually completed the project dialogues
1:26:00with all of these different components.
1:26:02So, now if you come back to the editor,
1:26:04this is going to look much better. It's
1:26:06no longer just a blank screen, but
1:26:08rather in the middle, it says create a
1:26:10project or open up an existing one,
1:26:12start a new architectural workspace, or
1:26:14choose is project from the sidebar. And
1:26:16if you click create project, it actually
1:26:19opens up a new project dialogue where
1:26:22you can type something like my project.
1:26:26It automatically creates a slug at the
1:26:28bottom as well. And if you click create,
1:26:30you saw that creating loading.
1:26:33And of course, the data is currently
1:26:35static, but you can see how it's going
1:26:37to look once it actually picks the data
1:26:39from the database.
1:26:41So, we have Ghost AI Core, which you can
1:26:44select
1:26:45to edit its name
1:26:47as well as to delete it. This means that
1:26:50the UI for all of the dialogue
1:26:52functionalities has now been implemented
1:26:54alongside this centerpiece of the
1:26:56application, which means that now that
1:26:58the majority of the UI is done, in the
1:27:00next lesson, we can start focusing on
1:27:03implementing real data with a real
1:27:05database to make our app come to life.
1:27:08But before we dive into the database,
1:27:10let's make sure that our current code is
1:27:12good. And I want to show you another
1:27:14Code Rabbit feature allowing you to
1:27:16review your code directly within your VS
1:27:18Code, which means that you don't even
1:27:20have to create a PR and you can be that
1:27:22much faster. You can install the Code
1:27:24Rabbit extension, authenticate to your
1:27:26account.
1:27:28It'll notice the changes that you have
1:27:29right now, so you can just click review
1:27:32all changes. And the review will start
1:27:34directly within your editor.
1:27:37So, let's give it a minute. We'll check
1:27:39the changes and if they're good, push
1:27:41them. If not, we're going to fix them.
1:27:43And within a minute, the review is in
1:27:45and Code Rabbit left the comments
1:27:47directly on our code base. So, you can
1:27:49expand all the files and click on them
1:27:52to see exactly what's happening.
1:27:55For example, right here, specific
1:27:57confirmation message and project name
1:27:58display, the delete project dialogue
1:28:01lacks key detail. What text should be
1:28:03shown to the user? Should the dialogue
1:28:05show which project is being deleted? And
1:28:08should we show the cancel button as
1:28:09well? These are some important
1:28:11questions. And it's good because in this
1:28:13case Code Rabbit is telling us that we
1:28:15can be even more precise with our
1:28:17feature specifications. But, let's see
1:28:19if it has any comments within our code
1:28:21base. It says right here that the
1:28:22project item that is this one right here
1:28:25appears clickable, but it lacks
1:28:27interaction handling. It has a cursor
1:28:29pointer, that is this one right here in
1:28:31the sidebar, but when I click on it, it
1:28:34doesn't do anything. That's fine for now
1:28:36because later on we're going to make it
1:28:37open the actual canvas of that project.
1:28:40Since that is not implemented yet,
1:28:42that's totally fine. Then, another
1:28:44inconsistency, feature four is being
1:28:46marked as both in the current phase as
1:28:49well as finished right here at the
1:28:50bottom. So, we need to modify the
1:28:52progress tracker to say that the feature
1:28:54five is to be done. And then, and then
1:28:57modify the current goal to be determined
1:29:00for feature five. Next up, feature five.
1:29:03This is good. And finally, in the use
1:29:05project dialog hooks, we have a couple
1:29:08of comments as well.
1:29:09First, we have missing validation for
1:29:12empty slug edge case.
1:29:14If the user enters a name containing
1:29:16only special characters, it passes the
1:29:18truthy check, but returns an empty
1:29:20string, resulting in a project with an
1:29:22empty slug. So, if I head back right
1:29:25here and try to create something, take a
1:29:27look. It's true.
1:29:29I can actually create a new project, but
1:29:31nothing gets added to the slug because
1:29:33these are not valid slug characters. So,
1:29:36we definitely have to fix this.
1:29:37Thankfully, what we need to do is add
1:29:40slug validation.
1:29:42So, I'll just press accept right here
1:29:45and it'll update the code for me. That's
1:29:47another perk of using Code Rabbit within
1:29:49VS Code. And I think there is one other
1:29:51comment right here in this dialog, which
1:29:54is the same issue right here.
1:29:56Same as before, the rename operation has
1:29:58the edge case where the slug could end
1:30:00up empty. So, we definitely want to fix
1:30:02the validation there as well.
1:30:05Now, we can push the changes by saying
1:30:07get add dot, get commit {dash} m
1:30:11implement dialogues
1:30:14and then get pull to pull the latest
1:30:16changes and then run get push {dash}
1:30:19{dash} force to push the latest changes.
1:30:22Because remember, we deleted that file
1:30:24directly on GitHub, but still we want to
1:30:25push the local changes as well. So now
1:30:28if you want to, you can manually open up
1:30:30a PR by heading over to new pull request
1:30:33from the development branch
1:30:34over to the main branch
1:30:37and we can immediately merge it because
1:30:39we've already reviewed all the changes
1:30:41with Code Rabbit with their VS Code
1:30:43extension. So back within the
1:30:45application, you can run get pull
1:30:48and you can also check out to main and
1:30:50run get pull there as well
1:30:53to be up to date. But make sure to
1:30:55switch back to the development branch
1:30:56because that is where we're actively
1:30:58developing new features.
— Prisma Setup
1:31:02Now that our project management UI is
1:31:04wired up, we need the actual database
1:31:06behind it. And for that, we're going to
1:31:08use Prisma with Postgres.
1:31:11If you prefer a different database, the
1:31:13process is the same. Just swap the
1:31:15provider in the schema.
1:31:17Prisma supports Postgres, MySQL, SQLite,
1:31:20and more. So let's start with a manual
1:31:23installation right here within the
1:31:24terminal. Stop the app from running and
1:31:27then run npm install Prisma tsx {at}
1:31:31types {forward-slash} pg {dash} {dash}
1:31:33save {dash} dev. These are the dev
1:31:35dependencies so that we have a nice
1:31:37development workflow. After that is
1:31:39done, you can install all the necessary
1:31:41packages needed for us to set up our
1:31:43database such as {at} Prisma
1:31:45{forward-slash} client, Prisma adapter
1:31:48pg, {dot} env, and pg itself. And press
1:31:52enter. After that is done, initialize
1:31:54Prisma with the correct output path for
1:31:57the Next.js router.
1:31:59You can do that by running npx Prisma
1:32:01init dash dash output, data slash app
1:32:04generated Prisma. This creates a new
1:32:07Prisma folder with the schema.prisma
1:32:10file and an ENV file at the root. So,
1:32:13head over to the Prisma dashboard. I'll
1:32:15leave the link down in the description
1:32:17and create a new account. You can sign
1:32:19in with GitHub or Google. And once
1:32:22you're in, you can create a new project.
1:32:25I'll call it Ghost AI and they'll give
1:32:28you a one command setup, but in this
1:32:30case we can proceed with the connection
1:32:32string.
1:32:33So, you can just copy it and then head
1:32:35over into your .env and override the
1:32:38current database URL with the new one
1:32:40that you just got from the dashboard. As
1:32:42a matter of fact, we can take this
1:32:43database URL and put it within the
1:32:45.env.local as that's what we're using
1:32:47for our environment variables. Then,
1:32:49open up your prisma.config.ts
1:32:53and update the path to the schema. It's
1:32:55just going to be prisma forward slash.
1:32:57We want to remove this schema.prisma
1:33:00because we'll make a separate Prisma
1:33:02model.
1:33:03So, just use prisma forward slash. Now,
1:33:06before we run our spec, let's also
1:33:08install Prisma Agent Skill. We did the
1:33:11same thing with Clerk, so the same idea
1:33:13applies. Just for a different library.
1:33:16Open up your terminal and run MPX skills
1:33:19add Prisma skills.
1:33:22It'll ask you which ones you
1:33:24specifically want to install and in this
1:33:26case you can select all of them. CLI,
1:33:28client API, database setup, Postgres,
1:33:31Postgres setup, and upgrade V7 and press
1:33:33enter.
1:33:35You can install them for cloud code
1:33:37within this project with simlink.
1:33:40And we can proceed with the
1:33:41installation. And now we are ready to
1:33:43generate our schemas. So, let's create a
1:33:46new file right here within our context
1:33:49feature specs
1:33:5105-prisma.md.
1:33:54And one sentence update is that Prisma
1:33:56is already installed, but we needed to
1:33:59add the project data models, Prisma
1:34:01client singleton, and the first
1:34:03migration. So, then we have to start
1:34:05specifying the models that we want to
1:34:07install.
1:34:09That we want to set up.
1:34:11First, we're going to have the project
1:34:13model.
1:34:14So, we'll ask it to add project that has
1:34:19to have an owner ID mapped to the Clerk
1:34:21user, a name, an optional description, a
1:34:25status enum, either draft or archived, a
1:34:28canvas JSON path for future canvas blob
1:34:31storage, timestamps, and indexes on
1:34:34owner ID and creation date. So, we can
1:34:36actually search through them. The second
1:34:38model we want to have is going to be the
1:34:40project collaborator, which is a project
1:34:43relation with cascading delete. It
1:34:46includes collaborator email, timestamp,
1:34:49and some constraints on the project and
1:34:51email, and indexes, so we can actually
1:34:54map over it. Do not add any extra fields
1:34:56unless required by Prisma. Finally, to
1:34:59be able to use these models, we have to
1:35:01set up Prisma client.
1:35:03So, create a lib prisma.ts file as a
1:35:07cached singleton,
1:35:08branch by database URL. If it starts
1:35:11with Prisma plus Postgres, then use
1:35:13accelerate, else direct to add Prisma
1:35:16adapter PG. In our case, it's going to
1:35:19start with Postgres forward slash, which
1:35:22means that it is actually hosted
1:35:24somewhere. Finally, we want to ask it to
1:35:27run the migrations and to generate that
1:35:29Prisma client, and want to verify
1:35:31whether it has installed all the
1:35:32dependencies,
1:35:34such as Prisma, Prisma client, Prisma
1:35:36adapter PG, and PG itself, and some
1:35:39checks are whether schema has both
1:35:41models with correct relations and
1:35:43indexes, the lib Prisma file exports one
1:35:46cached Prisma instance,
1:35:48the migration runs successfully, and the
1:35:50NPM run build passes. So, let's open up
1:35:54Claude Code, give it this file, and tell
1:35:56it to read this file, update the
1:35:59progress tracker, and implement it
1:36:01exactly as specified. I think you get
1:36:04the idea with these prompts.
1:36:06The actual spec file is doing the heavy
1:36:08lifting.
1:36:09So, let's see how it approaches it.
1:36:11It'll first read the Prisma spec,
1:36:14the progress tracker, the architecture
1:36:16context, and only then will it start
1:36:18implementing it.
1:36:20It'll first verify we have all the
1:36:22necessary packages, and it might even
1:36:24consult the Prisma skill. So, the agent
1:36:27does a better way implementing the best
1:36:29practices. So, let's give it some time,
1:36:31and I'll be right back. Now, it's in the
1:36:33process of creating the Prisma models
1:36:36for the project and the project
1:36:37collaborator.
1:36:39Then, creating the Prisma singleton, so
1:36:41we can actually have the client and run
1:36:43it. It's asking us whether we can
1:36:45actually run Prisma migrate. So, I'll
1:36:47allow it to run it.
1:36:50And then, it'll verify everything is
1:36:51done, and we'll be able to check it all
1:36:53out.
1:36:54You can see that we have many files
1:36:56changed, but once again, the majority of
1:36:58these are coming from the agents that we
1:37:00installed or the agent skills. The
1:37:02actual generated files that we care
1:37:04about are going to be within just a
1:37:06couple of files, such as this Prisma
1:37:08model right here,
1:37:10and this Prisma client. That's it. But
1:37:13okay, let's let it do its thing, and the
1:37:15feature five is done. Here's what's
1:37:18created, just two files that we need to
1:37:20take a look at.
1:37:21The first one is the project model, and
1:37:24the second one is the Prisma client. So,
1:37:27let's go ahead and check them out. I'll
1:37:28first open up the model that is going to
1:37:31be within models, project.prisma, and it
1:37:34looks like I'm missing the Prisma syntax
1:37:37highlighting. So, I'll head over into
1:37:39extensions and install Prisma, which
1:37:42would add the syntax highlighting,
1:37:43formatting, auto completion, and more.
1:37:46There we go. This now looks better. But
1:37:48yeah, essentially we have created a new
1:37:50model for the project with the ID, owner
1:37:53ID, name, description, status, the
1:37:56canvas, which is going to be attached to
1:37:57a project very soon, and a potential
1:38:00list of collaborators. And we also made
1:38:02it indexable so we can search for these
1:38:04projects.
1:38:05Same thing happens with the project
1:38:07collaborator. That's going to be a clerk
1:38:09ID connected to it. And it's going to
1:38:12have access to one or more projects.
1:38:15Then, if we take a look at the actual
1:38:17Prisma client, that's going to be within
1:38:20lib Prisma,
1:38:22we're just using the Prisma PG adapter
1:38:24to create a new Prisma client and
1:38:27connect it to it.
1:38:29Then, we export this global Prisma
1:38:31instance that we can use to make any
1:38:33kind of database calls. We can't really
1:38:36test a lot of stuff right here because
1:38:38we've just built a database.
1:38:39But in the next lesson, we can build a
1:38:41couple of API routes that'll bring us
1:38:43one step closer to actually making use
1:38:45of this data.
1:38:47So, for time being, let's just run git
1:38:49add dot, git commit {dash} m, implement
1:38:54Prisma,
1:38:55and then git push.
— Project CRUD APIs
1:38:59Now that our schema is ready, we're
1:39:02ready to build the API routes that sit
1:39:05on top of it.
1:39:06This is back end only.
1:39:08We're not yet wiring the UI. That'll
1:39:11come next. But right now, we need to
1:39:13focus on one single thing.
1:39:15A clean, secure set of routes for
1:39:18creating, listing, renaming, and
1:39:21deleting projects.
1:39:23So, create a new spec right here under
1:39:26context feature specs 06
1:39:30project APIs.md.
1:39:33And within it, we want to tell it that
1:39:35the database schema is ready. So, we
1:39:38need to build the back end project API
1:39:41routes. The routes are as follows: the
1:39:44REST endpoints for get API project,
1:39:46which is going to list the current
1:39:48user's projects, the post for API
1:39:51projects, which creates a project, we
1:39:54have the patch for renaming, and delete
1:39:57for, obviously, deleting.
1:39:59We can also give it a couple of rules,
1:40:02such as use the authenticated clerk user
1:40:05ID as an owner ID.
1:40:07And when creating, default missing
1:40:10project to untitled project and use the
1:40:12schema's existing ID strategy without
1:40:15adding sequential IDs. We also want to
1:40:18tighten up the security a bit by telling
1:40:20it that the unauthenticated requests
1:40:22return 401, and only the project owner
1:40:26can rename or delete. Non-owners
1:40:28mutations return 403. Which means that
1:40:31we're making our app secure not only on
1:40:33the client side, but the server-side API
1:40:36calls are also going to return invalid
1:40:38responses if somebody tries to break
1:40:41them. And again, we're not yet wiring
1:40:43any UI.
1:40:44And finally, to verify, we need to check
1:40:46whether the routes exist, whether the
1:40:49owner's checks are enforced, whether 401
1:40:52and 403 responses are handled correctly,
1:40:54and that the npm run dev build passes.
1:40:57You know the drill. Open up Claude code,
1:40:59tell it to read the file, and execute.
1:41:03Read this file, update the progress
1:41:05tracker, and implement it exactly as
1:41:07specified. Let's see how it does.
1:41:10The process of actually creating four
1:41:13REST API routes would take us some time,
1:41:16and you most likely already know how to
1:41:17do that. But our agent is just going to
1:41:19do it much more quickly for us. And then
1:41:22when you take a look at it, you can
1:41:23fully understand the structure, and
1:41:25you'll be able to add any other
1:41:26additional routes very easily, because
1:41:28it's mostly boilerplate. So, let's give
1:41:30it some time, and I'll be right back.
1:41:32There we go. That was quick. Four routes
1:41:35have been created, and the build is
1:41:37clean.
1:41:38All of them are right here within
1:41:40project routes or project project ID
1:41:43route. So, these are the general ones
1:41:46and these are the ones for update and
1:41:48delete.
1:41:49Let's go ahead and check them out. They
1:41:51are right here under app API route.ts
1:41:56for general project routes where we have
1:41:59a asynchronous get function where we
1:42:01first get the user ID from auth
1:42:03belonging to clerk.
1:42:06We check whether the user ID doesn't
1:42:07exist. In that case, we return a 401.
1:42:10But, if it does exist, we try to find
1:42:13all the projects that match with that
1:42:15user and then we return them.
1:42:17Similarly, if the user is trying to
1:42:19create a post, we get all the data from
1:42:23request.json. We take the name and
1:42:26finally create it. Then, if we want to
1:42:28update or delete them, you can head over
1:42:30to the project ID route where we can
1:42:33patch it based on the project ID. So, we
1:42:36first check whether the user has access.
1:42:39If they do, we take a look at the
1:42:40context params,
1:42:42find the project, parse the data,
1:42:45update the project data, and then return
1:42:47it.
1:42:48And the same thing goes with the delete.
1:42:50It's going to be even simpler. We find
1:42:52it, we delete it, and we call it a day.
1:42:55It's even returning a proper 204 status,
1:42:58which means deleted. But now, in the
1:43:00same lesson, I want to actually create
1:43:02an additional feature spec, which is
1:43:04going to be 07
1:43:06wireeditorhome.md.
1:43:10Where we want to wire the editor home
1:43:13sidebar and dialogues to the real
1:43:16project APIs we just created.
1:43:19So, first, we got to deal with data
1:43:21fetching because the editor home page is
1:43:24a server component. So, we need to fetch
1:43:27owned and shared projects server-side
1:43:30using the existing project data helper
1:43:33and pass both lists to the sidebar. And
1:43:35I don't want to see any client-side
1:43:37fetching for the initial load. Now, we
1:43:39can do that by using the project
1:43:42actions. So, I want to create a new hook
1:43:45in the hooks folder that manages dialog
1:43:48state and project mutation.
1:43:51That's going to look something like
1:43:52this.
1:43:53Create manage create dialog state,
1:43:56manage project name input, generate a
1:43:59short unique suffix, slugify the name,
1:44:02call the post API projects, and then
1:44:05navigate over to the new workspace.
1:44:07The project ID and Liveblocks room ID
1:44:09should stay aligned, and we can do a
1:44:12similar thing for rename and delete. The
1:44:15rename simply has to store the target
1:44:17project ID plus current name, and then
1:44:20patch the name, and delete has to store
1:44:22the target project ID, and then patch
1:44:24it. Finally, we are ready to wire it all
1:44:27together by connecting the hook to the
1:44:30sidebar and dialogs. Create dialog will
1:44:32show a room ID preview.
1:44:35Rename dialog will prefill the current
1:44:37name, and delete dialog will show the
1:44:39project name. As usual, we want to run
1:44:42some checks when we are done, and that
1:44:43is to see whether the sidebar uses real
1:44:46project data, not the fake dummy data it
1:44:48uses right now, whether the create
1:44:50actually navigates to the workspace,
1:44:52whether the rename updates correctly,
1:44:55and whether delete refreshes or
1:44:57redirects correctly.
1:44:58Finally, the build has to pass. So, this
1:45:01is the moment that our sidebar goes from
1:45:03mock data to real data.
1:45:06So, let's open up our agent,
1:45:09tell it to read the file,
1:45:12update the progress tracker, and
1:45:14implement it exactly as specified. So,
1:45:16let's let it do its thing, and once it's
1:45:19done, we can test it out in the browser.
1:45:21And after some time, we are back. The
1:45:23build passes clean, and it created a
1:45:26couple of new files. The lib projects is
1:45:29used for fetching the own projects and
1:45:31shared projects. We can quickly check
1:45:33that out right here under lib projects.
1:45:38And you can see that this file simply
1:45:40exports one function called get projects
1:45:43for user, which calls all the projects
1:45:45where the owner ID is the user ID that
1:45:48is currently signed in, and then it
1:45:50returns all the owned and shared
1:45:52projects. It also created a hook that
1:45:54deals with all the project actions.
1:45:57And it modified some additional files
1:45:59such as the editor, the projects route,
1:46:02and some more types and props updates
1:46:04over the sidebar and the dialogues. So,
1:46:06don't forget to rerun your application
1:46:08by running npm run dev. And then back on
1:46:11localhost:3000, we are ready to test it
1:46:14out by creating a new project. I'll give
1:46:17it a name such as my system design.
1:46:22And you can see that it's going to give
1:46:24it an additional slug right here, and we
1:46:26can click create project.
1:46:29It's creating it, and we get redirected
1:46:31to editor my system design. So, the
1:46:34redirect is actually working, but
1:46:36there's no route under that page. But,
1:46:39that's to be expected because so far, we
1:46:41just wanted to test whether we can wire
1:46:43the database functions with the API
1:46:45routes. So, if I head back over to the
1:46:47editor and then open up the sidebar,
1:46:49you'll see that there's a new my system
1:46:51design project. And you can also rename
1:46:55it to something like Ghost AI system
1:46:59architecture, and you can see that it
1:47:01updated it in real time. Or, you can
1:47:03also just delete it. Let's test it out.
1:47:06Yep, that works and it updates in real
1:47:08time. So, this means that we have not
1:47:10only implemented the dialogues to create
1:47:13all of this, but also implemented the
1:47:16API routes that handle the
1:47:17functionality.
1:47:19And if you're wondering why we didn't do
1:47:20all of this in a single prompt? Well,
1:47:23the API routes touch the back-end layer
1:47:26and the UI wiring touches the front-end
1:47:28and server components.
1:47:30Combining them gives the agent too much
1:47:32surface area to make assumptions across.
1:47:35Instead, we had two focused prompts,
1:47:38which means that we got back two clean
1:47:40results without messing up stuff on
1:47:42front-end and the back-end. So, before
1:47:44we go ahead and review these changes,
1:47:46head over to GitHub and merge the
1:47:48previous PR, which contained
1:47:50implementing Prisma and even more
1:47:52importantly adding all of those
1:47:54additional files for agent skills. So,
1:47:57I'll just go ahead and merge it. So, now
1:48:00when we push this over to GitHub, we'll
1:48:01be able to review just those changes.
1:48:04So, run git add dot,
1:48:06git commit {dash} m, wire up Prisma
1:48:11UI and REST APIs,
1:48:14and run git push. You can head over to
1:48:16your repo, open up a new pull request,
1:48:19and do it specifically from the
1:48:20development branch. This one will be
1:48:22fairly quick as there's only 11 files
1:48:24changed. So, let's give the Dr. Code
1:48:27Rabbit some time to review it. In this
1:48:29PR, we've finally introduced the REST
1:48:32APIs and CRUD functionalities, making
1:48:35our app, well, full stack. We've done
1:48:37that across all the different files and
1:48:39I always like when Code Rabbit thinks
1:48:42the functionality is so detailed that it
1:48:44actually gives us a diagram that we can
1:48:46review. So, as the user clicks the new
1:48:49project, the action that open create
1:48:51dialog runs.
1:48:53Then we initialize the dialog and
1:48:55generate the room ID with a suffix.
1:48:58We ask the user to enter the project
1:49:00name and submit. After they submit, we
1:49:03set the loading and we then make a post
1:49:05request to our API projects, which is
1:49:08our CRUD REST API route, which then
1:49:11calls Prisma project create.
1:49:13As soon as the project is created in the
1:49:15database, we return the project data,
1:49:18set the loading to false, and bring it
1:49:20back and then navigate over to the new
1:49:22project. It looks like we have one issue
1:49:25where we need to import the use project
1:49:27actions as a value and not as a type.
1:49:30So, return type of requires the hook in
1:49:32a value space, but import type is a type
1:49:35only import that TypeScript erases from
1:49:38the value namespace. So, what we can do
1:49:40is just copy this import right here,
1:49:43find where we're already importing a
1:49:45type, specifically the use project
1:49:48actions. That's going to be right here.
1:49:50And instead of it, we can just import
1:49:52the use project actions without the type
1:49:55at the start.
1:49:57That way, when it's referred right here,
1:49:59we can say type of use project actions,
1:50:02which is a function that TypeScript can
1:50:04now actually understand. This is a
1:50:05pretty nice save. And there's another
1:50:07issue in the use project actions where
1:50:10we need to handle the failed mutations
1:50:12before closing or redirecting. So,
1:50:15rename and delete always close the
1:50:17dialogue and refresh push even if the
1:50:20API calls fail. So, a server or off
1:50:23error can look like a success. We need
1:50:25to gate those transitions on res.ok and
1:50:28keep the dialogue open on failure.
1:50:30Thankfully, there's a quick fix right
1:50:31here or we can copy just this part,
1:50:33which is going to be the submit
1:50:35function. So, copy the submit,
1:50:37head over into the project actions or
1:50:41use project actions.
1:50:44And again, the these changes might be
1:50:46different for you as they are for me.
1:50:48So, if you have some other issues to
1:50:49fix, you can definitely do that.
1:50:52I'll remove the submit,
1:50:55bring in the correct one, push the
1:50:57changes by running git add dot, git
1:50:59commit {dash} m, implement
1:51:03CodeRabbit suggested
1:51:06fixes
1:51:07and run git push.
1:51:10The changes will automatically be
1:51:12recognized right here. So, we can go
1:51:14ahead and merge it and we are ready to
1:51:16continue.
— Editor: Access Sharing
1:51:19Before we build the canvas, let me
1:51:21quickly explain how Liveblocks works.
1:51:24Because once you understand it, the
1:51:26build order will make complete sense.
1:51:29Liveblocks gives every project a shared
1:51:32real-time room.
1:51:33Think of it like a live session.
1:51:36Everyone who opens the same project
1:51:38connects to that room over web sockets.
1:51:41And any change one person makes
1:51:43instantly appears on everyone else's.
1:51:46That canvas state, multiple users'
1:51:48cursors, the AI drawing nodes, all of it
1:51:52lives in that shared room and syncs in
1:51:54real time. Liveblocks rooms are open by
1:51:57default. So, technically, anyone could
1:51:59connect if we let them.
1:52:01But since Ghost AI is a project-based
1:52:04app where only owners and collaborators
1:52:06should have access, we need to control
1:52:09who gets in with a sharing mechanism.
1:52:12So, before Liveblocks connects a user to
1:52:14a room, it has to call authentication
1:52:16endpoints first.
1:52:18And when we check whether that user
1:52:20belongs to a specific project, only if
1:52:22they do, we issue a token to let them
1:52:25in.
1:52:26That token endpoint is what we're
1:52:28building in this chapter. And for it to
1:52:30work, the access control and the
1:52:32collaborator model have to exist. So,
1:52:34we're doing it in this order. Workspace
1:52:37access first, so each project has a
1:52:39secure route only authorized users can
1:52:42enter. And then we'll focus on the
1:52:44collaborator model second, so the system
1:52:46knows exactly who those authorized users
1:52:49are.
1:52:50Then Liveblocks and then the canvas.
1:52:53So, head over into context, feature
1:52:55specs, and create a new file called 08
1:52:59editor-workspace-shell.md.
1:53:03And within it, we'll explain exactly
1:53:05what has to happen next. So, let's go
1:53:07through it together. The goal of this
1:53:09prompt is to build the editor room ID
1:53:12workspace.
1:53:13Remember, that's that page that we got
1:53:15redirected to and we saw a 404. So, if I
1:53:19create a new spec right here and we
1:53:22navigate over to it, we get a 404. So,
1:53:25this is the page we're building. Before
1:53:27rendering, the unauthenticated users
1:53:29will be redirected to sign in. The users
1:53:32without project access will see the
1:53:34access denied and non-existent projects
1:53:37will also show access denied.
1:53:40Then, we need to create that access
1:53:42denied component with a centered layout,
1:53:44lock icon, short message, and a link
1:53:47back to the editor.
1:53:48And we can also create some helpers that
1:53:50are going to make it simpler for us to
1:53:52reuse the access functionality
1:53:54such as getting access to Clerk's
1:53:56identity and checking whether they can
1:53:58access a specific project.
1:54:01When it comes to the layout, we want to
1:54:02build a full viewport workspace layout
1:54:05with a top bar showing the project name,
1:54:07navbar actions at the top right, the
1:54:10existing projects hyper on the left, the
1:54:13current room highlighted in the sidebar,
1:54:16central canvas placeholder with a dark
1:54:18background and a centered message, and
1:54:19the right sidebar placeholder for future
1:54:21AI chat. This is exactly how the final
1:54:24version of the application looks like.
1:54:26We have the left sidebar with the
1:54:27currently open project highlighted, the
1:54:30top bar with the icons on the right, and
1:54:32then we have the AI chat, which is going
1:54:34to be coming soon. The canvas area, of
1:54:36course, should fill the remaining space.
1:54:39And in this case, we want to tell it to
1:54:41not add any real canvas logic, live
1:54:44blocks, AI chat, or sharing behavior
1:54:47yet. That's the keyword right here.
1:54:50When it's done, it should perform the
1:54:51following checks. So, let's open it up
1:54:54within our agent
1:54:55and tell it to read the current file,
1:54:58update the progress, and execute it
1:55:01exactly as specified. Of course, make
1:55:03sure that it knows what file you're
1:55:05talking about. Let's run it and give it
1:55:07some time to process it. And after a
1:55:10couple of minutes, it is done. I
1:55:12actually took Codex for a spin to see
1:55:14how well it can handle it. And yeah, it
1:55:16said that it implemented feature eight
1:55:18as specified. The guarded workspace
1:55:20route now lives under this page, stays
1:55:23server-side, and the redirects
1:55:25unauthenticated users to sign in. The
1:55:27workspace shell component is right here,
1:55:30and it includes a project aware top
1:55:32navbar with buttons to share. Okay, so
1:55:35let's go ahead and test it. Oh, this is
1:55:38looking nice. It is a bit different from
1:55:39the final design, but I actually love
1:55:42it. You can see how the sidebar
1:55:44collapses and then the central part
1:55:46actually expands. Later on, we can play
1:55:48a bit more with the design, but so far,
1:55:50I love it. So, right now we are looking
1:55:52the details of the new spec project
1:55:55because you can see that we're on that
1:55:56specific URL. And if you just head over
1:55:58to the editor, you'll be able to see
1:56:00something like this. You can expand the
1:56:02sidebar and then navigate over to the
1:56:04details page.
1:56:06If the redirect isn't working for
1:56:07whatever reason, send a small corrective
1:56:10prompt describing exactly what's
1:56:12happening, and it'll fix it. Now, let's
1:56:15test access control.
1:56:17Open up an incognito tab and then head
1:56:19over to the same URL. You'll
1:56:21automatically be redirected back to sign
1:56:23in, which is the first good sign. Go
1:56:25ahead and create a new account. I'll try
1:56:28to use the email and password this time.
1:56:31Let's go with contact js mastery pro and
1:56:34a password. And it's good to see that
1:56:36Clerk is trying to keep us safe, so it's
1:56:38suggesting a stronger password. So, let
1:56:40me do that. There we go. And for the
1:56:44email, I'll use a secondary email that I
1:56:47have because the first one is already
1:56:49tied with GitHub. There we go. And we're
1:56:51going to even have the email
1:56:53verification. So, you're going to get an
1:56:55email that looks something like this. Go
1:56:56ahead and copy the verification code.
1:56:59Then, paste it right here and you'll be
1:57:01logged in and redirected to the editor.
1:57:04So, if once again, you try to go to that
1:57:06same project URL,
1:57:09you should hit the access denied screen.
1:57:11You don't have access to this workspace.
1:57:13Hit back to your editor home to open up
1:57:15a project you actually can access.
1:57:17That's great. So, now, let's allow this
1:57:19user to actually invite that
1:57:21collaborator. To do that, I'll head over
1:57:24within our context, feature specs, and
1:57:27create a new file called 09 share
1:57:31dialog.md.
1:57:33Let's go through it together. In this
1:57:34case, we're basically working on the
1:57:36share button.
1:57:37It's going to be within the editor
1:57:39navbar and it's going to open up the
1:57:41share dialog, allowing owners to invite
1:57:44collaborators by email, view current
1:57:47collaborators, remove collaborators, and
1:57:49copy the project link with the temporary
1:57:52copy feedback.
1:57:53Collaborators can view the collaborator
1:57:55list only and not invite, remove, or
1:57:59manage access. It's also important that
1:58:01we're going to use Clerk data for the
1:58:03collaborator sharing system. So, they're
1:58:05going to be stored by email in the
1:58:07database and we'll use Clerk's backend
1:58:09API to enrich the collaborator email
1:58:12with display name and avatar image.
1:58:16And if a Clerk user is not found for an
1:58:18email, then we can fall back to showing
1:58:20the email only. We want to add the
1:58:22required API logic for listing,
1:58:24inviting, and removing collaborators,
1:58:27and we want to enforce ownership
1:58:28server-side, not client-side, for
1:58:31inviting and removing actions. Finally,
1:58:34we have a couple of checks to see
1:58:35whether everything's been done properly.
1:58:37So, once again, let's open up Codex or
1:58:40whichever agent you're using and tell it
1:58:42to read the 09 share dialog,
1:58:47update the progress tracker,
1:58:49and implement it exactly as specified.
1:58:52We can do it within the same chat
1:58:55because this is somewhat related to the
1:58:56functionality we just worked on. Let's
1:58:58give it some time and I'll be right
1:59:00back. And in about 5 minutes, feature
1:59:02number nine got implemented exactly
1:59:05within the specs scope. The workspace
1:59:08navbar share button now opens a new
1:59:10dialogue.
1:59:12So, what do you say that we actually
1:59:13test it out? Back within the browser,
1:59:15but not the anonymous one, which doesn't
1:59:17have the access. We have to go to the
1:59:19one that is the owner of this workspace.
1:59:22We can now click this share button,
1:59:24which allows us to copy the workspace
1:59:25link or invite them via email. And I
1:59:28love this share interface. So, I'll
1:59:30enter the email of my second account
1:59:34and click invite. You can see that the
1:59:36user has been invited as the
1:59:37collaborator, and since we signed up via
1:59:40email, this user doesn't have a profile
1:59:42photo. So, now if you head back and
1:59:44reload,
1:59:46check this out.
1:59:48The user now has access. And for this
1:59:50user, it's not under my projects, but
1:59:52under shared projects. And this user
1:59:55doesn't have the permissions to update,
1:59:57rename, or delete. Whereas for this
2:00:00user, you can see that it's under my
2:00:01projects and we have full permissions.
2:00:04That's the full collaborator flow
2:00:06working end-to-end.
2:00:08Owner invites, collaborator receives the
2:00:10invites, and nobody else can access it.
2:00:13And before we push the changes, let's
2:00:15review them with Code Rabbit. This time,
2:00:17I'll do it directly within VS Code
2:00:19through the Code Rabbit extension. And
2:00:21as soon as the comments are in, we can
2:00:23go ahead and check them out. Within the
2:00:25use project share.ts file, that is the
2:00:28hook that the share button borrows the
2:00:31functionality from.
2:00:32And right here, where we have the copy
2:00:35link button, this does nothing else than
2:00:37basically copy the URL to clipboard.
2:00:40But currently, the copy link doesn't
2:00:42handle the clipboard API failures, and
2:00:44the timer lacks cleanup. So, if the
2:00:46clipboard fails due to permissions or
2:00:48something else, the set timeout will
2:00:51still fire letting the user know that it
2:00:53has been copied, whereas that's not
2:00:55really true.
2:00:56So, we have to set the error in case
2:00:58something goes wrong. We can very easily
2:01:00do that. I'll just copy this part right
2:01:04here with a try and catch block, and we
2:01:06have to replace everything from await
2:01:09navigator. So, right here, await
2:01:11navigator,
2:01:12replace it with this part right here.
2:01:15And we have to remove these plus signs
2:01:17right here at the start. Okay, great.
2:01:20So, this one has been fixed, and I
2:01:22believe there's another one right here
2:01:24where we have the reload functionality.
2:01:26Reload silently swallows errors, which
2:01:29may cause stale UI state. If the reload
2:01:32function fails due to a network error,
2:01:34the error is not captured, and the UI
2:01:36may show outdated collaborator data
2:01:39after a successful invite or remove,
2:01:41which means that here we also have to
2:01:43check for errors and display them if
2:01:45there are any. So, once again, I'll copy
2:01:48this part and replace the old one.
2:01:51And don't forget to remove the plus
2:01:53signs just to make sure it works.
2:01:55Wonderful. So, two comments in this file
2:01:57resolved. We have one more in the
2:01:59project share dialogue. This is the
2:02:02actual dialogue where we have a key
2:02:04collision when collaborators share the
2:02:06same email or display name. Well, that's
2:02:09not really going to happen, right?
2:02:11Because each one of the collaborators is
2:02:12unique. So, this isn't unlikely, but
2:02:15it's impossible. So, yeah, this one is
2:02:17good. And finally, there is one more
2:02:19under project collaborators where it
2:02:22says batch emails in chunks of 500 to
2:02:25respect Clerk's API limit. Well, yeah,
2:02:28here where we're getting the users, uh
2:02:30we definitely don't want to fetch more
2:02:31than 500 users. This is great regardless
2:02:34of Clerk's limit, but I'm glad that it
2:02:36caught it because you never want to
2:02:38fetch so many emails at the same time.
2:02:41So, in this case, it's not just a copy
2:02:43and paste that couple of lines of code.
2:02:45What we can do here is use our agent to
2:02:48fix it. So, if you click that button,
2:02:50it's going to copy a little prompt and
2:02:53put it into the chat that's going to
2:02:54know exactly which part it has to fix.
2:02:56So, let's give it a second to read it
2:02:58and fix it. And it's done. The get clerk
2:03:01email now batches emails into chunks of
2:03:03500 so that it doesn't go over the
2:03:05limit. But, instead of doing that, what
2:03:08I actually want to do is never fetch
2:03:10more than 500. I mean, that's too much.
2:03:13So, I'll tell it
2:03:14instead of chunking the emails, maybe we
2:03:17just don't have to fetch so many. We can
2:03:19just fetch fewer emails. So, let's see
2:03:21how it handles a bit more vague
2:03:23response. I mean, so far it has been
2:03:25implementing everything so perfectly
2:03:28because our prompts, or our specs,
2:03:31should I say, were so precise. So, it
2:03:33never had any issues.
2:03:35But, yeah, in this case, it simplified
2:03:37it. It's going to call fewer emails.
2:03:39Good. So, we're going to keep this. And
2:03:41now that all the issues have been fixed,
2:03:43we actually fixed these two manually, we
2:03:45can just go ahead and push all of these
2:03:47changes by opening up our terminal and
2:03:50running get add dot get commit {dash} m
2:03:54implement share functionality.
2:03:58And get push.
2:04:00Perfect. Great job.
— Editor: Liveblocks Canvas
2:04:03Now that the access control is done and
2:04:06collaborators and sharing is enabled,
2:04:10we are ready to finally build the
2:04:12canvas. First, click the link down in
2:04:14the description and head over to
2:04:16Liveblocks.
2:04:18If you haven't already, create a new
2:04:20account, create a new project, call it
2:04:23Ghost AI, and choose a region that is
2:04:25closest to you. And the environment can
2:04:27be set to development for now. Once the
2:04:29project is created, select personalized
2:04:32setup
2:04:33and multiplayer. Then, select canvases
2:04:36as we want to focus on workflow
2:04:38diagrams, whiteboards, design tools, or
2:04:40in this case, code architecture. And
2:04:43then for the guide, you can select React
2:04:45Flow with Next.js. That's exactly what
2:04:48we're using. Then, copy the first
2:04:49command that you can see right here, and
2:04:51paste it within your terminal. You can
2:04:53press enter, and here we're installing a
2:04:55couple of things.
2:04:56The Liveblocks client is the core client
2:04:59that manages the WebSocket connection to
2:05:02Liveblocks.
2:05:03Then, there's the Liveblocks React,
2:05:05which contains the React hooks for
2:05:07accessing shared room state, presence,
2:05:10and storage.
2:05:11There's also the Liveblocks React UI,
2:05:13containing the pre-built UI components.
2:05:16There's also Liveblocks React Flow,
2:05:19which is the bridge between Liveblocks
2:05:21and React Flow, so canvases are synced
2:05:24in real time across all the connected
2:05:26users.
2:05:27And finally, the XY Flow is the React
2:05:30Flow itself, which handles the canvas,
2:05:32nodes, edges, and interaction. Once that
2:05:35is done, we can initialize the
2:05:37Liveblocks config file. So, go ahead and
2:05:39copy this command, and paste it into the
2:05:42terminal,
2:05:43and press enter.
2:05:45This will generate a Liveblocks
2:05:47config.ts file at the root of our
2:05:49directory.
2:05:50This is where we define the TypeScript
2:05:52types for the shared room, what the
2:05:54presence will look like, what user data
2:05:56we want to attach to each connected
2:05:58session. You can think of it as the
2:06:00contract that describes everything
2:06:02shared between users in real time.
2:06:05And we'll configure it with AI. But
2:06:07before we do, let's install Liveblocks
2:06:10agent skills. Same pattern as with Clerk
2:06:13and Prisma. We want to allow our agent
2:06:15to be fluent in how Liveblocks works.
2:06:19So, go ahead and copy this command, or
2:06:21follow along with me,
2:06:23and just run
2:06:26npx skills add Liveblocks skills.
2:06:30Say Y to update it, and then select
2:06:33Liveblocks best practices, and we don't
2:06:35need the second part. Press enter.
2:06:38Select Claude or whichever agent you're
2:06:40using,
2:06:41and install it for the project and using
2:06:43Simulink.
2:06:45Perfect. This was quick. Now, I want to
2:06:47split this specific feature
2:06:49implementation into three separate spec
2:06:52files. So, let's take it step by step,
2:06:55and I'll walk you through everything.
2:06:56First, let's dive into feature number
2:06:5810, Liveblocks setup. Here, we want to
2:07:01tell it to set up the real-time
2:07:03collaboration infrastructure using
2:07:06Liveblocks. This file will wire up the
2:07:09entire collaboration infrastructure. The
2:07:11config file will get the presence and
2:07:13user metadata types, and then we'll
2:07:15create a Liveblocks client. Finally,
2:07:18we'll build the off endpoints like API
2:07:21Liveblocks off, so we can verify that
2:07:23people can actually access the room. So,
2:07:25go ahead and open it with either Codex
2:07:28or Claude Code. Tell it to read this
2:07:31file,
2:07:32update the progress,
2:07:34and execute everything as specified, and
2:07:37press enter. Let's give it some time to
2:07:39do all the hard work for us while we
2:07:42remain the architect and monitor exactly
2:07:45what it is doing. And very soon, feature
2:07:4710 is complete. It implemented just
2:07:50three separate files. The Liveblocks
2:07:52config, now with types for the presence
2:07:55and user metadata, the lib Liveblocks
2:07:58file, which allows us to fetch the
2:08:00cached Liveblocks node client, so we can
2:08:03use its instance, and we also have the
2:08:06function that maps out a specific user
2:08:08color. Most importantly, we have the
2:08:10post endpoint that verifies whether the
2:08:12user has been authenticated and whether
2:08:15it has access to the project. If it
2:08:17does, it returns all the data, and we're
2:08:20one step closer to testing it out. But,
2:08:22all of these were just some
2:08:23functionalities that we need to use
2:08:25within our code, but we're not using
2:08:27them yet. So, instead of testing at this
2:08:29point, what I want to do is head over to
2:08:32feature number 11.
2:08:34That's going to be the base canvas.md.
2:08:38So, right here in lesson 11, we want to
2:08:41replace the canvas placeholder with a
2:08:44real LiveBlocks backed ReactFlow canvas.
2:08:48Allowing multiple users to connect to a
2:08:51LiveBlocks room and share the same
2:08:53canvas state. That means that the nodes
2:08:56and edges will sync in real time. And
2:08:59after that, we can basically say that we
2:09:01have a full collaborative canvas
2:09:03application.
2:09:04Not yet polished, but real. We don't
2:09:08want to add any controls yet or custom
2:09:10nodes or edges. We just want to create
2:09:12that canvas.
2:09:14So, let's open this up in a new chat. As
2:09:17usual, I'll tell it to read the file,
2:09:21update the progress,
2:09:23and execute as specified. And I've just
2:09:27went to grab a coffee, and feature 11 is
2:09:30done. So, let's review it. It built out
2:09:32the canvas types, so that our
2:09:34application remains heavily typed.
2:09:37Then, it added the storage part over to
2:09:40the LiveBlocks config.
2:09:41This matters right here because it knows
2:09:43that we're going to be keeping track of
2:09:45the presence of the cursors and whether
2:09:47they're thinking,
2:09:48but also the storage of different nodes
2:09:50and edges and users on the screens.
2:09:52Later on, we're going to fill up these
2:09:54two.
2:09:55using LiveBlocks provider and the room
2:09:57provider, it created the canvas room.
2:10:00And most importantly, the canvas
2:10:02placeholder got replaced with that room.
2:10:05So, what do you say that we go ahead and
2:10:07test it out?
2:10:08Back on localhost:3000, we got
2:10:10connecting to a room, but there's one
2:10:13issue. It says LiveBlocks authentication
2:10:16failed, reason not provided. Maybe
2:10:18because this project was created before
2:10:20our whole LiveBlocks setup. So, what
2:10:22I'll do is delete it, reload the page,
2:10:25and create a new project.
2:10:27I'll call it live blocks live room and
2:10:31create. Unfortunately, we still get the
2:10:34same issue. Thankfully, the error in the
2:10:36terminal gives us a bit more info. It
2:10:39looks like we're missing the live blocks
2:10:40keys. So, if you head over to your
2:10:42project under API keys,
2:10:44you'll be able to see your public key
2:10:46and the secret key. So, go ahead and
2:10:48copy them and add them to your
2:10:50.env.local
2:10:52where we can first specify the live
2:10:54blocks public key as well as the live
2:10:58blocks secret key.
2:11:00With these two keys in place, head back
2:11:02over to your application and reload. And
2:11:05after connecting to room, you'll be able
2:11:07to see the actual canvas. This is the
2:11:10React Flow canvas underneath that you
2:11:12can move through. And there's even a
2:11:14little window that you'll be able to
2:11:16scroll through to very quickly access
2:11:18specific parts of the workspace. This is
2:11:20looking absolutely amazing. We can even
2:11:23expand it by collapsing the left
2:11:24sidebar. Oh, and the right sidebar is
2:11:27collapsible, too. So, now we have a real
2:11:30fully functional React Flow canvas. But,
2:11:34of course, what is the canvas for if we
2:11:36can't add any elements on top of it? So,
2:11:38let's create a little bottom navigation
2:11:41that allows us to add some shapes that
2:11:43are going to act as specific parts of
2:11:45the diagrams within our application.
2:11:47We'll do that by creating a new feature
2:11:50spec called 12 shape panel.md.
2:11:55You can add it right here. This one will
2:11:56be simple.
2:11:58And what this does is it adds a floating
2:12:01toolbar at the bottom of the canvas
2:12:03where users can drag shapes onto the
2:12:06canvas to create notes. Rectangle,
2:12:09diamond shapes, circles, pills,
2:12:11cylinders, and hexagons for databases.
2:12:14And after this, users will actually be
2:12:17able to start creating the architectural
2:12:20diagrams. We want to allow the users to
2:12:22drag a shape, including the shape name
2:12:24and the default size. Then, they can
2:12:27also drag and drop it.
2:12:29And on drop, we need to read the dragged
2:12:31shape payload, convert it to the screen
2:12:34position, create a new node at that
2:12:36position, use an empty label, and the
2:12:39default color. We then want to give each
2:12:41one of these shapes a node ID with their
2:12:44shape name, timestamp, and a counter.
2:12:47And we want to render it.
2:12:49So, this is an exciting one. So, let's
2:12:51get it built. Read the file, update the
2:12:54progress, and execute it exactly as
2:12:56specified. I'm sure there's an easier
2:12:58way for me to just to read the file and
2:13:01not have to repeat this sentence every
2:13:03now and then. I mean, I could have put
2:13:05it at the top of the file and just share
2:13:06the file itself. That would have worked.
2:13:09Uh but yeah, I don't mind it. It's the
2:13:11actual work that you put before that
2:13:14saves you so much time later down the
2:13:16line. This one took a bit longer, but
2:13:18feature 12 is done. It created the
2:13:21canvas node renderer and a floating pill
2:13:23toolbar at the bottom with six draggable
2:13:26shapes. So, if you head back over to
2:13:28your room,
2:13:30take a look at the bottom.
2:13:31You have the rectangle, which you can
2:13:33drag and drop. Hopefully, nope. As I
2:13:37dropped it, it didn't appear on the
2:13:39screen.
2:13:40Let's try with a second one.
2:13:42It looks like I can drag and drop them,
2:13:44but they're not actually showing up on
2:13:46the screen, which makes it a great
2:13:47opportunity to debug it the proper way
2:13:50by specifying in detail the current
2:13:52issue that we're experiencing. So, I
2:13:55took a second to write all of the issues
2:13:57that I believe we currently have with
2:13:59the application. At least that I have on
2:14:01my version of the app. Your agent might
2:14:04have done something different. So, let's
2:14:05go through everything together first,
2:14:07and then you'll be able to tell your
2:14:09agent to fix some issues that you see on
2:14:11your end as well. Let's think of this as
2:14:13a little corrective prompt.
2:14:15So, review the editor canvas
2:14:16implementation and fix the visual
2:14:18issues. The canvas currently looks like
2:14:21it's floating above the background
2:14:23inside a border box. Instead of feeling
2:14:25like a real design canvas. And I want to
2:14:28teach you how we can also add images to
2:14:29the context. So, right here within the
2:14:32context, you can create a new folder
2:14:34called screenshots.
2:14:36And then you can screenshot the entire
2:14:37design and simply drag and drop it in.
2:14:42Of course, I'll rename it to image as I
2:14:44specified right here within the
2:14:46document. That way you can also give
2:14:48your agent some visual feedback.
2:14:51Also, read the current canvas component
2:14:53code in the components editor. And here
2:14:55are a couple of issues to look for.
2:14:57Obviously and first of all, the drag and
2:15:00drop issues, right? The canvas nodes
2:15:02from the node panel cannot be dragged
2:15:05and dropped onto the canvas. We need to
2:15:06investigate the full drag and drop
2:15:09pipeline. We need to confirm that the
2:15:11draggable nodes in the node panel have
2:15:13the correct draggable attribute, that
2:15:15the canvas has the on drag over handler
2:15:19and on drop allowing us to drop them.
2:15:21That the drop handler reads the node
2:15:23type from data transfer, calculates the
2:15:26coordinates, and then actually creates a
2:15:28new node at the drop position.
2:15:30And while we're fixing the drag and drop
2:15:32issues, I also thought we can fix some
2:15:34visual inconsistencies such as the way
2:15:37that the canvas and the left and the
2:15:39right sidebar are positioned. So, the
2:15:41left and the right sidebar should float
2:15:42over the canvas, not push or shrink it.
2:15:45Sidebars must use the fixed position or
2:15:48absolute with a higher Z index and so
2:15:50on. So, after documenting all issues, we
2:15:53want to fix all of the above so that it
2:15:55basically corrects what we specified.
2:15:58So, let's actually open this up right
2:16:00here. We can tell it to read this file
2:16:03and fix all of the listed issues and
2:16:06then run it.
2:16:08So, we've done three things. First, we
2:16:11gave Claude code some file references up
2:16:14front and the screenshots right here.
2:16:16This stops it from wandering around the
2:16:18entire project trying to figure out
2:16:20where things live.
2:16:21We're pointing it exactly where to look.
2:16:23We're listing every issue one by one and
2:16:26mixing some technical hints alongside
2:16:28them.
2:16:29These technical details act as
2:16:31shortcuts. They give the AI the stronger
2:16:34starting point so it can find and fix
2:16:36the root cause faster instead of
2:16:38guessing. And third, at the bottom, we
2:16:40describe exactly what success looks
2:16:42like. The AI now knows not just what's
2:16:45broken, but what done looks like. So,
2:16:48let's see if we can manage to correct
2:16:50itself and fix it. Quickly after reading
2:16:52through some of these files, it has a
2:16:54full picture of all the issues, so it's
2:16:57going to use the to-do right to track
2:16:59the fixes and then implement them. Fix
2:17:02the canvas layout was the first thing,
2:17:04removing the card styling and making it
2:17:06a fill full viewport, fixing the left
2:17:09sidebar not fully hiding when closed,
2:17:11fixing the dotted canvas background
2:17:13visibility, and finally and most
2:17:14importantly, verifying the drag-and-drop
2:17:17pipeline. And see how well we did. Uh
2:17:20it's even thanking us,
2:17:21uh specifying that the core issue is
2:17:23clear from the screenshot of the code.
2:17:26The canvas is boxed inside a padded main
2:17:29with card styling and the layout shrinks
2:17:32the canvas to accommodate sidebars
2:17:34instead of floating over them. So, it's
2:17:36going to fix it very easily.
2:17:38Perfect. Hopefully, this video is
2:17:40teaching you how you can approach
2:17:42developing apps with AI. You are the
2:17:45architect and agent is just a coder. And
2:17:49there we go. All four files have been
2:17:51updated, so let's test it out.
2:17:54Back into the browser, I will reload.
2:17:57You can see that now the canvas spans
2:17:59across left and right and the left and
2:18:02right sidebar appear to be floating on
2:18:04top of it. So, if you want to hide it,
2:18:06it just gets completely hidden away,
2:18:08which is great. And the right sidebar
2:18:11also gets completely hidden away, so we
2:18:14have a complete canvas. This is a big
2:18:16difference from what we had before,
2:18:18because now if you hide the sidebars,
2:18:20you have so much more space to work
2:18:22with. And what happens if we try to drag
2:18:24and drop an element?
2:18:26It looks like that functionality is
2:18:28still not working properly. And I think
2:18:30that's because Claude focused mostly on
2:18:32cosmetic changes and the layout. I don't
2:18:35see too many mentions of the drag and
2:18:38drop functionality being fixed. So, what
2:18:40we can do now is head over into the
2:18:42current issues and specify that we want
2:18:45to fix the drag and drop functionality.
2:18:50So, we're going to remove everything
2:18:52else besides the drag and drop and keep
2:18:55the last part saying nodes can be
2:18:57dragged from the node panel and dropped
2:18:59onto the canvas as correctly. So, now we
2:19:01can say read the current issues file
2:19:04again and fix the remaining issue and
2:19:07press enter.
2:19:09This time, it's all about the drag and
2:19:11drop. And after some thinking, it looks
2:19:14like it found the root cause. The React
2:19:17Flow viewport has pointer events none,
2:19:20and React Flow internally uses D3 zoom
2:19:23or native pointer listeners that call
2:19:25prevent default on pointer down. So,
2:19:27when the shape panel is rendered inside
2:19:30of the React Flow as a panel, those
2:19:32pointer handlers interfere with the
2:19:34browser's drag gesture recognition
2:19:36before Dragster can fire. The standard
2:19:39React Flow drag and drop pattern puts
2:19:41drag sources outside of the React Flow
2:19:44and puts on drop or on drag over on a
2:19:46wrapper div, not on the React Flow
2:19:48component. So, it's going to fix this
2:19:50issue for both the canvas editor and the
2:19:52shape panel. That's it. Note that there
2:19:55is the fix is clean. We specified what
2:19:57was wrong, and the fix was to move the
2:20:00on drag over and on drop from the React
2:20:03Flow component to the outer wrapper. And
2:20:05also to replace the panel position from
2:20:07bottom center, which is React internal
2:20:09panel, with a plain absolutely
2:20:11positioned div.
2:20:12So, let's test it out. Back on localhost
2:20:153000 within a specific project, I'll
2:20:17drag and drop this rectangle, and it
2:20:20still doesn't do it. So, what I'm
2:20:22thinking is maybe we can make use of
2:20:25that Liveblocks agent skills that we
2:20:27installed earlier.
2:20:28I mean, this is the exact situation
2:20:30they're built for. So, in the same
2:20:31context window, let's simply tell the
2:20:34agent to check the Liveblocks best
2:20:37practices and fix the drag and drop
2:20:39flow. Dragging shapes from the shape
2:20:41panel should create new nodes on the
2:20:43canvas. But, it's still not working.
2:20:46Nodes should be draggable from the panel
2:20:49and dropped onto the canvas correctly.
2:20:51So, by telling it to check the
2:20:52Liveblocks best practices, I'm hoping
2:20:54that it's going to consult the
2:20:56Liveblocks agent skill. So, let's give
2:20:58it a shot, and you can see that that is
2:20:59the first thing that it did. Liveblocks
2:21:01best practices. After analyzing it, it
2:21:04came back with the complete picture with
2:21:06three things that are wrong.
2:21:08The storage type declares top-level
2:21:10nodes and edges, but use Liveblocks flow
2:21:13actually stores everything under a
2:21:15nested flow key. Within the canvas room,
2:21:17the initial storage creates top-level
2:21:19nodes and edges, once again, wrong
2:21:21schema. And then the canvas editor add
2:21:23node mutation calls storage. get nodes,
2:21:27which writes to the wrong storage path.
2:21:30Instead, it should use on nodes change
2:21:32with a type of add and then the item
2:21:34we're adding.
2:21:35So, let's wait until it fixes all three,
2:21:38and it says clean. So, let's actually
2:21:40test it out. Back within the browser,
2:21:42I'll reload the page, and I'll try drag
2:21:45and dropping a node once again.
2:21:47And you can see that this time we
2:21:49actually get a a
2:21:50Now, if you try drag and dropping
2:21:52another shape, you'll see that it'll
2:21:54just give you another rectangle. And yet
2:21:56another rectangle of a different size.
2:21:59You're only getting rectangles, and
2:22:01that's okay for now. Because in the next
2:22:04lessons, we're going to turn these
2:22:05rectangles into different shapes. We'll
2:22:08basically make it like a real
2:22:09architecture canvas where you can
2:22:11connect different nodes and give them
2:22:13titles and make them make sense. But
2:22:16thankfully, we fixed this shape drag and
2:22:18dropping feature, which is one of the
2:22:19bigger features in the app. And while
2:22:21fixing it, I wanted to keep the full
2:22:23process of me doing that. So, you're not
2:22:25just watching me copy and paste from the
2:22:27other screen.
2:22:28We're actually debugging this together.
2:22:31And in this case, the solution was to
2:22:33invoke an agent skill. So, whenever
2:22:35you're working with specific tools,
2:22:37always verify that they also have their
2:22:39agent skills, install them, and ask your
2:22:42agent to use them.
2:22:44Okay, great. So, let's actually get
2:22:46these changes pushed by saying get add
2:22:48dot, get commit {dash} m,
2:22:50implement drag and drop canvas
2:22:54functionality,
2:22:55and then run get push. And I'll actually
2:22:57open up a PR for this and get it merged
2:23:00so that in the next lesson, we can focus
2:23:02on adding more canvas features. For
2:23:05these, we already reviewed most of the
2:23:07changes before.
2:23:08So, I won't be reviewing them again. And
2:23:11we have to resolve the conflicts right
2:23:13here within our current issues. And
2:23:15there's a fix with Copilot thing right
2:23:16here, resolve the merge conflicts in
2:23:18this pull request by clearing the
2:23:22current issues.md file.
2:23:25We basically want to make it empty as we
2:23:27said. We don't need to push whatever is
2:23:29in the current issues file. This is also
2:23:31a new feature by GitHub where the
2:23:33Copilot can automatically fix it on our
2:23:35behalf.
2:23:37And once it does, we'll be able to merge
2:23:39it back to the main branch and continue
2:23:41developing. And there we go. It
2:23:42basically cleared out the file, and
2:23:44we're good to merge it. So now, back
2:23:46within the editor, you can just run git
2:23:48pull to pull the latest changes and
2:23:50we're ready to continue with the next
2:23:52feature.
— Editor: Canvas Features
2:23:55In this lesson, the goal is to make the
2:23:57canvas feel like a real product. So,
2:24:01open up your feature specs and go ahead
2:24:04and create another one that's going to
2:24:06be 13 node shape.md.
2:24:09In this lesson, we're going to actually
2:24:11polish and add six features. Each one
2:24:14will have a distinct layer of
2:24:16interaction. So, that by the end, the
2:24:18canvas will have proper shape rendering,
2:24:21node colors, edge behavior, and keyboard
2:24:23shortcuts. Everything that makes a
2:24:25difference between a working prototype
2:24:27and something that genuinely feels
2:24:29polished to use. So, starting with the
2:24:32first one, it's going to be the node
2:24:34shape. It's a fairly simple one where we
2:24:36just want to replace the placeholder
2:24:38node renderer with proper shape
2:24:40rendering and a drag preview.
2:24:43So, instead of a placeholder node shape,
2:24:45which is just a rectangle, we want to
2:24:47render all of these different types of
2:24:48shapes through SVGs.
2:24:51They should scale with the node size and
2:24:53we want to keep the border subtle. We
2:24:54also want to add a shape drag preview
2:24:57while we're dragging.
2:24:59We want to keep the node rendering
2:25:00connected to the existing collaborative
2:25:02canvas state.
2:25:03We don't want to rebuild anything.
2:25:06And when we're done, we want to check
2:25:08that the node renderer renders the
2:25:10correct shape variant for each type.
2:25:13So, you know the drill. Open up your
2:25:15agent and let's ask it to read this
2:25:18file, update the progress, and implement
2:25:20it as specified. Let's give it some time
2:25:23and I'll be right back. And a couple of
2:25:25minutes after, feature 13 is done. It
2:25:28replaced the single style placeholder
2:25:30with a shape aware rendering and also
2:25:33added the cursor tracking ghost preview.
2:25:35I'll show you what that means quickly.
2:25:38But, basically now, you can see that our
2:25:40array of rectangles that we created
2:25:42before is now a bunch of different
2:25:45shapes. And we can actually move across
2:25:47the canvas and drag and drop different
2:25:49shapes. There's a mini map at the bottom
2:25:50as well, which we can later on remove as
2:25:53we don't need it as we'll be mostly
2:25:54working within a single centralized
2:25:56place. But yeah, we now have the shapes
2:25:59and you can actually connect the shapes
2:26:01together. But we still can't change the
2:26:03size of the nodes, right?
2:26:05They are always of the same size and we
2:26:07can't edit their labels. Like currently
2:26:09these are just shapes. They don't have
2:26:11any data associated with them.
2:26:14So what we can do is create another file
2:26:17right here, another feature
2:26:1914 node editing.md.
2:26:23And within it, we essentially want to
2:26:25add resizing and inline label editing to
2:26:28the canvas. So that when we select a
2:26:30specific node, we can resize it and
2:26:32scale them however we need to. And if we
2:26:34double click it, we'll be able to edit
2:26:37its label. So this is the next feature
2:26:39that we're going to develop. I'll tell
2:26:40it to read the file, update the
2:26:43progress,
2:26:45and implement it exactly as specified.
2:26:49And you know what? I will actually copy
2:26:51this part right here. So in the future I
2:26:53can just paste it into the prompt.
2:26:56Okay, and after some wearing and
2:26:59cogitating and thinking, let's see what
2:27:02it'll actually come up with. And as it's
2:27:04close to being done after running a
2:27:06couple of minutes, um near the end, uh
2:27:09when it tries to build and check for
2:27:11errors, it almost always finds some type
2:27:15issues. Like right here, it was
2:27:17complaining about the canvas node. So it
2:27:20expanded the types and made it a bit
2:27:22more type safe. And only once that is
2:27:24done, it actually updates the progress
2:27:26tracker and then finishes with the
2:27:28feature. So it's always great to ask it
2:27:30to test the application and test the
2:27:32output.
2:27:33Okay, great. So the The
2:27:35from XY flow now renders when a node is
2:27:38selected and we can resize dimension
2:27:40changes through live boxes on node
2:27:42change automatically. Also,
2:27:44double-clicking any node opens up a text
2:27:47area.
2:27:48So, let's go ahead and test it out. We
2:27:50now have these labels, which is perfect.
2:27:53But first, let's test the resizing. If I
2:27:56now pull it at any of the borders
2:27:59or even at any of the edges, you can see
2:28:02that this is very precise and would take
2:28:05us some time to implement properly. You
2:28:06can now resize it both horizontally and
2:28:09vertically or if you want to keep the
2:28:11aspect ratio, you can resize it by the
2:28:13edges. This is absolutely amazing.
2:28:17And let's also test changing the label
2:28:19by double-clicking right here and typing
2:28:21something like start
2:28:23of the app
2:28:25and leaving it and you can see that it
2:28:27looks good. It quickly brings the text
2:28:29up in case you want to type more stuff
2:28:31into it.
2:28:32But
2:28:33but we can fix that later on because the
2:28:35text will mostly always stay centered.
2:28:38So, if we pull some kind of a label, we
2:28:40can now mark this as a post grass DB,
2:28:43for example, and it's going to fall down
2:28:46right here and we can now connect the
2:28:48application that way. Perfect. Let's
2:28:50just quickly tell it that even though
2:28:52while we're typing into the field, we
2:28:54want the text to stay in the middle.
2:28:56Right now, it jumps to the start. So,
2:28:59I'll take a quick screenshot while I am
2:29:01typing
2:29:03and drag and drop it into the chat and
2:29:05tell it while the label editing is
2:29:07focused, the text appears at the top of
2:29:10the element instead of in the middle
2:29:12where it falls back to when not focused
2:29:15any longer.
2:29:16Make it so that when we're typing, we're
2:29:18also typing within the middle of the
2:29:20element. Hopefully, this should be clear
2:29:22enough for a little quick corrective fix
2:29:25and very quickly, the build passes. It
2:29:27just swapped the text area for the
2:29:29content editable div. So, now if we head
2:29:32back right here and start typing, you
2:29:34can see that it remains in the middle
2:29:36and you can say start, here we can say
2:29:40label and as soon as we start typing, it
2:29:43basically centers itself right in the
2:29:46middle.
2:29:47Wonderful. So, now that we can actually
2:29:49type within these inputs, what do you
2:29:51say that we implement the functionality
2:29:53to also be able to differentiate them by
2:29:56color? So, create a new feature spec
2:29:58called 15 nodes color toolbar.md
2:30:03and here we want to add a floating color
2:30:05toolbar that appears when a node is
2:30:08selected. We then want to allow the user
2:30:10to pick a color and both the node
2:30:13background and text color will update
2:30:15instantly synced across all
2:30:17collaborators in real-time. So, it'll
2:30:20check our UI context for the node color
2:30:22palette, include a background color as
2:30:25well as a matching text color so that
2:30:27the contrast is nice
2:30:28and we want to reuse existing colors if
2:30:31they exist in globals, otherwise it can
2:30:33keep the palette in the canvas types.
2:30:35Then we want to add a toolbar
2:30:38above the selected node that'll only
2:30:40show when the node is selected.
2:30:43And when a color is selected, we want to
2:30:45update the node background color and
2:30:47text color. I think this is pretty
2:30:49understandable.
2:30:51So, let's actually build it by telling
2:30:53it our usual secret sentence which is
2:30:56super simple, but again, the spec file
2:30:58does the heavy lifting. This one for
2:31:00some reason was super quick. It added
2:31:03the text color, and the ability to
2:31:05choose between a predefined set of
2:31:07colors and also the color background.
2:31:10So, if we head back over here and select
2:31:12an element, would you look at that? I
2:31:14mean, this is really starting to come
2:31:15into life and I'm not sure how much time
2:31:18this would take me to manually code it
2:31:20out. But this way, we just have to think
2:31:23of an idea, write a fairly simple yet
2:31:26descriptive uh spec file, and then it
2:31:29does it instantly. And
2:31:32take a look.
2:31:33You can actually select the colors of
2:31:35each one of the nodes
2:31:38separately.
2:31:39So, if I make this one purple, the
2:31:41database, for example, can be green.
2:31:44Uh yep, for example, this one can be
2:31:46MongoDB in its classic green color.
2:31:49Then, we can have some kind of an
2:31:50algorithm right here going on, which can
2:31:53be orange. And yeah, it is all working
2:31:56wonderfully. So, on top of the colors,
2:31:59what else is there to implement?
2:32:00Currently, there's these custom edges
2:32:02from all four sides. But no matter which
2:32:05you select, it's always going to start
2:32:06making a connection from the bottom, as
2:32:08you can see right here.
2:32:09And it'll connect it to the bottom or
2:32:11the top as well. So, we want to fix that
2:32:13edge behavior.
2:32:15So, I'll create a new spec just for
2:32:17that, and you can see how I'm being very
2:32:19specific with the specs, only focusing
2:32:22on the smallest thing possible. So, 16
2:32:26edge behavior
2:32:28.md. And the goal of this spec is to
2:32:31just replace the default canvas edges
2:32:33with custom edges that feel easier to
2:32:36follow, easier to click, and support
2:32:39inline labels. So, we want to add
2:32:41connection handles to every node at the
2:32:43top, right, bottom, and left sides, and
2:32:45user should be able to connect from any
2:32:47handle to any other handle. Oh, and
2:32:50another thing you can notice is that
2:32:52we're opening up a new chat for every
2:32:55single one of these specs. This helps us
2:32:57with lowering the overall context window
2:33:00and the amount of tokens that we're
2:33:02spending for each transaction, and it
2:33:04also makes the agent that much more
2:33:06focused on the task at hand. And all
2:33:09these little things together, like the
2:33:11separate feature spec.md files instead
2:33:13of just regular messages,
2:33:15or the current issues file, where we can
2:33:17specify what's wrong, and these six
2:33:20files that altogether make the system
2:33:22work, just makes it a breeze to work
2:33:24with an AI, which is typically not the
2:33:26case because it starts imagining things
2:33:29and creating stuff that we don't want or
2:33:30breaking other things. But this way, it
2:33:33is super predictable. And if you take a
2:33:35look at the progress tracker, which we
2:33:38haven't looked at in a long time now,
2:33:40you'll see that everything we've done so
2:33:42far is written right here. But what I
2:33:45care more about is that it even kept
2:33:48some additional architectural decision
2:33:50so that it knows what we're doing. And
2:33:52the session notes for specifically what
2:33:54it thought is very important. Like the
2:33:56specific versions of different tools
2:33:58that we're using, the fact that the
2:33:59Prisma config uses Prisma forward slash
2:34:02and not some other path, and that it
2:34:04reads the database URL from the .env.
2:34:07It kept everything it needs and
2:34:09everything that future sessions with AI
2:34:11or other developers continuing to
2:34:13develop this project need to do it in a
2:34:16sane and happy way. And another build
2:34:19passes.
2:34:20It implemented the canvas edge data
2:34:22pieces, allowing us to connect the
2:34:24source and the end.
2:34:27So if you head back over here, you would
2:34:29see that we have plenty of different
2:34:30lines happening. But now, if you select
2:34:32it from the left side, you can see how
2:34:34it creates a bit of a different kind of
2:34:36connection, and you can now connect it
2:34:38to another element. This line looks a
2:34:40bit different from other more
2:34:42organic-looking lines. Oh, and right
2:34:44now, we cannot even delete the elements.
2:34:46So we have plenty of elements on the
2:34:48screen.
2:34:49But I'll create a new one right here and
2:34:51another database on the right.
2:34:54Drag [snorts] the canvas so we can see
2:34:56them right here. Now the mini map starts
2:34:58making a bit more sense, as you can see.
2:35:00But if you try to connect it, you can
2:35:02now do that in a bit of a more
2:35:04consistent and better way.
2:35:06Now that the canvas is looking so good,
2:35:08I guess we can focus on little
2:35:10quality-of-life fixes.
2:35:12Like under feature specs, we're going to
2:35:14do 17 canvas ergonomics.md.
2:35:18And within it we can focus on adding a
2:35:20little floating control bar for zooming
2:35:23in and out and undoing or redoing and
2:35:26then wiring those actions to keyboard as
2:35:28well. So if you press plus or equal it's
2:35:30going to zoom in minus to zoom out
2:35:33command or control Z or Y to undo and
2:35:36redo and I also don't like that mini map
2:35:39at the bottom right. I don't think our
2:35:41app will need it. So simply say remove
2:35:44the mini map at the bottom right.
2:35:47It's just a personal preference. But
2:35:49yeah, this is that finishing layer that
2:35:51makes the canvas feel like a tool that
2:35:53somebody would actually use every day.
2:35:54So let's implement this one as well and
2:35:57very quickly feature 17 is done as well.
2:36:00We now have a new pill shaped floating
2:36:02bar at the bottom left for zooming in
2:36:05and out or fitting within the view. So
2:36:07if we collapse the sidebars, you'll be
2:36:09able to see it right here at the bottom.
2:36:11Ooh, it it actually looks very nice and
2:36:14if I move some things around, you can
2:36:16see that the left arrow or the undo
2:36:19actually starts shining and you can undo
2:36:22previous actions or redo them. You can
2:36:25also use command or control Z or Y to
2:36:28actually go back and forth, which is
2:36:30absolutely amazing. There's this fit in
2:36:32view, which is going to fit all the
2:36:34elements within the view. So if you're
2:36:36working on something that is a bit
2:36:37closer together, it's going to actually
2:36:39increase the view, which is always nice.
2:36:41And you can zoom in or you can zoom out,
2:36:43but that's hidden by the next JS logo,
2:36:46but that's only in development.
2:36:48So these little things really do make a
2:36:50big difference. And finally, I had an
2:36:52idea that isn't even necessary, but I
2:36:55want to make it work.
2:36:57So let's create a new feature spec 18
2:37:00starter template.md.
2:37:03The goal of this one is to create a
2:37:05template library that lets users start
2:37:08from a pre-built diagram instead of
2:37:11blank canvases. So we can start with
2:37:13some ideas, like let's say microservices
2:37:17or a CICD pipeline or an event-driven
2:37:19system. Each one will load directly into
2:37:22the shared canvas. That's going to make
2:37:24it easier for users to figure out how to
2:37:26actually use the app and what are the
2:37:28different use cases. Because maybe
2:37:30somebody's not going to even get the
2:37:31idea of what they can use this app for.
2:37:34So, the templates should give them a
2:37:35pretty good idea. This one took a bit
2:37:37longer, but feature 18 is done. We now
2:37:40have the starter templates file.
2:37:43So, if you head over here and collapse
2:37:46everything, maybe we should start with a
2:37:48blank canvas, but we can keep doing it
2:37:51within our mess right here. Uh at the
2:37:53top right, there's the templates button.
2:37:56And if you click it, you'll see a couple
2:37:58of different starter templates, which
2:38:00aren't looking that good right now. They
2:38:02look a bit too vertical, but yeah, as
2:38:05soon as you click import, it'll actually
2:38:07import a lot of stuff pre-labeled. Uh
2:38:10but it may be better to do that within a
2:38:12new project, so we can see it a bit
2:38:13better. So, I'll call it CICD
2:38:16and create a new project.
2:38:19And then we'll start from a template by
2:38:21going with maybe the CICD pipeline or
2:38:24let's go with microservices.
2:38:27And you can take a look that that
2:38:29immediately tells you a bit about how we
2:38:31can start structuring the application
2:38:33infrastructure. But yeah, this right now
2:38:35is looking a bit too vertical. The
2:38:38design is not actually being displayed.
2:38:40So, we want to take this screenshot and
2:38:42we want to feed it into the chat by
2:38:44holding the shift key and then drag and
2:38:46dropping. And then we want to compare
2:38:47that with how it looks like on the
2:38:49finished product.
2:38:50Where we can clearly see the full
2:38:53template before we drag and drop it. So,
2:38:55I'll also put that here.
2:38:57And I'll say
2:38:59I'll pass in the screenshot of how the
2:39:02templates look right now. They look a
2:39:04bit too vertical and the actual elements
2:39:07are not clearly visible. Whereas, on
2:39:10another screenshot, which is the
2:39:11finished product, you can see how each
2:39:13card gets its own space, and they show
2:39:16the entire diagram or the entire
2:39:18template that's going to be entered in
2:39:20once clicked. Make it look more similar
2:39:22to that. So, once again, this is a
2:39:24little corrective prompt with two
2:39:26different screenshots, one before and
2:39:28one after. Let's see how well it does.
2:39:31So, currently, it looks like this. So,
2:39:33I'm going to snap my fingers, and we
2:39:35come down to something that looks like
2:39:37this.
2:39:38Definitely not good.
2:39:40Even though now it's a bit better, as
2:39:42it's showing the full template that's
2:39:43going to be inserted in, but I'm not
2:39:46quite sure why it cannot get the widths
2:39:49right. So, we could be a bit more
2:39:50specific and tell it to increase the
2:39:52width of the whole import template card,
2:39:54and then to increase the width of the
2:39:56cards within it, so that they are the
2:39:58same as this.
2:40:00And that's what I'll tell it for the
2:40:01second time
2:40:03by saying, "Increase the width of the
2:40:05entire import template overlay, and then
2:40:08increase the width of the card within it
2:40:10as well, so it looks the same as on the
2:40:12final screenshot." And I'm purposefully
2:40:15being not as descriptive right here as
2:40:17we are within our spec files, because I
2:40:19want to show you how
2:40:21it can cost you a lot by not being
2:40:23specific from the start. A lot of
2:40:25back-and-forth messages, conversations,
2:40:27and corrective fixes. And very soon, the
2:40:30fix is in, and now it actually looks
2:40:32amazing. It increased the width, and
2:40:34it's much clearer what it's actually
2:40:36going to insert in. So, that means that
2:40:37we now also officially have the
2:40:40templates. So, we implemented a lot of
2:40:43stuff across these five separate feature
2:40:45specs, 18 files changed in total, which
2:40:48means a lot of stuff to review. This
2:40:49actually deserves its own pull request.
2:40:51So, let's go ahead and push the changes
2:40:53either via terminal or via the source
2:40:55control right here. It's going to
2:40:57auto-craft a nice message, such as
2:40:59improve node shape, added resizing and
2:41:01inline labels, and adding color
2:41:04toolbars.
2:41:05Then, once you commit and sync, back on
2:41:08GitHub, you'll see that the development
2:41:09branch had recent pushes 7 seconds ago.
2:41:13So, let's just open up a new pull
2:41:15request, and let's wait Code Rabbit to
2:41:17provide us with a review. I think this
2:41:19walkthrough will be particularly useful,
2:41:21as we've covered many different
2:41:22features.
2:41:24So, this PR implements a comprehensive
2:41:26canvas editing feature set, including
2:41:28custom edge rendering with inline
2:41:30labels, node resizing with color
2:41:33toolbars, keyboard shortcuts for zooming
2:41:35and undoing and redoing, a canvas
2:41:37control bar, and a starter template
2:41:40system with a modal dialogue and
2:41:42SVG-based previews. We've made changes
2:41:45across 19 different files, and here is
2:41:48the sequence diagram. The user clicks
2:41:50the templates button. It opens the
2:41:52editor navbar, and then we open up the
2:41:54templates. We display the templates with
2:41:57card previews. The user imports them,
2:42:00and then once they do, we pass the
2:42:02pending template prop. Then, we forward
2:42:05that pending template to the canvas
2:42:07editor. Live Blocks clears the existing
2:42:09nodes or edges, adds the templates,
2:42:12syncs this new graph to the canvas,
2:42:14triggers the fit view automatically, and
2:42:17then once it's imported, we bring it
2:42:19back to the user.
2:42:20This is a nice diagram on the starter
2:42:22templates feature. This was a complex
2:42:24PR, so I'm assuming there's going to be
2:42:27a lot of different things we can fix.
2:42:29The first one points out to exposing an
2:42:31accessible name on these icon-only
2:42:34buttons.
2:42:35Because title is not a dependable
2:42:37accessible label. It's telling us to add
2:42:40the area label title for the zoom, fit,
2:42:43undo, redo buttons for people who are
2:42:46using some assistive technologies. So,
2:42:47if I head back over here and hover over
2:42:50them, it will tell me that this is an
2:42:52undo. But, for people with screen
2:42:54readers, this is not going to do it, and
2:42:56they will have no idea what these
2:42:58buttons are all about. I mean, it's rare
2:43:01that these types of people would be
2:43:02using this kind of an application
2:43:04anyway, but it's always great to
2:43:06implement anything you're doing with
2:43:08best practices in mind. So, in this
2:43:09case, CodeRabbit provided me with a
2:43:11prompt for AI agents to use, so we can
2:43:14add those area labels. After that,
2:43:15there's a major potential issue on
2:43:17avoiding double committing the edge
2:43:20label on enter or escape. So, when
2:43:22pressing enter or escape, it'll call the
2:43:25commit edit immediately, then the
2:43:27input's on blur handler will call it
2:43:29again when the element loses focus. This
2:43:31sends two mutations through live blocks,
2:43:33creating duplicate undo retries for a
2:43:36single edit. That's interesting. So, the
2:43:38fix is simply to call the prevent
2:43:40default and prevent blur instead of
2:43:43commit edit. So, let's find that. I'll
2:43:45find where this commit edit is getting
2:43:47called.
2:43:48It is right here in canvas edge
2:43:50component. Specifically, we're looking
2:43:52at it on the enter key or the escape
2:43:55key. So, right here, and instead of
2:43:58that, we want to apply those two lines,
2:44:01prevent blur and
2:44:03prevent default.
2:44:05And we also want to remove the commit
2:44:07edit from the dependency array. After
2:44:09that, there's a minor issue to move ref
2:44:12updates into a sync effect to comply
2:44:14with React 19 ref values. So, basically,
2:44:17it's telling us to put it within a use
2:44:19effect. That's super simple. Let's just
2:44:21find where in the code this is.
2:44:23And after that, you can see that even
2:44:25it's complaining right here about the
2:44:26code, but we didn't take a look at it
2:44:28because we weren't looking at the actual
2:44:30code.
2:44:31It's always good to use the best
2:44:33practices. So, we just need a use effect
2:44:36right here below that's going to put all
2:44:38of these node refs into edges and nodes
2:44:41like this.
2:44:42And everybody's happy. After that, we're
2:44:45missing the enter key for handling the
2:44:46label commit. So, if it says save the
2:44:49label on blur, enter, or escape, but on
2:44:53handle key down, we only add escape. So,
2:44:55right here, we also have to add the
2:44:58enter key. I think we searched for this
2:45:00across the code base just before. That
2:45:02was right here in the canvas edge.
2:45:05But, not this instance, the instance
2:45:07where we just had the escape. So, I'll
2:45:09search for escape.
2:45:11And it's right here. We have to replace
2:45:13it with if key is escape or if is key is
2:45:16enter. And once again, for you, these
2:45:19suggestions might be completely
2:45:20different from mine. And that's normal
2:45:22because agentic development is not
2:45:24predictable. So, for me, it's adding
2:45:26some accessibility names or guarding
2:45:28some colors. For you, it's going to be
2:45:30something else. But, I always love
2:45:32learning about everything that Code
2:45:34Rabbit throws at me because the next
2:45:35time, I can immediately do it better. Or
2:45:37in this case, we can make our feature
2:45:39specs more specific so that our agents
2:45:42don't make these mistakes in the first
2:45:44place. Oh, and this one is interesting.
2:45:46When trying to undo or redo, it's
2:45:48currently looking only at a lower letter
2:45:50Z, but in case somebody has caps lock
2:45:52turned on, we also want it to take a
2:45:54look at the upper case Z.
2:45:56This is interesting. So, let's go ahead
2:45:58and push those Code Rabbit suggested
2:46:00fixes.
2:46:02All of them were within the canvas.
2:46:06And then, once they're up, we can simply
2:46:09go ahead and merge this PR.
2:46:12Amazing job on adding all of these
2:46:14little improvements that make our canvas
2:46:17that much more interactive.
— Avatar Feature, Chat Sidebar UI & Auto Save
2:46:21In this chapter, we'll add the
2:46:23collaborators avatar. So, when somebody
2:46:25else is moving something on the screen,
2:46:27you can see who is messing with you.
2:46:29We'll also complete the chat sidebar UI
2:46:32and implement a canvas auto save, which
2:46:35will save our canvas data to a Vercel
2:46:37blob. So, when you create something, you
2:46:40don't lose it. And I'll break down all
2:46:42of these within three separate feature
2:46:45spec files. Each one is independent, but
2:46:47together they complete the collaborative
2:46:50experience before AI generation enters
2:46:53the picture. So, back within our
2:46:54application, create a new 19 presence
2:46:59avatars cursors.md
2:47:02chat. The goal of this spec is to
2:47:04implement presence avatars and cursors.
2:47:08Because right now, multiple users can
2:47:10edit the same canvas, but they can't see
2:47:13each other.
2:47:14And after this, every connected
2:47:16collaborator will appear as a live
2:47:19cursor directly on the canvas with their
2:47:22name and color. So, we want to render
2:47:25those collaborator avatars and add those
2:47:27cursors to the canvas. And also, on the
2:47:30top right, we can show a stacked group
2:47:33of avatars where we can see everyone in
2:47:35the room at one place. So, you know the
2:47:38drill. Let's get it implemented. And the
2:47:40presence feature has now been
2:47:42implemented. There's a new component
2:47:44rendered inside of the canvas wrapper
2:47:46called the presence cursor as well as
2:47:49the collaborators avatars. So, if you
2:47:51head back over to a specific project, we
2:47:53can stay within the CICD, open up this
2:47:56project within a second tab. And check
2:47:58this out. Right here on top, you should
2:48:00see that Adrian is also within this
2:48:02canvas. But in this case, both of us are
2:48:04Adrians. So, you can see another
2:48:06person's cursor. And if I put these tabs
2:48:09side by side, take a look at this.
2:48:12Using LiveBlocks, while one user is
2:48:15doing something, all the other users in
2:48:17the room can see exactly what they're
2:48:19doing. So, this is a great first step
2:48:22toward interactivity, but we can do
2:48:24more.
2:48:25Head over and create a new feature spec,
2:48:28or just get it from the zip, called 20
2:48:30AI sidebar.md.
2:48:34The goal of this one is to create a
2:48:37sidebar placeholder that'll later on get
2:48:39replaced with a proper UI.
2:48:42Two tabs, the AI architect and the
2:48:45specs. The AI architect tab will have a
2:48:48scrollable chat area, starter prompt
2:48:50chips, message bubbles, and a composer
2:48:53input. While the specs tab will have a
2:48:56generate button and a placeholder spec
2:48:58card.
2:49:00No back-end logic yet, just the UI that
2:49:02everything else will connect to.
2:49:04So, let's run it and let's see how it
2:49:07does. And after a couple of minutes, it
2:49:09is done. In this case, it touched only
2:49:12the AI sidebar, which is a new
2:49:14standalone component, as well as the
2:49:16editor workspace client, where it
2:49:18replaced the old aside with this new AI
2:49:21sidebar. So, now we got a new sidebar
2:49:24that looks something like this. You can
2:49:26see it on the right side. There's the AI
2:49:29architect, which soon enough will allow
2:49:31you to design full workflows and systems
2:49:34within the canvas, which looks like a
2:49:36chat because you're speaking with an
2:49:38agent. And then there's a specs tab,
2:49:41which allows you to generate a specific
2:49:43specification and then later on download
2:49:45it. This is just the UI for now, but
2:49:47soon enough this will become real. You
2:49:49can also type multiple messages by
2:49:51pressing shift and then enter, which is
2:49:54useful for those longer prompt. Oh, and
2:49:56before we run the next spec, which is
2:49:58all about auto-saving what we're
2:50:00currently working on,
2:50:01we need to set up Vercel blob storage.
2:50:04Vercel blob is basically an object
2:50:06storage service. Think of it like a
2:50:09cloud drive for your app. So, instead of
2:50:11storing large files directly in your
2:50:13database, you upload them to the blob
2:50:16and store only the URL reference in
2:50:18Prisma.
2:50:19We're using it for two different things
2:50:21in Ghost AI.
2:50:22Canvas snapshots as saved JSON and
2:50:25generated specs that will be available
2:50:27for download later on through markdown.
2:50:30It's completely free to get started
2:50:32with. You can just head over to
2:50:33vercel.com. Then on the left side, click
2:50:36storage and create a new database.
2:50:38Choose blob for store name, enter
2:50:41something like ghost AI, and we'll set
2:50:43it to private because private storage
2:50:45means that your blob URLs require a
2:50:48token to access.
2:50:49Without this, anyone who gets a hold of
2:50:52your blob URL can read the file
2:50:53directly, which means the canvas data
2:50:56and generated specs would be publicly
2:50:58readable. But private storage keeps
2:51:00everything scoped to your application
2:51:02only.
2:51:04So, create it. And once it is created,
2:51:07click on it, and then right here you'll
2:51:09be able to find your .env.local, where
2:51:11you can just copy the snippet. Then
2:51:13within your .env.local,
2:51:15you can simply add this new .env, blob
2:51:18read write token, and we are ready to
2:51:21execute our next feature spec
2:51:23by adding a file called
2:51:2521_canvas_autosave.md.
2:51:30Before we add that AI generation, canvas
2:51:33state needs to persist.
2:51:35So, after this, every change to the
2:51:37canvas is automatically saved to Vercel
2:51:39blob, which we just set up. And then
2:51:41we'll save the blob URL, store it in
2:51:43Prisma, and our canvas data will be
2:51:46saved securely. So, this spec is all
2:51:48about saving the current canvas state.
2:51:51It should be pretty simple for our agent
2:51:53to implement, given that we gave it all
2:51:55the information about the Vercel blob
2:51:57and about the environment variable that
2:51:59it can use to save data. And after a
2:52:01couple of minutes, it is done. A Vercel
2:52:04blob is installed, and blob read write
2:52:07token is already present in the
2:52:09.env.local.
2:52:10But if we get back to the editor, at
2:52:12least on my end, there seems to be a
2:52:14bug. So, let me show you how I would
2:52:16approach fixing it. To fix this issue,
2:52:19you can just copy it, head back over to
2:52:21our application,
2:52:23specifically into the current issues.md,
2:52:27and specify that this is the current
2:52:31error that I'm seeing on the screen. And
2:52:35you can paste it right here. It's going
2:52:36to give it all the necessary
2:52:37information, and you can say
2:52:40fix it.
2:52:42So, make sure that it has access to the
2:52:44current issues, and say
2:52:46fix it. Or you could have just pasted
2:52:48that into the chat itself.
2:52:50For just a singular issue like this, it
2:52:52should be totally okay. And as it's
2:52:54working through it, I can already see
2:52:55what was the issue. Even though we ran
2:52:57MPX Prisma generate and migrate, the
2:53:00generated client is correct, but there's
2:53:03a stale .next cache bundling the old
2:53:06schema. So, it's going to clear the old
2:53:08cache and then delete it, which forces
2:53:10Turbopack to recompile from a freshly
2:53:13generated Prisma client, which already
2:53:15has the canvas blob URL. Then, we just
2:53:17have to restart our server, and the
2:53:19error will be gone. So, open up your
2:53:21terminal and just to rerun it by saying
2:53:23npm run dev. And then back on
2:53:25localhost:3000, if you reload, the error
2:53:28will be gone. So, now we are ready to
2:53:30test that final feature we implemented.
2:53:33Head over to one of the active projects,
2:53:35add a couple of nodes right here,
2:53:37refresh the page, and make sure it
2:53:39remains at the exact space where you
2:53:40left it at.
2:53:41So, I'll try to change its name and move
2:53:44it below the auth service, and just make
2:53:46sure that it actually remains there,
2:53:47which would mean that the auto save is
2:53:49working.
2:53:51Perfect. But even though it saved it, it
2:53:53wasn't saved through a blob, because if
2:53:55you head over to the Ghost AI blob
2:53:57storage, you'll see that there is
2:53:59absolutely nothing there. So, let's fix
2:54:01this issue and uh couple others.
2:54:04I went through the application, tested
2:54:06it deeply, and came up with a couple of
2:54:08different issues that I added in the
2:54:10current issues.md file. You should
2:54:13already have a file like this in your
2:54:15zip, which you can just refer to. But
2:54:17again, it's possible that for you,
2:54:19you'll have to go through the app
2:54:20yourself, try to find some specific
2:54:22issues within your app, and then write
2:54:24the prompt to fix them.
2:54:26But yeah, in this case, let's go over
2:54:28the most important one, which is the
2:54:30saving button in the workspace navbar.
2:54:33Uh the workspace navbar is missing a
2:54:35save button. The auto save hook exists,
2:54:38but we need to wire the button to it. It
2:54:41should default to save. While saving, it
2:54:43should switch over to saving. After
2:54:45successful save, it should switch over
2:54:47to save. And that's it. The most
2:54:50important thing that I noticed right
2:54:51here is in this canvas route.ts
2:54:55file. If you take a look at the blob
2:54:58storage, the access is set to public.
2:55:01And remember what I said before, it has
2:55:03to be set to private because we don't
2:55:05want other people to be able to view the
2:55:07changes we make within the canvas. So,
2:55:09we need to set it to private. And we're
2:55:11not asking it to change anything else
2:55:13for now. For now, I just want to focus
2:55:15on issue one and then later on go
2:55:17through all of the other issues. So,
2:55:19yeah, let's ask it to fix the save
2:55:21button first. I'll open up a new chat
2:55:24and tell it to read the current issue.md
2:55:27file, check the issue one and fix it.
2:55:29After fixing it, mark it as pending to
2:55:31test. Let's run it and see how well it
2:55:34does. And very quickly, issue one is
2:55:36fully implemented and marked as pending
2:55:39for tests. So, let's go ahead and test
2:55:41it out. You can see the save button
2:55:43right here. And as you move something on
2:55:45the screen and change its color and
2:55:47click save, you can see that the saving
2:55:50indicator is not really changing. So,
2:55:52you can quickly head over to the code
2:55:53base, see there's no errors in the
2:55:55terminal, but it's still not doing its
2:55:57thing. So, let's send it one quick
2:55:59corrective prompt by telling it that
2:56:02issue one still hasn't been fully fixed.
2:56:05The saving status indicator is not being
2:56:07changed, and when I click the save
2:56:09button, nothing is changing either.
2:56:11Analyze the issue further and fix it.
2:56:14And we can send it out. And it was doing
2:56:16a bit of thinking, but it came back with
2:56:18the response that the root cause was
2:56:20React strict mode, Enabled by default in
2:56:23next js app router. It double invokes
2:56:26effects in development. So on initial
2:56:28mount, the cleanup is intentionally
2:56:30fired by the strict mode before remount,
2:56:33and then remount effect only registers
2:56:35the cleanup again, but it never resets.
2:56:38So that means that the is mounted is
2:56:40permanently set to false in development.
2:56:42So the save exited every time. This fix
2:56:45removes the is mounted ref entirely. Uh
2:56:48because react 18 made this guard
2:56:50unnecessary. Perfect. So if we head back
2:56:52right here and reload, and if you move
2:56:55something around, and then press save at
2:56:58the top right,
2:57:00you can see that it says saving, and
2:57:02then we get an error. Save failed.
2:57:05If I move it again a couple of times and
2:57:07click save again,
2:57:08it says save failed.
2:57:11It's funny because when I tested it
2:57:12initially, it actually said saving and
2:57:15then saved. So back in the blob storage,
2:57:18you can see that I have this new canvas,
2:57:20and it actually stored the JSON data.
2:57:22But then when I tried doing it again, it
2:57:25failed. So if you open up the terminal,
2:57:27you'll see that we have an error with
2:57:29the vercel blob saying that the blob
2:57:31already exists, so we should add allow
2:57:34override is set to true if we want to
2:57:37override it. So what we can do is simply
2:57:39find this file. That's the canvas root.
2:57:42So you can go over to canvas root, and
2:57:46just add an additional prop of allow
2:57:49override is set to true. This single
2:57:51line should immediately allow us to save
2:57:55every other time,
2:57:57not just the first time. You can see it
2:58:00says saved, and back when I did the
2:58:01blob, you can see that it got modified
2:58:04less than a minute ago, which means that
2:58:06it is consistently saving data. And
2:58:08right now the size of this JSON file is
2:58:116.11 kilobytes, but if we head back and
2:58:13add a couple more labels and models and
2:58:17shapes and some pieces of text within
2:58:19them and then save again and then head
2:58:22to the blob. You can see that now the
2:58:24size increased, which means that it is
2:58:26properly saving all the data. So, now
2:58:29that our issue one has been fixed right
2:58:31here,
2:58:32uh we can ask it to continue fixing the
2:58:34other issues.
2:58:35Uh specifically, some issues that are
2:58:37found are about deleting nodes and
2:58:40edges. Uh so, right now we don't have a
2:58:43way to delete anything. So, if I try
2:58:46pressing backspace or the delete keys or
2:58:48anything, I can't do it. So, the
2:58:51solution is simple. We just need to add
2:58:53a key down event listener to the canvas
2:58:56that listens for the delete and
2:58:57backspace keys, doesn't fire when the
2:59:00event target is an input or text area,
2:59:02but just gets fired when we're selecting
2:59:05specific nodes, and then it deletes
2:59:07them.
2:59:08As the third issue, uh currently, we can
2:59:10also just connect from top to the top of
2:59:15the handle. You can see, even if I drag
2:59:17from the right handle, it connects from
2:59:19top to top. We want to be able to drag
2:59:22and drop from any side of the element to
2:59:25any other side. That will allow us to
2:59:27make these graphics so much more
2:59:29customizable.
2:59:30So, we just want to allow it to be
2:59:32connected from top, right, bottom, and
2:59:35left.
2:59:36Then, as the fourth issue, I noticed
2:59:38that when I drag and drop specific
2:59:40elements, they drop a bit lower than
2:59:43where they're supposed to. So, I just
2:59:45wanted to fix that offset.
2:59:48And notice when I dragged and dropped
2:59:50it, it actually said saving at the
2:59:52bottom left right here and then saved,
2:59:54which means that the auto save
2:59:55functionality is working. And then, the
2:59:58fifth issue is the auto zoom on first
3:00:01node. We want to read the Liveblocks
3:00:03agent skills before implementing it, and
3:00:05then as soon as we drop it, we want to
3:00:06zoom it in. And for six and seven, we
3:00:09want to check the clerk skills, um and
3:00:11we have some issues with the
3:00:12collaborator avatar images. If the image
3:00:15is coming from Clerk, we want to add it
3:00:17to next config, so it gets properly
3:00:19shown. And also, currently, we have two
3:00:23different Clerk buttons. We just want to
3:00:25keep the one and remove the other one.
3:00:27So, let's ask it to fix the errors from
3:00:30two all the way to seven. Okay, now that
3:00:33the issue one in the current issues.md
3:00:36file has been fixed, uh analyze and fix
3:00:39issues from two to seven. Press enter.
3:00:43And let's see how well it does. Okay, it
3:00:45looks like all the remaining issues have
3:00:48been implemented, and uh it marked them
3:00:51for pending test, uh which is a good
3:00:53thing for us to test right now. We have
3:00:55to test deleting the nodes and edges,
3:00:58um handling uh the positions or the
3:01:01lines in between the elements, drop
3:01:04position offset, auto zooming, and then
3:01:06Clerk images.
3:01:08So, if I head back right here and reload
3:01:11the page, let's first test the drag and
3:01:13drop. So, if I try to drag and drop it
3:01:15here, you can see that it gets placed at
3:01:17the exact position. This is pretty
3:01:19crazy.
3:01:21Take a look at once again. I'll try to
3:01:22place it right here,
3:01:24and it worked. Of course, it's going to
3:01:26depend on the zoom position. I think if
3:01:28we're at a normal regular zoom, and you
3:01:30once again drag and drop it to a
3:01:32specific position, that's exactly where
3:01:34it places it. So, that has been fixed.
3:01:37Also, now there's just one Clerk icon
3:01:39right here, where you can see your
3:01:40account.
3:01:42Uh what else did we fix? The edges
3:01:45connections. So, for example, let's try
3:01:47to drag and drop the bottom of this edge
3:01:49right here to the left side of the test
3:01:52suite, and take a look at how well it
3:01:55connects. It even places the arrow from
3:01:57here to here. Or maybe when I go into
3:01:59the package, that works as well, and the
3:02:02arrows move. This makes our application
3:02:04so much more usable, because now, for
3:02:06example, we can connect the auth service
3:02:09horizontally with the database
3:02:12and then take the database output and do
3:02:14something with it.
3:02:16Again, this canvas is becoming a mess
3:02:18right now because there's so much stuff
3:02:20happening. So, it would make sense to
3:02:22test out the delete. Select a couple of
3:02:24elements and try to press the backspace
3:02:27or the delete key and see whether it's
3:02:30going to remove them. Because for me, it
3:02:33doesn't. So, it looks like that's the
3:02:35last issue that's still pending. So,
3:02:37let's just tell it that issue two
3:02:40remains unfixed. When I select an
3:02:42element and press the backspace or the
3:02:44delete key, the element is still there
3:02:47and isn't being deleted from the canvas.
3:02:49Analyze it and fix it. So, let's see if
3:02:52we can just narrow it down to one issue,
3:02:54whether it can finish that last one. And
3:02:57again, this just shows you that whenever
3:02:58you give it more stuff to fix,
3:03:01it's easy for some of these to fall
3:03:04through because it maybe didn't analyze
3:03:06them as deeply in isolation.
3:03:09So, you can see why these feature specs
3:03:11worked so well so far because they were
3:03:14super detailed and well-documented. But
3:03:17as soon as you go deeper into the
3:03:18conversation, the agent can easily get
3:03:20lost.
3:03:22So, this is its last chance. Let's see
3:03:24how well it does.
3:03:27It looks like it was able to find a root
3:03:29cause in about 30 seconds. In apply no
3:03:32changes inside of the live blocks react
3:03:35flow source, we had a case of remove
3:03:38which was not doing anything.
3:03:40So, it was silently ignoring the remove
3:03:43type changes, whereas live blocks
3:03:46exposes a dedicated on delete function
3:03:49that correctly deletes from the live
3:03:51map. So, it fixed it and let's see how
3:03:54well it does right now.
3:03:55I'll reload the page and then I'll
3:03:58select a couple of elements and press
3:04:00the backspace key and now it deletes
3:04:02them properly. Now, if you don't want to
3:04:04delete them one by one, you can also
3:04:06hold the shift key, and then drag and
3:04:09drop over multiple, and remove them that
3:04:11way.
3:04:12Also, another thing that I didn't tell
3:04:13you so far is that you can also add uh
3:04:16text to the lines connecting different
3:04:19elements. Like, we want to build and
3:04:23then
3:04:24run
3:04:25the test suite. So, this way kind of it
3:04:28makes more sense with the connection.
3:04:30But, you can also use the text on the
3:04:31lines for significantly more detailed
3:04:34instructions when it comes to connecting
3:04:36different elements. But, yeah, now that
3:04:38all of these issues are fixed, we can go
3:04:40ahead and push them. So, say get add
3:04:43dot, get commit {dash} m, and this
3:04:45lesson was all about canvas interaction.
3:04:48So, say implement improved canvas
3:04:51interaction.
3:04:53And then run get push. Now, in the next
3:04:55lesson, we'll focus on the AI
3:04:58collaborative side of the application.
— Trigger.dev Setup
3:05:02And now that we have the full canvas
3:05:05right here, where we can drag and drop
3:05:07elements, move things around, and create
3:05:10our own architectural systems,
3:05:13we're ready to help our users a bit by
3:05:16implementing the AI workspace, where we
3:05:19can allow our users to collaborate with
3:05:22our AI agent within the application,
3:05:25that I'll teach you how to implement
3:05:26right now.
3:05:28But, I want you to think about it a bit.
3:05:30Like, before we start building the AI
3:05:32generation features,
3:05:34um you need to understand why these
3:05:36can't run inside a normal API route.
3:05:40See, when a user sends a prompt to Ghost
3:05:42AI, something like this, to design an
3:05:45e-commerce back end, the agent doesn't
3:05:48just make one call. It analyzes the
3:05:50request, runs multiple AI steps,
3:05:53generates nodes and edges, and syncs
3:05:56everything back to the canvas all
3:05:58keeping the user updated. And that can
3:06:00easily take 30 to 60 seconds, maybe even
3:06:04a couple of minutes. And I mean, you've
3:06:05already seen this pattern. When you
3:06:07generate an image with ChatGPT or
3:06:09Gemini, it doesn't happen instantly. The
3:06:12work runs in the background, and you
3:06:14keep receiving the updates until the
3:06:16response is ready. And the API routes
3:06:18within our application are not designed
3:06:20for that. They have execution limits,
3:06:23which means that long-running requests
3:06:24can time out. And even if they didn't,
3:06:27keeping a request open for that long
3:06:29isn't how production systems are built.
3:06:31That's what I'll teach you how to
3:06:33implement Trigger.dev to solve this
3:06:35problem.
3:06:36In the final version of the application,
3:06:38if you give it a detailed prompt and
3:06:40send it, you'll be able to see direct
3:06:43updates as it is working on the thing.
3:06:46It'll first start with analyzing your
3:06:48architecture request, and all of these
3:06:51functions will run as durable background
3:06:54tasks outside of the request life cycle,
3:06:57which means that the work is running in
3:06:59the background, and you'll be updated on
3:07:02the process. So, the work of the API
3:07:04route is to just trigger the task and
3:07:06then return, while the heavy work
3:07:08continues in the background. And that's
3:07:10why both the AI design generation and
3:07:13the spec generation in Ghost AI run
3:07:16through Trigger.dev instead of directly
3:07:18in API routes. And even before we used
3:07:20Trigger on Ghost AI, I was already using
3:07:24it within jsmastey.com platform, where
3:07:27every time that users go through
3:07:28different lessons, check out the
3:07:30overviews, contents, or transcripts, we
3:07:32can perform some actions on the back end
3:07:35without slowing down or freezing what
3:07:37the user can see on the front end, which
3:07:39is super helpful for quizzes or sharing
3:07:41the status of the AI interviewer that we
3:07:44worked on. So, yeah, let me show you how
3:07:46we can set up Trigger.dev in your
3:07:48project.
3:07:49First, click the link down in the
3:07:51description to be able to follow along
3:07:53and see exactly what I'm seeing.
3:07:55And then create a new account. Once
3:07:58you're in, you should be able to see
3:07:59your dashboard that looks something like
3:08:01this.
3:08:02So, go ahead and create a new project.
3:08:04I'll call it Ghost AI.
3:08:08We are working on an AI agent, and under
3:08:11technologies, I thought that Gemini
3:08:13works the best. So, you can proceed with
3:08:15Google Gemini. And what we're trying to
3:08:17do with Trigger? Well, we're still
3:08:19learning how Trigger works, as well as
3:08:21potentially shipping a production
3:08:23workflow. So, let's create it. Then,
3:08:25once it is created, you can run this CLI
3:08:28command to initialize it in the existing
3:08:31project. So, just copy it.
3:08:33Head back. Open up the terminal.
3:08:36You can do it under a new tab.
3:08:39And
3:08:40just run NPX trigger.dev@latest
3:08:43in it, and then pass your specific
3:08:45project ID.
3:08:46Say yes to install the Trigger.dev CLI.
3:08:49And this will now install the
3:08:51Trigger.dev SDK and initialize the
3:08:54config file. But, it'll first ask you a
3:08:56couple of questions, such as choose how
3:08:59you want to initialize your project. In
3:09:01this case, we'll proceed with the
3:09:02Trigger.dev MCP, which allows you to
3:09:05vibe your way to a new project. Well, in
3:09:08this case, write detailed project
3:09:10specifications to come up with a new
3:09:12scalable project. It's going to ask you
3:09:14whether you want to restrict the MCP
3:09:16server to the dev environment only. I'll
3:09:18say no.
3:09:19And then, you'll be able to choose one
3:09:21or more clients to install the MCP
3:09:23server into. You can choose all the ones
3:09:26that you're using, such as Claude Code,
3:09:28VS Code, OpenAI Codex, or really
3:09:31anything else that you want.
3:09:33And then, press enter.
3:09:35Where should the MCP server for Claude
3:09:37Code be installed? I'll go over with the
3:09:40project.
3:09:41Same thing for all the other ones.
3:09:45And that's it. We don't need to
3:09:47our MCP clients. In your client, look
3:09:50for a server named trigger, and then get
3:09:52started with trigger dev by asking it to
3:09:55add trigger to our project. So, let's do
3:09:58just that. I'll open up our Claude code,
3:10:01and that's going to be on a new chat
3:10:03window, and I'll simply tell it to add
3:10:05trigger dev to my project. And we can
3:10:07now run it, but just before I do, I want
3:10:10to share another thing with you, and
3:10:11that is that trigger also supports agent
3:10:15skills to teach any AI coding assistant
3:10:17best practices for writing tasks,
3:10:20agents, and workflows. So, we can
3:10:22install it as we did for all the other
3:10:24dev tools by opening up the terminal and
3:10:27running MPX skills add trigger.dev
3:10:31skills.
3:10:32We can choose uh trigger agents, config,
3:10:36and maybe setup. I think that's going to
3:10:38be all we need for now, but you can just
3:10:40go ahead and add all of them as well.
3:10:43Choose your specific agents you want to
3:10:45add it for.
3:10:47Do it within the projects through
3:10:49symlink, and then quickly install it.
3:10:52Now, let's go ahead and ask it to add
3:10:54trigger dev to my project. Then, let's
3:10:57run it. It's going to explore the
3:10:58project structure,
3:11:00find all the relevant files, and the
3:11:02architecture context, which matters a
3:11:03lot, it's going to get the full picture,
3:11:06and it'll start with configuring the
3:11:08trigger config.ts,
3:11:10creating a trigger directory with
3:11:12example tasks, adding the trigger.dev
3:11:15route handler, and finally updating the
3:11:17progress tracker. And there we go, after
3:11:19a couple of minutes, the trigger dev is
3:11:21now set up. So, it first added the
3:11:24trigger config.js.
3:11:27So, let's quickly go over into it.
3:11:30That's going to be within our
3:11:33trigger.config.ts
3:11:34in the root of the application,
3:11:36and it looks something like this.
3:11:39It is coming from trigger dev SDK.
3:11:41We define a specific configuration, and
3:11:44then we need to connect it to a project
3:11:46reference.
3:11:48This is coming from our environment
3:11:49variables. So, very soon we'll have to
3:11:51find it in our Trigger Dev dashboard,
3:11:53and then add it to our .env's. Then,
3:11:56below the project, we can add a runtime
3:11:59and set the environment to node. When it
3:12:02comes to directories, ./trigger is going
3:12:05to be fine, or in this case, I think we
3:12:07can just leave it to trigger. Now, max
3:12:09duration stands for the max compute
3:12:12seconds a task is allowed to run.
3:12:15If the tasks exceed this duration, it'll
3:12:17be stopped. And by default, we can set
3:12:20it to something like 3600,
3:12:23so that any task can run up to an hour
3:12:25before it's stopped.
3:12:27But again, that's not needed because
3:12:28most of our AI tasks will be done in a
3:12:30couple of minutes.
3:12:32And then for the retries, if something
3:12:34fails, Trigger Dev will retry it up to
3:12:37three times with increasing delays
3:12:39between each attempt. So, having that
3:12:41built right in to make sure that if
3:12:43something does go wrong, which it often
3:12:44does with AI generation,
3:12:47it retries it. So, when your user comes
3:12:49back to your app, whatever they were
3:12:50waiting for from an AI agent is waiting
3:12:52for them there. Okay, so let's get those
3:12:54keys.
3:12:55I'll head back over to Trigger Dev,
3:12:57specifically scroll down to API keys,
3:13:01and right here you'll be able to find
3:13:02your secret key. So, simply copy it.
3:13:05Then, head over into your .env.local,
3:13:09and here we can add, as it specifies,
3:13:13the Trigger project ref,
3:13:16as well as the Trigger secret key. So,
3:13:19the secret key is the one we just
3:13:20copied, and the project key you can find
3:13:22right here by going over to the tasks
3:13:25and copying it right here from the end
3:13:28part of the CLI init command.
3:13:31And then just paste it over here. And
3:13:33the next part is to start your local dev
3:13:36workflow. And I think this is the exact
3:13:39second step from our 3-minute setup. NPX
3:13:42trigger dev at latest dev. So, you can
3:13:45open up your terminal, make sure that
3:13:47it's not the one running your
3:13:48application.
3:13:49And before we run this, let me actually
3:13:51explain what it does.
3:13:53This is specific to local development.
3:13:55Trigger dev background tasks don't run
3:13:58on your machine automatically. They need
3:14:00a local worker process running alongside
3:14:03your Next.js dev server.
3:14:05So, just to run this command, would you
3:14:07like to install the Trigger dev code
3:14:09agent rules? Well, yeah, go ahead.
3:14:11We can select it for Claude code or
3:14:14whichever other agent you're using.
3:14:16And choose the rules you want to
3:14:17install. Let's go with basic, advanced.
3:14:21You know what? Let me go with all of
3:14:22them.
3:14:24Now, it's going to wait for you to log
3:14:26in to your Trigger dev account, which
3:14:28you can do by simply heading over to
3:14:30this URL and then authenticating your
3:14:32account. Once you're successfully
3:14:34authenticated, you can head back and
3:14:36you'll be able to see a screen that
3:14:38looks something like this.
3:14:40Logged in successfully, Trigger dev is
3:14:42running, and the local worker is ready.
3:14:46So, keep this running whenever you're
3:14:47developing locally, because without it,
3:14:49any task you trigger will queue, but
3:14:52never execute.
3:14:53And in production on Vercel, this isn't
3:14:55needed because Trigger dev will handle
3:14:58the worker infrastructure for you. So,
3:15:00if you now head back over to your
3:15:01dashboard, you'll be able to see two
3:15:03different tasks generate design and
3:15:05generate spec, which is absolutely
3:15:07amazing. This was going to be one of my
3:15:10upcoming project specifications to add,
3:15:12but it looks like the MCP already
3:15:14figured out exactly what we want, and it
3:15:17created those tasks right here. So, soon
3:15:19enough, you'll be able to see how those
3:15:21tasks actually run and then see more
3:15:24information about it. So, that was it
3:15:26for the setup. And then in the next
3:15:28lesson, let's build the AI generation
3:15:30logic.
— AI Architecture Design Generation Flow
3:15:33Okay, Trigger.dev and the agent together
3:15:36already created for us the task
3:15:38skeletons. But in this lesson, we'll
3:15:41build the full AI generation flow. Now,
3:15:44before we make our system design agent
3:15:46logic, we'll have to install a couple
3:15:48more packages. So, open up your
3:15:50terminal. We'll have to go with the
3:15:52third one right here.
3:15:54And run npm install
3:15:56@trigger.dev/react-hooks,
3:16:00which lets the front-end subscribe to a
3:16:02live Trigger.dev background run without
3:16:05manually polling. So, think of it like
3:16:07instead of asking is the job done yet
3:16:10every few seconds, the hook will stream
3:16:12in real-time status updates directly to
3:16:15the UI as the task progresses.
3:16:18Then, there's the @ai-sdk/google,
3:16:21which is the Google provider for the
3:16:22Vercel AI SDK. And in this case, we'll
3:16:25be using Gemini for both design
3:16:27generation and spec generation. But of
3:16:29course, feel free to use any other agent
3:16:31you prefer. And finally, AI is the core
3:16:34Vercel AI SDK, which is going to give us
3:16:37utilities for generating text or
3:16:39generating different objects by working
3:16:41consistently with any kind of AI
3:16:43provider. So, go ahead and install these
3:16:46three. And while they're getting
3:16:47installed, head over to AI
3:16:49Studio.google.com,
3:16:51which we're going to connect with
3:16:53Trigger.dev to infuse it with all sorts
3:16:55of different functionalities. So, click
3:16:57get started. Make sure to authenticate
3:16:59with your account.
3:17:01And Google AI Studio will give you free
3:17:03credits in most regions, which is going
3:17:05to be enough to test out the generation
3:17:07flow throughout this build.
3:17:09So, click get API key, then create a new
3:17:13API key. You can call it something like
3:17:15Ghost AI.
3:17:18And then, you'll be given all your API
3:17:20key details. So, first, copy the actual
3:17:22API key
3:17:24and paste it into your .env.local.
3:17:28Specifically, that's going to be under
3:17:31google_ai_api_key,
3:17:35and it's going to be set to this key you
3:17:37just copied. Or, if you want to get a
3:17:40completely free alternative to Google
3:17:43Gemini, OpenRouter has a pretty good
3:17:45selection of free models. I recommend
3:17:47this Nvidia Neumotron 3 Nano Omni.
3:17:51That's a mouthful. Which is a free model
3:17:54that has a 256,000 context window, and
3:17:58it handles the architecture generation
3:18:00pretty well. Just keep in mind that the
3:18:01free tier models always have usage
3:18:03limits. So, if you go that route, simply
3:18:06swap that AI SDK Google thing for the
3:18:09OpenRouter SDK in the generation task.
3:18:12For this video, I'll personally use
3:18:13Google AI Studio, but again, as with the
3:18:16AI agents, follow along with whichever
3:18:18one works for you. So, now with the
3:18:20setup ready, we are ready to start
3:18:22adding the Trigger.dev task spec files.
3:18:26So, head over into our context feature
3:18:29specs,
3:18:30and find or create a new one called
3:18:3322_design_agent_api.md.
3:18:38The goal of this feature is to set up
3:18:40the back-end flow for design generation
3:18:43using Trigger.dev.
3:18:44And keep in mind that when we're
3:18:45referring to design here, we're
3:18:47referring to the systems or architecture
3:18:49design of our applications. So, we're
3:18:52setting up this design agent API for the
3:18:54back-end wiring. Starting with the
3:18:57trigger route, where we need to create a
3:18:59specific post route that should accept
3:19:01the design prompt and some context, and
3:19:04then trigger the design task through
3:19:06Trigger.dev. Because, as I said, these
3:19:09tasks and these AI prompts can take some
3:19:11time to process. Then, we'll add task
3:19:14tracking. This is going to be a special
3:19:16model in Prisma to track Trigger.dev
3:19:19runs and verify ownership.
3:19:21Who created the run, when, and what is
3:19:24it all about?
3:19:25We should add the token route to verify
3:19:27ownership, and then finally create the
3:19:30actual design task. We want to check the
3:19:33existing Trigger.dev setup and installed
3:19:35agent features, reuse the existing
3:19:37setup, export a minimal design task, and
3:19:40for now not add any design logic yet.
3:19:44Just the infrastructure that makes the
3:19:45next spec possible.
3:19:47You learned that already. Spec by spec,
3:19:50we're getting closer to the final
3:19:51solution.
3:19:53So, let's go ahead and run it. As usual,
3:19:56you can just tell it to read the current
3:19:58file, update the context progress
3:20:00tracker, and implement it exactly as
3:20:03specified. And after some work, feature
3:20:0622, the design agent API, has now been
3:20:09completed. It created a new Prisma model
3:20:13called task run with run ID, project ID,
3:20:16user ID, so we know which projects are
3:20:18being run over on Trigger.dev. Then,
3:20:20there's the design agent itself, which
3:20:23is a minimal task accepting a prompt and
3:20:25a room ID, which logs and echoes the
3:20:28inputs.
3:20:29So, if you open it up, you'll see that
3:20:31it looks like this. Currently, there's
3:20:34no logic and functionality, but we'll
3:20:36add it very soon.
3:20:38Then, there's the post API AI design,
3:20:41which requires authentication. And what
3:20:43this one does is it basically figures
3:20:45out whether the user is authenticated,
3:20:47takes in the body, and finally creates a
3:20:50new task run in the database.
3:20:53But of course, before it hands it over
3:20:55to Trigger.dev. But now, the actual task
3:20:58is pretty empty. It's just console
3:21:00logging design agent triggered, and it's
3:21:02not doing anything else.
3:21:04So, our next task is to implement the
3:21:07full AI design agent logic, so a user
3:21:10prompt results in real-time updates on
3:21:13the canvas. To do that, head over into
3:21:15our feature specs, and let's focus on
3:21:18adding the 23rd spec, which is design
3:21:21agent
3:21:23logic.md.
3:21:25And as I said, the goal here is to
3:21:27implement the full AI design agent
3:21:29logic.
3:21:31So, first we need to update the file
3:21:32which was just created.
3:21:34Before, when I check the product
3:21:36behavior of the system, check the live
3:21:38blocks and trigger agent skills. This is
3:21:40very important. Sometimes you have to
3:21:41explicitly tell it to check the skills.
3:21:44Follow the trigger dev setup and agent
3:21:46patterns already set up. Reuse the
3:21:48existing live blocks flow and presence
3:21:50patterns instead of creating new ones,
3:21:52and then implement use Gemini logic to
3:21:56interpret the user prompt, update the
3:21:59canvas using the collaborative flow
3:22:01utilities, supporting actions like
3:22:03adding nodes, moving them, resizing
3:22:05them, updating them, deleting them.
3:22:07Basically, everything that our
3:22:09application is already doing.
3:22:11Then, want to publish that AI activity
3:22:13to the shared status feed, so all users
3:22:15can see the progress.
3:22:17Update the AI presence through cursors
3:22:19and thinking state, and push clear
3:22:21status messages at key steps. If you
3:22:24think about it, this is a very important
3:22:28and big feature spec. Maybe the biggest
3:22:31one in our application so far, because
3:22:33now we're essentially telling our AI
3:22:35agent and trigger
3:22:37to figure out the entirety of our
3:22:40application
3:22:42and to replicate its functionality on
3:22:44its own while following the user prompt.
3:22:47Like, the user is telling it to create
3:22:50something on the canvas, and it should
3:22:52create it.
3:22:53So, let's go ahead and see how well it
3:22:55does by opening up a new Claude code
3:22:57window, giving it access to the file,
3:22:59and asking it to read the spec file,
3:23:04update the progress document, and
3:23:07implement it exactly as specified. This
3:23:09is a big one, so I'm super excited to
3:23:12see how well it actually does it. And
3:23:14feature 23 is now complete. This one
3:23:17took a bit longer because it's a it's a
3:23:19huge one. And we now have a full AI
3:23:23agent built within our application
3:23:25powered by trigger.dev.
3:23:27It uses Gemini 2.0 flash via create
3:23:31Google generative AI plus generate text
3:23:34with output object to produce a typed
3:23:37list of canvas actions for adding,
3:23:40moving, resizing, updating, and deleting
3:23:42nodes and edges. It sets the AI presence
3:23:46by thinking is set to true. So, all
3:23:48collaborators can see the AI cursor. It
3:23:51broadcasts three different statuses:
3:23:53start, thinking, and complete. And it
3:23:56applies all mutations atomically in a
3:23:59single mutate storage call.
3:24:01Perfect. This is exactly what we wanted.
3:24:03So, while we can't see it on the screen
3:24:06just yet,
3:24:07what we can do is head over to our
3:24:09trigger.dev dashboard under tasks.
3:24:12Currently, we have three separate tasks.
3:24:14That's because our initial setup task
3:24:17created the skeletons for generate
3:24:19design and generate spec, but the new
3:24:21one is right here under design agent.
3:24:23So, what we can do is maybe we can clean
3:24:26up the other two just so they're not
3:24:28confusing us. So, under trigger, we can
3:24:30remove the generate design and generate
3:24:32spec, which are just empty placeholders.
3:24:36And instead, we can just leave the
3:24:37design agent right here. So, that's the
3:24:39only one that remains.
3:24:41Now, open it up
3:24:43and perform a test run.
3:24:46It's going to ask you to pass the data
3:24:48as a regular user would through the
3:24:49application.
3:24:50And we can write something like room ID,
3:24:53and you'll have to get this room ID
3:24:55directly from one of your canvases.
3:24:57So, head over here and create a new
3:24:59project. Let's call it something like um
3:25:03system design.
3:25:06Created. And you can copy the project ID
3:25:08directly from the URL right here at the
3:25:11top.
3:25:13Then, if you head back, you can update
3:25:15it over here. And we can ask it to
3:25:17architect a real-time chat application.
3:25:20I need a web socket server for live
3:25:23messaging, a presence service to track
3:25:25online users, and a relational database
3:25:27for message history. Connect the
3:25:29services using a pub sub system like
3:25:31Redis, so it can scale horizontally, and
3:25:33include an S3 bucket for media
3:25:36attachments.
3:25:37I'll give you a prompt like this in the
3:25:39video kit link down in the description,
3:25:41so you can copy it and paste it.
3:25:43But make sure to update the room ID. And
3:25:46now, let's go ahead and run the test.
3:25:48Oh, but before, make sure that your dev
3:25:51server is connected right here. If it's
3:25:53not, connect it, and then we'll be able
3:25:55to run the test.
3:25:57As you run it, you'll be able to see all
3:26:00the information about this specific task
3:26:02run. When it was triggered, when it was
3:26:05queued, and when it has started. Now,
3:26:07immediately, it'll fail after three
3:26:10attempts, saying that we exceeded our
3:26:12current quota, and we need to check our
3:26:14Google billing plan. So, it's super
3:26:16convenient to be able to test the runs
3:26:18directly within the Trigger dashboard.
3:26:20So, once you top it up or change your
3:26:22providers, uh you can just reload your
3:26:24Trigger dev server, and then replay the
3:26:26run.
3:26:28Hopefully, this time, it continues
3:26:30running after just a couple of seconds.
3:26:32And we get another little API error,
3:26:35saying that models Gemini 2.0 Flash is
3:26:37no longer available.
3:26:39Um I wish they made those backwards
3:26:41compatible, but it is what it is. We got
3:26:44to find one that actually is compatible.
3:26:46So, instead of using 2.0 Flash, I'll
3:26:49head over into this design agent file,
3:26:52that's going to be right here,
3:26:54and I'll switch it over to 2.5
3:26:58Flash. I believe that is the latest
3:27:00running model. Then, when you change it,
3:27:03don't forget to rerun your server.
3:27:06Again, the Trigger.dev server by running
3:27:08MPX Trigger.dev at latest dev. Then,
3:27:11after it creates the local worker, go
3:27:13ahead and replay the run. Third times
3:27:15the charm. Or, apparently not, because
3:27:18after about 20 seconds, we got a little
3:27:20error saying, "No object generated.
3:27:23Response did not match the schema."
3:27:27So, open up a new chat, and let's tell
3:27:30it something like this.
3:27:32While implementing
3:27:35at 23, that's going to be the 23rd
3:27:39design spec
3:27:41that we tried to do, the agent logic. Or
3:27:44rather, we can say after implementing, I
3:27:46tried to run a test. And then, I give it
3:27:49some more information that we used for
3:27:51the test itself, and then pasted the
3:27:53error that we got, which you can get
3:27:54from the web.
3:27:56Finally, we can tell it something like
3:27:57analyze trigger.dev and Liveblocks best
3:28:00practices. Analyze this doc, and I
3:28:03shared it a link to the doc of the
3:28:06generating structured data part, because
3:28:08so far, it generated it just as text.
3:28:11But, what we need to do instead is have
3:28:14some kind of a schema that we can
3:28:16validate against. So, currently, it is
3:28:18using this output, but what we can do is
3:28:21use a so-called tool calling approach
3:28:24with generate text with a tools object.
3:28:28So, I'll tell it analyze this docs page
3:28:31and the error, and then, I'll pass some
3:28:33additional info which I think is the
3:28:35reason for why it's failing.
3:28:37So, that design agent is failing because
3:28:39generate text is being used with output
3:28:42object for structured output. That
3:28:45experimental API is causing the runtime
3:28:47error.
3:28:48So, instead, replace it with a tool
3:28:50calling approach using generate text
3:28:52with a tools object. Each canvas action
3:28:55should be its own tool. So, we have the
3:28:57add node, move node, and so on. And this
3:29:00way it's going to follow the schema
3:29:02precisely. So, have a message that looks
3:29:04like this and send it out.
3:29:08This is also a useful approach to share
3:29:10it a link to a specific documentation
3:29:13page so that it can do a web fetch and
3:29:16figure out how it works. Or
3:29:17alternatively, there's also this context
3:29:20seven, which is a library of the latest
3:29:22documentations that you can directly
3:29:24fuse with cursor, Claude, or other LLMs.
3:29:28For example, it has the latest docs for
3:29:29Next.js updated 9 hours ago. Same thing
3:29:32for React and most likely for AI agents
3:29:36as well. There we go. AI SDK. This is
3:29:39what we're using. So, if we hook this up
3:29:41with our agent, and you can do that very
3:29:43easily just by copy and pasting this
3:29:45thing right here, it would have all the
3:29:47latest docs. But let's see what it can
3:29:49do with just the URL. And very quickly,
3:29:51the build passes. Here's what changed.
3:29:54The output object in generate text
3:29:56relies on the model emitting a single
3:29:59structured JSON blob. But Gemini 2.5
3:30:02Flash returned a response that didn't
3:30:04match the schema, triggering the AI no
3:30:06object generated. So, in this case,
3:30:08we're replacing the output with a tool
3:30:11approach where each canvas action is its
3:30:13own named tool, like add node, move
3:30:16node, and so on. So, after generation,
3:30:18all tool calls are collected and then we
3:30:20get the final canvas.
3:30:22Okay, so let's go ahead and test it out.
3:30:25Back on Trigger.dev, we can just replay
3:30:27the run. And in about 15 seconds, it
3:30:30finished. We got a successful output
3:30:32with 11 actions applied and a summary of
3:30:36what happened. Now, let's make it even
3:30:38better by adding another feature spec
3:30:42right here. That's going to be 24 AI
3:30:45presence state.md.
3:30:48And the goal of this one is to add a
3:30:51shared activity indicator so that when
3:30:54AI is generating, everyone in the room
3:30:56will see it. A status indicator in the
3:30:59sidebar, as well as a thinking spinner
3:31:02cursor, and the input will be disabled
3:31:04while generation is active. So, let's go
3:31:06ahead and run it with the usual message.
3:31:11And let's give it a minute. And I sped
3:31:13up the process for you, but this one
3:31:15definitely took some time, but
3:31:17thankfully the build passes cleanly.
3:31:20We added some new types for the
3:31:23tasks.ts,
3:31:24modified the liveblocks.config, and
3:31:27added the presence cursors. But before
3:31:29we actually go ahead and test this out,
3:31:31I want to finish the implementation of
3:31:33the sidebar so we can test everything
3:31:35we've been working on together.
3:31:37So, go ahead and open up a 25 sidebar
3:31:42chat feed.md
3:31:44file.
3:31:45And within this one, the goal is to wire
3:31:49up the AI chat liveblocks feed into the
3:31:52sidebar.
3:31:53See, liveblocks feeds are real-time
3:31:56message channels. Think of them like a
3:31:59group chat that is tied to a specific
3:32:01project room. Anyone in that room can
3:32:04send a message, and everyone else can
3:32:06instantly see it. In this case, we're
3:32:08using two separate feeds, the AI chat
3:32:11feed and the AI status feed. The AI
3:32:14status feed is simply for some kind of
3:32:17progress signals, and AI chat is for the
3:32:20actual conversation. This spec handles
3:32:22the chat side where users can send
3:32:25messages and make them appear across
3:32:27every connected client. So, let's go
3:32:29ahead and run it, and I'll speed it up
3:32:32for you. And feature 25 is done. Here's
3:32:35what was changed across three files.
3:32:37First, added some additional types.
3:32:40Then, in the config, we extended the
3:32:42feed message data with optional chat
3:32:45fields. So, now it also supports the
3:32:47sender, role, content, and timestamp
3:32:50alongside the existing AI status fields,
3:32:53so they both coexist under the same
3:32:55type. The key addition right here was
3:32:57the chat feed ID constant. So, now we
3:33:01support the chat alongside these status
3:33:03updates as well. And finally, just one
3:33:06more thing before we test it, we have to
3:33:09wire everything together. So far, this
3:33:11is just the UI. In the last couple of
3:33:13prompts, we implemented the UI side of
3:33:15things and Liveblocks side of
3:33:17functionality. Just before that, we
3:33:19implemented background tasks through
3:33:22trigger.dev, but now we have to wire it
3:33:25all together.
3:33:26So, go ahead and open up 26
3:33:30design agent
3:33:33the goal here
3:33:36is really to connect everything
3:33:38together.
3:33:39We are submitting a prompt from the AI
3:33:42sidebar, then we're tracking the status
3:33:45live through the use real-time run
3:33:48function. We're also showing a status
3:33:51strip while the task runs,
3:33:54and we're letting Liveblocks handle the
3:33:56updates of the canvas automatically.
3:33:59So, let's go ahead and run it. And as
3:34:01soon as it is finished, we're going to
3:34:03test everything together.
3:34:05And in a couple of minutes, definitely
3:34:07much faster than it would take me to do
3:34:09all of this, feature 26 is done. Again,
3:34:13just modify some of the types,
3:34:16modify the config to widen it just a
3:34:18bit, and then most importantly, the
3:34:20major rework happened within the AI
3:34:22sidebar, where the handle send now
3:34:25actually pushes the user message to AI
3:34:28chat, calls our API endpoint to get the
3:34:32run ID, and then the token as well to
3:34:34get the public token, and then stores
3:34:36both in state.
3:34:39Finally, trigger.dev hooks run. We track
3:34:42the run and once it reaches a final
3:34:44status, it pushes the final AI message
3:34:46to the AI chat, resetting the loading
3:34:48state and clearing the presence. So,
3:34:51what do you say that we head back over
3:34:53to localhost:3000,
3:34:54reload the page, and you'll see a
3:34:56missing access token in trigger context
3:34:59auth or use API client options. We can
3:35:02quickly head back over to just a
3:35:04localhost:3000 and editor. It is
3:35:07possible that that's because of this
3:35:08specific project. So, I'll create a new
3:35:11project now.
3:35:12I'll call it
3:35:14testing
3:35:15AI designs
3:35:18and create it.
3:35:20And once again, we immediately get the
3:35:22missing access token in trigger auth
3:35:25context or use API client options.
3:35:28So, there's this handy little copy error
3:35:31info click that you can copy, and then
3:35:33you can directly paste the full error
3:35:35right here. When I try to open a
3:35:37specific canvas, this is the error that
3:35:40I get. And then we can just display that
3:35:42error.
3:35:43Tell it to analyze it by following the
3:35:47best practices from both Liveblocks and
3:35:50Trigger.dev, and provide me your
3:35:53reasoning and idea of what is wrong and
3:35:56how to fix it. So, something like this
3:35:58does. I noticed when you don't ask it to
3:36:00fix it immediately, but rather you first
3:36:03ask it to analyze it and give you the
3:36:05idea of what it would do, that's not
3:36:07cluttering the context as much, and it
3:36:10gives you the ability to say whether you
3:36:13would like it to execute what it
3:36:15suggests or not. So, let's see what it
3:36:17comes up with, and then we can
3:36:18potentially execute it. And very
3:36:20quickly, it was able to figure out the
3:36:22reasoning, and I believe it actually
3:36:24applied it because it was just a couple
3:36:26of lines of code. The root cause was
3:36:28that the use real-time run performs an
3:36:30eager validation check for access token
3:36:33on every render, and it doesn't wait for
3:36:36a connection attempt. So, if we pass
3:36:38empty string, it's the same as if we
3:36:40don't pass it at all, so it immediately
3:36:42throws the error on the first render.
3:36:44But rather, we need to wait, and only
3:36:47when there's an active run, then we need
3:36:49to check for the token. So, now if we
3:36:50head back, you can see that we can open
3:36:53up a design, the new one as well as the
3:36:57old one. So, now let's go within the new
3:36:59project we created,
3:37:01and we are ready to describe a new
3:37:03system to AI.
3:37:06In this case, you can really go ahead
3:37:07and type anything. I'll write something
3:37:09like this. You can also pause your
3:37:11screen and write it with me. But the
3:37:13goal here is to design a high-scale
3:37:16e-commerce back-end architecture,
3:37:18including an API gateway, a user service
3:37:21with auth, a product catalog service
3:37:24using a no SQL database,
3:37:26and an order processing system that
3:37:28communicates via a message queue. Also,
3:37:31don't forget to add a Redis cache for
3:37:33the catalog to handle high traffic.
3:37:36And now, we can go ahead and click send.
3:37:39You can see that Ghost AI is analyzing
3:37:42your request in real time right now.
3:37:46I'm going to zoom it in. So, this is the
3:37:48first status that we're seeing. It says
3:37:50working right here.
3:37:51And then immediately after, we were able
3:37:54to see a completed product. So, in this
3:37:57case, we get the API gateway,
3:38:00the user accessing it, browsing
3:38:03different products, placing an order
3:38:05through the product catalog service, and
3:38:07that goes into the message queue, and
3:38:09then the order is actually processed.
3:38:11So, this is a great and pretty detailed
3:38:14system architecture diagram, and we were
3:38:16able to generate it using AI very
3:38:19quickly. Because we already had the
3:38:21underlying system for creating different
3:38:23nodes and adding labels within the
3:38:25elements and the edges.
3:38:28And then we just created a system that
3:38:30works in the background through
3:38:31trigger.dev that makes use of all of
3:38:34those functionalities within the app,
3:38:37generates that JSON, and then we just
3:38:39slap it onto the canvas. So, now that
3:38:41everything is working so well, the last
3:38:43missing piece of the puzzle is to
3:38:46generate the specs. We need to take
3:38:49everything that is on the canvas and
3:38:51generate a markdown document that we can
3:38:53then actually feed into a thinking or
3:38:57planning AI agent, which is going to
3:38:58make it the strongest possible starting
3:39:00point for starting to develop any kind
3:39:03of an application for real. But, the
3:39:06first step is now done. It's all about
3:39:08approaching the architecture from a
3:39:10planning and very detailed way, like you
3:39:13are an architect really, designing a
3:39:15specification document. So, in the next
3:39:17lesson, let's focus on generating that
3:39:20spec.
— AI Spec Generation
3:39:22The flow for generating the spec is
3:39:25almost the same pattern as for
3:39:27generating the design. A trigger route,
3:39:30a background task, and a token route for
3:39:33ownership tracking. Then, persistence to
3:39:36Vercel blob, and finally, a piece of UI
3:39:39to then view and download the result.
3:39:42Because we've already built this pattern
3:39:44once, these specs will move faster. So,
3:39:48let's go ahead and build it one by one
3:39:50by creating a feature spec number 27
3:39:53called spec generation flow.md.
3:39:58And yeah, as I told you, the goal of
3:40:01this one is to build the full backend,
3:40:04the trigger route, and the trigger.dev
3:40:06task that calls Gemini and then
3:40:09generates a markdown spec, but most
3:40:11importantly, it generates it from the
3:40:14canvas state and the chat history. You
3:40:18can see that right here, spec generation
3:40:20based on the project ID, room ID, chat
3:40:24history, nodes, and edges. No backend
3:40:27yet, We just wanted to execute that
3:40:29task. So, let's go ahead and run it. 27
3:40:32spec already. I'm not sure how to feel
3:40:35about it. Is it too high or too low of a
3:40:37number? I mean, considering what we've
3:40:39implemented and what we've done so far,
3:40:42like a full stack, full canvas
3:40:45AI-powered application with templates
3:40:48and everything else we've done. I mean,
3:40:50I think this would take us months to
3:40:53develop manually. And yeah, now that I
3:40:56think about it, I'm sure. 27 spec files
3:41:00is definitely not a lot for this
3:41:02production-ready application we've
3:41:04developed so far. We're very close, so I
3:41:07believe we'll be able to get it done in
3:41:08under 30. Let's make it run.
3:41:11In this case, it looks like it's
3:41:13actually running all three files in
3:41:15parallel. This one was actually pretty
3:41:17quick. Feature 27 is complete, and the
3:41:20only thing we need to check from this
3:41:21one is the trigger task called generate
3:41:24spec. So, let's head over into that
3:41:26file, and you'll notice that it's quite
3:41:29similar to design agent. It takes all
3:41:31the schemas, it builds the context,
3:41:34returns it all, and here's a system
3:41:36prompt saying that we are a ghost AI
3:41:39senior technical architect whose job is
3:41:42to generate a comprehensive markdown
3:41:44technical specification document based
3:41:46on the provider architecture canvas and
3:41:49the conversation context. And we then
3:41:51give it some information on how to
3:41:52structure the spec. Finally, we call the
3:41:55schema task coming from trigger, give it
3:41:58an ID, the schema of what it needs to
3:42:01do, the ability to retry it after
3:42:03specific options, and then what is it
3:42:06actually running? In this case, we're
3:42:07calling the Google generative AI and
3:42:10asking it to generate the spec, build
3:42:13the context, generate it in text. Again,
3:42:16we have to use 2.5 flash. 2.0 is no
3:42:20longer there. And then finally, it gets
3:42:22the spec and returns it. Before we go
3:42:25ahead and test it, we have to first
3:42:27implement a couple of functionalities
3:42:29and then wire it to the sidebar. So, go
3:42:32ahead and open feature specs and create
3:42:35a unit 28 spec persistence
3:42:39download.md.
3:42:41The goal of this one is to save the
3:42:44generated spec file to Vercel blob,
3:42:47store the URL in project spec Prisma
3:42:49model, and then add a secure download
3:42:52route. Basically, same Prisma metadata
3:42:55plus the blob content pattern that we
3:42:57use for the canvas auto save. That's how
3:42:59we want to save it.
3:43:00And then finally, we want to ensure that
3:43:02it's downloadable through this specific
3:43:05API route. It should authenticate the
3:43:08user, verify the access, verify the spec
3:43:11belongs to that project, and then fetch
3:43:13the file and return it as a downloadable
3:43:16markdown file.
3:43:18Go ahead and open this with your AI
3:43:20agent. As usual, we'll tell it to read
3:43:23the file, update our progress tracker,
3:43:25and then implement it exactly as
3:43:27specified.
3:43:29And while it's doing its job, let's
3:43:31quickly check how the progress tracker
3:43:33is coming along. I mean, we've
3:43:34implemented so many features so far, and
3:43:37all of it is here for future developers
3:43:40within your team or future agents
3:43:42working on the project to check out and
3:43:44then continue developing new features on
3:43:47top of. All the architecture decisions,
3:43:49session notes, and more.
3:43:52So, yeah. Let's see how it approaches
3:43:54this one. And feature 28 is done. A
3:43:57summary right here is that it added a
3:43:59project spec Prisma model with the ID,
3:44:03project ID, file path, and created at.
3:44:05And it's attached to a project. So, if
3:44:08we delete it, it's going to be deleted
3:44:09as well. Migrations have already been
3:44:11applied for us. I love agents for doing
3:44:14that automatically, so we don't have to
3:44:16run Prisma generate and migrate on our
3:44:18own. And finally, it updated the
3:44:20generate spec so that after the markdown
3:44:23is generated, it uploads it to a Vercel
3:44:25blob so that we can download it later
3:44:28on. And this route is responsible for
3:44:30that. So now, just before we tested we
3:44:34need to wire everything together. You
3:44:36know how I like to make sure that these
3:44:38spec files are really only handling one
3:44:40thing at a time. So in 27, we handled
3:44:43the spec generation flow. Then we made
3:44:46it persist by storing it into Vercel
3:44:48blob and creating a download route. And
3:44:51now finally, in 29, spec UI
3:44:55integration.md,
3:44:57we are going to wire everything together
3:44:59into the specs tab in the AI sidebar
3:45:03through a list of generated specs. We
3:45:05could have multiple, a preview model
3:45:07that renders the markdown in case you
3:45:09want to preview it in the browser, and
3:45:11finally a download action to download it
3:45:14on your device. So, go ahead and run it.
3:45:17And let's give it some time to implement
3:45:18it. And let's let it do its thing.
3:45:21And now that feature 29 is fully
3:45:23implemented and the build passes, which
3:45:25is important, we'll soon be ready to
3:45:27test it out. So, there's two new backend
3:45:30routes. The first one gets access to all
3:45:33the project specifications for a
3:45:34specific project, and then the second
3:45:37one actually creates a downloadable
3:45:39markdown text. And the AI sidebar got
3:45:42updated so we can test it all out. Back
3:45:45within the browser, if you now head over
3:45:47to specs and click generate spec, you'll
3:45:50notice that, at least on my end,
3:45:52nothing's happening.
3:45:54On your end, it might be working. Maybe
3:45:56your agent did a bit of a better job,
3:45:58but at least here, it's not budging. But
3:46:01thankfully, we are getting some errors
3:46:03right here within the terminal.
3:46:05So, I'll go ahead and copy these errors
3:46:08and tell it when I click the generate
3:46:11spec button, it doesn't do anything. And
3:46:14this is what I see in the terminal. I
3:46:15pasted it and then I'll say analyze it
3:46:19and provide a solution.
3:46:21Refer to trigger.dev and liveblocks
3:46:25skills if needed and press enter.
3:46:28I find it that sometimes it's great to
3:46:30explicitly mention that it can refer to
3:46:32specific skills if you think it's going
3:46:34to be useful. Yeah, this definitely took
3:46:36some time. But yeah, it says right here
3:46:39just to restart your server. It did some
3:46:43additional changes, but I think after
3:46:45all the changes, it's necessary to just
3:46:47reload it. So that's what I'm going to
3:46:49do. Back on localhost 3000, it's going
3:46:51to be active.
3:46:53Make sure to reload the page and then
3:46:55head over to specs.
3:46:58Click generate spec
3:47:00and it is generating. While it is
3:47:02actually generating, you can head over
3:47:05to trigger.dev under runs and you can
3:47:08see generate spec is being executed as
3:47:10we speak. So here you can see a bit more
3:47:13information on exactly what is
3:47:14happening. This is the payload that we
3:47:16sent into it, all the different nodes,
3:47:19all the information about the
3:47:20architecture and more. And it looks like
3:47:23it generated it and returned it. So now
3:47:25if you head back, you'll be able to see
3:47:27a new markdown file that when you click
3:47:30on it, it'll be opened within a markdown
3:47:32previewer right here in the browser.
3:47:36There we go. It's a bit tiny. We can
3:47:38definitely increase its width.
3:47:40But yeah, I mean, take a look at this.
3:47:42This architecture diagram, it was able
3:47:45to generate a pretty hefty technical
3:47:47specification document that outlines
3:47:50this huge system that we can send over
3:47:52to our planning agent and start
3:47:54developing it. Let's test the download
3:47:56functionality as well. I clicked the
3:47:58button and it opened it up. It's a long
3:48:01file, but if you've been following along
3:48:04with this course, you know that
3:48:05specifications have to be long,
3:48:08especially if you're creating one spec
3:48:10for the entire system. So, that's it.
3:48:13That's the full Ghost AI feature set
3:48:15working end to end. So, what do you say
3:48:18that we test out the functionalities one
3:48:20more time on two separate screens? At
3:48:22least right now, I'm on two separate
3:48:24tabs, so still logged in with the same
3:48:26account, but that should be okay.
3:48:28So, I'll say final test and create a new
3:48:32project.
3:48:34That project will immediately show up
3:48:36right here as well after reloading the
3:48:39page.
3:48:40And now we are viewing it from both
3:48:42sides.
3:48:44Both of us are in the same room, and you
3:48:45can see my name
3:48:48and cursor pointing different locations.
3:48:51Of course, if it were some other people
3:48:53right here, you'll be able to see their
3:48:55profiles as well.
3:48:57And as soon as I drag and drop
3:48:58something, you can see that it appears
3:49:01immediately. It also auto zoomed in
3:49:03right here, and we can continue creating
3:49:06the diagram. You can see it happens in
3:49:09real time. And that's the beauty of
3:49:11Liveblocks. What we can do next is test
3:49:14the AI functionalities. So, I'll open up
3:49:17the AI window right here,
3:49:20and let's go with something simple and
3:49:22default like design an e-commerce
3:49:24backend.
3:49:26I'll first select all of these by
3:49:27holding the shift key and then remove
3:49:29them.
3:49:31And design that e-commerce backend
3:49:33by sending a new AI chat message. It'll
3:49:37start working on it,
3:49:39and very quickly it'll generate the full
3:49:42UI right here. And that UI got generated
3:49:46for the other user as well. Finally, we
3:49:48can generate some specs out of it. And
3:49:50of course, this is happening in the
3:49:52background as a task that is being run
3:49:55on the trigger.dev side of things. Or
3:49:58specifically, we are running an agent,
3:50:00in this case Gemini, to process
3:50:02something. But, of course, you can run
3:50:04all sorts of different AI agents, media
3:50:06processing, media generation. Like,
3:50:08there's tons of different use cases for
3:50:10how Trigger can be used, and we can
3:50:12definitely explore some more of it in
3:50:14one of the upcoming videos.
3:50:16But, yeah, the spec is here. We can
3:50:18preview it, download it, and that's it.
3:50:22That is the entire app. So, in the next
3:50:25lesson, let's get it deployed.
— Deployment
3:50:30Okay. Now that the app is complete,
3:50:32before we deploy, we need to switch a
3:50:35few services from development to
3:50:37production.
3:50:39Dev keys are rate-limited and aren't
3:50:41meant for real traffic. So, we have to
3:50:44update Liveblocks, Trigger.dev, and make
3:50:46sure that every environment variable is
3:50:49set correctly in Vercel. Let's start
3:50:51with Liveblocks.
3:50:52Create a new project and call it Ghost
3:50:56AI production or something like that.
3:50:59And set the environment to production.
3:51:02Then, from the API key section, simply
3:51:05copy the public key,
3:51:07head over into your .env.local,
3:51:10and paste it right here.
3:51:12Liveblocks public key, you can override
3:51:15the development one, or in case you want
3:51:17to keep it, you can just comment it out.
3:51:19But, again, make sure to add new
3:51:22production ones right here. Or at least
3:51:24when we're deploying the project on
3:51:25Vercel, you'll have to put these new
3:51:27updated ones.
3:51:29So, yeah, let's go ahead and update it.
3:51:32Uh the Liveblocks public key is going to
3:51:34be this new prod key right here.
3:51:37And also, let's generate a secret key,
3:51:40which you can copy,
3:51:42and then also override right here.
3:51:45Step two is get the Trigger.dev
3:51:47production API keys by heading over to
3:51:50the dashboard, and then in the top
3:51:52selector right here, where it says
3:51:55environment, switch it over from
3:51:57development to production. Then, from
3:52:01the project settings right here under
3:52:03API keys,
3:52:04go ahead and copy the secret key and
3:52:07replace the existing one right here
3:52:09under ENV local.
3:52:11Once again, I will comment out the
3:52:12previous one and just update this one
3:52:15that's going to have prod right here in
3:52:17the middle. And below the API keys, you
3:52:19can head over to general project
3:52:21settings and copy the project ref, which
3:52:24I believe should be the same. So, if you
3:52:27duplicate it
3:52:29and override it here, you'll see that
3:52:31one is actually the same, so we can
3:52:33leave it as it is. Now, go ahead and
3:52:35copy all of your ENVs
3:52:38and we have to push our changes.
3:52:40So, run git add dot, git commit {dash}
3:52:44m,
3:52:45finalize the app and prepare it for
3:52:48deployment, and then run git push.
3:52:51Then, head over to Vercel, click add a
3:52:54new project, and just import it from
3:52:56git. Once pushed, it should have changes
3:52:59a couple of seconds ago, not a couple of
3:53:01hours ago. But, the reason it says 5
3:53:03hours here is because we've pushed the
3:53:05changes to our dev branch. So, for them
3:53:09to show up on Vercel, we first have to
3:53:10push them over to the main branch by
3:53:13creating a new PR.
3:53:16And in this case, there were so many
3:53:17changes that we've done, most of which
3:53:19are actually agent skills that we
3:53:22installed. So, there's going to be 1.7
3:53:24million lines of code to review. In this
3:53:27case, what I'm going to do just to get
3:53:29it deployed is I will first fix the
3:53:32conflict we have with Copilot. I'll just
3:53:35tell it to completely remove the current
3:53:38issues.md file,
3:53:41which should fix the conflict,
3:53:42obviously.
3:53:44That's because we're hoping not to have
3:53:46any more issues. It's going to look into
3:53:48it and start implementing it right away.
3:53:51And as soon as it has been removed, we
3:53:53are ready to merge the PR,
3:53:55which means that the latest changes
3:53:57under the Ghost AI project should be
3:53:59reflected momentarily. And after a
3:54:02reload, they say just now. So, we can go
3:54:05ahead and import it.
3:54:07And immediately add the environment
3:54:10variables by pasting all of them into
3:54:12this first key and value pair, and it'll
3:54:14automatically populate all of them. So,
3:54:17go ahead and scroll down and click
3:54:19deploy.
3:54:20Now, we've implemented so many
3:54:22individual features within this
3:54:24application,
3:54:25integrated and wired up so many services
3:54:28across front-end and back-end and Prisma
3:54:30databases.
3:54:32It would be a miracle if it worked first
3:54:34try. So, just as I said that, in about 8
3:54:37seconds, the initial build failed saying
3:54:40that we can find a full log right here.
3:54:43So, I'll go ahead and inspect the
3:54:44deployment, and it looks like it just
3:54:47failed on running npm install.
3:54:50So, typically when we face an error this
3:54:52early with the npm install, most likely
3:54:55culprit is going to be the
3:54:57package-lock.json.
3:54:59So, what I like to do in this situation
3:55:01is just make sure that we are on the
3:55:04main branch, and we are, and want to
3:55:07delete the package-lock.json entirely,
3:55:10and then push to the main without it by
3:55:12running git add dot, git commit {dash} m
3:55:15remove package-lock.json,
3:55:19and want to get push.
3:55:21This is going to push it straight over
3:55:22to main, which means that it should
3:55:24automatically re-trigger another
3:55:26deployment. And this time, it is
3:55:28building past second eight, successfully
3:55:32installing the dependencies. So, this
3:55:33time we've gotten a bit further, but it
3:55:36still breaks right here on Prisma.
3:55:39Uh this is a typical procedure. Whenever
3:55:41you're publishing a Next.js project on
3:55:44Vercel with Prisma, you also have to add
3:55:47an additional script to the
3:55:49package.json.
3:55:50That's going to be right here.
3:55:52Right after lint, go ahead and add a
3:55:55post
3:55:56install script where you can run Prisma
3:56:00generate. So, once again, you can make
3:56:03that change
3:56:04and push it over to the main branch.
3:56:08As I told you, it's going to be unlikely
3:56:10that the build is going to succeed from
3:56:12the first try. As we've been developing
3:56:14this project for so long locally and in
3:56:17development version, and now we want to
3:56:19run it in production.
3:56:21But, don't worry, we're going to get
3:56:22there. And third time's the charm
3:56:25because the status is now ready and we
3:56:27are live. So, go to the project overview
3:56:31and visit your URL, which brings us back
3:56:34to our auth and home page at the same
3:56:36time, where after you sign in, you are
3:56:39redirected to an empty project screen or
3:56:42since we already logged in with the same
3:56:44account, you can find all of your
3:56:45previous projects. This means that
3:56:48everything is working exactly as it's
3:56:50supposed to, even on the deployed
3:56:52version of the application. So,
3:56:54congrats.
3:56:55This was a long build and quite a
3:56:58different one from the typical ones
3:57:00where we manually code everything out.
3:57:02So, I want to hear from you.
3:57:04How did you like this new approach?
3:57:06Would you like to dive even deeper
3:57:08within the Agenty course that I'm
3:57:10working on?
3:57:11If so, you can join the waitlist. And if
3:57:13you haven't already, also go ahead and
3:57:15download that six-file contact system
3:57:17for Agenty development that I prepared
3:57:19for you alongside this video. That way,
3:57:22you can use the same system we followed
3:57:24along with this build and implement it
3:57:27with your future builds. And yeah, huge
3:57:29thanks to Trigger for amazing background
3:57:32tasks and AI agent functionalities that
3:57:35they allow you to add to your app. Also,
3:57:37huge thanks to Liveblocks for allowing
3:57:39us to make our applications interactive.
3:57:42And finally, to Clerk for simplifying
3:57:44authentication and user management.
3:57:47Thank you not only for sponsoring this
3:57:49video, but for developing such amazing
3:57:51developer tools that I get to share with
3:57:54you guys watching this video.
3:57:56That was it for this one. I'll see you
3:57:58within jsmastey.com
3:58:00or if not there, within one of the
3:58:03upcoming videos on the YouTube channel.
3:58:05Once again, thank you for watching and
3:58:08have a wonderful day.