What the model can't see
LLM debugging on the frontend is an observation problem, not a code-reading one.
An LLM debugs your frontend in proportion to what you let it observe, not in proportion to how much of your code it’s read.
The usual way to use one is as a very fast code reader. You hit an error and paste the stack trace and the offending component into a chat window, and something that has seen ten thousand versions of that mistake tells you which line is wrong. It works, and it’s faster than reading the file yourself. If you’re not doing it you’re leaving time on the table for nothing at all.
Notice what kind of bug that is though. It’s the sort where the code is wrong on its face: the missing dependency, the await that isn’t there, the effect that sets the state it depends on and spins forever. Bugs like that are visible in the text, which means you could have found them yourself by reading slowly and the model has just read slowly at a speed you can’t match.
The bugs that cost you a day aren’t like that. The code is fine in every sense that a reviewer would recognise. It’s correct and it’s idiomatic and it would pass anything you put it through. The page still does something stupid on a Tuesday for one customer on Safari. Nothing in the file will tell you why (the file is not where the fault is) because the fault is in the state the browser was in at the moment the code ran, and that browser is four hundred miles away on a laptop you will never touch.
So the expensive part of frontend debugging has never been the fix: it’s the reproduction, and once you can make the thing happen on demand with the elements in front of you and a breakpoint in the right place the fix is usually a few minutes of work and quite often embarrassingly obvious. Which is why the biggest single change you can make to how a model performs here is giving it a browser. Point it at a real page through Playwright or something like it and let it click the thing, read the console, dump the computed styles and take a screenshot. It then stops working from your description of the problem and starts working from the problem itself. I’ve had Claude debug GLSL shaders running in WebGL that way and it isn’t a thing it could have done from source.
The model doesn’t get cleverer when you do any of that. What changes is that a model with nothing to observe will still answer you, and what comes back is the most statistically plausible cause of a bug resembling the one you described. The correct response to a question you haven’t been given enough information to answer is a clarifying question, and that is the response you will almost never get. Anything else rests on an assumption that could be wrong. Sometimes it’ll be right and sometimes it won’t. Deciding which of those you are holding lies entirely with you.
Wrong code can’t fool you for long because you can run it and code that doesn’t work is immediately and obviously broken. A wrong explanation isn’t like that. Nothing executes it and nothing fails, so instead it sits in your head quietly shaping what you look at next. That’s where the damage is, because every plausible fix is locally reasonable. You apply it and the bug is still there, so you say so and you get handed the next most plausible cause instead, and five rounds later you’re holding five speculative changes and a mental model assembled out of the model’s guesses (rather than out of anything you saw happen). The original bug hasn’t moved an inch the whole time.
There’s a second thing it can’t see and that’s the machine. Web devs have always shipped things that aren’t slow on the Macbook they were written on, and then a user on a cheap Windows laptop can’t click anything. A model, by contrast, has no machine at all. Throttle the CPU and throttle the network and run the thing against something like what your users are really holding, because a class of bug that doesn’t exist in the environment you handed over is a class of bug it’s never going to find.
Data tells the same story. Bugs live in the long name and the empty array and the thirty second response and the record from 2014 with a null in a column that’s been non-null since 2016. Give the model your seed fixtures and it’ll tell you the component is fine (it is fine) and you’ll be no further forward than when you started.
And then there’s the class where none of this helps at all. Most bugs in shipped software are entirely valid code. Someone wrote something good that does the wrong thing because what the right thing was had never been pinned down properly. No amount of observation surfaces that, because the page is doing exactly what it was built to do, and the model has no access whatsoever to the thing it should have been built to do instead. It won’t flag that situation either, which is the part that should worry you. Ask it why the checkout total is wrong and it’ll find you a rounding error, because you asked it for a bug and it’s an obliging sort of thing. Knowing what correct actually looks like is still your job, and it’s still the hard part of the work.
None of this makes it a small tool, and it would be daft to read it that way. It’s an enormous one, and it’s improving fast enough that any confident statement about where its limits sit will look silly inside a year. The thing worth investing in isn’t the model though. It’s the pipe between your running application and the model, and that’s dull work so most people skip it (and then conclude from the wreckage that the model is bad at debugging).
What you’re left with is the job you always had. You still have to understand the code well enough to fix what you can’t prompt your way out of, you still have to know what right looks like before you can tell whether you’re looking at it, and you still have to be accountable for whatever you commit. All it really removes is the part where you type it in.