@dianzhong/create-harness-app 0.1.2 → 0.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 (38) hide show
  1. package/package.json +1 -1
  2. package/templates/harness/full/.claude/agents/code-reviewer.md +1 -1
  3. package/templates/harness/full/.claude/agents/harness-reviewer.md +0 -2
  4. package/templates/harness/full/.claude/rules/skills-mcp.md +2 -3
  5. package/templates/harness/full/.claude/settings.json +1 -1
  6. package/templates/harness/full/docs/ai-harness.md +4 -6
  7. package/templates/harness/full/docs/harness-quick-reference.md +1 -1
  8. package/templates/harness/full/docs/review-checklist.md +1 -1
  9. package/templates/harness/full/scripts/verify-skills.mjs +6 -61
  10. package/templates/harness/full/scripts/verify-skills.test.mjs +1 -11
  11. package/templates/harness/full/.agents/skills/find-skills/SKILL.md +0 -143
  12. package/templates/harness/full/.agents/skills/vue-best-practices/LICENSE.md +0 -21
  13. package/templates/harness/full/.agents/skills/vue-best-practices/SKILL.md +0 -155
  14. package/templates/harness/full/.agents/skills/vue-best-practices/SYNC.md +0 -5
  15. package/templates/harness/full/.agents/skills/vue-best-practices/references/animation-class-based-technique.md +0 -258
  16. package/templates/harness/full/.agents/skills/vue-best-practices/references/animation-state-driven-technique.md +0 -287
  17. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-async.md +0 -99
  18. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-data-flow.md +0 -313
  19. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-fallthrough-attrs.md +0 -179
  20. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-keep-alive.md +0 -139
  21. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-slots.md +0 -226
  22. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-suspense.md +0 -231
  23. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-teleport.md +0 -110
  24. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-transition-group.md +0 -131
  25. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-transition.md +0 -135
  26. package/templates/harness/full/.agents/skills/vue-best-practices/references/composables.md +0 -303
  27. package/templates/harness/full/.agents/skills/vue-best-practices/references/directives.md +0 -168
  28. package/templates/harness/full/.agents/skills/vue-best-practices/references/perf-avoid-component-abstraction-in-lists.md +0 -177
  29. package/templates/harness/full/.agents/skills/vue-best-practices/references/perf-v-once-v-memo-directives.md +0 -185
  30. package/templates/harness/full/.agents/skills/vue-best-practices/references/perf-virtualize-large-lists.md +0 -182
  31. package/templates/harness/full/.agents/skills/vue-best-practices/references/plugins.md +0 -178
  32. package/templates/harness/full/.agents/skills/vue-best-practices/references/reactivity.md +0 -371
  33. package/templates/harness/full/.agents/skills/vue-best-practices/references/render-functions.md +0 -227
  34. package/templates/harness/full/.agents/skills/vue-best-practices/references/sfc.md +0 -355
  35. package/templates/harness/full/.agents/skills/vue-best-practices/references/state-management.md +0 -138
  36. package/templates/harness/full/.agents/skills/vue-best-practices/references/updated-hook-performance.md +0 -193
  37. package/templates/harness/full/AGENTS.md +0 -3
  38. package/templates/harness/full/GEMINI.md +0 -3
@@ -1,313 +0,0 @@
1
- ---
2
- title: Component Data Flow Best Practices
3
- impact: HIGH
4
- impactDescription: Clear data flow between components prevents state bugs, stale UI, and brittle coupling
5
- type: best-practice
6
- tags: [vue3, props, emits, v-model, provide-inject, data-flow, typescript]
7
- ---
8
-
9
- # Component Data Flow Best Practices
10
-
11
- **Impact: HIGH** - Vue components stay reliable when data flow is explicit: props go down, events go up, `v-model` handles two-way bindings, and provide/inject supports cross-tree dependencies. Blurring these boundaries leads to stale state, hidden coupling, and hard-to-debug UI.
12
-
13
- The main principle of data flow in Vue.js is **Props Down / Events Up**. This is the most maintainable default, and one-way flow scales well.
14
-
15
- ## Task List
16
-
17
- - Treat props as read-only inputs
18
- - Use props/emit for component communication; reserve refs for imperative actions
19
- - When refs are required for imperative APIs, type them with template refs
20
- - Emit events instead of mutating parent state directly
21
- - Use `defineModel` for v-model in modern Vue (3.4+)
22
- - Handle v-model modifiers deliberately in child components
23
- - Use symbols for provide/inject keys to avoid props drilling (over ~3 layers)
24
- - Keep mutations in the provider or expose explicit actions
25
- - In TypeScript projects, prefer type-based `defineProps`, `defineEmits`, and `InjectionKey`
26
-
27
- ## Props: One-Way Data Down
28
-
29
- Props are inputs. Do not mutate them in the child.
30
-
31
- **BAD:**
32
-
33
- ```vue
34
- <script setup>
35
- const props = defineProps({ count: Number })
36
-
37
- function increment() {
38
- props.count++
39
- }
40
- </script>
41
- ```
42
-
43
- **GOOD:**
44
-
45
- If state needs to change, emit an event, use `v-model` or create a local copy.
46
-
47
- ## Prefer props/emit over component refs
48
-
49
- **BAD:**
50
-
51
- ```vue
52
- <script setup>
53
- import { ref } from 'vue'
54
-
55
- import UserForm from './UserForm.vue'
56
-
57
- const formRef = ref(null)
58
-
59
- function submitForm() {
60
- if (formRef.value.isValid) {
61
- formRef.value.submit()
62
- }
63
- }
64
- </script>
65
-
66
- <template>
67
- <UserForm ref="formRef" />
68
- <button @click="submitForm">Submit</button>
69
- </template>
70
- ```
71
-
72
- **GOOD:**
73
-
74
- ```vue
75
- <script setup>
76
- import UserForm from './UserForm.vue'
77
-
78
- function handleSubmit(formData) {
79
- api.submit(formData)
80
- }
81
- </script>
82
-
83
- <template>
84
- <UserForm @submit="handleSubmit" />
85
- </template>
86
- ```
87
-
88
- ## Type component refs when imperative access is required
89
-
90
- Prefer props/emits by default. When a parent must call an exposed child method, type the ref explicitly and expose only the intended API from the child with `defineExpose`.
91
-
92
- **BAD:**
93
-
94
- ```vue
95
- <script setup lang="ts">
96
- import { onMounted, ref } from 'vue'
97
-
98
- import DialogPanel from './DialogPanel.vue'
99
-
100
- const panelRef = ref(null)
101
-
102
- onMounted(() => {
103
- panelRef.value.open()
104
- })
105
- </script>
106
-
107
- <template>
108
- <DialogPanel ref="panelRef" />
109
- </template>
110
- ```
111
-
112
- **GOOD:**
113
-
114
- ```vue
115
- <!-- DialogPanel.vue -->
116
- <script setup lang="ts">
117
- function open() {}
118
-
119
- defineExpose({ open })
120
- </script>
121
- ```
122
-
123
- ```vue
124
- <!-- Parent.vue -->
125
- <script setup lang="ts">
126
- import { onMounted, useTemplateRef } from 'vue'
127
-
128
- import DialogPanel from './DialogPanel.vue'
129
-
130
- // Vue 3.5+ with useTemplateRef
131
- const panelRef = useTemplateRef('panelRef')
132
-
133
- // Before Vue 3.5 with manual typing and ref
134
- // const panelRef = ref<InstanceType<typeof DialogPanel> | null>(null)
135
-
136
- onMounted(() => {
137
- panelRef.value?.open()
138
- })
139
- </script>
140
-
141
- <template>
142
- <DialogPanel ref="panelRef" />
143
- </template>
144
- ```
145
-
146
- ## Emits: Explicit Events Up
147
-
148
- Component events do not bubble. If a parent needs to know about an event, re-emit it explicitly.
149
-
150
- **BAD:**
151
-
152
- ```vue
153
- <!-- Parent expects "saved" from grandchild, but it won't bubble -->
154
- <Child @saved="onSaved" />
155
- ```
156
-
157
- **GOOD:**
158
-
159
- ```vue
160
- <!-- Child.vue -->
161
- <script setup>
162
- const emit = defineEmits(['saved'])
163
-
164
- function onGrandchildSaved(payload) {
165
- emit('saved', payload)
166
- }
167
- </script>
168
-
169
- <template>
170
- <Grandchild @saved="onGrandchildSaved" />
171
- </template>
172
- ```
173
-
174
- **Event naming:** use kebab-case in templates and camelCase in script:
175
-
176
- ```vue
177
- <script setup>
178
- const emit = defineEmits(['updateUser'])
179
- </script>
180
-
181
- <template>
182
- <ProfileForm @update-user="emit('updateUser', $event)" />
183
- </template>
184
- ```
185
-
186
- ## `v-model`: Predictable Two-Way Bindings
187
-
188
- Use `defineModel` by default for component bindings and emit updates on input. Only use the `modelValue` + `update:modelValue` pattern if you are on Vue < 3.4.
189
-
190
- **BAD:**
191
-
192
- ```vue
193
- <script setup>
194
- const props = defineProps({ value: String })
195
- </script>
196
-
197
- <template>
198
- <input :value="props.value" @input="$emit('input', $event.target.value)" />
199
- </template>
200
- ```
201
-
202
- **GOOD (Vue 3.4+):**
203
-
204
- ```vue
205
- <script setup>
206
- const model = defineModel({ type: String })
207
- </script>
208
-
209
- <template>
210
- <input v-model="model" />
211
- </template>
212
- ```
213
-
214
- **GOOD (Vue < 3.4):**
215
-
216
- ```vue
217
- <script setup>
218
- const props = defineProps({ modelValue: String })
219
- const emit = defineEmits(['update:modelValue'])
220
- </script>
221
-
222
- <template>
223
- <input :value="props.modelValue" @input="emit('update:modelValue', $event.target.value)" />
224
- </template>
225
- ```
226
-
227
- If you need the updated value immediately after a change, use the input event value or `nextTick` in the parent.
228
-
229
- ## Provide/Inject: Shared Context Without Prop Drilling
230
-
231
- Use provide/inject for cross-tree state, but keep mutations centralized in the provider and expose explicit actions.
232
-
233
- **BAD:**
234
-
235
- ```vue
236
- // Provider.vue provide('theme', reactive({ dark: false })) // Consumer.vue const theme =
237
- inject('theme') // Mutating shared state from any depth becomes hard to track theme.dark = true
238
- ```
239
-
240
- **GOOD:**
241
-
242
- ```vue
243
- // Provider.vue const theme = reactive({ dark: false }) const toggleTheme = () => { theme.dark =
244
- !theme.dark } provide(themeKey, readonly(theme)) provide(themeActionsKey, { toggleTheme }) //
245
- Consumer.vue const theme = inject(themeKey) const { toggleTheme } = inject(themeActionsKey)
246
- ```
247
-
248
- Use symbols for keys to avoid collisions in large apps:
249
-
250
- ```ts
251
- export const themeKey = Symbol('theme')
252
- export const themeActionsKey = Symbol('theme-actions')
253
- ```
254
-
255
- ## Use TypeScript Contracts for Public Component APIs
256
-
257
- In TypeScript projects, type component boundaries directly with `defineProps`, `defineEmits`, and `InjectionKey` so invalid payloads and mismatched injections fail at compile time.
258
-
259
- **BAD:**
260
-
261
- ```vue
262
- <script setup lang="ts">
263
- import { inject } from 'vue'
264
-
265
- const props = defineProps({
266
- userId: String,
267
- })
268
-
269
- const emit = defineEmits(['save'])
270
- const settings = inject('settings')
271
-
272
- // Payload shape is not checked here
273
- emit('save', 123)
274
-
275
- // Key is string-based and not type-safe
276
- settings?.theme = 'dark'
277
- </script>
278
- ```
279
-
280
- **GOOD:**
281
-
282
- ```vue
283
- <script setup lang="ts">
284
- import type { InjectionKey } from 'vue'
285
-
286
- import { inject, provide } from 'vue'
287
-
288
- interface Props {
289
- userId: string
290
- }
291
-
292
- interface Emits {
293
- save: [payload: { id: string; draft: boolean }]
294
- }
295
-
296
- interface Settings {
297
- theme: 'light' | 'dark'
298
- }
299
-
300
- const props = defineProps<Props>()
301
-
302
- const emit = defineEmits<Emits>()
303
-
304
- const settingsKey: InjectionKey<Settings> = Symbol('settings')
305
-
306
- provide(settingsKey, { theme: 'light' })
307
-
308
- const settings = inject(settingsKey)
309
- if (settings) {
310
- emit('save', { id: props.userId, draft: false })
311
- }
312
- </script>
313
- ```
@@ -1,179 +0,0 @@
1
- ---
2
- title: Component Fallthrough Attributes Best Practices
3
- impact: MEDIUM
4
- impactDescription: Incorrect $attrs access and reactivity assumptions can cause undefined values and watchers that never run
5
- type: best-practice
6
- tags: [vue3, attrs, fallthrough-attributes, composition-api, reactivity]
7
- ---
8
-
9
- # Component Fallthrough Attributes Best Practices
10
-
11
- **Impact: MEDIUM** - Fallthrough attributes are straightforward once you follow Vue's conventions: hyphenated names use bracket notation, listener keys are camelCase `onX`, and `useAttrs()` is current-but-not-reactive.
12
-
13
- ## Task List
14
-
15
- - Access hyphenated attribute names with bracket notation (for example `attrs['data-testid']`)
16
- - Access event listeners with camelCase `onX` keys (for example `attrs.onClick`)
17
- - Do not `watch()` values returned from `useAttrs()`; those watchers do not trigger on attr changes
18
- - Use `onUpdated()` for attr-driven side effects
19
- - Promote frequently observed attrs to props when reactive observation is required
20
-
21
- ## Access Attribute and Listener Keys Correctly
22
-
23
- Hyphenated attribute names preserve their original casing in JavaScript, so dot notation does not work for keys that include `-`.
24
-
25
- **BAD:**
26
-
27
- ```vue
28
- <script setup>
29
- import { useAttrs } from 'vue'
30
-
31
- const attrs = useAttrs()
32
-
33
- console.log(attrs.data - testid) // Syntax error
34
- console.log(attrs.dataTestid) // undefined for data-testid
35
- console.log(attrs['on-click']) // undefined
36
- console.log(attrs['@click']) // undefined
37
- </script>
38
- ```
39
-
40
- **GOOD:**
41
-
42
- ```vue
43
- <script setup>
44
- import { useAttrs } from 'vue'
45
-
46
- const attrs = useAttrs()
47
-
48
- console.log(attrs['data-testid'])
49
- console.log(attrs['aria-label'])
50
- console.log(attrs['foo-bar'])
51
-
52
- console.log(attrs.onClick)
53
- console.log(attrs.onCustomEvent)
54
- console.log(attrs.onMouseEnter)
55
- </script>
56
- ```
57
-
58
- ### Naming Reference
59
-
60
- | Parent Usage | Access in `attrs` |
61
- | ------------------------- | ------------------------------ |
62
- | `class="foo"` | `attrs.class` |
63
- | `data-id="123"` | `attrs['data-id']` |
64
- | `aria-label="..."` | `attrs['aria-label']` |
65
- | `foo-bar="baz"` | `attrs['foo-bar']` |
66
- | `@click="fn"` | `attrs.onClick` |
67
- | `@custom-event="fn"` | `attrs.onCustomEvent` |
68
- | `@update:modelValue="fn"` | `attrs['onUpdate:modelValue']` |
69
-
70
- ## `useAttrs()` Is Not Reactive
71
-
72
- `useAttrs()` always reflects the latest values, but it is intentionally not reactive for watcher tracking.
73
-
74
- **BAD:**
75
-
76
- ```vue
77
- <script setup>
78
- import { useAttrs, watch, watchEffect } from 'vue'
79
-
80
- const attrs = useAttrs()
81
-
82
- watch(
83
- () => attrs.someAttr,
84
- (newValue) => {
85
- console.log('Changed:', newValue) // Never runs on attr changes
86
- },
87
- )
88
-
89
- watchEffect(() => {
90
- console.log(attrs.class) // Runs on setup, not on attr updates
91
- })
92
- </script>
93
- ```
94
-
95
- **GOOD:**
96
-
97
- ```vue
98
- <script setup>
99
- import { onUpdated, useAttrs } from 'vue'
100
-
101
- const attrs = useAttrs()
102
-
103
- onUpdated(() => {
104
- console.log('Latest attrs:', attrs)
105
- })
106
- </script>
107
- ```
108
-
109
- **GOOD:**
110
-
111
- ```vue
112
- <script setup>
113
- import { watch } from 'vue'
114
-
115
- const props = defineProps({
116
- someAttr: String,
117
- })
118
-
119
- watch(
120
- () => props.someAttr,
121
- (newValue) => {
122
- console.log('Changed:', newValue)
123
- },
124
- )
125
- </script>
126
- ```
127
-
128
- ## Common Patterns
129
-
130
- ### Check for optional attrs safely
131
-
132
- ```vue
133
- <script setup>
134
- import { computed, useAttrs } from 'vue'
135
-
136
- const attrs = useAttrs()
137
-
138
- const hasTestId = computed(() => 'data-testid' in attrs)
139
- const ariaLabel = computed(() => attrs['aria-label'] ?? 'Default label')
140
- </script>
141
- ```
142
-
143
- ### Forward listeners after internal logic
144
-
145
- ```vue
146
- <script setup>
147
- import { useAttrs } from 'vue'
148
-
149
- defineOptions({ inheritAttrs: false })
150
-
151
- const attrs = useAttrs()
152
-
153
- function handleClick(event) {
154
- console.log('Internal handling first')
155
- attrs.onClick?.(event)
156
- }
157
- </script>
158
-
159
- <template>
160
- <button @click="handleClick">
161
- <slot />
162
- </button>
163
- </template>
164
- ```
165
-
166
- ## TypeScript Notes
167
-
168
- `useAttrs()` is typed as `Record<string, unknown>`, so cast individual keys when needed.
169
-
170
- ```vue
171
- <script setup lang="ts">
172
- import { useAttrs } from 'vue'
173
-
174
- const attrs = useAttrs()
175
-
176
- const testId = attrs['data-testid'] as string | undefined
177
- const onClick = attrs.onClick as ((event: MouseEvent) => void) | undefined
178
- </script>
179
- ```
@@ -1,139 +0,0 @@
1
- ---
2
- title: KeepAlive Component Best Practices
3
- impact: HIGH
4
- impactDescription: KeepAlive caches component instances; misuse causes stale data, memory growth, or unexpected lifecycle behavior
5
- type: best-practice
6
- tags: [vue3, keepalive, cache, performance, router, dynamic-components]
7
- ---
8
-
9
- # KeepAlive Component Best Practices
10
-
11
- **Impact: HIGH** - `<KeepAlive>` caches component instances instead of destroying them. Use it to preserve state across switches, but manage cache size and freshness explicitly to avoid memory growth or stale UI.
12
-
13
- ## Task List
14
-
15
- - Use KeepAlive only where state preservation improves UX
16
- - Set a reasonable `max` to cap cache size
17
- - Declare component names for include/exclude matching
18
- - Use `onActivated`/`onDeactivated` for cache-aware logic
19
- - Decide how and when cached views refresh their data
20
- - Avoid caching memory-heavy or security-sensitive views
21
-
22
- ## When to Use KeepAlive
23
-
24
- Use KeepAlive when switching between views where state should persist (tabs, multi-step forms, dashboards). Avoid it when each visit should start fresh.
25
-
26
- **BAD:**
27
-
28
- ```vue
29
- <template>
30
- <!-- State resets on every switch -->
31
- <component :is="currentTab" />
32
- </template>
33
- ```
34
-
35
- **GOOD:**
36
-
37
- ```vue
38
- <template>
39
- <!-- State preserved between switches -->
40
- <KeepAlive>
41
- <component :is="currentTab" />
42
- </KeepAlive>
43
- </template>
44
- ```
45
-
46
- ## When NOT to Use KeepAlive
47
-
48
- - Search or filter pages where users expect fresh results
49
- - Memory-heavy components (maps, large tables, media players)
50
- - Sensitive flows where data must be cleared on exit
51
- - Components with heavy background activity you cannot pause
52
-
53
- ## Limit and Control the Cache
54
-
55
- Always cap cache size with `max` and restrict caching to specific components when possible.
56
-
57
- ```vue
58
- <template>
59
- <KeepAlive :max="5" include="Dashboard,Settings">
60
- <component :is="currentView" />
61
- </KeepAlive>
62
- </template>
63
- ```
64
-
65
- ## Ensure Component Names Match include/exclude
66
-
67
- `include` and `exclude` match the component `name` option. Explicitly set names for reliable caching.
68
-
69
- ```vue
70
- <!-- TabA.vue -->
71
- <script setup>
72
- defineOptions({ name: 'TabA' })
73
- </script>
74
- ```
75
-
76
- ```vue
77
- <template>
78
- <KeepAlive include="TabA,TabB">
79
- <component :is="currentTab" />
80
- </KeepAlive>
81
- </template>
82
- ```
83
-
84
- ## Cache Invalidation Strategies
85
-
86
- Vue 3 has no direct API to remove a specific cached instance. Use keys or dynamic include/exclude to force refreshes.
87
-
88
- ```vue
89
- <script setup>
90
- import { reactive, ref } from 'vue'
91
-
92
- const currentView = ref('Dashboard')
93
- const viewKeys = reactive({ Dashboard: 0, Settings: 0 })
94
-
95
- function invalidateCache(view) {
96
- viewKeys[view]++
97
- }
98
- </script>
99
-
100
- <template>
101
- <KeepAlive>
102
- <component :is="currentView" :key="`${currentView}-${viewKeys[currentView]}`" />
103
- </KeepAlive>
104
- </template>
105
- ```
106
-
107
- ## Lifecycle Hooks for Cached Components
108
-
109
- Cached components are not destroyed on switch. Use activation hooks for refresh and cleanup.
110
-
111
- ```vue
112
- <script setup>
113
- import { onActivated, onDeactivated } from 'vue'
114
-
115
- onActivated(() => {
116
- refreshData()
117
- })
118
-
119
- onDeactivated(() => {
120
- pauseTimers()
121
- })
122
- </script>
123
- ```
124
-
125
- ## Router Caching and Freshness
126
-
127
- Decide whether navigation should show cached state or a fresh view. A common pattern is to key by route when params change.
128
-
129
- ```vue
130
- <template>
131
- <router-view v-slot="{ Component, route }">
132
- <KeepAlive>
133
- <component :is="Component" :key="route.fullPath" />
134
- </KeepAlive>
135
- </router-view>
136
- </template>
137
- ```
138
-
139
- If you want cache reuse but fresh data, refresh in `onActivated` and compare query/params before fetching.