mirror of
https://github.com/jarrodwatts/claude-hud.git
synced 2026-05-18 13:52:42 +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>
132 lines
5.2 KiB
Markdown
132 lines
5.2 KiB
Markdown
# ADR 002: Data Flow Architecture
|
|
|
|
## Status
|
|
Accepted
|
|
|
|
## Context
|
|
The current implementation has multiple data sources:
|
|
1. FIFO events from hooks (PreToolUse, PostToolUse, etc.)
|
|
2. Polling settings reader (30s interval)
|
|
3. Polling context detector (30s interval)
|
|
4. 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)
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
1. **settings-reader.ts polling** → Settings read once on startup
|
|
2. **context-detector.ts polling** → Context from PostToolUse events
|
|
3. **transcript-reader.ts polling** → Token data from PostToolUse events
|
|
|
|
### Token/Context Data Source
|
|
|
|
Previously: Polling transcript file
|
|
Now: Extract from PostToolUse event response
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
1. Enrich capture-event.sh to include all needed data
|
|
2. Remove separate polling utilities
|
|
3. Add single consolidated poll for git/mcp
|
|
4. Update hooks to process enriched events
|
|
|
|
## Implementation Notes
|
|
|
|
- capture-event.sh must be non-blocking (use `timeout` for commands)
|
|
- EventReader handles reconnection transparently
|
|
- State hooks receive typed events, not raw JSON
|