- Incremental progress over big bangs - Small changes that compile and pass tests
- Learning from existing code - Study and plan before implementing
- Pragmatic over dogmatic - Adapt to project reality
- Clear intent over clever code - Be boring and obvious
- Single responsibility per function/class
- Avoid premature abstractions
- No clever tricks - choose the boring solution
- If you need to explain it, it's too complex
Always start with "tracer bullets" - minimal, end-to-end implementations that prove the core concept works:
- Build the thinnest possible vertical slice that demonstrates the main functionality
- Deploy and test early - Get feedback on the core concept before adding features
- Iterate in small, working increments - Each commit should add one small, complete piece
- Avoid big rewrites - Refactor continuously in tiny steps
Break complex work into 3-5 stages. Document in IMPLEMENTATION_PLAN.md
using checkbox format:
## Stage N: [Name]
**Goal**: [Specific deliverable]
**Status**: [ ] Not Started | [X] Complete
### Tasks
- [X] Completed task
- [ ] Pending task
- [ ] Another pending task
IMPORTANT: Always check IMPLEMENTATION_PLAN.md
before starting work:
- Follow the checkbox tasks in order
- Mark tasks as
[X]
when completed - Use
/reset
between stages to save tokens - Update status and current focus section
- Understand - Study existing patterns in codebase
- Test - Write test first (red)
- Implement - Minimal code to pass (green)
- Refactor - Clean up with tests passing
- Commit - With clear message linking to plan
CRITICAL: Maximum 3 attempts per issue, then STOP.
-
Document what failed:
- What you tried
- Specific error messages
- Why you think it failed
-
Research alternatives:
- Find 2-3 similar implementations
- Note different approaches used
-
Question fundamentals:
- Is this the right abstraction level?
- Can this be split into smaller problems?
- Is there a simpler approach entirely?
-
Try different angle:
- Different library/framework feature?
- Different architectural pattern?
- Remove abstraction instead of adding?
- Composition over inheritance - Use dependency injection
- Interfaces over singletons - Enable testing and flexibility
- Explicit over implicit - Clear data flow and dependencies
- Test-driven when possible - Never disable tests, fix them
-
Every commit must:
- Compile successfully
- Pass all existing tests
- Include tests for new functionality
- Follow project formatting/linting
-
Before committing:
- Run formatters/linters
- Self-review changes
- Use descriptive commit titles only (no body/description)
- Fail fast with descriptive messages
- Include context for debugging
- Handle errors at appropriate level
- Never silently swallow exceptions
When multiple valid approaches exist, choose based on:
- Testability - Can I easily test this?
- Readability - Will someone understand this in 6 months?
- Consistency - Does this match project patterns?
- Simplicity - Is this the simplest solution that works?
- Reversibility - How hard to change later?
- Find 3 similar features/components
- Identify common patterns and conventions
- Use same libraries/utilities when possible
- Follow existing test patterns
- Use project's existing build system
- Use project's test framework
- Use project's formatter/linter settings
- Don't introduce new tools without strong justification
- Tests written and passing
- Code follows project conventions
- No linter/formatter warnings
- Commit messages are clear
- Implementation matches plan
- No TODOs without issue numbers
- Code is self-documenting with ZERO comments or explanations
- Test behavior, not implementation
- One assertion per test when possible
- Clear test names describing scenario
- Use existing test utilities/helpers
- Tests should be deterministic
- Element Selection: Always use data-testid attributes, never CSS classes
- Selector Priority: data-testid > semantic selectors > text content > CSS classes (avoid)
NEVER:
- Use
--no-verify
to bypass commit hooks - Disable tests instead of fixing them
- Commit code that doesn't compile
- Make assumptions - verify with existing code
- Add comments or explanations in code - code must be self-documenting
- Use CSS classes for element selection - always prefer data-testid attributes
ALWAYS:
- Commit working code incrementally
- Update plan documentation as you go
- Learn from existing implementations
- Stop after 3 failed attempts and reassess
- Use ES Modules (ESM) as primary module system
- Use
node:
prefix for built-in modules to prevent conflicts - Leverage top-level await for cleaner module initialization
- Embrace async/await with comprehensive error handling
- Use
Promise.all()
for parallel asynchronous operations
- Use Worker Threads for CPU-intensive background processing
- Implement performance monitoring with built-in performance hooks
- Utilize Web Streams for interoperable stream processing
- Create structured, contextual error classes
- Include rich error metadata (timestamp, context, status codes)
- Leverage Node.js permission model to restrict application access
- Use built-in APIs instead of external dependencies when possible
- Use
--watch
mode for automatic reloading - Utilize
--env-file
for environment management - Use built-in Node.js test runner
- Implement dynamic module imports for conditional feature loading
- Assume success - Update UI immediately, let server catch up
- Combine with startTransition for smooth state updates
- Keep update functions pure - No side effects in optimistic updaters
- Use sparingly - Only for high-confidence actions
- Visual feedback - Gray out or mark optimistic items
- Handle failures - Provide fallback mechanisms
- High-level modules should not depend on low-level modules - Both should depend on abstractions
- Decouple data fetching from rendering - Separate concerns between retrieval and presentation
- Design for testability - Components should work without direct dependencies
- Create clear interfaces - Define contracts for all external dependencies
- Inject through props or context - Avoid direct imports of concrete implementations
- Keep interfaces minimal - Single responsibility for each abstraction
- Independent Development - No waiting for backend services
- Rapid Iterations - Instant responses without network latency
- Scenario Simulation - Test error states and edge cases easily
- Predictable Testing - Consistent, repeatable test environments
- Define contracts first - Clear interfaces before implementation
- Consistent methods - Same signatures across implementations
- Mock parity - Mock behavior matches real API
- Type safety - Full TypeScript coverage
- Container/Presenter - Separate data logic from UI rendering
- Dependency Injection - Inject repositories via props or context
- Environment switching - Easy toggle between mock and real APIs
- Unit tests with mocks - Test component behavior with predictable data
- Integration tests - Verify real API compatibility
- Error scenarios - Test network failures and edge cases
- Performance testing - Simulate slow responses and timeouts
- Interface first - Define clear contracts before implementation
- Consistent methods - Same signatures across implementations
- Error handling - Mock should throw similar errors as real API
- Type safety - Full TypeScript coverage
- Single responsibility - One repository per domain/entity
- One reason to change - Each component should have exactly one responsibility
- Separate concerns - Break down complex components into focused units
- Composition over complexity - Build features from simple, reusable parts
- Extract custom hooks - Move data logic to hooks
- Pure presentation - Components that only render UI
- Container pattern - Components that orchestrate but don't render much
- "And" in description - Component does multiple things
- Multiple reasons to change - UI, API, and business logic changes all affect same component
- Large prop interfaces - Components accepting too many unrelated props
- Mixed concerns - Data fetching, state management, and rendering together
- Multiple components work together - Share state through context
- Flexible API surface - Consumers arrange components as needed
- Clear relationships - Explicit parent-child relationships
- Granular contexts - Break complex state into focused contexts
- Custom hooks for access - Provide convenient hooks for each context
- Separation of concerns - Each context handles specific domain
- Inversion of control - Component exposes data/behavior to consumers
- Maximum flexibility - Consumers control rendering completely
- Polymorphic components - Accept different component types through props
- Flexible rendering - Same logic with different presentations
- Customizable primitives - Override default components
- Base layer - Design tokens, constants, utilities
- Primitive layer - Low-level, unstyled components
- Shared library - Styled, reusable components
- Product-specific - Domain-specific adaptations
- Specialized - Complex, feature-specific components
- Single Responsibility - Each component handles one clear concern
- Inversion of Control - Empower consumers to extend and customize
- Context for coordination - Share state between related components
- Hooks for reusability - Extract common logic into custom hooks
- Flexible APIs - Provide sensible defaults with customization options
- Test Runner: Vitest (not Jest) - Modern, fast, native TypeScript support
- Testing Library: React Testing Library + @testing-library/jest-dom
- User Interactions: @testing-library/user-event for realistic user simulation
- Test Utilities: Custom
renderWithProviders()
wrapper with QueryClient and context - Mocking Strategy: Vitest's
vi.mock()
for module and hook mocking
- CSS Framework: Tailwind CSS v4+ (not plain CSS)
- Plugin: @tailwindcss/vite for modern Vite integration
- Utility Composition: tailwind-merge for class conflict resolution
- Design System: Consistent design tokens via Tailwind configuration
- HTTP Client: TanStack Query v5 (not raw fetch)
- Query Management: Custom hooks wrapping useQuery/useMutation
- Cache Strategy: Strategic invalidation and background refetching
- Error Handling: Consistent error boundaries and toast notifications
- API Client: Class-based HTTP client with methods
- Authentication: Automatic bearer token injection
- Error Handling: Custom ApiError class with status codes
- Type Safety: Full TypeScript coverage for all API interactions
- Domain-Specific Contexts: Separate contexts for Auth, Toast, etc.
- Custom Hooks: Each context exports a typed hook for access
- Error Boundaries: Proper error handling for context usage
- Provider Composition: Layered provider architecture
src/
├── components/ # Reusable UI components
├── contexts/ # React context providers
├── hooks/ # Custom hooks (data fetching, logic)
├── services/ # API clients and external services
├── types/ # TypeScript type definitions
├── utils/ # Pure utility functions
├── pages/ # Page-level components
├── test/ # Test utilities and setup
└── locales/ # i18n translation files
- Provider Composition: Layered providers (QueryClient → Auth → Toast → Router)
- Type Safety: Comprehensive TypeScript throughout the stack
- Modern React: React 19 with concurrent features and modern patterns
- Performance: Bundle optimization, lazy loading, and efficient re-renders
- Accessibility: Semantic HTML, ARIA attributes, keyboard navigation
MANDATORY: All services (frontend, backend, database, etc.) MUST have their own Dockerfile.
- Individual Dockerfiles - Each service maintains its own optimized Dockerfile
- Multi-stage builds - Use multi-stage builds for production optimization
- Security practices - Non-root users, minimal base images, vulnerability scanning
- Environment configuration - All environment variables configurable via Docker
- docker-compose.yml - Complete stack orchestration
- Development environment - Local development with hot reload
- Makefile integration -
make dev
,make build
,make deploy
use Docker - Testing in containers - All tests run in containerized environment
- CI/CD ready - Docker images built and tagged for deployment
- Layered caching - Optimize layer order for build speed
- Health checks - All services include health check endpoints
- Graceful shutdown - Proper signal handling for clean shutdowns
- Resource limits - CPU and memory limits defined
- Security scanning - Container images scanned for vulnerabilities