symfonia-ai-tools 1.0.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.
- package/README.md +489 -0
- package/bin/cli.mjs +35 -0
- package/lib/installer.mjs +495 -0
- package/lib/questions.mjs +332 -0
- package/lib/ui.mjs +76 -0
- package/lib/utils.mjs +231 -0
- package/package.json +26 -0
- package/templates/base/CLAUDE.md +34 -0
- package/templates/base/_ai/_guidelines_header.md +70 -0
- package/templates/base/_ai/context/README.md +20 -0
- package/templates/base/_ai/prompts/codereview.prompt.md +324 -0
- package/templates/base/_ai/prompts/duplicate-code-analysis.prompt.md +128 -0
- package/templates/base/_ai/prompts/figma-analysis.prompt.md +155 -0
- package/templates/base/_ai/prompts/security-review.prompt.md +46 -0
- package/templates/base/_ai/skills/README.md +80 -0
- package/templates/base/_ai/skills/TEMPLATE.md +106 -0
- package/templates/base/_ai/skills/babysit-prs/SKILL.md +105 -0
- package/templates/base/_ai/skills/debug/SKILL.md +93 -0
- package/templates/base/_ai/skills/fill-worklogs/SKILL.md +158 -0
- package/templates/base/_ai/skills/hotfix/SKILL.md +52 -0
- package/templates/base/_ai/skills/jira-task/SKILL.md +170 -0
- package/templates/base/_ai/skills/my-prs/SKILL.md +78 -0
- package/templates/base/_ai/skills/pr-dashboard/SKILL.md +43 -0
- package/templates/base/_ai/skills/pr-prepare/SKILL.md +106 -0
- package/templates/base/_ai/skills/refactor/SKILL.md +87 -0
- package/templates/base/_ai/skills/write-tests/SKILL.md +109 -0
- package/templates/base/_claude/settings.local.json +37 -0
- package/templates/base/_cursor/rules/global.mdc +7 -0
- package/templates/base/_editorconfig +18 -0
- package/templates/base/_gemini/settings.json +3 -0
- package/templates/base/_github/copilot-instructions.md +1 -0
- package/templates/base/_github/pull_request_template.md +23 -0
- package/templates/base/_gitignore +22 -0
- package/templates/base/_junie/guidelines.md +1 -0
- package/templates/base/commit-instructions.md +92 -0
- package/templates/packs/docker/_ai/instructions/docker.instructions.md +193 -0
- package/templates/packs/docker/_guidelines.md +10 -0
- package/templates/packs/docker/pack.json +8 -0
- package/templates/packs/laravel/_ai/instructions/api-resource.instructions.md +251 -0
- package/templates/packs/laravel/_ai/instructions/module.instructions.md +133 -0
- package/templates/packs/laravel/_ai/instructions/service-repository.instructions.md +215 -0
- package/templates/packs/laravel/_ai/instructions/testing.instructions.md +278 -0
- package/templates/packs/laravel/_ai/skills/migration/SKILL.md +172 -0
- package/templates/packs/laravel/_ai/skills/new-endpoint/SKILL.md +165 -0
- package/templates/packs/laravel/_ai/skills/new-module/SKILL.md +208 -0
- package/templates/packs/laravel/_ai/skills/queued-job/SKILL.md +248 -0
- package/templates/packs/laravel/_ai/skills/testing-feature/SKILL.md +196 -0
- package/templates/packs/laravel/_ai/skills/testing-manual/SKILL.md +186 -0
- package/templates/packs/laravel/_ai/skills/testing-unit/SKILL.md +200 -0
- package/templates/packs/laravel/_guidelines.md +25 -0
- package/templates/packs/laravel/pack.json +6 -0
- package/templates/packs/playwright/_ai/instructions/playwright.instructions.md +219 -0
- package/templates/packs/playwright/_ai/skills/playwright/README.md +194 -0
- package/templates/packs/playwright/_ai/skills/playwright/SKILL.md +1245 -0
- package/templates/packs/playwright/_ai/skills/playwright-codereview/SKILL.md +642 -0
- package/templates/packs/playwright/_ai/skills/playwright-record/README.md +87 -0
- package/templates/packs/playwright/_ai/skills/playwright-record/SKILL.md +564 -0
- package/templates/packs/playwright/_guidelines.md +12 -0
- package/templates/packs/playwright/pack.json +9 -0
- package/templates/packs/storybook/_ai/instructions/storybook.instructions.md +181 -0
- package/templates/packs/storybook/pack.json +6 -0
- package/templates/packs/vitest/_ai/instructions/vitest.instructions.md +688 -0
- package/templates/packs/vitest/pack.json +6 -0
- package/templates/packs/vue3/_ai/instructions/api.instructions.md +163 -0
- package/templates/packs/vue3/_ai/instructions/coding-conventions.instructions.md +160 -0
- package/templates/packs/vue3/_ai/instructions/composables.instructions.md +218 -0
- package/templates/packs/vue3/_ai/instructions/forms.instructions.md +227 -0
- package/templates/packs/vue3/_ai/instructions/store.instructions.md +504 -0
- package/templates/packs/vue3/_ai/instructions/vue.instructions.md +339 -0
- package/templates/packs/vue3/_ai/skills/api-integration/SKILL.md +195 -0
- package/templates/packs/vue3/_ai/skills/new-component/SKILL.md +133 -0
- package/templates/packs/vue3/_ai/skills/new-module/SKILL.md +177 -0
- package/templates/packs/vue3/_guidelines.md +45 -0
- package/templates/packs/vue3/pack.json +11 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "**/*.vue"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Vue Component Instructions
|
|
6
|
+
|
|
7
|
+
## Core Vue Component Principles
|
|
8
|
+
|
|
9
|
+
### Architecture Rules
|
|
10
|
+
- **Vue components (.vue files)**: Visual layer ONLY - NO business logic
|
|
11
|
+
- **ALL business logic** must go in composables (`useXxx` pattern)
|
|
12
|
+
|
|
13
|
+
### Component Structure Template
|
|
14
|
+
```vue
|
|
15
|
+
<template>
|
|
16
|
+
<!-- Use components from your component library -->
|
|
17
|
+
<Button
|
|
18
|
+
:disabled="loading"
|
|
19
|
+
:data-testid="dataTestId"
|
|
20
|
+
@click="handleClick"
|
|
21
|
+
>
|
|
22
|
+
{{ $t('button.save') }}
|
|
23
|
+
</Button>
|
|
24
|
+
|
|
25
|
+
<!-- Always use translation keys, never hardcoded text -->
|
|
26
|
+
<div>
|
|
27
|
+
{{ $t('component.description') }}
|
|
28
|
+
</div>
|
|
29
|
+
</template>
|
|
30
|
+
|
|
31
|
+
<script setup lang="ts">// Import components
|
|
32
|
+
import { Button } from '{{COMPONENT_LIB_IMPORT}}'
|
|
33
|
+
// 1. Import composables for ALL business logic
|
|
34
|
+
import { useComponentLogic } from '@/composables/useComponentLogic'
|
|
35
|
+
|
|
36
|
+
// 2. Define props with proper TypeScript interfaces (prefix with I)
|
|
37
|
+
interface IComponentProps {
|
|
38
|
+
userId: string
|
|
39
|
+
isEditable: boolean
|
|
40
|
+
dataTestId?: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 3. Define emits with proper TypeScript interfaces
|
|
44
|
+
interface IComponentEmits {
|
|
45
|
+
save: [data: IFormData]
|
|
46
|
+
cancel: []
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const props = withDefaults(defineProps<IComponentProps>(), {
|
|
50
|
+
dataTestId: 'component'
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const emit = defineEmits<IComponentEmits>()
|
|
54
|
+
|
|
55
|
+
// 4. Use composables for ALL business logic - NO logic in component
|
|
56
|
+
const {
|
|
57
|
+
loading,
|
|
58
|
+
handleSave,
|
|
59
|
+
handleCancel,
|
|
60
|
+
buttonVariant,
|
|
61
|
+
textVariant
|
|
62
|
+
} = useComponentLogic(props, emit)
|
|
63
|
+
|
|
64
|
+
// 5. Only simple event handlers that delegate to composables
|
|
65
|
+
const handleClick = () => {
|
|
66
|
+
handleSave()
|
|
67
|
+
}
|
|
68
|
+
</script>
|
|
69
|
+
|
|
70
|
+
<style scoped>
|
|
71
|
+
/* Minimal styles only */
|
|
72
|
+
/* DO NOT interfere with existing CSS */
|
|
73
|
+
</style>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Mandatory Requirements
|
|
77
|
+
|
|
78
|
+
### 1. TypeScript Interfaces
|
|
79
|
+
- **ALL props must have interfaces** prefixed with `I` (e.g., `IComponentProps`)
|
|
80
|
+
- **ALL emits must be typed** with interfaces (e.g., `IComponentEmits`)
|
|
81
|
+
- **NEVER use `any` type** - strict typing required
|
|
82
|
+
- **Import types properly** with `import type { ... }`
|
|
83
|
+
|
|
84
|
+
### 2. Business Logic Separation
|
|
85
|
+
- **NO business logic in Vue components**
|
|
86
|
+
- **ALL logic goes in composables** (`/src/composables/useXxxLogic.ts`)
|
|
87
|
+
- **Components only handle**: rendering, props, emits, basic event delegation
|
|
88
|
+
- **Use composables for**: API calls, state management, calculations, validations
|
|
89
|
+
|
|
90
|
+
### 3. Component Library Usage
|
|
91
|
+
- **ALWAYS use existing components** from your component library (Button, InputText, Loader, Alert, Modal, etc.)
|
|
92
|
+
- **Check existing components** before creating new ones
|
|
93
|
+
- **Follow existing patterns** as examples in the codebase
|
|
94
|
+
- **NEVER recreate existing functionality**
|
|
95
|
+
- Import components: `import { Button, InputText, Alert } from '{{COMPONENT_LIB_IMPORT}}'`
|
|
96
|
+
|
|
97
|
+
### 4. Internationalization (i18n)
|
|
98
|
+
- **ALL user-facing text MUST use `$t('key')`**
|
|
99
|
+
- **NEVER hardcode strings** in templates
|
|
100
|
+
- **Check existing translation keys** first
|
|
101
|
+
- **Use descriptive, hierarchical keys**: `module.component.action`
|
|
102
|
+
|
|
103
|
+
### 5. Data Test IDs
|
|
104
|
+
- **ALWAYS add `data-testid`** for testing
|
|
105
|
+
- **Pass `dataTestId` as prop** with default value
|
|
106
|
+
- **Use camelCase for `dataTestId` values**: `'bellNotifications'`, `'userForm'`, `'saveButton'`
|
|
107
|
+
- Correct: `dataTestId: 'bellNotifications'`
|
|
108
|
+
- Wrong: `dataTestId: 'bell-notifications'`
|
|
109
|
+
|
|
110
|
+
### 6. CSS Class Naming
|
|
111
|
+
- **Use single dash `-` as separator** (NOT BEM double underscore `__`)
|
|
112
|
+
- Correct: `.notification-item-content`, `.user-form-header`
|
|
113
|
+
- Wrong: `.notification-item__content`, `.user-form__header`
|
|
114
|
+
- **No CSS comments** - styles should be self-explanatory
|
|
115
|
+
|
|
116
|
+
### 7. Console and Debugging
|
|
117
|
+
- **NEVER use console methods** in production code
|
|
118
|
+
- Use a centralized error handler utility for errors
|
|
119
|
+
- Remove all debugging statements before committing
|
|
120
|
+
|
|
121
|
+
## Component Development Guidelines
|
|
122
|
+
|
|
123
|
+
### Props and Emits Best Practices
|
|
124
|
+
```vue
|
|
125
|
+
<script setup lang="ts">
|
|
126
|
+
// Good: Proper interface definition
|
|
127
|
+
interface IUserFormProps {
|
|
128
|
+
userId: string
|
|
129
|
+
initialData?: IUserData
|
|
130
|
+
isReadonly: boolean
|
|
131
|
+
dataTestId?: string
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
interface IUserFormEmits {
|
|
135
|
+
save: [userData: IUserData]
|
|
136
|
+
cancel: []
|
|
137
|
+
validation: [isValid: boolean]
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Good: Default values with camelCase
|
|
141
|
+
const props = withDefaults(defineProps<IUserFormProps>(), {
|
|
142
|
+
dataTestId: 'userForm',
|
|
143
|
+
isReadonly: false
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
const emit = defineEmits<IUserFormEmits>()
|
|
147
|
+
|
|
148
|
+
// Good: Delegate everything to composables
|
|
149
|
+
const { formData, validation, handleSave } = useUserForm(props, emit)
|
|
150
|
+
</script>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Error Handling
|
|
154
|
+
```vue
|
|
155
|
+
<script setup lang="ts">
|
|
156
|
+
// Good: Error handling in composables
|
|
157
|
+
const { error, loading, clearError } = useUserManagement()
|
|
158
|
+
|
|
159
|
+
// Good: Error display with components
|
|
160
|
+
</script>
|
|
161
|
+
|
|
162
|
+
<template>
|
|
163
|
+
<Alert
|
|
164
|
+
v-if="error"
|
|
165
|
+
type="error"
|
|
166
|
+
:data-testid="`${dataTestId}-error`"
|
|
167
|
+
@close="clearError"
|
|
168
|
+
>
|
|
169
|
+
{{ $t('error.generic') }}
|
|
170
|
+
</Alert>
|
|
171
|
+
</template>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Loading States
|
|
175
|
+
```vue
|
|
176
|
+
<template>
|
|
177
|
+
<!-- Good: Loading states with components -->
|
|
178
|
+
<Loader v-if="loading" :data-testid="`${dataTestId}-loader`" />
|
|
179
|
+
|
|
180
|
+
<Button
|
|
181
|
+
:disabled="loading"
|
|
182
|
+
:loading="loading"
|
|
183
|
+
:data-testid="`${dataTestId}-submit`"
|
|
184
|
+
>
|
|
185
|
+
{{ $t('form.submit') }}
|
|
186
|
+
</Button>
|
|
187
|
+
</template>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Common Mistakes to Avoid
|
|
191
|
+
|
|
192
|
+
### Never Do This
|
|
193
|
+
```vue
|
|
194
|
+
<script setup lang="ts">
|
|
195
|
+
// BAD: Business logic in component
|
|
196
|
+
const saveUser = async () => {
|
|
197
|
+
try {
|
|
198
|
+
const response = await api.post('/users', userData)
|
|
199
|
+
// Complex business logic here
|
|
200
|
+
} catch (error) {
|
|
201
|
+
// Error handling here
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// BAD: Hardcoded strings
|
|
206
|
+
const title = 'User Profile'
|
|
207
|
+
|
|
208
|
+
// BAD: Using any type
|
|
209
|
+
const props = defineProps<{ data: any }>()
|
|
210
|
+
|
|
211
|
+
</script>
|
|
212
|
+
|
|
213
|
+
<template>
|
|
214
|
+
<!-- BAD: Hardcoded text -->
|
|
215
|
+
<h1>User Profile</h1>
|
|
216
|
+
</template>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Always Do This
|
|
220
|
+
```vue
|
|
221
|
+
<script setup lang="ts">
|
|
222
|
+
// GOOD: Use composables for business logic
|
|
223
|
+
const { saveUser, loading, error } = useUserManagement(props, emit)
|
|
224
|
+
|
|
225
|
+
// GOOD: Proper TypeScript interfaces
|
|
226
|
+
interface IUserProfileProps {
|
|
227
|
+
userId: string
|
|
228
|
+
dataTestId?: string
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const props = withDefaults(defineProps<IUserProfileProps>(), {
|
|
232
|
+
dataTestId: 'user-profile'
|
|
233
|
+
})
|
|
234
|
+
</script>
|
|
235
|
+
|
|
236
|
+
<template>
|
|
237
|
+
<!-- GOOD: Use translation keys -->
|
|
238
|
+
<h1 :data-testid="`${dataTestId}-title`">
|
|
239
|
+
{{ $t('user.profile.title') }}
|
|
240
|
+
</h1>
|
|
241
|
+
|
|
242
|
+
<!-- GOOD: Use components from your component library -->
|
|
243
|
+
<Button
|
|
244
|
+
:disabled="loading"
|
|
245
|
+
:data-testid="`${dataTestId}-save`"
|
|
246
|
+
@click="saveUser"
|
|
247
|
+
>
|
|
248
|
+
{{ $t('form.save') }}
|
|
249
|
+
</Button>
|
|
250
|
+
</template>
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Testing Requirements
|
|
254
|
+
|
|
255
|
+
### Component Testing with VITEST
|
|
256
|
+
- **Test props and emits behavior**
|
|
257
|
+
- **Test user interactions**
|
|
258
|
+
- **Mock composables, not component internals**
|
|
259
|
+
- **Use `data-testid` for element selection**
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
// Example component test
|
|
263
|
+
describe('UserForm Component', () => {
|
|
264
|
+
it('should emit save event when form is submitted', async () => {
|
|
265
|
+
const wrapper = mount(UserForm, {
|
|
266
|
+
props: { userId: '123', dataTestId: 'test-form' }
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
await wrapper.find('[data-testid="test-form-save"]').trigger('click')
|
|
270
|
+
expect(wrapper.emitted('save')).toBeTruthy()
|
|
271
|
+
})
|
|
272
|
+
})
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Performance Optimization
|
|
276
|
+
|
|
277
|
+
### Vue Performance Best Practices
|
|
278
|
+
```vue
|
|
279
|
+
<template>
|
|
280
|
+
<!-- Use v-memo for expensive renders -->
|
|
281
|
+
<div v-memo="[expensiveData]">
|
|
282
|
+
<!-- Complex list items -->
|
|
283
|
+
</div>
|
|
284
|
+
|
|
285
|
+
<!-- Proper key attributes in v-for -->
|
|
286
|
+
<div
|
|
287
|
+
v-for="item in items"
|
|
288
|
+
:key="item.id"
|
|
289
|
+
:data-testid="`item-${item.id}`"
|
|
290
|
+
>
|
|
291
|
+
{{ item.name }}
|
|
292
|
+
</div>
|
|
293
|
+
</template>
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## State Management Integration
|
|
297
|
+
|
|
298
|
+
### Pinia Store Usage
|
|
299
|
+
```vue
|
|
300
|
+
<script setup lang="ts">
|
|
301
|
+
// Good: Access stores through composables
|
|
302
|
+
const { users, addUser } = useUserStore()
|
|
303
|
+
|
|
304
|
+
// Good: Reactive computed properties
|
|
305
|
+
const filteredUsers = computed(() =>
|
|
306
|
+
users.value.filter(user => user.isActive)
|
|
307
|
+
)
|
|
308
|
+
</script>
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## File Organization
|
|
312
|
+
|
|
313
|
+
### Component Location Rules
|
|
314
|
+
- **Module-specific components**: `{{MODULE_PATH}}/components/`
|
|
315
|
+
- **Shared components**: `{{COMPONENT_LIB_PATH}}`
|
|
316
|
+
- **Component composables**: `{{MODULE_PATH}}/composables/`
|
|
317
|
+
- **Component types**: `{{MODULE_PATH}}/types/`
|
|
318
|
+
|
|
319
|
+
### Import Organization
|
|
320
|
+
```vue
|
|
321
|
+
<script setup lang="ts">
|
|
322
|
+
// 1. Vue imports
|
|
323
|
+
import { computed, ref } from 'vue'
|
|
324
|
+
|
|
325
|
+
// 2. External library imports
|
|
326
|
+
import { useI18n } from 'vue-i18n'
|
|
327
|
+
|
|
328
|
+
// 3. Project components
|
|
329
|
+
import { Button, InputText, Alert, Loader } from '{{COMPONENT_LIB_IMPORT}}'
|
|
330
|
+
|
|
331
|
+
// 4. Internal composables
|
|
332
|
+
import { useUserManagement } from '@/composables/useUserManagement'
|
|
333
|
+
|
|
334
|
+
// 5. Types
|
|
335
|
+
import type { IUser, IUserForm } from '@/types/user'
|
|
336
|
+
</script>
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Remember: Vue components are purely visual layers. All business logic, API calls, and complex state management must be handled by composables following the established architecture patterns.
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# Skill: API Integration (Vue 3 + TypeScript)
|
|
2
|
+
|
|
3
|
+
## Trigger
|
|
4
|
+
Use when connecting frontend to a new or existing backend API endpoint.
|
|
5
|
+
|
|
6
|
+
## Input
|
|
7
|
+
- API endpoint (method + URL, e.g. `GET /api/users`)
|
|
8
|
+
- Request payload (if any)
|
|
9
|
+
- Response format (JSON structure)
|
|
10
|
+
- Target module (where to add integration)
|
|
11
|
+
|
|
12
|
+
## Steps
|
|
13
|
+
|
|
14
|
+
### 1. Define types from API contract
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
// types/[module].types.ts
|
|
18
|
+
|
|
19
|
+
// Response type - matches API response exactly
|
|
20
|
+
export interface I[Entity]Response {
|
|
21
|
+
id: number;
|
|
22
|
+
name: string;
|
|
23
|
+
status: T[Entity]Status;
|
|
24
|
+
// ... match API response fields
|
|
25
|
+
created_at: string; // keep API casing in response type
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Frontend type - camelCase, parsed dates
|
|
29
|
+
export interface I[Entity] {
|
|
30
|
+
id: number;
|
|
31
|
+
name: string;
|
|
32
|
+
status: T[Entity]Status;
|
|
33
|
+
createdAt: Date;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Request payload type
|
|
37
|
+
export interface I[Entity]CreatePayload {
|
|
38
|
+
name: string;
|
|
39
|
+
status: T[Entity]Status;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Mapper
|
|
43
|
+
export function map[Entity](raw: I[Entity]Response): I[Entity] {
|
|
44
|
+
return {
|
|
45
|
+
id: raw.id,
|
|
46
|
+
name: raw.name,
|
|
47
|
+
status: raw.status,
|
|
48
|
+
createdAt: new Date(raw.created_at),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 2. Add service method
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// services/[Module].service.ts
|
|
57
|
+
import { useApi } from '@/composables/useApi';
|
|
58
|
+
import type { I[Entity]Response, I[Entity]CreatePayload } from '../types/[module].types';
|
|
59
|
+
|
|
60
|
+
const api = useApi();
|
|
61
|
+
const BASE = '{{API_BASE_URL}}/[endpoint]';
|
|
62
|
+
|
|
63
|
+
const [Module]Service = {
|
|
64
|
+
// ... existing methods
|
|
65
|
+
|
|
66
|
+
// New method
|
|
67
|
+
fetchItems: (params?: Record<string, unknown>) =>
|
|
68
|
+
api.get<I[Entity]Response[]>(BASE, { params }),
|
|
69
|
+
|
|
70
|
+
createItem: (data: I[Entity]CreatePayload) =>
|
|
71
|
+
api.post<I[Entity]Response>(BASE, data),
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export default [Module]Service;
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 3. Update store
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// [module].store.ts - add to existing store
|
|
81
|
+
|
|
82
|
+
import { map[Entity] } from './types/[module].types';
|
|
83
|
+
|
|
84
|
+
// Inside defineStore:
|
|
85
|
+
async function fetchItems() {
|
|
86
|
+
loading.value = true;
|
|
87
|
+
try {
|
|
88
|
+
const { data } = await [Module]Service.fetchItems();
|
|
89
|
+
items.value = data.map(map[Entity]);
|
|
90
|
+
} finally {
|
|
91
|
+
loading.value = false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### 4. Create/update composable
|
|
97
|
+
|
|
98
|
+
If the integration needs form handling or complex logic, create a composable:
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
// composables/use[Action].ts
|
|
102
|
+
import { ref } from 'vue';
|
|
103
|
+
import { use[Module]Store } from '../[module].store';
|
|
104
|
+
import [Module]Service from '../services/[Module].service';
|
|
105
|
+
import type { I[Entity]CreatePayload } from '../types/[module].types';
|
|
106
|
+
|
|
107
|
+
export function use[Action]() {
|
|
108
|
+
const store = use[Module]Store();
|
|
109
|
+
const submitting = ref(false);
|
|
110
|
+
const error = ref<string | null>(null);
|
|
111
|
+
|
|
112
|
+
async function submit(payload: I[Entity]CreatePayload) {
|
|
113
|
+
submitting.value = true;
|
|
114
|
+
error.value = null;
|
|
115
|
+
try {
|
|
116
|
+
await [Module]Service.createItem(payload);
|
|
117
|
+
await store.fetchItems(); // refresh list
|
|
118
|
+
} catch (e) {
|
|
119
|
+
error.value = 'Operation failed';
|
|
120
|
+
throw e;
|
|
121
|
+
} finally {
|
|
122
|
+
submitting.value = false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return { submitting, error, submit };
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 5. Connect to view
|
|
131
|
+
|
|
132
|
+
```vue
|
|
133
|
+
<script setup lang="ts">
|
|
134
|
+
import { use[Module]Store } from '../[module].store';
|
|
135
|
+
import { use[Action] } from '../composables/use[Action]';
|
|
136
|
+
|
|
137
|
+
const store = use[Module]Store();
|
|
138
|
+
const { submitting, error, submit } = use[Action]();
|
|
139
|
+
|
|
140
|
+
// Fetch on mount
|
|
141
|
+
store.fetchItems();
|
|
142
|
+
</script>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 6. Handle errors
|
|
146
|
+
|
|
147
|
+
Ensure error states are handled in the UI:
|
|
148
|
+
- Loading state (spinner/skeleton)
|
|
149
|
+
- Empty state (no results)
|
|
150
|
+
- Error state (API failure message)
|
|
151
|
+
- Validation errors (422 response → field-level errors)
|
|
152
|
+
|
|
153
|
+
### 7. Write tests
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
// Test service method (mock API)
|
|
157
|
+
describe('[Module]Service', () => {
|
|
158
|
+
it('fetches items from API', async () => {
|
|
159
|
+
// mock useApi().get to return test data
|
|
160
|
+
// verify service calls correct URL
|
|
161
|
+
// verify response is returned
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Test store action
|
|
166
|
+
describe('use[Module]Store', () => {
|
|
167
|
+
it('fetchItems maps response and updates state', async () => {
|
|
168
|
+
// mock service
|
|
169
|
+
// call store.fetchItems()
|
|
170
|
+
// verify items are mapped and stored
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Test composable
|
|
175
|
+
describe('use[Action]', () => {
|
|
176
|
+
it('submits and refreshes list', async () => {
|
|
177
|
+
// mock service + store
|
|
178
|
+
// call submit()
|
|
179
|
+
// verify service called + store refreshed
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### 8. Verification checklist
|
|
185
|
+
|
|
186
|
+
- [ ] Types match API contract exactly
|
|
187
|
+
- [ ] Mapper converts API casing → frontend casing
|
|
188
|
+
- [ ] Service method uses correct HTTP method + URL
|
|
189
|
+
- [ ] Store action handles loading state
|
|
190
|
+
- [ ] Error states handled in UI (loading, empty, error)
|
|
191
|
+
- [ ] 422 validation errors mapped to form fields
|
|
192
|
+
- [ ] Composable created if logic is non-trivial
|
|
193
|
+
- [ ] Tests cover service, store, composable
|
|
194
|
+
- [ ] No `any` types — everything strictly typed
|
|
195
|
+
- [ ] No hardcoded API URLs (use base URL constant)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Skill: New Component (Vue 3 + TypeScript)
|
|
2
|
+
|
|
3
|
+
## Trigger
|
|
4
|
+
Use when creating a new reusable Vue component.
|
|
5
|
+
|
|
6
|
+
## Input
|
|
7
|
+
- Component name (PascalCase, e.g. "UserCard")
|
|
8
|
+
- Props specification
|
|
9
|
+
- Events specification
|
|
10
|
+
- Target directory
|
|
11
|
+
|
|
12
|
+
## Steps
|
|
13
|
+
|
|
14
|
+
### 1. Create files
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
[target-dir]/
|
|
18
|
+
├── [ComponentName].vue
|
|
19
|
+
├── [ComponentName].stories.ts
|
|
20
|
+
└── [ComponentName].test.ts
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### 2. Write component
|
|
24
|
+
|
|
25
|
+
```vue
|
|
26
|
+
<template>
|
|
27
|
+
<div class="[component-name]" :data-testid="dataTestId">
|
|
28
|
+
<!-- component template -->
|
|
29
|
+
</div>
|
|
30
|
+
</template>
|
|
31
|
+
|
|
32
|
+
<script setup lang="ts">
|
|
33
|
+
interface I[ComponentName]Props {
|
|
34
|
+
/** Description of prop */
|
|
35
|
+
dataTestId?: string;
|
|
36
|
+
// ... props
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface I[ComponentName]Emits {
|
|
40
|
+
(e: 'update', value: unknown): void;
|
|
41
|
+
// ... events
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const props = withDefaults(defineProps<I[ComponentName]Props>(), {
|
|
45
|
+
dataTestId: '[componentName]',
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const emit = defineEmits<I[ComponentName]Emits>();
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<style scoped lang="scss">
|
|
52
|
+
.component-name {
|
|
53
|
+
// single-dash CSS classes only
|
|
54
|
+
}
|
|
55
|
+
</style>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Rules:
|
|
59
|
+
- `<template>` → `<script setup>` → `<style scoped>` order
|
|
60
|
+
- Interface props with `I` prefix
|
|
61
|
+
- `withDefaults` for default values
|
|
62
|
+
- JSDoc on props for Storybook autodocs
|
|
63
|
+
- `data-testid` on root element
|
|
64
|
+
- CSS: single-dash classes, no BEM
|
|
65
|
+
|
|
66
|
+
### 3. Write Storybook story
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import type { Meta, StoryObj } from '@storybook/vue3';
|
|
70
|
+
import [ComponentName] from './[ComponentName].vue';
|
|
71
|
+
|
|
72
|
+
const meta: Meta<typeof [ComponentName]> = {
|
|
73
|
+
title: 'Components/[Category]/[ComponentName]',
|
|
74
|
+
component: [ComponentName],
|
|
75
|
+
argTypes: {
|
|
76
|
+
// control for each prop
|
|
77
|
+
},
|
|
78
|
+
tags: ['autodocs'],
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export default meta;
|
|
82
|
+
type Story = StoryObj<typeof [ComponentName]>;
|
|
83
|
+
|
|
84
|
+
export const Default: Story = {
|
|
85
|
+
args: {
|
|
86
|
+
// default props
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Add variants: Disabled, Loading, Error, etc.
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 4. Write test
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { mount } from '@vue/test-utils';
|
|
97
|
+
import { describe, expect, it } from 'vitest';
|
|
98
|
+
import [ComponentName] from './[ComponentName].vue';
|
|
99
|
+
|
|
100
|
+
describe('[ComponentName]', () => {
|
|
101
|
+
it('renders with default props', () => {
|
|
102
|
+
const wrapper = mount([ComponentName]);
|
|
103
|
+
expect(wrapper.find('.[component-name]').exists()).toBe(true);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('emits events correctly', async () => {
|
|
107
|
+
const wrapper = mount([ComponentName]);
|
|
108
|
+
// trigger action
|
|
109
|
+
expect(wrapper.emitted('update')).toBeTruthy();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('has correct data-testid', () => {
|
|
113
|
+
const wrapper = mount([ComponentName], {
|
|
114
|
+
props: { dataTestId: 'custom' },
|
|
115
|
+
});
|
|
116
|
+
expect(wrapper.attributes('data-testid')).toBe('custom');
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 5. Verification checklist
|
|
122
|
+
|
|
123
|
+
- [ ] Component renders correctly
|
|
124
|
+
- [ ] Props typed with interface + `I` prefix
|
|
125
|
+
- [ ] Emits typed with interface
|
|
126
|
+
- [ ] `withDefaults` for optional props
|
|
127
|
+
- [ ] JSDoc comments on props (for Storybook)
|
|
128
|
+
- [ ] `data-testid` on root and interactive elements
|
|
129
|
+
- [ ] Story with `tags: ['autodocs']` and argTypes
|
|
130
|
+
- [ ] At least Default + 2 variant stories
|
|
131
|
+
- [ ] Test covers render, props, events
|
|
132
|
+
- [ ] CSS uses single-dash classes, scoped
|
|
133
|
+
- [ ] No `console.log`
|