@dianzhong/create-harness-app 0.1.1 → 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 (39) hide show
  1. package/README.md +68 -0
  2. package/package.json +1 -1
  3. package/templates/harness/full/.claude/agents/code-reviewer.md +1 -1
  4. package/templates/harness/full/.claude/agents/harness-reviewer.md +0 -2
  5. package/templates/harness/full/.claude/rules/skills-mcp.md +2 -3
  6. package/templates/harness/full/.claude/settings.json +1 -1
  7. package/templates/harness/full/docs/ai-harness.md +4 -6
  8. package/templates/harness/full/docs/harness-quick-reference.md +1 -1
  9. package/templates/harness/full/docs/review-checklist.md +1 -1
  10. package/templates/harness/full/scripts/verify-skills.mjs +6 -61
  11. package/templates/harness/full/scripts/verify-skills.test.mjs +1 -11
  12. package/templates/harness/full/.agents/skills/find-skills/SKILL.md +0 -143
  13. package/templates/harness/full/.agents/skills/vue-best-practices/LICENSE.md +0 -21
  14. package/templates/harness/full/.agents/skills/vue-best-practices/SKILL.md +0 -155
  15. package/templates/harness/full/.agents/skills/vue-best-practices/SYNC.md +0 -5
  16. package/templates/harness/full/.agents/skills/vue-best-practices/references/animation-class-based-technique.md +0 -258
  17. package/templates/harness/full/.agents/skills/vue-best-practices/references/animation-state-driven-technique.md +0 -287
  18. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-async.md +0 -99
  19. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-data-flow.md +0 -313
  20. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-fallthrough-attrs.md +0 -179
  21. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-keep-alive.md +0 -139
  22. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-slots.md +0 -226
  23. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-suspense.md +0 -231
  24. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-teleport.md +0 -110
  25. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-transition-group.md +0 -131
  26. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-transition.md +0 -135
  27. package/templates/harness/full/.agents/skills/vue-best-practices/references/composables.md +0 -303
  28. package/templates/harness/full/.agents/skills/vue-best-practices/references/directives.md +0 -168
  29. package/templates/harness/full/.agents/skills/vue-best-practices/references/perf-avoid-component-abstraction-in-lists.md +0 -177
  30. package/templates/harness/full/.agents/skills/vue-best-practices/references/perf-v-once-v-memo-directives.md +0 -185
  31. package/templates/harness/full/.agents/skills/vue-best-practices/references/perf-virtualize-large-lists.md +0 -182
  32. package/templates/harness/full/.agents/skills/vue-best-practices/references/plugins.md +0 -178
  33. package/templates/harness/full/.agents/skills/vue-best-practices/references/reactivity.md +0 -371
  34. package/templates/harness/full/.agents/skills/vue-best-practices/references/render-functions.md +0 -227
  35. package/templates/harness/full/.agents/skills/vue-best-practices/references/sfc.md +0 -355
  36. package/templates/harness/full/.agents/skills/vue-best-practices/references/state-management.md +0 -138
  37. package/templates/harness/full/.agents/skills/vue-best-practices/references/updated-hook-performance.md +0 -193
  38. package/templates/harness/full/AGENTS.md +0 -3
  39. package/templates/harness/full/GEMINI.md +0 -3
@@ -1,99 +0,0 @@
1
- ---
2
- title: Async Component Best Practices
3
- impact: MEDIUM
4
- impactDescription: Poor async component strategy can delay interactivity in SSR apps and create loading UI flicker
5
- type: best-practice
6
- tags: [vue3, async-components, ssr, hydration, performance, ux]
7
- ---
8
-
9
- # Async Component Best Practices
10
-
11
- **Impact: MEDIUM** - Async components should reduce JavaScript cost without degrading perceived performance. Focus on hydration timing in SSR and stable loading UX.
12
-
13
- ## Task List
14
-
15
- - Use lazy hydration strategies for non-critical SSR component trees
16
- - Import only the hydration helpers you actually use
17
- - Keep `loadingComponent` delay near the default `200ms` unless real UX data suggests otherwise
18
- - Configure `delay` and `timeout` together for predictable loading behavior
19
-
20
- ## Use Lazy Hydration Strategies in SSR
21
-
22
- In Vue 3.5+, async components can delay hydration until idle time, visibility, media query match, or user interaction.
23
-
24
- **BAD:**
25
-
26
- ```vue
27
- <script setup lang="ts">
28
- import { defineAsyncComponent } from 'vue'
29
-
30
- const AsyncComments = defineAsyncComponent({
31
- loader: () => import('./Comments.vue'),
32
- })
33
- </script>
34
- ```
35
-
36
- **GOOD:**
37
-
38
- ```vue
39
- <script setup lang="ts">
40
- import { defineAsyncComponent, hydrateOnIdle, hydrateOnVisible } from 'vue'
41
-
42
- const AsyncComments = defineAsyncComponent({
43
- loader: () => import('./Comments.vue'),
44
- hydrate: hydrateOnVisible({ rootMargin: '100px' }),
45
- })
46
-
47
- const AsyncFooter = defineAsyncComponent({
48
- loader: () => import('./Footer.vue'),
49
- hydrate: hydrateOnIdle(5000),
50
- })
51
- </script>
52
- ```
53
-
54
- ## Prevent Loading Spinner Flicker
55
-
56
- Avoid showing loading UI immediately for components that usually resolve quickly.
57
-
58
- **BAD:**
59
-
60
- ```vue
61
- <script setup lang="ts">
62
- import { defineAsyncComponent } from 'vue'
63
-
64
- import LoadingSpinner from './LoadingSpinner.vue'
65
-
66
- const AsyncDashboard = defineAsyncComponent({
67
- loader: () => import('./Dashboard.vue'),
68
- loadingComponent: LoadingSpinner,
69
- delay: 0,
70
- })
71
- </script>
72
- ```
73
-
74
- **GOOD:**
75
-
76
- ```vue
77
- <script setup lang="ts">
78
- import { defineAsyncComponent } from 'vue'
79
-
80
- import ErrorDisplay from './ErrorDisplay.vue'
81
- import LoadingSpinner from './LoadingSpinner.vue'
82
-
83
- const AsyncDashboard = defineAsyncComponent({
84
- loader: () => import('./Dashboard.vue'),
85
- loadingComponent: LoadingSpinner,
86
- errorComponent: ErrorDisplay,
87
- delay: 200,
88
- timeout: 30000,
89
- })
90
- </script>
91
- ```
92
-
93
- ## Delay Guidelines
94
-
95
- | Scenario | Recommended Delay |
96
- | ----------------------------- | ----------------- |
97
- | Small component, fast network | `200ms` |
98
- | Known heavy component | `100ms` |
99
- | Background or non-critical UI | `300-500ms` |
@@ -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
- ```