Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:25:20 +08:00
commit 34b2a798b6
17 changed files with 7796 additions and 0 deletions

567
templates/AGENTS_CONFIG.md Normal file
View File

@@ -0,0 +1,567 @@
# AI Agents Configuration: [Project Name]
**AI Provider**: [OpenAI / Claude / Gemini / Cloudflare AI]
**Framework**: [Vercel AI SDK / Custom / Cloudflare Workers AI]
**Agent Architecture**: [Single agent / Multi-agent / Agentic workflows]
**Last Updated**: [Date]
---
## Overview
This document defines AI agents, their capabilities, tools, and workflows for this project.
**Agent Philosophy**:
- **Purpose-built agents** - Each agent has a specific, well-defined role
- **Tool-equipped** - Agents have access to functions they need
- **Conversational** - Agents can ask clarifying questions
- **Stateful when needed** - Use Durable Objects for long-running conversations
- **Fail gracefully** - Always have fallback responses
---
## AI Provider Configuration
### Primary Provider: [Provider Name]
**Model**: [e.g., gpt-5, claude-sonnet-4-5, gemini-2.5-pro]
**API Key**: Stored in environment variable `[KEY_NAME]`
**Base URL**: [API endpoint]
**Configuration**:
```typescript
// src/lib/ai-config.ts
export const aiConfig = {
provider: '[provider]',
model: '[model-name]',
apiKey: process.env.[KEY_NAME],
temperature: 0.7,
maxTokens: 2000,
topP: 1.0
}
```
**Fallback Provider** (optional): [Secondary provider if primary fails]
---
## Agents
### Agent 1: [Agent Name]
**Purpose**: [What this agent does]
**Model**: [Specific model if different from default]
**Context Window**: [Token limit for this agent]
**Capabilities**:
- [Capability 1]
- [Capability 2]
- [Capability 3]
**System Prompt**:
```
You are [agent role]. Your goal is to [agent purpose].
Guidelines:
- [Guideline 1]
- [Guideline 2]
- [Guideline 3]
When you need information you don't have:
- Use available tools
- Ask the user clarifying questions
- Provide your best answer with caveats
Response format:
- Be concise and actionable
- Use markdown for formatting
- Include code examples when helpful
```
**Available Tools**:
- `[tool_name]` - [Description]
- `[tool_name]` - [Description]
**Example Conversation**:
```
User: [Example input]
Agent: [Example response]
User: [Follow-up]
Agent: [Agent uses tool and responds]
```
**Endpoint**: `POST /api/agents/[agent-name]`
**Request**:
```json
{
"message": "User message",
"conversationId": "optional-conversation-id",
"context": { "optional": "context" }
}
```
**Response** (streaming):
```
data: {"type":"text","content":"Agent response..."}
data: {"type":"tool_call","name":"search","args":{"query":"..."}}
data: {"type":"tool_result","result":{...}}
data: {"type":"done"}
```
---
### Agent 2: [Agent Name]
**Purpose**: [What this agent does]
[... repeat structure from Agent 1 ...]
---
## Tools (Functions)
AI agents can call these functions to perform actions or retrieve information.
### Tool: `[tool_name]`
**Purpose**: [What this tool does]
**Parameters**:
```typescript
{
param1: string, // Description
param2: number, // Description
param3?: boolean // Optional description
}
```
**Implementation**:
```typescript
// src/lib/ai-tools.ts
export async function [tool_name](params: ToolParams, context: Context) {
// Tool logic
const result = await performAction(params)
return result
}
```
**Example**:
```typescript
// Agent calls tool
const result = await [tool_name]({
param1: "value",
param2: 42
})
// Tool returns
{
success: true,
data: { /* result */ }
}
```
**Failure Handling**: [How tool handles errors]
---
### Tool: `search_database`
**Purpose**: Search the database for user-specific information
**Parameters**:
```typescript
{
query: string, // Natural language search query
table: string, // Which table to search
limit?: number // Max results (default 5)
}
```
**Implementation**:
```typescript
export async function search_database(
{ query, table, limit = 5 }: SearchParams,
context: Context
) {
const userId = context.get('userId')
// Convert natural language to SQL (simplified example)
const results = await context.env.DB.prepare(
`SELECT * FROM ${table} WHERE user_id = ? LIMIT ?`
).bind(userId, limit).all()
return {
success: true,
data: results.results
}
}
```
---
## Agent Workflows
### Workflow: [Workflow Name]
**Purpose**: [What this workflow accomplishes]
**Agents Involved**:
1. [Agent 1] - [Role in workflow]
2. [Agent 2] - [Role in workflow]
**Flow**:
```
1. User submits [input]
2. [Agent 1] analyzes input
3. [Agent 1] calls tool: [tool_name]
4. Tool returns data
5. [Agent 1] generates response
6. If needed: Hand off to [Agent 2]
7. [Agent 2] completes task
8. Return final result to user
```
**Example**:
```
User: "Find all high-priority tasks and create a summary report"
[Planner Agent] → Calls search_database(query="high priority tasks")
→ Receives 5 tasks
→ Hands off to [Writer Agent] with task data
[Writer Agent] → Generates formatted report
→ Returns markdown report to user
```
---
## Conversation State
### Stateless Agents (Default)
**When to use**: Single-turn interactions, no context needed
**Implementation**: Each request is independent
**Example**: Simple Q&A, content generation
---
### Stateful Agents (Durable Objects)
**When to use**: Multi-turn conversations, context retention
**Implementation**: Store conversation history in Durable Object
**Setup**:
```typescript
// src/durable-objects/conversation.ts
export class Conversation implements DurableObject {
private messages: Message[] = []
async fetch(request: Request) {
const { message } = await request.json()
// Add user message to history
this.messages.push({ role: 'user', content: message })
// Call AI with full conversation history
const response = await callAI({
messages: this.messages,
tools: availableTools
})
// Add assistant response to history
this.messages.push({ role: 'assistant', content: response })
return new Response(JSON.stringify({ response }))
}
}
```
**Wrangler Config**:
```jsonc
{
"durable_objects": {
"bindings": [
{
"name": "CONVERSATIONS",
"class_name": "Conversation",
"script_name": "app"
}
]
}
}
```
**Usage**:
```typescript
// Create/get conversation
const conversationId = crypto.randomUUID()
const durableObjectId = env.CONVERSATIONS.idFromName(conversationId)
const stub = env.CONVERSATIONS.get(durableObjectId)
// Send message to conversation
const response = await stub.fetch(request)
```
---
## Streaming Responses
**Why stream**: Better UX, appears faster, shows progress
**Implementation** (Server-Sent Events):
```typescript
// src/routes/agents.ts
app.post('/api/agents/:agentName', async (c) => {
const { message } = await c.req.json()
const stream = await streamAIResponse({
message,
agent: c.req.param('agentName')
})
return c.newResponse(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
}
})
})
```
**Client** (Vercel AI SDK):
```typescript
import { useChat } from '@ai-sdk/react'
export function ChatInterface() {
const { messages, input, handleInputChange, handleSubmit } = useChat({
api: '/api/agents/chat'
})
return (
<div>
{messages.map(m => (
<div key={m.id}>{m.content}</div>
))}
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleInputChange} />
</form>
</div>
)
}
```
---
## Prompt Engineering
### System Prompt Best Practices
**Structure**:
1. **Role definition** - "You are a [role]"
2. **Capabilities** - What the agent can do
3. **Constraints** - What the agent cannot do
4. **Tone/style** - How the agent should respond
5. **Output format** - Markdown, JSON, etc
**Example**:
```
You are a helpful task management assistant.
Your capabilities:
- Search user's tasks
- Create, update, delete tasks
- Generate task summaries and reports
- Set reminders and priorities
Your constraints:
- Never access other users' data
- Always confirm before deleting tasks
- Ask for clarification if user intent is unclear
Response style:
- Be concise and actionable
- Use bullet points for lists
- Include task IDs for reference
Output format:
- Use markdown formatting
- Bold important information
- Use code blocks for task IDs
```
---
## Token Management
**Cost Optimization**:
- Use smaller models for simple tasks (gpt-5-mini, claude-haiku-4-5)
- Use larger models only when needed (gpt-5, claude-sonnet-4-5)
- Limit conversation history (keep last N messages)
- Summarize long conversations to reduce tokens
**Token Budgets**:
```typescript
const TOKEN_BUDGETS = {
'simple-qa': {
model: 'gpt-5-mini',
maxInputTokens: 500,
maxOutputTokens: 500
},
'complex-analysis': {
model: 'gpt-5',
maxInputTokens: 4000,
maxOutputTokens: 2000
}
}
```
---
## Error Handling
### AI Provider Failures
**Handle**:
- Rate limits (retry with backoff)
- API errors (fallback provider or error message)
- Timeout (abort and inform user)
**Example**:
```typescript
try {
const response = await callAI({ message, tools })
return response
} catch (error) {
if (error.code === 'rate_limit') {
// Retry with exponential backoff
await sleep(2000)
return await callAI({ message, tools })
} else {
// Return graceful error
return {
error: true,
message: "I'm having trouble processing that right now. Please try again."
}
}
}
```
---
## Testing AI Agents
### Unit Tests (Tool Functions)
Test each tool independently:
```typescript
describe('search_database tool', () => {
it('returns user-specific results', async () => {
const result = await search_database({
query: 'high priority',
table: 'tasks'
}, mockContext)
expect(result.success).toBe(true)
expect(result.data.length).toBeGreaterThan(0)
})
})
```
---
### Integration Tests (Agent Endpoints)
Test agent responses:
```typescript
describe('POST /api/agents/assistant', () => {
it('responds to simple query', async () => {
const res = await app.request('/api/agents/assistant', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token'
},
body: JSON.stringify({
message: 'List my tasks'
})
})
expect(res.status).toBe(200)
const data = await res.json()
expect(data.response).toContain('task')
})
})
```
---
### Manual Testing
**Test Prompts**:
- Simple queries: "What tasks do I have?"
- Tool usage: "Create a new task called 'Review PR'"
- Edge cases: "Delete all my tasks" (should confirm first)
- Unclear input: "Do the thing" (should ask for clarification)
- Multi-step: "Find high-priority tasks and summarize them"
---
## Monitoring and Observability
**Metrics to track**:
- Agent invocations per day
- Average response time
- Token usage (input + output)
- Tool call frequency
- Error rate
**Logging**:
```typescript
console.log('[Agent]', {
agent: 'assistant',
userId: context.get('userId'),
message: message.substring(0, 50),
tokensUsed: response.usage.total_tokens,
toolsCalled: toolCalls.map(t => t.name),
responseTime: Date.now() - startTime
})
```
**Cloudflare Workers Analytics Engine** (optional):
```typescript
await env.ANALYTICS.writeDataPoint({
indexes: [userId],
doubles: [responseTime, tokensUsed],
blobs: [agentName, toolsCalled]
})
```
---
## Future Agent Enhancements
- [ ] Add [agent name] for [purpose]
- [ ] Integrate [tool name] for [capability]
- [ ] Implement [workflow name]
- [ ] Add voice input/output
- [ ] Multi-modal support (images, files)
---
## Revision History
**v1.0** ([Date]): Initial agent configuration
**v1.1** ([Date]): [Changes made]

438
templates/API_ENDPOINTS.md Normal file
View File

@@ -0,0 +1,438 @@
# API Endpoints: [Project Name]
**Base URL**: `/api`
**Framework**: Hono (Cloudflare Workers)
**Auth**: Clerk JWT with custom template
**Validation**: Zod schemas
**Last Updated**: [Date]
---
## Overview
All API endpoints follow RESTful conventions and return JSON responses.
**Base URL**:
- Local dev: `http://localhost:5173/api`
- Production: `https://[your-domain].workers.dev/api`
**Authentication**: Most endpoints require a valid Clerk JWT in the `Authorization` header:
```
Authorization: Bearer <jwt_token>
```
**Content Type**: All requests and responses use `application/json`
---
## Response Format
### Success Response
```json
{
"data": { /* response data */ },
"meta": { /* optional metadata like pagination */ }
}
```
### Error Response
```json
{
"error": "Human-readable error message",
"code": "ERROR_CODE",
"details": { /* optional additional context */ }
}
```
**Standard Error Codes**:
- `VALIDATION_ERROR` (400): Request body failed validation
- `UNAUTHORIZED` (401): Missing or invalid JWT
- `FORBIDDEN` (403): Valid JWT but insufficient permissions
- `NOT_FOUND` (404): Resource doesn't exist
- `INTERNAL_ERROR` (500): Server error
---
## Authentication Endpoints
### POST `/api/auth/verify`
**Purpose**: Verify JWT token validity
**Auth**: None (public endpoint)
**Request Body**:
```json
{
"token": "string"
}
```
**Response 200**:
```json
{
"data": {
"valid": true,
"email": "user@example.com",
"userId": "clerk_user_123"
}
}
```
**Response 401**:
```json
{
"error": "Invalid or expired token",
"code": "UNAUTHORIZED"
}
```
**Validation**:
```typescript
const schema = z.object({
token: z.string().min(1)
})
```
---
### GET `/api/auth/me`
**Purpose**: Get current authenticated user's profile
**Auth**: Required
**Response 200**:
```json
{
"data": {
"id": 1,
"email": "user@example.com",
"displayName": "John Doe",
"avatarUrl": "https://r2.../avatar.jpg",
"createdAt": 1234567890
}
}
```
**Response 401**:
```json
{
"error": "Not authenticated",
"code": "UNAUTHORIZED"
}
```
---
## [Resource] Endpoints
### GET `/api/[resource]`
**Purpose**: List all [resources] for authenticated user
**Auth**: Required
**Query Parameters**:
- `limit` (optional): Number of items to return (default: 50, max: 100)
- `offset` (optional): Number of items to skip (default: 0)
- `sort` (optional): Sort field (default: `created_at`)
- `order` (optional): Sort order - `asc` or `desc` (default: `desc`)
**Example**: `/api/[resource]?limit=20&offset=0&sort=created_at&order=desc`
**Response 200**:
```json
{
"data": [
{
"id": 1,
"userId": 1,
"[field]": "value",
"createdAt": 1234567890,
"updatedAt": 1234567890
}
],
"meta": {
"total": 42,
"limit": 20,
"offset": 0
}
}
```
**Response 401**: Not authenticated
---
### GET `/api/[resource]/:id`
**Purpose**: Get a specific [resource] by ID
**Auth**: Required
**Response 200**:
```json
{
"data": {
"id": 1,
"userId": 1,
"[field]": "value",
"createdAt": 1234567890,
"updatedAt": 1234567890
}
}
```
**Response 404**:
```json
{
"error": "[Resource] not found",
"code": "NOT_FOUND"
}
```
**Response 403**: User doesn't own this resource
---
### POST `/api/[resource]`
**Purpose**: Create a new [resource]
**Auth**: Required
**Request Body**:
```json
{
"[field1]": "value",
"[field2]": "value"
}
```
**Validation**:
```typescript
const schema = z.object({
[field1]: z.string().min(1).max(100),
[field2]: z.string().optional(),
// ... other fields
})
```
**Response 201**:
```json
{
"data": {
"id": 42,
"userId": 1,
"[field1]": "value",
"[field2]": "value",
"createdAt": 1234567890,
"updatedAt": 1234567890
}
}
```
**Response 400** (validation error):
```json
{
"error": "Validation failed",
"code": "VALIDATION_ERROR",
"details": {
"field1": "Must be at least 1 character"
}
}
```
---
### PATCH `/api/[resource]/:id`
**Purpose**: Update an existing [resource]
**Auth**: Required
**Request Body** (all fields optional):
```json
{
"[field1]": "new value",
"[field2]": "new value"
}
```
**Validation**:
```typescript
const schema = z.object({
[field1]: z.string().min(1).max(100).optional(),
[field2]: z.string().optional(),
}).refine(data => Object.keys(data).length > 0, {
message: "At least one field must be provided"
})
```
**Response 200**:
```json
{
"data": {
"id": 42,
"userId": 1,
"[field1]": "new value",
"[field2]": "new value",
"createdAt": 1234567890,
"updatedAt": 1234567999
}
}
```
**Response 404**: Resource not found
**Response 403**: User doesn't own this resource
---
### DELETE `/api/[resource]/:id`
**Purpose**: Delete a [resource]
**Auth**: Required
**Response 204**: No content (success)
**Response 404**: Resource not found
**Response 403**: User doesn't own this resource
---
## Middleware
### CORS Middleware
**Applies to**: All routes
**Headers**:
```
Access-Control-Allow-Origin: https://[your-domain].com
Access-Control-Allow-Methods: GET, POST, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
```
### Auth Middleware
**Applies to**: All routes except public endpoints
**Validates**: Clerk JWT in Authorization header
**Adds to context**: `c.get('userId')`, `c.get('email')`
**Rejects**: Missing, invalid, or expired tokens (401)
### Error Handler Middleware
**Applies to**: All routes
**Catches**: Unhandled errors
**Returns**: 500 with sanitized error message
**Logs**: Full error details for debugging
### Validation Middleware
**Applies to**: POST and PATCH routes
**Validates**: Request body against Zod schema
**Returns**: 400 with field-specific errors if validation fails
---
## Rate Limiting
[Optional: Define rate limits if applicable]
**Default Limits**:
- Anonymous requests: 10 requests per minute
- Authenticated requests: 100 requests per minute
**Headers**:
```
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1234567890
```
---
## Webhook Endpoints
[Optional: Document webhooks if applicable]
### POST `/api/webhooks/[service]`
**Purpose**: Handle webhook from [third-party service]
**Auth**: Webhook signature verification
**Source**: [Service name]
**Expected Payload**:
```json
{
"event": "event_type",
"data": { /* event data */ }
}
```
**Response 200**: Webhook processed successfully
**Response 400**: Invalid signature or payload
---
## Testing
### Manual Testing with curl
**Create resource**:
```bash
curl -X POST http://localhost:5173/api/[resource] \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{"field1": "value"}'
```
**Get resource**:
```bash
curl http://localhost:5173/api/[resource]/1 \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
```
**Update resource**:
```bash
curl -X PATCH http://localhost:5173/api/[resource]/1 \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{"field1": "new value"}'
```
**Delete resource**:
```bash
curl -X DELETE http://localhost:5173/api/[resource]/1 \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
```
### Getting a Test JWT
Use Clerk's development mode or sign in to get a token:
```javascript
// In browser console after logging in
const token = await window.Clerk.session.getToken();
console.log(token);
```
---
## Deployment
**Wrangler Configuration**: Ensure `wrangler.jsonc` includes necessary bindings:
```jsonc
{
"vars": {
"CLERK_PUBLISHABLE_KEY": "pk_test_..."
},
"d1_databases": [
{
"binding": "DB",
"database_name": "[your-database]",
"database_id": "[database-id]"
}
]
}
```
**Environment Variables** (set via dashboard or CLI):
- `CLERK_SECRET_KEY`: Secret key for JWT verification
---
## Future Endpoints
Planned endpoints to implement:
- [ ] `/api/[feature]` - [Description]
- [ ] `/api/[other-feature]` - [Description]
---
## Revision History
**v1.0** ([Date]): Initial API design
**v1.1** ([Date]): [Changes made]

495
templates/ARCHITECTURE.md Normal file
View File

@@ -0,0 +1,495 @@
# Architecture: [Project Name]
**Deployment Platform**: Cloudflare Workers
**Frontend**: Vite + React + Tailwind v4 + shadcn/ui
**Backend**: Hono (API routes on same Worker)
**Database**: Cloudflare D1 (SQLite)
**Storage**: Cloudflare R2 (object storage)
**Auth**: Clerk (JWT-based)
**Last Updated**: [Date]
---
## System Overview
```
┌──────────────────────────────────────────────────────────────┐
│ Browser │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ React App (Vite build) │ │
│ │ - Components (shadcn/ui + custom) │ │
│ │ - State (TanStack Query + Zustand) │ │
│ │ - Forms (React Hook Form + Zod) │ │
│ └───────────┬────────────────────────────────────────────┘ │
└──────────────┼───────────────────────────────────────────────┘
│ HTTPS
┌──────────────────────────────────────────────────────────────┐
│ Cloudflare Worker (Edge Runtime) │
│ ┌────────────────────────┐ ┌──────────────────────────┐ │
│ │ Static Assets │ │ API Routes (Hono) │ │
│ │ (Vite build output) │ │ /api/* │ │
│ │ Served directly │ │ │ │
│ │ │ │ Middleware: │ │
│ │ / → index.html │ │ - CORS │ │
│ │ /assets/* │ │ - Auth (JWT verify) │ │
│ │ │ │ - Error handling │ │
│ └────────────────────────┘ │ - Validation (Zod) │ │
│ └───────┬──────────────────┘ │
│ │ │
└────────────────────────────────────────┼─────────────────────┘
┌──────────────────────────────┼──────────────────────────┐
│ │ │
↓ ↓ ↓
┌───────────────────┐ ┌───────────────────┐ ┌──────────────────┐
│ Cloudflare D1 │ │ Cloudflare R2 │ │ Clerk Auth │
│ (Database) │ │ (File Storage) │ │ (External) │
│ │ │ │ │ │
│ - users │ │ - avatars/ │ │ - User accounts │
│ - [other tables] │ │ - uploads/ │ │ - JWT tokens │
│ │ │ │ │ - Social auth │
└───────────────────┘ └───────────────────┘ └──────────────────┘
```
---
## Components Breakdown
### Frontend (Browser)
**Technology**: React 19 + Vite + Tailwind v4
**Responsibilities**:
- Render UI components
- Handle user interactions
- Client-side validation (Zod)
- Optimistic updates
- State management
- Routing (React Router or TanStack Router)
**Key Libraries**:
- `@clerk/clerk-react` - Authentication UI and hooks
- `@tanstack/react-query` - Server state management (caching, refetching)
- `zustand` - Client state management (UI state, preferences)
- `react-hook-form` - Form state and validation
- `zod` - Schema validation
- `shadcn/ui` - UI component library (Radix UI primitives)
**State Architecture**:
```
Server State (TanStack Query)
Cached API responses, auto-refetch, background sync
Examples: user data, tasks, analytics
Client State (Zustand)
UI state, form state, user preferences
Examples: sidebar open/closed, theme, filters
```
---
### Backend (Cloudflare Worker)
**Technology**: Hono web framework on Cloudflare Workers
**Responsibilities**:
- Serve static assets (Vite build)
- API request routing
- Authentication/authorization
- Server-side validation
- Business logic
- Database operations
- Third-party API integration
**Route Structure**:
```typescript
app.use('*', cors())
app.use('/api/*', authMiddleware)
// Static assets
app.get('/', serveStatic({ path: './dist/index.html' }))
app.get('/assets/*', serveStatic({ root: './dist' }))
// API routes
app.route('/api/auth', authRoutes)
app.route('/api/[resource]', [resource]Routes)
// ... more routes
app.onError(errorHandler)
```
**Middleware Pipeline**:
```
Request
CORS Middleware (allow frontend origin)
Auth Middleware (verify JWT for /api/* routes)
Route Handler
Validation Middleware (Zod schema)
Business Logic
Response
Error Handler (catch unhandled errors)
```
---
### Database (Cloudflare D1)
**Technology**: SQLite (via D1)
**Access Pattern**: SQL queries via Worker bindings
**Schema**: See `DATABASE_SCHEMA.md` for full details
**Usage**:
```typescript
// In Worker
const result = await c.env.DB.prepare(
'SELECT * FROM users WHERE id = ?'
).bind(userId).first()
```
**Migrations**: Manual SQL files in `migrations/`
**Backups**: Export via `npx wrangler d1 export`
---
### Storage (Cloudflare R2)
**Technology**: S3-compatible object storage
**Use Cases**:
- User avatars
- File uploads
- Generated assets
**Access Pattern**: Direct upload from Worker, signed URLs for browser access
**Bucket Structure**:
```
[bucket-name]/
├── avatars/
│ ├── user-1.jpg
│ └── user-2.png
├── uploads/
│ └── [user-id]/
│ └── document.pdf
└── generated/
└── export-123.csv
```
---
### Authentication (Clerk)
**Technology**: Clerk (external SaaS)
**Flow**:
```
1. User clicks "Sign In"
2. Clerk modal opens (handled by @clerk/clerk-react)
3. User authenticates (email/password or social)
4. Clerk returns JWT
5. Frontend includes JWT in API requests (Authorization: Bearer ...)
6. Worker verifies JWT using Clerk secret key
7. Worker extracts user email/ID from JWT
8. Worker processes request with user context
```
**JWT Custom Template** (configured in Clerk):
```json
{
"email": "{{user.primary_email_address}}",
"userId": "{{user.id}}",
"metadata": {
"displayName": "{{user.first_name}} {{user.last_name}}"
}
}
```
**Verification** (in Worker):
```typescript
import { verifyToken } from '@clerk/backend'
const token = c.req.header('Authorization')?.replace('Bearer ', '')
const verified = await verifyToken(token, {
secretKey: c.env.CLERK_SECRET_KEY
})
```
---
## Data Flow Patterns
### User Authentication Flow
```
1. User loads app
→ React app served from Worker static assets
→ ClerkProvider wraps app
2. User not authenticated
→ Show sign-in button
→ User clicks sign-in
→ Clerk modal opens
3. User signs in
→ Clerk handles authentication
→ Returns JWT to browser
→ React stores JWT in memory (via ClerkProvider)
4. User makes API request
→ React includes JWT in Authorization header
→ Worker middleware verifies JWT
→ Extracts user info from JWT
→ Passes to route handler via context
```
---
### CRUD Operation Flow (Example: Create Task)
```
1. User fills out form
2. React Hook Form validates locally (Zod schema)
3. Validation passes → Submit to API
4. POST /api/tasks with JWT in header
5. Worker receives request
6. CORS middleware allows request
7. Auth middleware verifies JWT → extracts userId
8. Route handler receives request
9. Validation middleware validates body (Zod schema)
10. Business logic creates task in D1
INSERT INTO tasks (user_id, title, ...) VALUES (?, ?, ...)
11. Return created task (201)
12. TanStack Query updates cache
13. React re-renders with new task
```
---
### File Upload Flow
```
1. User selects file in browser
2. React sends file to POST /api/upload
3. Worker receives file (multipart/form-data)
4. Worker validates file (size, type)
5. Worker uploads to R2
await c.env.R2_BUCKET.put(`uploads/${userId}/${filename}`, file)
6. Worker creates DB record with R2 key
INSERT INTO files (user_id, r2_key, filename, ...) VALUES (...)
7. Return file metadata (200)
8. Frontend shows uploaded file with signed URL
GET /api/files/:id/url → Worker generates R2 signed URL
```
---
## Deployment Architecture
### Development Environment
```
localhost:5173
Vite dev server (HMR)
@cloudflare/vite-plugin runs Worker alongside Vite
Worker connects to local D1 database
All requests proxied correctly (frontend ↔ API on same port)
```
**Start dev**:
```bash
npm run dev
# Vite serves frontend on :5173
# Worker API available at :5173/api
# Uses local D1 and R2 buckets
```
---
### Production Environment
```
https://[app-name].[account].workers.dev (or custom domain)
Cloudflare Worker (edge locations globally)
Static assets cached at edge
API requests hit Worker
D1 database (regional, auto-replicated)
R2 storage (global, low latency)
```
**Deploy**:
```bash
npm run build # Vite builds frontend → dist/
npx wrangler deploy # Uploads Worker + assets
```
---
## Security Architecture
### Authentication
- **JWT verification** on all protected routes
- **Clerk handles** password hashing, session management, social auth
- **No passwords stored** in our database
### Authorization
- **User ownership checks** before mutations
- Example: Can't delete another user's task
```typescript
const task = await getTask(id)
if (task.user_id !== c.get('userId')) {
return c.json({ error: 'Forbidden' }, 403)
}
```
### Input Validation
- **Client-side** (Zod): Fast feedback, better UX
- **Server-side** (Zod): Security, trust boundary
- **Same schemas** used on both sides
### CORS
- **Restrict origins** to production domain
- **Allow credentials** for JWT cookies (if used)
### Secrets Management
- **Environment variables** for API keys
- **Never committed** to git
- **Wrangler secrets** for production
### Rate Limiting
[Optional: Add if implemented]
- X requests per minute per IP
- Higher limits for authenticated users
---
## Scaling Considerations
### Current Architecture Scales to:
- **Requests**: Millions/day (Cloudflare Workers auto-scale)
- **Users**: 10k-100k (D1 suitable for this range)
- **Data**: Moderate (D1 max 10GB per database)
### If You Need to Scale Beyond:
- **Database**: Consider Hyperdrive + external Postgres for >100k users
- **Storage**: R2 scales infinitely
- **Real-time**: Add Durable Objects for WebSocket connections
- **Compute**: Workers already global and auto-scaling
---
## Monitoring and Observability
[Optional: Define monitoring strategy]
**Metrics to Track**:
- Request volume and latency
- Error rates (4xx, 5xx)
- Database query performance
- R2 upload/download volumes
**Tools**:
- Cloudflare Analytics (built-in)
- Workers Analytics Engine (custom metrics)
- Sentry or similar for error tracking
---
## Development Workflow
1. **Local development**: `npm run dev`
2. **Make changes**: Edit code, hot reload
3. **Test locally**: Manual testing or automated tests
4. **Commit**: `git commit -m "feat: add feature"`
5. **Deploy**: `npx wrangler deploy`
6. **Monitor**: Check Cloudflare dashboard for errors
---
## Technology Choices Rationale
**Why Cloudflare Workers?**
- Global edge deployment (low latency)
- Auto-scaling (no server management)
- Integrated services (D1, R2, KV)
- Cost-effective for moderate traffic
**Why Vite?**
- Fast dev server with HMR
- Excellent React support
- Simple config
- `@cloudflare/vite-plugin` integrates perfectly with Workers
**Why Hono?**
- Lightweight and fast
- Express-like API (familiar)
- Native TypeScript support
- Works on any runtime (including Workers)
**Why Clerk?**
- Handles complex auth flows
- Social auth out of the box
- Great DX
- No need to build/maintain auth system
**Why Tailwind v4?**
- Utility-first CSS
- shadcn/ui compatibility
- Dark mode support
- v4 Vite plugin (no PostCSS needed)
---
## Future Architecture Enhancements
Potential additions:
- [ ] Durable Objects for real-time features (WebSockets)
- [ ] Workers AI for AI-powered features
- [ ] Queues for background jobs
- [ ] Hyperdrive for external database connections
- [ ] Vectorize for semantic search
---
## Revision History
**v1.0** ([Date]): Initial architecture design
**v1.1** ([Date]): [Changes made]

View File

@@ -0,0 +1,269 @@
# Database Schema: [Project Name]
**Database**: Cloudflare D1 (SQLite)
**Migrations**: `migrations/` directory
**ORM**: [Drizzle ORM / Raw SQL / None]
**Schema Version**: 1.0
**Last Updated**: [Date]
---
## Overview
This document defines the complete database schema including tables, relationships, indexes, and migrations.
**Design Principles**:
- Normalize data to reduce redundancy
- Index frequently queried columns
- Use foreign keys for referential integrity
- Include timestamps for audit trail
- Use INTEGER for IDs (SQLite auto-increment)
- Use TEXT for strings (SQLite doesn't enforce varchar limits)
- Use INTEGER for booleans (0 = false, 1 = true)
- Use INTEGER for timestamps (Unix epoch)
---
## Tables
### `users`
**Purpose**: User accounts and authentication data
| Column | Type | Constraints | Default | Notes |
|--------|------|-------------|---------|-------|
| `id` | INTEGER | PRIMARY KEY | AUTO | Auto-increment |
| `email` | TEXT | UNIQUE, NOT NULL | - | Login identifier |
| `clerk_id` | TEXT | UNIQUE, NOT NULL | - | Clerk user ID |
| `display_name` | TEXT | NULL | - | User's display name |
| `avatar_url` | TEXT | NULL | - | Profile picture URL (R2) |
| `created_at` | INTEGER | NOT NULL | - | Unix timestamp |
| `updated_at` | INTEGER | NOT NULL | - | Unix timestamp |
**Indexes**:
- `idx_users_email` on `email` (for login lookups)
- `idx_users_clerk_id` on `clerk_id` (for auth verification)
**Relationships**:
- One-to-many with `[related_table]`
**Notes**:
- `clerk_id` comes from Clerk authentication
- `avatar_url` points to R2 storage if user uploads custom avatar
- Emails are unique and used for account identification
---
### `[table_name]`
**Purpose**: [Description of what this table stores]
| Column | Type | Constraints | Default | Notes |
|--------|------|-------------|---------|-------|
| `id` | INTEGER | PRIMARY KEY | AUTO | Auto-increment |
| `user_id` | INTEGER | FOREIGN KEY, NOT NULL | - | References `users(id)` |
| `[field]` | [TYPE] | [CONSTRAINTS] | [DEFAULT] | [Notes] |
| `created_at` | INTEGER | NOT NULL | - | Unix timestamp |
| `updated_at` | INTEGER | NOT NULL | - | Unix timestamp |
**Indexes**:
- `idx_[table]_user_id` on `user_id` (for user-specific queries)
- `idx_[table]_[field]` on `[field]` (if frequently queried)
**Relationships**:
- Many-to-one with `users`
- [Other relationships]
**Notes**:
- [Important details about this table]
---
### `[junction_table]` (for many-to-many relationships)
**Purpose**: Links [table_a] and [table_b] in many-to-many relationship
| Column | Type | Constraints | Default | Notes |
|--------|------|-------------|---------|-------|
| `id` | INTEGER | PRIMARY KEY | AUTO | Auto-increment |
| `[table_a]_id` | INTEGER | FOREIGN KEY, NOT NULL | - | References `[table_a](id)` |
| `[table_b]_id` | INTEGER | FOREIGN KEY, NOT NULL | - | References `[table_b](id)` |
| `created_at` | INTEGER | NOT NULL | - | Unix timestamp |
**Indexes**:
- `idx_[junction]_[table_a]_id` on `[table_a]_id`
- `idx_[junction]_[table_b]_id` on `[table_b]_id`
- `idx_[junction]_composite` on `([table_a]_id, [table_b]_id)` UNIQUE
**Relationships**:
- Many-to-one with `[table_a]`
- Many-to-one with `[table_b]`
**Notes**:
- Composite unique index prevents duplicate associations
---
## Relationships Diagram
```
┌─────────────┐
│ users │
└──────┬──────┘
│ 1
│ N
┌──────┴──────────┐
│ [child_table] │
└─────────────────┘
[Add more relationships as needed]
```
---
## Migrations
### Migration 0001: Initial Schema
**File**: `migrations/0001_initial_schema.sql`
**Created**: [Date]
**Purpose**: Create initial tables for [core entities]
**Creates**:
- `users` table
- `[other_tables]` tables
**Indexes**:
- All primary indexes for frequently queried columns
**Run**:
```bash
npx wrangler d1 execute [DB_NAME] --local --file=migrations/0001_initial_schema.sql
npx wrangler d1 execute [DB_NAME] --remote --file=migrations/0001_initial_schema.sql
```
---
### Migration 0002: [Description]
**File**: `migrations/0002_[name].sql`
**Created**: [Date]
**Purpose**: [What this migration does]
**Changes**:
- Add `[column]` to `[table]`
- Create `[new_table]` table
- Add index `[index_name]`
**Run**:
```bash
npx wrangler d1 execute [DB_NAME] --local --file=migrations/0002_[name].sql
npx wrangler d1 execute [DB_NAME] --remote --file=migrations/0002_[name].sql
```
---
## Seed Data
For development and testing, seed the database with sample data.
**File**: `migrations/seed.sql` (run manually, not in production)
**Sample Data**:
- 3-5 test users
- [Other sample data relevant to testing]
**Run**:
```bash
npx wrangler d1 execute [DB_NAME] --local --file=migrations/seed.sql
```
**Sample Users**:
```sql
INSERT INTO users (email, clerk_id, display_name, created_at, updated_at)
VALUES
('test1@example.com', 'clerk_test_1', 'Test User 1', strftime('%s', 'now'), strftime('%s', 'now')),
('test2@example.com', 'clerk_test_2', 'Test User 2', strftime('%s', 'now'), strftime('%s', 'now')),
('test3@example.com', 'clerk_test_3', 'Test User 3', strftime('%s', 'now'), strftime('%s', 'now'));
```
---
## Query Patterns
### Common Queries
**Get user by email**:
```sql
SELECT * FROM users WHERE email = ?;
```
**Get all [items] for a user**:
```sql
SELECT * FROM [table] WHERE user_id = ? ORDER BY created_at DESC;
```
**Get [item] with related data**:
```sql
SELECT
[table].*,
users.display_name,
users.avatar_url
FROM [table]
JOIN users ON [table].user_id = users.id
WHERE [table].id = ?;
```
---
## Constraints and Validation
### Enforced at Database Level
- Primary keys (unique, not null)
- Foreign keys (referential integrity)
- Unique constraints (email, composite indexes)
- Not null constraints (required fields)
### Enforced at Application Level (Zod)
- Email format validation
- String length limits
- Enum values
- Complex business logic
**Why split?**: Database enforces data integrity, application provides user-friendly error messages.
---
## Backup and Restore
### Export Database
```bash
npx wrangler d1 export [DB_NAME] --local --output=backup.sql
npx wrangler d1 export [DB_NAME] --remote --output=backup.sql
```
### Import Database
```bash
npx wrangler d1 execute [DB_NAME] --local --file=backup.sql
npx wrangler d1 execute [DB_NAME] --remote --file=backup.sql
```
---
## Performance Considerations
**Indexes**: All frequently queried columns are indexed
**Query Optimization**: Use `EXPLAIN QUERY PLAN` to check query performance
**Pagination**: Use `LIMIT` and `OFFSET` for large result sets
**Caching**: Consider Workers KV for frequently accessed, rarely changed data
---
## Future Enhancements
Potential schema changes to consider:
- [ ] [Feature requiring schema change]
- [ ] [Another potential enhancement]
---
## Revision History
**v1.0** ([Date]): Initial schema design
**v1.1** ([Date]): [Changes made]

View File

@@ -0,0 +1,229 @@
# Implementation Phases: [Project Name]
**Project Type**: [Web App / Dashboard / API / Tool / etc]
**Stack**: Cloudflare Workers + Vite + React + Tailwind v4 + shadcn/ui + D1
**Estimated Total**: [X hours] (~[Y minutes] human time with AI assistance)
**Created**: [Date]
---
## Overview
This document breaks down the project into context-safe phases. Each phase:
- Can be completed in one 2-4 hour session
- Includes built-in verification criteria
- Has clear exit criteria
- Touches 5-8 files maximum
- Requires minimal context from other phases
---
## Phase 1: [Name - Usually "Project Setup"]
**Type**: Infrastructure
**Estimated**: 2-3 hours (~2-3 minutes human time)
**Files**: `package.json`, `wrangler.jsonc`, `vite.config.ts`, `src/index.ts`, `src/index.css`
### Tasks
- [ ] Scaffold Cloudflare Worker with Vite
- [ ] Install dependencies (React, Tailwind v4, shadcn/ui, etc)
- [ ] Configure `wrangler.jsonc` with bindings
- [ ] Setup Tailwind v4 with `@tailwindcss/vite` plugin
- [ ] Initialize shadcn/ui with dark mode support
- [ ] Create basic "Hello World" component
- [ ] Test local dev server
- [ ] Test deployment to Cloudflare
### Verification Criteria
- [ ] `npm run dev` starts without errors
- [ ] `localhost:5173` shows React app with Tailwind styling
- [ ] Dark/light mode toggle works
- [ ] `npm run build` succeeds
- [ ] `npx wrangler deploy` deploys successfully
- [ ] Deployed URL shows working app
### Exit Criteria
Working development environment with successful test deployment. Can iterate on code locally and deploy to Cloudflare.
---
## Phase 2: [Name - Usually "Database Schema" or next major component]
**Type**: [Database/API/UI/Integration]
**Estimated**: [X hours]
**Files**: [List specific files]
### File Map
[Optional but recommended for API/UI/Integration phases]
- `src/[file-name].ts` (estimated ~XXX lines)
- **Purpose**: [What this file does]
- **Key exports**: [Main functions/components/types]
- **Dependencies**: [What it imports]
- **Used by**: [What uses it]
- `src/[another-file].ts` (modify existing)
- **Purpose**: [What this file does]
- **Modifications**: [What changes to make]
### Data Flow
[Optional - Use Mermaid diagram for complex interactions]
```mermaid
sequenceDiagram
participant A as Component A
participant B as Component B
A->>B: Action
B->>A: Response
```
### Critical Dependencies
**Internal**: [List codebase files this phase depends on]
**External**: [List npm packages needed]
**Configuration**: [Environment variables, config files]
**Bindings**: [Cloudflare bindings needed: D1, R2, KV, etc]
### Gotchas & Known Issues
[Optional but valuable - Document non-obvious behavior]
- **[Issue name]**: [Description and pattern/solution]
- **[Another issue]**: [Description and pattern/solution]
### Tasks
- [ ] [Specific task 1]
- [ ] [Specific task 2]
- [ ] [Specific task 3]
- [ ] Test basic functionality
### Verification Criteria
- [ ] [Specific test 1]
- [ ] [Specific test 2]
- [ ] [Specific test 3]
### Exit Criteria
[Clear definition of when this phase is complete]
---
## Phase 3: [Name]
**Type**: [Database/API/UI/Integration]
**Estimated**: [X hours]
**Files**: [List specific files]
### File Map
[Include for API/UI/Integration phases]
### Data Flow
[Include Mermaid diagram if helpful]
### Critical Dependencies
**Internal**: | **External**: | **Configuration**: | **Bindings**:
### Gotchas & Known Issues
[Document non-obvious behavior]
### Tasks
- [ ] [Tasks...]
### Verification Criteria
- [ ] [Tests...]
### Exit Criteria
[Definition of done]
---
## Phase 4: [Name]
**Type**: [Database/API/UI/Integration]
**Estimated**: [X hours]
**Files**: [List specific files]
### File Map
[Include for API/UI/Integration phases]
### Data Flow
[Include Mermaid diagram if helpful]
### Critical Dependencies
**Internal**: | **External**: | **Configuration**: | **Bindings**:
### Gotchas & Known Issues
[Document non-obvious behavior]
### Tasks
- [ ] [Tasks...]
### Verification Criteria
- [ ] [Tests...]
### Exit Criteria
[Definition of done]
---
## Phase 5: [Name - Optional: Testing/Polish/Deployment]
**Type**: [Testing/Integration]
**Estimated**: [X hours]
**Files**: [List specific files]
### Tasks
- [ ] [Tasks...]
### Verification Criteria
- [ ] [Tests...]
### Exit Criteria
[Definition of done]
---
## Notes
### Testing Strategy
[Describe when/how testing happens]
- Option A: Testing built into each phase (verification criteria)
- Option B: Separate testing phase at end
- Option C: Hybrid - smoke tests per phase, comprehensive tests at end
### Deployment Strategy
[Describe deployment approach]
- Option A: Deploy after each phase (continuous deployment)
- Option B: Deploy at milestones (after groups of phases)
- Option C: Deploy at end (single production push)
### Context Management
Phases are sized to fit within a single session including:
- Implementation time
- Verification time
- Expected debugging/fixes time
- Documentation updates
If a phase can't be completed in one session, it should be split into sub-phases (e.g., Phase 3.1, Phase 3.2).
### Dependencies
Phases are ordered to minimize dependencies:
1. Infrastructure (no dependencies)
2. Database (depends on Infrastructure)
3. API (depends on Infrastructure + Database)
4. UI (depends on API)
5. Integration (depends on relevant phases)
6. Testing (depends on all implementation phases)
### Known Risks
[Optional: List any known challenges or uncertainties]
- Risk 1: [Description and mitigation]
- Risk 2: [Description and mitigation]
---
## Success Metrics
**Phase Completion**: All verification criteria met, exit criteria satisfied
**Session Handoff**: SESSION.md updated, git checkpoint created
**Overall Success**: [Define what "done" means for this project]
---
## Revision History
**v1.0** ([Date]): Initial phase breakdown
**v1.1** ([Date]): [Changes made]

612
templates/INTEGRATION.md Normal file
View File

@@ -0,0 +1,612 @@
# Third-Party Integrations: [Project Name]
**Primary Integrations**: [List main services - e.g., Clerk, Stripe, OpenAI]
**Webhooks**: [Number of webhook handlers]
**Last Updated**: [Date]
---
## Overview
This document describes all third-party service integrations including API setup, authentication, webhooks, and error handling.
**Integration Principles**:
- **Environment-based config** - API keys in env vars, never committed
- **Graceful degradation** - App works (reduced functionality) if service is down
- **Webhook verification** - Always verify webhook signatures
- **Rate limit awareness** - Respect provider rate limits
- **Error handling** - Catch and log integration failures
---
## Integrations
### Clerk (Authentication)
**Purpose**: User authentication and management
**Docs**: https://clerk.com/docs
**Dashboard**: https://dashboard.clerk.com
**Setup**:
```bash
npm install @clerk/clerk-react @clerk/backend
```
**Environment Variables**:
```env
# Frontend
VITE_CLERK_PUBLISHABLE_KEY=pk_test_...
# Backend (Wrangler secret)
CLERK_SECRET_KEY=sk_test_...
```
**Frontend Integration**:
```tsx
// src/main.tsx
import { ClerkProvider } from '@clerk/clerk-react'
ReactDOM.createRoot(document.getElementById('root')!).render(
<ClerkProvider publishableKey={import.meta.env.VITE_CLERK_PUBLISHABLE_KEY}>
<App />
</ClerkProvider>
)
```
**Backend Integration** (JWT verification):
```typescript
// src/middleware/auth.ts
import { verifyToken } from '@clerk/backend'
export async function authMiddleware(c: Context, next: Next) {
const token = c.req.header('Authorization')?.replace('Bearer ', '')
if (!token) {
return c.json({ error: 'Unauthorized' }, 401)
}
try {
const verified = await verifyToken(token, {
secretKey: c.env.CLERK_SECRET_KEY
})
c.set('userId', verified.sub)
c.set('email', verified.email)
await next()
} catch (error) {
return c.json({ error: 'Invalid token' }, 401)
}
}
```
**Custom JWT Template** (configured in Clerk dashboard):
```json
{
"email": "{{user.primary_email_address}}",
"userId": "{{user.id}}",
"firstName": "{{user.first_name}}",
"lastName": "{{user.last_name}}"
}
```
**Webhooks**: See webhook section below
**Rate Limits**: 100 requests/second (Pro plan)
**Error Handling**:
- Token expired → Return 401, frontend refreshes token
- Service down → Return 503, show maintenance message
---
### [Integration Name - e.g., Stripe]
**Purpose**: [What this service provides - e.g., Payment processing]
**Docs**: [Documentation URL]
**Dashboard**: [Dashboard URL]
**Setup**:
```bash
npm install [package-name]
```
**Environment Variables**:
```env
[SERVICE]_API_KEY=...
[SERVICE]_WEBHOOK_SECRET=...
```
**API Client**:
```typescript
// src/lib/[service]-client.ts
import [ServiceSDK] from '[package-name]'
export function create[Service]Client(apiKey: string) {
return new [ServiceSDK]({
apiKey,
// other config
})
}
```
**Usage Example**:
```typescript
// In route handler
const client = create[Service]Client(c.env.[SERVICE]_API_KEY)
const result = await client.[method]({ params })
```
**Webhooks**: [Yes/No - see webhook section if yes]
**Rate Limits**: [Limits for this service]
**Error Handling**: [How to handle failures]
---
### OpenAI (AI Features) - Example
**Purpose**: AI-powered features (chat, completions, embeddings)
**Docs**: https://platform.openai.com/docs
**Dashboard**: https://platform.openai.com
**Setup**:
```bash
npm install openai
```
**Environment Variables**:
```env
OPENAI_API_KEY=sk-...
```
**API Client**:
```typescript
// src/lib/openai-client.ts
import OpenAI from 'openai'
export function createOpenAIClient(apiKey: string) {
return new OpenAI({ apiKey })
}
```
**Usage**:
```typescript
const openai = createOpenAIClient(c.env.OPENAI_API_KEY)
const response = await openai.chat.completions.create({
model: 'gpt-5',
messages: [{ role: 'user', content: 'Hello!' }],
stream: true
})
// Stream response to client
return new Response(response.body, {
headers: { 'Content-Type': 'text/event-stream' }
})
```
**Rate Limits**:
- Free tier: 3 requests/minute
- Paid: 10,000 requests/minute
**Error Handling**:
```typescript
try {
const response = await openai.chat.completions.create({ ... })
return response
} catch (error) {
if (error.code === 'rate_limit_exceeded') {
return c.json({ error: 'Too many requests. Please try again later.' }, 429)
} else {
console.error('OpenAI error:', error)
return c.json({ error: 'AI service unavailable' }, 503)
}
}
```
---
## Webhooks
Webhooks are HTTP callbacks from third-party services to notify our app of events.
### Webhook: Clerk User Events
**Purpose**: Sync user data when Clerk users are created, updated, or deleted
**Endpoint**: `POST /api/webhooks/clerk`
**Events**:
- `user.created` - New user signed up
- `user.updated` - User profile changed
- `user.deleted` - User account deleted
**Payload Example**:
```json
{
"type": "user.created",
"data": {
"id": "user_abc123",
"email_addresses": [
{ "email_address": "user@example.com" }
],
"first_name": "John",
"last_name": "Doe"
}
}
```
**Signature Verification**:
```typescript
import { Webhook } from 'svix'
export async function verifyClerkWebhook(
payload: string,
headers: Headers,
secret: string
) {
const wh = new Webhook(secret)
try {
return wh.verify(payload, {
'svix-id': headers.get('svix-id')!,
'svix-timestamp': headers.get('svix-timestamp')!,
'svix-signature': headers.get('svix-signature')!
})
} catch (error) {
throw new Error('Invalid webhook signature')
}
}
```
**Handler**:
```typescript
app.post('/api/webhooks/clerk', async (c) => {
const payload = await c.req.text()
const verified = await verifyClerkWebhook(
payload,
c.req.raw.headers,
c.env.CLERK_WEBHOOK_SECRET
)
const event = JSON.parse(payload)
switch (event.type) {
case 'user.created':
await createUserInDatabase(event.data)
break
case 'user.updated':
await updateUserInDatabase(event.data)
break
case 'user.deleted':
await deleteUserInDatabase(event.data.id)
break
}
return c.json({ received: true })
})
```
**Configuration** (Clerk dashboard):
1. Go to Webhooks section
2. Add endpoint: `https://[your-app].workers.dev/api/webhooks/clerk`
3. Select events: user.created, user.updated, user.deleted
4. Copy signing secret to environment variables
---
### Webhook: [Service Name]
**Purpose**: [What events this webhook handles]
**Endpoint**: `POST /api/webhooks/[service]`
**Events**: [List of event types]
**Signature Verification**: [How to verify]
**Handler**: [Implementation details]
---
## Environment Variables
### Development (.dev.vars)
```env
# Clerk
CLERK_SECRET_KEY=sk_test_...
CLERK_WEBHOOK_SECRET=whsec_...
# OpenAI
OPENAI_API_KEY=sk-...
# Stripe
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
# [Other services]
```
### Production (Wrangler secrets)
```bash
# Set secrets via CLI
npx wrangler secret put CLERK_SECRET_KEY
npx wrangler secret put OPENAI_API_KEY
npx wrangler secret put STRIPE_SECRET_KEY
# Or via dashboard
# Cloudflare Dashboard → Workers → [Your Worker] → Settings → Variables
```
**Never commit secrets to git**. Use `.dev.vars` locally (gitignored) and Wrangler secrets in production.
---
## API Rate Limiting
### Handling Rate Limits
**Strategy**: Exponential backoff + retry
**Implementation**:
```typescript
async function callWithRetry<T>(
fn: () => Promise<T>,
maxRetries = 3
): Promise<T> {
let attempt = 0
while (attempt < maxRetries) {
try {
return await fn()
} catch (error) {
if (error.code === 'rate_limit' && attempt < maxRetries - 1) {
const delay = Math.pow(2, attempt) * 1000 // 1s, 2s, 4s
await new Promise(resolve => setTimeout(resolve, delay))
attempt++
} else {
throw error
}
}
}
throw new Error('Max retries exceeded')
}
// Usage
const result = await callWithRetry(() =>
openai.chat.completions.create({ ... })
)
```
---
## Error Handling
### Integration Failure Patterns
**1. Service Temporarily Down**:
```typescript
try {
const result = await callExternalService()
return result
} catch (error) {
console.error('Service error:', error)
return c.json({
error: 'Service temporarily unavailable. Please try again later.',
code: 'SERVICE_UNAVAILABLE'
}, 503)
}
```
**2. Invalid Credentials**:
```typescript
if (error.code === 'invalid_api_key') {
console.error('Invalid API key for [service]')
return c.json({
error: 'Configuration error. Please contact support.',
code: 'CONFIGURATION_ERROR'
}, 500)
}
```
**3. Rate Limited**:
```typescript
if (error.code === 'rate_limit_exceeded') {
return c.json({
error: 'Too many requests. Please wait a moment and try again.',
code: 'RATE_LIMIT_EXCEEDED',
retryAfter: error.retryAfter
}, 429)
}
```
---
## Testing Integrations
### Mocking External Services
**Use MSW (Mock Service Worker)** for tests:
```typescript
// tests/mocks/handlers.ts
import { http, HttpResponse } from 'msw'
export const handlers = [
http.post('https://api.openai.com/v1/chat/completions', () => {
return HttpResponse.json({
choices: [
{ message: { content: 'Mocked response' } }
]
})
})
]
```
**Setup**:
```typescript
// tests/setup.ts
import { setupServer } from 'msw/node'
import { handlers } from './mocks/handlers'
const server = setupServer(...handlers)
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
```
---
### Testing Webhooks
**Test webhook signature verification**:
```typescript
describe('POST /api/webhooks/clerk', () => {
it('rejects invalid signature', async () => {
const res = await app.request('/api/webhooks/clerk', {
method: 'POST',
headers: {
'svix-signature': 'invalid_signature'
},
body: JSON.stringify({ type: 'user.created', data: {} })
})
expect(res.status).toBe(401)
})
it('processes valid webhook', async () => {
// Generate valid signature for testing
const payload = JSON.stringify({ type: 'user.created', data: {...} })
const signature = generateTestSignature(payload)
const res = await app.request('/api/webhooks/clerk', {
method: 'POST',
headers: {
'svix-signature': signature,
'svix-id': 'msg_123',
'svix-timestamp': Date.now().toString()
},
body: payload
})
expect(res.status).toBe(200)
})
})
```
---
## Monitoring
### Track Integration Health
**Metrics**:
- API call success rate (per service)
- API call latency (average, p95, p99)
- Rate limit hits
- Webhook delivery success rate
**Logging**:
```typescript
console.log('[Integration]', {
service: 'openai',
method: 'chat.completions.create',
success: true,
latency: responseTime,
tokensUsed: response.usage.total_tokens
})
```
**Alerts**:
- Integration success rate < 95% → Alert
- Average latency > 5s → Alert
- Webhook failures > 10 in 1 hour → Alert
---
## Graceful Degradation
**If integration fails, app should still function** (with reduced features).
**Example**: AI chat unavailable
```typescript
try {
const response = await openai.chat.completions.create({ ... })
return response
} catch (error) {
console.error('OpenAI unavailable:', error)
// Fallback: Return canned response
return {
content: "I'm currently unavailable. Please try again later or contact support.",
fallback: true
}
}
```
**Example**: Payment processing down
```typescript
if (!stripe.isHealthy()) {
return (
<Alert variant="warning">
Payment processing is temporarily unavailable. Please try again later.
</Alert>
)
}
```
---
## Security Best Practices
### API Keys
- ✅ Store in environment variables
- ✅ Use Wrangler secrets in production
- ✅ Rotate keys periodically
- ❌ Never commit to git
- ❌ Never log in production
### Webhook Security
- ✅ Always verify signatures
- ✅ Use HTTPS endpoints only
- ✅ Validate payload structure
- ✅ Implement replay protection (check timestamp)
- ❌ Never trust webhook data without verification
### CORS
- ✅ Restrict origins to your domain
- ✅ Allow only necessary methods
- ❌ Don't use wildcard (*) in production
---
## Future Integrations
Planned integrations:
- [ ] [Service name] - [Purpose]
- [ ] [Service name] - [Purpose]
---
## Integration Checklist
When adding a new integration:
- [ ] Install SDK/package
- [ ] Add environment variables
- [ ] Create API client wrapper
- [ ] Implement error handling
- [ ] Add rate limit handling
- [ ] Setup webhook handler (if applicable)
- [ ] Verify webhook signatures
- [ ] Write tests (mock external calls)
- [ ] Document in this file
- [ ] Add monitoring/logging
- [ ] Test in staging before production
---
## Revision History
**v1.0** ([Date]): Initial integration documentation
**v1.1** ([Date]): [Changes made]

508
templates/TESTING.md Normal file
View File

@@ -0,0 +1,508 @@
# Testing Strategy: [Project Name]
**Testing Framework**: Vitest (unit/integration)
**E2E Framework**: Playwright (optional)
**Coverage Target**: 70%+ for critical paths
**Last Updated**: [Date]
---
## Overview
This document defines the testing strategy including what to test, how to test, and when to test.
**Testing Philosophy**:
- **Test behavior, not implementation** - Focus on user-facing functionality
- **Test critical paths first** - Auth, data CRUD, payments, etc
- **Fast feedback** - Unit tests run in milliseconds
- **Realistic tests** - Integration tests use real-ish data
- **E2E for happy paths** - Cover main user workflows
**Testing Pyramid**:
```
┌──────────┐
│ E2E │ ← Few (slow, brittle)
└──────────┘
┌──────────────┐
│ Integration │ ← Some (medium speed)
└──────────────┘
┌───────────────────┐
│ Unit Tests │ ← Many (fast, focused)
└───────────────────┘
```
---
## What to Test
### ✅ Do Test
**Business Logic**:
- Data validation (Zod schemas)
- Data transformations
- Complex calculations
- State management logic
**API Endpoints**:
- All HTTP methods (GET, POST, PATCH, DELETE)
- All response codes (200, 400, 401, 404, 500)
- Request validation
- Authorization checks
**User Workflows**:
- Authentication flow
- CRUD operations (create, read, update, delete)
- Form submissions
- Error handling
**Critical Paths**:
- Payment processing (if applicable)
- Data export/import
- Email notifications
- File uploads
---
### ❌ Don't Test
**Third-party libraries**: Trust that React, Tailwind, shadcn/ui work
**Implementation details**: Internal function calls, component props (unless part of public API)
**Trivial code**: Simple getters/setters, pass-through functions
**UI styling**: Visual regression testing is overkill for most projects
---
## Testing Layers
### 1. Unit Tests (Many)
**Purpose**: Test individual functions and utilities in isolation
**Tool**: Vitest
**What to test**:
- Utility functions (`src/lib/utils.ts`)
- Zod schemas (`src/lib/schemas.ts`)
- Data transformations
- Pure functions
**Example**:
```typescript
// src/lib/utils.test.ts
import { describe, it, expect } from 'vitest'
import { formatDate, cn } from './utils'
describe('formatDate', () => {
it('formats Unix timestamp to readable date', () => {
expect(formatDate(1234567890)).toBe('Feb 14, 2009')
})
it('handles invalid timestamps', () => {
expect(formatDate(-1)).toBe('Invalid Date')
})
})
describe('cn', () => {
it('merges class names', () => {
expect(cn('foo', 'bar')).toBe('foo bar')
})
it('handles conditional classes', () => {
expect(cn('foo', false && 'bar', 'baz')).toBe('foo baz')
})
})
```
**Run**: `npm run test`
---
### 2. Integration Tests (Some)
**Purpose**: Test API endpoints with real database interactions
**Tool**: Vitest + Miniflare (Cloudflare Workers simulator)
**What to test**:
- API routes (`/api/*`)
- Middleware (auth, CORS, error handling)
- Database operations (CRUD)
**Setup**:
```typescript
// tests/setup.ts
import { beforeAll, afterAll, beforeEach } from 'vitest'
import { env, createExecutionContext, waitOnExecutionContext } from 'cloudflare:test'
beforeAll(async () => {
// Setup test database
await env.DB.exec('CREATE TABLE IF NOT EXISTS users (...)')
})
beforeEach(async () => {
// Clear database before each test
await env.DB.exec('DELETE FROM users')
})
afterAll(async () => {
// Cleanup
})
```
**Example Test**:
```typescript
// tests/api/tasks.test.ts
import { describe, it, expect } from 'vitest'
import app from '../../src/index'
describe('POST /api/tasks', () => {
it('creates a task for authenticated user', async () => {
const res = await app.request('/api/tasks', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer valid_jwt_token'
},
body: JSON.stringify({
title: 'Test Task',
description: 'Test Description'
})
})
expect(res.status).toBe(201)
const data = await res.json()
expect(data.title).toBe('Test Task')
})
it('returns 401 without auth', async () => {
const res = await app.request('/api/tasks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'Test' })
})
expect(res.status).toBe(401)
})
it('returns 400 with invalid data', async () => {
const res = await app.request('/api/tasks', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer valid_jwt_token'
},
body: JSON.stringify({
title: '' // invalid: empty title
})
})
expect(res.status).toBe(400)
})
})
```
**Run**: `npm run test`
---
### 3. E2E Tests (Few) - Optional
**Purpose**: Test complete user workflows in real browser
**Tool**: Playwright
**What to test**:
- Authentication flow (sign up, sign in, sign out)
- Main CRUD workflows
- Critical paths (payments, exports, etc)
**Setup**:
```bash
npm install -D @playwright/test
npx playwright install
```
**Example Test**:
```typescript
// e2e/auth.spec.ts
import { test, expect } from '@playwright/test'
test('user can sign in and create a task', async ({ page }) => {
// Sign in
await page.goto('http://localhost:5173')
await page.click('text=Sign In')
await page.fill('input[name="email"]', 'test@example.com')
await page.fill('input[name="password"]', 'password123')
await page.click('button[type="submit"]')
// Wait for redirect to dashboard
await expect(page).toHaveURL(/\/dashboard/)
// Create task
await page.click('text=Create Task')
await page.fill('input[name="title"]', 'New Task')
await page.fill('textarea[name="description"]', 'Task description')
await page.click('button:has-text("Save")')
// Verify task appears
await expect(page.locator('text=New Task')).toBeVisible()
})
```
**Run**: `npm run test:e2e`
---
## Test Coverage
**Target Coverage**: 70%+ for critical code
**What to cover**:
- ✅ All API routes
- ✅ All middleware
- ✅ Business logic and utilities
- ✅ Zod schemas (validation tests)
- ⚠️ React components (optional - prefer E2E for UI)
**Generate Coverage Report**:
```bash
npm run test -- --coverage
```
**View Report**: `coverage/index.html`
---
## Testing Checklist
Before marking a phase complete, verify:
### API Phase
- [ ] All endpoints return correct status codes (200, 400, 401, 404, 500)
- [ ] Request validation works (invalid data → 400)
- [ ] Authorization works (no token → 401, wrong user → 403)
- [ ] Database operations succeed
- [ ] Error handling catches exceptions
### UI Phase
- [ ] Forms validate input (Zod schemas)
- [ ] Forms show error messages
- [ ] Loading states display correctly
- [ ] Error states display correctly
- [ ] Happy path works (create, read, update, delete)
### Integration Phase
- [ ] Third-party service integration works
- [ ] Webhooks fire correctly
- [ ] Error handling for external failures
---
## Manual Testing
**Automated tests don't catch everything**. Manually test:
### Before Each Deployment
- [ ] Sign in works
- [ ] Main workflows work (create, edit, delete)
- [ ] Forms validate correctly
- [ ] Errors display properly
- [ ] Dark/light mode works
- [ ] Mobile layout works
### Smoke Test Checklist
```
1. Visit homepage → Should load
2. Click Sign In → Should show Clerk modal
3. Sign in → Should redirect to dashboard
4. Create [resource] → Should appear in list
5. Edit [resource] → Changes should save
6. Delete [resource] → Should remove from list
7. Sign out → Should return to homepage
```
---
## Continuous Integration (CI)
**GitHub Actions** (optional):
```yaml
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- run: npm install
- run: npm run test
- run: npm run build
```
**Run tests on**:
- Every push
- Every pull request
- Before deployment
---
## Test Data
### Seed Data for Testing
Create `migrations/seed.sql` with realistic test data:
```sql
INSERT INTO users (email, clerk_id, display_name, created_at, updated_at)
VALUES
('alice@example.com', 'clerk_alice', 'Alice', strftime('%s', 'now'), strftime('%s', 'now')),
('bob@example.com', 'clerk_bob', 'Bob', strftime('%s', 'now'), strftime('%s', 'now'));
INSERT INTO tasks (user_id, title, description, created_at, updated_at)
VALUES
(1, 'Review PR', 'Review the new feature PR', strftime('%s', 'now'), strftime('%s', 'now')),
(1, 'Write tests', 'Add tests for API endpoints', strftime('%s', 'now'), strftime('%s', 'now')),
(2, 'Deploy app', 'Deploy to production', strftime('%s', 'now'), strftime('%s', 'now'));
```
**Load seed data**:
```bash
npx wrangler d1 execute [DB_NAME] --local --file=migrations/seed.sql
```
---
## Testing Tools
### Installed
- **Vitest**: Unit and integration tests
- **@cloudflare/vitest-pool-workers**: Test Workers in Miniflare
- **@vitest/coverage-v8**: Code coverage
### Optional
- **Playwright**: E2E browser testing
- **@testing-library/react**: React component testing (if needed)
- **MSW**: Mock Service Worker (mock external APIs)
---
## Test Scripts
**package.json**:
```json
{
"scripts": {
"test": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest --coverage",
"test:e2e": "playwright test"
}
}
```
**Run tests**:
```bash
npm run test # Run all tests (watch mode)
npm run test:coverage # Generate coverage report
npm run test:e2e # Run E2E tests (Playwright)
```
---
## Debugging Tests
**Vitest UI** (visual test runner):
```bash
npm run test:ui
```
**Debug single test**:
```typescript
it.only('specific test', () => {
// Only this test runs
})
```
**Console logs in tests**: They appear in terminal output
**Playwright debug mode**:
```bash
npx playwright test --debug
```
---
## Testing Best Practices
### ✅ Do
- **Arrange, Act, Assert** - Structure tests clearly
- **One assertion per test** (when possible)
- **Descriptive test names** - "should return 401 when token is missing"
- **Test edge cases** - Empty strings, null values, large numbers
- **Use realistic data** - Test with production-like data
### ❌ Don't
- **Test implementation details** - Internal state, private methods
- **Over-mock** - Use real database in integration tests
- **Brittle selectors** - Avoid testing specific CSS classes
- **Flaky tests** - Fix immediately or remove
- **Slow tests** - Optimize or move to E2E
---
## Test-Driven Development (TDD) - Optional
**For critical features**, consider writing tests first:
1. Write failing test
2. Implement feature (test passes)
3. Refactor
4. Repeat
**Benefits**: Better design, fewer bugs, built-in documentation
**When to use**: Complex business logic, critical paths, bug fixes
---
## Monitoring Test Health
**Metrics to track**:
- Test pass rate (should be 100%)
- Test coverage (target 70%+)
- Test execution time (keep fast)
- Flaky test count (should be 0)
**Weekly review**:
- Are tests passing?
- Is coverage dropping?
- Are tests slowing down?
- Any flaky tests to fix?
---
## Future Testing Enhancements
- [ ] Add visual regression testing (if needed)
- [ ] Add performance testing (if needed)
- [ ] Add accessibility testing (axe-core)
- [ ] Add API contract testing (Pact)
---
## Revision History
**v1.0** ([Date]): Initial testing strategy
**v1.1** ([Date]): [Changes made]

653
templates/UI_COMPONENTS.md Normal file
View File

@@ -0,0 +1,653 @@
# UI Components: [Project Name]
**Framework**: React 19
**Component Library**: shadcn/ui (Radix UI primitives)
**Styling**: Tailwind v4
**Forms**: React Hook Form + Zod
**State**: TanStack Query (server) + Zustand (client)
**Last Updated**: [Date]
---
## Overview
This document outlines the component hierarchy, reusable components, forms, and UI patterns.
**Component Philosophy**:
- **Composition over configuration** - Build complex UIs from simple parts
- **Accessibility first** - All components keyboard navigable, ARIA compliant
- **shadcn/ui ownership** - Components copied to codebase, not npm dependency
- **Tailwind utility classes** - Minimal custom CSS
- **Dark mode support** - All components adapt to theme
---
## Component Hierarchy
```
App
├── Providers
│ ├── ClerkProvider (auth)
│ ├── ThemeProvider (dark/light mode)
│ └── QueryClientProvider (TanStack Query)
├── Layout
│ ├── Header
│ │ ├── Logo
│ │ ├── Navigation
│ │ └── UserMenu
│ ├── Main (page content)
│ └── Footer (optional)
└── Pages (routes)
├── HomePage
│ ├── HeroSection
│ ├── FeaturesSection
│ └── CTASection
├── DashboardPage
│ ├── Sidebar
│ │ └── NavLinks
│ └── DashboardContent
│ ├── StatsCards
│ └── [ResourceList]
├── [Resource]Page
│ ├── [Resource]List
│ │ ├── [Resource]Card (for each item)
│ │ └── Pagination
│ └── [Resource]CreateDialog
└── [Resource]DetailPage
├── [Resource]Header
├── [Resource]Info
└── [Resource]Actions
```
---
## Core Layout Components
### `App.tsx`
**Purpose**: Root component with providers
**Structure**:
```tsx
import { ClerkProvider } from '@clerk/clerk-react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ThemeProvider } from '@/components/theme-provider'
import { Router } from '@/router'
const queryClient = new QueryClient()
export function App() {
return (
<ClerkProvider publishableKey={...}>
<QueryClientProvider client={queryClient}>
<ThemeProvider defaultTheme="system" storageKey="app-theme">
<Router />
</ThemeProvider>
</QueryClientProvider>
</ClerkProvider>
)
}
```
---
### `Layout.tsx`
**Purpose**: Common layout wrapper for authenticated pages
**Props**: None (uses `Outlet` from react-router)
**Structure**:
```tsx
export function Layout() {
return (
<div className="min-h-screen bg-background">
<Header />
<main className="container mx-auto px-4 py-8">
<Outlet />
</main>
<Footer />
</div>
)
}
```
**Files**: `src/components/layout/Layout.tsx`, `Header.tsx`, `Footer.tsx`
---
### `Header.tsx`
**Purpose**: Top navigation bar
**Features**:
- Logo/brand
- Navigation links
- User menu (authenticated) or Sign In button (unauthenticated)
- Theme toggle (dark/light mode)
**Structure**:
```tsx
export function Header() {
return (
<header className="border-b bg-background">
<div className="container mx-auto flex h-16 items-center justify-between px-4">
<div className="flex items-center gap-6">
<Logo />
<Navigation />
</div>
<div className="flex items-center gap-4">
<ThemeToggle />
<UserButton />
</div>
</div>
</header>
)
}
```
**Files**: `src/components/layout/Header.tsx`
---
## shadcn/ui Components Used
### Installed Components
Run these to add components:
```bash
pnpm dlx shadcn@latest add button
pnpm dlx shadcn@latest add dialog
pnpm dlx shadcn@latest add dropdown-menu
pnpm dlx shadcn@latest add form
pnpm dlx shadcn@latest add input
pnpm dlx shadcn@latest add label
pnpm dlx shadcn@latest add select
pnpm dlx shadcn@latest add textarea
pnpm dlx shadcn@latest add card
pnpm dlx shadcn@latest add badge
pnpm dlx shadcn@latest add avatar
pnpm dlx shadcn@latest add skeleton
pnpm dlx shadcn@latest add toast
```
**Location**: `src/components/ui/`
**Ownership**: These are now part of our codebase, modify as needed
---
## Custom Components
### `[Resource]List.tsx`
**Purpose**: Display list of [resources] with loading/error states
**Props**:
```typescript
interface [Resource]ListProps {
// No props - uses TanStack Query internally
}
```
**Structure**:
```tsx
export function [Resource]List() {
const { data, isLoading, error } = useQuery({
queryKey: ['[resources]'],
queryFn: fetch[Resources]
})
if (isLoading) return <[Resource]ListSkeleton />
if (error) return <ErrorMessage error={error} />
return (
<div className="space-y-4">
{data.map(item => (
<[Resource]Card key={item.id} item={item} />
))}
</div>
)
}
```
**Files**: `src/components/[resource]/[Resource]List.tsx`
---
### `[Resource]Card.tsx`
**Purpose**: Display single [resource] item
**Props**:
```typescript
interface [Resource]CardProps {
item: [Resource]
onEdit?: (id: number) => void
onDelete?: (id: number) => void
}
```
**Structure**:
```tsx
export function [Resource]Card({ item, onEdit, onDelete }: [Resource]CardProps) {
return (
<Card>
<CardHeader>
<CardTitle>{item.title}</CardTitle>
</CardHeader>
<CardContent>
<p>{item.description}</p>
</CardContent>
<CardFooter>
<Button onClick={() => onEdit?.(item.id)}>Edit</Button>
<Button variant="destructive" onClick={() => onDelete?.(item.id)}>Delete</Button>
</CardFooter>
</Card>
)
}
```
**Files**: `src/components/[resource]/[Resource]Card.tsx`
---
### `[Resource]Form.tsx`
**Purpose**: Create or edit [resource]
**Props**:
```typescript
interface [Resource]FormProps {
item?: [Resource] // undefined for create, populated for edit
onSuccess?: () => void
}
```
**Structure** (using React Hook Form + Zod):
```tsx
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { [resource]Schema } from '@/lib/schemas'
export function [Resource]Form({ item, onSuccess }: [Resource]FormProps) {
const form = useForm({
resolver: zodResolver([resource]Schema),
defaultValues: item || {
field1: '',
field2: ''
}
})
const mutation = useMutation({
mutationFn: item ? update[Resource] : create[Resource],
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['[resources]'] })
onSuccess?.()
}
})
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(data => mutation.mutate(data))}>
<FormField name="field1" control={form.control} render={({ field }) => (
<FormItem>
<FormLabel>Field 1</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)} />
<Button type="submit" disabled={mutation.isPending}>
{mutation.isPending ? 'Saving...' : 'Save'}
</Button>
</form>
</Form>
)
}
```
**Files**: `src/components/[resource]/[Resource]Form.tsx`
---
### `[Resource]CreateDialog.tsx`
**Purpose**: Modal dialog for creating new [resource]
**Structure**:
```tsx
export function [Resource]CreateDialog() {
const [open, setOpen] = useState(false)
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button>Create [Resource]</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Create New [Resource]</DialogTitle>
</DialogHeader>
<[Resource]Form onSuccess={() => setOpen(false)} />
</DialogContent>
</Dialog>
)
}
```
**Files**: `src/components/[resource]/[Resource]CreateDialog.tsx`
---
## Loading and Error States
### `[Resource]ListSkeleton.tsx`
**Purpose**: Loading state for [resource] list
**Structure**:
```tsx
export function [Resource]ListSkeleton() {
return (
<div className="space-y-4">
{[1, 2, 3].map(i => (
<Card key={i}>
<CardHeader>
<Skeleton className="h-6 w-48" />
</CardHeader>
<CardContent>
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-3/4 mt-2" />
</CardContent>
</Card>
))}
</div>
)
}
```
---
### `ErrorMessage.tsx`
**Purpose**: Reusable error display
**Props**:
```typescript
interface ErrorMessageProps {
error: Error
retry?: () => void
}
```
**Structure**:
```tsx
export function ErrorMessage({ error, retry }: ErrorMessageProps) {
return (
<div className="rounded-lg border border-destructive bg-destructive/10 p-4">
<h3 className="font-semibold text-destructive">Error</h3>
<p className="text-sm text-muted-foreground">{error.message}</p>
{retry && (
<Button variant="outline" onClick={retry} className="mt-2">
Retry
</Button>
)}
</div>
)
}
```
---
## State Management Patterns
### Server State (TanStack Query)
**Use for**: Data from API (users, tasks, analytics, etc)
**Example**:
```tsx
// In component
const { data, isLoading, error } = useQuery({
queryKey: ['tasks'],
queryFn: () => api.get('/api/tasks').then(res => res.data)
})
// Mutation
const mutation = useMutation({
mutationFn: (newTask) => api.post('/api/tasks', newTask),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['tasks'] })
}
})
```
**Files**: Queries/mutations defined in `src/lib/api.ts` or component files
---
### Client State (Zustand)
**Use for**: UI state, preferences, filters
**Example Store**:
```tsx
// src/stores/ui-store.ts
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface UIStore {
sidebarOpen: boolean
setSidebarOpen: (open: boolean) => void
theme: 'light' | 'dark' | 'system'
setTheme: (theme: 'light' | 'dark' | 'system') => void
}
export const useUIStore = create<UIStore>()(
persist(
(set) => ({
sidebarOpen: true,
setSidebarOpen: (open) => set({ sidebarOpen: open }),
theme: 'system',
setTheme: (theme) => set({ theme })
}),
{ name: 'ui-store' }
)
)
```
**Usage**:
```tsx
const { sidebarOpen, setSidebarOpen } = useUIStore()
```
---
## Theme System
### ThemeProvider
**Purpose**: Manage dark/light/system theme
**Structure**:
```tsx
// src/components/theme-provider.tsx
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return (
<NextThemesProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
{...props}
>
{children}
</NextThemesProvider>
)
}
export function useTheme() {
return useNextTheme()
}
```
**Files**: `src/components/theme-provider.tsx`
---
### ThemeToggle
**Purpose**: Toggle between light/dark/system themes
**Structure**:
```tsx
export function ThemeToggle() {
const { theme, setTheme } = useTheme()
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<Sun className="rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem onClick={() => setTheme('light')}>Light</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('dark')}>Dark</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('system')}>System</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}
```
---
## Form Patterns
All forms use **React Hook Form + Zod** for validation.
**Schema Definition** (shared client + server):
```typescript
// src/lib/schemas.ts
import { z } from 'zod'
export const taskSchema = z.object({
title: z.string().min(1, 'Title required').max(100),
description: z.string().optional(),
dueDate: z.string().optional(),
priority: z.enum(['low', 'medium', 'high']).default('medium')
})
export type TaskFormData = z.infer<typeof taskSchema>
```
**Form Component**:
```tsx
const form = useForm<TaskFormData>({
resolver: zodResolver(taskSchema),
defaultValues: { ... }
})
```
---
## Responsive Design
**Breakpoints** (Tailwind defaults):
- `sm`: 640px
- `md`: 768px
- `lg`: 1024px
- `xl`: 1280px
- `2xl`: 1536px
**Patterns**:
```tsx
// Stack on mobile, grid on desktop
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
// Hide on mobile, show on desktop
<div className="hidden md:block">
// Different padding on mobile vs desktop
<div className="px-4 md:px-8">
```
---
## Accessibility
**Requirements**:
- All interactive elements keyboard navigable
- Focus states visible (use `ring` classes)
- Images have alt text
- Forms have associated labels
- Color contrast meets WCAG AA
- Screen reader friendly (ARIA labels)
**shadcn/ui benefits**: All components built with Radix UI (accessible by default)
---
## File Structure
```
src/
├── components/
│ ├── ui/ # shadcn/ui components
│ │ ├── button.tsx
│ │ ├── dialog.tsx
│ │ ├── form.tsx
│ │ └── ...
│ ├── layout/ # Layout components
│ │ ├── Header.tsx
│ │ ├── Footer.tsx
│ │ └── Layout.tsx
│ ├── [resource]/ # Feature-specific components
│ │ ├── [Resource]List.tsx
│ │ ├── [Resource]Card.tsx
│ │ ├── [Resource]Form.tsx
│ │ └── [Resource]CreateDialog.tsx
│ ├── theme-provider.tsx
│ └── error-message.tsx
├── lib/
│ ├── schemas.ts # Zod schemas
│ ├── api.ts # API client
│ └── utils.ts # cn() helper
├── stores/
│ └── ui-store.ts # Zustand stores
└── pages/
├── HomePage.tsx
├── DashboardPage.tsx
└── ...
```
---
## Design Tokens
See `src/index.css` for theme configuration.
**Colors** (semantic):
- `background` / `foreground`
- `primary` / `primary-foreground`
- `secondary` / `secondary-foreground`
- `destructive` / `destructive-foreground`
- `muted` / `muted-foreground`
- `accent` / `accent-foreground`
- `border`
- `ring`
**Usage**: These adapt to light/dark mode automatically.
---
## Future Components
Components to build:
- [ ] `[Component]` - [Description]
- [ ] `[Component]` - [Description]
---
## Revision History
**v1.0** ([Date]): Initial component structure
**v1.1** ([Date]): [Changes made]