How Senior Engineers Actually Build With AI in 2026 | Build a Full Stack Systems Architecture App — Transcript
Full transcript
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
About this transcript
This page contains the full transcript of How Senior Engineers Actually Build With AI in 2026 | Build a Full Stack Systems Architecture App by JavaScript Mastery, generated from the public captions YouTube serves with the video. The transcript has 40,317 words across 6,166 segments, with the original timestamps preserved so you can click any line to jump to that moment in the embedded player.
What you can do with it
Use the transcript to take notes, quote the speaker, build a study guide, generate a summary with ChatGPT or Claude via the YouTube Summary tool, or export it as a timed subtitle file with YouTube to SRT. You can also re-open it in the transcriber to translate the transcript into 100+ languages.
Free YouTube transcript tool
YouTube2Text is a free YouTube transcript generator — no signup, no daily limit. Paste any YouTube link and get the full transcript instantly, with timestamps, click-to-jump, translation to 100+ languages, AI prompts for ChatGPT, Claude, and Gemini, and exports to TXT, SRT, VTT, or Markdown.