# Code Style Guide

> Coding standards and best practices for Izri

## 📋 Overview

This guide defines the coding standards for Izri to ensure consistent, maintainable, and high-quality code across the project.

## 🔧 Tooling

### Biome (Formatter & Linter)

We use Biome for code formatting and linting:

```bash
# Format code
pnpm format:all

# Check formatting
pnpm biome check .

# Fix issues
pnpm biome check --write .
```

### TypeScript

```bash
# Type checking
pnpm type:check

# Per package
pnpm type:check:apps
pnpm type:check:packages
```

## 📐 TypeScript Style Guide

### Type Annotations

```typescript
// ✅ Use explicit return types for functions
function getProject(id: string): Promise<Project | null> {
  return db.query.projects.findFirst({ where: eq(projects.id, id) })
}

// ✅ Use type inference for obvious cases
const count = projects.length
const isValid = true

// ❌ Avoid unnecessary annotations
const count: number = projects.length  // Redundant
```

### Interfaces vs Types

```typescript
// ✅ Use interfaces for object shapes
interface Project {
  id: string
  name: string
  repository: string
}

// ✅ Use types for unions, intersections, utilities
type Status = 'pending' | 'running' | 'completed' | 'failed'
type ProjectWithTests = Project & { testRuns: TestRun[] }
type PartialProject = Partial<Project>
```

### Naming Conventions

```typescript
// PascalCase for types, interfaces, classes, components
interface ProjectSettings {}
class RepositoryAnalyzer {}
function ProjectCard() {}

// camelCase for variables, functions, methods
const projectId = 'proj_123'
function fetchProjects() {}

// SCREAMING_SNAKE_CASE for constants
const API_BASE_URL = 'https://api.example.com'
const MAX_RETRIES = 3

// Use descriptive names
const user = await getUser(id)  // ✅
const u = await getU(i)         // ❌
```

### Async/Await

```typescript
// ✅ Use async/await
async function createProject(data: ProjectData) {
  const project = await db.insert(projects).values(data)
  return project
}

// ❌ Avoid promise chains when possible
function createProject(data: ProjectData) {
  return db.insert(projects).values(data)
    .then(project => project)
}

// ✅ Handle errors properly
try {
  const project = await createProject(data)
} catch (error) {
  logger.error({ error }, 'Failed to create project')
  throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR' })
}
```

### Optional Chaining & Nullish Coalescing

```typescript
// ✅ Use optional chaining
const email = user?.profile?.email

// ✅ Use nullish coalescing
const name = user.name ?? 'Anonymous'

// ❌ Avoid long chains
const value = data?.items?.[0]?.properties?.value?.toString() // Too deep
```

## ⚛️ React Patterns

### Component Structure

```tsx
// ✅ Functional components with TypeScript
interface ProjectCardProps {
  project: Project
  onDelete?: (id: string) => void
}

export function ProjectCard({ project, onDelete }: ProjectCardProps) {
  // Hooks at the top
  const navigate = useNavigate()
  const [isDeleting, setIsDeleting] = useState(false)
  
  // Event handlers
  const handleDelete = async () => {
    setIsDeleting(true)
    try {
      await onDelete?.(project.id)
      navigate('/dashboard/projects')
    } finally {
      setIsDeleting(false)
    }
  }
  
  // Render
  return (
    <Card>
      <CardHeader>
        <CardTitle>{project.name}</CardTitle>
      </CardHeader>
      <CardContent>
        <Button onClick={handleDelete} disabled={isDeleting}>
          Delete
        </Button>
      </CardContent>
    </Card>
  )
}
```

### Hooks

```tsx
// ✅ Custom hooks start with 'use'
function useProject(projectId: string) {
  const { data, isLoading } = trpc.projects.getProject.useQuery({ projectId })
  return { project: data?.project, isLoading }
}

// ✅ Extract complex logic into hooks
function useProjectForm() {
  const form = useForm<ProjectForm>({
    resolver: zodResolver(projectSchema)
  })
  
  const createProject = trpc.projects.createProject.useMutation()
  
  const onSubmit = async (values: ProjectForm) => {
    await createProject.mutateAsync(values)
  }
  
  return { form, onSubmit, isSubmitting: createProject.isLoading }
}
```

### Component Composition

```tsx
// ✅ Compose small, focused components
function ProjectDetails({ project }: Props) {
  return (
    <div>
      <ProjectHeader project={project} />
      <ProjectStats project={project} />
      <ProjectTestRuns projectId={project.id} />
    </div>
  )
}

// ❌ Avoid monolithic components
function ProjectDetails({ project }: Props) {
  return (
    <div>
      {/* 500 lines of JSX */}
    </div>
  )
}
```

## 🎨 Styling Guidelines

### Tailwind CSS

```tsx
// ✅ Use Tailwind utility classes
<div className="flex items-center gap-4 p-4 rounded-lg bg-white shadow-md">
  <span className="text-lg font-semibold text-gray-900">Title</span>
</div>

// ✅ Use responsive modifiers
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
  {/* Content */}
</div>

// ✅ Use cn() for conditional classes
<div className={cn(
  "base-classes",
  isActive && "active-classes",
  isPending && "pending-classes"
)}>
  {/* Content */}
</div>
```

### Component Variants (CVA)

```typescript
import { cva, type VariantProps } from 'class-variance-authority'

const buttonVariants = cva(
  // Base classes
  "inline-flex items-center justify-center rounded-md font-medium transition-colors",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/90",
        destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
        outline: "border border-input bg-background hover:bg-accent"
      },
      size: {
        default: "h-10 px-4 py-2",
        sm: "h-9 px-3",
        lg: "h-11 px-8"
      }
    },
    defaultVariants: {
      variant: "default",
      size: "default"
    }
  }
)
```

## 📁 File Organization

### Directory Structure

```
src/
├── components/
│   ├── ui/           # shadcn/ui components
│   ├── features/     # Feature-specific components
│   └── layouts/      # Layout components
├── hooks/            # Custom hooks
├── lib/              # Utilities and helpers
├── routes/           # React Router routes
└── types/            # Shared TypeScript types
```

### File Naming

```
// Components: PascalCase
ProjectCard.tsx
UserProfile.tsx

// Utilities: camelCase
formatDate.ts
apiClient.ts

// Types: PascalCase
Project.ts
User.ts

// Hooks: camelCase (use prefix)
useProject.ts
useAuth.ts
```

## 🎯 Best Practices

### Error Handling

```typescript
// ✅ Use try-catch for async operations
try {
  const result = await riskyOperation()
  return result
} catch (error) {
  logger.error({ error }, 'Operation failed')
  
  if (error instanceof SpecificError) {
    // Handle specific error
  }
  
  throw new TRPCError({
    code: 'INTERNAL_SERVER_ERROR',
    message: 'Operation failed',
    cause: error
  })
}
```

### Logging

```typescript
// ✅ Use structured logging
logger.info({ userId, projectId }, 'Project created')

// ✅ Include context in error logs
logger.error({ error, userId, data }, 'Failed to create project')

// ❌ Avoid string concatenation
logger.info('User ' + userId + ' created project ' + projectId)
```

### Performance

```typescript
// ✅ Use React.memo for expensive components
export const ExpensiveComponent = React.memo(({ data }: Props) => {
  // Complex rendering logic
})

// ✅ Use useMemo for expensive calculations
const sortedProjects = useMemo(
  () => projects.sort((a, b) => a.name.localeCompare(b.name)),
  [projects]
)

// ✅ Use useCallback for event handlers passed to children
const handleDelete = useCallback(
  (id: string) => {
    deleteProject.mutate({ id })
  },
  [deleteProject]
)
```

## 🧪 Testing

### Test File Naming

```
// Next to source file
Button.tsx
Button.test.tsx

// Or in __tests__ directory
Button.tsx
__tests__/Button.test.tsx
```

### Test Structure

```typescript
describe('ProjectCard', () => {
  it('renders project name', () => {
    const project = { name: 'Test Project', ...otherFields }
    render(<ProjectCard project={project} />)
    expect(screen.getByText('Test Project')).toBeInTheDocument()
  })
  
  it('calls onDelete when delete button clicked', async () => {
    const onDelete = vi.fn()
    const project = { id: 'proj_123', ...otherFields }
    
    render(<ProjectCard project={project} onDelete={onDelete} />)
    
    await userEvent.click(screen.getByRole('button', { name: /delete/i }))
    
    expect(onDelete).toHaveBeenCalledWith('proj_123')
  })
})
```

## 🔗 Related Documentation

- **Development Setup**: [Local setup guide](../development/local-setup.md)
- **Testing Guidelines**: [Testing standards](./testing-guidelines.md)
- **Commit Conventions**: [Commit message format](./commit-conventions.md)

---

*Next: [Commit Conventions](./commit-conventions.md) →*
