mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-06-16 08:26:52 +08:00
feat(rules): add vue and nuxt rule sets (#2250)
* feat(rules): add vue and nuxt rule sets Add rules/vue/ and rules/nuxt/, each with the standard 5-file layout (coding-style, hooks, patterns, security, testing) that extends common/, following the Adding a New Language convention in rules/README.md. Vue rules reference the frontend-patterns and vite-patterns skills. Nuxt rules reference the nuxt4-patterns and vite-patterns skills. Content is concise (1.5 to 4 KB per file) since rules load as always-on context. * fix(rules): address PR review on vue and nuxt rule sets - nuxt/coding-style: generalize the srcDir-override note (drop project-specific 'this repo' phrasing so it is correct for any Nuxt project). - vue/hooks: add **/*.ts and **/*.tsx to paths so the lint/typecheck guidance loads when editing composables and stores. - nuxt/hooks: add **/*.vue to paths (covers pages/layouts/components) and wrap nuxi typecheck in a timeout, mirroring web/hooks.md. - nuxt/security: tighten the /security-review auto-trigger scope to external fetch, credential handling, and sensitive mutations, with examples. - nuxt/testing: correct 'Vitest-only' to note built-in Playwright E2E, and drop the @nuxt/test-utils version pin. - README: register vue and nuxt in the structure tree and install examples. Skipped: 'X specific' -> 'X-specific' hyphenation (all existing rule sets use the unhyphenated form, changing only vue/nuxt would be inconsistent); repeating the 80%/TDD mandate in nuxt/testing (already inherited from common/testing.md).
This commit is contained in:
parent
3a08b0c7a8
commit
5108b20954
@ -16,6 +16,8 @@ rules/
|
|||||||
│ └── security.md
|
│ └── security.md
|
||||||
├── typescript/ # TypeScript/JavaScript specific
|
├── typescript/ # TypeScript/JavaScript specific
|
||||||
├── angular/ # Angular specific
|
├── angular/ # Angular specific
|
||||||
|
├── vue/ # Vue 3 specific
|
||||||
|
├── nuxt/ # Nuxt 4 specific
|
||||||
├── python/ # Python specific
|
├── python/ # Python specific
|
||||||
├── golang/ # Go specific
|
├── golang/ # Go specific
|
||||||
├── web/ # Web and frontend specific
|
├── web/ # Web and frontend specific
|
||||||
@ -36,6 +38,8 @@ rules/
|
|||||||
# Install common + one or more language-specific rule sets
|
# Install common + one or more language-specific rule sets
|
||||||
./install.sh typescript
|
./install.sh typescript
|
||||||
./install.sh angular
|
./install.sh angular
|
||||||
|
./install.sh vue
|
||||||
|
./install.sh nuxt
|
||||||
./install.sh python
|
./install.sh python
|
||||||
./install.sh golang
|
./install.sh golang
|
||||||
./install.sh web
|
./install.sh web
|
||||||
@ -70,6 +74,8 @@ cp -r rules/common ~/.claude/rules/ecc/
|
|||||||
# Install language-specific rules based on your project's tech stack
|
# Install language-specific rules based on your project's tech stack
|
||||||
cp -r rules/typescript ~/.claude/rules/ecc/
|
cp -r rules/typescript ~/.claude/rules/ecc/
|
||||||
cp -r rules/angular ~/.claude/rules/ecc/
|
cp -r rules/angular ~/.claude/rules/ecc/
|
||||||
|
cp -r rules/vue ~/.claude/rules/ecc/
|
||||||
|
cp -r rules/nuxt ~/.claude/rules/ecc/
|
||||||
cp -r rules/python ~/.claude/rules/ecc/
|
cp -r rules/python ~/.claude/rules/ecc/
|
||||||
cp -r rules/golang ~/.claude/rules/ecc/
|
cp -r rules/golang ~/.claude/rules/ecc/
|
||||||
cp -r rules/web ~/.claude/rules/ecc/
|
cp -r rules/web ~/.claude/rules/ecc/
|
||||||
|
|||||||
47
rules/nuxt/coding-style.md
Normal file
47
rules/nuxt/coding-style.md
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
---
|
||||||
|
paths:
|
||||||
|
- "**/nuxt.config.*"
|
||||||
|
- "**/app.config.*"
|
||||||
|
- "**/app.vue"
|
||||||
|
- "**/pages/**"
|
||||||
|
- "**/layouts/**"
|
||||||
|
- "**/middleware/**"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Nuxt Coding Style
|
||||||
|
|
||||||
|
> This file extends [common/coding-style.md](../common/coding-style.md) with Nuxt specific content.
|
||||||
|
|
||||||
|
## Directory layout
|
||||||
|
|
||||||
|
- Default `srcDir` is `app/`. Framework files live at `app/pages/`, `app/layouts/`, `app/middleware/`, `app/plugins/`, `app/app.config.ts`. `nuxt.config.ts` and `server/` stay at project root.
|
||||||
|
- Some projects override `srcDir` to `src/` for a Feature-Sliced Design layout, remapping `dir.pages` (for example to `src/app/routes`), `dir.layouts`, and the `@`/`~` aliases. Always check `nuxt.config.ts` before assuming a path.
|
||||||
|
|
||||||
|
## Auto-imports discipline
|
||||||
|
|
||||||
|
- Composables in `app/composables/` and `server/utils/` auto-import. Do NOT manually import Nuxt composables (`useFetch`, `useState`, `navigateTo`) or `defineStore` / `storeToRefs`.
|
||||||
|
- Do NOT add a standalone `vue-router` dep (Nuxt bundles v5) or hand-mount `createApp` / `createPinia` / `createRouter`. The framework wires these.
|
||||||
|
|
||||||
|
## Compiler macros
|
||||||
|
|
||||||
|
- `definePageMeta` is a compile-time macro. Static values only, no reactive data and no side-effect calls inside it.
|
||||||
|
- Augment typed `PageMeta` via `declare module '#app'` rather than casting.
|
||||||
|
|
||||||
|
## Config file separation
|
||||||
|
|
||||||
|
Three distinct files, do not conflate.
|
||||||
|
|
||||||
|
- `nuxt.config.ts` = build-time only (`routeRules`, `modules`, `nitro`, `ssr` flag). Not reactive.
|
||||||
|
- `runtimeConfig` (inside nuxt.config) = per-env runtime values, env-overridable via `NUXT_*`. Root keys are server-only, `public` keys are client-visible.
|
||||||
|
- `app/app.config.ts` = public build-fixed reactive settings (theme tokens, feature flags). No env override. NEVER secrets.
|
||||||
|
|
||||||
|
## Head and meta
|
||||||
|
|
||||||
|
- `app.head` in `nuxt.config.ts` takes static values only.
|
||||||
|
- Reactive meta goes through `useHead` / `useSeoMeta` in component setup, never via `app.head`.
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- ECC skills: `nuxt4-patterns`, `vite-patterns`, `frontend-patterns`.
|
||||||
|
- [Nuxt directory structure](https://nuxt.com/docs/guide/directory-structure/app)
|
||||||
|
- [Nuxt configuration](https://nuxt.com/docs/api/nuxt-config)
|
||||||
39
rules/nuxt/hooks.md
Normal file
39
rules/nuxt/hooks.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
paths:
|
||||||
|
- "**/nuxt.config.*"
|
||||||
|
- "**/app.config.*"
|
||||||
|
- "**/server/**/*.ts"
|
||||||
|
- "**/*.vue"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Nuxt Hooks
|
||||||
|
|
||||||
|
> This file extends [common/hooks.md](../common/hooks.md) with Nuxt specific content.
|
||||||
|
|
||||||
|
These are Claude Code harness hooks for Nuxt work. They run via the harness, not Claude.
|
||||||
|
|
||||||
|
## Typecheck
|
||||||
|
|
||||||
|
- `nuxi typecheck` wraps `vue-tsc`. Requires `vue-tsc` + `typescript` dev deps.
|
||||||
|
- Run on `.vue` / `.ts` edit or pre-commit. Typecheck is project-wide, so debounce it and wrap it in a timeout (mirror `web/hooks.md`, for example `timeout 60 nuxi typecheck`) so a hung type-check is reaped instead of accumulating across fast edits.
|
||||||
|
|
||||||
|
## Lint
|
||||||
|
|
||||||
|
- Use the `@nuxt/eslint` module (flat-config, project-aware, generates `.nuxt/eslint.config.mjs`).
|
||||||
|
- Run `eslint .` or `eslint --fix`. This is the Nuxt-official ESLint integration, prefer it over hand-rolled configs.
|
||||||
|
|
||||||
|
## Format
|
||||||
|
|
||||||
|
- `prettier --write`, or enable stylistic rules in `@nuxt/eslint` to avoid a Prettier/ESLint conflict.
|
||||||
|
- Pick one formatting authority. Do not run both Prettier and ESLint stylistic at once.
|
||||||
|
|
||||||
|
## Suggested PostToolUse chain
|
||||||
|
|
||||||
|
- On Edit to `app/**` and `server/**`: run `eslint --fix` then `timeout 60 nuxi typecheck`.
|
||||||
|
- Order matters: lint-fix first (mutates the file), the timed typecheck second (verifies the result). Debouncing still applies.
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- ECC skills: `nuxt4-patterns`, `vite-patterns`.
|
||||||
|
- [@nuxt/eslint module](https://eslint.nuxt.com/)
|
||||||
|
- [nuxi typecheck](https://nuxt.com/docs/api/commands/typecheck)
|
||||||
54
rules/nuxt/patterns.md
Normal file
54
rules/nuxt/patterns.md
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
paths:
|
||||||
|
- "**/nuxt.config.*"
|
||||||
|
- "**/app.config.*"
|
||||||
|
- "**/app.vue"
|
||||||
|
- "**/server/**/*.ts"
|
||||||
|
- "**/pages/**"
|
||||||
|
- "**/middleware/**"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Nuxt Patterns
|
||||||
|
|
||||||
|
> This file extends [common/patterns.md](../common/patterns.md) with Nuxt specific content.
|
||||||
|
|
||||||
|
## Data-fetch selection
|
||||||
|
|
||||||
|
Load-bearing. Pick by render timing, not habit.
|
||||||
|
|
||||||
|
- `useFetch(url)` = SSR-safe, URL-first initial/first-paint data. The default. Forwards the server result through the payload so there is no hydration double-fetch.
|
||||||
|
- `useAsyncData(key, fn)` = SSR-safe, custom async logic (SDK / GraphQL / combined calls). The explicit key shares the result across components.
|
||||||
|
- `$fetch` = client interactions only (form submit, button click, POST/PUT/DELETE). NOT SSR-safe, double-fetches if used for first paint.
|
||||||
|
- Rule: `useFetch` / `useAsyncData` for anything rendered on first paint, `$fetch` only for event-driven mutations.
|
||||||
|
|
||||||
|
## Shared state
|
||||||
|
|
||||||
|
- `useState('key', () => init)` for SSR-safe shared state. Values must be JSON-serializable.
|
||||||
|
- NEVER `export const x = ref()` at module scope. One shared instance leaks across concurrent SSR requests and causes a memory leak.
|
||||||
|
- With `@pinia/nuxt`: Pinia for domain state, `useState` for small cross-component primitives.
|
||||||
|
- Async server-side init goes in `callOnce(async () => {...})`, not as a side effect inside `useAsyncData`.
|
||||||
|
|
||||||
|
## Nitro server routes
|
||||||
|
|
||||||
|
- `server/api/*.{get,post}.ts` auto-register by path + method. Handler is `defineEventHandler((event) => ...)`.
|
||||||
|
- Errors via `throw createError({ status, statusText })`. Prefer the Web-API `status` / `statusText` over deprecated `statusCode` / `statusMessage`.
|
||||||
|
- `server/middleware/` must NOT return a response. Only mutate `event.context` or set headers.
|
||||||
|
|
||||||
|
## Route middleware
|
||||||
|
|
||||||
|
- `app/middleware/*.ts` with `defineNuxtRouteMiddleware((to, from) => ...)`.
|
||||||
|
- Use the `to` / `from` args. Do NOT call `useRoute()` inside middleware.
|
||||||
|
- `.global` suffix runs on every route. Return `navigateTo()` to redirect, `abortNavigation()` to stop.
|
||||||
|
|
||||||
|
## Hydration-safe rendering
|
||||||
|
|
||||||
|
- Route off `status` (`idle | pending | success | error`) for lazy fetches.
|
||||||
|
- `useAsyncData` payload uses `devalue` (Date/Map/Set/refs survive). A `server/api` response is `JSON.stringify`-only, so define `toJSON()` for non-JSON types.
|
||||||
|
- Shrink payload with `pick` / `transform`. This reduces serialized size, it does not skip the fetch.
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- ECC skills: `nuxt4-patterns`, `vite-patterns`, `frontend-patterns`.
|
||||||
|
- [Nuxt data fetching](https://nuxt.com/docs/getting-started/data-fetching)
|
||||||
|
- [Nuxt state management](https://nuxt.com/docs/getting-started/state-management)
|
||||||
|
- [Nuxt server engine (Nitro)](https://nuxt.com/docs/guide/directory-structure/server)
|
||||||
48
rules/nuxt/security.md
Normal file
48
rules/nuxt/security.md
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
paths:
|
||||||
|
- "**/nuxt.config.*"
|
||||||
|
- "**/app.config.*"
|
||||||
|
- "**/server/**/*.ts"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Nuxt Security
|
||||||
|
|
||||||
|
> This file extends [common/security.md](../common/security.md) with Nuxt specific content.
|
||||||
|
|
||||||
|
## runtimeConfig public vs private
|
||||||
|
|
||||||
|
- Root `runtimeConfig` keys are server-only. `runtimeConfig.public` serializes into EVERY page payload (client-visible).
|
||||||
|
- Secrets go at root only. Never put secrets in `app.config.ts` or `runtimeConfig.public`, both ship to the client bundle.
|
||||||
|
- Official warning: "Be careful not to expose runtime config keys to the client-side by either rendering them or passing them to `useState`."
|
||||||
|
|
||||||
|
## Server-route input validation
|
||||||
|
|
||||||
|
- Use h3 validating readers. Do NOT trust raw `readBody` / `getQuery` / `getRouterParam`.
|
||||||
|
- `readValidatedBody(event, schema)` validates the body.
|
||||||
|
- `getValidatedQuery(event, schema)` validates the query.
|
||||||
|
- `getValidatedRouterParams(event, schema)` validates route params.
|
||||||
|
- All accept a validation function or a Zod schema and throw on failure.
|
||||||
|
|
||||||
|
## SSR payload leakage
|
||||||
|
|
||||||
|
- Anything in `useState`, `useFetch` / `useAsyncData` results, or `runtimeConfig.public` is serialized into the client payload. Never write a secret into those.
|
||||||
|
- Use `useServerSeoMeta` for server-only meta with no client cost.
|
||||||
|
|
||||||
|
## Cookie and auth passthrough on SSR
|
||||||
|
|
||||||
|
- Nuxt does NOT auto-attach the incoming user's cookies to outbound server-side `$fetch`.
|
||||||
|
- Forward explicitly with `useRequestFetch()` (cleanest, pre-bound to request headers) or `useRequestHeaders(['cookie'])`.
|
||||||
|
- Relay a backend `Set-Cookie` to the browser via `$fetch.raw` + `appendResponseHeader(event, 'set-cookie', ...)`.
|
||||||
|
- socket.io is client-only (`.client.ts` plugin), never SSR.
|
||||||
|
|
||||||
|
## SSRF on server $fetch
|
||||||
|
|
||||||
|
- Server routes run with full network egress. Never pass user-controlled input directly into a server-side `$fetch` URL or host.
|
||||||
|
- Validate the param first (h3 utilities above), allowlist the target, pin to `runtimeConfig.public.apiBase`, reject user-supplied absolute URLs.
|
||||||
|
- Auto-trigger `/security-review` only for routes that make external network requests (server `$fetch`), handle auth tokens or credentials, or perform sensitive mutations or authorization checks. Examples: SSRF-prone proxy endpoints, token exchange or password reset, admin actions. Skip benign read-only routes that only accept validated query params.
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- ECC skills: `security-review`, `nuxt4-patterns`.
|
||||||
|
- [Nuxt runtime config](https://nuxt.com/docs/guide/going-further/runtime-config)
|
||||||
|
- [h3 request utils](https://v1.h3.dev/utils/request)
|
||||||
49
rules/nuxt/testing.md
Normal file
49
rules/nuxt/testing.md
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
---
|
||||||
|
paths:
|
||||||
|
- "**/nuxt.config.*"
|
||||||
|
- "**/server/**/*.ts"
|
||||||
|
- "**/pages/**"
|
||||||
|
- "**/layouts/**"
|
||||||
|
- "**/middleware/**"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Nuxt Testing
|
||||||
|
|
||||||
|
> This file extends [common/testing.md](../common/testing.md) with Nuxt specific content.
|
||||||
|
|
||||||
|
Package: `@nuxt/test-utils`. Vitest-first for unit and component tests, with built-in Playwright browser E2E support. nuxt-vitest and vitest-environment-nuxt are superseded and folded into it.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
- Install dev deps: `@nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core`.
|
||||||
|
- Config: `defineVitestConfig({ test: { environment: 'nuxt' } })` from `@nuxt/test-utils/config`. Use `defineVitestProject` for multi-project (separate unit / nuxt / e2e environments).
|
||||||
|
- Add `@nuxt/test-utils/module` to `nuxt.config`. Per-file opt-in via `// @vitest-environment nuxt`.
|
||||||
|
|
||||||
|
## Runtime helpers
|
||||||
|
|
||||||
|
Import from `@nuxt/test-utils/runtime`.
|
||||||
|
|
||||||
|
- `mountSuspended(component, opts)` mounts in the Nuxt env with async setup + plugin injection (accepts `@vue/test-utils` mount options + `route`).
|
||||||
|
- `renderSuspended(component, opts)` is the Testing Library variant (needs `@testing-library/vue`).
|
||||||
|
- `mockNuxtImport(name, factory)` mocks auto-imports (e.g. `useState`). Once per import per file, use `vi.hoisted()`.
|
||||||
|
- `mockComponent(name, factory)` mocks by PascalCase name or path.
|
||||||
|
- `registerEndpoint(path, handler|opts)` mocks a Nitro endpoint to test server routes or stub the backend. Supports method + `once`.
|
||||||
|
|
||||||
|
## E2E helpers
|
||||||
|
|
||||||
|
Import from `@nuxt/test-utils/e2e`.
|
||||||
|
|
||||||
|
- `await setup({ rootDir, server, browser, ... })` inside the describe block (manages beforeAll/afterAll).
|
||||||
|
- Then `$fetch(url)` (rendered HTML), `fetch(url)` (response object), `url(path)` (full URL with port), `createPage(url)` (Playwright).
|
||||||
|
- Playwright integration: import `expect` / `test` from `@nuxt/test-utils/playwright`.
|
||||||
|
|
||||||
|
## What to test how
|
||||||
|
|
||||||
|
- Composables: mock auto-imports with `mockNuxtImport`, mount a host component via `mountSuspended` to exercise `useState` / `useFetch` in the Nuxt runtime.
|
||||||
|
- Server routes: `registerEndpoint` to stub, or e2e `$fetch` / `fetch` against the real Nitro server.
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- ECC skills: `nuxt4-patterns`, `e2e-testing`, `vite-patterns`.
|
||||||
|
- [Nuxt testing docs](https://nuxt.com/docs/getting-started/testing)
|
||||||
|
- [@nuxt/test-utils npm](https://www.npmjs.com/package/@nuxt/test-utils)
|
||||||
54
rules/vue/coding-style.md
Normal file
54
rules/vue/coding-style.md
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
paths:
|
||||||
|
- "**/*.vue"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Vue Coding Style
|
||||||
|
|
||||||
|
> This file extends [common/coding-style.md](../common/coding-style.md) with Vue specific content.
|
||||||
|
|
||||||
|
## SFC Structure
|
||||||
|
|
||||||
|
- Always `<script setup lang="ts">` with the Composition API. No Options API in new code.
|
||||||
|
- Block order inside a `.vue` file: `<script setup>`, then `<template>`, then `<style scoped>`. One component per file.
|
||||||
|
- Naming: component files PascalCase (`AuctionCard.vue`), composables camelCase prefixed `useXxx` (`useAuctionTimer`).
|
||||||
|
- Format with Prettier plus ESLint flat config using `eslint-plugin-vue` (`vue/vue3-recommended`). Type-check with `vue-tsc`.
|
||||||
|
|
||||||
|
## Reactivity Discipline
|
||||||
|
|
||||||
|
- `ref` is the primary state API. Mutate via `.value` in script, auto-unwrapped only at template top level.
|
||||||
|
- Nested `ref` inside arrays, `Map`, or `Set` still needs `.value` to read.
|
||||||
|
- Reach for `reactive` only for grouped object state. Never reassign a whole `reactive` object.
|
||||||
|
- Never destructure a `reactive` object or a Pinia store without `toRefs` / `storeToRefs`. Plain destructure silently drops reactivity.
|
||||||
|
|
||||||
|
## Computed and Watchers
|
||||||
|
|
||||||
|
- `computed` getters must be pure: no side effects, no async, no DOM access.
|
||||||
|
- 3.4+ `computed` only triggers when the returned value changes. Return the prior object unchanged when equal to skip downstream updates.
|
||||||
|
- `watch` is lazy. Pass a getter for a reactive property (`watch(() => x.value, ...)`), not the bare reactive object.
|
||||||
|
- `watchEffect` is eager and stops tracking dependencies after its first `await`.
|
||||||
|
|
||||||
|
## Lifecycle and DOM
|
||||||
|
|
||||||
|
- Register lifecycle hooks synchronously inside `setup` (`onMounted`, `onUnmounted`).
|
||||||
|
- Clean up timers, listeners, and subscriptions in `onUnmounted`.
|
||||||
|
- Read or measure the DOM only after `await nextTick()`.
|
||||||
|
|
||||||
|
## Macros and Templates
|
||||||
|
|
||||||
|
- Macros: `defineProps` / `defineEmits` (tuple form `change: [id: number]`), `defineModel` (3.4+) for `v-model`, `withDefaults` or 3.5+ reactive-props-destructure for defaults, `defineExpose` for the public ref API.
|
||||||
|
- Put a `:key` on every `v-for`, a stable unique primitive. Never the array index, never an object.
|
||||||
|
- Never put `v-if` and `v-for` on the same element. Wrap with `<template v-for>` plus an inner `v-if`, or precompute a filtered list.
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps<{ id: number }>()
|
||||||
|
const emit = defineEmits<{ change: [id: number] }>()
|
||||||
|
const open = defineModel<boolean>('open', { default: false })
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- ECC skills: `frontend-patterns`, `vite-patterns`.
|
||||||
|
- Docs: https://vuejs.org/api/sfc-script-setup.html · https://vuejs.org/guide/essentials/reactivity-fundamentals.html · https://eslint.vuejs.org/
|
||||||
45
rules/vue/hooks.md
Normal file
45
rules/vue/hooks.md
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
paths:
|
||||||
|
- "**/*.vue"
|
||||||
|
- "**/*.ts"
|
||||||
|
- "**/*.tsx"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Vue Hooks
|
||||||
|
|
||||||
|
> This file extends [common/hooks.md](../common/hooks.md) with Vue specific content.
|
||||||
|
|
||||||
|
## PostToolUse Targets
|
||||||
|
|
||||||
|
Run on `*.vue`, `*.ts`, and `*.tsx` after edits. Scope to changed files where possible.
|
||||||
|
|
||||||
|
## Typecheck
|
||||||
|
|
||||||
|
- Use `vue-tsc --noEmit` for SFC plus TypeScript checking. Plain `tsc` cannot read `.vue` single-file components, so it must not be the typecheck hook for this project.
|
||||||
|
- Typecheck is project-wide. Debounce or scope it so a save-on-every-keystroke loop does not stall the editor.
|
||||||
|
|
||||||
|
## Lint and Format
|
||||||
|
|
||||||
|
- `eslint --fix` with `eslint-plugin-vue` (flat-config `vue/vue3-recommended`) covers both template and script lint.
|
||||||
|
- `prettier --write` for formatting. Prefer Prettier-via-ESLint over a separate Prettier pass to avoid double formatting and fight loops.
|
||||||
|
|
||||||
|
## Architecture Boundaries
|
||||||
|
|
||||||
|
- Optional: enforce Feature-Sliced Design slice boundaries with `@feature-sliced/steiger` or `eslint-plugin-boundaries` to block deep cross-slice imports.
|
||||||
|
|
||||||
|
## Sequencing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# changed files only
|
||||||
|
eslint --fix "$FILE"
|
||||||
|
prettier --write "$FILE"
|
||||||
|
# project-wide, debounced
|
||||||
|
vue-tsc --noEmit
|
||||||
|
```
|
||||||
|
|
||||||
|
- Run lint and format per-file first, then the project-wide typecheck last so type errors reflect the formatted source.
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- ECC skills: `frontend-patterns`, `vite-patterns`.
|
||||||
|
- Docs: https://github.com/vuejs/language-tools (vue-tsc) · https://eslint.vuejs.org/ · https://github.com/feature-sliced/steiger
|
||||||
56
rules/vue/patterns.md
Normal file
56
rules/vue/patterns.md
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
---
|
||||||
|
paths:
|
||||||
|
- "**/*.vue"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Vue Patterns
|
||||||
|
|
||||||
|
> This file extends [common/patterns.md](../common/patterns.md) with Vue specific content.
|
||||||
|
|
||||||
|
## Composables
|
||||||
|
|
||||||
|
- The composable (`useXxx`) is the reusable-logic unit. In Feature-Sliced Design it lives in the slice `model` segment.
|
||||||
|
- Accept `MaybeRefOrGetter<T>` inputs and normalize with `toValue`, so callers can pass a ref, a getter, or a raw value.
|
||||||
|
- Return `toRefs(reactive(...))` so consumers can destructure without losing reactivity.
|
||||||
|
- A composable that uses lifecycle hooks or `provide` / `inject` must be called inside a component `setup`, not lazily or conditionally.
|
||||||
|
|
||||||
|
## Props, Emits, v-model
|
||||||
|
|
||||||
|
- Type-based `defineProps<Props>()` and tuple-form `defineEmits<{ change: [id: number] }>()`.
|
||||||
|
- `defineModel<T>('name', { default })` for two-way binding. It compiles to a prop plus an `update:*` emit.
|
||||||
|
|
||||||
|
## Provide / Inject
|
||||||
|
|
||||||
|
- Use `provide` / `inject` for tree-scoped data without prop drilling.
|
||||||
|
- Type-safe collision-free keys: `const key = Symbol() as InjectionKey<T>`.
|
||||||
|
- The provider owns mutations. Expose a `readonly` ref plus an explicit updater function, never a raw mutable ref.
|
||||||
|
|
||||||
|
## Pinia (FSD model segment)
|
||||||
|
|
||||||
|
- Prefer setup stores: `ref` is state, `computed` is getters, `function` is actions.
|
||||||
|
- Setup stores do not get `$reset` for free. Define your own.
|
||||||
|
- Use `storeToRefs` for state and getters. Destructure actions directly off the store.
|
||||||
|
- Never persist raw auth tokens to `localStorage`.
|
||||||
|
|
||||||
|
## vue-router
|
||||||
|
|
||||||
|
- Lazy-load route components with dynamic `import()`.
|
||||||
|
- A global `beforeEach` auth gate keyed on `meta.requiresAuth`. Guards return `false` (cancel), a route location (redirect), or `undefined` / `true` (continue).
|
||||||
|
- Watch `() => route.params.id`, not the whole `route` object.
|
||||||
|
|
||||||
|
## vue-query (server cache)
|
||||||
|
|
||||||
|
- `@tanstack/vue-query` owns server-cache state. Pinia owns client state.
|
||||||
|
- Put request functions plus `queryOptions` factories in the FSD `api` segment.
|
||||||
|
- Critical: put the ref or computed ITSELF in the query key, never `.value`. Passing `.value` freezes the key and kills reactive refetch.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
useQuery({ queryKey: ['auction', id], queryFn: () => fetchAuction(toValue(id)) })
|
||||||
|
// after a mutation
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['auction', id] })
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- ECC skills: `frontend-patterns`, `vite-patterns`.
|
||||||
|
- Docs: https://pinia.vuejs.org/ · https://router.vuejs.org/ · https://tanstack.com/query/latest/docs/framework/vue/overview · https://vuejs.org/guide/reusability/composables.html
|
||||||
46
rules/vue/security.md
Normal file
46
rules/vue/security.md
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
paths:
|
||||||
|
- "**/*.vue"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Vue Security
|
||||||
|
|
||||||
|
> This file extends [common/security.md](../common/security.md) with Vue specific content.
|
||||||
|
|
||||||
|
## What Vue Escapes Automatically
|
||||||
|
|
||||||
|
- Text interpolation `{{ }}` and dynamic attribute bindings (`:title`) are auto-escaped. The vectors below are NOT protected.
|
||||||
|
|
||||||
|
## Rule No.1: Templates from Trusted Sources Only
|
||||||
|
|
||||||
|
- Never use non-trusted content as a component template. No runtime template compilation from user input.
|
||||||
|
- No user-controlled `:is` that resolves a component from an arbitrary string.
|
||||||
|
|
||||||
|
## v-html and Render Functions
|
||||||
|
|
||||||
|
- `v-html` bypasses escaping and is a direct XSS vector. Avoid it on user content.
|
||||||
|
- If unavoidable, sanitize with DOMPurify (allowlist config) before binding, or render in a sandboxed iframe. Vue itself recommends sanitizing on the backend before persisting.
|
||||||
|
- Render-function and scoped-slot output carry the same risk. Passing user HTML through `h()` with `innerHTML` is `v-html` by another name. Sanitize first.
|
||||||
|
|
||||||
|
## URL, Style, and Event Injection
|
||||||
|
|
||||||
|
- `:href` and `:src` are not escaped. `javascript:` URLs execute. Validate the scheme, allow `http` / `https` / `mailto` only. Vue docs reference `@braintree/sanitize-url`, but sanitize on the backend before persisting.
|
||||||
|
- `:style` with user input is unsafe (CSS exfiltration). Use object syntax with whitelisted properties, never a raw user string.
|
||||||
|
- Never bind user input to `onclick`, `onfocus`, or any event attribute.
|
||||||
|
|
||||||
|
## Client Bundle Secrets
|
||||||
|
|
||||||
|
- Anything in `import.meta.env.VITE_*` ships to the browser. Keep API keys and tokens server-side.
|
||||||
|
- Use httpOnly cookies for session tokens. Never bundle credentials into the client.
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<!-- unsafe -->
|
||||||
|
<div v-html="userBio" />
|
||||||
|
<!-- safe -->
|
||||||
|
<div v-html="sanitize(userBio)" />
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- ECC skills: `frontend-patterns`, `vite-patterns`.
|
||||||
|
- Docs: https://vuejs.org/guide/best-practices/security.html · https://github.com/cure53/DOMPurify · https://github.com/braintree/sanitize-url
|
||||||
53
rules/vue/testing.md
Normal file
53
rules/vue/testing.md
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
---
|
||||||
|
paths:
|
||||||
|
- "**/*.vue"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Vue Testing
|
||||||
|
|
||||||
|
> This file extends [common/testing.md](../common/testing.md) with Vue specific content.
|
||||||
|
|
||||||
|
## Stack
|
||||||
|
|
||||||
|
- Vitest (Vite-native runner) plus `@vue/test-utils`. `create-vue` scaffolds `@vitejs/plugin-vue`.
|
||||||
|
- DOM environment via `happy-dom` or `jsdom`, set in `vite.config.ts` under `test.environment`.
|
||||||
|
|
||||||
|
## Rendering and Async
|
||||||
|
|
||||||
|
- `mount` for a full render. `shallowMount` to stub all child components.
|
||||||
|
- `trigger` and `setValue` return promises, `await` them.
|
||||||
|
- `flushPromises` flushes resolved promise handlers. `nextTick` settles the DOM after a state change.
|
||||||
|
|
||||||
|
## What to Test
|
||||||
|
|
||||||
|
- Test the public interface only: props, emitted events, slots, rendered output.
|
||||||
|
- Do not assert private state or internal methods, and do not rely solely on snapshots.
|
||||||
|
|
||||||
|
## Composables
|
||||||
|
|
||||||
|
- Composables that use only reactivity APIs unit-test directly: call the function, assert on the returned refs.
|
||||||
|
- Composables that use lifecycle hooks or `inject` must be tested through a host component.
|
||||||
|
|
||||||
|
## Pinia
|
||||||
|
|
||||||
|
- In components: `createTestingPinia()` from `@pinia/testing`, passed via `global.plugins`. Actions are stubbed by default, set `stubActions: false` to run them. `createSpy: vi.fn` is required under Vitest (no Jest globals).
|
||||||
|
- In isolation: `beforeEach(() => setActivePinia(createPinia()))` gives a fresh store per test and prevents state leakage.
|
||||||
|
|
||||||
|
## Mount Config
|
||||||
|
|
||||||
|
- `global.plugins`, `global.stubs` (stubs `Transition` / `TransitionGroup` by default), `global.mocks` (e.g. `$router`), `global.provide` (for `inject`, Symbol keys supported).
|
||||||
|
- `RouterLinkStub` stubs `router-link` without mounting a full router.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const wrapper = mount(AuctionCard, {
|
||||||
|
props: { id: 1 },
|
||||||
|
global: { plugins: [createTestingPinia({ createSpy: vi.fn })] },
|
||||||
|
})
|
||||||
|
await wrapper.find('button').trigger('click')
|
||||||
|
expect(wrapper.emitted('bid')).toBeTruthy()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- ECC skills: `frontend-patterns`, `vite-patterns`.
|
||||||
|
- Docs: https://test-utils.vuejs.org/api/ · https://pinia.vuejs.org/cookbook/testing.html · https://vitest.dev/
|
||||||
Loading…
x
Reference in New Issue
Block a user