mirror of
https://github.com/jarrodwatts/claude-hud.git
synced 2026-05-21 15:52:37 +00:00
docs: Add architecture decision records for v2
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>
This commit is contained in:
131
docs/adr/002-data-flow.md
Normal file
131
docs/adr/002-data-flow.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user