mirror of
https://github.com/jarrodwatts/claude-hud.git
synced 2026-05-12 17:22: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>
5.2 KiB
5.2 KiB
ADR 002: Data Flow Architecture
Status
Accepted
Context
The current implementation has multiple data sources:
- FIFO events from hooks (PreToolUse, PostToolUse, etc.)
- Polling settings reader (30s interval)
- Polling context detector (30s interval)
- Polling transcript reader (30s interval)
This causes:
- Flickering: Multiple sources updating at different times
- Race conditions: Stale data overwriting fresh data
- Inconsistency: Different sources have different views of state
- Performance overhead: 3 timers running constantly
Decision
Event-driven primary, consolidated polling secondary.
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Claude Code │
│ │ │
│ Hook Events │
│ ▼ │
├─────────────────────────────────────────────────────────────┤
│ capture-event.sh │
│ (minimal: extract data, write to FIFO) │
│ │ │
│ ▼ │
├─────────────────────────────────────────────────────────────┤
│ FIFO │
│ ~/.claude/hud/events/<session>.fifo │
│ │ │
│ ▼ │
├─────────────────────────────────────────────────────────────┤
│ EventReader │
│ (single source of truth, auto-reconnect) │
│ │ │
│ ┌─────────────┼─────────────┐ │
│ ▼ ▼ ▼ │
│ useToolStream useContextState useAgents │
│ │
└─────────────────────────────────────────────────────────────┘
Event Types (Primary Data Source)
| Hook Event | Data Provided |
|---|---|
| SessionStart | session_id, cwd, model |
| PreToolUse | tool_name, input (starting) |
| PostToolUse | tool_name, output, duration, tokens |
| Stop | idle state |
| SubagentStop | agent completion |
| PreCompact | compaction warning |
| UserPromptSubmit | user prompt |
Consolidated Polling (Secondary, Single Timer)
One 60-second poll for data NOT available in events:
- Git status (branch, staged, modified)
- MCP server status (connected servers)
// Single consolidated poll
useEffect(() => {
const poll = async () => {
const [git, mcp] = await Promise.all([
getGitStatus(),
getMcpStatus()
]);
setGitStatus(git);
setMcpStatus(mcp);
};
poll();
const interval = setInterval(poll, 60_000);
return () => clearInterval(interval);
}, []);
What Gets Removed
- settings-reader.ts polling → Settings read once on startup
- context-detector.ts polling → Context from PostToolUse events
- transcript-reader.ts polling → Token data from PostToolUse events
Token/Context Data Source
Previously: Polling transcript file Now: Extract from PostToolUse event response
// In capture-event.sh, extract from PostToolUse
if [[ "$HOOK_EVENT_NAME" == "PostToolUse" ]]; then
# Extract token usage from the response
tokens=$(echo "$response" | jq -r '.usage // empty')
fi
Consequences
Positive
- Single source of truth: One event stream, not multiple pollers
- No flickering: Updates come from one place
- Real-time: Hook events are immediate
- Less CPU: One timer instead of three
- Predictable: State changes trace back to discrete events
Negative
- Dependency on hooks: If hooks don't fire, no data
- Some data requires polling: Git status not in events
- Event ordering: Must handle out-of-order events gracefully
Migration Path
- Enrich capture-event.sh to include all needed data
- Remove separate polling utilities
- Add single consolidated poll for git/mcp
- Update hooks to process enriched events
Implementation Notes
- capture-event.sh must be non-blocking (use
timeoutfor commands) - EventReader handles reconnection transparently
- State hooks receive typed events, not raw JSON