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

Leilão de Embriões Nelore PO DNA Genética Aditiva
LANCE RURAL OFICIAL · Portuguese (Portugal, Brazil)

Leilão Peso Pesado Rima Agropecuária
LANCE RURAL OFICIAL · Portuguese (Portugal, Brazil)

النبي .. جبران خليل جبران .. إقرا بودانك
اقرا بودانك · Arabic

Leilão Internacional CIA
LANCE RURAL OFICIAL · Portuguese (Portugal, Brazil)

23° Mega Leilão Genética Aditiva - 1ª Etapa Fêmeas Nelore PO
LANCE RURAL OFICIAL · Portuguese (Portugal, Brazil)

كتاب رسالة الغفران
كتابي المنقذ · Arabic

Erkenntnistheorie 7 Immanuel Kant II
Dominik Finkelde - Hochschule f. Philosophie · English

Schwarze Löcher Erklärt - Von der Geburt bis zum Tod
Dinge Erklärt – Kurzgesagt · German

Como construir uma esfera de Dyson – A Megaestrutura Suprema
Em Poucas Palavras – Kurzgesagt · Portuguese (Portugal, Brazil)

[Histoire des sciences] L’histoire de l’intelligence artificielle (IA)
CEA · French

ميكانيكا الكم│1│الواقع الوهمى - كيف بدأ الكم ؟!
Sharafestien - شرفشتــاين (Sharafestien) · Arabic

Mi niñez fue un fusil AK-47
Comisión de la Verdad · Spanish