I have shipped SPAs in Vue and in Angular. Today most of the UI code in my projects is written by a coding agent, and the library underneath is one I wrote myself: @relax.js/core, a small Web Component library with routing, forms, templates and DI, and no virtual DOM.
That is a strange choice on paper. This series is about why it works, where it does not, and what I had to build to make it work.
An agent has seen millions of Vue components and Angular modules in training. Its first draft of a Vue SFC is usually right. Its first draft of a Relaxjs component is usually wrong :O It carries habits over. It marks connectedCallback as async and expects the browser to wait. It reaches for a reactive store. It adds its own submit listener next to the one the library already owns.
The core skill that ships with the library opens with this sentence, because it is the failure mode:
Patterns carried over from React compile, type-check and do nothing.
I have not written React myself, but I have watched agents write it unprompted, and it is what they reach for when nobody tells them otherwise.
There is a second point against me. Angular's compiler type-checks templates. vue-tsc does the same for Vue SFCs. A typo in a template is a build error before anything runs. In a library where the template is a string, {{user.naem}} is just a string until the page renders. I will come back to that in a later article, because it bothered me enough to fix.
So: worse priors, and until recently weaker static checking. Why bother?
An agent cannot open a browser. It reads files, greps for names, runs the type checker and runs the tests. That is the whole feedback loop. Anything that is only observable at runtime is invisible to it.
Now think about the bugs I spent the most time on in Vue and Angular, the ones that survived review:
watch that fired one tick later than I thought, so the DOM showed the previous value.ngOnInit that assumed an @Input that arrives on the next cycle.
None of these are visible in the file where the symptom appears. The cause is in the framework's scheduler, and you find it by opening DevTools, setting a breakpoint and watching. An agent cannot do any of that. It will reason about the file it opened, conclude that the file is correct, and start changing things speculatively. That is the most expensive thing an agent does: producing plausible edits in the wrong place.
Explicit code does not have that class of bug. When the update is this.nameSpan.textContent = user.name at the place where user changed, the update is where the change is. A reviewer can verify it by reading. So can an agent. Nothing decides later whether it ran.
The usual pitch for a small library is "the whole thing fits in the context window". True, and beside the point. An agent rarely needs to read a framework's source; it needs correct memory of the framework's behaviour, and that memory rots with every major version.
The three properties that matter:
Failure locality. The bug is in the file that shows the symptom. No scheduler, no dependency graph, no zone.
Greppability. A typed event class is one grep away from every producer and every consumer. r-click="save()" in a template is one grep away from save. Agents navigate by search, and a connection that is not a shared literal name is a connection the agent will guess at.
Reviewability. The diff says what will happen. Not what the framework will decide to do with it.
Small and explicit is not enough on its own. Three things turned "an agent can in principle work here" into "an agent does work here":
Skills, not docs. The library ships a set of short files an agent loads before it starts, and every sentence in them is something an agent gets wrong from habit. The docs explain how things work; the skills say what you will get wrong. The border between them is stated, and it is worth reading.
Silence turned into errors. A template that cannot resolve a path renders an empty string. For a human that is a blank spot on the page; for an agent it is a green test. Every such quiet failure now reports through one error channel, and a test helper turns the channel into assertions.
A checker. npx @relax.js/core check resolves every template expression against the TypeScript types at the call site and prints tsc-style lines. That closes most of the gap with Angular's template type-checking, and it does so without a compiler in the build.
Then the test seam: `mount()`, `flush()`, `fakeServer()`, `mountRouting()`. The agent verifies by running vitest, not by asking me to click.
If the agent's zero-shot correctness is the whole game, meaning nobody reviews the diff and no skills get loaded, Vue or Angular wins on priors alone. If the app is large with deeply interdependent state, the reactive engine earns its complexity, and I said as much in the library's own README. If you need SSR, there is nothing here for you.
For a small-to-medium SPA where a human reads what the agent wrote, I have found the explicit model cheaper every time, and the rest of this series is the evidence.
Next: what a skill is and why it is not documentation.