Dashcam
Architecture

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:

  1. Load config from ~/.dashcam/config.json
  2. Open SQLite database, run migrations
  3. Start JSONL file watcher
  4. Start HTTP + WebSocket server
  5. Start session manager (window tracking)
  6. Start bridges (if enabled via flags)
  7. Write PID file

Shutdown sequence:

  1. Close WebSocket connections
  2. Stop file watchers and bridges
  3. Checkpoint SQLite WAL
  4. 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 Accepted immediately.
  • 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 cs CLI
  • ECC Bridge — Tracks Everything Claude Code agent harness events

Tech Stack

LayerTechnologyWhy
RuntimeBunFast startup, native SQLite driver (bun:sqlite), single binary
DashboardReact 19Concurrent rendering for smooth timeline scrubbing
StylingTailwind CSS v4Utility-first, dark theme support, minimal CSS bundle
PanelsDockviewVS Code-style dockable, tabbable, splittable panels
TimelineCanvas API60fps rendering of 10K+ event markers with zoom and scrub
DatabaseSQLiteZero-config, embedded, WAL mode for concurrent access, FTS5 for search
ScreenshotsPlaywrightHeadless Chromium for pixel-perfect captures
IconsPhosphor IconsConsistent, 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 changed

Events 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.

On this page