# 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` - Component callbacks: callbacks are async promises (`onHpChange: (newHp: number) => Promise`) - 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()` - 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*