# Capture and Error Semantics

How the BugTape SDK captures, buffers, deduplicates, and submits bug reports.

## Automatic capture

Once `BugTape.init()` is called, six recorders start automatically:

| Recorder | Events captured | Can disable? |
|----------|----------------|-------------|
| DOM | rrweb visual replay (mutations, mouse, scroll, input) | `disableDomRecording: true` |
| Network | Fetch, XHR, resource timing (URL, status, duration) | `disableNetworkRecording: true` |
| Console | `console.log/warn/error/info/debug` | `disableConsoleRecording: true` |
| Errors | `window.onerror` — uncaught exceptions with stack traces | Always on |
| Rejections | `unhandledrejection` — unhandled promise rejections | Always on |
| Rage clicks | Rapid repeated clicks on same element | Always on |

Sensitive tables / client grids: mark with `data-bugtape-ignore` / `.bugtape-ignore` / `.rr-block` before any capture key. Prove with the [privacy proof checklist](./privacy-proof-checklist.md) and [privacy fixture](./examples/privacy-fixture/). Default PII regex is not enough for named clients in visible text.

All recorders are wrapped in error boundaries. If a recorder fails, the SDK logs a warning and continues — it never crashes the host application.

## Browser crash and tab close

The SDK does **not** attempt to capture browser crashes or `beforeunload` events. If the browser tab crashes or is closed:

- Buffered events are lost (they are in-memory only).
- Any pending report from a previous session that was stored in `localStorage` (due to network failure) will be resubmitted on the next `BugTape.init()`.

For crash monitoring, use a complementary service or beacon API.

## Unhandled error capture

### `window.onerror`

Captures uncaught exceptions globally. Event data:

```typescript
{ type: "error", data: { message, filename, lineno, colno, stack } }
```

The SDK does **not** call `preventDefault()` — errors still propagate to the browser console and any other error handlers.

### `unhandledrejection`

Captures unhandled promise rejections. Event data:

```typescript
{ type: "error:unhandledrejection", data: { message, stack } }
```

### Auto-reporting

Errors are **buffered, not auto-submitted**. The SDK does not automatically submit a report when an error occurs. Reports are only submitted when:

1. A user clicks the bug button and submits through the widget.
2. Your code calls the API directly (manual report).
3. You implement custom logic in the `onSubmit` callback.

If you want auto-reporting on errors, implement it yourself:

```typescript
window.addEventListener('error', async (event) => {
  await fetch('https://app.bugtape.ai/v1/ingest', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-BugTape-Key': 'bt_live_xxx',
    },
    body: JSON.stringify({
      title: event.error?.message || 'Uncaught error',
      severity: 'high',
      url: window.location.href,
      userAgent: navigator.userAgent,
      viewport: `${innerWidth}x${innerHeight}`,
      events: bt.getEvents(),
    }),
  });
});
```

**Recommendation:** If you implement auto-reporting, add throttling (see below).

## Manual issue reporting

Users file reports through the SDK widget (floating bug button) or your own UI. The widget flow:

1. User clicks bug button → widget opens.
2. User optionally captures a screenshot (with annotation tools).
3. User optionally records an audio note.
4. SDK generates an AI summary (if `openaiKey` configured).
5. User fills in title, description, severity.
6. User clicks submit → report sent to API with all buffered events.

For programmatic reporting without the widget, POST directly to `/v1/ingest`.

## Deduplication

### Server-side fingerprinting

Every report is fingerprinted on the server:

```
SHA256(errorType : normalizedMessage : normalizedUrl).slice(0, 16)
```

- **Same fingerprint + open bug**: report count incremented, no new bug created.
- **Same fingerprint + resolved bug**: bug reopened as regression.
- **New fingerprint**: new bug created.

### Client-side throttling (recommended)

The SDK does not deduplicate on the client. If you implement auto-reporting, add your own throttle:

```typescript
const reported = new Set<string>();

function shouldReport(errorMessage: string): boolean {
  const key = errorMessage.slice(0, 100);
  if (reported.has(key)) return false;
  reported.add(key);
  setTimeout(() => reported.delete(key), 60_000); // Allow re-report after 1 min
  return true;
}
```

**Recommended throttle:** Max 5 auto-reports per minute per session. The server rate limit is 100 requests/minute per IP, but lower client-side throttling reduces noise.

## Event ordering

Events are stored with millisecond Unix timestamps (`event.timestamp`). The SDK does not guarantee strict ordering — events from different recorders may interleave. The server stores events in insertion order.

When displaying events, sort by `timestamp`. For DOM replay, rrweb events contain their own internal ordering.

## Session correlation

The SDK does **not** currently assign a session ID. All events in a single report are implicitly from one session (the buffer contents at submission time).

To correlate multiple reports from the same user session:

1. Generate a session ID in your app (e.g., `crypto.randomUUID()`).
2. Include it in the report description or as a custom event:

```typescript
bt.buffer.push({
  type: 'session',
  timestamp: Date.now(),
  data: { sessionId: 'your-session-id', userId: 'user-123' }
});
```

3. Search for the session ID in the BugTape console.

## Attachments and replay data

### Screenshots

The SDK captures screenshots using `modern-screenshot` (DOM-to-image). Screenshots are:
- Rendered at device pixel ratio (up to 2x).
- Annotatable with a drawing tool before submission.
- Included as base64 in the report payload.

### Audio notes

Users can record voice notes via the MediaRecorder API:
- Format: WebM with Opus codec.
- Stored as base64 data URL in the report payload.

### Session replay

DOM events recorded by rrweb are included in the `events` array. The BugTape console can replay these events visually. The replay player is read-only — the rrweb events are opaque blobs stored as-is.

## Offline and flaky network behavior

### Retry logic

1. SDK submits the report via `fetch()`.
2. If the request fails, the SDK retries **once** after a 1-second delay.
3. If the retry also fails, the report is serialized and stored in `localStorage` under a BugTape-specific key.

### Pending report resubmission

On the next `BugTape.init()` call (e.g., page reload), the SDK checks `localStorage` for pending reports and resubmits them. Successfully submitted pending reports are cleared from storage.

### Buffered events during offline

Events continue to be captured in the ring buffer while offline. If the user submits a report while offline, the events captured up to that point are included. The report will be stored locally and resubmitted when connectivity returns.

### Network failure events

Network failures are captured as `network:fetch:error` events and included in the buffer, providing context for debugging connectivity issues.

## Best practices for auto-reporting

1. **Throttle client-side.** Max 5 auto-reports per minute. Use a `Set` or `Map` to track recently reported error messages.

2. **Prioritize severity.** Auto-report `critical` and `high` severity errors (uncaught exceptions, unhandled rejections). Log `medium` and `low` severity events to the buffer only.

3. **Include buffered events.** Always call `bt.getEvents()` and include the result. This gives BugTape the full context (what happened before the error).

4. **Set reporterEmail.** If you know the user's identity, include `reporterEmail` so reports can be attributed.

5. **Handle 402 gracefully.** If the API returns 402 (plan limit), stop auto-reporting for the rest of the session to avoid noise.

6. **Handle 429 gracefully.** If rate-limited, back off exponentially. The `retryAfter` field tells you how long to wait.

7. **Don't suppress widget reports.** Even with auto-reporting, keep the widget available. User-submitted reports include context (title, description, screenshot) that auto-reports lack.
