Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions .claude/skills/datadog-sdk-event-inspection/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
---
name: datadog-sdk-event-inspection
description: Use when verifying SDK event payloads in the Browser SDK sandbox, debugging what events are emitted, or validating custom attributes and event fields via chrome_devtools MCP.
---

# Datadog Browser SDK — Event Inspection

## Overview

The SDK calls `window.__ddBrowserSdkExtensionCallback(msg)` for every event before
batching. Setting this callback captures full event payloads before they reach intake.

**Core principle:** Set the callback BEFORE the SDK emits the event you want to inspect.

**Do not use network request interception for this.** Events are not flushed immediately (view events only finalize on navigation/session end). The callback approach is reliable and gives you events the moment they are emitted.

## When to Use

- Verifying a change emits the correct event type or fields
- Checking custom attributes or user attributes appear on an event
- Debugging why an action/error/log was or wasn't captured
- Validating SDK behavior in the sandbox (`yarn dev` → `http://localhost:8080`)

## Setup — Choose Your Injection Path

```mermaid
flowchart TD
Q["Need initial view event<br/>or events from init()?"]
Q -- "yes" --> B["Path B: CDP pre-load injection"]
Q -- "no" --> A["Path A: Console injection"]
```

### Path A — Console Injection (post-load events only)

For actions, errors, logs, and events triggered **after** the page has fully loaded.

Paste in the DevTools console after the page loads:

```js
window.__ddBrowserSdkExtensionCallback = (msg) => {
console.log('[SDK Event]', JSON.stringify(msg))
}
```

Then trigger the interaction. Read `[SDK Event]` lines from console output.

### Path B — CDP Pre-load Injection (initial view + events from init)

The sandbox calls `DD_RUM.init()` synchronously in `<head>`. The initial view event fires during page load — before any post-load console injection runs.

**Before navigating**, use the `chrome-devtools_navigate_page` tool with the `initScript` parameter. This injects the script into every new document before any other code runs — before `DD_RUM.init()` fires.

```
Tool: chrome-devtools_navigate_page
Params:
type: "url"
url: "http://localhost:8080"
initScript: "window.__ddBrowserSdkExtensionCallback = (msg) => { console.log('[SDK Event]', JSON.stringify(msg)) }"
```

Every event — including the initial view — will appear in console logs.

**Do not inject via console and then reload.** Reloading clears the injected console code.

## Event Message Structure

```
{ type: 'rum', payload: RumEvent } // view, action, error, resource, long_task
{ type: 'logs', payload: LogsEvent }
{ type: 'telemetry', payload: TelemetryEvent }
{ type: 'record', payload: { record } } // Session Replay only
```

## Key Fields Quick Reference

| What to check | Field path |
| --------------------------------------------------------- | -------------------------------- |
| Event category | `msg.type` |
| RUM sub-type | `msg.payload.type` |
| User attribute (via `setUser`) | `msg.payload.usr.<key>` |
| Global context attribute (via `setGlobalContextProperty`) | `msg.payload.context.<key>` |
| Action name | `msg.payload.action.target.name` |
| Error message | `msg.payload.error.message` |
| View URL | `msg.payload.view.url` |
| Log message | `msg.payload.message` |
| Telemetry status | `msg.payload.telemetry.status` |

Note: user attributes use `usr` (not `user`) in the serialized payload.

## Flush Caveat

**View events are only finalized when the view ends** (navigation or session end).
The initial view event will be updated multiple times as the page runs — earlier emissions are incomplete.

To get the final view event: trigger a navigation, or — **only as a last resort for debugging, as it permanently ends the current session** — call `window.DD_RUM.stopSession()` in the console.

## Common Mistakes

| Mistake | Fix |
| -------------------------------------------------- | --------------------------------------------------------------------- |
| Using network requests to verify events | Events may not be flushed yet; use the callback instead |
| Console injection + reload to capture early events | Reload clears injected code — use Path B (CDP pre-load) |
| Missing the initial view event | Use Path B: `Page.addScriptToEvaluateOnNewDocument` before navigating |
| Seeing no `[SDK Event]` logs after reload | The callback was cleared by reload — re-run Path B setup |
| Incomplete view event data | View updates until view ends — navigate away or call `stopSession()` |
| Looking for `user.plan` in payload | It's `usr.plan` in serialized RUM events |
195 changes: 119 additions & 76 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,41 +9,23 @@ This project uses Yarn workspaces (v4.12.0). Never use `npm` or `npx`.
## Key Commands

```bash
# Development server
yarn dev

# Build all packages
yarn build

# build test apps (for E2E and performance testing)
yarn build:apps

# Run unit tests
yarn test:unit

# Run specific test file
yarn test:unit --spec packages/core/src/path/to/feature.spec.ts

# Run tests on a specific seed
yarn test:unit --seed 123

# setup E2E tests (installs Playwright and builds test apps)
yarn test:e2e:init

# Run E2E tests
yarn test:e2e

# Run specific E2E test which names match “unhandled rejections”
yarn test:e2e -g "unhandled rejections"

# Type checking
yarn typecheck

# Linting
yarn lint

# Format code
yarn format
yarn dev # Development server
yarn build # Build all packages
yarn build:apps # Build test apps (for E2E and performance testing)
yarn typecheck # Type checking
yarn lint # Linting
yarn format # Format code

# Unit tests (Jasmine + Karma)
yarn test:unit # Run all unit tests
yarn test:unit --spec packages/core/src/browser/addEventListener.spec.ts # Run single file
yarn test:unit --spec "packages/**/addEventListener.spec.ts" # Pattern match
yarn test:unit --seed 123 # Reproduce flaky test

# E2E tests (Playwright)
yarn test:e2e:init # Install Playwright and build test apps
yarn test:e2e # Run all E2E tests
yarn test:e2e -g "unhandled rejections" # Filter by name
```

## Monorepo Structure
Expand All @@ -69,66 +51,127 @@ test/
scripts/ # Build, deploy, release automation
```

## Critical Patterns
## Code Style

### Unit Tests
### Formatting (Prettier)

- Test framework: Jasmine + Karma. Spec files co-located with implementation: `feature.ts` → `feature.spec.ts`
- Focus tests with `fit()` / `fdescribe()`, skip with `xit()` / `xdescribe()`
- Use `registerCleanupTask()` for cleanup, NOT `afterEach()`
- Mock values/functions: wrap with `mockable()` in source, use `replaceMockable()` or `replaceMockableWithSpy()` in tests (auto-cleanup)
`singleQuote: true`, `semi: false`, `printWidth: 120`, `trailingComma: 'es5'`, `tabWidth: 2`

### Naming Conventions
### TypeScript

- Use **camelCase** for all internal variables and object properties
- Conversion to snake_case/pascal_case happens at the serialization boundary (just before sending events)
- Never use snake_case in internal code, even if the final event format requires it
- **No classes** — use plain functions and closures instead (enforced by lint)
- **No default exports** — named exports only
- **`interface` for object shapes** — not `type` aliases (enforced by `@typescript-eslint/consistent-type-definitions`)
- **Separate `import type`** — type-only imports must use `import type { ... }` on their own line
- **Import order**: builtin → external → internal → parent → sibling → index (enforced by `eslint-plugin-import`)
- **Prefer TypeScript type narrowing** over runtime type assertions
- **Use discriminated unions** to make invalid states unrepresentable at compile time
- **`const` object maps** for enums: `export const ErrorSource = { AGENT: 'agent' } as const` + `export type ErrorSource = (typeof ErrorSource)[keyof typeof ErrorSource]`
- **`const enum`** for compile-time-only constants (zero bundle cost): `const enum ErrorHandling { HANDLED = 'handled' }`
- **`camelCase`** for all internal variables and properties — snake_case only at the serialization boundary (event payload fields in `*Event.types.ts`)
- **Filenames**: `camelCase` in `src/`, `kebab-case` in `scripts/`
- **No `Date.now()`** — use `dateNow()` wrapper (mockable in tests)
- **No `console.*`** in package source code
- **No array spread** — use `.concat()` instead

### TypeScript Patterns
### Imports (Cross-Package vs Within-Package)

- Prefer **TypeScript type narrowing** over runtime type assertions (e.g., don't use `typeof x === 'object'` when proper return types can express the shape)
- Use discriminated unions and return types to make invalid states unrepresentable at compile time
```ts
// Cross-package: use package name
import type { TimeStamp } from '@datadog/browser-core'
import { addTelemetryUsage } from '@datadog/browser-core'

### Telemetry Usage
// Within-package: relative paths
import { createTaskQueue } from '../../tools/taskQueue'
```

- `addTelemetryUsage` tracks **which public API the customer calls and which options they pass** (static call-site information)
- Do NOT include runtime state analysis (e.g., whether a view was active, whether a value was overwritten) in telemetry usage — that belongs elsewhere
### Error Handling

### Auto-Generated Files
- **`monitor(fn)`** — wraps a function; catches any throw and routes to telemetry. Use for all public API methods.
- **`callMonitored(fn)`** — one-off call with error capture; use when a return value is needed.
- **`catchUserErrors(fn)`** — wraps user-provided callbacks only (keeps user errors separate from SDK errors).
- **No `try/catch` in domain code** — domain functions propagate errors via `undefined` returns or discriminated unions.
- Errors are represented as plain `RawError` data objects, not `Error` subclasses.

- **NEVER manually edit auto-generated files.** They have a `DO NOT MODIFY IT BY HAND` comment at the top — respect it
- Example: `telemetryEvent.types.ts` is generated from the `rum-events-format` schema repository
- Any changes to these files require a **corresponding PR in the upstream source repo first**, then regeneration
### Lifecycle / Cleanup Pattern

## Commit Messages
Modules return `{ stop }` and collect teardown work in a local array:

```ts
const cleanupTasks: Array<() => void> = []
const sub = someObservable.subscribe(handler)
cleanupTasks.push(() => sub.unsubscribe())
const { stop: stopChild } = startChildModule(...)
cleanupTasks.push(stopChild)
return { stop: () => cleanupTasks.forEach((task) => task()) }
```

### Observables

Custom `Observable<T>` (not RxJS). Constructor accepts an optional `onFirstSubscribe` callback that can return a teardown function. Use `BufferedObservable<T>` to replay buffered events to late subscribers.

### Mockable Pattern

Use gitmoji conventions (based on actual usage in this repo):
Wrap values/functions with `mockable()` in source to enable test substitution without DI:

### User-Facing Changes
```ts
// source
const result = mockable(startRum)(configuration, ...)
const url = mockable(location).href

- ✨ **New feature** - New public API, behavior, event, property
- 🐛 **Bug fix** - Fix bugs, regressions, crashes
- ⚡️ **Performance** - Improve performance, reduce bundle size
- 💥 **Breaking change** - Breaking API changes
- 📝 **Documentation** - User-facing documentation
- ⚗️ **Experimental** - New public feature behind a feature flag
// test
replaceMockable(startRum, fakeStartRum) // auto-cleaned up after each test
```

### Feature Flags

```ts
if (isExperimentalFeatureEnabled(ExperimentalFeature.SOME_FLAG)) { ... }
```

### Internal Changes
`ExperimentalFeature` is a real (non-const) enum — required for runtime string validation.

### Auto-Generated Files

**Never manually edit** files with a `DO NOT MODIFY IT BY HAND` header (e.g., `telemetryEvent.types.ts`). Changes require a PR in the upstream schema repo first, then regeneration.

## Unit Tests
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔨 warning: ‏It's missing mention of Jasmine and Karma. Without that Claude used to be confused in test APIs

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already there — AGENTS.md has - **Test framework**: Jasmine + Karma in the Unit Tests section.


- **Test framework**: Jasmine + Karma
- **Co-location**: `feature.ts` → `feature.spec.ts` in the same directory
- **Focus/skip**: `fdescribe` / `fit` to focus; `xdescribe` / `xit` to skip
- **Cleanup**: use `registerCleanupTask(() => ...)` — never `afterEach()`
- **DOM helpers**: use `appendElement(html)` — never manually `createElement` + `appendChild`
- **Time**: use `mockClock()` + `clock.tick(ms)` — never real `setTimeout` delays
- **One behavior per test**: keep `it()` blocks focused on a single assertion
- **TDD**: write the spec file before the implementation file (RED → GREEN → REFACTOR)

## Commit Messages

- 👷 **Build/CI** - Dependencies, tooling, deployment, CI config
- ♻️ **Refactor** - Code restructuring, architectural changes
- 🎨 **Code structure** - Improve code structure, formatting
- ✅ **Tests** - Add/fix/improve tests
- 🔧 **Configuration** - Config files, project setup
- 🔥 **Removal** - Remove code, features, deprecated items
- 👌 **Code review** - Address code review feedback
- 🚨 **Linting** - Add/fix linter rules
- 🧹 **Cleanup** - Minor cleanup, housekeeping
- 🔊 **Logging** - Add/modify debug logs, telemetry
Use gitmoji conventions:

| Emoji | Use case |
| ----- | ----------------------------------------- |
| ✨ | New feature (public API, behavior, event) |
| 🐛 | Bug fix |
| ⚡️ | Performance / bundle size |
| 💥 | Breaking change |
| ⚗️ | Experimental feature behind a flag |
| ♻️ | Refactor |
| ✅ | Tests |
| 👷 | Build / CI / dependencies |
| 🔥 | Removal |
| 🧹 | Cleanup |
| 🔊 | Telemetry / debug logging |
| 📝 | Documentation |
| 🎨 | Code structure / formatting |
| 🔧 | Configuration / project setup |
| 🚨 | Linting rules |
| 👌 | Address code review feedback |

## Git Workflow

- Branch naming: `<username>/<feature>` (e.g., `john.doe/fix-session-bug`)
- Always branch from `main` unless explicitly decided otherwise
- PR title follows commit message convention (used when squashing to main)
- PR template at `.github/PULL_REQUEST_TEMPLATE.md` - use it for all PRs
- PR template at `.github/PULL_REQUEST_TEMPLATE.md` — fill it out for all PRs