# Coding Conventions

**Analysis Date:** 2026-04-27

## Naming Patterns

**Files:**
- Components: kebab-case (e.g., `character-sheet-page.tsx`, `add-condition-modal.tsx`, `hp-control.tsx`)
- Utilities/Services: kebab-case (e.g., `use-character-socket.ts`, `export-character-html.ts`)
- Directories: kebab-case (e.g., `features/`, `shared/`, `components/`)

**Functions:**
- camelCase for all function names
- Hooks prefixed with `use` (e.g., `useAuthStore`, `useCharacterSocket`, `useMemo`, `useState`)
- Event handlers prefixed with `handle` (e.g., `handleApply`, `handleConnection`, `handleDisconnect`)
- Getter/setter methods follow camelCase (e.g., `getToken()`, `setToken()`)

**Variables:**
- camelCase for all variable names
- Constants in UPPER_SNAKE_CASE (e.g., `PROFICIENCY_BONUS`, `SKILL_DATA`, `TABS`)
- Type unions with capitalized names (e.g., `CharacterType`, `AbilityType`, `TabType`)
- Boolean prefixes: `is`, `has`, `should`, `can` (e.g., `isLoading`, `isOwner`, `hasAccess`)

**Types:**
- PascalCase for all types, interfaces, and enums
- Domain types in shared: `Character`, `Campaign`, `User`, `CharacterItem`, `CharacterFeat`
- Props interfaces suffixed with `Props` (e.g., `HpControlProps`, `AddConditionModalProps`)
- DTO interfaces suffixed with `Dto` (e.g., `CreateCharacterDto`, `LoginDto`, `RegisterDto`)
- State types in Zustand: domain-specific (e.g., `AuthState`)

## Code Style

**Formatting:**
- Prettier configured in `server/.prettierrc` with:
  - Single quotes (`singleQuote: true`)
  - Trailing commas on all (`trailingComma: "all"`)
- Line length: implicit (Prettier default ~80 chars but allows overflow)
- Indentation: 2 spaces

**Linting:**
- Client: ESLint 9 with flat config (`eslint.config.js`)
  - Uses TypeScript ESLint recommended rules
  - React Hooks plugin with recommended rules
  - React Refresh plugin for Vite
  - No custom strict rules beyond defaults
- Server: ESLint 9 with flat config (`eslint.config.mjs`)
  - Uses TypeScript ESLint recommended rules with type checking (`recommendedTypeChecked`)
  - Prettier plugin for code formatting integration
  - **Important:** `@typescript-eslint/no-explicit-any` is **turned OFF** (line 29)
  - Warnings (not errors) for: `no-floating-promises`, `no-unsafe-argument`
  - Custom Prettier rule with `endOfLine: "auto"` for Windows compatibility

**TypeScript:**
- **Strict Mode Enforced** on client (`client/tsconfig.app.json`):
  - `"strict": true` ✓
  - `"noUnusedLocals": true` ✓
  - `"noUnusedParameters": true` ✓
  - `"noFallthroughCasesInSwitch": true` ✓
  - `"noUncheckedSideEffectImports": true` ✓
- Server TypeScript (`server/tsconfig.json`):
  - `"strictNullChecks": true` ✓
  - `"forceConsistentCasingInFileNames": true` ✓
  - `"noImplicitAny": false` (allows any, soft enforcement)
  - `"skipLibCheck": true` (skips lib type checking)

## Import Organization

**Order:** (observed pattern)
1. React/Framework imports (`react`, `react-router-dom`)
2. Third-party utilities (`zustand`, `axios`, `socket.io-client`)
3. NestJS/Framework imports (`@nestjs/*`)
4. Type-only imports (`type { ... }`)
5. Relative imports from shared (`@/shared/...`, `@/features/...`)
6. Decorators and metadata (after other imports)

**Path Aliases:**
- Client: `@/*` → `./src/*` (defined in `vite.config.ts` and `tsconfig.app.json`)
- Server: No path alias configured; uses relative paths
- Always use `@/` prefix on client to avoid relative path hell

**Module exports:**
- Barrel files used: `features/*/index.ts`, `shared/components/ui/index.ts`
- Default exports on pages: `export default App`
- Named exports for utilities, types, hooks: `export { HpControl }`, `export const api = new ApiClient()`

## Error Handling

**Philosophy (per CLAUDE.md):** No shortcuts. Proper error handling, not try/catch with console.log.

**Server-side (NestJS):**
- Use NestJS exception classes: `NotFoundException`, `ForbiddenException`, `UnauthorizedException`, `ConflictException`
- Access checks before operations (e.g., `checkCampaignAccess()`, `checkCharacterAccess()` in `characters.service.ts`)
- Return meaningful error messages: "Campaign not found", "No access to this campaign"
- Prisma operations wrapped in try-catch only when necessary for data validation
- JWT verification throws exceptions through guard: `JwtAuthGuard` handles 401s

**Client-side (React):**
- Axios interceptors for request/response handling (in `api.ts`)
- 401 errors trigger logout and redirect to `/login` (except auth endpoints)
- Error state in components: `isLoading`, `error`, `pendingChange` states
- Modal operations: try/catch with `setIsLoading(false)` in finally block
- Error logging: `console.error()` for debugging, no silent failures
- User feedback: Navigation on error (`navigate()`) or via error state

**Socket.io (WebSocket Gateway):**
- Token validation on connection (throws `client.disconnect()`)
- Logger for all connection/disconnection events
- Errors logged but don't crash server

## Logging

**Framework:** 
- Server: NestJS `Logger` (in modules/gateways)
  - E.g., `private logger = new Logger('CharactersGateway')`
  - Used for connection events, warnings, errors
- Client: `console.error()` for error debugging

**Patterns:**
- Server logs connection events with user context
- Client silently catches most errors, logs critical ones
- No debug logging infrastructure in place

## Comments

**When to Comment:**
- Constants with unclear meaning (e.g., `PROFICIENCY_BONUS` values, skill-to-ability mappings)
- Complex calculations (e.g., HP percentage calculations)
- Business logic that isn't immediately obvious (e.g., Pathfinder 2e rules)
- NOT used for obvious code ("increment counter")

**JSDoc/TSDoc:**
- Minimal usage observed
- API methods documented with Swagger decorators on server: `@ApiOperation()`, `@ApiResponse()`
- No runtime JSDoc comments in source

## Function Design

**Size:** 
- Modal components: 500-1700 lines (large, but feature-complete)
- Service methods: 10-50 lines (concise, focused)
- Utility functions: 5-20 lines

**Parameters:**
- Interface-based: Pass objects instead of multiple params
  - E.g., `onAdd(condition: { name: string; nameGerman: string; value?: number })`
  - DTO pattern on server: `CreateCharacterDto`, `LoginDto`
- Optional params with defaults: `remember: boolean = false`

**Return Values:**
- Async functions return typed Promises: `async getCharacter(): Promise<Character>`
- Component callbacks: callbacks are async promises (`onHpChange: (newHp: number) => Promise<void>`)
- Services return full domain objects with relations included

## Module Design

**Exports:**
- Components export as named: `export function HpControl() { ... }`
- Utilities export as named: `export const api = new ApiClient()`
- One export per file (generally)

**Barrel Files:**
- Used in features: `features/auth/index.ts` re-exports `useAuthStore`, `LoginPage`, `RegisterPage`
- Used for UI components: `shared/components/ui/index.ts` re-exports all buttons, cards, inputs
- Reduces import complexity

**Dependency Injection (Server):**
- NestJS @Injectable() decorator for services
- Constructor injection: `constructor(private prisma: PrismaService)`
- Circular dependencies resolved with @Inject(forwardRef()): seen in `CharactersService`

## State Management

**Client (Zustand):**
- Single store pattern: `create<AuthState>()`
- Persisted state with middleware: `persist()`
- Partialize: stores only non-sensitive fields (`user`, `isAuthenticated`)
- Actions as methods in store: `login()`, `logout()`, `checkAuth()`
- Error state in store: `error: string | null`

**Server (NestJS):**
- No client state management needed
- WebSocket gateway maintains connection map: `connectedClients = new Map()`

## Design Tokens (UI)

**Colors:**
- Primary: Magenta `#c26dbc` (in Tailwind as `primary-500`, `primary-600`, etc.)
- Secondary: Dark grays for dark mode (`secondary-800`, `secondary-700`)
- Text: Primary (light), secondary (dimmer), tertiary (dimmest)
- Background: Primary, secondary, tertiary layers
- Error: Red (`error-500`, `error-600`)
- Success: Green (`green-500`)
- Warning: Yellow (`yellow-500`)

**Typography:**
- Font sizes: sm, base, lg (Tailwind defaults)
- Weights: normal, medium (600), semibold
- All UI text in German except code/technical terms

**Icons:**
- Lucide React exclusively (e.g., `Heart`, `Swords`, `BookOpen`, `Package`)
- No emoji anywhere
- Icon sizes: `h-4 w-4` (standard), `h-6 w-6` (large)

**Touch Targets:**
- Minimum 44px (mobile-first, accessible)
- Buttons: `h-11` (44px), `h-9` (36px for small), `h-12` (48px for large)
- Icon buttons: `h-11 w-11` (44x44px)

**Spacing:**
- Tailwind v4 defaults: `p-4`, `gap-2`, `rounded-lg`, `rounded-2xl`
- Modals: `rounded-t-2xl sm:rounded-2xl` (bottom sheet on mobile, centered on desktop)

**Responsive:**
- Mobile-first breakpoints: `sm:` prefix for tablet+ (e.g., `sm:items-center`, `sm:rounded-2xl`)
- Flexbox for layouts: `flex`, `flex-col`, `items-center`, `justify-between`
- Grid rarely used

---

*Convention analysis: 2026-04-27*
