mirror of
https://github.com/jarrodwatts/claude-hud.git
synced 2026-05-20 15:02:41 +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>
6.1 KiB
6.1 KiB
ADR 004: Session Handling
Status
Accepted
Context
Current problems with session handling:
- HUD doesn't attach correctly when user runs
/new /exitand/resumeleave HUD in stale state- Session ID not tracked consistently
- 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:
interface HudEvent {
session_id: string; // Always present
hook_event_name: string;
timestamp: number;
// ... other fields
}
Session Change Detection
// 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
// 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
# 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
- Rapid /new /new /new: Each creates new FIFO, HUD follows last one
- FIFO deleted externally: Reconnect logic handles gracefully
- Claude Code crashes: SessionEnd may not fire, rely on FIFO absence
- Multiple terminals: Each Claude instance has its own FIFO
Implementation Notes
- Add
session_idto all events in capture-event.sh - Create
useSessionhook for session state management - Emit 'session_changed' event when ID changes
- All state hooks listen for session_changed to reset
- Add reconnection logic to EventReader
- Add session state indicator to StatusBar component