|
| 1 | +# Cursor IDE Configuration for Draft Builder |
| 2 | + |
| 3 | +## Project-Specific Instructions |
| 4 | + |
| 5 | +This document provides Cursor IDE with project-specific coding standards, patterns, and conventions for the Draft Builder fantasy sports application. These instructions should be used in conjunction with Cursor's AI assistance to maintain consistency and follow established patterns. |
| 6 | + |
| 7 | +## Documentation and Design Resources |
| 8 | + |
| 9 | +**For AI Programming Assistants**: Before beginning work on this project, consult the following directories for comprehensive project context: |
| 10 | + |
| 11 | +### Design Documentation Structure |
| 12 | +``` |
| 13 | +design-docs/ |
| 14 | +├── assistants/ # AI-specific guidance and instructions |
| 15 | +│ ├── cursor-instructions.md # This file - coding standards and patterns |
| 16 | +│ └── ... # Additional AI assistant resources |
| 17 | +├── architecture/ # System design and technical architecture |
| 18 | +├── features/ # Feature specifications and requirements |
| 19 | +├── api/ # API design and integration patterns |
| 20 | +└── ... # Additional high-level design documents |
| 21 | +``` |
| 22 | + |
| 23 | +### Key Information Sources |
| 24 | + |
| 25 | +**High-Level Project Information**: |
| 26 | +- `design-docs/` - Contains architectural decisions, feature specifications, and design rationale |
| 27 | +- Review architecture documents before making structural changes |
| 28 | +- Check feature documentation to understand user requirements and acceptance criteria |
| 29 | + |
| 30 | +**AI Assistant Resources**: |
| 31 | +- `design-docs/assistants/` - Specialized guidance for AI programming tools |
| 32 | +- Contains prompt templates, coding patterns, and assistant-specific instructions |
| 33 | +- Reference these documents when unclear about project conventions or patterns |
| 34 | + |
| 35 | +**Before Starting Development**: |
| 36 | +1. **Read** relevant design documents in `design-docs/` for project context |
| 37 | +2. **Check** `design-docs/assistants/` for AI-specific guidance and examples |
| 38 | +3. **Follow** the established patterns documented in this file |
| 39 | +4. **Consult** existing code examples that follow these conventions |
| 40 | + |
| 41 | +This approach ensures consistency with project vision and maintains alignment with established architectural decisions. |
| 42 | + |
| 43 | +## Technology Stack Context |
| 44 | + |
| 45 | +**Framework**: Next.js 15.3.3 with App Router |
| 46 | +**Language**: TypeScript with strict mode enabled |
| 47 | +**UI**: React 19.1.0 with Tailwind CSS 3.4.1 |
| 48 | +**Testing**: Jest 29.7.0 with React Testing Library |
| 49 | +**State Management**: React hooks and local storage |
| 50 | +**API Integration**: Custom platform adapters for ESPN and Sleeper |
| 51 | +**Caching**: Redis with ioredis client |
| 52 | +**Deployment**: Vercel |
| 53 | + |
| 54 | +## File Organization Standards |
| 55 | + |
| 56 | +### Directory Structure |
| 57 | +``` |
| 58 | +src/ |
| 59 | +├── app/ # Next.js App Router pages and API routes |
| 60 | +│ ├── api/ # Server-side API endpoints |
| 61 | +│ ├── league/[leagueID]/ # Dynamic league-specific routes |
| 62 | +│ └── storage/ # Client-side data persistence |
| 63 | +├── platforms/ # External platform integrations |
| 64 | +│ ├── espn/ # ESPN API wrapper |
| 65 | +│ ├── sleeper/ # Sleeper API wrapper |
| 66 | +│ └── common.ts # Shared platform types |
| 67 | +├── rankings/ # Player ranking systems |
| 68 | +├── redis/ # Redis caching utilities |
| 69 | +└── ui/ # Reusable UI components |
| 70 | +``` |
| 71 | + |
| 72 | +### Naming Conventions |
| 73 | + |
| 74 | +**Files**: |
| 75 | +- Use PascalCase for React components: `MockDraft.tsx` |
| 76 | +- Use camelCase for utilities and APIs: `sleeperApi.ts` |
| 77 | +- Use kebab-case for page routes: `[league-id]/page.tsx` |
| 78 | +- Test files: `ComponentName.test.tsx` |
| 79 | + |
| 80 | +**Variables and Functions**: |
| 81 | +- camelCase for variables and functions: `calculateBudget()` |
| 82 | +- PascalCase for React components: `<LoadingScreen />` |
| 83 | +- UPPER_SNAKE_CASE for constants: `IN_PROGRESS_SELECTIONS_KEY` |
| 84 | + |
| 85 | +**Types and Interfaces**: |
| 86 | +- PascalCase with descriptive names: `LeagueInfo`, `MockPlayer` |
| 87 | +- Suffix with 'State' for state objects: `SearchSettingsState` |
| 88 | +- Use union types for platform distinctions: `Platform = 'sleeper' | 'espn'` |
| 89 | + |
| 90 | +## Code Style Guidelines |
| 91 | + |
| 92 | +### TypeScript Patterns |
| 93 | + |
| 94 | +**Type Safety**: |
| 95 | +```typescript |
| 96 | +// Always use strict typing |
| 97 | +interface Player { |
| 98 | + id: string; // Use string IDs for consistency |
| 99 | + name: string; |
| 100 | + defaultPosition: string; |
| 101 | + positions: string[]; |
| 102 | + suggestedCost?: number; // Optional properties with ? |
| 103 | +} |
| 104 | + |
| 105 | +// Use type guards for runtime checking |
| 106 | +function isValidPlayer(obj: any): obj is Player { |
| 107 | + return obj && |
| 108 | + typeof obj.id === 'string' && |
| 109 | + typeof obj.name === 'string'; |
| 110 | +} |
| 111 | +``` |
| 112 | + |
| 113 | +**Error Handling**: |
| 114 | +```typescript |
| 115 | +// Platform APIs return data or HTTP error codes |
| 116 | +async function fetchData(): Promise<PlayerData | number> { |
| 117 | + try { |
| 118 | + const response = await api.call(); |
| 119 | + return response.data; |
| 120 | + } catch (error) { |
| 121 | + logRequestError("Failed to fetch player data", error); |
| 122 | + return 500; // Return HTTP status code |
| 123 | + } |
| 124 | +} |
| 125 | +``` |
| 126 | + |
| 127 | +**Async Patterns**: |
| 128 | +```typescript |
| 129 | +// Use proper loading states with useCallback |
| 130 | +const handleSubmit = useCallback(async (data: FormData) => { |
| 131 | + if (isSubmitting) return; |
| 132 | + |
| 133 | + try { |
| 134 | + setIsSubmitting(true); |
| 135 | + await submitData(data); |
| 136 | + } finally { |
| 137 | + setIsSubmitting(false); |
| 138 | + } |
| 139 | +}, [isSubmitting]); |
| 140 | +``` |
| 141 | + |
| 142 | +### React Component Patterns |
| 143 | + |
| 144 | +**Component Structure**: |
| 145 | +```typescript |
| 146 | +// Props interface first |
| 147 | +interface ComponentProps { |
| 148 | + required: string; |
| 149 | + optional?: boolean; |
| 150 | + children?: React.ReactNode; |
| 151 | +} |
| 152 | + |
| 153 | +// Component with proper typing |
| 154 | +export default function Component({ required, optional = false }: ComponentProps) { |
| 155 | + // Hooks at the top |
| 156 | + const [state, setState] = useState<StateType>(initialValue); |
| 157 | + const memoizedValue = useMemo(() => expensiveCalculation(), [deps]); |
| 158 | + |
| 159 | + // Event handlers with useCallback |
| 160 | + const handleEvent = useCallback((e: React.MouseEvent) => { |
| 161 | + e.preventDefault(); |
| 162 | + // Handle event |
| 163 | + }, [dependencies]); |
| 164 | + |
| 165 | + // Render |
| 166 | + return ( |
| 167 | + <div className="tailwind-classes"> |
| 168 | + {/* JSX content */} |
| 169 | + </div> |
| 170 | + ); |
| 171 | +} |
| 172 | +``` |
| 173 | + |
| 174 | +**Loading States**: |
| 175 | +```typescript |
| 176 | +// Use LoadingTask pattern for complex loading |
| 177 | +const tasks = useMemo(() => new Set([ |
| 178 | + new LoadingTask(dataPromise, "Loading player data..."), |
| 179 | + new LoadingTask(() => isReady, "Preparing interface...") |
| 180 | +]), [dataPromise, isReady]); |
| 181 | + |
| 182 | +return ( |
| 183 | + <LoadingScreen tasks={tasks}> |
| 184 | + <MainContent /> |
| 185 | + </LoadingScreen> |
| 186 | +); |
| 187 | +``` |
| 188 | + |
| 189 | +### API Integration Patterns |
| 190 | + |
| 191 | +**Platform API Structure**: |
| 192 | +```typescript |
| 193 | +// Extend PlatformApi base class |
| 194 | +export class SleeperApi extends PlatformApi { |
| 195 | + async fetchLeague(season?: SeasonId): Promise<LeagueInfo | number> { |
| 196 | + try { |
| 197 | + const response = await fetchLeagueInfo(this.league.id); |
| 198 | + return importSleeperLeagueInfo(response); |
| 199 | + } catch (error) { |
| 200 | + return this.handleError(error); |
| 201 | + } |
| 202 | + } |
| 203 | +} |
| 204 | + |
| 205 | +// Use factory pattern for API selection |
| 206 | +export function apiFor(league: PlatformLeague): PlatformApi { |
| 207 | + switch (league.platform) { |
| 208 | + case 'sleeper': return new SleeperApi(league); |
| 209 | + case 'espn': return new EspnApi(league); |
| 210 | + default: throw new Error(`Unsupported platform: ${league.platform}`); |
| 211 | + } |
| 212 | +} |
| 213 | +``` |
| 214 | + |
| 215 | +### Testing Standards |
| 216 | + |
| 217 | +**Test File Structure**: |
| 218 | +```typescript |
| 219 | +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; |
| 220 | +import userEvent from '@testing-library/user-event'; |
| 221 | +import ComponentName from './ComponentName'; |
| 222 | + |
| 223 | +describe('ComponentName', () => { |
| 224 | + // Setup/teardown if needed |
| 225 | + beforeEach(() => { |
| 226 | + // Reset mocks |
| 227 | + }); |
| 228 | + |
| 229 | + it('should render with required props', () => { |
| 230 | + render(<ComponentName required="value" />); |
| 231 | + expect(screen.getByText('expected')).toBeInTheDocument(); |
| 232 | + }); |
| 233 | + |
| 234 | + it('should handle user interactions', async () => { |
| 235 | + const user = userEvent.setup(); |
| 236 | + const mockCallback = jest.fn(); |
| 237 | + |
| 238 | + render(<ComponentName onAction={mockCallback} />); |
| 239 | + |
| 240 | + await user.click(screen.getByRole('button')); |
| 241 | + |
| 242 | + expect(mockCallback).toHaveBeenCalledWith(expectedArgs); |
| 243 | + }); |
| 244 | +}); |
| 245 | +``` |
| 246 | + |
| 247 | +**Mock Patterns**: |
| 248 | +```typescript |
| 249 | +// Mock external dependencies |
| 250 | +jest.mock('./api', () => ({ |
| 251 | + fetchData: jest.fn(), |
| 252 | +})); |
| 253 | + |
| 254 | +// Type-safe mocks |
| 255 | +const mockFetchData = fetchData as jest.MockedFunction<typeof fetchData>; |
| 256 | + |
| 257 | +beforeEach(() => { |
| 258 | + mockFetchData.mockReset(); |
| 259 | +}); |
| 260 | +``` |
| 261 | + |
| 262 | +## Common Anti-Patterns to Avoid |
| 263 | + |
| 264 | +**Don't**: |
| 265 | +- Use `any` type except in very specific cases |
| 266 | +- Mutate props or state directly |
| 267 | +- Create components inside render functions |
| 268 | +- Use string refs (use useRef hook) |
| 269 | +- Ignore error boundaries |
| 270 | +- Use index as key in dynamic lists |
| 271 | +- Mix platform-specific logic in shared components |
| 272 | + |
| 273 | +**Do**: |
| 274 | +- Use strict TypeScript typing |
| 275 | +- Implement proper error boundaries |
| 276 | +- Use React.memo for expensive pure components |
| 277 | +- Implement proper loading and error states |
| 278 | +- Use useCallback for event handlers in optimized components |
| 279 | +- Follow the established platform abstraction patterns |
| 280 | + |
| 281 | +## Performance Guidelines |
| 282 | + |
| 283 | +**Optimization Patterns**: |
| 284 | +```typescript |
| 285 | +// Memoize expensive calculations |
| 286 | +const expensiveValue = useMemo(() => { |
| 287 | + return complexCalculation(data); |
| 288 | +}, [data]); |
| 289 | + |
| 290 | +// Memoize components that receive complex props |
| 291 | +const MemoizedComponent = React.memo(Component, (prevProps, nextProps) => { |
| 292 | + return prevProps.complexProp.id === nextProps.complexProp.id; |
| 293 | +}); |
| 294 | + |
| 295 | +// Use dynamic imports for code splitting |
| 296 | +const HeavyComponent = dynamic(() => import('./HeavyComponent'), { |
| 297 | + loading: () => <LoadingSpinner />, |
| 298 | +}); |
| 299 | +``` |
| 300 | + |
| 301 | +## Error Handling Standards |
| 302 | + |
| 303 | +**Client-Side Errors**: |
| 304 | +```typescript |
| 305 | +// Use error boundaries for component errors |
| 306 | +<ErrorBoundary fallback={<ErrorScreen />}> |
| 307 | + <RiskyComponent /> |
| 308 | +</ErrorBoundary> |
| 309 | + |
| 310 | +// Handle async errors gracefully |
| 311 | +const [error, setError] = useState<string | null>(null); |
| 312 | + |
| 313 | +try { |
| 314 | + await riskyOperation(); |
| 315 | +} catch (err) { |
| 316 | + setError(err instanceof Error ? err.message : 'Unknown error'); |
| 317 | +} |
| 318 | +``` |
| 319 | + |
| 320 | +**API Error Handling**: |
| 321 | +```typescript |
| 322 | +// Consistent error logging |
| 323 | +export function logRequestError(message: string, error: any): void { |
| 324 | + console.error("Error with", message); |
| 325 | + console.error("Error type:", typeof error); |
| 326 | + console.error("Error details:", JSON.stringify(error).substring(0, 500)); |
| 327 | +} |
| 328 | +``` |
| 329 | + |
| 330 | +## State Management Patterns |
| 331 | + |
| 332 | +**Local Storage Integration**: |
| 333 | +```typescript |
| 334 | +// Use type-safe local storage hooks |
| 335 | +const [storedData, setStoredData] = useLocalStorage<DataType>('key', defaultValue); |
| 336 | + |
| 337 | +// Handle storage migrations |
| 338 | +const migratedData = migrate(rawStorageData); |
| 339 | +``` |
| 340 | + |
| 341 | +**Form State Management**: |
| 342 | +```typescript |
| 343 | +// Use controlled components with proper validation |
| 344 | +const [formData, setFormData] = useState<FormType>(initialForm); |
| 345 | + |
| 346 | +const handleInputChange = (field: keyof FormType) => (value: any) => { |
| 347 | + setFormData(prev => ({ ...prev, [field]: value })); |
| 348 | +}; |
| 349 | +``` |
| 350 | + |
| 351 | +## Accessibility Requirements |
| 352 | + |
| 353 | +**Always Include**: |
| 354 | +- Proper ARIA labels: `aria-label`, `aria-describedby` |
| 355 | +- Semantic HTML roles: `role="button"`, `role="tooltip"` |
| 356 | +- Keyboard navigation support |
| 357 | +- Screen reader compatible text |
| 358 | +- Color contrast compliance |
| 359 | +- Focus management for dynamic content |
| 360 | + |
| 361 | +## Code Review Checklist |
| 362 | + |
| 363 | +Before submitting code, ensure: |
| 364 | +- [ ] All TypeScript errors resolved |
| 365 | +- [ ] Tests pass and have adequate coverage |
| 366 | +- [ ] Error handling implemented |
| 367 | +- [ ] Loading states provided |
| 368 | +- [ ] Accessibility attributes included |
| 369 | +- [ ] Performance considerations addressed |
| 370 | +- [ ] Follows established patterns |
| 371 | +- [ ] No console.log statements in production code |
| 372 | +- [ ] Proper error logging used |
| 373 | + |
| 374 | +## Import Organization |
| 375 | + |
| 376 | +**Order**: |
| 377 | +1. React and Next.js imports |
| 378 | +2. Third-party libraries |
| 379 | +3. Internal utilities and types |
| 380 | +4. Relative imports |
| 381 | +5. Type-only imports at the end |
| 382 | + |
| 383 | +```typescript |
| 384 | +import React, { useState, useCallback } from 'react'; |
| 385 | +import { useRouter } from 'next/navigation'; |
| 386 | +import axios from 'axios'; |
| 387 | + |
| 388 | +import { apiFor } from '@/platforms/ApiClient'; |
| 389 | +import { LoadingScreen } from '@/ui/LoadingScreen'; |
| 390 | +import { logRequestError } from '@/utils/errors'; |
| 391 | + |
| 392 | +import './Component.module.css'; |
| 393 | + |
| 394 | +import type { PlatformLeague } from '@/platforms/common'; |
| 395 | +``` |
0 commit comments