mirror of
https://github.com/jarrodwatts/claude-hud.git
synced 2026-05-20 23:19:58 +00:00
ADR 001: State Management - Custom hooks + useReducer - Extract state logic from app.tsx into domain-specific hooks - useReducer for predictable state transitions - Each hook independently testable ADR 002: Data Flow - Event-driven with minimal polling - Primary: Hook events via FIFO (real-time) - Secondary: Single consolidated poll (git/mcp only) - Eliminate redundant polling sources that cause flickering ADR 003: Shell vs TypeScript - Minimal shell, logic in TS - Hooks must be shell scripts (Claude Code requirement) - Keep shell scripts minimal (extract data, write FIFO) - Complex logic moves to testable TypeScript ADR 004: Session Handling - Track session ID, graceful reset - Session ID in all events for change detection - Reset state on session change (/new, /exit, /resume) - Exponential backoff reconnection strategy 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
185 lines
6.1 KiB
Markdown
185 lines
6.1 KiB
Markdown
# ADR 004: Session Handling
|
|
|
|
## Status
|
|
Accepted
|
|
|
|
## Context
|
|
Current problems with session handling:
|
|
1. HUD doesn't attach correctly when user runs `/new`
|
|
2. `/exit` and `/resume` leave HUD in stale state
|
|
3. Session ID not tracked consistently
|
|
4. No graceful handling of session switches
|
|
|
|
Users expect the HUD to "just work" regardless of how they navigate Claude Code sessions.
|
|
|
|
## Decision
|
|
**Track session ID in all events, detect changes, and reset state gracefully.**
|
|
|
|
### Session Lifecycle
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ Session States │
|
|
│ │
|
|
│ ┌──────────┐ │
|
|
│ │ INIT │ ─────────────────────────┐ │
|
|
│ └────┬─────┘ │ │
|
|
│ │ SessionStart │ │
|
|
│ ▼ │ │
|
|
│ ┌──────────┐ /new or /exit │ │
|
|
│ │ ACTIVE │ ──────────────────► ┌───┴───┐ │
|
|
│ └────┬─────┘ │ RESET │ │
|
|
│ │ └───┬───┘ │
|
|
│ │ Stop event │ │
|
|
│ ▼ │ │
|
|
│ ┌──────────┐ │ │
|
|
│ │ IDLE │ ◄────────────────────────┘ │
|
|
│ └──────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Session ID Tracking
|
|
|
|
Every event includes `session_id`:
|
|
|
|
```typescript
|
|
interface HudEvent {
|
|
session_id: string; // Always present
|
|
hook_event_name: string;
|
|
timestamp: number;
|
|
// ... other fields
|
|
}
|
|
```
|
|
|
|
### Session Change Detection
|
|
|
|
```typescript
|
|
// hooks/useSession.ts
|
|
function useSession() {
|
|
const [currentSession, setCurrentSession] = useState<string | null>(null);
|
|
|
|
const handleEvent = useCallback((event: HudEvent) => {
|
|
if (currentSession && event.session_id !== currentSession) {
|
|
// SESSION CHANGED - reset all state
|
|
resetAllState();
|
|
}
|
|
setCurrentSession(event.session_id);
|
|
}, [currentSession]);
|
|
|
|
return { currentSession, handleEvent };
|
|
}
|
|
```
|
|
|
|
### State Reset on Session Change
|
|
|
|
When session changes, reset:
|
|
- Tool history (clear)
|
|
- Agent list (clear)
|
|
- Context tracking (reset to zero)
|
|
- Cost tracking (reset)
|
|
- Todos (clear)
|
|
- Modified files (clear)
|
|
|
|
Do NOT reset:
|
|
- Git status (session-independent)
|
|
- MCP status (session-independent)
|
|
- UI preferences (if any)
|
|
|
|
### FIFO Reconnection Strategy
|
|
|
|
```typescript
|
|
// lib/event-reader.ts
|
|
class EventReader {
|
|
private sessionId: string;
|
|
private reconnectAttempts = 0;
|
|
private maxReconnectDelay = 30_000; // 30 seconds max
|
|
|
|
private getReconnectDelay(): number {
|
|
// Exponential backoff: 100ms, 200ms, 400ms, ... up to 30s
|
|
const delay = Math.min(
|
|
100 * Math.pow(2, this.reconnectAttempts),
|
|
this.maxReconnectDelay
|
|
);
|
|
this.reconnectAttempts++;
|
|
return delay;
|
|
}
|
|
|
|
private async reconnect(): Promise<void> {
|
|
const delay = this.getReconnectDelay();
|
|
await sleep(delay);
|
|
|
|
// Check if FIFO still exists
|
|
if (await this.fifoExists()) {
|
|
await this.connect();
|
|
this.reconnectAttempts = 0; // Reset on success
|
|
} else {
|
|
// Session ended, clean up
|
|
this.emit('session_ended');
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Handling /new, /exit, /resume
|
|
|
|
| Command | Expected Behavior |
|
|
|---------|-------------------|
|
|
| `/new` | New session starts, HUD resets and attaches to new session |
|
|
| `/exit` | Session ends, HUD shows idle/disconnected state |
|
|
| `/resume` | Resume session, HUD reconnects to existing FIFO |
|
|
|
|
### Implementation: SessionStart Hook
|
|
|
|
```bash
|
|
# session-start.sh
|
|
# Check if HUD is already running for this session
|
|
existing_pid=$(cat "$PID_FILE" 2>/dev/null)
|
|
if kill -0 "$existing_pid" 2>/dev/null; then
|
|
# Send signal to reconnect instead of spawning new
|
|
kill -USR1 "$existing_pid"
|
|
else
|
|
# Spawn new HUD instance
|
|
spawn_hud
|
|
fi
|
|
```
|
|
|
|
### UI Feedback for Session States
|
|
|
|
| State | UI Indication |
|
|
|-------|---------------|
|
|
| INIT | "Connecting..." |
|
|
| ACTIVE | Normal display |
|
|
| IDLE | "Idle" badge, dimmed metrics |
|
|
| DISCONNECTED | "Reconnecting..." with spinner |
|
|
| SESSION_CHANGED | Brief "New Session" indicator, then reset |
|
|
|
|
## Consequences
|
|
|
|
### Positive
|
|
- **Seamless transitions**: /new /exit /resume just work
|
|
- **No stale data**: Session changes trigger clean reset
|
|
- **User confidence**: HUD always shows current session state
|
|
- **Debuggable**: Session ID in all events for tracing
|
|
|
|
### Negative
|
|
- **State loss on /new**: Previous session data gone (acceptable)
|
|
- **Brief disruption**: Reset causes momentary blank UI
|
|
- **Complexity**: More state to manage
|
|
|
|
### Edge Cases
|
|
|
|
1. **Rapid /new /new /new**: Each creates new FIFO, HUD follows last one
|
|
2. **FIFO deleted externally**: Reconnect logic handles gracefully
|
|
3. **Claude Code crashes**: SessionEnd may not fire, rely on FIFO absence
|
|
4. **Multiple terminals**: Each Claude instance has its own FIFO
|
|
|
|
## Implementation Notes
|
|
|
|
1. Add `session_id` to all events in capture-event.sh
|
|
2. Create `useSession` hook for session state management
|
|
3. Emit 'session_changed' event when ID changes
|
|
4. All state hooks listen for session_changed to reset
|
|
5. Add reconnection logic to EventReader
|
|
6. Add session state indicator to StatusBar component
|