@codihaus/claude-skills 1.4.0 → 1.5.1
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codihaus/claude-skills",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"description": "Claude Code skills for software development workflow",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,10 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"build": "node scripts/build.js",
|
|
11
|
-
"
|
|
11
|
+
"release": "node scripts/publish.js",
|
|
12
|
+
"release:patch": "node scripts/publish.js patch",
|
|
13
|
+
"release:minor": "node scripts/publish.js minor",
|
|
14
|
+
"release:major": "node scripts/publish.js major",
|
|
12
15
|
"test": "node bin/cli.js --help"
|
|
13
16
|
},
|
|
14
17
|
"type": "module",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: dev-coding-backend
|
|
3
3
|
description: Backend implementation patterns and workflows
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.1.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# /dev-coding-backend - Backend Implementation
|
|
@@ -11,6 +11,20 @@ version: 2.0.0
|
|
|
11
11
|
|
|
12
12
|
Backend-specific workflow for API, schema, and data layer implementation.
|
|
13
13
|
|
|
14
|
+
## Fundamentals (MUST READ FIRST)
|
|
15
|
+
|
|
16
|
+
**Before implementing, understand the principles:**
|
|
17
|
+
|
|
18
|
+
→ Read `references/fundamentals.md`
|
|
19
|
+
|
|
20
|
+
This covers:
|
|
21
|
+
- **Core Mindset**: "Guardian of truth, trust nothing, enforce rules"
|
|
22
|
+
- **7 Principles**: Separation of Concerns, Single Source of Truth, Don't Repeat Work, Don't Make Users Wait, Don't Trust Anyone, Plan for Failure, Stateless Design
|
|
23
|
+
- **Pattern Recognition**: When to use Service Layer, Repository, Queue, Cache, Circuit Breaker, etc.
|
|
24
|
+
- **Architecture Layers**: Route → Service → Domain → Repository → Infrastructure
|
|
25
|
+
|
|
26
|
+
**Key Decision**: For each piece of code, ask "which principle applies?" Then choose the pattern that principle suggests.
|
|
27
|
+
|
|
14
28
|
## Knowledge Loading (CRITICAL)
|
|
15
29
|
|
|
16
30
|
Before implementing, load relevant knowledge:
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
# Backend Fundamentals
|
|
2
|
+
|
|
3
|
+
The principles and patterns that enable scalable, maintainable backend systems.
|
|
4
|
+
|
|
5
|
+
## Core Mindset
|
|
6
|
+
|
|
7
|
+
**"I am the guardian of truth. I trust nothing. I enforce rules. I must be reliable."**
|
|
8
|
+
|
|
9
|
+
The backend is the last line of defense. It:
|
|
10
|
+
- Protects data integrity
|
|
11
|
+
- Enforces business rules
|
|
12
|
+
- Handles failures gracefully
|
|
13
|
+
- Scales under load
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## The Principles
|
|
18
|
+
|
|
19
|
+
Principles are the fundamental laws. Patterns are solutions derived from them.
|
|
20
|
+
|
|
21
|
+
### 1. Separation of Concerns
|
|
22
|
+
|
|
23
|
+
**Law**: Each piece of code should have ONE responsibility.
|
|
24
|
+
|
|
25
|
+
**Why**: When things are mixed, changing one breaks another. Testing becomes impossible. New developers get lost.
|
|
26
|
+
|
|
27
|
+
**Patterns derived**:
|
|
28
|
+
|
|
29
|
+
| Pattern | What it Separates |
|
|
30
|
+
|---------|-------------------|
|
|
31
|
+
| Service Layer | Business logic FROM HTTP handling |
|
|
32
|
+
| Repository | Data access FROM business logic |
|
|
33
|
+
| Controller/Route | Request parsing FROM processing |
|
|
34
|
+
| Middleware | Cross-cutting concerns FROM main logic |
|
|
35
|
+
| Event-Driven | "What happened" FROM "What to do about it" |
|
|
36
|
+
|
|
37
|
+
**Recognition signals**:
|
|
38
|
+
- Function does more than one thing
|
|
39
|
+
- Can't test without real database/HTTP
|
|
40
|
+
- Changing one feature breaks another
|
|
41
|
+
- File is > 300 lines
|
|
42
|
+
|
|
43
|
+
**Structure example**:
|
|
44
|
+
```
|
|
45
|
+
# BAD: Mixed concerns
|
|
46
|
+
route('/orders', async (req) => {
|
|
47
|
+
validate(req.body) # validation
|
|
48
|
+
const user = await db.users.find(req.userId) # data access
|
|
49
|
+
if (user.balance < req.body.total) throw Error # business rule
|
|
50
|
+
await db.orders.create(...) # data access
|
|
51
|
+
await sendEmail(user.email) # side effect
|
|
52
|
+
return order
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
# GOOD: Separated concerns
|
|
56
|
+
route('/orders', OrderController.create)
|
|
57
|
+
|
|
58
|
+
OrderController.create(req) {
|
|
59
|
+
const dto = OrderValidator.validate(req.body) # validation layer
|
|
60
|
+
const order = await OrderService.create(dto, req.userId) # service layer
|
|
61
|
+
return OrderPresenter.format(order) # presentation layer
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
OrderService.create(dto, userId) {
|
|
65
|
+
const user = await UserRepository.findById(userId) # repository
|
|
66
|
+
OrderRules.validateCanOrder(user, dto) # business rules
|
|
67
|
+
const order = await OrderRepository.create(dto)
|
|
68
|
+
await EventBus.emit('order.created', order) # events for side effects
|
|
69
|
+
return order
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
### 2. Single Source of Truth
|
|
76
|
+
|
|
77
|
+
**Law**: Each piece of data or rule should exist in ONE place only.
|
|
78
|
+
|
|
79
|
+
**Why**: Duplicated data gets out of sync. Duplicated rules diverge over time.
|
|
80
|
+
|
|
81
|
+
**Patterns derived**:
|
|
82
|
+
|
|
83
|
+
| Pattern | What it Centralizes |
|
|
84
|
+
|---------|---------------------|
|
|
85
|
+
| Repository | All access to a data type |
|
|
86
|
+
| Domain Model | Business rules for an entity |
|
|
87
|
+
| Configuration | Environment-specific values |
|
|
88
|
+
| Constants/Enums | Magic values and statuses |
|
|
89
|
+
| Event Store | Historical record of changes |
|
|
90
|
+
|
|
91
|
+
**Recognition signals**:
|
|
92
|
+
- Same validation in multiple places
|
|
93
|
+
- Same query written twice
|
|
94
|
+
- Hardcoded values scattered around
|
|
95
|
+
- "Which one is correct?" questions
|
|
96
|
+
|
|
97
|
+
**Structure example**:
|
|
98
|
+
```
|
|
99
|
+
# BAD: Rules in multiple places
|
|
100
|
+
# In controller:
|
|
101
|
+
if (order.status !== 'pending') throw Error
|
|
102
|
+
|
|
103
|
+
# In service:
|
|
104
|
+
if (order.status !== 'pending') throw Error
|
|
105
|
+
|
|
106
|
+
# In another service:
|
|
107
|
+
if (order.status != 'pending' && order.status != 'draft') throw Error # diverged!
|
|
108
|
+
|
|
109
|
+
# GOOD: Single source
|
|
110
|
+
class Order {
|
|
111
|
+
canBeModified() {
|
|
112
|
+
return ['pending', 'draft'].includes(this.status)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# Everywhere else:
|
|
117
|
+
if (!order.canBeModified()) throw Error
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
### 3. Don't Repeat Work
|
|
123
|
+
|
|
124
|
+
**Law**: If the same work is done repeatedly with the same result, do it once and remember.
|
|
125
|
+
|
|
126
|
+
**Why**: Wasted resources, slower response, higher costs, database overload.
|
|
127
|
+
|
|
128
|
+
**Patterns derived**:
|
|
129
|
+
|
|
130
|
+
| Pattern | What it Remembers |
|
|
131
|
+
|---------|-------------------|
|
|
132
|
+
| Caching (Redis/Memory) | Expensive query results |
|
|
133
|
+
| Memoization | Function computation |
|
|
134
|
+
| Database Indexes | Query paths |
|
|
135
|
+
| CDN | Static assets |
|
|
136
|
+
| Materialized Views | Pre-computed aggregations |
|
|
137
|
+
|
|
138
|
+
**Recognition signals**:
|
|
139
|
+
- Same query runs multiple times per request
|
|
140
|
+
- Same computation on same input
|
|
141
|
+
- Dashboard aggregations are slow
|
|
142
|
+
- Database CPU is high
|
|
143
|
+
|
|
144
|
+
**Decision guide**:
|
|
145
|
+
```
|
|
146
|
+
How often does data change?
|
|
147
|
+
├── Never (static) → CDN, aggressive cache
|
|
148
|
+
├── Rarely (config) → Long TTL cache (hours)
|
|
149
|
+
├── Sometimes (user profile) → Medium TTL (minutes)
|
|
150
|
+
├── Often (feed) → Short TTL (seconds) or real-time
|
|
151
|
+
└── Always (balance) → No cache, or cache with invalidation
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
### 4. Don't Make Users Wait
|
|
157
|
+
|
|
158
|
+
**Law**: If something takes long and user doesn't need immediate result, do it later.
|
|
159
|
+
|
|
160
|
+
**Why**: Users leave, connections timeout, server resources blocked.
|
|
161
|
+
|
|
162
|
+
**Patterns derived**:
|
|
163
|
+
|
|
164
|
+
| Pattern | Use When |
|
|
165
|
+
|---------|----------|
|
|
166
|
+
| Message Queue | Task takes > 1-2 seconds |
|
|
167
|
+
| Background Jobs | Scheduled or deferred work |
|
|
168
|
+
| Webhooks | Notify instead of poll |
|
|
169
|
+
| Streaming | Large data transfer |
|
|
170
|
+
| Pagination | Large result sets |
|
|
171
|
+
|
|
172
|
+
**Recognition signals**:
|
|
173
|
+
- API timeout errors
|
|
174
|
+
- User waits > 3 seconds
|
|
175
|
+
- Sending emails in request
|
|
176
|
+
- Processing files in request
|
|
177
|
+
- Generating reports in request
|
|
178
|
+
|
|
179
|
+
**Structure example**:
|
|
180
|
+
```
|
|
181
|
+
# BAD: User waits for everything
|
|
182
|
+
route('/orders', async (req) => {
|
|
183
|
+
const order = await createOrder(req.body)
|
|
184
|
+
await generateInvoicePDF(order) # 3 seconds
|
|
185
|
+
await sendEmail(order) # 2 seconds
|
|
186
|
+
await notifyWarehouse(order) # 1 second
|
|
187
|
+
await updateAnalytics(order) # 500ms
|
|
188
|
+
return order # User waited 6.5 seconds!
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
# GOOD: Return fast, process later
|
|
192
|
+
route('/orders', async (req) => {
|
|
193
|
+
const order = await createOrder(req.body)
|
|
194
|
+
await queue.add('order.process', { orderId: order.id })
|
|
195
|
+
return order # User waited 200ms
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
# Background worker handles the rest
|
|
199
|
+
worker.process('order.process', async (job) => {
|
|
200
|
+
const order = await OrderRepository.find(job.orderId)
|
|
201
|
+
await generateInvoicePDF(order)
|
|
202
|
+
await sendEmail(order)
|
|
203
|
+
await notifyWarehouse(order)
|
|
204
|
+
await updateAnalytics(order)
|
|
205
|
+
})
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
### 5. Don't Trust Anyone
|
|
211
|
+
|
|
212
|
+
**Law**: Every input is potentially malicious or malformed. Validate at boundaries.
|
|
213
|
+
|
|
214
|
+
**Why**: Security breaches, data corruption, system crashes.
|
|
215
|
+
|
|
216
|
+
**Patterns derived**:
|
|
217
|
+
|
|
218
|
+
| Pattern | What it Protects |
|
|
219
|
+
|---------|------------------|
|
|
220
|
+
| Input Validation | Data integrity |
|
|
221
|
+
| Authentication | Identity verification |
|
|
222
|
+
| Authorization | Access control |
|
|
223
|
+
| Rate Limiting | Resource protection |
|
|
224
|
+
| Sanitization | Injection prevention |
|
|
225
|
+
|
|
226
|
+
**Recognition signals**:
|
|
227
|
+
- User input used directly in queries
|
|
228
|
+
- No authentication on endpoints
|
|
229
|
+
- Missing role checks
|
|
230
|
+
- No request size limits
|
|
231
|
+
- Error messages expose internals
|
|
232
|
+
|
|
233
|
+
**Validation layers**:
|
|
234
|
+
```
|
|
235
|
+
Request arrives
|
|
236
|
+
↓
|
|
237
|
+
[1. Rate Limit] → Too many? Reject 429
|
|
238
|
+
↓
|
|
239
|
+
[2. Authentication] → Who is this? Reject 401 if unknown
|
|
240
|
+
↓
|
|
241
|
+
[3. Authorization] → Can they do this? Reject 403 if not
|
|
242
|
+
↓
|
|
243
|
+
[4. Input Validation] → Is data valid? Reject 400 if not
|
|
244
|
+
↓
|
|
245
|
+
[5. Business Rules] → Is action allowed? Reject 422 if not
|
|
246
|
+
↓
|
|
247
|
+
Process request
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
### 6. Plan for Failure
|
|
253
|
+
|
|
254
|
+
**Law**: Everything will fail eventually. Design for it.
|
|
255
|
+
|
|
256
|
+
**Why**: Network fails, services go down, databases crash, disks fill up.
|
|
257
|
+
|
|
258
|
+
**Patterns derived**:
|
|
259
|
+
|
|
260
|
+
| Pattern | Failure it Handles |
|
|
261
|
+
|---------|-------------------|
|
|
262
|
+
| Circuit Breaker | External service down |
|
|
263
|
+
| Retry with Backoff | Temporary failures |
|
|
264
|
+
| Timeout | Hung connections |
|
|
265
|
+
| Fallback | Degraded operation |
|
|
266
|
+
| Dead Letter Queue | Unprocessable messages |
|
|
267
|
+
| Idempotency | Duplicate requests |
|
|
268
|
+
|
|
269
|
+
**Recognition signals**:
|
|
270
|
+
- No try/catch around external calls
|
|
271
|
+
- No timeout on HTTP requests
|
|
272
|
+
- Retrying without backoff (hammering)
|
|
273
|
+
- No fallback when service unavailable
|
|
274
|
+
- Duplicate orders when user clicks twice
|
|
275
|
+
|
|
276
|
+
**Structure example**:
|
|
277
|
+
```
|
|
278
|
+
# BAD: Assumes success
|
|
279
|
+
const payment = await paymentService.charge(order)
|
|
280
|
+
|
|
281
|
+
# GOOD: Plans for failure
|
|
282
|
+
const payment = await circuitBreaker.call(
|
|
283
|
+
() => paymentService.charge(order),
|
|
284
|
+
{
|
|
285
|
+
timeout: 5000,
|
|
286
|
+
retry: { attempts: 3, backoff: 'exponential' },
|
|
287
|
+
fallback: () => {
|
|
288
|
+
queue.add('payment.retry', { orderId: order.id })
|
|
289
|
+
return { status: 'pending', message: 'Processing...' }
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
)
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**Idempotency example**:
|
|
296
|
+
```
|
|
297
|
+
# Problem: User clicks "Pay" twice, charged twice
|
|
298
|
+
|
|
299
|
+
# Solution: Idempotency key
|
|
300
|
+
route('/payments', async (req) => {
|
|
301
|
+
const existingPayment = await PaymentRepository.findByIdempotencyKey(
|
|
302
|
+
req.headers['idempotency-key']
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
if (existingPayment) {
|
|
306
|
+
return existingPayment # Return same result, don't process again
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const payment = await processPayment(req.body)
|
|
310
|
+
payment.idempotencyKey = req.headers['idempotency-key']
|
|
311
|
+
await PaymentRepository.save(payment)
|
|
312
|
+
return payment
|
|
313
|
+
})
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
### 7. Stateless Design
|
|
319
|
+
|
|
320
|
+
**Law**: Server should not remember anything between requests (store state externally).
|
|
321
|
+
|
|
322
|
+
**Why**: Enables horizontal scaling, survives server restarts, simplifies deployment.
|
|
323
|
+
|
|
324
|
+
**Patterns derived**:
|
|
325
|
+
|
|
326
|
+
| Pattern | State it Externalizes |
|
|
327
|
+
|---------|----------------------|
|
|
328
|
+
| JWT Tokens | Session data in token |
|
|
329
|
+
| External Session Store | Session data in Redis |
|
|
330
|
+
| Database | All persistent data |
|
|
331
|
+
| Shared Cache | Computed/temporary data |
|
|
332
|
+
| Object Storage | Files and uploads |
|
|
333
|
+
|
|
334
|
+
**Recognition signals**:
|
|
335
|
+
- Using server memory for sessions
|
|
336
|
+
- Files stored on local disk
|
|
337
|
+
- In-memory caches that miss after deploy
|
|
338
|
+
- "Works on one server, fails with two"
|
|
339
|
+
|
|
340
|
+
**Structure example**:
|
|
341
|
+
```
|
|
342
|
+
# BAD: Stateful (breaks with multiple servers)
|
|
343
|
+
const sessions = {} # In memory!
|
|
344
|
+
app.post('/login', (req) => {
|
|
345
|
+
sessions[userId] = { user, timestamp }
|
|
346
|
+
})
|
|
347
|
+
app.get('/me', (req) => {
|
|
348
|
+
return sessions[req.userId] # Other server doesn't have this!
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
# GOOD: Stateless (works with any number of servers)
|
|
352
|
+
app.post('/login', async (req) => {
|
|
353
|
+
const token = jwt.sign({ userId, role }) # State in token
|
|
354
|
+
await redis.set(`session:${userId}`, { lastLogin }) # State in Redis
|
|
355
|
+
return { token }
|
|
356
|
+
})
|
|
357
|
+
app.get('/me', async (req) => {
|
|
358
|
+
const payload = jwt.verify(req.token) # State from token
|
|
359
|
+
const session = await redis.get(`session:${payload.userId}`) # State from Redis
|
|
360
|
+
return { user: payload, session }
|
|
361
|
+
})
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## Pattern Decision Matrix
|
|
367
|
+
|
|
368
|
+
Quick reference for choosing patterns:
|
|
369
|
+
|
|
370
|
+
| Situation | Apply These Patterns |
|
|
371
|
+
|-----------|---------------------|
|
|
372
|
+
| Code is getting messy, hard to test | Service Layer + Repository + DI |
|
|
373
|
+
| Same logic in multiple places | Extract to Domain Model or Service |
|
|
374
|
+
| API is slow | Caching, Queue, Pagination |
|
|
375
|
+
| High database load | Caching, Indexing, Read Replicas |
|
|
376
|
+
| External service unreliable | Circuit Breaker, Retry, Fallback |
|
|
377
|
+
| Need audit trail | Event Sourcing or Audit Log |
|
|
378
|
+
| Complex workflows | Saga, State Machine |
|
|
379
|
+
| Many services communicating | Event Bus, API Gateway |
|
|
380
|
+
| Users hitting API too hard | Rate Limiting, Throttling |
|
|
381
|
+
| File uploads | Object Storage, Signed URLs |
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## Architecture Layers
|
|
386
|
+
|
|
387
|
+
Standard backend structure:
|
|
388
|
+
|
|
389
|
+
```
|
|
390
|
+
┌─────────────────────────────────────────────┐
|
|
391
|
+
│ HTTP Layer (Routes/Controllers) │
|
|
392
|
+
│ - Parse request │
|
|
393
|
+
│ - Call service │
|
|
394
|
+
│ - Format response │
|
|
395
|
+
├─────────────────────────────────────────────┤
|
|
396
|
+
│ Service Layer │
|
|
397
|
+
│ - Business logic │
|
|
398
|
+
│ - Orchestrate operations │
|
|
399
|
+
│ - Emit events │
|
|
400
|
+
├─────────────────────────────────────────────┤
|
|
401
|
+
│ Domain Layer (Models/Entities) │
|
|
402
|
+
│ - Business rules │
|
|
403
|
+
│ - Validation │
|
|
404
|
+
│ - State transitions │
|
|
405
|
+
├─────────────────────────────────────────────┤
|
|
406
|
+
│ Repository Layer │
|
|
407
|
+
│ - Data access │
|
|
408
|
+
│ - Query building │
|
|
409
|
+
│ - Caching │
|
|
410
|
+
├─────────────────────────────────────────────┤
|
|
411
|
+
│ Infrastructure (Database, Queue, Cache) │
|
|
412
|
+
└─────────────────────────────────────────────┘
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
**Rule**: Upper layers can call lower layers. Never the reverse.
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
## Checklist Before Implementation
|
|
420
|
+
|
|
421
|
+
- [ ] Which principle applies to this feature?
|
|
422
|
+
- [ ] What patterns does that principle suggest?
|
|
423
|
+
- [ ] Is there existing similar code to follow?
|
|
424
|
+
- [ ] Where does validation happen?
|
|
425
|
+
- [ ] What if this fails?
|
|
426
|
+
- [ ] What if 1000 users do this at once?
|
|
427
|
+
- [ ] Does this need to be synchronous?
|
|
428
|
+
- [ ] What state needs to be stored?
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: dev-coding-frontend
|
|
3
3
|
description: Frontend implementation patterns and workflows
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.1.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# /dev-coding-frontend - Frontend Implementation
|
|
@@ -12,6 +12,22 @@ version: 2.0.0
|
|
|
12
12
|
|
|
13
13
|
Frontend-specific workflow for UI components, pages, and client-side logic.
|
|
14
14
|
|
|
15
|
+
## Fundamentals (MUST READ FIRST)
|
|
16
|
+
|
|
17
|
+
**Before implementing, understand the principles:**
|
|
18
|
+
|
|
19
|
+
→ Read `references/fundamentals.md`
|
|
20
|
+
|
|
21
|
+
This covers:
|
|
22
|
+
- **Core Mindset**: "Communicator with humans - clear, responsive, consistent"
|
|
23
|
+
- **6 Principles**: Always Communicate, Feel Instant, Stay Consistent, Work for Everyone, Single Source of Truth, Minimize Complexity
|
|
24
|
+
- **Pattern Recognition**: When to use Loading States, Optimistic UI, State Management, Lazy Loading, etc.
|
|
25
|
+
- **UI States**: Every component must handle loading, error, empty, success
|
|
26
|
+
|
|
27
|
+
**Key Decision**: For each piece of code, ask "which principle applies?" Then choose the pattern that principle suggests.
|
|
28
|
+
|
|
29
|
+
**CRITICAL - Consistency**: Before writing ANY code, check existing codebase for naming conventions, file structure, component patterns, and styling approach. Match them exactly.
|
|
30
|
+
|
|
15
31
|
## Knowledge Loading (CRITICAL)
|
|
16
32
|
|
|
17
33
|
Before implementing, load relevant knowledge:
|
|
@@ -0,0 +1,577 @@
|
|
|
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?
|