-->
{One-sentence description. Example: "A SaaS dashboard for managing team subscriptions and billing."}
Stack: {TypeScript, React 18, Vite, Tailwind CSS, Express, PostgreSQL, Prisma} Node: {20.x} | Package manager: {pnpm}
# Development
{pnpm dev} # Start dev server (port 3000)
{pnpm dev:api} # Start API server (port 4000)
# Building
{pnpm build} # Production build
{pnpm typecheck} # TypeScript check (no emit)
# Testing
{pnpm test} # Run all tests (Vitest)
{pnpm test:unit} # Unit tests only
{pnpm test:e2e} # E2E tests (Playwright)
{pnpm test -- src/path/to/file.test.ts} # Single file
# Code Quality
{pnpm lint} # ESLint
{pnpm lint:fix} # ESLint with auto-fix
{pnpm format} # Prettiersrc/
├── components/ # React components (PascalCase directories)
│ ├── ui/ # Generic reusable components (Button, Modal, Input)
│ └── features/ # Feature-specific components (Dashboard, Settings)
├── hooks/ # Custom React hooks (useAuth, useDebounce)
├── services/ # Business logic and API calls
├── stores/ # State management ({Zustand} stores)
├── types/ # Shared TypeScript types and interfaces
├── utils/ # Pure utility functions
└── pages/ # Route-level page components
server/
├── routes/ # Express route handlers
├── middleware/ # Auth, validation, error handling
├── services/ # Server-side business logic
└── db/ # Prisma schema, migrations, seed
tests/
├── unit/ # Mirrors src/ structure
├── integration/ # API integration tests
└── e2e/ # Playwright browser tests
- Strict mode enabled — no
anytypes without explicit justification - Use
interfacefor object shapes,typefor unions and intersections - Named exports only — no default exports
- Prefer
constarrow functions for components:export const Button = () => {} - Enums: use
as constobjects instead of TypeScript enums
- Functional components only — no class components
- Props: define interface above component, name it
{ComponentName}Props - Hooks: custom hooks go in
src/hooks/, prefixed withuse - State: local state with
useState, shared state with {Zustand} - Styling: {Tailwind CSS} utility classes, no inline styles or CSS-in-JS
- Files:
kebab-case.tsfor utilities,PascalCase.tsxfor components - Variables/functions:
camelCase - Types/interfaces:
PascalCase - Constants:
SCREAMING_SNAKE_CASE - Test files:
{name}.test.tsalongside source or intests/mirror
- Framework: {Vitest} for unit/integration, {Playwright} for E2E
- Pattern: AAA (Arrange / Act / Assert) with
describe/itblocks - Mocking: use
vi.mock()for module mocks,vi.fn()for function mocks - Coverage: aim for 80%+ on services and utils, components tested via behavior
- Test data: use factory functions from
tests/factories/— not raw object literals - Naming:
it('should {expected behavior} when {condition}')format
// Example test structure
describe('UserService', () => {
describe('createUser', () => {
it('should create a user with valid input', async () => {
// Arrange
const input = createUserInput();
// Act
const result = await userService.createUser(input);
// Assert
expect(result.id).toBeDefined();
});
});
});- REST endpoints follow:
{verb} /api/v1/{resource} - Request validation: {Zod} schemas in
server/middleware/validate.ts - Error responses:
{ error: { code: string, message: string, details?: object } } - Auth: Bearer token in Authorization header, validated by
authMiddleware - Pagination:
?page=1&limit=20, response includes{ data, total, page, limit }
- The
src/generated/directory is auto-generated by {Prisma} — never edit these files manually - Environment variables are in
.env.local(not committed) — see.env.examplefor the full list - Database migrations must be created with
{pnpm prisma migrate dev --name descriptive_name}— do not edit migration files after they are created - The
ui/components use a specific prop pattern — check an existing component before creating new ones
- Do not install new dependencies without asking first
- Do not modify CI/CD configuration files (
.github/workflows/) - Do not commit
.envfiles or any secrets - Do not use
console.login production code — use the logger fromsrc/utils/logger.ts - Do not write SQL directly — use Prisma client methods