tribunal-kit 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/.agent/ARCHITECTURE.md +99 -99
  2. package/.agent/GEMINI.md +52 -52
  3. package/.agent/agents/accessibility-reviewer.md +187 -220
  4. package/.agent/agents/ai-code-reviewer.md +199 -233
  5. package/.agent/agents/backend-specialist.md +215 -238
  6. package/.agent/agents/code-archaeologist.md +161 -181
  7. package/.agent/agents/database-architect.md +184 -207
  8. package/.agent/agents/debugger.md +191 -218
  9. package/.agent/agents/dependency-reviewer.md +103 -136
  10. package/.agent/agents/devops-engineer.md +218 -238
  11. package/.agent/agents/documentation-writer.md +201 -221
  12. package/.agent/agents/explorer-agent.md +160 -180
  13. package/.agent/agents/frontend-reviewer.md +160 -194
  14. package/.agent/agents/frontend-specialist.md +248 -237
  15. package/.agent/agents/game-developer.md +48 -52
  16. package/.agent/agents/logic-reviewer.md +116 -149
  17. package/.agent/agents/mobile-developer.md +200 -223
  18. package/.agent/agents/mobile-reviewer.md +162 -195
  19. package/.agent/agents/orchestrator.md +181 -211
  20. package/.agent/agents/penetration-tester.md +157 -174
  21. package/.agent/agents/performance-optimizer.md +183 -203
  22. package/.agent/agents/performance-reviewer.md +178 -211
  23. package/.agent/agents/product-manager.md +142 -162
  24. package/.agent/agents/product-owner.md +6 -25
  25. package/.agent/agents/project-planner.md +142 -162
  26. package/.agent/agents/qa-automation-engineer.md +225 -242
  27. package/.agent/agents/security-auditor.md +174 -194
  28. package/.agent/agents/seo-specialist.md +193 -213
  29. package/.agent/agents/sql-reviewer.md +161 -194
  30. package/.agent/agents/supervisor-agent.md +184 -203
  31. package/.agent/agents/swarm-worker-contracts.md +17 -17
  32. package/.agent/agents/swarm-worker-registry.md +46 -46
  33. package/.agent/agents/test-coverage-reviewer.md +160 -193
  34. package/.agent/agents/test-engineer.md +0 -21
  35. package/.agent/agents/type-safety-reviewer.md +175 -208
  36. package/.agent/patterns/generator.md +9 -9
  37. package/.agent/patterns/inversion.md +12 -12
  38. package/.agent/patterns/pipeline.md +9 -9
  39. package/.agent/patterns/reviewer.md +13 -13
  40. package/.agent/patterns/tool-wrapper.md +9 -9
  41. package/.agent/rules/GEMINI.md +63 -63
  42. package/.agent/scripts/compress_skills.py +167 -0
  43. package/.agent/scripts/consolidate_skills.py +173 -0
  44. package/.agent/scripts/deep_compress.py +202 -0
  45. package/.agent/scripts/minify_context.py +80 -0
  46. package/.agent/scripts/security_scan.py +1 -1
  47. package/.agent/scripts/strip_tribunal.py +41 -0
  48. package/.agent/skills/agent-organizer/SKILL.md +92 -126
  49. package/.agent/skills/agentic-patterns/SKILL.md +0 -70
  50. package/.agent/skills/ai-prompt-injection-defense/SKILL.md +126 -160
  51. package/.agent/skills/api-patterns/SKILL.md +123 -215
  52. package/.agent/skills/api-security-auditor/SKILL.md +143 -177
  53. package/.agent/skills/app-builder/SKILL.md +326 -50
  54. package/.agent/skills/app-builder/templates/SKILL.md +13 -15
  55. package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +16 -16
  56. package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +22 -22
  57. package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +18 -18
  58. package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +20 -20
  59. package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +17 -17
  60. package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +18 -18
  61. package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +21 -21
  62. package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +19 -19
  63. package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +26 -26
  64. package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +26 -26
  65. package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +19 -19
  66. package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +18 -18
  67. package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +20 -20
  68. package/.agent/skills/appflow-wireframe/SKILL.md +87 -121
  69. package/.agent/skills/architecture/SKILL.md +82 -252
  70. package/.agent/skills/authentication-best-practices/SKILL.md +139 -173
  71. package/.agent/skills/bash-linux/SKILL.md +120 -154
  72. package/.agent/skills/behavioral-modes/SKILL.md +8 -69
  73. package/.agent/skills/brainstorming/SKILL.md +428 -104
  74. package/.agent/skills/building-native-ui/SKILL.md +143 -174
  75. package/.agent/skills/clean-code/SKILL.md +323 -360
  76. package/.agent/skills/code-review-checklist/SKILL.md +0 -62
  77. package/.agent/skills/config-validator/SKILL.md +107 -141
  78. package/.agent/skills/csharp-developer/SKILL.md +468 -528
  79. package/.agent/skills/database-design/SKILL.md +104 -369
  80. package/.agent/skills/deployment-procedures/SKILL.md +111 -145
  81. package/.agent/skills/devops-engineer/SKILL.md +295 -332
  82. package/.agent/skills/devops-incident-responder/SKILL.md +79 -113
  83. package/.agent/skills/doc.md +5 -5
  84. package/.agent/skills/documentation-templates/SKILL.md +19 -63
  85. package/.agent/skills/edge-computing/SKILL.md +123 -157
  86. package/.agent/skills/extract-design-system/SKILL.md +100 -134
  87. package/.agent/skills/framer-motion-expert/SKILL.md +111 -855
  88. package/.agent/skills/frontend-design/SKILL.md +151 -499
  89. package/.agent/skills/game-design-expert/SKILL.md +71 -105
  90. package/.agent/skills/game-engineering-expert/SKILL.md +88 -122
  91. package/.agent/skills/geo-fundamentals/SKILL.md +89 -124
  92. package/.agent/skills/github-operations/SKILL.md +279 -314
  93. package/.agent/skills/gsap-expert/SKILL.md +119 -826
  94. package/.agent/skills/i18n-localization/SKILL.md +104 -138
  95. package/.agent/skills/intelligent-routing/SKILL.md +159 -127
  96. package/.agent/skills/lint-and-validate/SKILL.md +8 -52
  97. package/.agent/skills/llm-engineering/SKILL.md +344 -357
  98. package/.agent/skills/local-first/SKILL.md +120 -154
  99. package/.agent/skills/mcp-builder/SKILL.md +84 -118
  100. package/.agent/skills/mobile-design/SKILL.md +213 -219
  101. package/.agent/skills/motion-engineering/SKILL.md +184 -0
  102. package/.agent/skills/nextjs-react-expert/SKILL.md +99 -698
  103. package/.agent/skills/nodejs-best-practices/SKILL.md +498 -559
  104. package/.agent/skills/observability/SKILL.md +293 -330
  105. package/.agent/skills/parallel-agents/SKILL.md +88 -122
  106. package/.agent/skills/performance-profiling/SKILL.md +217 -254
  107. package/.agent/skills/plan-writing/SKILL.md +84 -118
  108. package/.agent/skills/platform-engineer/SKILL.md +89 -123
  109. package/.agent/skills/playwright-best-practices/SKILL.md +128 -162
  110. package/.agent/skills/powershell-windows/SKILL.md +112 -146
  111. package/.agent/skills/python-patterns/SKILL.md +7 -35
  112. package/.agent/skills/python-pro/SKILL.md +148 -754
  113. package/.agent/skills/react-specialist/SKILL.md +123 -827
  114. package/.agent/skills/readme-builder/SKILL.md +15 -85
  115. package/.agent/skills/realtime-patterns/SKILL.md +269 -304
  116. package/.agent/skills/red-team-tactics/SKILL.md +10 -51
  117. package/.agent/skills/rust-pro/SKILL.md +623 -701
  118. package/.agent/skills/seo-fundamentals/SKILL.md +120 -154
  119. package/.agent/skills/server-management/SKILL.md +156 -190
  120. package/.agent/skills/shadcn-ui-expert/SKILL.md +172 -206
  121. package/.agent/skills/skill-creator/SKILL.md +18 -58
  122. package/.agent/skills/sql-pro/SKILL.md +579 -633
  123. package/.agent/skills/supabase-postgres-best-practices/SKILL.md +28 -68
  124. package/.agent/skills/swiftui-expert/SKILL.md +142 -176
  125. package/.agent/skills/systematic-debugging/SKILL.md +84 -118
  126. package/.agent/skills/tailwind-patterns/SKILL.md +516 -576
  127. package/.agent/skills/tdd-workflow/SKILL.md +103 -137
  128. package/.agent/skills/test-result-analyzer/SKILL.md +33 -73
  129. package/.agent/skills/testing-patterns/SKILL.md +512 -573
  130. package/.agent/skills/trend-researcher/SKILL.md +30 -71
  131. package/.agent/skills/ui-ux-pro-max/SKILL.md +0 -41
  132. package/.agent/skills/ui-ux-researcher/SKILL.md +51 -91
  133. package/.agent/skills/vue-expert/SKILL.md +127 -866
  134. package/.agent/skills/vulnerability-scanner/SKILL.md +354 -269
  135. package/.agent/skills/web-accessibility-auditor/SKILL.md +159 -193
  136. package/.agent/skills/web-design-guidelines/SKILL.md +17 -61
  137. package/.agent/skills/webapp-testing/SKILL.md +111 -145
  138. package/.agent/skills/whimsy-injector/SKILL.md +58 -132
  139. package/.agent/skills/workflow-optimizer/SKILL.md +28 -68
  140. package/.agent/workflows/api-tester.md +151 -151
  141. package/.agent/workflows/audit.md +127 -138
  142. package/.agent/workflows/brainstorm.md +110 -110
  143. package/.agent/workflows/changelog.md +112 -112
  144. package/.agent/workflows/create.md +124 -124
  145. package/.agent/workflows/debug.md +165 -189
  146. package/.agent/workflows/deploy.md +180 -189
  147. package/.agent/workflows/enhance.md +128 -151
  148. package/.agent/workflows/fix.md +114 -135
  149. package/.agent/workflows/generate.md +12 -4
  150. package/.agent/workflows/migrate.md +160 -160
  151. package/.agent/workflows/orchestrate.md +168 -168
  152. package/.agent/workflows/performance-benchmarker.md +114 -123
  153. package/.agent/workflows/plan.md +173 -173
  154. package/.agent/workflows/preview.md +80 -80
  155. package/.agent/workflows/refactor.md +161 -183
  156. package/.agent/workflows/review-ai.md +101 -129
  157. package/.agent/workflows/review.md +116 -116
  158. package/.agent/workflows/session.md +94 -94
  159. package/.agent/workflows/status.md +79 -79
  160. package/.agent/workflows/strengthen-skills.md +138 -139
  161. package/.agent/workflows/swarm.md +179 -179
  162. package/.agent/workflows/test.md +189 -211
  163. package/.agent/workflows/tribunal-backend.md +93 -113
  164. package/.agent/workflows/tribunal-database.md +94 -115
  165. package/.agent/workflows/tribunal-frontend.md +95 -118
  166. package/.agent/workflows/tribunal-full.md +92 -133
  167. package/.agent/workflows/tribunal-mobile.md +94 -119
  168. package/.agent/workflows/tribunal-performance.md +109 -133
  169. package/.agent/workflows/ui-ux-pro-max.md +122 -143
  170. package/package.json +1 -1
  171. package/.agent/skills/api-patterns/api-style.md +0 -42
  172. package/.agent/skills/api-patterns/auth.md +0 -24
  173. package/.agent/skills/api-patterns/documentation.md +0 -26
  174. package/.agent/skills/api-patterns/graphql.md +0 -41
  175. package/.agent/skills/api-patterns/rate-limiting.md +0 -31
  176. package/.agent/skills/api-patterns/response.md +0 -37
  177. package/.agent/skills/api-patterns/rest.md +0 -40
  178. package/.agent/skills/api-patterns/security-testing.md +0 -122
  179. package/.agent/skills/api-patterns/trpc.md +0 -41
  180. package/.agent/skills/api-patterns/versioning.md +0 -22
  181. package/.agent/skills/app-builder/agent-coordination.md +0 -71
  182. package/.agent/skills/app-builder/feature-building.md +0 -53
  183. package/.agent/skills/app-builder/project-detection.md +0 -34
  184. package/.agent/skills/app-builder/scaffolding.md +0 -118
  185. package/.agent/skills/app-builder/tech-stack.md +0 -40
  186. package/.agent/skills/architecture/context-discovery.md +0 -43
  187. package/.agent/skills/architecture/examples.md +0 -94
  188. package/.agent/skills/architecture/pattern-selection.md +0 -68
  189. package/.agent/skills/architecture/patterns-reference.md +0 -50
  190. package/.agent/skills/architecture/trade-off-analysis.md +0 -77
  191. package/.agent/skills/brainstorming/dynamic-questioning.md +0 -360
  192. package/.agent/skills/database-design/database-selection.md +0 -43
  193. package/.agent/skills/database-design/indexing.md +0 -39
  194. package/.agent/skills/database-design/migrations.md +0 -48
  195. package/.agent/skills/database-design/optimization.md +0 -36
  196. package/.agent/skills/database-design/orm-selection.md +0 -30
  197. package/.agent/skills/database-design/schema-design.md +0 -56
  198. package/.agent/skills/frontend-design/animation-guide.md +0 -331
  199. package/.agent/skills/frontend-design/color-system.md +0 -329
  200. package/.agent/skills/frontend-design/decision-trees.md +0 -418
  201. package/.agent/skills/frontend-design/motion-graphics.md +0 -306
  202. package/.agent/skills/frontend-design/typography-system.md +0 -363
  203. package/.agent/skills/frontend-design/ux-psychology.md +0 -1116
  204. package/.agent/skills/frontend-design/visual-effects.md +0 -383
  205. package/.agent/skills/intelligent-routing/router-manifest.md +0 -65
  206. package/.agent/skills/mobile-design/decision-trees.md +0 -516
  207. package/.agent/skills/mobile-design/mobile-backend.md +0 -491
  208. package/.agent/skills/mobile-design/mobile-color-system.md +0 -420
  209. package/.agent/skills/mobile-design/mobile-debugging.md +0 -122
  210. package/.agent/skills/mobile-design/mobile-design-thinking.md +0 -357
  211. package/.agent/skills/mobile-design/mobile-navigation.md +0 -458
  212. package/.agent/skills/mobile-design/mobile-performance.md +0 -767
  213. package/.agent/skills/mobile-design/mobile-testing.md +0 -356
  214. package/.agent/skills/mobile-design/mobile-typography.md +0 -433
  215. package/.agent/skills/mobile-design/platform-android.md +0 -666
  216. package/.agent/skills/mobile-design/platform-ios.md +0 -561
  217. package/.agent/skills/mobile-design/touch-psychology.md +0 -537
  218. package/.agent/skills/nextjs-react-expert/1-async-eliminating-waterfalls.md +0 -312
  219. package/.agent/skills/nextjs-react-expert/2-bundle-bundle-size-optimization.md +0 -240
  220. package/.agent/skills/nextjs-react-expert/3-server-server-side-performance.md +0 -490
  221. package/.agent/skills/nextjs-react-expert/4-client-client-side-data-fetching.md +0 -264
  222. package/.agent/skills/nextjs-react-expert/5-rerender-re-render-optimization.md +0 -581
  223. package/.agent/skills/nextjs-react-expert/6-rendering-rendering-performance.md +0 -432
  224. package/.agent/skills/nextjs-react-expert/7-js-javascript-performance.md +0 -684
  225. package/.agent/skills/nextjs-react-expert/8-advanced-advanced-patterns.md +0 -150
  226. package/.agent/skills/vulnerability-scanner/checklists.md +0 -121
@@ -1,208 +1,74 @@
1
1
  ---
2
2
  name: vue-expert
3
- description: Vue 3.5+ Composition API mastery. Script setup, reactive refs, computed, watchers, composables, Pinia stores, Vue Router 4, Nuxt 4, Teleport, Transition, provide/inject, TypeScript integration, performance optimization, and testing with Vitest. Use when building Vue applications, designing composables, managing state, or implementing Nuxt patterns.
3
+ description: Vue 3.5+ Composition API. Script setup, reactive refs, computed, watchers, composables, Pinia, Vue Router 4, Nuxt 4. Use when building Vue/Nuxt applications.
4
4
  allowed-tools: Read, Write, Edit, Glob, Grep
5
- version: 2.0.0
6
- last-updated: 2026-03-30
7
- applies-to-model: gemini-2.5-pro, claude-3-7-sonnet
5
+ version: 3.1.0
6
+ last-updated: 2026-04-06
8
7
  ---
9
8
 
10
- # Vue Expert — Vue 3.5+ & Nuxt 4 Mastery
9
+ # Vue 3.5+ & Nuxt 4 — Dense Reference
11
10
 
12
- > Vue 3.5 is Composition API everywhere. Options API is legacy. Vuex is dead. Pinia is the standard.
13
- > Every component uses `<script setup>`. Every store uses Pinia. Every composable returns refs. No exceptions.
11
+ ## Hallucination Traps (Read First)
12
+ - Options API (`data()`, `methods:`, `computed:`) `<script setup lang="ts">`
13
+ - ❌ `defineComponent()` with `<script setup>` → ✅ redundant, skip it
14
+ - ❌ `defineModel` in Vue < 3.4 → ✅ added in 3.4+
15
+ - ❌ `ref.value` in template → ✅ auto-unwrapped in template (no `.value`)
16
+ - ❌ `reactive()` for primitives → ✅ use `ref()` — `reactive()` breaks on reassign
17
+ - ❌ `watch(state.count, ...)` (primitive) → ✅ `watch(() => state.count, ...)`
18
+ - ❌ `onBeforeMount` for data fetch → ✅ use `await` directly in `<script setup>` + `<Suspense>`
19
+ - ❌ Pinia `this.$store` → ✅ `useStore()` from `pinia`
20
+ - ❌ `useRoute()` / `useRouter()` outside setup → ✅ only works inside `<script setup>` or composables
14
21
 
15
22
  ---
16
23
 
17
- ## Script Setup (Mandatory)
24
+ ## `<script setup>` — The Only Way
18
25
 
19
26
  ```vue
20
27
  <script setup lang="ts">
21
- // <script setup> is the ONLY way to write Vue 3.5+ components
22
- // It compiles to a render function with zero boilerplate
23
-
24
- import { ref, computed, onMounted } from "vue";
25
- import { useRouter } from "vue-router";
28
+ import { ref, computed, watch, onMounted } from "vue";
26
29
 
27
30
  // Props
28
- const props = defineProps<{
29
- title: string;
30
- count?: number;
31
- items: string[];
32
- }>();
33
-
34
- // Props with defaults
35
- const props = withDefaults(defineProps<{
36
- title: string;
37
- variant?: "primary" | "secondary";
38
- size?: number;
39
- }>(), {
40
- variant: "primary",
41
- size: 16,
42
- });
43
-
44
- // Emits (typed)
45
- const emit = defineEmits<{
46
- update: [value: string];
47
- delete: [id: number];
48
- "item-click": [item: Item, index: number];
49
- }>();
50
-
51
- // Expose (for parent ref access)
52
- defineExpose({
53
- reset: () => { /* ... */ },
54
- focus: () => { /* ... */ },
55
- });
31
+ const props = defineProps<{ title: string; count?: number }>();
32
+ // With defaults:
33
+ const props = withDefaults(defineProps<{ variant?: "primary" | "secondary" }>(), { variant: "primary" });
56
34
 
57
- // Models (Vue 3.4+ — replaces v-model boilerplate)
58
- const modelValue = defineModel<string>(); // default v-model
59
- const count = defineModel<number>("count"); // named v-model
35
+ // Emits
36
+ const emit = defineEmits<{ update: [value: string]; delete: [id: number] }>();
60
37
 
61
- // HALLUCINATION TRAP: defineModel was added in Vue 3.4+
62
- // Before 3.4, v-model required manual prop + emit boilerplate
63
- // HALLUCINATION TRAP: Do NOT use defineComponent() with <script setup>
64
- // <script setup> IS the setup function — defineComponent is redundant
65
- </script>
66
- ```
38
+ // v-model (Vue 3.4+)
39
+ const modelValue = defineModel<string>(); // default model
40
+ const count = defineModel<number>("count"); // named model
67
41
 
68
- ### Options API vs Composition API
69
-
70
- ```vue
71
- <!-- ❌ LEGACY — Options API (Vue 2 pattern) -->
72
- <script>
73
- export default {
74
- data() { return { count: 0 } },
75
- computed: { doubled() { return this.count * 2 } },
76
- methods: { increment() { this.count++ } },
77
- mounted() { console.log("mounted") },
78
- }
79
- </script>
80
-
81
- <!-- ✅ MODERN — Composition API with <script setup> -->
82
- <script setup lang="ts">
83
- import { ref, computed, onMounted } from "vue";
84
-
85
- const count = ref(0);
86
- const doubled = computed(() => count.value * 2);
87
- function increment() { count.value++; }
88
- onMounted(() => console.log("mounted"));
42
+ // Expose to parent ref
43
+ defineExpose({ reset: () => {}, focus: () => {} });
89
44
  </script>
90
-
91
- <!-- ❌ HALLUCINATION TRAP: Never generate Options API in Vue 3.5+ projects
92
- unless explicitly maintaining legacy code -->
93
45
  ```
94
46
 
95
47
  ---
96
48
 
97
- ## Reactivity System
98
-
99
- ### ref vs reactive
49
+ ## Reactivity
100
50
 
101
51
  ```ts
102
- import { ref, reactive, toRefs, toRef } from "vue";
103
-
104
- // ref — single value (primitive or object)
52
+ // ref for primitives and objects (access via .value in JS, auto-unwrap in template)
105
53
  const count = ref(0);
106
- count.value++; // must use .value in <script>
107
- // In <template>, .value is auto-unwrapped: {{ count }}
108
-
109
- // reactive — object (deep reactive)
110
- const state = reactive({
111
- user: { name: "Alice", age: 30 },
112
- items: ["a", "b", "c"],
113
- });
114
- state.user.name = "Bob"; // direct mutation (no .value needed)
115
-
116
- // ❌ HALLUCINATION TRAP: Destructuring reactive LOSES reactivity
117
- const { name, age } = state.user; // ❌ name and age are NOT reactive
54
+ count.value++;
118
55
 
119
- // Fix: use toRefs
120
- const { name, age } = toRefs(state.user); // name.value and age.value ARE reactive
56
+ // reactive for objects (loses reactivity on reassign/destructure)
57
+ const state = reactive({ name: "Alice", age: 25 });
58
+ // ❌ const { name } = state; // loses reactivity
59
+ // ✅ const name = computed(() => state.name);
121
60
 
122
- // Fix: use toRef for a single property
123
- const name = toRef(state.user, "name");
124
-
125
- // RULE: Prefer ref() for everything. Use reactive() only for complex nested state.
126
- // ref() is more predictable — .value makes reactivity explicit.
127
- ```
128
-
129
- ### Computed
130
-
131
- ```ts
132
- import { ref, computed } from "vue";
133
-
134
- const items = ref<Item[]>([]);
135
- const searchQuery = ref("");
136
-
137
- // Read-only computed
138
- const filteredItems = computed(() =>
139
- items.value.filter((item) =>
140
- item.name.toLowerCase().includes(searchQuery.value.toLowerCase())
141
- )
142
- );
143
-
144
- // Writable computed
61
+ // computed cached, re-runs only when deps change
62
+ const doubled = computed(() => count.value * 2);
145
63
  const fullName = computed({
146
- get: () => `${firstName.value} ${lastName.value}`,
147
- set: (val: string) => {
148
- const [first, ...rest] = val.split(" ");
149
- firstName.value = first;
150
- lastName.value = rest.join(" ");
151
- },
64
+ get: () => `${first.value} ${last.value}`,
65
+ set: (v) => { [first.value, last.value] = v.split(" "); },
152
66
  });
153
- ```
154
-
155
- ### Watchers
156
-
157
- ```ts
158
- import { ref, watch, watchEffect, watchPostEffect } from "vue";
159
67
 
160
- const query = ref("");
161
- const userId = ref(1);
162
-
163
- // watch explicit sources, previous value available
164
- watch(query, (newVal, oldVal) => {
165
- console.log(`Query changed: ${oldVal} → ${newVal}`);
166
- fetchResults(newVal);
167
- });
168
-
169
- // Watch multiple sources
170
- watch([query, userId], ([newQuery, newId], [oldQuery, oldId]) => {
171
- console.log("Query or userId changed");
172
- });
173
-
174
- // Deep watch (for objects/arrays)
175
- watch(
176
- () => state.user,
177
- (newUser) => { console.log("User changed:", newUser); },
178
- { deep: true }
179
- );
180
-
181
- // Immediate watch (runs on mount)
182
- watch(userId, (id) => fetchUser(id), { immediate: true });
183
-
184
- // watchEffect — auto-tracks dependencies
185
- watchEffect(async () => {
186
- // Automatically re-runs when query.value or userId.value changes
187
- const data = await fetch(`/api/search?q=${query.value}&user=${userId.value}`);
188
- results.value = await data.json();
189
- });
190
-
191
- // watchPostEffect — runs after DOM update (replaces watchEffect with flush: 'post')
192
- watchPostEffect(() => {
193
- // Safe to access updated DOM here
194
- scrollToBottom(container.value);
195
- });
196
-
197
- // Cleanup (prevent stale async results)
198
- watchEffect((onCleanup) => {
199
- const controller = new AbortController();
200
- onCleanup(() => controller.abort());
201
-
202
- fetch(`/api/data?q=${query.value}`, { signal: controller.signal })
203
- .then((res) => res.json())
204
- .then((data) => { results.value = data; });
205
- });
68
+ // watch
69
+ watch(count, (newVal, oldVal) => {}); // immediate: false by default
70
+ watch(() => props.id, fetchUser, { immediate: true });
71
+ watchEffect(() => { console.log(count.value); }); // auto-tracks deps
206
72
  ```
207
73
 
208
74
  ---
@@ -210,431 +76,117 @@ watchEffect((onCleanup) => {
210
76
  ## Composables (Custom Hooks)
211
77
 
212
78
  ```ts
213
- // composables/useFetch.ts
214
- import { ref, watchEffect, type Ref } from "vue";
215
-
216
- interface UseFetchReturn<T> {
217
- data: Ref<T | null>;
218
- error: Ref<Error | null>;
219
- isLoading: Ref<boolean>;
220
- refresh: () => Promise<void>;
79
+ // useCounter.ts
80
+ export function useCounter(initial = 0) {
81
+ const count = ref(initial);
82
+ const increment = () => count.value++;
83
+ const reset = () => (count.value = initial);
84
+ return { count: readonly(count), increment, reset };
221
85
  }
222
86
 
223
- export function useFetch<T>(url: Ref<string> | string): UseFetchReturn<T> {
224
- const data = ref<T | null>(null) as Ref<T | null>;
87
+ // useAsyncData.ts
88
+ export function useAsyncData<T>(fn: () => Promise<T>) {
89
+ const data = ref<T | null>(null);
225
90
  const error = ref<Error | null>(null);
226
- const isLoading = ref(false);
227
-
228
- async function fetchData() {
229
- isLoading.value = true;
230
- error.value = null;
231
-
232
- try {
233
- const resolvedUrl = typeof url === "string" ? url : url.value;
234
- const response = await fetch(resolvedUrl);
235
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
236
- data.value = await response.json();
237
- } catch (e) {
238
- error.value = e instanceof Error ? e : new Error(String(e));
239
- } finally {
240
- isLoading.value = false;
241
- }
91
+ const loading = ref(false);
92
+ async function execute() {
93
+ loading.value = true;
94
+ try { data.value = await fn(); }
95
+ catch (e) { error.value = e as Error; }
96
+ finally { loading.value = false; }
242
97
  }
243
-
244
- // Auto-refetch when URL changes (if URL is a ref)
245
- if (typeof url !== "string") {
246
- watchEffect(() => {
247
- if (url.value) fetchData();
248
- });
249
- } else {
250
- fetchData();
251
- }
252
-
253
- return { data, error, isLoading, refresh: fetchData };
254
- }
255
-
256
- // Usage:
257
- const apiUrl = computed(() => `/api/users/${userId.value}`);
258
- const { data: user, isLoading, error } = useFetch<User>(apiUrl);
259
- ```
260
-
261
- ### useLocalStorage Composable
262
-
263
- ```ts
264
- // composables/useLocalStorage.ts
265
- import { ref, watch, type Ref } from "vue";
266
-
267
- export function useLocalStorage<T>(key: string, defaultValue: T): Ref<T> {
268
- const stored = localStorage.getItem(key);
269
- const data = ref<T>(stored ? JSON.parse(stored) : defaultValue) as Ref<T>;
270
-
271
- watch(data, (newVal) => {
272
- localStorage.setItem(key, JSON.stringify(newVal));
273
- }, { deep: true });
274
-
275
- return data;
276
- }
277
-
278
- // Usage:
279
- const theme = useLocalStorage("theme", "dark");
280
- theme.value = "light"; // auto-saves to localStorage
281
- ```
282
-
283
- ### useDebounce Composable
284
-
285
- ```ts
286
- // composables/useDebounce.ts
287
- import { ref, watch, type Ref } from "vue";
288
-
289
- export function useDebounce<T>(source: Ref<T>, delay = 300): Ref<T> {
290
- const debounced = ref(source.value) as Ref<T>;
291
-
292
- let timeout: ReturnType<typeof setTimeout>;
293
- watch(source, (val) => {
294
- clearTimeout(timeout);
295
- timeout = setTimeout(() => { debounced.value = val; }, delay);
296
- });
297
-
298
- return debounced;
98
+ execute();
99
+ return { data, error, loading, refresh: execute };
299
100
  }
300
-
301
- // Usage:
302
- const query = ref("");
303
- const debouncedQuery = useDebounce(query, 500);
304
- // debouncedQuery only updates 500ms after the user stops typing
305
101
  ```
306
102
 
307
103
  ---
308
104
 
309
- ## Pinia (State Management)
310
-
311
- ### Setup Store (Recommended)
105
+ ## Pinia
312
106
 
313
107
  ```ts
314
- // stores/cart.ts
108
+ // stores/counter.ts
315
109
  import { defineStore } from "pinia";
316
- import { ref, computed } from "vue";
317
-
318
- export const useCartStore = defineStore("cart", () => {
319
- // State
320
- const items = ref<CartItem[]>([]);
321
-
322
- // Getters (computed)
323
- const totalItems = computed(() => items.value.length);
324
- const totalPrice = computed(() =>
325
- items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
326
- );
327
- const isEmpty = computed(() => items.value.length === 0);
328
-
329
- // Actions
330
- function addItem(product: Product, quantity = 1) {
331
- const existing = items.value.find((i) => i.productId === product.id);
332
- if (existing) {
333
- existing.quantity += quantity;
334
- } else {
335
- items.value.push({
336
- productId: product.id,
337
- name: product.name,
338
- price: product.price,
339
- quantity,
340
- });
341
- }
342
- }
343
-
344
- function removeItem(productId: string) {
345
- items.value = items.value.filter((i) => i.productId !== productId);
346
- }
347
-
348
- function clearCart() {
349
- items.value = [];
350
- }
351
-
352
- // Async action
353
- async function checkout() {
354
- const response = await fetch("/api/checkout", {
355
- method: "POST",
356
- body: JSON.stringify({ items: items.value }),
357
- });
358
- if (response.ok) clearCart();
359
- return response.json();
360
- }
361
-
362
- return { items, totalItems, totalPrice, isEmpty, addItem, removeItem, clearCart, checkout };
110
+ export const useCounterStore = defineStore("counter", () => {
111
+ const count = ref(0); // Setup Store (preferred)
112
+ const doubled = computed(() => count.value * 2);
113
+ function increment() { count.value++; }
114
+ return { count, doubled, increment };
363
115
  });
364
116
 
365
- // HALLUCINATION TRAP: Do NOT use Vuex in Vue 3.5+ projects
366
- // Vuex is in maintenance mode. Pinia is the official replacement.
367
- // ❌ import { createStore } from "vuex" ← LEGACY
368
- // ✅ import { defineStore } from "pinia"
369
- ```
370
-
371
- ### Using Stores in Components
372
-
373
- ```vue
374
- <script setup lang="ts">
375
- import { useCartStore } from "@/stores/cart";
117
+ // Usage in component:
118
+ const store = useCounterStore();
119
+ // ❌ const { count } = store; // loses reactivity!
120
+ // ✅ const count = storeToRefs(store).count;
376
121
  import { storeToRefs } from "pinia";
122
+ const { count } = storeToRefs(store);
377
123
 
378
- const cartStore = useCartStore();
379
-
380
- // ✅ Use storeToRefs for reactive destructuring of state/getters
381
- const { items, totalPrice, isEmpty } = storeToRefs(cartStore);
382
-
383
- // Actions can be destructured directly (they're not reactive)
384
- const { addItem, removeItem, clearCart } = cartStore;
385
-
386
- // ❌ HALLUCINATION TRAP: Destructuring state WITHOUT storeToRefs loses reactivity
387
- // ❌ const { items, totalPrice } = cartStore; ← NOT reactive!
388
- // ✅ const { items, totalPrice } = storeToRefs(cartStore); ← reactive
389
- </script>
390
-
391
- <template>
392
- <div v-if="isEmpty">Cart is empty</div>
393
- <ul v-else>
394
- <li v-for="item in items" :key="item.productId">
395
- {{ item.name }} — ${{ item.price }} × {{ item.quantity }}
396
- <button @click="removeItem(item.productId)">Remove</button>
397
- </li>
398
- </ul>
399
- <p>Total: ${{ totalPrice.toFixed(2) }}</p>
400
- </template>
401
- ```
402
-
403
- ### Pinia Persistence Plugin
404
-
405
- ```ts
406
- // main.ts
124
+ // Persist plugin:
407
125
  import { createPinia } from "pinia";
408
126
  import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
409
-
410
- const pinia = createPinia();
411
- pinia.use(piniaPluginPersistedstate);
412
-
413
- // In store:
414
- export const useSettingsStore = defineStore("settings", () => {
415
- const theme = ref("dark");
416
- const locale = ref("en");
417
- return { theme, locale };
418
- }, {
419
- persist: true, // auto-saves to localStorage
420
- });
127
+ const pinia = createPinia().use(piniaPluginPersistedstate);
421
128
  ```
422
129
 
423
130
  ---
424
131
 
425
132
  ## Vue Router 4
426
133
 
427
- ### Route Configuration
428
-
429
134
  ```ts
430
135
  // router/index.ts
431
- import { createRouter, createWebHistory, type RouteRecordRaw } from "vue-router";
432
-
433
- const routes: RouteRecordRaw[] = [
434
- {
435
- path: "/",
436
- component: () => import("@/layouts/DefaultLayout.vue"),
437
- children: [
438
- { path: "", name: "home", component: () => import("@/pages/Home.vue") },
439
- { path: "about", name: "about", component: () => import("@/pages/About.vue") },
440
- ],
441
- },
442
- {
443
- path: "/dashboard",
444
- component: () => import("@/layouts/DashboardLayout.vue"),
445
- meta: { requiresAuth: true },
446
- children: [
447
- { path: "", name: "dashboard", component: () => import("@/pages/Dashboard.vue") },
448
- {
449
- path: "users/:id",
450
- name: "user-detail",
451
- component: () => import("@/pages/UserDetail.vue"),
452
- props: true, // pass route params as props
453
- },
454
- ],
455
- },
456
- { path: "/:pathMatch(.*)*", name: "not-found", component: () => import("@/pages/NotFound.vue") },
457
- ];
458
-
136
+ import { createRouter, createWebHistory } from "vue-router";
459
137
  const router = createRouter({
460
138
  history: createWebHistory(),
461
- routes,
462
- scrollBehavior(to, from, savedPosition) {
463
- return savedPosition || { top: 0 };
464
- },
139
+ routes: [
140
+ { path: "/", component: () => import("./views/Home.vue") }, // lazy-loaded
141
+ { path: "/user/:id", component: UserView, props: true }, // props:true passes params as props
142
+ { path: "/:pathMatch(.*)*", component: NotFound }, // 404 catch-all
143
+ ],
465
144
  });
466
-
467
- // Navigation guard
145
+ // Route guards
468
146
  router.beforeEach(async (to, from) => {
469
- const authStore = useAuthStore();
470
-
471
- if (to.meta.requiresAuth && !authStore.isAuthenticated) {
472
- return { name: "login", query: { redirect: to.fullPath } };
473
- }
147
+ if (to.meta.requiresAuth && !isLoggedIn()) return { name: "Login" };
474
148
  });
475
149
 
476
- export default router;
477
- ```
478
-
479
- ### Router Composables
480
-
481
- ```vue
482
- <script setup lang="ts">
150
+ // In component:
483
151
  import { useRouter, useRoute } from "vue-router";
484
-
485
152
  const router = useRouter();
486
153
  const route = useRoute();
487
-
488
- // Reactive route params
489
- const userId = computed(() => route.params.id as string);
490
-
491
- // Programmatic navigation
492
- function goToUser(id: string) {
493
- router.push({ name: "user-detail", params: { id } });
494
- }
495
-
496
- function goBack() {
497
- router.back();
498
- }
499
-
500
- // ❌ HALLUCINATION TRAP: route.params values are always strings
501
- // Even if the URL is /users/123, params.id is "123" (string), not 123 (number)
502
- // Always parse: parseInt(route.params.id as string)
503
- </script>
154
+ router.push({ name: "User", params: { id: 42 } });
155
+ const userId = route.params.id as string;
504
156
  ```
505
157
 
506
158
  ---
507
159
 
508
- ## Component Patterns
509
-
510
- ### Slots
160
+ ## Templates
511
161
 
512
162
  ```vue
513
- <!-- BaseCard.vue -->
514
- <template>
515
- <div class="card">
516
- <header v-if="$slots.header" class="card-header">
517
- <slot name="header" />
518
- </header>
519
-
520
- <div class="card-body">
521
- <slot /> <!-- default slot -->
522
- </div>
523
-
524
- <footer v-if="$slots.footer" class="card-footer">
525
- <slot name="footer" />
526
- </footer>
527
- </div>
528
- </template>
529
-
530
- <!-- Scoped slot (pass data to parent) -->
531
- <!-- DataList.vue -->
532
163
  <template>
533
- <ul>
534
- <li v-for="(item, index) in items" :key="item.id">
535
- <slot name="item" :item="item" :index="index" :is-last="index === items.length - 1">
536
- <!-- Default content if parent doesn't provide slot -->
537
- {{ item.name }}
538
- </slot>
539
- </li>
540
- </ul>
164
+ <!-- v-model -->
165
+ <input v-model="email" />
166
+ <MyInput v-model:title="title" v-model:count="count" /> <!-- named model -->
167
+
168
+ <!-- v-for with key (ALWAYS set key) -->
169
+ <li v-for="item in items" :key="item.id">{{ item.name }}</li>
170
+
171
+ <!-- Dynamic components -->
172
+ <component :is="currentTab" />
173
+
174
+ <!-- Teleport — render in a different DOM node -->
175
+ <Teleport to="body"><Modal v-if="showModal" /></Teleport>
176
+
177
+ <!-- Transition -->
178
+ <Transition name="fade" mode="out-in">
179
+ <component :is="view" :key="view" />
180
+ </Transition>
181
+
182
+ <!-- Suspense (async components / composables with await) -->
183
+ <Suspense><AsyncComponent /><template #fallback>Loading...</template></Suspense>
541
184
  </template>
542
185
 
543
- <!-- Usage with scoped slot -->
544
- <DataList :items="users">
545
- <template #item="{ item, index, isLast }">
546
- <UserCard :user="item" :highlighted="index === 0" />
547
- <hr v-if="!isLast" />
548
- </template>
549
- </DataList>
550
- ```
551
-
552
- ### Provide / Inject (Dependency Injection)
553
-
554
- ```ts
555
- // Parent component
556
- import { provide, ref, type InjectionKey } from "vue";
557
-
558
- interface ThemeContext {
559
- theme: Ref<string>;
560
- toggleTheme: () => void;
561
- }
562
-
563
- export const ThemeKey: InjectionKey<ThemeContext> = Symbol("theme");
564
-
565
- // In parent <script setup>:
566
- const theme = ref("dark");
567
- function toggleTheme() {
568
- theme.value = theme.value === "dark" ? "light" : "dark";
569
- }
570
- provide(ThemeKey, { theme, toggleTheme });
571
-
572
- // Child component (any depth)
573
- import { inject } from "vue";
574
- import { ThemeKey } from "@/keys";
575
-
576
- const themeCtx = inject(ThemeKey);
577
- if (!themeCtx) throw new Error("ThemeKey not provided");
578
- // themeCtx.theme.value === "dark"
579
-
580
- // ❌ HALLUCINATION TRAP: Always use InjectionKey<T> for type safety
581
- // inject("theme") returns unknown — inject(ThemeKey) returns ThemeContext
582
- ```
583
-
584
- ### Teleport
585
-
586
- ```vue
587
- <!-- Render modal content at <body> level to escape overflow/z-index traps -->
588
- <Teleport to="body">
589
- <div v-if="showModal" class="modal-overlay">
590
- <div class="modal-content">
591
- <slot />
592
- <button @click="$emit('close')">Close</button>
593
- </div>
594
- </div>
595
- </Teleport>
596
- ```
597
-
598
- ### Transition
599
-
600
- ```vue
601
- <Transition
602
- name="fade"
603
- mode="out-in"
604
- @before-enter="onBeforeEnter"
605
- @enter="onEnter"
606
- @leave="onLeave"
607
- >
608
- <component :is="currentComponent" :key="currentRoute" />
609
- </Transition>
610
-
611
186
  <style>
612
- .fade-enter-active, .fade-leave-active {
613
- transition: opacity 0.3s ease;
614
- }
615
- .fade-enter-from, .fade-leave-to {
616
- opacity: 0;
617
- }
618
- </style>
619
-
620
- <!-- TransitionGroup for lists -->
621
- <TransitionGroup name="list" tag="ul">
622
- <li v-for="item in items" :key="item.id">
623
- {{ item.name }}
624
- </li>
625
- </TransitionGroup>
626
-
627
- <style>
628
- .list-enter-active, .list-leave-active {
629
- transition: all 0.3s ease;
630
- }
631
- .list-enter-from, .list-leave-from {
632
- opacity: 0;
633
- transform: translateY(20px);
634
- }
635
- .list-leave-active {
636
- position: absolute; /* prevents layout shift during leave */
637
- }
187
+ /* Transition CSS */
188
+ .fade-enter-active, .fade-leave-active { transition: opacity 0.3s; }
189
+ .fade-enter-from, .fade-leave-to { opacity: 0; }
638
190
  </style>
639
191
  ```
640
192
 
@@ -642,323 +194,32 @@ if (!themeCtx) throw new Error("ThemeKey not provided");
642
194
 
643
195
  ## Nuxt 4
644
196
 
645
- ### File-Based Routing
646
-
647
- ```
648
- pages/
649
- ├── index.vue → /
650
- ├── about.vue → /about
651
- ├── users/
652
- │ ├── index.vue → /users
653
- │ └── [id].vue → /users/:id
654
- ├── blog/
655
- │ └── [...slug].vue → /blog/* (catch-all)
656
- └── [[optional]].vue → /:optional? (optional param)
657
- ```
658
-
659
- ### Data Fetching
660
-
661
- ```vue
662
- <script setup lang="ts">
663
- // useFetch — SSR-friendly, auto-cached, deduped
664
- const { data: users, status, error, refresh } = await useFetch<User[]>("/api/users", {
665
- query: { page: currentPage }, // reactive query params
666
- pick: ["id", "name", "email"], // only extract these fields (reduces payload)
667
- transform: (data) => data.filter((u) => u.isActive), // client-side transform
668
- watch: [currentPage], // auto-refetch when page changes
669
- });
670
-
671
- // useAsyncData — for non-fetch async operations
672
- const { data: config } = await useAsyncData("app-config", () => {
673
- return $fetch("/api/config");
674
- });
675
-
676
- // $fetch — raw fetch (NOT SSR-cached, no dedup)
677
- const data = await $fetch("/api/endpoint");
678
-
679
- // ❌ HALLUCINATION TRAP: useFetch auto-deduplicates during SSR
680
- // Calling useFetch twice with the same key returns the same promise
681
- // Use $fetch when you intentionally want separate requests
682
-
683
- // ❌ HALLUCINATION TRAP: useFetch MUST be called at the top level of setup
684
- // It cannot be called inside functions, loops, or conditionals
685
- </script>
686
-
687
- <template>
688
- <div v-if="status === 'pending'">Loading...</div>
689
- <div v-else-if="error">Error: {{ error.message }}</div>
690
- <ul v-else>
691
- <li v-for="user in users" :key="user.id">{{ user.name }}</li>
692
- </ul>
693
- </template>
694
- ```
695
-
696
- ### Runtime Config
697
-
698
- ```ts
699
- // nuxt.config.ts
700
- export default defineNuxtConfig({
701
- runtimeConfig: {
702
- // Server-only (never exposed to client)
703
- apiSecret: process.env.API_SECRET,
704
- dbUrl: process.env.DATABASE_URL,
705
-
706
- // Client-accessible
707
- public: {
708
- apiBase: process.env.NUXT_PUBLIC_API_BASE || "https://api.example.com",
709
- appName: "My App",
710
- },
711
- },
712
- });
713
-
714
- // Usage in components/composables:
715
- const config = useRuntimeConfig();
716
- // config.public.apiBase — ✅ accessible on client and server
717
- // config.apiSecret — ✅ accessible on server only, undefined on client
718
-
719
- // ❌ HALLUCINATION TRAP: Private keys are ONLY available server-side
720
- // Accessing config.apiSecret in a client component returns undefined
721
- ```
722
-
723
- ### Server Routes (Nitro)
724
-
725
- ```ts
726
- // server/api/users.get.ts — responds to GET /api/users
727
- export default defineEventHandler(async (event) => {
728
- const query = getQuery(event);
729
- const users = await db.user.findMany({
730
- skip: Number(query.offset) || 0,
731
- take: Number(query.limit) || 20,
732
- });
733
- return users;
734
- });
735
-
736
- // server/api/users.post.ts — responds to POST /api/users
737
- export default defineEventHandler(async (event) => {
738
- const body = await readBody(event);
739
- // Validate body...
740
- const user = await db.user.create({ data: body });
741
- return user;
742
- });
743
-
744
- // server/api/users/[id].get.ts — responds to GET /api/users/:id
745
- export default defineEventHandler(async (event) => {
746
- const id = getRouterParam(event, "id");
747
- const user = await db.user.findUnique({ where: { id } });
748
- if (!user) {
749
- throw createError({ statusCode: 404, message: "User not found" });
750
- }
751
- return user;
752
- });
753
- ```
754
-
755
- ---
756
-
757
- ## TypeScript Integration
758
-
759
- ```vue
760
- <script setup lang="ts">
761
- // Component with full TypeScript
762
- interface User {
763
- id: number;
764
- name: string;
765
- email: string;
766
- role: "admin" | "user" | "moderator";
767
- }
768
-
769
- const props = defineProps<{
770
- user: User;
771
- editable?: boolean;
772
- }>();
773
-
774
- const emit = defineEmits<{
775
- save: [user: User];
776
- cancel: [];
777
- }>();
778
-
779
- // Template refs
780
- const inputRef = ref<HTMLInputElement | null>(null);
781
- const formRef = ref<InstanceType<typeof FormComponent> | null>(null);
782
-
783
- onMounted(() => {
784
- inputRef.value?.focus();
785
- });
786
- </script>
787
197
  ```
788
-
789
- ---
790
-
791
- ## Performance Optimization
792
-
793
- ```vue
794
- <script setup lang="ts">
795
- import { shallowRef, shallowReactive, triggerRef } from "vue";
796
-
797
- // shallowRef — only tracks .value changes, not deep mutations
798
- const bigList = shallowRef<Item[]>([]);
799
- // Mutating items inside won't trigger updates
800
- // Must replace the entire array: bigList.value = [...newItems]
801
- // Or manually trigger: triggerRef(bigList)
802
-
803
- // v-once — render once, never update (static content)
804
- // v-memo — skip re-rendering unless dependencies change
805
- </script>
806
-
807
- <template>
808
- <!-- Static content — rendered once -->
809
- <footer v-once>
810
- <p>© 2024 My Company. All rights reserved.</p>
811
- </footer>
812
-
813
- <!-- v-memo — skip re-render unless item.id or selected changes -->
814
- <div v-for="item in list" :key="item.id" v-memo="[item.id, selected === item.id]">
815
- <ItemCard :item="item" :selected="selected === item.id" />
816
- </div>
817
-
818
- <!-- Async components (lazy loading) -->
819
- <component :is="defineAsyncComponent(() => import('./HeavyWidget.vue'))" />
820
- </template>
198
+ auto-imports: ref, computed, useRoute, useFetch — no imports needed
199
+ composables/: auto-imported by filename
200
+ server/api/: server routes (GET/POST)
201
+ pages/: file-based routing
202
+ layouts/: layout components
203
+ middleware/: route guards
821
204
  ```
822
205
 
823
- ---
824
-
825
- ## Testing with Vitest
826
-
827
206
  ```ts
828
- // tests/components/Counter.test.ts
829
- import { describe, it, expect } from "vitest";
830
- import { mount } from "@vue/test-utils";
831
- import Counter from "@/components/Counter.vue";
832
-
833
- describe("Counter", () => {
834
- it("renders initial count", () => {
835
- const wrapper = mount(Counter, { props: { initial: 5 } });
836
- expect(wrapper.text()).toContain("5");
837
- });
838
-
839
- it("increments on click", async () => {
840
- const wrapper = mount(Counter);
841
- await wrapper.find("button").trigger("click");
842
- expect(wrapper.text()).toContain("1");
843
- });
844
-
845
- it("emits update event", async () => {
846
- const wrapper = mount(Counter);
847
- await wrapper.find("button").trigger("click");
848
- expect(wrapper.emitted("update")).toHaveLength(1);
849
- expect(wrapper.emitted("update")![0]).toEqual([1]);
850
- });
207
+ // pages/users/[id].vue
208
+ const { id } = useRoute().params; // auto-imported
209
+ const { data, error, refresh } = await useFetch(`/api/users/${id}`, {
210
+ lazy: false, // SSR: wait for data before rendering
211
+ server: true, // fetch on server (default)
212
+ transform: (r) => r.user,
851
213
  });
852
-
853
- // Testing composables
854
- import { useDebounce } from "@/composables/useDebounce";
855
-
856
- describe("useDebounce", () => {
857
- it("debounces value updates", async () => {
858
- vi.useFakeTimers();
859
- const source = ref("hello");
860
- const debounced = useDebounce(source, 300);
861
-
862
- source.value = "world";
863
- expect(debounced.value).toBe("hello"); // not yet
864
-
865
- vi.advanceTimersByTime(300);
866
- expect(debounced.value).toBe("world"); // now updated
867
-
868
- vi.useRealTimers();
869
- });
870
- });
871
-
872
- // Testing Pinia stores
873
- import { setActivePinia, createPinia } from "pinia";
874
- import { useCartStore } from "@/stores/cart";
875
-
876
- describe("Cart Store", () => {
877
- beforeEach(() => {
878
- setActivePinia(createPinia());
879
- });
880
-
881
- it("adds items", () => {
882
- const cart = useCartStore();
883
- cart.addItem({ id: "1", name: "Widget", price: 10 });
884
- expect(cart.totalItems).toBe(1);
885
- expect(cart.totalPrice).toBe(10);
886
- });
887
- });
888
- ```
889
-
890
- ---
891
-
892
- ## Output Format
893
-
894
- When this skill produces or reviews code, structure your output as follows:
895
-
896
- ```
897
- ━━━ Vue Expert Report ━━━━━━━━━━━━━━━━━━━━━━━━
898
- Skill: Vue Expert
899
- Vue Ver: 3.5+
900
- Scope: [N files · N components]
901
- ─────────────────────────────────────────────────
902
- ✅ Passed: [checks that passed, or "All clean"]
903
- ⚠️ Warnings: [non-blocking issues, or "None"]
904
- ❌ Blocked: [blocking issues requiring fix, or "None"]
905
- ─────────────────────────────────────────────────
906
- VBC status: PENDING → VERIFIED
907
- Evidence: [test output / lint pass / compile success]
214
+ // ❌ TRAP: useFetch in Nuxt ≠ @tanstack/react-query. It's Nuxt-specific.
215
+ // TRAP: useAsyncData key must be UNIQUE per page/component
908
216
  ```
909
217
 
910
- **VBC (Verification-Before-Completion) is mandatory.**
911
- Do not mark status as VERIFIED until concrete terminal evidence is provided.
912
-
913
- ---
914
-
915
- ## 🤖 LLM-Specific Traps
916
-
917
- AI coding assistants often fall into specific bad habits when generating Vue code. These are strictly forbidden:
918
-
919
- 1. **Options API in Vue 3.5+:** Never generate `export default { data(), methods, computed, mounted() }` in modern Vue projects. Always use `<script setup>` with Composition API.
920
- 2. **Vuex Instead of Pinia:** Vuex is in maintenance mode. Never import from `vuex`. Use `pinia` with `defineStore()`.
921
- 3. **Destructuring Reactive Without `toRefs`/`storeToRefs`:** Destructuring a `reactive()` object or a Pinia store without `toRefs()`/`storeToRefs()` breaks reactivity.
922
- 4. **Mutating Props Directly:** Never mutate a prop value. Use `defineEmits` to emit updates, or use `defineModel()` (Vue 3.4+) for v-model bindings.
923
- 5. **Using `v-for` Without `:key`:** Every `v-for` must have a unique, stable `:key` binding. Never use the iteration index as the key if the list can reorder.
924
- 6. **`defineComponent` With `<script setup>`:** `defineComponent()` is redundant inside `<script setup>`. The setup syntax IS the component definition.
925
- 7. **`this` in Composition API:** There is no `this` in `<script setup>`. Access reactive state directly by variable name. `this.count` does not exist.
926
- 8. **`useFetch` Inside Functions:** Nuxt's `useFetch` must be called at the top level of `<script setup>`, not inside callbacks, conditionals, or loops.
927
- 9. **Route Params as Numbers:** `route.params.id` is always a string. Never treat it as a number without explicit parsing (`parseInt()`).
928
- 10. **Exposing Private Runtime Config:** `runtimeConfig` (non-public) keys are server-only. Accessing them in client components returns `undefined`.
929
-
930
218
  ---
931
219
 
932
- ## 🏛️ Tribunal Integration (Anti-Hallucination)
933
-
934
- **Slash command: `/tribunal-frontend`**
935
- **Active reviewers: `logic` · `security` · `frontend` · `type-safety`**
936
-
937
- ###Forbidden AI Tropes
938
-
939
- 1. **Blind Assumptions:** Never make an assumption without documenting it clearly with `// VERIFY: [reason]`.
940
- 2. **Silent Degradation:** Catching and suppressing errors without logging or handling.
941
- 3. **Context Amnesia:** Forgetting Vue version, Nuxt vs vanilla Vue, or Pinia vs Vuex constraints.
942
- 4. **Sloppy Layout Generation:** Never build UI without explicit 4px grid spacing and flex/grid layouts.
943
-
944
- ### ✅ Pre-Flight Self-Audit
945
-
946
- Review these questions before confirming output:
947
- ```
948
- ✅ Did I use <script setup lang="ts"> (not Options API)?
949
- ✅ Did I use Pinia (not Vuex)?
950
- ✅ Did I use storeToRefs for reactive store destructuring?
951
- ✅ Did I use defineModel (Vue 3.4+) for v-model bindings?
952
- ✅ Did I use toRefs when destructuring reactive objects?
953
- ✅ Does every v-for have a unique, stable :key?
954
- ✅ Did I use useRuntimeConfig for env variables (not process.env)?
955
- ✅ Did I call useFetch at the top level of setup?
956
- ✅ Are route params parsed correctly (string → number)?
957
- ✅ Did I write Vitest tests with @vue/test-utils?
958
- ```
959
-
960
- ### 🛑 Verification-Before-Completion (VBC) Protocol
961
-
962
- **CRITICAL:** You must follow a strict "evidence-based closeout" state machine.
963
- - ❌ **Forbidden:** Declaring a Vue component "works" because it compiles without errors.
964
- - ✅ **Required:** You are explicitly forbidden from completing your task without providing **concrete terminal/test evidence** (e.g., passing Vitest logs, successful build, or dev server confirmation) proving the component works correctly.
220
+ ## Performance
221
+ - ✅ Use `v-memo` for expensive list items that rarely change
222
+ - `defineAsyncComponent(() => import("./Heavy.vue"))` for code splitting
223
+ - `:key` on `<component :is>` forces re-mount on route change (prevents stale state)
224
+ - ❌ Avoid deeply nested reactive objects — use `shallowRef`/`shallowReactive` for large data
225
+ -Never mutate props — emit events instead