Architecture Overview
How Dashcam is built: Bun daemon, React dashboard, SQLite, and WebSocket.
Architecture Overview
Dashcam is a single-process application. One Bun daemon handles HTTP serving, WebSocket connections, file watching, SQLite persistence, screenshot capture, and the React dashboard — all on a single port. There is no Redis, no Postgres, no message queue, no microservice mesh. This simplicity is deliberate: a flight recorder for coding agents should be as easy to run as the agents themselves.
System Diagram
┌─────────────────────────────┐
│ Dashboard (React 19) │
│ served at :57580 │
│ │
│ Session Browser │ Timeline │
│ Dockable Panels │ Live View │
└──────────┬────────────────────┘
│
HTTP + WebSocket
│
┌──────────▼────────────────────┐
│ Daemon (Bun) │
│ │
│ ┌───────────────────────────┐ │
│ │ HTTP Server │ │
│ │ ├─ REST API (/api/*) │ │
│ │ ├─ Hook endpoints (/hook)│ │
│ │ └─ Static files (SPA) │ │
│ ├───────────────────────────┤ │
│ │ WebSocket Server (/ws) │ │
│ │ └─ Channel broadcasts │ │
│ └───────────┬───────────────┘ │
│ │ │
│ ┌───────────▼───────────────┐ │
│ │ Event Collector │ │
│ │ ├─ Hook receiver │ │
│ │ └─ JSONL file watcher │ │
│ └───────────┬───────────────┘ │
│ │ │
│ ┌───────────▼───────────────┐ │
│ │ SQLite (bun:sqlite) │ │
│ │ WAL mode, FTS5 │ │
│ └───────────────────────────┘ │
│ │
│ ┌───────────────────────────┐ │
│ │ Session Manager │ │
│ │ └─ 5-hour window FSM │ │
│ ├───────────────────────────┤ │
│ │ Screenshot Service │ │
│ │ └─ Playwright + phash │ │
│ ├───────────────────────────┤ │
│ │ Video Service │ │
│ │ └─ Playwright recording │ │
│ ├───────────────────────────┤ │
│ │ Bridges (optional) │ │
│ │ ├─ Loki Mode bridge │ │
│ │ ├─ Claude Squad bridge │ │
│ │ └─ ECC bridge │ │
│ └───────────────────────────┘ │
└─────────────────────────────────┘
▲
┌──────────────┴──────────────────┐
│ Claude Code Hooks │
│ (curl POST, fire-and-forget) │
│ │
│ JSONL Files │
│ (~/.claude/projects/*/*.jsonl) │
└──────────────────────────────────┘Module Breakdown
Daemon (src/daemon/index.ts)
The orchestrator. Starts every service in sequence, handles shutdown gracefully, and manages the process lifecycle.
Startup sequence:
- Load config from
~/.dashcam/config.json - Open SQLite database, run migrations
- Start JSONL file watcher
- Start HTTP + WebSocket server
- Start session manager (window tracking)
- Start bridges (if enabled via flags)
- Write PID file
Shutdown sequence:
- Close WebSocket connections
- Stop file watchers and bridges
- Checkpoint SQLite WAL
- Remove PID file
Event Collector (src/daemon/collector.ts)
The funnel. All events — from hooks and from JSONL files — flow through the collector for normalization, deduplication, and persistence.
Two input channels:
- Hook receiver — HTTP POST endpoints that receive events from Claude Code hooks. Returns
202 Acceptedimmediately. - JSONL watcher —
fs.watch()on~/.claude/projects/*/with incremental parsing. Tracks byte offset per file.
Output: Normalized events inserted into SQLite, broadcast via WebSocket, and evaluated for screenshot triggers.
Session Manager (src/daemon/session-manager.ts)
Tracks 5-hour subscription windows using a finite state machine: IDLE to ACTIVE to WARNING to WINDING_DOWN to COOLDOWN and back to IDLE. Persists window state to SQLite so it survives daemon restarts.
Screenshot Service (src/daemon/screenshot-service.ts)
Evaluates whether a screenshot should be captured after each PostToolUse event. Scans for running dev servers, launches Playwright for capture, computes perceptual hashes for deduplication, and generates thumbnails.
Video Service (src/daemon/video-service.ts)
Detects test execution commands in PostToolUse events and records the test run as a WebM video using Playwright's built-in recording capability.
Bridges (src/daemon/*-bridge.ts)
Optional modules that integrate with external tools:
- Loki Bridge — Watches
.loki/state files and connects to Loki Mode's WebSocket on port 57374 - Squad Bridge — Monitors tmux sessions created by the Claude Squad
csCLI - ECC Bridge — Tracks Everything Claude Code agent harness events
Tech Stack
| Layer | Technology | Why |
|---|---|---|
| Runtime | Bun | Fast startup, native SQLite driver (bun:sqlite), single binary |
| Dashboard | React 19 | Concurrent rendering for smooth timeline scrubbing |
| Styling | Tailwind CSS v4 | Utility-first, dark theme support, minimal CSS bundle |
| Panels | Dockview | VS Code-style dockable, tabbable, splittable panels |
| Timeline | Canvas API | 60fps rendering of 10K+ event markers with zoom and scrub |
| Database | SQLite | Zero-config, embedded, WAL mode for concurrent access, FTS5 for search |
| Screenshots | Playwright | Headless Chromium for pixel-perfect captures |
| Icons | Phosphor Icons | Consistent, well-designed icon set |
Data Flow
The complete path from an agent action to a dashboard update:
1. Claude Code executes a tool (e.g., Write)
│
2. PostToolUse hook fires ──curl POST──▶ /hook/post-tool-use
│
3. Collector normalizes the event
│
┌──────┼──────────┐
│ │ │
4. SQLite 5. WebSocket 6. Screenshot
INSERT broadcast evaluation
│ │
Dashboard Playwright
updates captures URL
in <100ms if changedEvents also arrive via the JSONL watcher (step 2 alternative), which tails ~/.claude/projects/*/*.jsonl files. Both channels converge at the collector (step 3) and follow the same persistence and broadcast path.
The entire system runs in a single OS process. There is no IPC overhead, no serialization between services, and no network calls between components. This is why events reach the dashboard in under 100ms.