---
name: critical-rules
description: Use always - non-negotiable rules for TypeScript safety, socket events, and React patterns
---
# Critical Rules
**MANDATORY rules that must NEVER be violated.** These prevent common bugs and ensure code quality.
## Checklist
### Rule 1: TypeScript Type Safety
- [ ] **NEVER use `any` type** - no exceptions, including tests
- ❌ `const data: any = response`
- ✅ `const data: unknown = response`
- ✅ `const data = response as UserData`
- ✅ For test mocks: `as unknown as Type`
**Why:** `any` disables all type checking and hides bugs. Use proper types, `unknown`, or type assertions instead.
**Examples:**
```typescript
// ❌ Bad - loses all type safety
function process(data: any) {
return data.user.name // No error if user is undefined!
}
// ✅ Good - proper typing
function process(data: unknown) {
if (isUserData(data)) {
return data.user.name
}
throw new Error('Invalid data')
}
// ✅ Good - type assertion when you know the type
const mockSocket = {
on: vi.fn(),
emit: vi.fn()
} as unknown as Socket
```
### Rule 2: Socket Event Handling
- [ ] **NEVER use `socket.on()` directly in components**
- ❌ `socket.on('event', callback)` - Breaks on reconnection
- ✅ `useSocketEvent('event', callback, [callback])` - Reactive and safe
**Why:** Direct `socket.on()` doesn't re-subscribe when socket reconnects. Use the `useSocketEvent` hook which handles reconnection automatically.
**Examples:**
```typescript
// ❌ Bad - loses events after reconnection
useEffect(() => {
const socket = useSocket.getState().socket
socket?.on(SocketEvents.DOWNLOAD_START, handleDownload)
}, [])
// ✅ Good - handles reconnection automatically
useSocketEvent(SocketEvents.DOWNLOAD_START, handleDownload, [handleDownload])
```
### Rule 3: useEffect Cleanup
- [ ] **Cleanup functions must be synchronous**
- ❌ `return async () => { await cleanup() }` - Breaks React
- ✅ `return () => { cleanup().catch(console.error) }` - Fire-and-forget
**Why:** React expects cleanup functions to be synchronous. Async cleanup functions are ignored.
**Examples:**
```typescript
// ❌ Bad - async cleanup is ignored
useEffect(() => {
startService()
return async () => {
await stopService() // Never runs!
}
}, [])
// ✅ Good - synchronous cleanup with fire-and-forget async
useEffect(() => {
startService()
return () => {
stopService().catch(console.error)
}
}, [])
// ✅ Good - synchronous cleanup only
useEffect(() => {
const interval = setInterval(poll, 1000)
return () => clearInterval(interval)
}, [])
```
### Rule 4: Test Behavior, Not Implementation
- [ ] **Test what the code does, not how it does it**
- ❌ Testing that `socket.on` was called 3 times
- ✅ Testing that component responds correctly to events
**Why:** Implementation details change frequently. Behavior tests remain stable and catch real bugs.
**Examples:**
```typescript
// ❌ Bad - tests implementation
it('should call socket.on with download event', () => {
render()
expect(mockSocket.on).toHaveBeenCalledWith(SocketEvents.DOWNLOAD_START, expect.any(Function))
})
// ✅ Good - tests behavior
it('should show download progress when download starts', () => {
render()
act(() => {
handlers[SocketEvents.DOWNLOAD_START]({ id: '123', filename: 'model.bin' })
})
expect(screen.getByText('Downloading model.bin')).toBeInTheDocument()
})
```
## Verification
Before committing code, verify these rules are followed:
- [ ] Run `pnpm run type-check` - catches `any` types
- [ ] Search for `socket.on` - should only appear in `useSocketEvent` hook
- [ ] Review useEffect cleanup - must be synchronous
- [ ] Review tests - focus on behavior, not implementation
## Consequences
**Violating these rules leads to:**
- Type safety violations → Runtime errors in production
- Socket event loss → Features stop working after reconnect
- Broken cleanup → Memory leaks and stale subscriptions
- Brittle tests → False failures during refactoring