{"filename":"vue-pinia-cursorrules-prompt-file.mdc","content":"---\ndescription: \"Vue 3 and TypeScript state management guidance for Pinia stores, Composition API usage, SSR, persistence, and testing.\"\nglobs: **/*.ts, **/*.vue\nalwaysApply: false\n---\nYou are an expert in Vue 3, TypeScript, and Pinia state management.\n\n# Vue + Pinia Guidelines\n\n## State Ownership\n- Keep component-only state in the component with `ref`, `reactive`, or `computed`.\n- Use Pinia for shared client state that spans routes, layouts, or unrelated component trees.\n- Use route params and query strings for shareable navigation state.\n- Use Nuxt `useFetch` / `useAsyncData`, TanStack Query for Vue, or the existing API layer for server state.\n- Do not copy server cache data into Pinia unless it is an intentional editable draft, offline cache, or workflow snapshot.\n\n## Store Structure\n- Prefer setup stores with `defineStore('name', () => { ... })` for Vue 3 Composition API projects.\n- Use one store file per domain under `stores/`, such as `stores/cart.ts` or `stores/session.ts`.\n- Keep state, computed getters, and actions together when they represent one cohesive domain.\n- Return every state property from setup stores so Pinia can track it for devtools, SSR, and plugins.\n- Keep getters pure and side-effect free; put writes, I/O, and orchestration in actions.\n\n```ts\nimport { computed, ref } from 'vue'\nimport { defineStore } from 'pinia'\n\ninterface CartLine {\n  id: string\n  name: string\n  quantity: number\n  unitPrice: number\n}\n\nexport const useCartStore = defineStore('cart', () => {\n  const lines = ref<CartLine[]>([])\n\n  const itemCount = computed(() =>\n    lines.value.reduce((total, line) => total + line.quantity, 0),\n  )\n\n  function addLine(line: CartLine) {\n    const existing = lines.value.find((item) => item.id === line.id)\n    if (existing) {\n      existing.quantity += line.quantity\n      return\n    }\n    lines.value.push(line)\n  }\n\n  function clearCart() {\n    lines.value = []\n  }\n\n  return {\n    lines,\n    itemCount,\n    addLine,\n    clearCart,\n  }\n})\n```\n\n## Component Usage\n- Call stores at the top of `<script setup>` or inside setup functions, getters, and actions.\n- Use `storeToRefs()` when destructuring store state or getters in components.\n- Destructure actions directly when useful; actions remain bound to the store.\n- Avoid writing large business workflows in components; move them to store actions or composables.\n- Prefer computed values over watchers when deriving state.\n\n```vue\n<script setup lang=\"ts\">\nimport { storeToRefs } from 'pinia'\nimport { useCartStore } from '@/stores/cart'\n\nconst cart = useCartStore()\nconst { itemCount } = storeToRefs(cart)\nconst { clearCart } = cart\n</script>\n\n<template>\n  <button type=\"button\" :disabled=\"itemCount === 0\" @click=\"clearCart\">\n    Clear cart\n  </button>\n</template>\n```\n\n## TypeScript\n- Type store state, action payloads, and API responses explicitly.\n- Avoid `any`; use `unknown` and narrow external inputs before committing them to state.\n- Use interfaces for object state that is shared across components or API boundaries.\n- Prefer discriminated unions for workflow status and error state.\n- Keep store IDs stable and descriptive because they appear in devtools and persistence keys.\n\n## Actions and Side Effects\n- Actions may be sync or async; keep each action focused on one user or domain workflow.\n- Validate action inputs at the boundary before mutating store state.\n- Represent async workflows with explicit `status`, `error`, and `lastUpdatedAt` fields when the UI depends on them.\n- Reset stale errors before retrying an async action.\n- Keep subscriptions, intervals, sockets, and browser listeners outside stores unless the store owns their lifecycle and cleanup.\n\n## SSR, Nuxt, and Router Guards\n- In SSR contexts, use the store inside setup, getters, or actions so Pinia can resolve the active app instance.\n- When using a store outside setup, such as in a router guard, pass the active Pinia instance if the framework requires it.\n- Do not read browser-only storage during server rendering.\n- In Nuxt, prefer the Nuxt Pinia integration and SSR-safe composables for data fetching.\n- Avoid singleton state leaks across requests by relying on the framework-created Pinia instance.\n\n## Persistence\n- Persist only the fields that must survive reloads, such as preferences or incomplete local drafts.\n- Never persist secrets, access tokens, refresh tokens, raw PII, or authorization decisions in browser storage.\n- Use field allowlists and versioned migrations for persisted store schemas.\n- Treat persisted state as untrusted input and validate it before using it for critical workflows.\n- Account for hydration timing before rendering UI that depends on persisted values.\n\n## Testing and Tooling\n- Use `@pinia/testing` for component tests that need stores.\n- Test store actions directly for domain behavior and edge cases.\n- Reset Pinia between tests to avoid shared state leakage.\n- Add HMR support with `acceptHMRUpdate()` in stores when the project uses Vite HMR patterns.\n- Keep stores easy to inspect in Vue Devtools by using clear state names and focused stores.\n\n## Anti-Patterns\n- Do not use Pinia as a dumping ground for every reactive value.\n- Do not destructure state directly from a store without `storeToRefs()`.\n- Do not mutate props or route objects through store actions.\n- Do not put server-only objects, request instances, DOM nodes, or timers in store state.\n- Do not create circular reads between stores in setup functions; compose stores through actions or computed values instead.\n"}