Free YouTube Transcribe

Video transcript

How Senior Engineers Actually Build With AI in 2026 | Build a Full Stack Systems Architecture App

JavaScript Mastery · 40,317 words · 184 min read

Want to search this transcript, jump the video from any line, or download it as TXT, SRT, or VTT?

Open in the transcript tool

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.

This transcript was generated from the captions YouTube publishes for this video. Get the transcript of any YouTube video atfreeyoutubetranscribe.com: free, unlimited, no sign-up.