---
name: probie-integration
description: Add Probie passive session capture (JS errors, failing requests, rage clicks, form friction, triaged into pulses) to an app. Use when asked to integrate Probie, add session telemetry, or wire up issue capture.
---

# Integrate Probie

Probie watches real user sessions and turns breakage into triaged issues (pulses). Passive capture does not read input values, hashes element labels, and drops query strings apart from `utm_*` campaign tags.

This copy is not tied to a project. Provision it before editing application code:

1. Infer a concise project name from repository metadata. Prefer, in order, the package/app manifest name, the repository directory or git remote, then ask if neither is meaningful.
2. Ask the human for their email address. Also ask this separate yes/no question: **“May Probie send you limited setup or product follow-up?”** Registration is allowed when the answer is no. Do not infer either answer or use an email found in git config, commits, environment variables, or source files.
3. Generate a cryptographically random idempotency key with at least 32 bytes of entropy and retain it for every retry of this registration. Do not print it or commit it.
   Never follow the old instruction “Ask the user for their token”; registration returns the token directly.
4. Register the project:

```bash
curl -fsS -X POST https://probie.dev/api/agent/registrations \
  -H 'Content-Type: application/json' \
  -H "Idempotency-Key: $PROBIE_REGISTRATION_KEY" \
  -d '{"project_name":"PROJECT_NAME","email":"HUMAN_EMAIL","contact_consent":false}'
```

Use the human's actual consent answer. This is an integration, so keep `project_name` in the request (inspection-only agents omit it or call `register_inspection_access`). Save `dispatch_token`, `widget_token`, `skill_url`, `claim_url`, and `expires_at` from the JSON response for the rest of this run. A retry must use the retained key and identical fields and returns the original credentials; changing any registration field with the same key returns 409. The transactional claim email is sent whether consent is true or false.

5. Substitute the returned `widget_token` everywhere this file says `<YOUR_WIDGET_TOKEN>`, install the matching platform integration below, and run the passive-event verification.
6. Tell the human the `claim_url` and exact `expires_at`. Explain that they must sign in with the emailed address before expiration to attach the project and retained telemetry to an account.

- Widget token after registration: `<YOUR_WIDGET_TOKEN>`
- Host: `https://probie.dev`

## Choose the integration by platform

**Server-rendered or static web (plain HTML, Rails, Django, PHP, ...)**: add before `</body>` on every page to watch:

```html
<script src="https://probie.dev/assets/probie-widget.js" data-token="<YOUR_WIDGET_TOKEN>"></script>
```

This includes a floating "Report an issue" button plus passive capture.

**Capture only, no visible UI**: if the app should not render a Probie button or dialog, load the collector bundle on its own. Same attributes, same signals, nothing rendered:

```html
<script src="https://probie.dev/assets/probie-widget-events.js" data-token="<YOUR_WIDGET_TOKEN>"></script>
```

Use this when the product has its own feedback surface, or when the UI is not wanted on the page. The reverse (`data-events="0"` on the main widget) gives button-only with no capture.

**React / Next.js / Vue via npm (client-rendered)**: no package to install; the script is served from the Probie instance. Inject once on the client, env-gated so dev and preview builds no-op:

```js
// Root layout / app entry. Guard for SSR: the widget needs the DOM.
const token = process.env.NEXT_PUBLIC_PROBIE_TOKEN; // set to <YOUR_WIDGET_TOKEN> in the deploy env
if (token && typeof document !== 'undefined' && !document.getElementById('probie-widget')) {
  const s = document.createElement('script');
  s.id = 'probie-widget';
  s.src = 'https://probie.dev/assets/probie-widget.js';
  s.dataset.token = token;
  s.async = true;
  document.body.appendChild(s);
}
```

For capture only with no visible UI, point `s.src` at `https://probie.dev/assets/probie-widget-events.js` instead. Everything else stays the same.

After load, attribute sessions to a signed-in user: `window.probie('identify', userId)`. This works with either bundle.

**Expo / React Native (native)**

This is a two-step install. **Do step 1, then stop.** Step 2 is optional and must not be done unless the user asks for it.

*Step 1, the whole install:*

```bash
npx expo install @probie-dev/react-native
```

The package has no runtime dependencies and cannot conflict with the app's Expo SDK. **If npm reports a version conflict, do not "fix" it.** Never add an `overrides` or `resolutions` block to the app's package.json, never pass `--legacy-peer-deps` or `--force`, and never change the versions of the app's existing packages. Stop and report the conflict: it is coming from something other than this package.

Then mount the provider once at the app root. Find the real root rather than assuming: Expo Router apps use `app/_layout.tsx`, classic apps use `App.tsx`. Add the token directly: it is public and belongs in source the same way a Sentry DSN or PostHog key does:

```tsx
import { ProbieProvider } from '@probie-dev/react-native';

export default function RootLayout() {
  return (
    <ProbieProvider config={{ token: '<YOUR_WIDGET_TOKEN>', apiBase: 'https://probie.dev' }}>
      {/* the app's existing root content, unchanged */}
    </ProbieProvider>
  );
}
```

That is the install. It captures JS errors, unhandled promise rejections, network failures, and app lifecycle. Do not add env-var plumbing, wrapper components, identify/reset call sites, or navigation and tap instrumentation as part of this step. Stop here and tell the user what step 2 would add.

*Step 2, richer capture, only when asked:*

- Screens: `useProbieExpoRouter()` (Expo Router) or `trackScreen(name)` (hand-rolled navigation)
- Taps and rage-taps: swap `Pressable` for `ProbiePressable` on the elements that matter
- Form friction: `useProbieForm()`: telemetry only, it does not perform the submit
- User attribution: `identify(userId)` at login, `reset()` at logout

Exact signatures are in the package README (`node_modules/@probie-dev/react-native/README.md`) and its TypeScript types. Add one signal at a time; each is independent.

Expo on web: use the npm/web path above when `Platform.OS === 'web'`.

**macOS / iOS (Swift, native)**: add the SwiftPM dependency and start the SDK once, early. Instrumentation is explicit: there is no auto-capture or swizzling:

```swift
// Package.swift dependency (Xcode: File → Add Package Dependencies… with the same URL)
.package(url: "https://github.com/ProbieAI/probie-swift", from: "0.1.0")
```

```swift
import Probie

// App init. Read the token from build config (xcconfig/Info.plist) and skip
// start() when it is absent so dev builds capture nothing.
if let token = Bundle.main.object(forInfoDictionaryKey: "ProbieToken") as? String, !token.isEmpty {
    Probie.start(.init(token: token, apiBase: "https://probie.dev"))
}
```

Then instrument where it matters: `.probieScreen("Checkout")` / `.probieTap("buy", label: "Buy now")` on SwiftUI views, `Probie.form("signup")` for form friction, `Probie.captureError(error)` in catch blocks, and `URLSession(configuration: Probie.instrumented(.default))` for failing-request capture. `Probie.identify(userId)` at login, `Probie.reset()` at logout; both are safe no-ops if start() never ran. Sandboxed macOS apps need the `com.apple.security.network.client` entitlement.

## Conventions

- Mirror the codebase's existing analytics wiring (PostHog, Segment, ...): same file layout, same env-gating pattern, identify/reset at the same call sites.
- The token is public and safe to commit. Env-gating is optional: do it only if the team already gates its other analytics, or if they ask to keep local development out of pulses. On native, read the env var into a variable and render the plain children when it is missing (`ProbieProvider` throws on an empty token); on web, skip injecting the script.
- Do not double-install: check for an existing Probie script tag or provider first.
- Change as little as possible. Adding Probie should touch one file on native and one on web. If a change requires editing a lockfile, a build config, or another package's version, stop and ask first.

## Verify before claiming done

1. Web: render the app with the env var set and confirm the HTML contains exactly one Probie script tag with `data-token`; without the env var, zero. Native: confirm the app still builds and that `git diff` touches only the root layout and, for a fresh install, package.json's dependency on `@probie-dev/react-native`.
2. Prove the pipe end-to-end (uses the real token; sends one synthetic pageview):

```bash
curl -s -X POST https://probie.dev/api/widget/events -H 'Content-Type: application/json' -d '{"token":"<YOUR_WIDGET_TOKEN>","events":[{"type":"pageview","ts":'$(date +%s000)',"session_id":"skill-verify","url":"","route":"verify","referrer":"","element":null,"payload":{}}]}'
```

Expect `{"received":1,...}`. A 401 means the token is wrong; a 400 names the malformed field.

3. Do not submit a Pulse or expect derived analytics before the human claims the project. A successful synthetic passive event is the complete pre-claim verification. Report the saved claim URL and expiration now.

