# Event Debugger

Real-time interactive terminal dashboard for monitoring application events, job flows, and background workers.

## Overview

The Event Debugger provides a full-screen, scrollable terminal interface that displays:

- **Active flows** — ongoing jobs and operations with live updates
- **Completed flows** — recently finished operations with final status
- **Nested events** — global events automatically nested under their parent job
- **Error details** — full stack traces for failed operations
- **Job logging** — log-level-aware output from background workers

## Features

### Full-Screen Dashboard

- **Alternate screen buffer** — takes over the terminal like `vim` or `htop`
- **No scrollback pollution** — exits cleanly, restoring your original terminal
- **Hidden cursor** — clean display without cursor flicker
- **Auto-refresh** — updates every 80ms with smooth spinners

### Console Capture

- **Intercepts all console output** — `console.log`, `console.info`, `console.warn`, `console.error`, `console.debug`
- **Displays in dedicated section** — console logs appear below flows with timestamps
- **Color-coded by level** — errors in red, warnings in yellow, info in cyan, etc.
- **Formatted output** — objects are JSON-stringified, errors show name and message
- **Circular buffer** — keeps last 100 console logs, automatically pruning old entries
- **Clearable** — press `c` to clear all console logs

### Interactive Scrolling

- **Keyboard navigation** — vim-style and arrow key controls
- **Viewport windowing** — only renders visible content
- **Scroll indicator** — shows current position and available navigation
- **Bounded scrolling** — automatically clamps to valid ranges

### Smart Flow Tracking

- **Hierarchical flows** — groups related events under a single header
- **Job-aware routing** — associates global events with their parent job
- **Per-worker isolation** — each job gets its own flow keyed by `workerId`
- **Terminal event detection** — flows complete on `DONE`, `SKIP`, `ERROR`, or `job:finished`
- **Late event handling** — socket events arriving after job completion are appended to completed flows

### Log Level Support

Jobs can emit logs with different severity levels:

- `INFO` (cyan) — informational messages
- `WARN` (yellow) — warnings
- `ERROR` (red) — errors
- `DEBUG` (gray) — debug output

## Usage

### Setup

```typescript
import { setupEventDebugger } from "./modules/logging/eventDebugger";

// Enable in development
if (Bun.env.NODE_ENV === "development") {
  setupEventDebugger();
}
```

### Emitting Events

```typescript
import { events } from "./modules/globalEvents";

// Start a job (creates a flow)
events.emit("job:started", {
  workerId: "abc123",
  queueType: WorkerQueue.REFRESH_ACTIVE_OPPORTUNITIES,
});

// Log from within a job
events.emit("job:log", {
  message: "Processing 500 opportunities",
  level: "INFO",
  timestamp: new Date().toISOString(),
  workerId: "abc123",
  queueType: WorkerQueue.REFRESH_ACTIVE_OPPORTUNITIES,
});

// Emit global events (automatically nested under the job)
events.emit("cache:opportunities:refresh:started", { totalOpportunities: 500 });

// Finish the job (terminates the flow)
events.emit("job:finished", {
  workerId: "abc123",
  queueType: WorkerQueue.REFRESH_ACTIVE_OPPORTUNITIES,
});
```

### Worker Logging

```typescript
import { workerLog } from "./modules/workers/jobFactory";

async function myWorker(workerSocket: Socket) {
  workerLog(workerSocket, "Starting refresh", "INFO");
  workerLog(workerSocket, "Cache miss detected", "WARN");
  workerLog(workerSocket, "Failed to fetch data", "ERROR");
  workerLog(workerSocket, "Detailed trace info", "DEBUG");
}
```

### Console Logging

All console output is automatically captured and displayed:

```typescript
console.log("Processing user request");
console.info("Connected to database");
console.warn("Rate limit approaching");
console.error("Database connection failed");
console.debug("Query execution time: 45ms");
```

These appear in the `CONSOLE OUTPUT` section below all flows.

## Keyboard Controls

| Key             | Action                    |
| --------------- | ------------------------- |
| `↑` or `k`      | Scroll up one line        |
| `↓` or `j`      | Scroll down one line      |
| `Page Up`       | Scroll up one page        |
| `Page Down`     | Scroll down one page      |
| `Home` or `g`   | Jump to top               |
| `End` or `G`    | Jump to bottom            |
| `c`             | Clear console logs        |
| `q` or `Ctrl+C` | Exit and restore terminal |

## Configuration

Environment variables control display behavior:

```bash
# Disable dynamic rendering (fallback to simple line-by-line logging)
export EVENT_DEBUGGER_DYNAMIC=false

# Maximum children shown per active flow (default: 20)
export EVENT_DEBUGGER_MAX_FLOW_CHILDREN=50

# Maximum children shown per completed flow (default: 4)
export EVENT_DEBUGGER_MAX_COMPLETED_CHILDREN=10

# Event name column width (default: 96)
export EVENT_DEBUGGER_EVENT_WIDTH=120

# Summary column width (default: 180)
export EVENT_DEBUGGER_SUMMARY_WIDTH=200
```

## Display Format

### Flow Header

```
⠋ 12:34:56 [START] cache > opportunities > refresh > active (2.3s)
```

- **Spinner** — rotating while active, status icon when complete (✓ ✗ ⊘)
- **Timestamp** — HH:MM:SS of last update
- **Status** — current label (START, UPDATE, DONE, ERROR, etc.)
- **Title** — human-readable flow name
- **Elapsed** — time since flow started

### Job Flow Header

```
⠋ 12:34:56 [START] [JOB] cache > opportunities > refresh > active #a3f1b2c8 (2.3s)
```

- `[JOB]` prefix
- Queue type as human-readable path
- First 8 characters of workerId

### Child Events

```
  - 12:34:56 INFO   cache:opportunities:refresh:started -> totalOpportunities=500
  - 12:34:57 WARN   job:log -> message=Cache miss detected level=WARN workerId=abc123
```

- Indented under parent flow
- Timestamp + Label + Event name + Summary
- Color-coded by log level (for job logs) or event type

### Completed Flows

```
✓ 12:35:00 [DONE] cache > opportunities > refresh > active (14.6s)
  - final: 12:34:59 DONE   cache:opportunities:refresh:completed -> totalOpportunities=500 updated=472
  ... 18 older event(s) hidden
```

- Only last 4 children shown (configurable)
- "final:" prefix on children
- Count of hidden events if truncated

### Console Output Section

```
━━━ CONSOLE OUTPUT ━━━

  12:34:56 INFO   Connected to database
  12:34:57 WARN   Rate limit approaching: 95/100
  12:34:58 ERROR  Database connection failed
  12:34:59 DEBUG  Query execution time: 45ms
  12:35:00 LOG    Processing user request
```

- Appears below all flows
- Horizontal separator with section title
- Timestamp + Level + Message
- Objects are JSON-stringified
- Errors show `ErrorName: message`
- Last 100 logs kept (circular buffer)
- Press `c` to clear

## Flow Lifecycle

### 1. Flow Creation

A flow is created when:

- A `job:started` event is emitted
- Any event with a `START`, `CHECK`, or flow-tracked label arrives

### 2. Event Nesting

Events are nested under a flow if:

- Their event name starts with `job:` and shares the same `workerId`
- Their event name matches a registered job prefix (e.g., `cache:opportunities:refresh:*`)
- They have a flow-tracked label (`UPDATE`, `LOG`, etc.)

### 3. Flow Completion

A flow terminates when:

- A `job:finished` or `job:error` event arrives (for job flows)
- Any event with a terminal label (`DONE`, `SKIP`, `ERROR`) arrives (for non-job flows)

Completed flows are:

- Moved to the completed section
- Displayed for 5 minutes (or until `ERROR` status)
- Automatically pruned after TTL expires

## Technical Details

### Alternate Screen Buffer

Uses ANSI escape sequences to take over the terminal:

- `\x1b[?1049h` — enter alternate screen
- `\x1b[?1049l` — exit alternate screen
- `\x1b[?25l` — hide cursor
- `\x1b[?25h` — show cursor
- `\x1b[H` — move cursor to home (0,0)
- `\x1b[J` — clear from cursor to end of screen

### Raw Mode Input

Enables character-by-character input without buffering:

- Captures arrow keys, Page Up/Down, Home/End
- Supports vim-style navigation (`j`, `k`, `g`, `G`)
- Processes `Ctrl+C` and `q` for clean exit

### Event Prefix Mapping

When a job flow starts, its queue type is converted to an event prefix:

```
cache/opportunities/refresh/active → cache:opportunities:refresh
```

Any event matching that prefix is automatically nested under the job flow while it's active.

### Late Event Handling

Socket.io messages (`job:log`) arrive asynchronously. If they arrive after `job:finished` has already terminated the flow:

- The completed flow is located in `dashboardState.completed`
- The late event is appended to the completed flow's children
- The dashboard re-renders to show the updated flow
- No orphaned active flows are created

## Non-Interactive Mode

When TTY is not available or `EVENT_DEBUGGER_DYNAMIC=false`:

- Falls back to simple line-by-line logging
- No alternate screen buffer
- No scrolling or keyboard input
- Same event format, just appended to stdout

## Exit Behavior

On normal exit or `Ctrl+C`:

1. Raw mode is disabled
2. Cursor is shown
3. Alternate screen buffer is exited
4. Original terminal content is restored
5. Process exits cleanly

## Troubleshooting

**Dashboard doesn't appear**

- Check that `process.stdout.isTTY` is `true`
- Verify `EVENT_DEBUGGER_DYNAMIC` is not set to `false`
- Ensure you're running in a real terminal, not piping output

**Scroll doesn't work**

- Ensure `process.stdin.isTTY` is `true`
- Check that raw mode is enabled (no errors on startup)
- Try arrow keys and vim keys (`j`/`k`)

**Events not nesting under jobs**

- Verify `job:started` fires before any global events
- Check that `queueTypeToEventPrefix()` returns the correct prefix for your queue type
- Ensure global events match the prefix pattern (e.g., `cache:opportunities:refresh:*`)

**Ghost flows appear**

- This was fixed — late socket events are now routed to completed flows
- Ensure you're using the latest version of the event debugger

## Examples

### Simple Job Flow

```typescript
// 1. Start job
events.emit("job:started", { workerId: "worker-123", queueType: "my/queue" });

// 2. Log some work
events.emit("job:log", {
  message: "Processing items",
  level: "INFO",
  timestamp: new Date().toISOString(),
  workerId: "worker-123",
  queueType: "my/queue",
});

// 3. Complete job
events.emit("job:finished", { workerId: "worker-123", queueType: "my/queue" });
```

### Job with Nested Global Events

```typescript
// 1. Start job
events.emit("job:started", {
  workerId: "w1",
  queueType: "cache/opportunities/refresh/active",
});

// 2. Emit global events (auto-nested because prefix matches)
events.emit("cache:opportunities:refresh:started", { total: 100 });
events.emit("cache:opportunities:refresh:progress", { processed: 50 });
events.emit("cache:opportunities:refresh:completed", { updated: 48 });

// 3. Complete job
events.emit("job:finished", {
  workerId: "w1",
  queueType: "cache/opportunities/refresh/active",
});
```

Result: all `cache:opportunities:refresh:*` events are nested under the job flow.

## See Also

- `src/modules/globalEvents.ts` — Event bus and type definitions
- `src/modules/workers/jobFactory.ts` — Worker job creation and `workerLog()` helper
- `src/modules/workers/coms.ts` — Socket.io server for worker communication
