Browse Source

fix(skills): Refresh vue-ops to current majors (verifier drift)

Live registry check 2026-07-05: nuxt 4.4.x stable vs documented Nuxt 3.

- Migrate coverage to Nuxt 4: new app/ srcDir directory-structure
  section (aliases resolve to app/, shared/ + #shared, migration note),
  data-fetching semantics (status over deprecated pending, undefined
  defaults, shallow reactivity, keyed dedup + unmount cleanup)
- Keep short 'Nuxt 3 differences' notes where the delta matters
- Flag Nuxt Content v2 queryContent vs v3 queryCollection split
- Zod sample in server routes bumped to Zod 4 top-level z.email()
- vue-facts.json: nuxt documented_major 3 -> 4, gate token 'Nuxt 4'

check-vue-facts.py --offline and --live both exit 0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
0xDarkMatter 1 week ago
parent
commit
017d3a6bd9
3 changed files with 67 additions and 21 deletions
  1. 11 7
      skills/vue-ops/SKILL.md
  2. 3 3
      skills/vue-ops/assets/vue-facts.json
  3. 53 11
      skills/vue-ops/references/nuxt.md

+ 11 - 7
skills/vue-ops/SKILL.md

@@ -1,6 +1,6 @@
 ---
 name: vue-ops
-description: "Vue 3 development patterns, Composition API, Pinia state management, Vue Router, and Nuxt 3. Use for: vue, vuejs, composition api, pinia, vue router, nuxt, nuxt3, script setup, composable, reactive, defineProps, defineEmits, defineModel, v-model, provide inject, vue3."
+description: "Vue 3 development patterns, Composition API, Pinia state management, Vue Router, and Nuxt 4. Use for: vue, vuejs, composition api, pinia, vue router, nuxt, nuxt4, nuxt3, script setup, composable, reactive, defineProps, defineEmits, defineModel, v-model, provide inject, vue3."
 license: MIT
 allowed-tools: "Read Write Bash"
 metadata:
@@ -10,9 +10,9 @@ metadata:
 
 # Vue Operations
 
-Comprehensive Vue 3 reference covering Composition API, Pinia, Vue Router, Nuxt 3, and testing — production patterns with TypeScript throughout.
+Comprehensive Vue 3 reference covering Composition API, Pinia, Vue Router, Nuxt 4, and testing — production patterns with TypeScript throughout.
 
-> Vue 3 / Nuxt 3 ecosystem facts verified as of 2026-07.
+> Vue 3 / Nuxt 4 ecosystem facts verified as of 2026-07-05.
 
 ---
 
@@ -367,7 +367,11 @@ declare module 'vue-router' {
 
 ---
 
-## Nuxt 3 Decision Tree
+## Nuxt 4 Decision Tree
+
+Nuxt 4's flagship change over Nuxt 3 is the `app/` source directory (app code separated
+from `server/` and root config — see [./references/nuxt.md](./references/nuxt.md)); the
+rendering strategies below are unchanged.
 
 ```
 What rendering strategy does my app need?
@@ -450,12 +454,12 @@ What rendering strategy does my app need?
 |------|-------------|
 | [./references/composition-api.md](./references/composition-api.md) | Composables, provide/inject, template refs, custom directives, Teleport, Suspense, slots, transitions, v-model deep patterns |
 | [./references/state-routing.md](./references/state-routing.md) | Pinia advanced patterns (plugins, SSR, store composition), Vue Router (guards, meta typing, scroll behavior, transitions) |
-| [./references/nuxt.md](./references/nuxt.md) | Nuxt 3 data fetching, server routes, middleware, plugins, modules, SEO, deployment, Nuxt Content |
+| [./references/nuxt.md](./references/nuxt.md) | Nuxt 4 directory structure, data fetching, server routes, middleware, plugins, modules, SEO, deployment, Nuxt Content |
 | [./references/testing.md](./references/testing.md) | Vitest setup, Vue Test Utils, Pinia/Router testing, composable testing, MSW, Playwright, Nuxt test utils |
 
 ## Staleness Verifier
 
-This skill encodes fast-moving facts (the Vue 3 minor-version gates, the Nuxt 3
+This skill encodes fast-moving facts (the Vue 3 minor-version gates, the Nuxt 4
 meta-framework, the ecosystem package stack). [`scripts/check-vue-facts.py`](scripts/check-vue-facts.py)
 guards them against silent drift — internal consistency in PR CI, live
 major-version drift in the scheduled freshness job:
@@ -466,7 +470,7 @@ major-version drift in the scheduled freshness job:
 python3 skills/vue-ops/scripts/check-vue-facts.py --offline        # exit 0 consistent, 10 drift
 
 # Live (weekly freshness job, never blocks a PR): is any documented major
-# now behind npm's latest dist-tag? (e.g. Nuxt 4 while the prose says Nuxt 3.)
+# now behind npm's latest dist-tag? (e.g. Nuxt 5 while the prose says Nuxt 4.)
 python3 skills/vue-ops/scripts/check-vue-facts.py --live           # exit 10 a major moved ahead, 7 npm unreachable
 ```
 

+ 3 - 3
skills/vue-ops/assets/vue-facts.json

@@ -1,5 +1,5 @@
 {
-  "_comment": "Canonical fast-moving facts the vue-ops skill encodes. scripts/check-vue-facts.py asserts SKILL.md + references name these consistently (--offline) and probes the npm registry for major-version drift (--live). Edit deliberately: a change here is a skill-content decision, not housekeeping. documented_major is the major the skill's prose commits to (vue 3, nuxt 3) or the current tracked major for ecosystem libs the prose names without pinning a version.",
+  "_comment": "Canonical fast-moving facts the vue-ops skill encodes. scripts/check-vue-facts.py asserts SKILL.md + references name these consistently (--offline) and probes the npm registry for major-version drift (--live). Edit deliberately: a change here is a skill-content decision, not housekeeping. documented_major is the major the skill's prose commits to (vue 3, nuxt 4) or the current tracked major for ecosystem libs the prose names without pinning a version.",
   "schema": "claude-mods.vue-ops.facts/v1",
   "as_of": "2026-07-05",
   "vue_major": 3,
@@ -8,11 +8,11 @@
     "define_model": "Vue 3.4",
     "define_options": "Vue 3.3",
     "use_template_ref": "Vue 3.5",
-    "nuxt_major": "Nuxt 3"
+    "nuxt_major": "Nuxt 4"
   },
   "packages": {
     "vue":               { "documented_major": 3, "prose": ["Vue 3"],              "role": "core (Composition API, <script setup>)" },
-    "nuxt":              { "documented_major": 3, "prose": ["Nuxt 3"],             "role": "meta-framework (SSR/SSG/ISR, App Router host)" },
+    "nuxt":              { "documented_major": 4, "prose": ["Nuxt 4"],             "role": "meta-framework (SSR/SSG/ISR, app/ srcDir host)" },
     "pinia":             { "documented_major": 3, "prose": ["pinia"],              "role": "store" },
     "vue-router":        { "documented_major": 5, "prose": ["vue-router"],         "role": "routing" },
     "@vueuse/core":      { "documented_major": 14, "prose": ["VueUse"],            "role": "composable collection" },

+ 53 - 11
skills/vue-ops/references/nuxt.md

@@ -1,17 +1,50 @@
-# Nuxt 3 Reference
+# Nuxt 4 Reference
 
-Production patterns for Nuxt 3: rendering modes, data fetching, server routes, middleware, plugins, modules, SEO, deployment, and Nuxt Content.
+Production patterns for Nuxt 4: directory structure, rendering modes, data fetching, server routes, middleware, plugins, modules, SEO, deployment, and Nuxt Content.
 
 ---
 
 ## Architecture Overview
 
-Nuxt 3 is built on:
+Nuxt 4 is built on:
 - **Nitro** — universal server engine (runs on Node, Cloudflare Workers, Deno, Bun, etc.)
 - **Vite** — fast dev server and build tool
-- **Vue 3** — Composition API throughout
+- **Vue 3** — Composition API throughout (Nuxt 4.4+ ships Vue Router v5)
 - **Auto-imports** — no need to import `ref`, `computed`, `useFetch`, etc. — Nuxt imports them automatically
-- **File-based routing** — `pages/` directory maps to routes
+- **File-based routing** — `app/pages/` directory maps to routes
+
+---
+
+## Directory Structure (the Nuxt 4 change)
+
+Application code lives under `app/` (the srcDir); server code and config stay at the root. This separates app-environment code from server-environment code — faster dev-server boot, better IDE type inference per environment.
+
+```
+my-app/
+├── app/                  # srcDir — everything that runs in the Vue app
+│   ├── assets/
+│   ├── components/
+│   ├── composables/
+│   ├── layouts/
+│   ├── middleware/       # route middleware (client navigation)
+│   ├── pages/
+│   ├── plugins/
+│   ├── utils/
+│   ├── app.vue
+│   └── error.vue
+├── public/
+├── server/               # stays at root — Nitro (api/, routes/, middleware/)
+├── shared/               # code shared by app/ and server/ — import via #shared
+├── nuxt.config.ts
+└── package.json
+```
+
+Key rules:
+- **`~/` and `@/` resolve to `app/`**, not the project root. Helpers that sat at root level and were imported with `~/` need to move into `app/` (or `shared/`) or get an explicit alias.
+- **`shared/`** is the sanctioned home for code both environments import (types, validators, constants) via the `#shared` alias — server code must not import from `app/`.
+- **Auto-imports scan `app/`** — `app/composables/`, `app/components/`, `app/utils/` work exactly as their root-level Nuxt 3 counterparts did.
+
+**Nuxt 3 differences**: in Nuxt 3 all of these directories sat at the project root and `~/` pointed to the root. Nuxt 4 auto-detects the old flat layout and keeps working with it, so migration is mechanical — move the app directories (plus `app.vue` / `error.vue`) into `app/` and fix any `~/` imports that referenced root-level files.
 
 ---
 
@@ -61,12 +94,17 @@ export default defineNuxtConfig({
 
 ### useFetch — SSR-safe primary fetching
 
+Nuxt 4 semantics: `data`/`error` default to `undefined` (Nuxt 3 used `null`), results are
+shallow-reactive by default (`deep: false`), requests sharing a key are deduplicated and
+their state is cleaned up when the last consuming component unmounts. The `pending` ref is
+deprecated — branch on `status` (`'idle' | 'pending' | 'success' | 'error'`) instead.
+
 ```vue
 <script setup lang="ts">
 interface Post { id: number; title: string; body: string }
 
 // Automatically de-duplicates on server/client, serializes for hydration
-const { data: post, pending, error, refresh } = await useFetch<Post>(
+const { data: post, status, error, refresh } = await useFetch<Post>(
   '/api/posts/1',
   {
     key: 'post-1',                      // deduplicate key (auto-generated if omitted)
@@ -146,11 +184,11 @@ try {
 ```vue
 <script setup lang="ts">
 // lazy: true — don't block render, data loads async
-const { data: comments, pending } = useFetch('/api/comments', { lazy: true })
+const { data: comments, status } = useFetch('/api/comments', { lazy: true })
 </script>
 
 <template>
-  <div v-if="pending" class="skeleton">Loading comments...</div>
+  <div v-if="status === 'pending'" class="skeleton">Loading comments...</div>
   <CommentList v-else :comments="comments" />
 </template>
 ```
@@ -204,7 +242,7 @@ import { z } from 'zod'
 
 const CreateUserSchema = z.object({
   name: z.string().min(2).max(100),
-  email: z.string().email(),
+  email: z.email(),
   role: z.enum(['user', 'admin']).default('user'),
 })
 
@@ -588,7 +626,7 @@ useHead({
 ### Error page (error.vue)
 
 ```vue
-<!-- error.vue — root level, replaces app.vue on error -->
+<!-- app/error.vue — replaces app.vue on error -->
 <script setup lang="ts">
 const props = defineProps<{
   error: {
@@ -747,8 +785,12 @@ export default defineNuxtConfig({
 
 ### Querying content
 
+> The examples below use the Nuxt Content v2 `queryContent()` API. Nuxt Content v3
+> replaces it with typed collections (`content.config.ts` + `queryCollection()`); check
+> which major the project uses before reaching for either API.
+
 ```vue
-<!-- pages/blog/[slug].vue -->
+<!-- app/pages/blog/[slug].vue -->
 <script setup lang="ts">
 const route = useRoute()