@champpaba/claude-agent-kit 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/LICENSE +21 -0
- package/README.md +561 -0
- package/bin/cli.js +61 -0
- package/lib/init.js +52 -0
- package/lib/update.js +73 -0
- package/package.json +47 -0
- package/template/.claude/CHANGELOG-v1.1.1.md +259 -0
- package/template/.claude/CLAUDE.md +329 -0
- package/template/.claude/agents/01-integration.md +797 -0
- package/template/.claude/agents/02-uxui-frontend.md +899 -0
- package/template/.claude/agents/03-test-debug.md +759 -0
- package/template/.claude/agents/04-frontend.md +1099 -0
- package/template/.claude/agents/05-backend.md +1217 -0
- package/template/.claude/agents/06-database.md +969 -0
- package/template/.claude/commands/agentsetup.md +1464 -0
- package/template/.claude/commands/cdev.md +327 -0
- package/template/.claude/commands/csetup.md +447 -0
- package/template/.claude/commands/cstatus.md +60 -0
- package/template/.claude/commands/cview.md +364 -0
- package/template/.claude/commands/psetup.md +101 -0
- package/template/.claude/contexts/design/accessibility.md +611 -0
- package/template/.claude/contexts/design/box-thinking.md +553 -0
- package/template/.claude/contexts/design/color-theory.md +498 -0
- package/template/.claude/contexts/design/index.md +247 -0
- package/template/.claude/contexts/design/layout.md +400 -0
- package/template/.claude/contexts/design/responsive.md +551 -0
- package/template/.claude/contexts/design/shadows.md +522 -0
- package/template/.claude/contexts/design/spacing.md +428 -0
- package/template/.claude/contexts/design/typography.md +465 -0
- package/template/.claude/contexts/domain/README.md +164 -0
- package/template/.claude/contexts/patterns/agent-coordination.md +388 -0
- package/template/.claude/contexts/patterns/agent-discovery.md +182 -0
- package/template/.claude/contexts/patterns/change-workflow.md +538 -0
- package/template/.claude/contexts/patterns/code-standards.md +515 -0
- package/template/.claude/contexts/patterns/development-principles.md +513 -0
- package/template/.claude/contexts/patterns/error-handling.md +478 -0
- package/template/.claude/contexts/patterns/error-recovery.md +365 -0
- package/template/.claude/contexts/patterns/frontend-component-strategy.md +365 -0
- package/template/.claude/contexts/patterns/git-workflow.md +207 -0
- package/template/.claude/contexts/patterns/logging.md +424 -0
- package/template/.claude/contexts/patterns/task-breakdown.md +452 -0
- package/template/.claude/contexts/patterns/task-classification.md +523 -0
- package/template/.claude/contexts/patterns/tdd-classification.md +516 -0
- package/template/.claude/contexts/patterns/testing.md +413 -0
- package/template/.claude/contexts/patterns/ui-component-consistency.md +304 -0
- package/template/.claude/contexts/patterns/validation-framework.md +776 -0
- package/template/.claude/lib/README.md +39 -0
- package/template/.claude/lib/agent-executor.md +258 -0
- package/template/.claude/lib/agent-router.md +572 -0
- package/template/.claude/lib/flags-updater.md +469 -0
- package/template/.claude/lib/tdd-classifier.md +345 -0
- package/template/.claude/lib/validation-gates.md +484 -0
- package/template/.claude/settings.local.json +42 -0
- package/template/.claude/templates/context-template.md +45 -0
- package/template/.claude/templates/flags-template.json +42 -0
- package/template/.claude/templates/phase-templates.json +124 -0
- package/template/.claude/templates/phases-sections/accessibility-test.md +17 -0
- package/template/.claude/templates/phases-sections/api-design.md +37 -0
- package/template/.claude/templates/phases-sections/backend-tests.md +16 -0
- package/template/.claude/templates/phases-sections/backend.md +37 -0
- package/template/.claude/templates/phases-sections/business-logic-validation.md +16 -0
- package/template/.claude/templates/phases-sections/component-tests.md +17 -0
- package/template/.claude/templates/phases-sections/contract-backend.md +16 -0
- package/template/.claude/templates/phases-sections/contract-frontend.md +16 -0
- package/template/.claude/templates/phases-sections/database.md +35 -0
- package/template/.claude/templates/phases-sections/documentation.md +17 -0
- package/template/.claude/templates/phases-sections/e2e-tests.md +16 -0
- package/template/.claude/templates/phases-sections/fix-implementation.md +17 -0
- package/template/.claude/templates/phases-sections/frontend-integration.md +18 -0
- package/template/.claude/templates/phases-sections/frontend-mockup.md +123 -0
- package/template/.claude/templates/phases-sections/manual-flow-test.md +15 -0
- package/template/.claude/templates/phases-sections/manual-ux-test.md +16 -0
- package/template/.claude/templates/phases-sections/refactor-implementation.md +17 -0
- package/template/.claude/templates/phases-sections/refactor.md +16 -0
- package/template/.claude/templates/phases-sections/regression-tests.md +15 -0
- package/template/.claude/templates/phases-sections/report.md +16 -0
- package/template/.claude/templates/phases-sections/responsive-test.md +16 -0
- package/template/.claude/templates/phases-sections/script-implementation.md +43 -0
- package/template/.claude/templates/phases-sections/test-coverage.md +16 -0
- package/template/.claude/templates/phases-sections/user-approval.md +14 -0
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
# Testing Strategy & TDD Methodology
|
|
2
|
+
|
|
3
|
+
**Red-Green-Refactor:** Write test → Watch it fail → Write code → Make it pass → Refactor
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Test-Driven Development (TDD)
|
|
8
|
+
|
|
9
|
+
### Automatic TDD Classification
|
|
10
|
+
|
|
11
|
+
**Orchestrator automatically determines if TDD is required based on task content.**
|
|
12
|
+
|
|
13
|
+
You don't decide. Orchestrator decides. Your job is to **follow the `tdd_required` flag**.
|
|
14
|
+
|
|
15
|
+
### When You Receive a Task
|
|
16
|
+
|
|
17
|
+
**Check the metadata from Orchestrator:**
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"description": "Implement POST /api/auth/login",
|
|
21
|
+
"type": "critical",
|
|
22
|
+
"tdd_required": true,
|
|
23
|
+
"workflow": "red-green-refactor",
|
|
24
|
+
"reason": "API endpoint + authentication logic"
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Classification Rules (Orchestrator Logic)
|
|
29
|
+
|
|
30
|
+
✅ **TDD Required (`tdd_required: true`) for:**
|
|
31
|
+
- API mutations (POST, PUT, PATCH, DELETE)
|
|
32
|
+
- Business logic (calculations, transformations, validation)
|
|
33
|
+
- External service integrations (payment, email, SMS, storage)
|
|
34
|
+
- Data transformations (ETL, serialization, aggregations)
|
|
35
|
+
- Security operations (authentication, authorization, encryption)
|
|
36
|
+
- Complex UI logic (multi-step forms, state machines, accessibility)
|
|
37
|
+
|
|
38
|
+
⚠️ **Test-Alongside OK (`tdd_required: false`) for:**
|
|
39
|
+
- Simple GET endpoints (read-only)
|
|
40
|
+
- Presentational UI components
|
|
41
|
+
- Simple CRUD operations
|
|
42
|
+
- Configuration files
|
|
43
|
+
- Trivial utilities
|
|
44
|
+
|
|
45
|
+
**Note:** For full classification logic, see `@.claude/contexts/patterns/task-classification.md`
|
|
46
|
+
|
|
47
|
+
### TDD Workflow (Red-Green-Refactor)
|
|
48
|
+
|
|
49
|
+
**Use when:** `tdd_required: true`
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
1. RED Phase - Write the test first
|
|
53
|
+
→ Define expected behavior before implementation
|
|
54
|
+
→ Run test → MUST FAIL (proves test works)
|
|
55
|
+
→ Log: "tdd_red_phase"
|
|
56
|
+
|
|
57
|
+
2. GREEN Phase - Write minimal code
|
|
58
|
+
→ Just enough to make the test pass
|
|
59
|
+
→ Run test → MUST PASS
|
|
60
|
+
→ Log: "tdd_green_phase"
|
|
61
|
+
|
|
62
|
+
3. REFACTOR Phase - Improve code quality
|
|
63
|
+
→ Add logging for observability
|
|
64
|
+
→ Add error handling
|
|
65
|
+
→ Add documentation
|
|
66
|
+
→ Run test → MUST STILL PASS
|
|
67
|
+
→ Log: "tdd_refactor_phase"
|
|
68
|
+
|
|
69
|
+
4. Repeat
|
|
70
|
+
→ One test at a time
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Standard Workflow (Test-Alongside)
|
|
74
|
+
|
|
75
|
+
**Use when:** `tdd_required: false`
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
1. Write implementation first
|
|
79
|
+
2. Write tests to verify
|
|
80
|
+
3. Run tests → PASS
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Testing Layers
|
|
86
|
+
|
|
87
|
+
### 1. Unit Tests
|
|
88
|
+
|
|
89
|
+
**Purpose:** Test individual functions/modules in isolation
|
|
90
|
+
|
|
91
|
+
**Framework:** Vitest (JS/TS) | Pytest (Python)
|
|
92
|
+
|
|
93
|
+
#### TypeScript Example
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
// __tests__/scoring.test.ts
|
|
97
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
98
|
+
import { calculateDiscount } from '@/lib/pricing'
|
|
99
|
+
import { logger } from '@/lib/logger'
|
|
100
|
+
|
|
101
|
+
vi.mock('@/lib/logger')
|
|
102
|
+
|
|
103
|
+
describe('calculateDiscount', () => {
|
|
104
|
+
it('applies 10% discount for members', () => {
|
|
105
|
+
const result = calculateDiscount(100, { isMember: true })
|
|
106
|
+
expect(result).toBe(90)
|
|
107
|
+
|
|
108
|
+
expect(logger.info).toHaveBeenCalledWith(
|
|
109
|
+
'discount_calculated',
|
|
110
|
+
expect.objectContaining({
|
|
111
|
+
originalPrice: 100,
|
|
112
|
+
discount: 10,
|
|
113
|
+
finalPrice: 90
|
|
114
|
+
})
|
|
115
|
+
)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('no discount for non-members', () => {
|
|
119
|
+
const result = calculateDiscount(100, { isMember: false })
|
|
120
|
+
expect(result).toBe(100)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('throws on invalid input', () => {
|
|
124
|
+
expect(() => calculateDiscount(-1, {})).toThrow('Price must be positive')
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
#### Python Example
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
# tests/test_pricing.py
|
|
133
|
+
import pytest
|
|
134
|
+
from app.lib.pricing import calculate_discount
|
|
135
|
+
from unittest.mock import patch
|
|
136
|
+
|
|
137
|
+
@pytest.mark.parametrize("price,is_member,expected", [
|
|
138
|
+
(100, True, 90), # Member gets 10% discount
|
|
139
|
+
(100, False, 100), # Non-member no discount
|
|
140
|
+
(50, True, 45), # Member discount on lower price
|
|
141
|
+
])
|
|
142
|
+
def test_calculate_discount(price, is_member, expected):
|
|
143
|
+
result = calculate_discount(price, is_member=is_member)
|
|
144
|
+
assert result == expected
|
|
145
|
+
|
|
146
|
+
def test_calculate_discount_invalid_price():
|
|
147
|
+
with pytest.raises(ValueError, match="Price must be positive"):
|
|
148
|
+
calculate_discount(-1, is_member=True)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**When to use:**
|
|
152
|
+
- Pure functions
|
|
153
|
+
- Business logic
|
|
154
|
+
- Data transformations
|
|
155
|
+
- Utility functions
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
### 2. Integration Tests
|
|
160
|
+
|
|
161
|
+
**Purpose:** Test multiple components working together
|
|
162
|
+
|
|
163
|
+
#### API Route Test (Next.js)
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
// __tests__/api/items.test.ts
|
|
167
|
+
import { describe, it, expect, beforeEach } from 'vitest'
|
|
168
|
+
import { POST } from '@/app/api/items/route'
|
|
169
|
+
|
|
170
|
+
describe('POST /api/items', () => {
|
|
171
|
+
it('creates item and returns 201', async () => {
|
|
172
|
+
const request = new Request('http://localhost/api/items', {
|
|
173
|
+
method: 'POST',
|
|
174
|
+
headers: { 'Content-Type': 'application/json' },
|
|
175
|
+
body: JSON.stringify({
|
|
176
|
+
name: 'Test Item',
|
|
177
|
+
price: 99.99
|
|
178
|
+
})
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
const response = await POST(request)
|
|
182
|
+
const data = await response.json()
|
|
183
|
+
|
|
184
|
+
expect(response.status).toBe(201)
|
|
185
|
+
expect(data.id).toBeDefined()
|
|
186
|
+
expect(data.name).toBe('Test Item')
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('returns 400 on validation error', async () => {
|
|
190
|
+
const request = new Request('http://localhost/api/items', {
|
|
191
|
+
method: 'POST',
|
|
192
|
+
body: JSON.stringify({ name: '' }) // Missing price
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
const response = await POST(request)
|
|
196
|
+
expect(response.status).toBe(400)
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### API Test (FastAPI)
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
# tests/test_api.py
|
|
205
|
+
import pytest
|
|
206
|
+
from httpx import AsyncClient
|
|
207
|
+
|
|
208
|
+
@pytest.mark.asyncio
|
|
209
|
+
async def test_create_item(client: AsyncClient):
|
|
210
|
+
response = await client.post("/api/items", json={
|
|
211
|
+
"name": "Test Item",
|
|
212
|
+
"price": 99.99
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
assert response.status_code == 201
|
|
216
|
+
data = response.json()
|
|
217
|
+
assert data["id"] is not None
|
|
218
|
+
assert data["name"] == "Test Item"
|
|
219
|
+
|
|
220
|
+
@pytest.mark.asyncio
|
|
221
|
+
async def test_create_item_validation_error(client: AsyncClient):
|
|
222
|
+
response = await client.post("/api/items", json={
|
|
223
|
+
"name": "" # Invalid
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
assert response.status_code == 422 # Validation error
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**When to use:**
|
|
230
|
+
- API routes
|
|
231
|
+
- Database operations
|
|
232
|
+
- External service integrations
|
|
233
|
+
- Multi-step workflows
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
### 3. E2E Tests (Playwright)
|
|
238
|
+
|
|
239
|
+
**Purpose:** Test complete user flows in real browser
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
// e2e/checkout.spec.ts
|
|
243
|
+
import { test, expect } from '@playwright/test'
|
|
244
|
+
|
|
245
|
+
test('user completes checkout flow', async ({ page }) => {
|
|
246
|
+
// Add item to cart
|
|
247
|
+
await page.goto('/products')
|
|
248
|
+
await page.click('button:text("Add to Cart")')
|
|
249
|
+
|
|
250
|
+
// Go to cart
|
|
251
|
+
await page.click('[href="/cart"]')
|
|
252
|
+
await expect(page.locator('h1')).toContainText('Shopping Cart')
|
|
253
|
+
|
|
254
|
+
// Proceed to checkout
|
|
255
|
+
await page.click('button:text("Checkout")')
|
|
256
|
+
|
|
257
|
+
// Fill shipping info
|
|
258
|
+
await page.fill('#name', 'John Doe')
|
|
259
|
+
await page.fill('#email', 'john@example.com')
|
|
260
|
+
await page.fill('#address', '123 Main St')
|
|
261
|
+
await page.click('button:text("Continue")')
|
|
262
|
+
|
|
263
|
+
// Payment
|
|
264
|
+
await page.fill('#cardNumber', '4242424242424242')
|
|
265
|
+
await page.fill('#expiry', '12/25')
|
|
266
|
+
await page.fill('#cvc', '123')
|
|
267
|
+
await page.click('button:text("Pay Now")')
|
|
268
|
+
|
|
269
|
+
// Verify order confirmation
|
|
270
|
+
await expect(page.locator('h1')).toContainText('Order Confirmed')
|
|
271
|
+
await expect(page.locator('[data-testid="order-number"]')).toBeVisible()
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
test('shows validation errors on empty form', async ({ page }) => {
|
|
275
|
+
await page.goto('/checkout')
|
|
276
|
+
await page.click('button:text("Continue")')
|
|
277
|
+
|
|
278
|
+
await expect(page.locator('text=Name is required')).toBeVisible()
|
|
279
|
+
await expect(page.locator('text=Email is required')).toBeVisible()
|
|
280
|
+
})
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
**When to use:**
|
|
284
|
+
- Complete user journeys
|
|
285
|
+
- Cross-browser compatibility
|
|
286
|
+
- UI/UX validation
|
|
287
|
+
- Critical paths
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Testing Best Practices
|
|
292
|
+
|
|
293
|
+
### DO:
|
|
294
|
+
- ✅ Test behavior, not implementation
|
|
295
|
+
- ✅ Write tests first for critical code (TDD)
|
|
296
|
+
- ✅ Mock external dependencies
|
|
297
|
+
- ✅ Use descriptive test names
|
|
298
|
+
- ✅ Test edge cases and error scenarios
|
|
299
|
+
- ✅ Keep tests fast (< 1s per test)
|
|
300
|
+
- ✅ Verify logging in tests
|
|
301
|
+
|
|
302
|
+
### DON'T:
|
|
303
|
+
- ❌ Test framework internals (React hooks, etc.)
|
|
304
|
+
- ❌ Test trivial code (getters/setters)
|
|
305
|
+
- ❌ Write flaky tests (time-dependent)
|
|
306
|
+
- ❌ Skip error case tests
|
|
307
|
+
- ❌ Commit failing tests
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## Test Organization
|
|
312
|
+
|
|
313
|
+
### TypeScript/JavaScript
|
|
314
|
+
```
|
|
315
|
+
project/
|
|
316
|
+
├── __tests__/
|
|
317
|
+
│ ├── unit/
|
|
318
|
+
│ │ ├── lib/
|
|
319
|
+
│ │ │ ├── pricing.test.ts
|
|
320
|
+
│ │ │ └── validation.test.ts
|
|
321
|
+
│ │ └── utils/
|
|
322
|
+
│ │ └── formatters.test.ts
|
|
323
|
+
│ ├── integration/
|
|
324
|
+
│ │ ├── api/
|
|
325
|
+
│ │ │ ├── items.test.ts
|
|
326
|
+
│ │ │ └── users.test.ts
|
|
327
|
+
│ │ └── stores/
|
|
328
|
+
│ │ └── app-store.test.ts
|
|
329
|
+
│ └── e2e/
|
|
330
|
+
│ ├── checkout.spec.ts
|
|
331
|
+
│ └── auth.spec.ts
|
|
332
|
+
├── vitest.config.ts
|
|
333
|
+
└── playwright.config.ts
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Python
|
|
337
|
+
```
|
|
338
|
+
project/
|
|
339
|
+
├── tests/
|
|
340
|
+
│ ├── unit/
|
|
341
|
+
│ │ ├── test_pricing.py
|
|
342
|
+
│ │ └── test_validation.py
|
|
343
|
+
│ ├── integration/
|
|
344
|
+
│ │ ├── test_api.py
|
|
345
|
+
│ │ └── test_db.py
|
|
346
|
+
│ └── conftest.py (fixtures)
|
|
347
|
+
└── pytest.ini
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Coverage Guidelines
|
|
353
|
+
|
|
354
|
+
| Type | Target Coverage |
|
|
355
|
+
|------|----------------|
|
|
356
|
+
| Business Logic | 95%+ |
|
|
357
|
+
| API Routes | 90%+ |
|
|
358
|
+
| Utilities | 85%+ |
|
|
359
|
+
| UI Components | 85%+ |
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## Quick Reference
|
|
364
|
+
|
|
365
|
+
### Vitest Commands
|
|
366
|
+
```bash
|
|
367
|
+
pnpm test # Run all tests
|
|
368
|
+
pnpm test -- --run # Run without watch mode
|
|
369
|
+
pnpm test -- path/to/test # Run specific test
|
|
370
|
+
pnpm test -- --coverage # Run with coverage
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Pytest Commands
|
|
374
|
+
```bash
|
|
375
|
+
pytest # Run all tests
|
|
376
|
+
pytest -v # Verbose output
|
|
377
|
+
pytest tests/unit/ # Run specific directory
|
|
378
|
+
pytest --cov=app # Run with coverage
|
|
379
|
+
pytest -k "test_create" # Run tests matching pattern
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Playwright Commands
|
|
383
|
+
```bash
|
|
384
|
+
pnpm test:e2e # Run E2E tests
|
|
385
|
+
pnpm test:e2e --ui # Run with UI mode
|
|
386
|
+
pnpm test:e2e --debug # Run with debugger
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
## Trust the Classification
|
|
394
|
+
|
|
395
|
+
**Orchestrator uses comprehensive pattern matching:**
|
|
396
|
+
- API endpoints (HTTP methods, routes)
|
|
397
|
+
- Business logic (calculations, validation)
|
|
398
|
+
- External integrations (Stripe, SendGrid, Twilio, S3, etc.)
|
|
399
|
+
- Data transformations (ETL, serialization)
|
|
400
|
+
- Security operations (auth, encryption)
|
|
401
|
+
|
|
402
|
+
**Your responsibility:**
|
|
403
|
+
1. Check `tdd_required` flag in task metadata
|
|
404
|
+
2. If `true` → Use Red-Green-Refactor workflow
|
|
405
|
+
3. If `false` → Use Test-Alongside workflow
|
|
406
|
+
4. Log each phase for observability
|
|
407
|
+
|
|
408
|
+
**Don't override the classification unless you find a clear error.**
|
|
409
|
+
If classification seems wrong, report to user for pattern refinement.
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
**💡 Remember:** Tests are documentation. Write tests that explain what your code does!
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
# UI Component Visual Consistency
|
|
2
|
+
|
|
3
|
+
**Core Principle:** Before creating ANY UI component, extract and reuse design tokens from existing similar components to ensure 100% visual consistency.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🎯 The Problem
|
|
8
|
+
|
|
9
|
+
**Symptom:** New components look "slightly different" from existing ones.
|
|
10
|
+
|
|
11
|
+
**Common Issues:**
|
|
12
|
+
- Icon opacity: `opacity-50` vs `text-foreground/70`
|
|
13
|
+
- Spacing: `px-4` vs `px-3` (inconsistent padding)
|
|
14
|
+
- Colors: `text-gray-500` vs `text-foreground/70` (hardcoded vs theme-aware)
|
|
15
|
+
- Border radius: `rounded` vs `rounded-md` (inconsistent corners)
|
|
16
|
+
|
|
17
|
+
**Root Cause:**
|
|
18
|
+
- ❌ Agent creates component without checking existing ones
|
|
19
|
+
- ❌ No visual comparison with reference components
|
|
20
|
+
- ❌ Missing design token extraction step
|
|
21
|
+
- ❌ No cross-component validation
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 📋 Mandatory Workflow
|
|
26
|
+
|
|
27
|
+
**ALWAYS follow these 6 steps before creating UI components:**
|
|
28
|
+
|
|
29
|
+
### Step 1: Search for Similar Components
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Find components with similar functionality
|
|
33
|
+
Glob: "**/*{Field,Input,Select,Dropdown}*.{tsx,jsx,vue}"
|
|
34
|
+
Grep: "ChevronDown"
|
|
35
|
+
Grep: "dropdown.*icon"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Questions to ask:**
|
|
39
|
+
- Is there a component with similar user interaction?
|
|
40
|
+
- What component has the same visual element (icon, border, shadow)?
|
|
41
|
+
- Which component shares the same state behavior (focus, error, disabled)?
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
### Step 2: Extract Design Tokens
|
|
46
|
+
|
|
47
|
+
**Read reference component and document these properties:**
|
|
48
|
+
|
|
49
|
+
#### Icon Properties
|
|
50
|
+
```typescript
|
|
51
|
+
const ICON_TOKENS = {
|
|
52
|
+
component: 'ChevronDown', // Which icon?
|
|
53
|
+
size: 'h-4 w-4', // Dimensions
|
|
54
|
+
color: 'text-foreground/70', // ⚠️ NOT opacity-50
|
|
55
|
+
position: 'right-3 top-1/2 -translate-y-1/2', // Absolute positioning
|
|
56
|
+
pointer: 'pointer-events-none', // Interaction
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
#### Input/Select Properties
|
|
61
|
+
```typescript
|
|
62
|
+
const INPUT_TOKENS = {
|
|
63
|
+
padding: 'pl-3 pr-10 py-2', // pr-10 = space for icon
|
|
64
|
+
border: 'border-input',
|
|
65
|
+
borderError: 'border-destructive',
|
|
66
|
+
borderRadius: 'rounded-md',
|
|
67
|
+
background: 'bg-background',
|
|
68
|
+
text: 'text-foreground',
|
|
69
|
+
placeholder: 'placeholder:text-muted-foreground',
|
|
70
|
+
|
|
71
|
+
// States
|
|
72
|
+
focus: 'focus:ring-2 focus:ring-primary focus:ring-offset-2',
|
|
73
|
+
disabled: 'disabled:opacity-50 disabled:cursor-not-allowed',
|
|
74
|
+
transition: 'transition-colors',
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
#### Layout Structure
|
|
79
|
+
```typescript
|
|
80
|
+
const LAYOUT_TOKENS = {
|
|
81
|
+
wrapper: 'relative', // Required for absolute icon positioning
|
|
82
|
+
input: 'appearance-none', // Hide native select arrow
|
|
83
|
+
icon: 'absolute', // Icon positioning
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
### Step 3: Apply Tokens to New Component
|
|
90
|
+
|
|
91
|
+
**Example: Creating ComboboxField**
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
// ❌ WRONG - Custom values without reference
|
|
95
|
+
<div>
|
|
96
|
+
<select className="px-4 py-3">...</select>
|
|
97
|
+
<ChevronDown className="opacity-50 h-5 w-5" />
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
// ✅ CORRECT - Extracted tokens from FormField
|
|
101
|
+
<div className="relative">
|
|
102
|
+
<Combobox className="appearance-none pl-3 pr-10 py-2 rounded-md border border-input">
|
|
103
|
+
...
|
|
104
|
+
</Combobox>
|
|
105
|
+
<ChevronDown className="absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 text-foreground/70 pointer-events-none" />
|
|
106
|
+
</div>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Key Rules:**
|
|
110
|
+
1. Use **exact same classes** for identical elements
|
|
111
|
+
2. Prefer **theme tokens** (`text-foreground/70`) over hardcoded colors (`text-gray-500`)
|
|
112
|
+
3. Match **spacing patterns** exactly (`pl-3 pr-10 py-2`)
|
|
113
|
+
4. Reuse **state classes** (focus, hover, disabled)
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
### Step 4: Visual Comparison Test
|
|
118
|
+
|
|
119
|
+
**Use Browser DevTools to compare pixel-by-pixel:**
|
|
120
|
+
|
|
121
|
+
1. Open page with both components side-by-side
|
|
122
|
+
2. Press F12 → Inspect Element
|
|
123
|
+
3. Compare CSS computed values:
|
|
124
|
+
|
|
125
|
+
```css
|
|
126
|
+
/* Icon - Must match exactly */
|
|
127
|
+
width: 16px /* h-4 */
|
|
128
|
+
height: 16px /* w-4 */
|
|
129
|
+
right: 12px /* right-3 (0.75rem) */
|
|
130
|
+
top: 50% /* top-1/2 */
|
|
131
|
+
transform: translateY(-50%) /* -translate-y-1/2 */
|
|
132
|
+
opacity: 0.7 /* text-foreground/70 */
|
|
133
|
+
|
|
134
|
+
/* Input - Must match exactly */
|
|
135
|
+
padding-left: 12px /* pl-3 */
|
|
136
|
+
padding-right: 40px /* pr-10 */
|
|
137
|
+
padding-top: 8px /* py-2 */
|
|
138
|
+
padding-bottom: 8px /* py-2 */
|
|
139
|
+
border-radius: 6px /* rounded-md */
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
4. **If values differ → STOP and fix immediately**
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
### Step 5: State Consistency Check
|
|
147
|
+
|
|
148
|
+
**Test every interactive state:**
|
|
149
|
+
|
|
150
|
+
| State | Expected Behavior |
|
|
151
|
+
|-------|------------------|
|
|
152
|
+
| Normal | Icon visible, clear contrast |
|
|
153
|
+
| Hover | Cursor changes if interactive |
|
|
154
|
+
| Focus | Ring appears (same color/offset as reference) |
|
|
155
|
+
| Error | Border becomes `border-destructive` |
|
|
156
|
+
| Disabled | `opacity-50` + `cursor-not-allowed` |
|
|
157
|
+
|
|
158
|
+
**Compare each state with reference component → Must be identical**
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
### Step 6: Document Design Reference
|
|
163
|
+
|
|
164
|
+
**Add JSDoc comment with design lineage:**
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
/**
|
|
168
|
+
* ComboboxField - Searchable dropdown field
|
|
169
|
+
*
|
|
170
|
+
* VISUAL CONSISTENCY:
|
|
171
|
+
* - Extracted design tokens from: components/ui/form-field.tsx
|
|
172
|
+
* - Icon: ChevronDown (h-4 w-4, text-foreground/70, right-3)
|
|
173
|
+
* - Matches FormField: focus ring, error states, spacing
|
|
174
|
+
* - States tested: normal, focus, error, disabled
|
|
175
|
+
*
|
|
176
|
+
* @see components/ui/form-field.tsx - Reference component
|
|
177
|
+
*/
|
|
178
|
+
export function ComboboxField({ ... }) {
|
|
179
|
+
// Implementation
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## 🚨 Red Flags (Stop Immediately)
|
|
186
|
+
|
|
187
|
+
### ❌ Hardcoded Colors
|
|
188
|
+
```typescript
|
|
189
|
+
// WRONG
|
|
190
|
+
className="text-gray-500" // Not theme-aware
|
|
191
|
+
className="bg-blue-600" // Hardcoded color
|
|
192
|
+
|
|
193
|
+
// CORRECT
|
|
194
|
+
className="text-foreground/70" // Theme token
|
|
195
|
+
className="bg-primary" // Theme token
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### ❌ Non-Standard Opacity/Size
|
|
199
|
+
```typescript
|
|
200
|
+
// WRONG
|
|
201
|
+
className="opacity-50" // Too light for foreground
|
|
202
|
+
className="h-5 w-5" // Larger than standard
|
|
203
|
+
|
|
204
|
+
// CORRECT
|
|
205
|
+
className="text-foreground/70" // Standard opacity via color
|
|
206
|
+
className="h-4 w-4" // Standard icon size
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### ❌ Inconsistent Spacing
|
|
210
|
+
```typescript
|
|
211
|
+
// WRONG
|
|
212
|
+
className="px-4 py-3" // Different from FormField
|
|
213
|
+
|
|
214
|
+
// CORRECT
|
|
215
|
+
className="pl-3 pr-10 py-2" // Matches FormField exactly
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### ❌ Visual Discrepancy
|
|
219
|
+
- **If it "looks different" → STOP**
|
|
220
|
+
- Open browser side-by-side
|
|
221
|
+
- Compare with DevTools
|
|
222
|
+
- Fix before proceeding
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## 📊 Common Design Tokens Reference
|
|
227
|
+
|
|
228
|
+
### Dropdown Components
|
|
229
|
+
```typescript
|
|
230
|
+
// Icon
|
|
231
|
+
size: 'h-4 w-4'
|
|
232
|
+
color: 'text-foreground/70' // ⚠️ NOT opacity-50
|
|
233
|
+
position: 'right-3 top-1/2 -translate-y-1/2'
|
|
234
|
+
|
|
235
|
+
// Container
|
|
236
|
+
padding: 'pl-3 pr-10 py-2' // pr-10 for icon space
|
|
237
|
+
border: 'rounded-md border border-input'
|
|
238
|
+
focus: 'focus:ring-2 focus:ring-primary focus:ring-offset-2'
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Input Components
|
|
242
|
+
```typescript
|
|
243
|
+
padding: 'px-3 py-2'
|
|
244
|
+
border: 'rounded-md border border-input'
|
|
245
|
+
background: 'bg-background'
|
|
246
|
+
text: 'text-foreground'
|
|
247
|
+
placeholder: 'placeholder:text-muted-foreground'
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Button Components
|
|
251
|
+
```typescript
|
|
252
|
+
height: 'h-10'
|
|
253
|
+
padding: 'px-4 py-2'
|
|
254
|
+
borderRadius: 'rounded-md'
|
|
255
|
+
fontWeight: 'font-medium'
|
|
256
|
+
transition: 'transition-colors'
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## ✅ Quick Checklist
|
|
262
|
+
|
|
263
|
+
**Before committing any UI component:**
|
|
264
|
+
|
|
265
|
+
- [ ] Found similar reference component in codebase
|
|
266
|
+
- [ ] Extracted all design tokens (icon, spacing, colors, states)
|
|
267
|
+
- [ ] Applied tokens exactly (no guessing)
|
|
268
|
+
- [ ] Tested visual appearance in browser
|
|
269
|
+
- [ ] Compared computed CSS values with DevTools
|
|
270
|
+
- [ ] Tested all states (normal, hover, focus, error, disabled)
|
|
271
|
+
- [ ] Documented reference component in JSDoc
|
|
272
|
+
- [ ] Visually indistinguishable from reference component
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## 🎯 Summary
|
|
277
|
+
|
|
278
|
+
**One-Sentence Rule:**
|
|
279
|
+
> "Before creating a UI component → Find the most similar existing component → Extract its design tokens → Apply them exactly → Test that they're 100% identical"
|
|
280
|
+
|
|
281
|
+
**Prevention Strategy:**
|
|
282
|
+
1. ✅ Search for reference component first
|
|
283
|
+
2. ✅ Extract design tokens systematically
|
|
284
|
+
3. ✅ Apply tokens without modification
|
|
285
|
+
4. ✅ Validate visual consistency in browser
|
|
286
|
+
5. ✅ Document design lineage
|
|
287
|
+
|
|
288
|
+
**Anti-Pattern:**
|
|
289
|
+
1. ❌ Create component without checking existing ones
|
|
290
|
+
2. ❌ Use custom values instead of extracted tokens
|
|
291
|
+
3. ❌ Skip visual comparison test
|
|
292
|
+
4. ❌ Commit without cross-component validation
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## 📚 Related Patterns
|
|
297
|
+
|
|
298
|
+
- `@/.claude/contexts/patterns/frontend-component-strategy.md` - Component reuse strategy
|
|
299
|
+
- `@/.claude/contexts/design/index.md` - Design system foundation
|
|
300
|
+
- `@/.claude/contexts/design/box-thinking.md` - Layout analysis
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
**Remember:** Visual consistency is not optional. Users notice when components "feel different" even if they can't articulate why.
|