tribunal-kit 3.0.0 → 4.0.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 (233) 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/precedence-reviewer.md +213 -0
  24. package/.agent/agents/product-manager.md +142 -162
  25. package/.agent/agents/product-owner.md +6 -25
  26. package/.agent/agents/project-planner.md +142 -162
  27. package/.agent/agents/qa-automation-engineer.md +225 -242
  28. package/.agent/agents/security-auditor.md +174 -194
  29. package/.agent/agents/seo-specialist.md +193 -213
  30. package/.agent/agents/sql-reviewer.md +161 -194
  31. package/.agent/agents/supervisor-agent.md +184 -203
  32. package/.agent/agents/swarm-worker-contracts.md +17 -17
  33. package/.agent/agents/swarm-worker-registry.md +46 -46
  34. package/.agent/agents/test-coverage-reviewer.md +160 -193
  35. package/.agent/agents/test-engineer.md +0 -21
  36. package/.agent/agents/type-safety-reviewer.md +175 -208
  37. package/.agent/patterns/generator.md +9 -9
  38. package/.agent/patterns/inversion.md +12 -12
  39. package/.agent/patterns/pipeline.md +9 -9
  40. package/.agent/patterns/reviewer.md +13 -13
  41. package/.agent/patterns/tool-wrapper.md +9 -9
  42. package/.agent/rules/GEMINI.md +63 -63
  43. package/.agent/scripts/append_flow.js +72 -0
  44. package/.agent/scripts/case_law_manager.py +525 -0
  45. package/.agent/scripts/compress_skills.py +167 -0
  46. package/.agent/scripts/consolidate_skills.py +173 -0
  47. package/.agent/scripts/deep_compress.py +202 -0
  48. package/.agent/scripts/minify_context.py +80 -0
  49. package/.agent/scripts/security_scan.py +1 -1
  50. package/.agent/scripts/skill_evolution.py +563 -0
  51. package/.agent/scripts/strip_tribunal.py +41 -0
  52. package/.agent/skills/agent-organizer/SKILL.md +100 -126
  53. package/.agent/skills/agentic-patterns/SKILL.md +0 -70
  54. package/.agent/skills/ai-prompt-injection-defense/SKILL.md +134 -160
  55. package/.agent/skills/api-patterns/SKILL.md +123 -215
  56. package/.agent/skills/api-security-auditor/SKILL.md +143 -177
  57. package/.agent/skills/app-builder/SKILL.md +334 -50
  58. package/.agent/skills/app-builder/templates/SKILL.md +13 -15
  59. package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +16 -16
  60. package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +22 -22
  61. package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +18 -18
  62. package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +20 -20
  63. package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +17 -17
  64. package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +18 -18
  65. package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +21 -21
  66. package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +19 -19
  67. package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +26 -26
  68. package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +26 -26
  69. package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +19 -19
  70. package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +18 -18
  71. package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +20 -20
  72. package/.agent/skills/appflow-wireframe/SKILL.md +95 -121
  73. package/.agent/skills/architecture/SKILL.md +169 -331
  74. package/.agent/skills/authentication-best-practices/SKILL.md +139 -173
  75. package/.agent/skills/bash-linux/SKILL.md +129 -154
  76. package/.agent/skills/behavioral-modes/SKILL.md +8 -69
  77. package/.agent/skills/brainstorming/SKILL.md +436 -104
  78. package/.agent/skills/building-native-ui/SKILL.md +152 -174
  79. package/.agent/skills/clean-code/SKILL.md +331 -360
  80. package/.agent/skills/code-review-checklist/SKILL.md +0 -62
  81. package/.agent/skills/config-validator/SKILL.md +115 -141
  82. package/.agent/skills/csharp-developer/SKILL.md +468 -528
  83. package/.agent/skills/database-design/SKILL.md +104 -369
  84. package/.agent/skills/deployment-procedures/SKILL.md +119 -145
  85. package/.agent/skills/devops-engineer/SKILL.md +295 -332
  86. package/.agent/skills/devops-incident-responder/SKILL.md +87 -113
  87. package/.agent/skills/doc.md +5 -5
  88. package/.agent/skills/documentation-templates/SKILL.md +27 -63
  89. package/.agent/skills/edge-computing/SKILL.md +131 -157
  90. package/.agent/skills/extract-design-system/SKILL.md +108 -134
  91. package/.agent/skills/framer-motion-expert/SKILL.md +111 -855
  92. package/.agent/skills/frontend-design/SKILL.md +151 -499
  93. package/.agent/skills/game-design-expert/SKILL.md +79 -105
  94. package/.agent/skills/game-engineering-expert/SKILL.md +96 -122
  95. package/.agent/skills/geo-fundamentals/SKILL.md +97 -124
  96. package/.agent/skills/github-operations/SKILL.md +279 -314
  97. package/.agent/skills/gsap-expert/SKILL.md +119 -826
  98. package/.agent/skills/i18n-localization/SKILL.md +113 -138
  99. package/.agent/skills/intelligent-routing/SKILL.md +167 -127
  100. package/.agent/skills/lint-and-validate/SKILL.md +16 -52
  101. package/.agent/skills/llm-engineering/SKILL.md +344 -357
  102. package/.agent/skills/local-first/SKILL.md +128 -154
  103. package/.agent/skills/mcp-builder/SKILL.md +92 -118
  104. package/.agent/skills/mobile-design/SKILL.md +213 -219
  105. package/.agent/skills/motion-engineering/SKILL.md +184 -0
  106. package/.agent/skills/nextjs-react-expert/SKILL.md +99 -698
  107. package/.agent/skills/nodejs-best-practices/SKILL.md +498 -559
  108. package/.agent/skills/observability/SKILL.md +293 -330
  109. package/.agent/skills/parallel-agents/SKILL.md +96 -122
  110. package/.agent/skills/performance-profiling/SKILL.md +217 -254
  111. package/.agent/skills/plan-writing/SKILL.md +92 -118
  112. package/.agent/skills/platform-engineer/SKILL.md +97 -123
  113. package/.agent/skills/playwright-best-practices/SKILL.md +137 -162
  114. package/.agent/skills/powershell-windows/SKILL.md +112 -146
  115. package/.agent/skills/project-idioms/SKILL.md +87 -0
  116. package/.agent/skills/python-patterns/SKILL.md +15 -35
  117. package/.agent/skills/python-pro/SKILL.md +148 -754
  118. package/.agent/skills/react-specialist/SKILL.md +123 -827
  119. package/.agent/skills/readme-builder/SKILL.md +23 -85
  120. package/.agent/skills/realtime-patterns/SKILL.md +269 -304
  121. package/.agent/skills/red-team-tactics/SKILL.md +18 -51
  122. package/.agent/skills/rust-pro/SKILL.md +623 -701
  123. package/.agent/skills/seo-fundamentals/SKILL.md +129 -154
  124. package/.agent/skills/server-management/SKILL.md +164 -190
  125. package/.agent/skills/shadcn-ui-expert/SKILL.md +181 -206
  126. package/.agent/skills/skill-creator/SKILL.md +24 -56
  127. package/.agent/skills/sql-pro/SKILL.md +579 -633
  128. package/.agent/skills/supabase-postgres-best-practices/SKILL.md +35 -66
  129. package/.agent/skills/swiftui-expert/SKILL.md +151 -176
  130. package/.agent/skills/systematic-debugging/SKILL.md +92 -118
  131. package/.agent/skills/tailwind-patterns/SKILL.md +516 -576
  132. package/.agent/skills/tdd-workflow/SKILL.md +111 -137
  133. package/.agent/skills/test-result-analyzer/SKILL.md +33 -73
  134. package/.agent/skills/testing-patterns/SKILL.md +512 -573
  135. package/.agent/skills/trend-researcher/SKILL.md +30 -71
  136. package/.agent/skills/ui-ux-pro-max/SKILL.md +8 -41
  137. package/.agent/skills/ui-ux-researcher/SKILL.md +51 -91
  138. package/.agent/skills/vue-expert/SKILL.md +127 -866
  139. package/.agent/skills/vulnerability-scanner/SKILL.md +354 -269
  140. package/.agent/skills/web-accessibility-auditor/SKILL.md +168 -193
  141. package/.agent/skills/web-design-guidelines/SKILL.md +25 -61
  142. package/.agent/skills/webapp-testing/SKILL.md +119 -145
  143. package/.agent/skills/whimsy-injector/SKILL.md +58 -132
  144. package/.agent/skills/workflow-optimizer/SKILL.md +28 -68
  145. package/.agent/workflows/api-tester.md +151 -151
  146. package/.agent/workflows/audit.md +127 -138
  147. package/.agent/workflows/brainstorm.md +110 -110
  148. package/.agent/workflows/changelog.md +112 -112
  149. package/.agent/workflows/create.md +124 -124
  150. package/.agent/workflows/debug.md +165 -189
  151. package/.agent/workflows/deploy.md +180 -189
  152. package/.agent/workflows/enhance.md +128 -151
  153. package/.agent/workflows/fix.md +114 -135
  154. package/.agent/workflows/generate.md +13 -4
  155. package/.agent/workflows/migrate.md +160 -160
  156. package/.agent/workflows/orchestrate.md +168 -168
  157. package/.agent/workflows/performance-benchmarker.md +114 -123
  158. package/.agent/workflows/plan.md +173 -173
  159. package/.agent/workflows/preview.md +80 -80
  160. package/.agent/workflows/refactor.md +161 -183
  161. package/.agent/workflows/review-ai.md +101 -129
  162. package/.agent/workflows/review.md +116 -116
  163. package/.agent/workflows/session.md +94 -94
  164. package/.agent/workflows/status.md +79 -79
  165. package/.agent/workflows/strengthen-skills.md +138 -139
  166. package/.agent/workflows/swarm.md +179 -179
  167. package/.agent/workflows/test.md +189 -211
  168. package/.agent/workflows/tribunal-backend.md +94 -113
  169. package/.agent/workflows/tribunal-database.md +95 -115
  170. package/.agent/workflows/tribunal-frontend.md +96 -118
  171. package/.agent/workflows/tribunal-full.md +93 -133
  172. package/.agent/workflows/tribunal-mobile.md +95 -119
  173. package/.agent/workflows/tribunal-performance.md +110 -133
  174. package/.agent/workflows/ui-ux-pro-max.md +122 -143
  175. package/README.md +30 -1
  176. package/bin/tribunal-kit.js +175 -12
  177. package/package.json +25 -4
  178. package/.agent/skills/api-patterns/api-style.md +0 -42
  179. package/.agent/skills/api-patterns/auth.md +0 -24
  180. package/.agent/skills/api-patterns/documentation.md +0 -26
  181. package/.agent/skills/api-patterns/graphql.md +0 -41
  182. package/.agent/skills/api-patterns/rate-limiting.md +0 -31
  183. package/.agent/skills/api-patterns/response.md +0 -37
  184. package/.agent/skills/api-patterns/rest.md +0 -40
  185. package/.agent/skills/api-patterns/security-testing.md +0 -122
  186. package/.agent/skills/api-patterns/trpc.md +0 -41
  187. package/.agent/skills/api-patterns/versioning.md +0 -22
  188. package/.agent/skills/app-builder/agent-coordination.md +0 -71
  189. package/.agent/skills/app-builder/feature-building.md +0 -53
  190. package/.agent/skills/app-builder/project-detection.md +0 -34
  191. package/.agent/skills/app-builder/scaffolding.md +0 -118
  192. package/.agent/skills/app-builder/tech-stack.md +0 -40
  193. package/.agent/skills/architecture/context-discovery.md +0 -43
  194. package/.agent/skills/architecture/examples.md +0 -94
  195. package/.agent/skills/architecture/pattern-selection.md +0 -68
  196. package/.agent/skills/architecture/patterns-reference.md +0 -50
  197. package/.agent/skills/architecture/trade-off-analysis.md +0 -77
  198. package/.agent/skills/brainstorming/dynamic-questioning.md +0 -360
  199. package/.agent/skills/database-design/database-selection.md +0 -43
  200. package/.agent/skills/database-design/indexing.md +0 -39
  201. package/.agent/skills/database-design/migrations.md +0 -48
  202. package/.agent/skills/database-design/optimization.md +0 -36
  203. package/.agent/skills/database-design/orm-selection.md +0 -30
  204. package/.agent/skills/database-design/schema-design.md +0 -56
  205. package/.agent/skills/frontend-design/animation-guide.md +0 -331
  206. package/.agent/skills/frontend-design/color-system.md +0 -329
  207. package/.agent/skills/frontend-design/decision-trees.md +0 -418
  208. package/.agent/skills/frontend-design/motion-graphics.md +0 -306
  209. package/.agent/skills/frontend-design/typography-system.md +0 -363
  210. package/.agent/skills/frontend-design/ux-psychology.md +0 -1116
  211. package/.agent/skills/frontend-design/visual-effects.md +0 -383
  212. package/.agent/skills/intelligent-routing/router-manifest.md +0 -65
  213. package/.agent/skills/mobile-design/decision-trees.md +0 -516
  214. package/.agent/skills/mobile-design/mobile-backend.md +0 -491
  215. package/.agent/skills/mobile-design/mobile-color-system.md +0 -420
  216. package/.agent/skills/mobile-design/mobile-debugging.md +0 -122
  217. package/.agent/skills/mobile-design/mobile-design-thinking.md +0 -357
  218. package/.agent/skills/mobile-design/mobile-navigation.md +0 -458
  219. package/.agent/skills/mobile-design/mobile-performance.md +0 -767
  220. package/.agent/skills/mobile-design/mobile-testing.md +0 -356
  221. package/.agent/skills/mobile-design/mobile-typography.md +0 -433
  222. package/.agent/skills/mobile-design/platform-android.md +0 -666
  223. package/.agent/skills/mobile-design/platform-ios.md +0 -561
  224. package/.agent/skills/mobile-design/touch-psychology.md +0 -537
  225. package/.agent/skills/nextjs-react-expert/1-async-eliminating-waterfalls.md +0 -312
  226. package/.agent/skills/nextjs-react-expert/2-bundle-bundle-size-optimization.md +0 -240
  227. package/.agent/skills/nextjs-react-expert/3-server-server-side-performance.md +0 -490
  228. package/.agent/skills/nextjs-react-expert/4-client-client-side-data-fetching.md +0 -264
  229. package/.agent/skills/nextjs-react-expert/5-rerender-re-render-optimization.md +0 -581
  230. package/.agent/skills/nextjs-react-expert/6-rendering-rendering-performance.md +0 -432
  231. package/.agent/skills/nextjs-react-expert/7-js-javascript-performance.md +0 -684
  232. package/.agent/skills/nextjs-react-expert/8-advanced-advanced-patterns.md +0 -150
  233. 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