@codihaus/claude-skills 1.6.6 → 1.6.7
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/package.json +1 -1
- package/skills/dev-coding/SKILL.md +190 -667
- package/skills/dev-coding/references/backend-principles.md +130 -0
- package/skills/dev-coding/references/frontend-principles.md +212 -0
- package/skills/dev-scout/SKILL.md +122 -873
- package/skills/dev-scout/references/output-template.md +231 -0
- package/skills/dev-specs/SKILL.md +104 -598
- package/skills/dev-specs/references/spec-template.md +146 -0
- package/src/utils/config.js +1 -1
- package/templates/scripts/safe-graph-update.sh +3 -1
- package/skills/dev-coding-backend/SKILL.md +0 -240
- package/skills/dev-coding-backend/references/fundamentals.md +0 -428
- package/skills/dev-coding-frontend/SKILL.md +0 -296
- package/skills/dev-coding-frontend/references/fundamentals.md +0 -577
- package/skills/dev-scout/references/feature-patterns.md +0 -210
- package/skills/dev-scout/references/file-patterns.md +0 -252
- package/skills/dev-scout/references/stack-patterns.md +0 -371
- package/skills/dev-scout/references/tech-detection.md +0 -211
- package/skills/dev-specs/references/checklist.md +0 -176
- package/skills/dev-specs/references/spec-templates.md +0 -460
|
@@ -1,577 +0,0 @@
|
|
|
1
|
-
# Frontend Fundamentals
|
|
2
|
-
|
|
3
|
-
The principles and patterns that create excellent user experiences.
|
|
4
|
-
|
|
5
|
-
## Core Mindset
|
|
6
|
-
|
|
7
|
-
**"I am the communicator with humans. I must be clear, responsive, and consistent."**
|
|
8
|
-
|
|
9
|
-
The frontend is the interface between humans and the system. It:
|
|
10
|
-
- Communicates what's happening
|
|
11
|
-
- Responds instantly (or appears to)
|
|
12
|
-
- Behaves consistently
|
|
13
|
-
- Works for everyone
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## The Principles
|
|
18
|
-
|
|
19
|
-
Principles are the fundamental laws. Patterns are solutions derived from them.
|
|
20
|
-
|
|
21
|
-
### 1. Always Communicate
|
|
22
|
-
|
|
23
|
-
**Law**: The user should NEVER wonder what's happening.
|
|
24
|
-
|
|
25
|
-
**Why**: Uncertainty creates anxiety. Users abandon apps that feel "broken" or "stuck."
|
|
26
|
-
|
|
27
|
-
**Patterns derived**:
|
|
28
|
-
|
|
29
|
-
| Pattern | What it Communicates |
|
|
30
|
-
|---------|---------------------|
|
|
31
|
-
| Loading State | "I'm working on it" |
|
|
32
|
-
| Error State | "Something went wrong, here's what" |
|
|
33
|
-
| Empty State | "Nothing here yet, here's why" |
|
|
34
|
-
| Success Feedback | "Done! Here's the result" |
|
|
35
|
-
| Progress Indicator | "X% complete" |
|
|
36
|
-
| Skeleton UI | "Content is coming, here's the shape" |
|
|
37
|
-
|
|
38
|
-
**Recognition signals**:
|
|
39
|
-
- Blank screen while loading
|
|
40
|
-
- Click button, nothing visible happens
|
|
41
|
-
- Error occurs, user sees nothing
|
|
42
|
-
- Form submits, no confirmation
|
|
43
|
-
- List might be empty, just shows nothing
|
|
44
|
-
|
|
45
|
-
**State matrix for EVERY data-fetching component**:
|
|
46
|
-
```
|
|
47
|
-
STATE WHAT USER SEES
|
|
48
|
-
─────────────────────────────────────
|
|
49
|
-
idle Initial state, maybe CTA
|
|
50
|
-
loading Spinner, skeleton, or shimmer
|
|
51
|
-
success The actual content
|
|
52
|
-
empty Helpful message + action
|
|
53
|
-
error What went wrong + retry option
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
**Structure example**:
|
|
57
|
-
```vue
|
|
58
|
-
<!-- BAD: No communication -->
|
|
59
|
-
<template>
|
|
60
|
-
<div v-for="item in items">{{ item.name }}</div>
|
|
61
|
-
</template>
|
|
62
|
-
|
|
63
|
-
<!-- GOOD: Always communicating -->
|
|
64
|
-
<template>
|
|
65
|
-
<!-- Loading: Show skeleton -->
|
|
66
|
-
<div v-if="pending">
|
|
67
|
-
<Skeleton v-for="i in 3" :key="i" />
|
|
68
|
-
</div>
|
|
69
|
-
|
|
70
|
-
<!-- Error: Explain and offer action -->
|
|
71
|
-
<div v-else-if="error">
|
|
72
|
-
<ErrorMessage :error="error" />
|
|
73
|
-
<Button @click="refresh">Try Again</Button>
|
|
74
|
-
</div>
|
|
75
|
-
|
|
76
|
-
<!-- Empty: Guide the user -->
|
|
77
|
-
<div v-else-if="items.length === 0">
|
|
78
|
-
<EmptyState
|
|
79
|
-
title="No items yet"
|
|
80
|
-
description="Create your first item to get started"
|
|
81
|
-
action="Create Item"
|
|
82
|
-
@action="openCreateModal"
|
|
83
|
-
/>
|
|
84
|
-
</div>
|
|
85
|
-
|
|
86
|
-
<!-- Success: Show content -->
|
|
87
|
-
<div v-else>
|
|
88
|
-
<Item v-for="item in items" :key="item.id" :item="item" />
|
|
89
|
-
</div>
|
|
90
|
-
</template>
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
---
|
|
94
|
-
|
|
95
|
-
### 2. Feel Instant
|
|
96
|
-
|
|
97
|
-
**Law**: Perception matters more than reality. Make it FEEL fast.
|
|
98
|
-
|
|
99
|
-
**Why**: Users perceive 100ms as instant. After 1s they notice delay. After 3s they leave.
|
|
100
|
-
|
|
101
|
-
**Patterns derived**:
|
|
102
|
-
|
|
103
|
-
| Pattern | How it Feels Faster |
|
|
104
|
-
|---------|---------------------|
|
|
105
|
-
| Optimistic UI | Show result before server confirms |
|
|
106
|
-
| Skeleton Loading | Show shape, brain fills in details |
|
|
107
|
-
| Lazy Loading | Load what's visible first |
|
|
108
|
-
| Prefetching | Load before user needs it |
|
|
109
|
-
| Debouncing | Don't overwork on rapid input |
|
|
110
|
-
| Code Splitting | Smaller initial bundle |
|
|
111
|
-
| Instant Navigation | Client-side routing |
|
|
112
|
-
|
|
113
|
-
**Recognition signals**:
|
|
114
|
-
- User clicks, waits, then sees change
|
|
115
|
-
- Full page reload on navigation
|
|
116
|
-
- Large bundle size, slow initial load
|
|
117
|
-
- Images load all at once, page jumps
|
|
118
|
-
- Search triggers on every keystroke
|
|
119
|
-
|
|
120
|
-
**Optimistic UI example**:
|
|
121
|
-
```javascript
|
|
122
|
-
// BAD: Wait for server
|
|
123
|
-
async function toggleLike(post) {
|
|
124
|
-
const result = await api.likePost(post.id) // User waits 500ms
|
|
125
|
-
post.liked = result.liked
|
|
126
|
-
post.likeCount = result.likeCount
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// GOOD: Optimistic update
|
|
130
|
-
async function toggleLike(post) {
|
|
131
|
-
// Immediately update UI (feels instant)
|
|
132
|
-
const previousState = { liked: post.liked, count: post.likeCount }
|
|
133
|
-
post.liked = !post.liked
|
|
134
|
-
post.likeCount += post.liked ? 1 : -1
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
await api.likePost(post.id)
|
|
138
|
-
} catch (error) {
|
|
139
|
-
// Rollback if server fails
|
|
140
|
-
post.liked = previousState.liked
|
|
141
|
-
post.likeCount = previousState.count
|
|
142
|
-
toast.error('Could not update like')
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
**Loading priority**:
|
|
148
|
-
```
|
|
149
|
-
1. Critical content (above fold) → Load immediately
|
|
150
|
-
2. Interactive elements → Load immediately
|
|
151
|
-
3. Below-fold content → Lazy load on scroll
|
|
152
|
-
4. Images → Lazy load + placeholder
|
|
153
|
-
5. Analytics, tracking → Load after page ready
|
|
154
|
-
6. Non-essential features → Load on interaction
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
---
|
|
158
|
-
|
|
159
|
-
### 3. Stay Consistent
|
|
160
|
-
|
|
161
|
-
**Law**: Same action should always produce same result. Match existing patterns.
|
|
162
|
-
|
|
163
|
-
**Why**: Consistency builds trust. Inconsistency confuses and frustrates.
|
|
164
|
-
|
|
165
|
-
**Patterns derived**:
|
|
166
|
-
|
|
167
|
-
| Pattern | What it Standardizes |
|
|
168
|
-
|---------|---------------------|
|
|
169
|
-
| Design System | Visual appearance |
|
|
170
|
-
| Component Library | UI behavior |
|
|
171
|
-
| Naming Conventions | Code readability |
|
|
172
|
-
| Layout Templates | Page structure |
|
|
173
|
-
| Form Patterns | Input behavior |
|
|
174
|
-
| Navigation Patterns | Movement through app |
|
|
175
|
-
|
|
176
|
-
**Recognition signals**:
|
|
177
|
-
- Buttons look different on different pages
|
|
178
|
-
- Same action has different animations
|
|
179
|
-
- Forms behave differently
|
|
180
|
-
- Different naming styles in codebase
|
|
181
|
-
- New code doesn't match existing patterns
|
|
182
|
-
|
|
183
|
-
**Consistency checklist**:
|
|
184
|
-
```
|
|
185
|
-
Before writing new code, check existing codebase for:
|
|
186
|
-
|
|
187
|
-
NAMING:
|
|
188
|
-
□ How are components named? (PascalCase, kebab-case?)
|
|
189
|
-
□ How are files named? (index.vue, ComponentName.vue?)
|
|
190
|
-
□ How are props named? (isOpen, open, opened?)
|
|
191
|
-
□ How are events named? (onClose, handleClose, close?)
|
|
192
|
-
□ How are functions named? (getData, fetchData, loadData?)
|
|
193
|
-
|
|
194
|
-
STRUCTURE:
|
|
195
|
-
□ Where do components live? (components/, shared/?)
|
|
196
|
-
□ How is state managed? (Pinia, composables, local?)
|
|
197
|
-
□ How are forms handled? (controlled, libraries?)
|
|
198
|
-
□ How is API data fetched? (useFetch, custom hooks?)
|
|
199
|
-
|
|
200
|
-
STYLE:
|
|
201
|
-
□ What CSS approach? (Tailwind, modules, styled?)
|
|
202
|
-
□ What design tokens exist? (colors, spacing, shadows?)
|
|
203
|
-
□ How are responsive breakpoints handled?
|
|
204
|
-
|
|
205
|
-
BEHAVIOR:
|
|
206
|
-
□ How do modals open/close?
|
|
207
|
-
□ How is form validation shown?
|
|
208
|
-
□ How are notifications displayed?
|
|
209
|
-
□ How do lists handle empty/loading states?
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
**Match existing pattern example**:
|
|
213
|
-
```javascript
|
|
214
|
-
// EXISTING CODE uses this pattern:
|
|
215
|
-
const { data: users, pending, error } = await useFetch('/api/users')
|
|
216
|
-
|
|
217
|
-
// NEW CODE should match:
|
|
218
|
-
// GOOD - matches existing
|
|
219
|
-
const { data: products, pending, error } = await useFetch('/api/products')
|
|
220
|
-
|
|
221
|
-
// BAD - different pattern
|
|
222
|
-
const products = ref([])
|
|
223
|
-
const loading = ref(true)
|
|
224
|
-
onMounted(async () => {
|
|
225
|
-
loading.value = true
|
|
226
|
-
products.value = await fetch('/api/products').then(r => r.json())
|
|
227
|
-
loading.value = false
|
|
228
|
-
})
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
---
|
|
232
|
-
|
|
233
|
-
### 4. Work for Everyone
|
|
234
|
-
|
|
235
|
-
**Law**: The interface must work for all users, not just ideal conditions.
|
|
236
|
-
|
|
237
|
-
**Why**: Users have disabilities, slow connections, old devices, different languages.
|
|
238
|
-
|
|
239
|
-
**Patterns derived**:
|
|
240
|
-
|
|
241
|
-
| Pattern | Who it Helps |
|
|
242
|
-
|---------|-------------|
|
|
243
|
-
| Semantic HTML | Screen readers, SEO |
|
|
244
|
-
| Keyboard Navigation | Motor disabilities, power users |
|
|
245
|
-
| Color Contrast | Visual impairments |
|
|
246
|
-
| Responsive Design | Mobile users |
|
|
247
|
-
| Offline Support | Poor connections |
|
|
248
|
-
| Error Recovery | All users |
|
|
249
|
-
| Internationalization | Non-English speakers |
|
|
250
|
-
|
|
251
|
-
**Recognition signals**:
|
|
252
|
-
- Can't use with keyboard only
|
|
253
|
-
- No alt text on images
|
|
254
|
-
- Low color contrast
|
|
255
|
-
- Breaks on mobile
|
|
256
|
-
- Crashes on slow connection
|
|
257
|
-
- No error recovery
|
|
258
|
-
|
|
259
|
-
**Accessibility minimum**:
|
|
260
|
-
```html
|
|
261
|
-
<!-- Semantic HTML first -->
|
|
262
|
-
<nav> not <div class="nav">
|
|
263
|
-
<button> not <div onclick>
|
|
264
|
-
<main> not <div class="main">
|
|
265
|
-
<h1>, <h2>, <h3> in order
|
|
266
|
-
|
|
267
|
-
<!-- Images -->
|
|
268
|
-
<img src="..." alt="Description of what image shows" />
|
|
269
|
-
<img src="decorative.png" alt="" /> <!-- Empty alt for decorative -->
|
|
270
|
-
|
|
271
|
-
<!-- Interactive elements -->
|
|
272
|
-
<button aria-label="Close dialog">×</button>
|
|
273
|
-
<input aria-describedby="help-text" />
|
|
274
|
-
<div id="help-text">Password must be 8+ characters</div>
|
|
275
|
-
|
|
276
|
-
<!-- Focus management -->
|
|
277
|
-
<!-- When modal opens: focus first element -->
|
|
278
|
-
<!-- When modal closes: focus trigger element -->
|
|
279
|
-
|
|
280
|
-
<!-- Keyboard -->
|
|
281
|
-
<!-- All interactive elements reachable via Tab -->
|
|
282
|
-
<!-- Enter/Space activates buttons -->
|
|
283
|
-
<!-- Escape closes modals/dropdowns -->
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
**Responsive approach**:
|
|
287
|
-
```
|
|
288
|
-
MOBILE FIRST:
|
|
289
|
-
1. Design for smallest screen first
|
|
290
|
-
2. Add complexity for larger screens
|
|
291
|
-
3. Test at: 320px, 768px, 1024px, 1440px
|
|
292
|
-
|
|
293
|
-
CSS ORDER:
|
|
294
|
-
.component {
|
|
295
|
-
/* Mobile styles (default) */
|
|
296
|
-
padding: 1rem;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
@media (min-width: 768px) {
|
|
300
|
-
/* Tablet and up */
|
|
301
|
-
padding: 2rem;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
@media (min-width: 1024px) {
|
|
305
|
-
/* Desktop and up */
|
|
306
|
-
padding: 3rem;
|
|
307
|
-
}
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
---
|
|
311
|
-
|
|
312
|
-
### 5. Single Source of Truth
|
|
313
|
-
|
|
314
|
-
**Law**: Each piece of state should live in ONE place.
|
|
315
|
-
|
|
316
|
-
**Why**: Duplicate state gets out of sync. Bugs appear. Debugging is nightmare.
|
|
317
|
-
|
|
318
|
-
**Patterns derived**:
|
|
319
|
-
|
|
320
|
-
| Pattern | What it Centralizes |
|
|
321
|
-
|---------|---------------------|
|
|
322
|
-
| State Management (Pinia) | Global app state |
|
|
323
|
-
| Composables | Shared logic |
|
|
324
|
-
| Controlled Components | Form input values |
|
|
325
|
-
| URL as State | Navigation/filter state |
|
|
326
|
-
| Props Down, Events Up | Component communication |
|
|
327
|
-
|
|
328
|
-
**Recognition signals**:
|
|
329
|
-
- Same data stored in multiple places
|
|
330
|
-
- Prop drilling through many levels
|
|
331
|
-
- Components directly modifying parent state
|
|
332
|
-
- State out of sync after actions
|
|
333
|
-
- "Why isn't this updating?"
|
|
334
|
-
|
|
335
|
-
**State location guide**:
|
|
336
|
-
```
|
|
337
|
-
WHO NEEDS THIS STATE?
|
|
338
|
-
│
|
|
339
|
-
├─ Just this component → Local state (ref/reactive)
|
|
340
|
-
│
|
|
341
|
-
├─ Parent and children → Props down, events up
|
|
342
|
-
│
|
|
343
|
-
├─ Siblings → Lift state to common parent, or use composable
|
|
344
|
-
│
|
|
345
|
-
├─ Many unrelated components → Global store (Pinia)
|
|
346
|
-
│
|
|
347
|
-
└─ Should survive refresh → URL params or localStorage
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
**Example**:
|
|
351
|
-
```javascript
|
|
352
|
-
// BAD: Duplicate state
|
|
353
|
-
// In Parent:
|
|
354
|
-
const selectedUser = ref(null)
|
|
355
|
-
|
|
356
|
-
// In Child (duplicating):
|
|
357
|
-
const selectedUser = ref(null) // Out of sync risk!
|
|
358
|
-
|
|
359
|
-
// GOOD: Single source
|
|
360
|
-
// In Parent:
|
|
361
|
-
const selectedUser = ref(null)
|
|
362
|
-
|
|
363
|
-
// In Child (receiving):
|
|
364
|
-
const props = defineProps(['selectedUser'])
|
|
365
|
-
const emit = defineEmits(['update:selectedUser'])
|
|
366
|
-
|
|
367
|
-
// Or use store for global state:
|
|
368
|
-
// store/user.js
|
|
369
|
-
export const useUserStore = defineStore('user', () => {
|
|
370
|
-
const selectedUser = ref(null)
|
|
371
|
-
const selectUser = (user) => selectedUser.value = user
|
|
372
|
-
return { selectedUser, selectUser }
|
|
373
|
-
})
|
|
374
|
-
|
|
375
|
-
// Any component:
|
|
376
|
-
const userStore = useUserStore()
|
|
377
|
-
userStore.selectUser(user)
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
---
|
|
381
|
-
|
|
382
|
-
### 6. Minimize Complexity
|
|
383
|
-
|
|
384
|
-
**Law**: Simple is better than clever. Obvious is better than magical.
|
|
385
|
-
|
|
386
|
-
**Why**: Others (and future you) must understand and modify this code.
|
|
387
|
-
|
|
388
|
-
**Patterns derived**:
|
|
389
|
-
|
|
390
|
-
| Pattern | How it Simplifies |
|
|
391
|
-
|---------|-------------------|
|
|
392
|
-
| Small Components | Each does one thing |
|
|
393
|
-
| Composition | Combine simple pieces |
|
|
394
|
-
| Explicit Props | Clear interface |
|
|
395
|
-
| Flat State | Avoid deep nesting |
|
|
396
|
-
| Co-location | Related code together |
|
|
397
|
-
|
|
398
|
-
**Recognition signals**:
|
|
399
|
-
- Component file > 300 lines
|
|
400
|
-
- Props > 10 items
|
|
401
|
-
- Deeply nested state (a.b.c.d.e)
|
|
402
|
-
- "Magic" that's hard to trace
|
|
403
|
-
- Heavy abstraction for simple tasks
|
|
404
|
-
|
|
405
|
-
**Component size guide**:
|
|
406
|
-
```
|
|
407
|
-
IDEAL COMPONENT:
|
|
408
|
-
- One clear responsibility
|
|
409
|
-
- < 200 lines (template + script)
|
|
410
|
-
- < 10 props
|
|
411
|
-
- Can describe in one sentence
|
|
412
|
-
|
|
413
|
-
TOO BIG IF:
|
|
414
|
-
- Multiple responsibilities
|
|
415
|
-
- Lots of conditional rendering
|
|
416
|
-
- Can't understand in 30 seconds
|
|
417
|
-
- Needs comments to explain
|
|
418
|
-
|
|
419
|
-
SPLIT INTO:
|
|
420
|
-
- Container (data/logic) + Presenter (UI)
|
|
421
|
-
- Multiple smaller components
|
|
422
|
-
- Composable for shared logic
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
**Composition over complexity**:
|
|
426
|
-
```vue
|
|
427
|
-
<!-- BAD: One complex component -->
|
|
428
|
-
<template>
|
|
429
|
-
<div>
|
|
430
|
-
<div v-if="mode === 'edit'">
|
|
431
|
-
<!-- 100 lines of edit form -->
|
|
432
|
-
</div>
|
|
433
|
-
<div v-else-if="mode === 'view'">
|
|
434
|
-
<!-- 100 lines of view display -->
|
|
435
|
-
</div>
|
|
436
|
-
<div v-else-if="mode === 'loading'">
|
|
437
|
-
<!-- 50 lines of skeleton -->
|
|
438
|
-
</div>
|
|
439
|
-
</div>
|
|
440
|
-
</template>
|
|
441
|
-
|
|
442
|
-
<!-- GOOD: Composed simple components -->
|
|
443
|
-
<template>
|
|
444
|
-
<ProductSkeleton v-if="pending" />
|
|
445
|
-
<ProductEditForm v-else-if="mode === 'edit'" :product="data" @save="save" />
|
|
446
|
-
<ProductView v-else :product="data" @edit="startEdit" />
|
|
447
|
-
</template>
|
|
448
|
-
```
|
|
449
|
-
|
|
450
|
-
---
|
|
451
|
-
|
|
452
|
-
## Pattern Decision Matrix
|
|
453
|
-
|
|
454
|
-
Quick reference for choosing patterns:
|
|
455
|
-
|
|
456
|
-
| Situation | Apply These Patterns |
|
|
457
|
-
|-----------|---------------------|
|
|
458
|
-
| Data from API | Loading + Error + Empty states |
|
|
459
|
-
| User action → Server | Optimistic UI or Loading feedback |
|
|
460
|
-
| Form with many fields | Form library + Validation schema |
|
|
461
|
-
| Large list (100+ items) | Virtualization |
|
|
462
|
-
| Heavy component rarely used | Lazy loading |
|
|
463
|
-
| Multiple components need same data | State management (Pinia) |
|
|
464
|
-
| Complex UI with many states | State machine |
|
|
465
|
-
| Need to match existing code | Study patterns first, match exactly |
|
|
466
|
-
| Reusable UI element | Extract to component |
|
|
467
|
-
| Reusable logic | Extract to composable |
|
|
468
|
-
|
|
469
|
-
---
|
|
470
|
-
|
|
471
|
-
## Component Structure
|
|
472
|
-
|
|
473
|
-
Standard component organization:
|
|
474
|
-
|
|
475
|
-
```vue
|
|
476
|
-
<script setup lang="ts">
|
|
477
|
-
// 1. Imports
|
|
478
|
-
import { ref, computed, watch } from 'vue'
|
|
479
|
-
import { useUserStore } from '@/stores/user'
|
|
480
|
-
import ChildComponent from './ChildComponent.vue'
|
|
481
|
-
|
|
482
|
-
// 2. Props & Emits
|
|
483
|
-
const props = defineProps<{
|
|
484
|
-
title: string
|
|
485
|
-
items: Item[]
|
|
486
|
-
}>()
|
|
487
|
-
|
|
488
|
-
const emit = defineEmits<{
|
|
489
|
-
select: [item: Item]
|
|
490
|
-
close: []
|
|
491
|
-
}>()
|
|
492
|
-
|
|
493
|
-
// 3. Composables & Stores
|
|
494
|
-
const userStore = useUserStore()
|
|
495
|
-
const { data, pending, error } = await useFetch('/api/data')
|
|
496
|
-
|
|
497
|
-
// 4. Local State
|
|
498
|
-
const isOpen = ref(false)
|
|
499
|
-
const searchQuery = ref('')
|
|
500
|
-
|
|
501
|
-
// 5. Computed
|
|
502
|
-
const filteredItems = computed(() =>
|
|
503
|
-
props.items.filter(item =>
|
|
504
|
-
item.name.includes(searchQuery.value)
|
|
505
|
-
)
|
|
506
|
-
)
|
|
507
|
-
|
|
508
|
-
// 6. Methods
|
|
509
|
-
function handleSelect(item: Item) {
|
|
510
|
-
emit('select', item)
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
// 7. Lifecycle & Watchers
|
|
514
|
-
watch(searchQuery, (newValue) => {
|
|
515
|
-
// React to changes
|
|
516
|
-
})
|
|
517
|
-
</script>
|
|
518
|
-
|
|
519
|
-
<template>
|
|
520
|
-
<!-- Single root with clear structure -->
|
|
521
|
-
</template>
|
|
522
|
-
|
|
523
|
-
<style scoped>
|
|
524
|
-
/* Component-specific styles */
|
|
525
|
-
</style>
|
|
526
|
-
```
|
|
527
|
-
|
|
528
|
-
---
|
|
529
|
-
|
|
530
|
-
## UI States Template
|
|
531
|
-
|
|
532
|
-
Every data component should handle:
|
|
533
|
-
|
|
534
|
-
```vue
|
|
535
|
-
<script setup>
|
|
536
|
-
const { data, pending, error, refresh } = await useFetch('/api/resource')
|
|
537
|
-
</script>
|
|
538
|
-
|
|
539
|
-
<template>
|
|
540
|
-
<div class="resource-container">
|
|
541
|
-
<!-- Loading -->
|
|
542
|
-
<ResourceSkeleton v-if="pending" />
|
|
543
|
-
|
|
544
|
-
<!-- Error -->
|
|
545
|
-
<ErrorState
|
|
546
|
-
v-else-if="error"
|
|
547
|
-
:message="error.message"
|
|
548
|
-
@retry="refresh"
|
|
549
|
-
/>
|
|
550
|
-
|
|
551
|
-
<!-- Empty -->
|
|
552
|
-
<EmptyState
|
|
553
|
-
v-else-if="!data || data.length === 0"
|
|
554
|
-
title="No resources found"
|
|
555
|
-
description="Create your first resource to get started"
|
|
556
|
-
>
|
|
557
|
-
<Button @click="openCreate">Create Resource</Button>
|
|
558
|
-
</EmptyState>
|
|
559
|
-
|
|
560
|
-
<!-- Success -->
|
|
561
|
-
<ResourceList v-else :items="data" />
|
|
562
|
-
</div>
|
|
563
|
-
</template>
|
|
564
|
-
```
|
|
565
|
-
|
|
566
|
-
---
|
|
567
|
-
|
|
568
|
-
## Checklist Before Implementation
|
|
569
|
-
|
|
570
|
-
- [ ] Which principle applies to this feature?
|
|
571
|
-
- [ ] Does this match existing code patterns?
|
|
572
|
-
- [ ] What are all the UI states? (loading, error, empty, success)
|
|
573
|
-
- [ ] How can this feel instant?
|
|
574
|
-
- [ ] Can it be used with keyboard only?
|
|
575
|
-
- [ ] Does it work on mobile?
|
|
576
|
-
- [ ] Where should state live?
|
|
577
|
-
- [ ] Is this component doing too much?
|