1
0 Comments

Vibe coding made writing cheap. It made reading expensive.

A junior I know shipped a change last month. Cursor wrote it. He ran it. Looked fine. Opened the PR.

Senior sent it back in five minutes: "this touches three things we don't have tests for."

He had nothing to say. He didn't write the code. It looked right. He genuinely didn't know what he'd missed — or where you'd even start looking.

He's not dumb. Nobody ever showed him what sits between "looks right" and "I checked."

The myth first

People keep saying AI made writing code free.

It didn't. Tokens cost real money. Run agents in a loop on a decent-sized repo for a week and go look at your bill. Nobody who's done that thinks generation is free.

What AI actually did is narrower than the hype. It made producing a plausible diff fast. It did exactly nothing for the hard part — knowing whether that diff is safe.

So the cost didn't vanish. It moved. Writing got cheaper. Reading got brutal, because now there's ten times more of it and none of it is yours.

Nobody teaches reading

Think about how we train people. Syntax. Hooks. Build a project. All production.

Then they get a job where most of the day is reviewing changes they didn't write, and we act surprised when they can't.

Most people file "can spot a bad change" under experience. Years of it. Can't teach it, you just earn it.

I don't buy that. I've watched enough seniors do it to see it's not magic. It's four moves.

Locate. Which element on screen does this actually affect? Not roughly. Exactly which JSX node.

Trace. Is this a shared definition, or one call site? Shared means everything using it. Call site means just here. This one question decides whether the next move matters.

Blast radius. If shared — who uses it? How many? Any references hiding behind a barrel or an export *, where your editor's search just doesn't look?

Run it. Does it render what you expected? Including the branch that isn't on screen — the other half of that ternary.

Four moves. All teachable. None taught.

"We have tools for this"

Do we? Let's check.

TypeScript catches type mismatches. But AI's signature move is code that types perfectly and sits in exactly the wrong place. Add a legal optional prop to a shared component. All green. Three of five call sites now render a UI element that shouldn't be there.

Unit tests only cover what someone already thought about. The danger with an AI change is that it landed on a call site nobody thought about. Which almost certainly has no coverage — that's the state of UI testing everywhere, not one lazy team.

Storybook, visual regression. These tell you something changed. Never why. Alarms after the fact, and only for components someone bothered to write a story for.

And then the diff view. This is the real problem, and it hides in plain sight.

A diff shows you text. It shows you nothing about position or reach. Three lines feels safe. Three hundred feels scary. And that feeling has zero connection to what either one actually breaks.

Sit with that for a second, because it's the whole thing:

Diff size and blast radius are unrelated. Human alarm scales with diff size anyway.

A three-line change can hit eleven files. A three-hundred-line refactor can be perfectly contained. Your gut has it backwards, and vibe coding means you now get to be wrong about this several times a day.

"Fine, better models will fix it"

Probably not. And not for the reason people assume.

The model sees a window. A few open files, some conversation. From inside that window everything looks isolated. Almost nothing in a real repo is isolated.

Look at what AI actually gets wrong. Same three patterns, forever:

Thinks it's editing one instance. Actually editing the shared definition. Five other places quietly change.

Imports through a path that isn't what it thought, because two barrels sat in between.

Edits a branch that your case doesn't even go through, so the change never renders at all.

None of those are syntax problems. They're all where does this line sit in the system problems. Position. And position is not something a language model has good intuition for. A bigger context window is not a map.

Someone always replies: so give the model the graph. Fair, and we do. It helps.

But two things survive. First, shipping a change is a responsibility question, not an information question — when the AI says "safe," someone still has to be able to check that, or you're trusting a system that bears no consequences. Second, the map has to exist before you can hand it to anyone. Build it first. Then argue about who reads it.

Why good engineers skip the checks anyway

Here's the part that took me a while to see.

The four moves aren't hard to understand. Everyone nods along. And then they don't do them.

Not laziness. Cost.

Locating means eyeballing a wall of JSX hunting for which part of the UI this touches. Inside a .map(), inside a conditional, in a component from another file — that hunt is half your review.

Blast radius means three greps, two of which return false hits from a string match, and none of which catch the reference coming in through a re-export.

Running it means switching to a terminal, maybe installing whatever the AI just pulled in, waiting for a build, waiting for a dev server. Two or three minutes to verify a two-second change. So people read it, decide it looks fine, and ship. That's not a discipline failure. That's an entirely rational response to an absurd ratio.

Layer awareness means rebuilding a component's whole structure in your head. At 6pm. On the fourth PR of the day.

Which is why a better PR template doesn't fix this. A template tells people what to check. It does nothing about what checking costs — or whether the answer you get back is even right.

So we made the checks cheap

But cost was only half of it. The other half is worse.

Even if you're willing to spend the half hour, some of these questions your tools cannot answer correctly.

Run a Find All References on a component in a real template. It returns what it indexed. It does not return the reference that arrived through an export * in a barrel file — there's no textual match to find, and the indexer didn't resolve the chain. You get a list. The list looks complete. It isn't, and nothing tells you that.

So when someone does the diligent thing — greps it, checks the callers, decides it's contained — they can be diligent and wrong at the same time. That's the failure mode that actually bothers me, more than the people who skip the check entirely. At least skipping feels like skipping.

That's the reason CrossUI Studio exists. Not to run the same checks faster. To make a couple of them answerable at all.

Locate is one click. Click the thing on screen, land on the exact JSX node that drew it — through the .map(), through the conditional, through a component defined in a different file. Works backwards too: cursor on a line, and everything it draws lights up on the canvas. Put the cursor inside a .map() and watch eight cards light up at once. That's the whole "one line, many elements" idea, no explanation needed.

Trace is shown, not reconstructed. Click again and you climb: this expression, then the component call, then the call site that passed the props, then the file the component actually lives in. "Shared definition or one call site" stops being a thing you work out in your head. It's just on screen.

Blast radius is a resolved graph, not a text search. We resolve imports the way a bundler would — through barrels, through path aliases, through export *. So "who imports this" returns the references a string search structurally can't reach. Fast is a side effect. Complete is the point.

Running it is instant. No install, no bundler, no dev server. Open a real repo, it renders in the browser. And that branch that isn't on screen? Put your cursor on it and we render it for you. No faking state, no editing test data to see what a VIP sees.

Two of those make a slow check fast. Two of them make a question answerable that wasn't. The second kind is the part I'd care about if I were choosing a tool.

What that looks like in practice

You ask AI for a discount badge on the product card. It adds a conditional inside <ProductCard>. Clean diff. Runs. No errors.

Click the badge. You land inside a .map() — so this isn't one card, it's every card in the list.

Climb up one layer. <ProductCard> is a shared definition. This page doesn't own it.

Check who imports it. Three places: cart, favorites, search results. Search results has no discount field at all.

Open search results, cursor on the new branch, render it. There it is. Empty badge, alignment broken.

Under a minute. No build. Without those four moves that diff merges clean and you hear about it from a user.

The team version of this problem

One more thing, because individual judgment isn't a process.

When half your team runs these checks and half doesn't, whether a bad PR gets caught depends on who got assigned. That's worse than a uniformly weak team, because it looks like something is working.

And it caps how hard you can lean on AI. Without this, you've got two options: throttle AI usage, or accept an incident rate you can't predict. Neither is a strategy.

The uncomfortable version for anyone teaching this

Same problem, one step earlier.

If a junior's day is mostly reviewing code they didn't write, then "hand-write a TodoList" is training the wrong muscle.

Better assignment: here's an AI-generated diff. It runs fine. Something in it is wrong. Find the real blast radius and show your work.

Build the material from the failure patterns above — bait diffs, tiny in lines, huge in reach. Train people out of trusting line count.

Not saying skip learning to write code. Someone who's never hand-written a .map() or been burned by conditional rendering can't judge either. Writing is the floor judgment stands on. It just isn't the whole building. (More on the teaching side in a separate post.)

Where this leaves us

The bottleneck moved. Writing is cheap now. Reading is expensive — and part of it isn't just expensive, it's guesswork wearing the clothes of diligence.

That's the piece I'd want closed before leaning harder on AI. Not because review takes too long. Because "I checked" should mean something.

If you want to try the four moves on something real: CrossUI Studio opens an actual React repo in the browser, no install. Click something and see how far down it goes.

on September 7, 2026