@dianzhong/create-harness-app 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/index.mjs +412 -0
  2. package/package.json +29 -0
  3. package/templates/axios/.env.example +2 -0
  4. package/templates/axios/src/api/auth.ts +19 -0
  5. package/templates/axios/src/api/request.ts +61 -0
  6. package/templates/axios/src/types/api.ts +26 -0
  7. package/templates/axios/src/utils/auth.ts +5 -0
  8. package/templates/axios/src/utils/storage.ts +17 -0
  9. package/templates/harness/full/.agents/skills/find-skills/SKILL.md +143 -0
  10. package/templates/harness/full/.agents/skills/vue-best-practices/LICENSE.md +21 -0
  11. package/templates/harness/full/.agents/skills/vue-best-practices/SKILL.md +155 -0
  12. package/templates/harness/full/.agents/skills/vue-best-practices/SYNC.md +5 -0
  13. package/templates/harness/full/.agents/skills/vue-best-practices/references/animation-class-based-technique.md +258 -0
  14. package/templates/harness/full/.agents/skills/vue-best-practices/references/animation-state-driven-technique.md +287 -0
  15. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-async.md +99 -0
  16. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-data-flow.md +313 -0
  17. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-fallthrough-attrs.md +179 -0
  18. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-keep-alive.md +139 -0
  19. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-slots.md +226 -0
  20. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-suspense.md +231 -0
  21. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-teleport.md +110 -0
  22. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-transition-group.md +131 -0
  23. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-transition.md +135 -0
  24. package/templates/harness/full/.agents/skills/vue-best-practices/references/composables.md +303 -0
  25. package/templates/harness/full/.agents/skills/vue-best-practices/references/directives.md +168 -0
  26. package/templates/harness/full/.agents/skills/vue-best-practices/references/perf-avoid-component-abstraction-in-lists.md +177 -0
  27. package/templates/harness/full/.agents/skills/vue-best-practices/references/perf-v-once-v-memo-directives.md +185 -0
  28. package/templates/harness/full/.agents/skills/vue-best-practices/references/perf-virtualize-large-lists.md +182 -0
  29. package/templates/harness/full/.agents/skills/vue-best-practices/references/plugins.md +178 -0
  30. package/templates/harness/full/.agents/skills/vue-best-practices/references/reactivity.md +371 -0
  31. package/templates/harness/full/.agents/skills/vue-best-practices/references/render-functions.md +227 -0
  32. package/templates/harness/full/.agents/skills/vue-best-practices/references/sfc.md +355 -0
  33. package/templates/harness/full/.agents/skills/vue-best-practices/references/state-management.md +138 -0
  34. package/templates/harness/full/.agents/skills/vue-best-practices/references/updated-hook-performance.md +193 -0
  35. package/templates/harness/full/.claude/agents/code-reviewer.md +109 -0
  36. package/templates/harness/full/.claude/agents/harness-reviewer.md +51 -0
  37. package/templates/harness/full/.claude/hooks/guard-tool.cjs +234 -0
  38. package/templates/harness/full/.claude/hooks/notify.cjs +168 -0
  39. package/templates/harness/full/.claude/hooks/quality-gate.cjs +135 -0
  40. package/templates/harness/full/.claude/rules/delivery.md +66 -0
  41. package/templates/harness/full/.claude/rules/formatting.md +7 -0
  42. package/templates/harness/full/.claude/rules/git.md +8 -0
  43. package/templates/harness/full/.claude/rules/skills-mcp.md +13 -0
  44. package/templates/harness/full/.claude/rules/vue.md +227 -0
  45. package/templates/harness/full/.claude/settings.json +123 -0
  46. package/templates/harness/full/.claude/skills/find-skills/SKILL.md +143 -0
  47. package/templates/harness/full/.claude/skills/vue-best-practices/LICENSE.md +21 -0
  48. package/templates/harness/full/.claude/skills/vue-best-practices/SKILL.md +155 -0
  49. package/templates/harness/full/.claude/skills/vue-best-practices/SYNC.md +5 -0
  50. package/templates/harness/full/.claude/skills/vue-best-practices/references/animation-class-based-technique.md +258 -0
  51. package/templates/harness/full/.claude/skills/vue-best-practices/references/animation-state-driven-technique.md +287 -0
  52. package/templates/harness/full/.claude/skills/vue-best-practices/references/component-async.md +99 -0
  53. package/templates/harness/full/.claude/skills/vue-best-practices/references/component-data-flow.md +313 -0
  54. package/templates/harness/full/.claude/skills/vue-best-practices/references/component-fallthrough-attrs.md +179 -0
  55. package/templates/harness/full/.claude/skills/vue-best-practices/references/component-keep-alive.md +139 -0
  56. package/templates/harness/full/.claude/skills/vue-best-practices/references/component-slots.md +226 -0
  57. package/templates/harness/full/.claude/skills/vue-best-practices/references/component-suspense.md +231 -0
  58. package/templates/harness/full/.claude/skills/vue-best-practices/references/component-teleport.md +110 -0
  59. package/templates/harness/full/.claude/skills/vue-best-practices/references/component-transition-group.md +131 -0
  60. package/templates/harness/full/.claude/skills/vue-best-practices/references/component-transition.md +135 -0
  61. package/templates/harness/full/.claude/skills/vue-best-practices/references/composables.md +303 -0
  62. package/templates/harness/full/.claude/skills/vue-best-practices/references/directives.md +168 -0
  63. package/templates/harness/full/.claude/skills/vue-best-practices/references/perf-avoid-component-abstraction-in-lists.md +177 -0
  64. package/templates/harness/full/.claude/skills/vue-best-practices/references/perf-v-once-v-memo-directives.md +185 -0
  65. package/templates/harness/full/.claude/skills/vue-best-practices/references/perf-virtualize-large-lists.md +182 -0
  66. package/templates/harness/full/.claude/skills/vue-best-practices/references/plugins.md +178 -0
  67. package/templates/harness/full/.claude/skills/vue-best-practices/references/reactivity.md +371 -0
  68. package/templates/harness/full/.claude/skills/vue-best-practices/references/render-functions.md +227 -0
  69. package/templates/harness/full/.claude/skills/vue-best-practices/references/sfc.md +355 -0
  70. package/templates/harness/full/.claude/skills/vue-best-practices/references/state-management.md +138 -0
  71. package/templates/harness/full/.claude/skills/vue-best-practices/references/updated-hook-performance.md +193 -0
  72. package/templates/harness/full/.editorconfig +8 -0
  73. package/templates/harness/full/.husky/commit-msg +1 -0
  74. package/templates/harness/full/.husky/pre-commit +1 -0
  75. package/templates/harness/full/.lintstagedrc.json +4 -0
  76. package/templates/harness/full/.nvmrc +1 -0
  77. package/templates/harness/full/.oxlintrc.json +11 -0
  78. package/templates/harness/full/.prettierrc.json +6 -0
  79. package/templates/harness/full/AGENTS.md +3 -0
  80. package/templates/harness/full/CLAUDE.md +28 -0
  81. package/templates/harness/full/GEMINI.md +3 -0
  82. package/templates/harness/full/commitlint.config.ts +3 -0
  83. package/templates/harness/full/docs/ai-harness.md +77 -0
  84. package/templates/harness/full/docs/delivery-template.md +66 -0
  85. package/templates/harness/full/docs/git.md +24 -0
  86. package/templates/harness/full/docs/harness-quick-reference.md +89 -0
  87. package/templates/harness/full/docs/review-checklist.md +49 -0
  88. package/templates/harness/full/scripts/harness-hooks.test.mjs +218 -0
  89. package/templates/harness/full/scripts/verify-skills.mjs +248 -0
  90. package/templates/harness/full/scripts/verify-skills.test.mjs +72 -0
  91. package/templates/harness/full/skills-lock.json +50 -0
  92. package/templates/harness/minimal/.claude/hooks/guard-tool.cjs +234 -0
  93. package/templates/harness/minimal/.claude/hooks/quality-gate.cjs +135 -0
  94. package/templates/harness/minimal/.claude/settings.json +27 -0
  95. package/templates/harness/minimal/CLAUDE.md +12 -0
@@ -0,0 +1,355 @@
1
+ ---
2
+ title: Single-File Component Structure, Styling, and Template Patterns
3
+ impact: MEDIUM
4
+ impactDescription: Consistent SFC structure and styling choices improve maintainability, tooling support, and render performance
5
+ type: best-practice
6
+ tags:
7
+ [
8
+ vue3,
9
+ sfc,
10
+ scoped-css,
11
+ styles,
12
+ build-tools,
13
+ performance,
14
+ template,
15
+ v-html,
16
+ v-for,
17
+ computed,
18
+ v-if,
19
+ v-show,
20
+ ]
21
+ ---
22
+
23
+ # Single-File Component Structure, Styling, and Template Patterns
24
+
25
+ **Impact: MEDIUM** - Using SFCs with consistent structure and performant styling keeps components easier to maintain and avoids unnecessary render overhead.
26
+
27
+ ## Task List
28
+
29
+ - Use `.vue` SFCs instead of separate `.js`/`.ts` and `.css` files for components
30
+ - Colocate template, script, and styles in the same SFC by default
31
+ - Use PascalCase for component names in templates and filenames
32
+ - Prefer component-scoped styles
33
+ - Prefer class selectors (not element selectors) in scoped CSS for performance
34
+ - Access DOM / component refs with `useTemplateRef()` in Vue 3.5+
35
+ - Use camelCase keys in `:style` bindings for consistency and IDE support
36
+ - Use `v-for` and `v-if` correctly
37
+ - Never use `v-html` with untrusted/user-provided content
38
+ - Choose `v-if` vs `v-show` based on toggle frequency and initial render cost
39
+
40
+ ## Colocate template, script, and styles
41
+
42
+ **BAD:**
43
+
44
+ ```
45
+ components/
46
+ ├── UserCard.vue
47
+ ├── UserCard.js
48
+ └── UserCard.css
49
+ ```
50
+
51
+ **GOOD:**
52
+
53
+ ```vue
54
+ <!-- components/UserCard.vue -->
55
+ <script setup>
56
+ import { computed } from 'vue'
57
+
58
+ const props = defineProps({
59
+ user: { type: Object, required: true },
60
+ })
61
+
62
+ const displayName = computed(() => `${props.user.firstName} ${props.user.lastName}`)
63
+ </script>
64
+
65
+ <template>
66
+ <div class="user-card">
67
+ <h3 class="name">
68
+ {{ displayName }}
69
+ </h3>
70
+ </div>
71
+ </template>
72
+
73
+ <style scoped>
74
+ .user-card {
75
+ padding: 1rem;
76
+ }
77
+
78
+ .name {
79
+ margin: 0;
80
+ }
81
+ </style>
82
+ ```
83
+
84
+ ## Use PascalCase for component names
85
+
86
+ **BAD:**
87
+
88
+ ```vue
89
+ <script setup>
90
+ import userProfile from './user-profile.vue'
91
+ </script>
92
+
93
+ <template>
94
+ <user-profile :user="currentUser" />
95
+ </template>
96
+ ```
97
+
98
+ **GOOD:**
99
+
100
+ ```vue
101
+ <script setup>
102
+ import UserProfile from './UserProfile.vue'
103
+ </script>
104
+
105
+ <template>
106
+ <UserProfile :user="currentUser" />
107
+ </template>
108
+ ```
109
+
110
+ ## Best practices for `<style>` block in SFCs
111
+
112
+ ### Prefer component-scoped styles
113
+
114
+ - Use `<style scoped>` for styles that belong to a component.
115
+ - Keep **global CSS** in a dedicated file (e.g. `src/assets/main.css`) for resets, typography, tokens, etc.
116
+ - Use `:deep()` sparingly (edge cases only).
117
+
118
+ **BAD:**
119
+
120
+ ```vue
121
+ <style>
122
+ /* ❌ leaks everywhere */
123
+ button {
124
+ border-radius: 999px;
125
+ }
126
+ </style>
127
+ ```
128
+
129
+ **GOOD:**
130
+
131
+ ```vue
132
+ <style scoped>
133
+ .button {
134
+ border-radius: 999px;
135
+ }
136
+ </style>
137
+ ```
138
+
139
+ **GOOD:**
140
+
141
+ ```css
142
+ /* src/assets/main.css */
143
+ /* ✅ resets, tokens, typography, app-wide rules */
144
+ :root {
145
+ --radius: 999px;
146
+ }
147
+ ```
148
+
149
+ ### Use class selectors in scoped CSS
150
+
151
+ **BAD:**
152
+
153
+ ```vue
154
+ <template>
155
+ <article>
156
+ <h1>{{ title }}</h1>
157
+ <p>{{ subtitle }}</p>
158
+ </article>
159
+ </template>
160
+
161
+ <style scoped>
162
+ article {
163
+ max-width: 800px;
164
+ }
165
+ h1 {
166
+ font-size: 2rem;
167
+ }
168
+ p {
169
+ line-height: 1.6;
170
+ }
171
+ </style>
172
+ ```
173
+
174
+ **GOOD:**
175
+
176
+ ```vue
177
+ <template>
178
+ <article class="article">
179
+ <h1 class="article-title">
180
+ {{ title }}
181
+ </h1>
182
+ <p class="article-subtitle">
183
+ {{ subtitle }}
184
+ </p>
185
+ </article>
186
+ </template>
187
+
188
+ <style scoped>
189
+ .article {
190
+ max-width: 800px;
191
+ }
192
+ .article-title {
193
+ font-size: 2rem;
194
+ }
195
+ .article-subtitle {
196
+ line-height: 1.6;
197
+ }
198
+ </style>
199
+ ```
200
+
201
+ ## Access DOM / component refs with `useTemplateRef()`
202
+
203
+ For Vue 3.5+: use `useTemplateRef()` to access template refs.
204
+
205
+ ```vue
206
+ <script setup lang="ts">
207
+ import { onMounted, useTemplateRef } from 'vue'
208
+
209
+ const inputRef = useTemplateRef<HTMLInputElement>('input')
210
+
211
+ onMounted(() => {
212
+ inputRef.value?.focus()
213
+ })
214
+ </script>
215
+
216
+ <template>
217
+ <input ref="input" />
218
+ </template>
219
+ ```
220
+
221
+ ## Use camelCase in `:style` bindings
222
+
223
+ **BAD:**
224
+
225
+ ```vue
226
+ <template>
227
+ <div :style="{ 'font-size': `${fontSize}px`, 'background-color': bg }">Content</div>
228
+ </template>
229
+ ```
230
+
231
+ **GOOD:**
232
+
233
+ ```vue
234
+ <template>
235
+ <div :style="{ fontSize: `${fontSize}px`, backgroundColor: bg }">Content</div>
236
+ </template>
237
+ ```
238
+
239
+ ## Use `v-for` and `v-if` correctly
240
+
241
+ ### Always provide a stable `:key`
242
+
243
+ - Prefer primitive keys (`string | number`).
244
+ - Avoid using objects as keys.
245
+
246
+ **GOOD:**
247
+
248
+ ```vue
249
+ <li v-for="item in items" :key="item.id">
250
+ <input v-model="item.text" />
251
+ </li>
252
+ ```
253
+
254
+ ### Avoid `v-if` and `v-for` on the same element
255
+
256
+ It leads to unclear intent and unnecessary work.
257
+ ([Reference](https://vuejs.org/guide/essentials/list.html#v-for-with-v-if))
258
+
259
+ **To filter items**
260
+ **BAD:**
261
+
262
+ ```vue
263
+ <li v-for="user in users" v-if="user.active" :key="user.id">
264
+ {{ user.name }}
265
+ </li>
266
+ ```
267
+
268
+ **GOOD:**
269
+
270
+ ```vue
271
+ <script setup lang="ts">
272
+ import { computed } from 'vue'
273
+
274
+ const activeUsers = computed(() => users.value.filter((u) => u.active))
275
+ </script>
276
+
277
+ <template>
278
+ <li v-for="user in activeUsers" :key="user.id">
279
+ {{ user.name }}
280
+ </li>
281
+ </template>
282
+ ```
283
+
284
+ **To conditionally show/hide the entire list**
285
+ **GOOD:**
286
+
287
+ ```vue
288
+ <ul v-if="shouldShowUsers">
289
+ <li v-for="user in users" :key="user.id">
290
+ {{ user.name }}
291
+ </li>
292
+ </ul>
293
+ ```
294
+
295
+ ## Never render untrusted HTML with `v-html`
296
+
297
+ **BAD:**
298
+
299
+ ```vue
300
+ <template>
301
+ <!-- DANGEROUS: untrusted input can inject scripts -->
302
+ <article v-html="userProvidedContent" />
303
+ </template>
304
+ ```
305
+
306
+ **GOOD:**
307
+
308
+ ```vue
309
+ <script setup>
310
+ import DOMPurify from 'dompurify'
311
+
312
+ import { computed } from 'vue'
313
+
314
+ const props = defineProps<{
315
+ trustedHtml?: string
316
+ plainText: string
317
+ }>()
318
+
319
+ const safeHtml = computed(() => DOMPurify.sanitize(props.trustedHtml ?? ''))
320
+ </script>
321
+
322
+ <template>
323
+ <!-- Preferred: escaped interpolation -->
324
+ <p>{{ props.plainText }}</p>
325
+
326
+ <!-- Only for trusted/sanitized HTML -->
327
+ <article v-html="safeHtml" />
328
+ </template>
329
+ ```
330
+
331
+ ## Choose `v-if` vs `v-show` by toggle behavior
332
+
333
+ **BAD:**
334
+
335
+ ```vue
336
+ <template>
337
+ <!-- Frequent toggles with v-if cause repeated mount/unmount -->
338
+ <ComplexPanel v-if="isPanelOpen" />
339
+
340
+ <!-- Rarely shown content with v-show pays initial render cost -->
341
+ <AdminPanel v-show="isAdmin" />
342
+ </template>
343
+ ```
344
+
345
+ **GOOD:**
346
+
347
+ ```vue
348
+ <template>
349
+ <!-- Frequent toggles: keep in DOM, toggle display -->
350
+ <ComplexPanel v-show="isPanelOpen" />
351
+
352
+ <!-- Rare condition: lazy render only when true -->
353
+ <AdminPanel v-if="isAdmin" />
354
+ </template>
355
+ ```
@@ -0,0 +1,138 @@
1
+ ---
2
+ title: State Management Strategy
3
+ impact: HIGH
4
+ impactDescription: Choosing the wrong store pattern can cause SSR request leaks, brittle mutation flows, and poor scaling
5
+ type: best-practice
6
+ tags: [vue3, state-management, pinia, composables, ssr, vueuse]
7
+ ---
8
+
9
+ # State Management Strategy
10
+
11
+ **Impact: HIGH** - Use the lightest state solution that fits your app architecture. SPA-only apps can use lightweight global composables, while SSR/Nuxt apps should default to Pinia for request-safe isolation and predictable tooling.
12
+
13
+ ## Task List
14
+
15
+ - Keep state local first, then promote to shared/global only when needed
16
+ - Use singleton composables only in non-SSR applications
17
+ - Expose global state as readonly and mutate through explicit actions
18
+ - Prefer Pinia for SSR/Nuxt, large apps, and advanced debugging/plugin needs
19
+ - Avoid exporting mutable module-level reactive state directly
20
+
21
+ ## Choose the Lightest Store Approach
22
+
23
+ - **Feature composable:** Default for reusable logic with local/feature-level state.
24
+ - **Singleton composable or VueUse `createGlobalState`:** Small non-SSR apps needing shared app state.
25
+ - **Pinia:** SSR/Nuxt apps, medium-to-large apps, and cases requiring DevTools, plugins, or action tracing.
26
+
27
+ ## Avoid Exporting Mutable Module State
28
+
29
+ **BAD:**
30
+
31
+ ```ts
32
+ // store/cart.ts
33
+ import { reactive } from 'vue'
34
+
35
+ export const cart = reactive({
36
+ items: [] as Array<{ id: string; qty: number }>,
37
+ })
38
+ ```
39
+
40
+ **GOOD:**
41
+
42
+ ```ts
43
+ // composables/useCartStore.ts
44
+ import { reactive, readonly } from 'vue'
45
+
46
+ let _store: ReturnType<typeof createCartStore> | null = null
47
+
48
+ function createCartStore() {
49
+ const state = reactive({
50
+ items: [] as Array<{ id: string; qty: number }>,
51
+ })
52
+
53
+ function addItem(id: string, qty = 1) {
54
+ const existing = state.items.find((item) => item.id === id)
55
+ if (existing) {
56
+ existing.qty += qty
57
+ return
58
+ }
59
+ state.items.push({ id, qty })
60
+ }
61
+
62
+ return {
63
+ state: readonly(state),
64
+ addItem,
65
+ }
66
+ }
67
+
68
+ export function useCartStore() {
69
+ if (!_store) _store = createCartStore()
70
+ return _store
71
+ }
72
+ ```
73
+
74
+ ## Do Not Use Runtime Singletons in SSR
75
+
76
+ Module singletons live for the runtime lifetime. In SSR this can leak state between requests.
77
+
78
+ **BAD:**
79
+
80
+ ```ts
81
+ // shared singleton reused across requests
82
+ const cartStore = useCartStore()
83
+
84
+ export function useServerCart() {
85
+ return cartStore
86
+ }
87
+ ```
88
+
89
+ **GOOD:**
90
+
91
+ > `pinia` dependency required.
92
+
93
+ ```ts
94
+ // stores/cart.ts
95
+ import { defineStore } from 'pinia'
96
+
97
+ export const useCartStore = defineStore('cart', {
98
+ state: () => ({
99
+ items: [] as Array<{ id: string; qty: number }>,
100
+ }),
101
+ actions: {
102
+ addItem(id: string, qty = 1) {
103
+ const existing = this.items.find((item) => item.id === id)
104
+ if (existing) {
105
+ existing.qty += qty
106
+ return
107
+ }
108
+ this.items.push({ id, qty })
109
+ },
110
+ },
111
+ })
112
+ ```
113
+
114
+ ## Use `createGlobalState` for Small SPA Global State
115
+
116
+ > `@vueuse/core` dependency required.
117
+
118
+ If the app is non-SSR and already uses VueUse, `createGlobalState` removes singleton boilerplate.
119
+
120
+ ```ts
121
+ import { createGlobalState } from '@vueuse/core'
122
+ import { computed, ref } from 'vue'
123
+
124
+ export const useAuthState = createGlobalState(() => {
125
+ const token = ref<string | null>(null)
126
+ const isAuthenticated = computed(() => token.value !== null)
127
+
128
+ function setToken(next: string | null) {
129
+ token.value = next
130
+ }
131
+
132
+ return {
133
+ token,
134
+ isAuthenticated,
135
+ setToken,
136
+ }
137
+ })
138
+ ```
@@ -0,0 +1,193 @@
1
+ ---
2
+ title: Avoid Expensive Operations in Updated Hook
3
+ impact: MEDIUM
4
+ impactDescription: Heavy computations in updated hook cause performance bottlenecks and potential infinite loops
5
+ type: capability
6
+ tags: [vue3, vue2, lifecycle, updated, performance, optimization, reactivity]
7
+ ---
8
+
9
+ # Avoid Expensive Operations in Updated Hook
10
+
11
+ **Impact: MEDIUM** - The `updated` hook runs after every reactive state change that causes a re-render. Placing expensive operations, API calls, or state mutations here can cause severe performance degradation, infinite loops, and dropped frames below the optimal 60fps threshold.
12
+
13
+ Use `updated`/`onUpdated` sparingly for post-DOM-update operations that cannot be handled by watchers or computed properties. For most reactive data handling, prefer watchers (`watch`/`watchEffect`) which provide more control over what triggers the callback.
14
+
15
+ ## Task List
16
+
17
+ - Never perform API calls in updated hook
18
+ - Never mutate reactive state inside updated (causes infinite loops)
19
+ - Use conditional checks to verify updates are relevant before acting
20
+ - Prefer `watch` or `watchEffect` for reacting to specific data changes
21
+ - Use throttling/debouncing if updated operations are expensive
22
+ - Reserve updated for low-level DOM synchronization tasks
23
+
24
+ **BAD:**
25
+
26
+ ```javascript
27
+ // BAD: API call in updated - fires on every re-render
28
+ export default {
29
+ data() {
30
+ return { items: [], lastUpdate: null }
31
+ },
32
+ updated() {
33
+ // This runs after every single state change!
34
+ fetch('/api/sync', {
35
+ method: 'POST',
36
+ body: JSON.stringify(this.items),
37
+ })
38
+ },
39
+ }
40
+ ```
41
+
42
+ ```javascript
43
+ // BAD: State mutation in updated - infinite loop
44
+ export default {
45
+ data() {
46
+ return { renderCount: 0 }
47
+ },
48
+ updated() {
49
+ // This causes another update, which triggers updated again!
50
+ this.renderCount++ // Infinite loop
51
+ },
52
+ }
53
+ ```
54
+
55
+ ```javascript
56
+ // BAD: Heavy computation on every update
57
+ export default {
58
+ updated() {
59
+ // Expensive operation runs on every keystroke, every state change
60
+ this.processedData = this.heavyComputation(this.rawData)
61
+ this.analytics = this.calculateMetrics(this.allData)
62
+ },
63
+ }
64
+ ```
65
+
66
+ **GOOD:**
67
+
68
+ ```javascript
69
+ import debounce from 'lodash-es/debounce'
70
+
71
+ // GOOD: Use watcher for specific data changes
72
+ export default {
73
+ data() {
74
+ return { items: [] }
75
+ },
76
+ watch: {
77
+ // Only fires when items actually changes
78
+ items: {
79
+ handler(newItems) {
80
+ this.syncToServer(newItems)
81
+ },
82
+ deep: true,
83
+ },
84
+ },
85
+ methods: {
86
+ syncToServer: debounce((items) => {
87
+ fetch('/api/sync', {
88
+ method: 'POST',
89
+ body: JSON.stringify(items),
90
+ })
91
+ }, 500),
92
+ },
93
+ }
94
+ ```
95
+
96
+ ```vue
97
+ <!-- GOOD: Composition API with targeted watchers -->
98
+ <script setup>
99
+ import { useDebounceFn } from '@vueuse/core'
100
+ import { onUpdated, ref, watch } from 'vue'
101
+
102
+ const items = ref([])
103
+ const scrollContainer = ref(null)
104
+
105
+ // Watch specific data - not all updates
106
+ watch(
107
+ items,
108
+ (newItems) => {
109
+ syncToServer(newItems)
110
+ },
111
+ { deep: true },
112
+ )
113
+
114
+ const syncToServer = useDebounceFn((items) => {
115
+ fetch('/api/sync', { method: 'POST', body: JSON.stringify(items) })
116
+ }, 500)
117
+
118
+ // Only use onUpdated for DOM synchronization
119
+ onUpdated(() => {
120
+ // Scroll to bottom only if content changed height
121
+ if (scrollContainer.value) {
122
+ scrollContainer.value.scrollTop = scrollContainer.value.scrollHeight
123
+ }
124
+ })
125
+ </script>
126
+ ```
127
+
128
+ ```javascript
129
+ // GOOD: Conditional check in updated hook
130
+ export default {
131
+ data() {
132
+ return {
133
+ content: '',
134
+ lastSyncedContent: '',
135
+ }
136
+ },
137
+ updated() {
138
+ // Only act if specific condition is met
139
+ if (this.content !== this.lastSyncedContent) {
140
+ this.syncContent()
141
+ this.lastSyncedContent = this.content
142
+ }
143
+ },
144
+ methods: {
145
+ syncContent: debounce(() => {
146
+ // Sync logic
147
+ }, 300),
148
+ },
149
+ }
150
+ ```
151
+
152
+ ## Valid Use Cases for Updated Hook
153
+
154
+ ```javascript
155
+ // GOOD: Low-level DOM synchronization
156
+ export default {
157
+ updated() {
158
+ // Sync third-party library with Vue's DOM
159
+ this.thirdPartyWidget.refresh()
160
+
161
+ // Update scroll position after content change
162
+ this.$nextTick(() => {
163
+ this.maintainScrollPosition()
164
+ })
165
+ },
166
+ }
167
+ ```
168
+
169
+ ## Prefer Computed Properties for Derived Data
170
+
171
+ ```javascript
172
+ // BAD: Calculating derived data in updated
173
+ export default {
174
+ data() {
175
+ return { numbers: [1, 2, 3, 4, 5] }
176
+ },
177
+ updated() {
178
+ this.sum = this.numbers.reduce((a, b) => a + b, 0) // Causes another update!
179
+ }
180
+ }
181
+
182
+ // GOOD: Use computed property instead
183
+ export default {
184
+ data() {
185
+ return { numbers: [1, 2, 3, 4, 5] }
186
+ },
187
+ computed: {
188
+ sum() {
189
+ return this.numbers.reduce((a, b) => a + b, 0)
190
+ }
191
+ }
192
+ }
193
+ ```