mdk-skills 2.1.3

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 (66) hide show
  1. package/.claude/.install.log +4 -0
  2. package/.claude/settings.json +64 -0
  3. package/.claude/settings.local.json +7 -0
  4. package/.claude/skills/agentation/.meta.json +6 -0
  5. package/.claude/skills/agentation/SKILL.md +107 -0
  6. package/.claude/skills/fe-biz-patterns/.meta.json +6 -0
  7. package/.claude/skills/fe-biz-patterns/SKILL.md +26 -0
  8. package/.claude/skills/fe-biz-patterns/references/infinite-scroll.md +292 -0
  9. package/.claude/skills/fe-biz-patterns/references/pinia-store.md +174 -0
  10. package/.claude/skills/fe-biz-patterns/references/service-layer.md +198 -0
  11. package/.claude/skills/fe-biz-patterns/references/tab-anchor.md +1125 -0
  12. package/.claude/skills/fe-biz-patterns/references/use-loading.md +114 -0
  13. package/.claude/skills/frontend-code-review/.meta.json +6 -0
  14. package/.claude/skills/frontend-code-review/SKILL.md +167 -0
  15. package/.claude/skills/frontend-code-review/references/checklist.md +298 -0
  16. package/.claude/skills/frontend-design/.meta.json +6 -0
  17. package/.claude/skills/frontend-design/LICENSE.txt +177 -0
  18. package/.claude/skills/frontend-design/SKILL.md +42 -0
  19. package/.claude/skills/moai-framework-electron/.meta.json +6 -0
  20. package/.claude/skills/moai-framework-electron/SKILL.md +328 -0
  21. package/.claude/skills/skill-creator/.meta.json +6 -0
  22. package/.claude/skills/skill-creator/SKILL.md +356 -0
  23. package/.claude/skills/skill-creator/references/output-patterns.md +82 -0
  24. package/.claude/skills/skill-creator/references/workflows.md +28 -0
  25. package/.claude/skills/skill-creator/scripts/init_skill.py +303 -0
  26. package/.claude/skills/skill-creator/scripts/package_skill.py +110 -0
  27. package/.claude/skills/skill-creator/scripts/quick_validate.py +95 -0
  28. package/.claude/skills/ui-ux-pro-max/.meta.json +6 -0
  29. package/.claude/skills/ui-ux-pro-max/SKILL.md +228 -0
  30. package/.claude/skills/ui-ux-pro-max/data/charts.csv +26 -0
  31. package/.claude/skills/ui-ux-pro-max/data/colors.csv +97 -0
  32. package/.claude/skills/ui-ux-pro-max/data/landing.csv +31 -0
  33. package/.claude/skills/ui-ux-pro-max/data/products.csv +97 -0
  34. package/.claude/skills/ui-ux-pro-max/data/prompts.csv +24 -0
  35. package/.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  36. package/.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  37. package/.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  38. package/.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  39. package/.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  40. package/.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  41. package/.claude/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  42. package/.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  43. package/.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  44. package/.claude/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  45. package/.claude/skills/ui-ux-pro-max/data/styles.csv +59 -0
  46. package/.claude/skills/ui-ux-pro-max/data/typography.csv +58 -0
  47. package/.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  48. package/.claude/skills/ui-ux-pro-max/scripts/core.py +238 -0
  49. package/.claude/skills/ui-ux-pro-max/scripts/search.py +61 -0
  50. package/.claude/skills/vue/.meta.json +6 -0
  51. package/.claude/skills/vue/SKILL.md +103 -0
  52. package/.claude/skills/vue/references/components.md +323 -0
  53. package/.claude/skills/vue/references/composables.md +358 -0
  54. package/.claude/skills/vue/references/directives.md +225 -0
  55. package/.claude/skills/vue/references/gotchas.md +438 -0
  56. package/.claude/skills/vue/references/provide-inject.md +174 -0
  57. package/.claude/skills/vue/references/reactivity.md +289 -0
  58. package/.claude/skills/vue/references/router.md +181 -0
  59. package/.claude/skills/vue/references/testing.md +294 -0
  60. package/.claude/skills/vue/references/typescript.md +172 -0
  61. package/.claude/skills/vue/references/utils-client.md +156 -0
  62. package/CLAUDE.md +131 -0
  63. package/package.json +23 -0
  64. package/scripts/cli.js +260 -0
  65. package/scripts/copy-skills.js +86 -0
  66. package/scripts/core.js +256 -0
@@ -0,0 +1,225 @@
1
+ ---
2
+ name: Custom Directives
3
+ description: Create reusable directives for low-level DOM manipulation
4
+ ---
5
+
6
+ # Custom Directives
7
+
8
+ Custom directives provide low-level DOM access for reusable behavior.
9
+
10
+ ## When to Use
11
+
12
+ Use custom directives when:
13
+
14
+ - You need direct DOM manipulation
15
+ - The behavior can't be achieved with components or composables
16
+ - You need to apply behavior to native elements
17
+
18
+ ## Basic Example
19
+
20
+ ```vue
21
+ <script setup lang="ts">
22
+ // v-focus directive
23
+ const vFocus = {
24
+ mounted: (el: HTMLElement) => el.focus()
25
+ }
26
+ </script>
27
+
28
+ <template>
29
+ <input v-focus />
30
+ </template>
31
+ ```
32
+
33
+ ## Directive Hooks
34
+
35
+ ```ts
36
+ const myDirective = {
37
+ // Before element attributes/listeners are applied
38
+ created(el, binding, vnode) {},
39
+
40
+ // Before element is inserted into DOM
41
+ beforeMount(el, binding, vnode) {},
42
+
43
+ // After element and children are mounted
44
+ mounted(el, binding, vnode) {},
45
+
46
+ // Before parent component updates
47
+ beforeUpdate(el, binding, vnode, prevVnode) {},
48
+
49
+ // After parent component updates
50
+ updated(el, binding, vnode, prevVnode) {},
51
+
52
+ // Before parent component unmounts
53
+ beforeUnmount(el, binding, vnode) {},
54
+
55
+ // After parent component unmounts
56
+ unmounted(el, binding, vnode) {}
57
+ }
58
+ ```
59
+
60
+ ## Hook Arguments
61
+
62
+ ```ts
63
+ interface DirectiveBinding<T = any> {
64
+ value: T // v-my-dir="value"
65
+ oldValue: T // Previous value (beforeUpdate/updated only)
66
+ arg?: string // v-my-dir:arg
67
+ modifiers: Record<string, boolean> // v-my-dir.foo.bar → { foo: true, bar: true }
68
+ instance: ComponentPublicInstance // Component using the directive
69
+ dir: ObjectDirective // Directive definition object
70
+ }
71
+ ```
72
+
73
+ Example usage:
74
+
75
+ ```vue-html
76
+ <div v-example:foo.bar="baz">
77
+ ```
78
+
79
+ ```ts
80
+ // binding object:
81
+ {
82
+ arg: 'foo',
83
+ modifiers: { bar: true },
84
+ value: /* value of baz */,
85
+ oldValue: /* previous value */
86
+ }
87
+ ```
88
+
89
+ ## Function Shorthand
90
+
91
+ When you only need `mounted` and `updated` with same behavior:
92
+
93
+ ```ts
94
+ // Full form
95
+ const vColor = {
96
+ mounted(el, binding) {
97
+ el.style.color = binding.value
98
+ },
99
+ updated(el, binding) {
100
+ el.style.color = binding.value
101
+ }
102
+ }
103
+
104
+ // Shorthand (same behavior)
105
+ const vColor = (el: HTMLElement, binding: DirectiveBinding<string>) => {
106
+ el.style.color = binding.value
107
+ }
108
+ ```
109
+
110
+ ## Global Registration
111
+
112
+ ```ts
113
+ // main.ts
114
+ const app = createApp(App)
115
+
116
+ app.directive('focus', {
117
+ mounted: (el) => el.focus()
118
+ })
119
+
120
+ // Shorthand
121
+ app.directive('color', (el, binding) => {
122
+ el.style.color = binding.value
123
+ })
124
+ ```
125
+
126
+ ## Object Literals
127
+
128
+ Pass multiple values:
129
+
130
+ ```vue-html
131
+ <div v-demo="{ color: 'white', text: 'hello' }">
132
+ ```
133
+
134
+ ```ts
135
+ const vDemo = (el: HTMLElement, binding: DirectiveBinding<{ color: string; text: string }>) => {
136
+ console.log(binding.value.color) // 'white'
137
+ console.log(binding.value.text) // 'hello'
138
+ }
139
+ ```
140
+
141
+ ## Dynamic Arguments
142
+
143
+ ```vue-html
144
+ <div v-my-directive:[dynamicArg]="value">
145
+ ```
146
+
147
+ ## Practical Examples
148
+
149
+ ### v-click-outside
150
+
151
+ ```ts
152
+ const vClickOutside = {
153
+ mounted(el: HTMLElement, binding: DirectiveBinding<() => void>) {
154
+ el._clickOutside = (event: MouseEvent) => {
155
+ if (!el.contains(event.target as Node)) {
156
+ binding.value()
157
+ }
158
+ }
159
+ document.addEventListener('click', el._clickOutside)
160
+ },
161
+ unmounted(el: HTMLElement) {
162
+ document.removeEventListener('click', el._clickOutside)
163
+ }
164
+ }
165
+ ```
166
+
167
+ ### v-tooltip
168
+
169
+ ```ts
170
+ const vTooltip = {
171
+ mounted(el: HTMLElement, binding: DirectiveBinding<string>) {
172
+ el.setAttribute('title', binding.value)
173
+ },
174
+ updated(el: HTMLElement, binding: DirectiveBinding<string>) {
175
+ el.setAttribute('title', binding.value)
176
+ }
177
+ }
178
+ ```
179
+
180
+ ### v-permission
181
+
182
+ ```ts
183
+ const vPermission = {
184
+ mounted(el: HTMLElement, binding: DirectiveBinding<string>) {
185
+ if (!hasPermission(binding.value)) {
186
+ el.parentNode?.removeChild(el)
187
+ }
188
+ }
189
+ }
190
+ ```
191
+
192
+ ## TypeScript: Global Directives
193
+
194
+ ```ts
195
+ // directives/highlight.ts
196
+ import type { Directive } from 'vue'
197
+
198
+ export type HighlightDirective = Directive<HTMLElement, string>
199
+
200
+ declare module 'vue' {
201
+ export interface ComponentCustomProperties {
202
+ vHighlight: HighlightDirective
203
+ }
204
+ }
205
+
206
+ export default {
207
+ mounted: (el, binding) => {
208
+ el.style.backgroundColor = binding.value
209
+ }
210
+ } satisfies HighlightDirective
211
+ ```
212
+
213
+ ## Usage on Components
214
+
215
+ ⚠️ **Not recommended** - directives apply to root element, which can be unpredictable with multi-root components.
216
+
217
+ ```vue-html
218
+ <!-- Applies to MyComponent's root element -->
219
+ <MyComponent v-my-directive />
220
+ ```
221
+
222
+ <!--
223
+ Source references:
224
+ - https://vuejs.org/guide/reusability/custom-directives.html
225
+ -->
@@ -0,0 +1,438 @@
1
+ # Vue Common Gotchas & Edge Cases
2
+
3
+ Critical Vue 3 gotchas that cause silent failures or hard-to-debug issues.
4
+
5
+ > Based on [vuejs-ai/skills](https://github.com/vuejs-ai/skills) vue-best-practices. For comprehensive coverage (200+ rules), see the upstream repo.
6
+
7
+ ## Reactivity
8
+
9
+ ### Always Use `.value` When Accessing ref() in Scripts
10
+
11
+ **Impact: HIGH** - Forgetting `.value` causes silent failures.
12
+
13
+ ```ts
14
+ const count = ref(0)
15
+
16
+ // WRONG
17
+ count++ // Tries to increment the ref object
18
+ count = 5 // Reassigns variable, loses reactivity
19
+ items.push(4) // Error: push is not a function
20
+
21
+ // CORRECT
22
+ count.value++
23
+ count.value = 5
24
+ items.value.push(4)
25
+
26
+ // In templates - NO .value needed (Vue unwraps automatically)
27
+ // {{ count }} works, not {{ count.value }}
28
+ ```
29
+
30
+ ### Never Destructure reactive() Objects Directly
31
+
32
+ **Impact: HIGH** - Destructuring breaks reactive connection.
33
+
34
+ ```ts
35
+ const state = reactive({ count: 0, name: 'Vue' })
36
+
37
+ // WRONG - destructured variables lose reactivity
38
+ const { count, name } = state
39
+ state.count++
40
+ console.log(count) // Still 0!
41
+
42
+ // CORRECT - use toRefs()
43
+ const { count, name } = toRefs(state)
44
+ state.count++
45
+ console.log(count.value) // 1
46
+
47
+ // BEST - just use ref() instead of reactive()
48
+ const count = ref(0)
49
+ const name = ref('Vue')
50
+ ```
51
+
52
+ ### Proxy Identity Hazard with reactive()
53
+
54
+ ```ts
55
+ const raw = {}
56
+ const proxy = reactive(raw)
57
+
58
+ // WRONG - comparing different objects
59
+ console.log(proxy === raw) // false
60
+
61
+ // WRONG - creating multiple proxies
62
+ const a = reactive({})
63
+ const b = reactive(a) // Returns same proxy
64
+ console.log(a === b) // true (same object)
65
+
66
+ // GOTCHA - nested objects get proxied too
67
+ const nested = reactive({ obj: {} })
68
+ console.log(nested.obj === nested.obj) // true (same proxy)
69
+ ```
70
+
71
+ ## Computed Properties
72
+
73
+ ### No Side Effects in Computed Getters
74
+
75
+ **Impact: HIGH** - Side effects break reactivity model.
76
+
77
+ ```ts
78
+ // WRONG - mutates state
79
+ const doubled = computed(() => {
80
+ count.value++ // Side effect!
81
+ return count.value * 2
82
+ })
83
+
84
+ // WRONG - async operation
85
+ const data = computed(async () => {
86
+ return await fetch('/api') // Side effect!
87
+ })
88
+
89
+ // CORRECT - pure computation only
90
+ const doubled = computed(() => count.value * 2)
91
+
92
+ // For side effects, use watch:
93
+ watch(count, (newVal) => {
94
+ document.title = `Count: ${newVal}`
95
+ })
96
+ ```
97
+
98
+ ### Computed Returns Are Read-Only
99
+
100
+ ```ts
101
+ const fullName = computed(() => `${first.value} ${last.value}`)
102
+
103
+ // WRONG - computed values are read-only
104
+ fullName.value = 'John Doe' // Error!
105
+
106
+ // CORRECT - use writable computed
107
+ const fullName = computed({
108
+ get: () => `${first.value} ${last.value}`,
109
+ set: (val) => {
110
+ const [f, l] = val.split(' ')
111
+ first.value = f
112
+ last.value = l
113
+ }
114
+ })
115
+ ```
116
+
117
+ ## Watchers
118
+
119
+ ### Clean Up Async Operations to Prevent Race Conditions
120
+
121
+ **Impact: HIGH** - Stale requests can overwrite newer data.
122
+
123
+ ```ts
124
+ const query = ref('')
125
+ const results = ref([])
126
+
127
+ // WRONG - race condition
128
+ watch(query, async (q) => {
129
+ const res = await fetch(`/api?q=${q}`)
130
+ results.value = await res.json() // May overwrite newer results!
131
+ })
132
+
133
+ // CORRECT - use onWatcherCleanup (Vue 3.5+)
134
+ watch(query, async (q) => {
135
+ const controller = new AbortController()
136
+ onWatcherCleanup(() => controller.abort())
137
+
138
+ try {
139
+ const res = await fetch(`/api?q=${q}`, { signal: controller.signal })
140
+ results.value = await res.json()
141
+ } catch (e) {
142
+ if (e.name !== 'AbortError') throw e
143
+ }
144
+ })
145
+
146
+ // Or use onCleanup parameter
147
+ watch(query, async (q, oldQ, onCleanup) => {
148
+ const controller = new AbortController()
149
+ onCleanup(() => controller.abort())
150
+ // ... same as above
151
+ })
152
+ ```
153
+
154
+ ### Deep Watch Returns Same Object Reference
155
+
156
+ ```ts
157
+ const obj = reactive({ nested: { count: 0 } })
158
+
159
+ // GOTCHA - oldValue === newValue for deep watches
160
+ watch(obj, (newVal, oldVal) => {
161
+ console.log(newVal === oldVal) // true! Same object
162
+ }, { deep: true })
163
+
164
+ // If you need old value, clone first:
165
+ watch(
166
+ () => structuredClone(obj),
167
+ (newVal, oldVal) => { /* now different */ }
168
+ )
169
+ ```
170
+
171
+ ## Props
172
+
173
+ ### Props Are Read-Only - Never Mutate
174
+
175
+ **Impact: HIGH** - Breaks one-way data flow.
176
+
177
+ ```ts
178
+ const props = defineProps<{ count: number; user: User }>()
179
+
180
+ // WRONG - direct mutation
181
+ props.count++ // Vue warning
182
+ props.user.name = 'New' // No warning but still wrong!
183
+
184
+ // CORRECT - emit to parent
185
+ const emit = defineEmits(['update:count', 'update-user'])
186
+ emit('update:count', props.count + 1)
187
+ emit('update-user', { ...props.user, name: 'New' })
188
+
189
+ // Or create local copy
190
+ const localUser = ref({ ...props.user })
191
+ ```
192
+
193
+ ### Destructured Props Don't Update Watchers (pre-3.5)
194
+
195
+ ```ts
196
+ // WRONG (Vue < 3.5)
197
+ const { count } = defineProps<{ count: number }>()
198
+ watch(count, () => {}) // Won't trigger!
199
+
200
+ // CORRECT - use getter
201
+ const props = defineProps<{ count: number }>()
202
+ watch(() => props.count, () => {})
203
+
204
+ // Vue 3.5+ - destructuring works with reactive props
205
+ const { count } = defineProps<{ count: number }>()
206
+ watch(() => count, () => {}) // Works in 3.5+
207
+ ```
208
+
209
+ ## Lifecycle Hooks
210
+
211
+ ### Register Hooks Synchronously During Setup
212
+
213
+ **Impact: HIGH** - Async hooks silently fail.
214
+
215
+ ```ts
216
+ // WRONG - hook registered after await
217
+ async setup() {
218
+ const data = await fetchData()
219
+ onMounted(() => {}) // Will NEVER run!
220
+ }
221
+
222
+ // WRONG - hook in setTimeout
223
+ setup() {
224
+ setTimeout(() => {
225
+ onMounted(() => {}) // Will NEVER run!
226
+ }, 100)
227
+ }
228
+
229
+ // CORRECT - register synchronously, async inside
230
+ setup() {
231
+ onMounted(async () => {
232
+ const data = await fetchData()
233
+ })
234
+ }
235
+ ```
236
+
237
+ ## Templates
238
+
239
+ ### Never Use v-if with v-for on Same Element
240
+
241
+ **Impact: HIGH** - Vue 2/3 precedence differs.
242
+
243
+ ```vue
244
+ <!-- WRONG - ambiguous precedence -->
245
+ <li v-for="user in users" v-if="user.active" :key="user.id">
246
+
247
+ <!-- Vue 3: v-if runs FIRST, 'user' undefined! -->
248
+
249
+ <!-- CORRECT - computed filter -->
250
+ <li v-for="user in activeUsers" :key="user.id">
251
+
252
+ <script setup>
253
+ const activeUsers = computed(() => users.filter(u => u.active))
254
+ </script>
255
+
256
+ <!-- CORRECT - template wrapper -->
257
+ <template v-for="user in users" :key="user.id">
258
+ <li v-if="user.active">{{ user.name }}</li>
259
+ </template>
260
+ ```
261
+
262
+ ### Template Refs Are Null with v-if
263
+
264
+ ```ts
265
+ const inputRef = ref<HTMLInputElement | null>(null)
266
+
267
+ // GOTCHA - ref is null when element hidden
268
+ <input v-if="show" ref="inputRef" />
269
+
270
+ // WRONG - may be null
271
+ inputRef.value.focus() // Error if !show
272
+
273
+ // CORRECT - null check
274
+ inputRef.value?.focus()
275
+
276
+ // Or use watchEffect with flush: 'post'
277
+ watchEffect(() => {
278
+ inputRef.value?.focus()
279
+ }, { flush: 'post' })
280
+ ```
281
+
282
+ ## defineModel
283
+
284
+ ### Object Mutations Don't Emit
285
+
286
+ ```ts
287
+ const model = defineModel<{ name: string }>()
288
+
289
+ // WRONG - mutation doesn't notify parent
290
+ model.value.name = 'New' // Parent won't know!
291
+
292
+ // CORRECT - replace entire object
293
+ model.value = { ...model.value, name: 'New' }
294
+ ```
295
+
296
+ ### Updated Value Needs nextTick
297
+
298
+ ```ts
299
+ const model = defineModel<string>()
300
+
301
+ // WRONG - value not updated yet
302
+ model.value = 'new'
303
+ console.log(model.value) // Still old value!
304
+
305
+ // CORRECT - wait for nextTick
306
+ model.value = 'new'
307
+ await nextTick()
308
+ console.log(model.value) // Now 'new'
309
+ ```
310
+
311
+ ## Component Events
312
+
313
+ ### Undeclared Emits Can Fire Twice
314
+
315
+ ```ts
316
+ // WRONG - missing emit declaration causes double firing
317
+ const emit = defineEmits([]) // 'click' not declared
318
+ <button @click="emit('click')"> // Fires twice!
319
+
320
+ // CORRECT - declare all custom events
321
+ const emit = defineEmits(['click'])
322
+ ```
323
+
324
+ ### Events Don't Bubble Through Components
325
+
326
+ ```vue
327
+ <!-- Parent can't listen to grandchild events directly -->
328
+ <Grandparent>
329
+ <Parent>
330
+ <Child @custom="handler" /> <!-- Only Parent can listen -->
331
+ </Parent>
332
+ </Grandparent>
333
+
334
+ <!-- Solution: re-emit or use provide/inject -->
335
+ ```
336
+
337
+ ## Provide/Inject
338
+
339
+ ### Reactivity Not Automatic
340
+
341
+ ```ts
342
+ // Provider
343
+ const count = ref(0)
344
+ provide('count', count) // Pass the ref, not .value
345
+
346
+ // Consumer
347
+ const count = inject('count') // Receives the ref
348
+ console.log(count.value) // Reactive!
349
+
350
+ // WRONG - loses reactivity
351
+ provide('count', count.value) // Just passes number
352
+ ```
353
+
354
+ ### Must Call Provide Synchronously
355
+
356
+ ```ts
357
+ // WRONG - provide after async
358
+ async setup() {
359
+ await fetchData()
360
+ provide('key', value) // Silently fails!
361
+ }
362
+
363
+ // CORRECT
364
+ setup() {
365
+ provide('key', value) // Synchronous
366
+ onMounted(async () => {
367
+ await fetchData()
368
+ })
369
+ }
370
+ ```
371
+
372
+ ## SSR
373
+
374
+ ### Lifecycle Hooks Don't Run on Server
375
+
376
+ ```ts
377
+ // onMounted, onUpdated, onUnmounted - client only
378
+ onMounted(() => {
379
+ // Only runs in browser
380
+ window.addEventListener('resize', handler)
381
+ })
382
+
383
+ // For SSR, use onServerPrefetch for data
384
+ onServerPrefetch(async () => {
385
+ data.value = await fetchData()
386
+ })
387
+ ```
388
+
389
+ ### Hydration Mismatch Causes
390
+
391
+ Common causes:
392
+
393
+ - Browser-only APIs (`window`, `localStorage`)
394
+ - Different timestamps
395
+ - Random values
396
+ - User-agent specific rendering
397
+
398
+ ```ts
399
+ // WRONG
400
+ const width = ref(window.innerWidth) // undefined on server
401
+
402
+ // CORRECT
403
+ const width = ref(0)
404
+ onMounted(() => {
405
+ width.value = window.innerWidth
406
+ })
407
+ ```
408
+
409
+ ## Performance
410
+
411
+ ### Use shallowRef for Large Non-Reactive Data
412
+
413
+ ```ts
414
+ // WRONG - deep reactivity overhead
415
+ const hugeList = ref(thousandsOfItems)
416
+
417
+ // CORRECT - only track .value assignment
418
+ const hugeList = shallowRef(thousandsOfItems)
419
+
420
+ // Trigger update by replacing entire array
421
+ hugeList.value = [...hugeList.value, newItem]
422
+ ```
423
+
424
+ ### markRaw for Non-Reactive Objects
425
+
426
+ ```ts
427
+ // WRONG - Chart.js instance becomes reactive (breaks it)
428
+ const chart = ref(new Chart(ctx, config))
429
+
430
+ // CORRECT - mark as non-reactive
431
+ const chart = ref(markRaw(new Chart(ctx, config)))
432
+ ```
433
+
434
+ ## References
435
+
436
+ - [vuejs-ai/skills vue-best-practices](https://github.com/vuejs-ai/skills/tree/main/skills/vue-best-practices) - Full 200+ rules
437
+ - [Vue Style Guide](https://vuejs.org/style-guide/)
438
+ - [Vue 3 Migration Guide](https://v3-migration.vuejs.org/)