@codihaus/claude-skills 1.6.17 → 1.6.19
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.
|
@@ -15,6 +15,7 @@ When skills detect a tech stack (via `stack.md`), they load the relevant stack f
|
|
|
15
15
|
| Stack | Folder | Type | Status |
|
|
16
16
|
|-------|--------|------|--------|
|
|
17
17
|
| React | `react/` | UI Library | Ready |
|
|
18
|
+
| Vue 3 | `vue/` | Progressive Framework | Ready |
|
|
18
19
|
| Next.js | `nextjs/` | React + SSR Framework | Ready |
|
|
19
20
|
| Nuxt.js | `nuxt/` | Vue + SSR Framework | Ready |
|
|
20
21
|
| Directus | `directus/` | Backend BaaS | Ready |
|
|
@@ -0,0 +1,750 @@
|
|
|
1
|
+
# Vue 3
|
|
2
|
+
|
|
3
|
+
> Progressive JavaScript framework for building user interfaces
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
**What it is:**
|
|
8
|
+
- Progressive framework (incrementally adoptable)
|
|
9
|
+
- Component-based with Single File Components (SFCs)
|
|
10
|
+
- Composition API for logic reuse (Vue 3+)
|
|
11
|
+
- Reactive and declarative
|
|
12
|
+
- Virtual DOM with optimized reactivity
|
|
13
|
+
- Template syntax with directives
|
|
14
|
+
|
|
15
|
+
**When to use:**
|
|
16
|
+
- Building interactive user interfaces
|
|
17
|
+
- Single-page applications (SPAs)
|
|
18
|
+
- Progressive enhancement of existing sites
|
|
19
|
+
- Projects needing gentle learning curve
|
|
20
|
+
- Teams wanting flexibility and simplicity
|
|
21
|
+
|
|
22
|
+
**Key concepts:**
|
|
23
|
+
- **SFC (Single File Component)** = .vue files with template, script, style
|
|
24
|
+
- **Composition API** = Composable logic with setup()
|
|
25
|
+
- **Reactivity** = ref(), reactive(), computed(), watch()
|
|
26
|
+
- **Template Syntax** = Directives (v-if, v-for, v-bind, v-on)
|
|
27
|
+
- **Components** = Reusable building blocks
|
|
28
|
+
- **Props & Emits** = Parent-child communication
|
|
29
|
+
- **Provide/Inject** = Dependency injection
|
|
30
|
+
|
|
31
|
+
## Best Practices
|
|
32
|
+
|
|
33
|
+
### Project Structure
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
vue-app/
|
|
37
|
+
├── src/
|
|
38
|
+
│ ├── components/ # Reusable components
|
|
39
|
+
│ │ ├── ui/ # UI primitives
|
|
40
|
+
│ │ ├── forms/ # Form components
|
|
41
|
+
│ │ └── common/ # Shared components
|
|
42
|
+
│ ├── composables/ # Composition API composables
|
|
43
|
+
│ │ └── useAuth.js # use* naming convention
|
|
44
|
+
│ ├── views/ # Page components (routes)
|
|
45
|
+
│ ├── stores/ # Pinia stores
|
|
46
|
+
│ ├── router/ # Vue Router config
|
|
47
|
+
│ ├── assets/ # Static assets
|
|
48
|
+
│ ├── utils/ # Helper functions
|
|
49
|
+
│ ├── types/ # TypeScript types
|
|
50
|
+
│ ├── App.vue # Root component
|
|
51
|
+
│ └── main.js # Entry point
|
|
52
|
+
└── public/ # Public static files
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Component Structure (SFC)
|
|
56
|
+
|
|
57
|
+
```vue
|
|
58
|
+
<!-- ComponentName.vue -->
|
|
59
|
+
<script setup>
|
|
60
|
+
// 1. Imports
|
|
61
|
+
import { ref, computed, watch, onMounted } from 'vue'
|
|
62
|
+
import { useRouter } from 'vue-router'
|
|
63
|
+
|
|
64
|
+
// 2. Props & Emits
|
|
65
|
+
const props = defineProps({
|
|
66
|
+
title: String,
|
|
67
|
+
count: { type: Number, required: true }
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const emit = defineEmits(['update', 'close'])
|
|
71
|
+
|
|
72
|
+
// 3. Composables
|
|
73
|
+
const router = useRouter()
|
|
74
|
+
const { user } = useAuth()
|
|
75
|
+
|
|
76
|
+
// 4. Reactive state
|
|
77
|
+
const localCount = ref(0)
|
|
78
|
+
const doubled = computed(() => localCount.value * 2)
|
|
79
|
+
|
|
80
|
+
// 5. Methods
|
|
81
|
+
function increment() {
|
|
82
|
+
localCount.value++
|
|
83
|
+
emit('update', localCount.value)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 6. Watchers
|
|
87
|
+
watch(() => props.count, (newVal) => {
|
|
88
|
+
localCount.value = newVal
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
// 7. Lifecycle hooks
|
|
92
|
+
onMounted(() => {
|
|
93
|
+
console.log('Component mounted')
|
|
94
|
+
})
|
|
95
|
+
</script>
|
|
96
|
+
|
|
97
|
+
<template>
|
|
98
|
+
<div class="component">
|
|
99
|
+
<h1>{{ title }}</h1>
|
|
100
|
+
<p>Count: {{ localCount }}</p>
|
|
101
|
+
<p>Doubled: {{ doubled }}</p>
|
|
102
|
+
<button @click="increment">Increment</button>
|
|
103
|
+
</div>
|
|
104
|
+
</template>
|
|
105
|
+
|
|
106
|
+
<style scoped>
|
|
107
|
+
.component {
|
|
108
|
+
/* Component styles */
|
|
109
|
+
}
|
|
110
|
+
</style>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Composition API Best Practices
|
|
114
|
+
|
|
115
|
+
**Using `<script setup>` (Recommended):**
|
|
116
|
+
```vue
|
|
117
|
+
DO:
|
|
118
|
+
✓ Use <script setup> (less boilerplate)
|
|
119
|
+
✓ Name composables with "use" prefix
|
|
120
|
+
✓ Keep composables focused (single concern)
|
|
121
|
+
✓ Return reactive values from composables
|
|
122
|
+
✓ Use ref() for primitives, reactive() for objects
|
|
123
|
+
|
|
124
|
+
DON'T:
|
|
125
|
+
✗ Mix Options API and Composition API
|
|
126
|
+
✗ Use setup() function (use <script setup> instead)
|
|
127
|
+
✗ Destructure reactive objects (loses reactivity)
|
|
128
|
+
✗ Forget .value on refs in script
|
|
129
|
+
✗ Over-abstract into composables too early
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Reactivity:**
|
|
133
|
+
```javascript
|
|
134
|
+
DO:
|
|
135
|
+
✓ Use ref() for primitives
|
|
136
|
+
const count = ref(0)
|
|
137
|
+
count.value++ // Access with .value in script
|
|
138
|
+
|
|
139
|
+
✓ Use reactive() for objects
|
|
140
|
+
const state = reactive({ count: 0 })
|
|
141
|
+
state.count++ // No .value needed
|
|
142
|
+
|
|
143
|
+
✓ Use computed() for derived state
|
|
144
|
+
const doubled = computed(() => count.value * 2)
|
|
145
|
+
|
|
146
|
+
✓ Use toRefs() when destructuring reactive objects
|
|
147
|
+
const { count } = toRefs(state)
|
|
148
|
+
|
|
149
|
+
DON'T:
|
|
150
|
+
✗ Reassign reactive() (loses reactivity)
|
|
151
|
+
state = { count: 1 } // Wrong
|
|
152
|
+
state.count = 1 // Correct
|
|
153
|
+
|
|
154
|
+
✗ Destructure reactive() directly
|
|
155
|
+
const { count } = reactive({ count: 0 }) // Loses reactivity
|
|
156
|
+
|
|
157
|
+
✗ Use ref() for large objects (use reactive())
|
|
158
|
+
✗ Compute values in watchers (use computed)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**Composables Pattern:**
|
|
162
|
+
```javascript
|
|
163
|
+
DO:
|
|
164
|
+
✓ Export composable function starting with "use"
|
|
165
|
+
✓ Return reactive values (refs, computed)
|
|
166
|
+
✓ Keep composables composable (can call other composables)
|
|
167
|
+
✓ Handle cleanup in onUnmounted
|
|
168
|
+
|
|
169
|
+
DON'T:
|
|
170
|
+
✗ Call composables conditionally
|
|
171
|
+
✗ Call composables in callbacks
|
|
172
|
+
✗ Call composables outside setup()
|
|
173
|
+
✗ Return plain values (use ref/reactive)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Template Best Practices
|
|
177
|
+
|
|
178
|
+
**Directives:**
|
|
179
|
+
```vue
|
|
180
|
+
DO:
|
|
181
|
+
✓ Use v-bind shorthand (:prop)
|
|
182
|
+
<img :src="imageUrl">
|
|
183
|
+
|
|
184
|
+
✓ Use v-on shorthand (@event)
|
|
185
|
+
<button @click="handler">
|
|
186
|
+
|
|
187
|
+
✓ Use v-for with unique :key
|
|
188
|
+
<div v-for="item in items" :key="item.id">
|
|
189
|
+
|
|
190
|
+
✓ Use v-if for conditional rendering
|
|
191
|
+
<div v-if="show">Content</div>
|
|
192
|
+
|
|
193
|
+
✓ Use v-show for frequent toggles
|
|
194
|
+
<div v-show="visible">Content</div>
|
|
195
|
+
|
|
196
|
+
DON'T:
|
|
197
|
+
✗ Use v-for with v-if on same element (use computed filter)
|
|
198
|
+
<div v-for="item in items" v-if="item.active"> <!-- BAD -->
|
|
199
|
+
|
|
200
|
+
✗ Use index as key for dynamic lists
|
|
201
|
+
v-for="(item, i) in items" :key="i" <!-- BAD -->
|
|
202
|
+
|
|
203
|
+
✗ Forget :key in v-for
|
|
204
|
+
✗ Use v-html with user input (XSS risk)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Component Communication:**
|
|
208
|
+
```vue
|
|
209
|
+
DO:
|
|
210
|
+
✓ Use props for parent → child
|
|
211
|
+
<Child :title="parentTitle" />
|
|
212
|
+
|
|
213
|
+
✓ Use emits for child → parent
|
|
214
|
+
emit('update', value)
|
|
215
|
+
|
|
216
|
+
✓ Use provide/inject for deep hierarchies
|
|
217
|
+
provide('key', value)
|
|
218
|
+
const value = inject('key')
|
|
219
|
+
|
|
220
|
+
✓ Use Pinia for global state
|
|
221
|
+
|
|
222
|
+
DON'T:
|
|
223
|
+
✗ Mutate props directly (one-way data flow)
|
|
224
|
+
props.title = 'new' // Wrong
|
|
225
|
+
emit('update:title', 'new') // Correct
|
|
226
|
+
|
|
227
|
+
✗ Prop drill through many levels (use provide/inject)
|
|
228
|
+
✗ Use event bus (deprecated in Vue 3)
|
|
229
|
+
✗ Access $parent or $root (tight coupling)
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### State Management
|
|
233
|
+
|
|
234
|
+
**Local State:**
|
|
235
|
+
```javascript
|
|
236
|
+
When: Component-specific, not shared
|
|
237
|
+
Example: Form inputs, toggles, local UI state
|
|
238
|
+
Use: ref(), reactive()
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Composables:**
|
|
242
|
+
```javascript
|
|
243
|
+
When: Reusable logic, shared between components
|
|
244
|
+
Example: useAuth(), useFetch(), useForm()
|
|
245
|
+
Use: Composable functions returning refs/computed
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**Pinia (Global Store):**
|
|
249
|
+
```javascript
|
|
250
|
+
When: Global or feature-level state
|
|
251
|
+
Example: Auth state, cart, user preferences
|
|
252
|
+
DO:
|
|
253
|
+
✓ One store per feature/domain
|
|
254
|
+
✓ Use composition stores (setup syntax)
|
|
255
|
+
✓ Keep actions for mutations
|
|
256
|
+
✓ Use getters for computed state
|
|
257
|
+
✓ Modularize stores
|
|
258
|
+
|
|
259
|
+
DON'T:
|
|
260
|
+
✗ Use Vuex (Pinia is official for Vue 3)
|
|
261
|
+
✗ Create one giant store
|
|
262
|
+
✗ Mutate state outside actions
|
|
263
|
+
✗ Store derived data (use getters)
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Performance Optimization
|
|
267
|
+
|
|
268
|
+
**Lazy Loading:**
|
|
269
|
+
```javascript
|
|
270
|
+
DO:
|
|
271
|
+
✓ Lazy load routes
|
|
272
|
+
const Home = () => import('./views/Home.vue')
|
|
273
|
+
|
|
274
|
+
✓ Lazy load heavy components
|
|
275
|
+
const Chart = defineAsyncComponent(() => import('./Chart.vue'))
|
|
276
|
+
|
|
277
|
+
✓ Use Suspense for async components
|
|
278
|
+
<Suspense>
|
|
279
|
+
<AsyncComponent />
|
|
280
|
+
</Suspense>
|
|
281
|
+
|
|
282
|
+
DON'T:
|
|
283
|
+
✗ Lazy load small components (overhead)
|
|
284
|
+
✗ Forget loading/error states
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
**Reactivity Optimization:**
|
|
288
|
+
```javascript
|
|
289
|
+
DO:
|
|
290
|
+
✓ Use shallowRef() for large objects (no deep reactivity)
|
|
291
|
+
✓ Use shallowReactive() for large structures
|
|
292
|
+
✓ Mark constants with markRaw()
|
|
293
|
+
✓ Use v-once for static content
|
|
294
|
+
✓ Use v-memo for expensive list items
|
|
295
|
+
|
|
296
|
+
DON'T:
|
|
297
|
+
✗ Create reactivity for everything
|
|
298
|
+
✗ Deep watch large objects unnecessarily
|
|
299
|
+
✗ Forget to unwatch in cleanup
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**List Rendering:**
|
|
303
|
+
```vue
|
|
304
|
+
DO:
|
|
305
|
+
✓ Use unique, stable keys (IDs from data)
|
|
306
|
+
<div v-for="item in items" :key="item.id">
|
|
307
|
+
|
|
308
|
+
✓ Filter/sort in computed, not in template
|
|
309
|
+
const filtered = computed(() => items.filter(fn))
|
|
310
|
+
|
|
311
|
+
✓ Use v-memo for expensive items
|
|
312
|
+
<div v-for="item in items" :key="item.id" v-memo="[item.id]">
|
|
313
|
+
|
|
314
|
+
DON'T:
|
|
315
|
+
✗ Use index as key for dynamic lists
|
|
316
|
+
✗ Filter/sort directly in v-for
|
|
317
|
+
✗ Create objects/arrays in template (re-renders)
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Forms & v-model
|
|
321
|
+
|
|
322
|
+
```vue
|
|
323
|
+
DO:
|
|
324
|
+
✓ Use v-model for two-way binding
|
|
325
|
+
<input v-model="text">
|
|
326
|
+
|
|
327
|
+
✓ Use v-model modifiers
|
|
328
|
+
<input v-model.trim="text">
|
|
329
|
+
<input v-model.number="age">
|
|
330
|
+
|
|
331
|
+
✓ Use custom v-model on components
|
|
332
|
+
defineProps(['modelValue'])
|
|
333
|
+
defineEmits(['update:modelValue'])
|
|
334
|
+
|
|
335
|
+
✓ Validate on blur or submit
|
|
336
|
+
|
|
337
|
+
DON'T:
|
|
338
|
+
✗ Validate on every keystroke (performance)
|
|
339
|
+
✗ Forget to trim/sanitize inputs
|
|
340
|
+
✗ Store all form state in parent (use composables)
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### TypeScript Integration
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
DO:
|
|
347
|
+
✓ Define prop types with TypeScript
|
|
348
|
+
defineProps<{
|
|
349
|
+
title: string
|
|
350
|
+
count?: number
|
|
351
|
+
}>()
|
|
352
|
+
|
|
353
|
+
✓ Type emit functions
|
|
354
|
+
const emit = defineEmits<{
|
|
355
|
+
update: [value: number]
|
|
356
|
+
close: []
|
|
357
|
+
}>()
|
|
358
|
+
|
|
359
|
+
✓ Type composables return values
|
|
360
|
+
function useCounter(): { count: Ref<number>, increment: () => void }
|
|
361
|
+
|
|
362
|
+
✓ Use generic components
|
|
363
|
+
defineComponent<Props, Emits>()
|
|
364
|
+
|
|
365
|
+
DON'T:
|
|
366
|
+
✗ Use PropType with <script setup> (use TS interface)
|
|
367
|
+
✗ Forget to type composables
|
|
368
|
+
✗ Over-use any
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
## Anti-Patterns
|
|
372
|
+
|
|
373
|
+
### Common Mistakes
|
|
374
|
+
|
|
375
|
+
```vue
|
|
376
|
+
❌ Mutating props
|
|
377
|
+
props.value = 'new' // Wrong
|
|
378
|
+
emit('update:value', 'new') // Correct
|
|
379
|
+
|
|
380
|
+
❌ v-for without :key
|
|
381
|
+
<div v-for="item in items"> <!-- Missing :key -->
|
|
382
|
+
|
|
383
|
+
❌ v-for + v-if on same element
|
|
384
|
+
<div v-for="item in items" v-if="item.active"> <!-- Use computed -->
|
|
385
|
+
|
|
386
|
+
❌ Destructuring reactive without toRefs
|
|
387
|
+
const { count } = reactive({ count: 0 }) // Loses reactivity
|
|
388
|
+
const { count } = toRefs(reactive({ count: 0 })) // Correct
|
|
389
|
+
|
|
390
|
+
❌ Not accessing .value on refs in script
|
|
391
|
+
const count = ref(0)
|
|
392
|
+
console.log(count) // Wrong: Ref object
|
|
393
|
+
console.log(count.value) // Correct
|
|
394
|
+
|
|
395
|
+
❌ Creating reactive values in template
|
|
396
|
+
<Component :data="{ value: x }" /> <!-- New object every render -->
|
|
397
|
+
|
|
398
|
+
❌ Large computed without caching
|
|
399
|
+
// Computed should be pure and efficient
|
|
400
|
+
const expensive = computed(() => {
|
|
401
|
+
// Heavy calculation on every access
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
❌ Conditional composables
|
|
405
|
+
if (condition) {
|
|
406
|
+
const data = useData() // Wrong: must be at top level
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
## For /dev-specs
|
|
411
|
+
|
|
412
|
+
When writing specs for Vue 3 projects:
|
|
413
|
+
|
|
414
|
+
**Component Specs:**
|
|
415
|
+
- Define props (name, type, required, default)
|
|
416
|
+
- Define emits (event names, payload types)
|
|
417
|
+
- Specify state management approach (local, composable, Pinia)
|
|
418
|
+
- Identify if lazy loading needed
|
|
419
|
+
- Note if TypeScript types required
|
|
420
|
+
|
|
421
|
+
**State Management:**
|
|
422
|
+
- Clarify scope (local, composable, Pinia store)
|
|
423
|
+
- If Pinia: specify store name and location
|
|
424
|
+
- If composable: specify composable name and return shape
|
|
425
|
+
|
|
426
|
+
**Performance Requirements:**
|
|
427
|
+
- List optimization needs (lazy load, v-memo, shallow)
|
|
428
|
+
- Specify bundle size constraints
|
|
429
|
+
|
|
430
|
+
**Patterns to Use:**
|
|
431
|
+
- Composables for reusable logic
|
|
432
|
+
- Provide/inject for deep prop drilling
|
|
433
|
+
- Pinia stores for global state
|
|
434
|
+
|
|
435
|
+
## For /dev-coding
|
|
436
|
+
|
|
437
|
+
When implementing Vue 3 features:
|
|
438
|
+
|
|
439
|
+
### 1. Component Creation
|
|
440
|
+
|
|
441
|
+
**Start with:**
|
|
442
|
+
- Read tech-context.md for project's Vue patterns
|
|
443
|
+
- Check if TypeScript is used
|
|
444
|
+
- Identify state management approach (Pinia, composables)
|
|
445
|
+
- Check for UI library (Vuetify, PrimeVue, Element Plus, etc.)
|
|
446
|
+
|
|
447
|
+
**Structure:**
|
|
448
|
+
```vue
|
|
449
|
+
<script setup lang="ts">
|
|
450
|
+
// 1. Imports
|
|
451
|
+
import { ref, computed, watch, onMounted } from 'vue'
|
|
452
|
+
|
|
453
|
+
// 2. Props & Emits
|
|
454
|
+
const props = defineProps<{
|
|
455
|
+
title: string
|
|
456
|
+
count: number
|
|
457
|
+
}>()
|
|
458
|
+
|
|
459
|
+
const emit = defineEmits<{
|
|
460
|
+
update: [value: number]
|
|
461
|
+
}>()
|
|
462
|
+
|
|
463
|
+
// 3. Composables
|
|
464
|
+
const { user, loading } = useAuth()
|
|
465
|
+
|
|
466
|
+
// 4. State
|
|
467
|
+
const localState = ref(0)
|
|
468
|
+
const derived = computed(() => localState.value * 2)
|
|
469
|
+
|
|
470
|
+
// 5. Methods
|
|
471
|
+
function handleClick() {
|
|
472
|
+
emit('update', localState.value)
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// 6. Watchers
|
|
476
|
+
watch(() => props.count, (newVal) => {
|
|
477
|
+
localState.value = newVal
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
// 7. Lifecycle
|
|
481
|
+
onMounted(() => {
|
|
482
|
+
// Setup logic
|
|
483
|
+
})
|
|
484
|
+
</script>
|
|
485
|
+
|
|
486
|
+
<template>
|
|
487
|
+
<div>
|
|
488
|
+
<!-- Template -->
|
|
489
|
+
</div>
|
|
490
|
+
</template>
|
|
491
|
+
|
|
492
|
+
<style scoped>
|
|
493
|
+
/* Scoped styles */
|
|
494
|
+
</style>
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
### 2. State Management
|
|
498
|
+
|
|
499
|
+
**Choose based on scope:**
|
|
500
|
+
- **Local UI state** → ref(), reactive()
|
|
501
|
+
- **Reusable logic** → Composable (useX)
|
|
502
|
+
- **Feature-level** → Pinia store
|
|
503
|
+
- **Deep hierarchy** → provide/inject
|
|
504
|
+
- **Global config** → app.config.globalProperties
|
|
505
|
+
|
|
506
|
+
### 3. Composables
|
|
507
|
+
|
|
508
|
+
**Create reusable logic:**
|
|
509
|
+
```javascript
|
|
510
|
+
// composables/useCounter.js
|
|
511
|
+
import { ref, computed } from 'vue'
|
|
512
|
+
|
|
513
|
+
export function useCounter(initial = 0) {
|
|
514
|
+
const count = ref(initial)
|
|
515
|
+
const doubled = computed(() => count.value * 2)
|
|
516
|
+
|
|
517
|
+
function increment() {
|
|
518
|
+
count.value++
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return { count, doubled, increment }
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Usage in component
|
|
525
|
+
const { count, doubled, increment } = useCounter(10)
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### 4. Pinia Store
|
|
529
|
+
|
|
530
|
+
**Create feature store:**
|
|
531
|
+
```javascript
|
|
532
|
+
// stores/auth.js
|
|
533
|
+
import { defineStore } from 'pinia'
|
|
534
|
+
import { ref, computed } from 'vue'
|
|
535
|
+
|
|
536
|
+
export const useAuthStore = defineStore('auth', () => {
|
|
537
|
+
// State
|
|
538
|
+
const user = ref(null)
|
|
539
|
+
const token = ref(null)
|
|
540
|
+
|
|
541
|
+
// Getters
|
|
542
|
+
const isAuthenticated = computed(() => !!token.value)
|
|
543
|
+
|
|
544
|
+
// Actions
|
|
545
|
+
async function login(credentials) {
|
|
546
|
+
const response = await api.login(credentials)
|
|
547
|
+
user.value = response.user
|
|
548
|
+
token.value = response.token
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function logout() {
|
|
552
|
+
user.value = null
|
|
553
|
+
token.value = null
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return { user, token, isAuthenticated, login, logout }
|
|
557
|
+
})
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
### 5. Common Patterns
|
|
561
|
+
|
|
562
|
+
**Fetch data:**
|
|
563
|
+
```vue
|
|
564
|
+
<script setup>
|
|
565
|
+
import { ref, onMounted } from 'vue'
|
|
566
|
+
|
|
567
|
+
const data = ref(null)
|
|
568
|
+
const loading = ref(true)
|
|
569
|
+
const error = ref(null)
|
|
570
|
+
|
|
571
|
+
onMounted(async () => {
|
|
572
|
+
try {
|
|
573
|
+
data.value = await api.getData()
|
|
574
|
+
} catch (err) {
|
|
575
|
+
error.value = err
|
|
576
|
+
} finally {
|
|
577
|
+
loading.value = false
|
|
578
|
+
}
|
|
579
|
+
})
|
|
580
|
+
</script>
|
|
581
|
+
|
|
582
|
+
<template>
|
|
583
|
+
<div v-if="loading">Loading...</div>
|
|
584
|
+
<div v-else-if="error">Error: {{ error.message }}</div>
|
|
585
|
+
<div v-else>{{ data }}</div>
|
|
586
|
+
</template>
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
**Form handling:**
|
|
590
|
+
```vue
|
|
591
|
+
<script setup>
|
|
592
|
+
import { reactive, computed } from 'vue'
|
|
593
|
+
|
|
594
|
+
const form = reactive({
|
|
595
|
+
email: '',
|
|
596
|
+
password: ''
|
|
597
|
+
})
|
|
598
|
+
|
|
599
|
+
const errors = reactive({
|
|
600
|
+
email: '',
|
|
601
|
+
password: ''
|
|
602
|
+
})
|
|
603
|
+
|
|
604
|
+
const isValid = computed(() =>
|
|
605
|
+
form.email && form.password && !errors.email && !errors.password
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
function validateEmail() {
|
|
609
|
+
errors.email = form.email.includes('@') ? '' : 'Invalid email'
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
async function submit() {
|
|
613
|
+
if (!isValid.value) return
|
|
614
|
+
await api.login(form)
|
|
615
|
+
}
|
|
616
|
+
</script>
|
|
617
|
+
|
|
618
|
+
<template>
|
|
619
|
+
<form @submit.prevent="submit">
|
|
620
|
+
<input
|
|
621
|
+
v-model="form.email"
|
|
622
|
+
@blur="validateEmail"
|
|
623
|
+
:class="{ error: errors.email }"
|
|
624
|
+
>
|
|
625
|
+
<span v-if="errors.email">{{ errors.email }}</span>
|
|
626
|
+
|
|
627
|
+
<input v-model="form.password" type="password">
|
|
628
|
+
|
|
629
|
+
<button :disabled="!isValid">Login</button>
|
|
630
|
+
</form>
|
|
631
|
+
</template>
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
## For /dev-review
|
|
635
|
+
|
|
636
|
+
When reviewing Vue 3 code:
|
|
637
|
+
|
|
638
|
+
**Component Quality:**
|
|
639
|
+
- [ ] Uses `<script setup>` (not Options API)
|
|
640
|
+
- [ ] Props properly typed (TypeScript or PropType)
|
|
641
|
+
- [ ] Emits properly defined
|
|
642
|
+
- [ ] Component names are PascalCase
|
|
643
|
+
- [ ] SFC order: script → template → style
|
|
644
|
+
|
|
645
|
+
**Composition API:**
|
|
646
|
+
- [ ] Composables follow "use*" naming
|
|
647
|
+
- [ ] No conditional composable calls
|
|
648
|
+
- [ ] Composables called at top level of setup
|
|
649
|
+
- [ ] Reactive values returned from composables
|
|
650
|
+
- [ ] Cleanup handled (watchers, listeners)
|
|
651
|
+
|
|
652
|
+
**Reactivity:**
|
|
653
|
+
- [ ] ref() used for primitives
|
|
654
|
+
- [ ] reactive() used for objects
|
|
655
|
+
- [ ] .value accessed in script (not template)
|
|
656
|
+
- [ ] No destructuring reactive without toRefs
|
|
657
|
+
- [ ] No reassigning reactive objects
|
|
658
|
+
- [ ] computed() for derived state
|
|
659
|
+
- [ ] No complex logic in template
|
|
660
|
+
|
|
661
|
+
**Template:**
|
|
662
|
+
- [ ] v-for has unique :key
|
|
663
|
+
- [ ] No v-for + v-if on same element
|
|
664
|
+
- [ ] v-bind/v-on use shorthands (: and @)
|
|
665
|
+
- [ ] No inline objects/arrays (causes re-renders)
|
|
666
|
+
- [ ] Proper event naming (kebab-case)
|
|
667
|
+
|
|
668
|
+
**State Management:**
|
|
669
|
+
- [ ] Appropriate scope (local, composable, store)
|
|
670
|
+
- [ ] Props not mutated directly
|
|
671
|
+
- [ ] Emits used for child → parent
|
|
672
|
+
- [ ] Pinia stores follow composition pattern
|
|
673
|
+
- [ ] No Vuex (use Pinia)
|
|
674
|
+
|
|
675
|
+
**Performance:**
|
|
676
|
+
- [ ] Heavy components lazy loaded
|
|
677
|
+
- [ ] Large lists use stable keys
|
|
678
|
+
- [ ] v-memo used for expensive items
|
|
679
|
+
- [ ] No unnecessary reactivity (shallowRef/shallowReactive)
|
|
680
|
+
- [ ] Watchers cleaned up
|
|
681
|
+
|
|
682
|
+
**TypeScript:**
|
|
683
|
+
- [ ] Props typed properly
|
|
684
|
+
- [ ] Emits typed
|
|
685
|
+
- [ ] Composables return types defined
|
|
686
|
+
- [ ] No excessive use of any
|
|
687
|
+
|
|
688
|
+
## Integration with Other Stacks
|
|
689
|
+
|
|
690
|
+
**Vue + Nuxt:**
|
|
691
|
+
- Follow Nuxt conventions (pages/, composables/, server/)
|
|
692
|
+
- Use auto-imports
|
|
693
|
+
- Use Nuxt composables (useAsyncData, useFetch, etc.)
|
|
694
|
+
- Follow SSR guidelines
|
|
695
|
+
|
|
696
|
+
**Vue + Vite:**
|
|
697
|
+
- Fast dev server with HMR
|
|
698
|
+
- Use Vite plugins
|
|
699
|
+
- Leverage Vite's optimized build
|
|
700
|
+
|
|
701
|
+
**Vue + TypeScript:**
|
|
702
|
+
- Use `<script setup lang="ts">`
|
|
703
|
+
- Define props with interface
|
|
704
|
+
- Type emits and composables
|
|
705
|
+
- Use generic components
|
|
706
|
+
|
|
707
|
+
**Vue + Pinia:**
|
|
708
|
+
- Composition-style stores
|
|
709
|
+
- TypeScript support built-in
|
|
710
|
+
- DevTools integration
|
|
711
|
+
- Hot module replacement
|
|
712
|
+
|
|
713
|
+
## Common Gotchas
|
|
714
|
+
|
|
715
|
+
1. **Ref .value**: Forgetting .value in script, adding .value in template
|
|
716
|
+
2. **Destructuring Reactivity**: Losing reactivity when destructuring reactive()
|
|
717
|
+
3. **Conditional Composables**: Calling composables inside if/loops
|
|
718
|
+
4. **Props Mutation**: Mutating props directly instead of emitting
|
|
719
|
+
5. **v-for Key**: Using index or missing key entirely
|
|
720
|
+
6. **v-for + v-if**: Combining on same element (use computed filter)
|
|
721
|
+
7. **Template Refs**: Accessing $refs before onMounted
|
|
722
|
+
8. **Async Setup**: Can't use async setup() without Suspense
|
|
723
|
+
|
|
724
|
+
## Vue 3 Features
|
|
725
|
+
|
|
726
|
+
**Composition API:**
|
|
727
|
+
- setup() and <script setup>
|
|
728
|
+
- Composables for logic reuse
|
|
729
|
+
- Better TypeScript support
|
|
730
|
+
- More flexible code organization
|
|
731
|
+
|
|
732
|
+
**New APIs:**
|
|
733
|
+
- Teleport (render outside component hierarchy)
|
|
734
|
+
- Fragments (multiple root nodes)
|
|
735
|
+
- Suspense (async component loading)
|
|
736
|
+
- defineAsyncComponent (lazy loading)
|
|
737
|
+
|
|
738
|
+
**Performance:**
|
|
739
|
+
- Faster reactivity system (Proxy-based)
|
|
740
|
+
- Smaller bundle size
|
|
741
|
+
- Better tree-shaking
|
|
742
|
+
- Optimized virtual DOM
|
|
743
|
+
|
|
744
|
+
## Resources
|
|
745
|
+
|
|
746
|
+
- [Vue 3 Docs](https://vuejs.org) - Official documentation
|
|
747
|
+
- [Vue DevTools](https://devtools.vuejs.org) - Browser extension
|
|
748
|
+
- [Pinia](https://pinia.vuejs.org) - Official state management
|
|
749
|
+
- [Vue Router](https://router.vuejs.org) - Official router
|
|
750
|
+
- [Vite](https://vitejs.dev) - Recommended build tool
|
package/package.json
CHANGED
package/skills/_registry.md
CHANGED
|
@@ -181,6 +181,7 @@ Technical knowledge - HOW to build with specific tools:
|
|
|
181
181
|
| Stack | Folder | Type |
|
|
182
182
|
|-------|--------|------|
|
|
183
183
|
| React | `stacks/react/` | UI Library |
|
|
184
|
+
| Vue 3 | `stacks/vue/` | Progressive Framework |
|
|
184
185
|
| Next.js | `stacks/nextjs/` | React + SSR Framework |
|
|
185
186
|
| Nuxt.js | `stacks/nuxt/` | Vue + SSR Framework |
|
|
186
187
|
| Directus | `stacks/directus/` | Backend BaaS |
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: dev-coding
|
|
3
3
|
description: Implement features as a Principal Engineering Developer
|
|
4
|
-
version: 2.1.
|
|
4
|
+
version: 2.1.3
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# /dev-coding - Implementation Skill
|
|
@@ -68,6 +68,7 @@ You have three layers of knowledge to apply:
|
|
|
68
68
|
|
|
69
69
|
**Stack file mapping:**
|
|
70
70
|
- "React" → `knowledge/stacks/react/_index.md`
|
|
71
|
+
- "Vue" → `knowledge/stacks/vue/_index.md`
|
|
71
72
|
- "Next.js" → `knowledge/stacks/nextjs/_index.md`
|
|
72
73
|
- "Nuxt" → `knowledge/stacks/nuxt/_index.md`
|
|
73
74
|
- "Directus" → `knowledge/stacks/directus/_index.md`
|
|
@@ -178,6 +179,7 @@ Implementation
|
|
|
178
179
|
|
|
179
180
|
2. **Load stack knowledge files:**
|
|
180
181
|
- If "React" detected → Read `knowledge/stacks/react/_index.md`
|
|
182
|
+
- If "Vue" detected → Read `knowledge/stacks/vue/_index.md`
|
|
181
183
|
- If "Next.js" detected → Read `knowledge/stacks/nextjs/_index.md`
|
|
182
184
|
- If "Nuxt" detected → Read `knowledge/stacks/nuxt/_index.md`
|
|
183
185
|
- If "Directus" detected → Read `knowledge/stacks/directus/_index.md`
|
|
@@ -337,6 +339,7 @@ Implementation successful when:
|
|
|
337
339
|
|
|
338
340
|
**Layer 2 - Framework:**
|
|
339
341
|
- `knowledge/stacks/react/_index.md` - React patterns (Hooks, Context, performance)
|
|
342
|
+
- `knowledge/stacks/vue/_index.md` - Vue 3 patterns (Composition API, reactivity, SFC)
|
|
340
343
|
- `knowledge/stacks/nextjs/_index.md` - Next.js patterns (Server Actions, App Router, RSC)
|
|
341
344
|
- `knowledge/stacks/nuxt/_index.md` - Nuxt patterns (composables, Nuxt UI, SSR)
|
|
342
345
|
- `knowledge/stacks/directus/_index.md` - Directus patterns (collections, flows, extensions)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: dev-review
|
|
3
3
|
description: Code review with focus on quality, security, and best practices
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.4.1
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# /dev-review - Code Review
|
|
@@ -80,12 +80,45 @@ Review report with verdict and categorized issues.
|
|
|
80
80
|
- Positives (acknowledge good work)
|
|
81
81
|
- Spec compliance (if UC provided)
|
|
82
82
|
|
|
83
|
+
## Review Workflow
|
|
84
|
+
|
|
85
|
+
**1. Load Context (once):**
|
|
86
|
+
- Read `plans/brd/tech-context.md` → Identify stack(s)
|
|
87
|
+
- Load `knowledge/stacks/{stack}/_index.md` → Focus on "For /dev-review" section
|
|
88
|
+
- Read `_quality-attributes.md` → Get all level checklists
|
|
89
|
+
- Read spec (if UC provided) → Get requirements
|
|
90
|
+
- Get git diff or file list → Understand what changed
|
|
91
|
+
|
|
92
|
+
**2. Analyze Changes:**
|
|
93
|
+
- Review each changed file
|
|
94
|
+
- Apply: Security + Quality + Conventions + Stack-Specific checks
|
|
95
|
+
- Note issues by severity (critical/important/suggestion)
|
|
96
|
+
- Acknowledge good practices
|
|
97
|
+
|
|
98
|
+
**3. Verify Spec Compliance (if UC provided):**
|
|
99
|
+
- Check all requirements met
|
|
100
|
+
- Identify missing items
|
|
101
|
+
- Flag unauthorized additions
|
|
102
|
+
|
|
103
|
+
**4. Generate Report:**
|
|
104
|
+
- Verdict (Approve/Request Changes/Needs Discussion)
|
|
105
|
+
- Issues by severity
|
|
106
|
+
- Issues by file
|
|
107
|
+
- Spec compliance status
|
|
108
|
+
- Positives
|
|
109
|
+
|
|
110
|
+
**5. Offer Fix (if issues found):**
|
|
111
|
+
- Critical/Important → Suggest auto-fix via `/dev-coding`
|
|
112
|
+
- Suggestions → User decides
|
|
113
|
+
|
|
83
114
|
## Success Criteria
|
|
84
115
|
|
|
116
|
+
- Stack knowledge loaded and applied
|
|
85
117
|
- Security risks identified
|
|
86
118
|
- Quality issues found
|
|
87
119
|
- Spec compliance verified
|
|
88
120
|
- Conventions checked
|
|
121
|
+
- Framework patterns verified (from stack knowledge)
|
|
89
122
|
- Constructive feedback with suggested fixes
|
|
90
123
|
- Clear verdict given
|
|
91
124
|
|
|
@@ -116,12 +149,47 @@ Review report with verdict and categorized issues.
|
|
|
116
149
|
- Not in spec (unauthorized additions)
|
|
117
150
|
|
|
118
151
|
### Stack-Specific
|
|
119
|
-
Read stack knowledge files (knowledge/stacks/) for stack-specific review checklists.
|
|
120
152
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
153
|
+
**CRITICAL: Load framework-specific review checklists**
|
|
154
|
+
|
|
155
|
+
After loading stack knowledge (from Context Sources → Step 1), apply framework-specific checks from the "For /dev-review" section:
|
|
156
|
+
|
|
157
|
+
**React projects:**
|
|
158
|
+
- Hooks follow Rules of Hooks (no conditional calls, correct order)
|
|
159
|
+
- All dependencies included in useEffect/useCallback/useMemo
|
|
160
|
+
- No infinite loops, no stale closures
|
|
161
|
+
- State updates are immutable
|
|
162
|
+
- Performance optimizations appropriate (memo, lazy)
|
|
163
|
+
- Lists have stable keys
|
|
164
|
+
|
|
165
|
+
**Vue 3 projects:**
|
|
166
|
+
- Uses `<script setup>` (not Options API)
|
|
167
|
+
- Composables follow naming convention (use*)
|
|
168
|
+
- No destructuring reactive without toRefs
|
|
169
|
+
- ref() accessed with .value in script (not in template)
|
|
170
|
+
- v-for has unique :key
|
|
171
|
+
- No v-for + v-if on same element
|
|
172
|
+
- Props not mutated directly
|
|
173
|
+
|
|
174
|
+
**Next.js projects:**
|
|
175
|
+
- Server vs Client Components used correctly
|
|
176
|
+
- Server Actions have "use server" directive
|
|
177
|
+
- Data fetching uses proper Next.js patterns
|
|
178
|
+
- No client-side code in Server Components
|
|
179
|
+
|
|
180
|
+
**Nuxt projects:**
|
|
181
|
+
- Composables follow naming convention (useX)
|
|
182
|
+
- Server routes in correct location
|
|
183
|
+
- Auto-imports used correctly
|
|
184
|
+
- SSR-safe code (no window access in setup)
|
|
185
|
+
|
|
186
|
+
**Directus projects:**
|
|
187
|
+
- Collections accessed properly
|
|
188
|
+
- Permissions configured
|
|
189
|
+
- Field types match requirements
|
|
190
|
+
- Flows and hooks follow patterns
|
|
191
|
+
|
|
192
|
+
**If stack knowledge file doesn't exist**, apply general best practices only.
|
|
125
193
|
|
|
126
194
|
## Context Sources
|
|
127
195
|
|
|
@@ -130,12 +198,23 @@ Examples:
|
|
|
130
198
|
- Specific files (if provided)
|
|
131
199
|
|
|
132
200
|
**Read to understand standards:**
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
-
|
|
136
|
-
-
|
|
137
|
-
-
|
|
138
|
-
-
|
|
201
|
+
|
|
202
|
+
**Step 1: Load stack knowledge (CRITICAL)**
|
|
203
|
+
- Read `plans/brd/tech-context.md` → Identify stack(s)
|
|
204
|
+
- Extract stack names (look for "Primary Stack" or "Tech Stack" section)
|
|
205
|
+
- Load corresponding knowledge files:
|
|
206
|
+
- If "React" → Read `knowledge/stacks/react/_index.md` → Focus on "For /dev-review" section
|
|
207
|
+
- If "Vue" → Read `knowledge/stacks/vue/_index.md` → Focus on "For /dev-review" section
|
|
208
|
+
- If "Next.js" → Read `knowledge/stacks/nextjs/_index.md` → Focus on "For /dev-review" section
|
|
209
|
+
- If "Nuxt" → Read `knowledge/stacks/nuxt/_index.md` → Focus on "For /dev-review" section
|
|
210
|
+
- If "Directus" → Read `knowledge/stacks/directus/_index.md` → Focus on "For /dev-review" section
|
|
211
|
+
|
|
212
|
+
**Step 2: Load project and spec context**
|
|
213
|
+
- `tech-context.md` - Project conventions
|
|
214
|
+
- `architecture.md` - Architecture decisions (if exists)
|
|
215
|
+
- `spec` - Requirements (if UC provided)
|
|
216
|
+
- `_quality-attributes.md` - ALL levels (Architecture, Specification, Implementation, Review)
|
|
217
|
+
- Config files (`.eslintrc`, `tsconfig.json`, etc.)
|
|
139
218
|
|
|
140
219
|
## Severity Levels
|
|
141
220
|
|
|
@@ -225,17 +304,22 @@ const posts = await db.posts.findMany({
|
|
|
225
304
|
```
|
|
226
305
|
User: /dev-review UC-AUTH-001
|
|
227
306
|
|
|
228
|
-
|
|
229
|
-
-
|
|
230
|
-
- Read
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
307
|
+
Step 1: Load Context
|
|
308
|
+
- Read tech-context.md → Stack: Next.js 14 + TypeScript
|
|
309
|
+
- Read knowledge/stacks/nextjs/_index.md → "For /dev-review" section
|
|
310
|
+
→ Check Server Actions, App Router, Server Components
|
|
311
|
+
- Read knowledge/stacks/react/_index.md → "For /dev-review" section
|
|
312
|
+
→ Check Hooks, state management, performance
|
|
313
|
+
- Read _quality-attributes.md → All level checklists
|
|
314
|
+
- Read UC-AUTH-001 spec → Requirements
|
|
315
|
+
- Get git diff → Changed files
|
|
316
|
+
|
|
317
|
+
Step 2: Analyze
|
|
234
318
|
- src/api/auth/login.ts: Clean ✓
|
|
235
|
-
- src/components/LoginForm.tsx: 1 issue
|
|
236
|
-
- src/lib/api.ts: 1 suggestion
|
|
319
|
+
- src/components/LoginForm.tsx: 1 issue (React hooks deps missing)
|
|
320
|
+
- src/lib/api.ts: 1 suggestion (naming)
|
|
237
321
|
|
|
238
|
-
|
|
322
|
+
Step 3: Generate Report
|
|
239
323
|
|
|
240
324
|
## Review Summary
|
|
241
325
|
|