Electron app that visualizes Claude Code session execution
Electron 28.x, React 18.x, TypeScript 5.x, Tailwind CSS 3.x, Zustand 4.x
Always use pnpm (not npm/yarn) for this project.
pnpm install- Install dependenciespnpm dev- Dev server with hot reloadpnpm build- Production buildpnpm typecheck- Type checkingpnpm lint:fix- Lint and auto-fixpnpm format- Format codepnpm test- Run all vitest testspnpm test:watch- Watch modepnpm test:coverage- Coverage reportpnpm test:coverage:critical- Critical path coveragepnpm test:chunks- Chunk building testspnpm test:semantic- Semantic step extraction testspnpm test:noise- Noise filtering testspnpm test:task-filtering- Task tool filtering tests
Use path aliases for imports:
@main/*→src/main/*@renderer/*→src/renderer/*@shared/*→src/shared/*@preload/*→src/preload/*
~/.claude/projects/{encoded-path}/*.jsonl - Session files ~/.claude/todos/{sessionId}.json - Todo data
Path encoding: /Users/name/project → -Users-name-project
isMeta: false= Real user message (creates new chunks)isMeta: true= Internal message (tool results, system-generated)
Independent chunk types for timeline visualization:
- UserChunk: Single user message with metrics
- AIChunk: All assistant responses with tool executions and spawned subagents
- SystemChunk: Command output/system messages
- CompactChunk: System metadata/structural messages
Each chunk has: timestamp, duration, metrics (tokens, cost, tools)
Task tool_use blocks are filtered when subagent exists Keep orphaned Task calls (no matching subagent) for visibility.
Claude Code's "Orchestrate Teams" feature: multiple sessions coordinate as a team.
- Process.team?
{ teamName, memberName, memberColor }— enriched by SubagentResolver from Task call inputs andteammate_spawnedtool results - Teammate messages arrive as
<teammate-message teammate_id="..." color="..." summary="...">content</teammate-message>in user messages (isMeta: false). Detected byisParsedTeammateMessage()— excluded from UserChunks, rendered asTeammateMessageItemcards - Session ongoing detection treats
SendMessageshutdown_response (approve: true) and its tool_result as ending events, not ongoing activity - Display summary counts distinct teammates (by name) separately from regular subagents
- Team tools: TeamCreate, TaskCreate, TaskUpdate, TaskList, TaskGet, SendMessage, TeamDelete — have readable summaries in
toolSummaryHelpers.ts
Tracks what consumes tokens in Claude's context window across 6 categories (discriminated union on category field):
| Category | Type | Source |
|---|---|---|
claude-md |
ClaudeMdContextInjection |
CLAUDE.md files (global, project, directory) |
mentioned-file |
MentionedFileInjection |
User @-mentioned files |
tool-output |
ToolOutputInjection |
Tool execution results (Read, Bash, etc.) |
thinking-text |
ThinkingTextInjection |
Extended thinking + text output tokens |
team-coordination |
TeamCoordinationInjection |
Team tools (SendMessage, TaskCreate, etc.) |
user-message |
UserMessageInjection |
User prompt text per turn |
- Types:
src/renderer/types/contextInjection.ts—ContextInjectionunion,ContextStats,TokensByCategory - Tracker:
src/renderer/utils/contextTracker.ts—computeContextStats(),processSessionContextWithPhases() - Context Phases: Compaction events reset accumulated injections, tracked via
ContextPhaseInfo - Display surfaces:
ContextBadge(per-turn popover),TokenUsageDisplay(hover breakdown),SessionContextPanel(full panel)
- Main: try/catch, console.error, return safe defaults
- Renderer: error state in Zustand store
- IPC: parameter validation, graceful degradation
- LRU Cache: Avoid re-parsing large JSONL files
- Streaming JSONL: Line-by-line processing
- Virtual Scrolling: For large session/message lists
- Debounced File Watching: 100ms debounce
rm -rf dist dist-electron node_modules
pnpm install
pnpm buildpnpm typecheckCheck for changes in message parsing or chunk building logic.
| Category | Convention | Example |
|---|---|---|
| Services/Components | PascalCase | ProjectScanner.ts |
| Utilities | camelCase | pathDecoder.ts |
| Constants | UPPER_SNAKE_CASE | PARALLEL_WINDOW_MS |
| Type Guards | isXxx | isRealUserMessage() |
| Builders | buildXxx | buildChunks() |
| Getters | getXxx | getResponses() |
// Message type guards (src/main/types/messages.ts)
isParsedRealUserMessage(msg) // isMeta: false, string content
isParsedInternalUserMessage(msg) // isMeta: true, array content
isAssistantMessage(msg) // type: "assistant"
// Chunk type guards
isUserChunk(chunk) // type: "user"
isAIChunk(chunk) // type: "ai"
isSystemChunk(chunk) // type: "system"
isCompactChunk(chunk) // type: "compact"
// Context injection type guards (component-scoped in ContextBadge.tsx, not exported)
isClaudeMdInjection(inj) // category: "claude-md"
isMentionedFileInjection(inj) // category: "mentioned-file"
isToolOutputInjection(inj) // category: "tool-output"
isThinkingTextInjection(inj) // category: "thinking-text"
isTeamCoordinationInjection(inj) // category: "team-coordination"
isUserMessageInjection(inj) // category: "user-message"src/main/services/ and its domain subdirectories have barrel exports via index.ts:
// Preferred
import { ChunkBuilder, ProjectScanner } from './services';
// Also valid
import { ChunkBuilder } from './services/analysis';Note: renderer utils/hooks/types do NOT have barrel exports — import directly from files.
- External packages
- Path aliases (@main, @renderer, @shared)
- Relative imports