8.9 KiB
8.9 KiB
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/.prettierrcwith:- Single quotes (
singleQuote: true) - Trailing commas on all (
trailingComma: "all")
- Single quotes (
- 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-anyis turned OFF (line 29) - Warnings (not errors) for:
no-floating-promises,no-unsafe-argument - Custom Prettier rule with
endOfLine: "auto"for Windows compatibility
- Uses TypeScript ESLint recommended rules with type checking (
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)
- React/Framework imports (
react,react-router-dom) - Third-party utilities (
zustand,axios,socket.io-client) - NestJS/Framework imports (
@nestjs/*) - Type-only imports (
type { ... }) - Relative imports from shared (
@/shared/...,@/features/...) - Decorators and metadata (after other imports)
Path Aliases:
- Client:
@/*→./src/*(defined invite.config.tsandtsconfig.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()incharacters.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:
JwtAuthGuardhandles 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,pendingChangestates - 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
- E.g.,
- 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_BONUSvalues, 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
- E.g.,
- 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.tsre-exportsuseAuthStore,LoginPage,RegisterPage - Used for UI components:
shared/components/ui/index.tsre-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 asprimary-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