Over the last two years, AI coding assistants have found their way into practically all our lives, much to the chagrin of those who swore they’d never use them (hi, that’s me too, a redeemed ex-skeptic). They work wonderfully on HTML, CSS, plain JavaScript and pure functions. Then you feed them a React component with five hooks talking to each other, or a Vue computed() hooked to state mutated three files away, and suddenly the assistant seems to have been drinking.
This isn’t an impression gathered at the bar after a meetup. There’s a precise reason, and it’s the same reason those frameworks trip up us humans too. The difference is that, with experience, we’ve developed a mental callus that helps us avoid certain traps. A model has no such callus, so it falls for them with enthusiasm.
The problem isn’t syntax, it’s magic
Language models generate code by predicting the next piece of text based on what they see around it. They do well when the behavior can be deduced from what’s right in front of them: a pure function explains itself, you look at the input and the output and you’re done. They do poorly when the behavior depends on something that isn’t written anywhere in the file they’re reading at that moment.
In recent years, reactive web frameworks have all moved in the same direction: more automatic magic, less hand-written code, all very convenient as long as it works. For an experienced developer it’s often a triumph: three lines and the update graph builds itself. For a model that has to generate correct code without being able to mentally execute that invisible graph, it’s an obstacle course in the dark.
A practical example. In Vue 3, destructuring a reactive object silently kills reactivity:
javascript
const state = reactive({ count: 0 })
// Stops updating. No error, no warning, just silence
const { count } = state
// This, instead, works just fine
const count = computed(() => state.count)Code language: JavaScript (javascript)
Anyone who has used Vue for a while knows this and avoids it almost by spinal reflex. A model that generates plausible code based on common JavaScript patterns, where destructuring an object is harmless 99% of the time, falls for it every time. And the same story repeats, with variations on the theme, for React’s useEffect dependency arrays (forget one and you get a silent bug, add one too many and you get a loop) or for the automatic tracking in computed().
Widening the family: Solid and Angular
To those who insist that “Solid’s signals are just a get/set, it’s not magic,” I answer: that’s true as long as you read a single signal in isolation. The trouble starts when you compose them. A memo or an effect in Solid tracks, at runtime, which signals it read during its own execution, without you declaring it anywhere. It’s the same principle as Vue’s automatic tracking, just moved up one level. And when one signal triggers another, which triggers an effect, which rewrites the first signal, the propagation graph isn’t written down anywhere in the code: it has to be reconstructed by following reads and writes at runtime. For a model, it’s a riddle from the same family as Vue’s, just wearing a different outfit.
Angular, for completeness, manages to suffer from two ailments at once. On one hand, it has embraced Solid-style signals too, bringing along the same implicit tracking. On the other, it keeps on being Angular: decorators, dependency injection, modules, and a verbosity that calling “generous” would be an understatement.
Here someone will object: with context windows now in the hundreds of thousands of tokens, who cares if Angular is verbose, there’s plenty of room anyway. True, but that’s not the point. The problem is no longer “running out of memory,” it’s the density of signal relative to noise. Two hundred lines of boilerplate to say “this component receives an input and exposes an output” means two hundred lines across which the model has to spread its attention, and the business logic that really matters ends up diluted among decorators, providers and repetitive configuration. It’s the same phenomenon known as “lost in the middle”: the more noise around the critical detail, the more likely that detail gets less attention, not because there’s no room but because the information density is low. The practical result: a greater chance of losing the thread between one decorator and the next, especially in long files.
Why web standards fare better
AI assistants generate better code on HTML, CSS and flat JavaScript for the exact same reason they struggle with the more “magical” frameworks: the less inference is needed to predict what happens, the better. A function that takes a state and returns a new one is an open book. An event handler that does exactly what it says it does, likewise. A system that tracks dependencies by eavesdropping on which variables get read during an execution, much less so.
This isn’t an attack on web frameworks per se, let me be clear: React, Vue, Svelte, Solid and Angular remain powerful tools, used successfully at enormous scale, and nobody is proposing to go back to building sites with document.write. It’s an observation about a trade-off that until now we’ve evaluated almost exclusively from the human point of view (how much I write, how convenient it is) and that today deserves to be looked at also from the point of view of whoever, more and more often, writes that code in pairs with a model, or has the model write it directly.
What this means in practice, today
A few heuristics I use myself when working with an AI assistant.
The more a framework hides “who needs to update when what changes,” the more carefully the generated code should be re-read. React hooks, Vue composables and Solid computations are prime candidates for subtle bugs slipped in by the AI without malice, simply out of ignorance of the implicit context.
Explicit code is easier to verify, for a model as well as for a human. A good test: ask the AI to explain why a piece of code does what it does. If the explanation requires guessing at invisible behavior not present in the file in front of you, you’ve found a delicate spot, and not just for the AI: it will be for the colleague who opens that file in six months without the context in their head.
Some projects are openly betting on more explicit architectures (state as plain data, rendering as a pure function, no proxies or compilers in between) precisely to stay readable by both humans and machines. Inglorious Web, the open-source framework I work on, is one of these attempts, but certainly not the only one: the same logic has long driven minimalist libraries like Alpine.js or “HTML-first” approaches like Htmx. Whether this path becomes the norm or remains a niche for diehards, it’s still a sign of where part of the frontend architecture conversation is heading these days.
An open question (and no crusade)
I’m not suggesting we all go back to hand-written vanilla JavaScript like in 2010, perhaps with jQuery to complete the nostalgic trip. “Magical” frameworks offer real advantages in human productivity that bare standards can’t easily replicate, and for many teams the trade-off is still worth it.
But it’s worth asking whether, when choosing an architecture today, the criterion “how readable and predictable is it, even for a model” should start to weigh as much as “how convenient is it for an experienced human.” Because in 2026, code increasingly no longer comes from a single human author carrying all the traps in memory: it comes from a human and a model working side by side, and readability for both stops being a stylistic whim. It becomes a property of the architecture itself, much to the chagrin of magic.

