A few weeks ago I published vue-enterprise, an Agent Skill that teaches Claude Code and GitHub Copilot how I want Vue 3 + TypeScript code written in large, long-lived apps. People asked a fair question: does a skill change anything, now that models write decent Vue on their own?
So I measured it. One app, built by an AI agent with no skill, with the community vuejs-ai/skills, and with vue-enterprise. Two runs each, six apps in total.
I wrote one of the three skills, so I built the test to keep my hands off the result. The details are below, and everything (prompts, harness, the six apps, the reviews) is public, so you can check my work.
The app
A small support desk on top of a fake REST API I wrote for the test:
- login, with every other page behind a route guard that sends you back where you wanted to go
- a ticket list with text search, a status filter and pagination
- a ticket detail page with comments and a comment form
- create and edit forms, with client-side validation and the API's 422 errors shown next to the fields
- a "Close ticket" button that updates the UI immediately and rolls back if the request fails
That fits in one agent session, yet it touches everything that separates a demo from a production app: server data, forms, routing, where state lives, tests and security.
Each build had two rounds. Round 1 was the prompt above. Round 2, in a fresh session, was a change request: add an assignee filter, and put search, filters and page in the URL so they survive a reload and work with Back and Forward. How the code takes a change matters as much as how it looks on day one.
Keeping it fair
- Same everything. Every run started from the same
create-vuescaffold (TypeScript, Router, Pinia, Vitest, Playwright, ESLint) with the same fake API, the same prompt, the same model and the same Claude Code version, headless. Nobody touched the code between rounds. - Clean environment. No user settings, plugins or other skills leaked in. The only difference between the arms was the contents of
.claude/skills(plus, for vue-enterprise, the always-on rules file its README tells you to install). - Same nudge. Every prompt ended with "Use any Vue skills available in this project". The vuejs-ai README recommends saying that, so I gave it to all three arms.
- Blind review. A stronger model reviewed the six apps without knowing which was which. It saw "app-K", "app-P" and so on, with a 12-point rubric. I ran the review twice with shuffled names to check that the scores held.
- Black-box tests. I wrote Playwright scripts that use the apps the way a person would, without looking at their code.
The twist: all six apps work
I expected the no-skill apps to break somewhere. They didn't.
| No skill | vuejs-ai/skills | vue-enterprise | |
|---|---|---|---|
type-check, lint, unit tests | ✓ ✓ | ✓ ✓ | ✓ ✓ |
| 20 feature checks in a real browser | 20/20, 20/20 | 20/20, 20/20 | 20/20, 20/20 |
| 8 harder checks (stale data, race conditions, expired session, open redirect, 404, double submit) | 7/8, 8/8 | 8/8, 8/8 | 8/8, 8/8 |
Two runs per arm, so two results in each cell. Today's models write working Vue. If your bar is "does it run and pass the tests it wrote", you may not need a skill at all.
The differences show up in the code.
What the code review found
The blind reviewer scored each app out of 60. Here are the averages of the two reviews:
| No skill | vuejs-ai/skills | vue-enterprise | |
|---|---|---|---|
| Run 1 | 38 | 38.5 | 55 |
| Run 2 | 29 | 44.5 | 57 |
| Average | 33.5 | 41.5 | 56 |
The two reviews never differed by more than four points on any app, and both put the same arm first.
In both reviews the two vue-enterprise apps came first and second, and the second no-skill app came last. vuejs-ai/skills landed in the middle, closer to "no skill" on structure and server data, but clearly better on details such as composables that take MaybeRefOrGetter and cleaning up after themselves.
Three differences explain most of the gap.
1. Server data: a cache, or an object you edit by hand
The no-skill and vuejs-ai apps fetch in each page and keep the result in a ref. To close a ticket optimistically, they flip one field on that object:
// no skill, run 2: src/lib/optimistic.ts return optimistic( () => { ticket.status = 'closed' }, () => { ticket.status = previous }, () => update(ticket.id, { status: 'closed' }), ) It works on the page you're looking at. Every other copy of that ticket (another list page, the list you came from) knows nothing about it until something refetches. In my browser test, a ticket closed while the "Open" filter was on stayed in the "Open" list (marked Closed) in all four apps without vue-enterprise. In the vue-enterprise apps it dropped out of the list.
Both vue-enterprise apps installed Pinia Colada, because the skill says server data is a cache, not state. The close becomes one mutation that updates every cached copy, rolls back only if nothing newer has replaced the optimistic value, and then invalidates so filtered lists correct themselves:
// vue-enterprise, run 2: src/features/tickets/useCloseTicket.ts return useMutation({ mutation: (ticket: Ticket) => api.updateTicket(ticket.id, { status: 'closed' }), onMutate(ticket) { queryCache.cancelQueries({ key: TICKET_KEYS.root }) // ...set the detail entry and every cached list page to "closed" }, onError(_error, ticket) { // ...restore, but only where the cache still holds the optimistic value }, onSettled: () => queryCache.invalidateQueries({ key: TICKET_KEYS.root }), }) Every request also gets an AbortSignal, so a slow, outdated search can't overwrite a newer one. (To be fair, the other apps guarded against that too, with a request counter. They just never cancel the request.)
2. One real bug, in both apps without a skill
Both no-skill apps serve "New ticket" and "Edit ticket" from the same component, which loads its data once, on mount:
// no skill, run 2: src/views/TicketFormView.vue const isEdit = computed(() => !!props.id) // ... onMounted(load) // nothing watches props.id Vue Router reuses the component when you go from /tickets/5/edit to /tickets/new. So if you click "New ticket" in the header while editing ticket 5, the "new" form opens filled in with ticket 5, and Save creates a duplicate. The reviewer spotted it in the code. My browser test confirmed it in both apps.
The vue-enterprise apps used separate create and edit pages around a presentational TicketForm (props in, typed events out), with the edit data coming from a query keyed on the route id. You can't hit that bug with that structure.
3. Accessibility and security nobody asked for
The prompt said nothing about accessibility. The vue-enterprise apps still tied every error to its input with aria-invalid and aria-describedby, gave each "Close ticket" button a label that names the ticket, and one moved focus to the first invalid field. One no-skill app has no aria-invalid anywhere, and its validation errors are plain <span>s a screen reader never announces.
On security, every app blocked the open-redirect trick I tried (/login?redirect=//evil.example), which surprised me. Only the vue-enterprise apps kept the token out of localStorage, which has a cost I come back to below.
Where my skill loses
This part matters more than the scores.
- It costs more. The vue-enterprise runs took about 60% longer (about 4.9 minutes for round 1, against about 3 minutes) and cost about 45% more in tokens (about $1.18 per app at API prices, against $0.80 to $0.84). The agent reads reference files before writing, and writes more code: about 1,330 lines against 850 for no skill.
- A reload logs you out. Both vue-enterprise apps kept the login token in memory, not in
localStorage, because the skill says tokens don't belong in web storage. With this fake API (a bearer token, no cookie session, no refresh token) that means every reload sends you back to the login page. The redirect brings you back to the same URL with your filters, but a user would notice. The skill's real answer is a cookie session or a backend-for-frontend; my fake API didn't offer either, so the agent picked the safe option over the convenient one. In a real project you'd make that call yourself. - It doesn't make things pretty. The skill is about architecture, not design. Both vue-enterprise apps (and one vuejs-ai app) kept the scaffold's green link colour, which fails WCAG contrast; the no-skill apps happened to replace it.
- Small sample. Two runs per arm, one app, one model, one tool. The gap between arms was much larger than the gap between runs of the same arm, but that's not a study. And an AI reviewer, even a blind one, is not a senior engineer's review. I checked its main findings with browser tests and they held, with one exception: it reported a double-submit bug that my test couldn't reproduce.
What I take from it
The model is no longer the bottleneck for "make it work". What a skill adds is the decisions an experienced team would make: where server data lives, how pages and components split, what a form must do for a screen reader, what you never put in localStorage. Without it, the agent makes reasonable local choices, and they add up to an app that works today and is harder to change tomorrow.
The change request showed that. All six apps got the URL filters working. The vue-enterprise apps did it with a tested parseListQuery and back/forward tests, touching only the list, the API layer and the tests. In one vuejs-ai app, the reviewer found a Back-button trap introduced along the way.
If you build long-lived Vue apps with AI, try it: github.com/AlinGhitu/vue-enterprise-skill. If you think the test was unfair, the prompts, the harness, all six apps, the agent transcripts and both reviews are here: evals/comparison-2026-11. Run it with your own skill and tell me what you get.
Originally published on ghitu.com.

