Files
claude-hud/docs/adr/002-data-flow.md
Jarrod Watts e39933fac2 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>
2026-01-02 23:10:26 +11:00

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