@reopt-ai/data-sdk-devtool
v0.2.0
Published
reopt-data SDK devtools — records what the browser SDK sends and shows it in a floating panel; off in production by default
Readme
@reopt-ai/data-sdk-devtool
A devtools panel for the reopt-data browser SDK: sanitized batches the SDK sent, the identity lifecycle the browser is tracked under, and any tabs your app adds.
Off in production by default. createDevtools() follows NODE_ENV; a production bundle carries an inert instance — the page's own fetch, an empty store, a component that renders null. Pass enabled: true to force it on for a demo or a staging deployment.
Small when inactive. No CSS import, UI library, or React context. The ESM entry is a ~0.8 KB gzip gate and the React entry is ~0.4 KB; the CommonJS compatibility entries are also guarded by explicit budgets. When disabled they keep the page's transport, render null, and request no recorder or UI chunks. When enabled, the recorder and status bar load lazily; the expanded panel loads only on the first click.
| Entry | What |
| ---------------------------------- | --------------------------------------------------------------------- |
| @reopt-ai/data-sdk-devtool | createDevtools() — a recording fetch and the store. No framework. |
| @reopt-ai/data-sdk-devtool/react | <ReoptDevtools />, useDevtoolsState() — "use client" |
Install
pnpm add -D @reopt-ai/data-sdk-devtoolPeer: @reopt-ai/data-sdk-client (and react for the panel).
Use
// lib/devtools.ts — one instance, shared by the SDK config and the panel
import { createDevtools } from "@reopt-ai/data-sdk-devtool";
export const devtools = createDevtools();
// createDevtools({ enabled: true }) to show it in production as well// the client boundary that creates the SDK client
"use client";
import { ReoptProvider } from "@reopt-ai/data-sdk-client/next";
import { ReoptDevtools } from "@reopt-ai/data-sdk-devtool/react";
import { devtools } from "@/lib/devtools";
export function Analytics({ children }) {
return (
<ReoptProvider config={{ writeKey, baseUrl: "/ingest", fetch: devtools.fetch, observe: devtools.observe }}>
{children}
<ReoptDevtools devtools={devtools} />
</ReoptProvider>
);
}fetch records the wire batch without intercepting or patching globals and reduces signed-session request/response data to a boolean. Sensitive headers, common personal fields, URLs, responses, and identity facts are redacted before they enter the store. observe adds enqueue-time lifecycle, identity, consent, tracking, and sanitized configuration facts, so an event appears immediately and advances from queued through sending to accepted, failed, or dropped. The status bar shows anonymous or signed in independently from session pending, session active, or no session, without exposing either identifier. The same pair works next to init() without a provider.
The Events tab uses low-contrast phase backgrounds instead of card outlines. Newly inserted rows reveal from the top with a short motion, while prefers-reduced-motion disables that animation.
Options
createDevtools({ enabled, maxBatches, maxEvents, maxTimeline, captureRaw, fetch })
enabled— defaultprocess.env.NODE_ENV !== "production".maxBatches— batches kept in memory, default 40.maxEvents— event lifecycle rows kept in memory, default 250.maxTimeline— identity, consent, and tracking rows kept in memory, default 100.captureRaw— retain exact diagnostic values, defaultfalse. Use only on a trusted local page.fetch— the transport to delegate to, default the page'sfetch.
<ReoptDevtools devtools panels title bottomOffset console expose />
panels— extra tabs:[{ id, label, render }]. The reopt-data example app uses one for its SDK option switches.bottomOffset— pixels to reserve below the tool for a host-owned footer.console—{ origin, projectId }; adds project and accepted-event links through the console's stable/open/projects/...resolver.expose— installwindow.__reoptDevtoolson mount (defaulttrue) so an end-to-end test can read the store:
const events = await page.evaluate(() => window.__reoptDevtools?.state().batches.flatMap((batch) => batch.events));Reading the store yourself
import { useDevtoolsState } from "@reopt-ai/data-sdk-devtool/react";
const { batches, totals, identity } = useDevtoolsState(devtools);The recorder redacts credentials and common personal fields before values enter memory, so the UI and window.__reoptDevtools are sanitized by default. Property names the recorder does not recognize can still carry sensitive application data; keep diagnostics scoped to development, staging, or an intentionally sanitized demo. captureRaw: true is an explicit local-only escape hatch.
