diff --git a/content/docs/expo-devtools/changelog.mdx b/content/docs/expo-devtools/changelog.mdx
index b178cc7..c0deac6 100644
--- a/content/docs/expo-devtools/changelog.mdx
+++ b/content/docs/expo-devtools/changelog.mdx
@@ -7,6 +7,64 @@ description: Every published release of @axonpack/expo-devtools, newest first.
Every published release, newest first. The current version is **2.5.4**.
+## 3.0.0
+
+
+
+- The whole list of what moved where is in
+ [Upgrading](https://axonpack.github.io/docs/expo-devtools/upgrading).
+
+**⚠️ Breaking Changes**
+
+- Setting the devtools up is now one provider around your app. `createDevtoolsClient`, `init()` and `` are gone, and there is no client to create or pass anywhere:
+
+ ```tsx
+ // before
+ export const devtools = createDevtoolsClient({ defaultTheme: 'dark' });
+ if (__DEV__) devtools.init();
+
+ <>
+
+ {__DEV__ && }
+ >;
+
+ // after
+
+
+ ;
+ ```
+
+- **`enabled` is back, and it is the only gate.** With it off the provider patches nothing, records nothing and draws no button, so the mount can stay in a release build unguarded. It is read on the first render, so it cannot be changed later in the session.
+
+- **An in-app browser takes one hook.** `useDevtoolsWebView` returns every prop the `` needs, in place of the four client helpers:
+
+ ```tsx
+ // before
+ ;
+
+ // after
+ const devtoolsWebView = useDevtoolsWebView('checkout');
+ ;
+ ```
+
+- **`webviewSources` is gone.** A browser view's name is whatever you hand the hook, and it is only the label its rows carry. Drop the option; nothing needs declaring up front.
+
+- **`mark`, `measure`, `clearMarks`, `clearMeasures`, `setCrashContext` and the stores** now come from the exported `devtools` object: `import { devtools } from '@axonpack/expo-devtools'`.
+
+**✨ Features**
+
+- **Open the panel from your own code.** `useDevtoolsPanel()` gives you `show`, `hide`, `toggle`, whether it is open, and whether the devtools are running at all.
+- **Hide the floating button.** `showFloatingButton={false}` leaves the panel working and takes the button off your screens.
+- **Everything is patched before your first screen mounts,** including whatever it requests as it appears.
+
## 2.5.4
diff --git a/content/docs/expo-devtools/console.mdx b/content/docs/expo-devtools/console.mdx
index d3d0759..279005a 100644
--- a/content/docs/expo-devtools/console.mdx
+++ b/content/docs/expo-devtools/console.mdx
@@ -44,20 +44,18 @@ Your app's files are bundled as private closures, so nothing can reach an import
way a browser console reaches a page's variables. Anything you want to poke at by name, hand over in
`context`:
-```ts
-createDevtoolsClient({
- console: { context: { store, queryClient } },
-});
+```tsx
+
```
It is also the only thing that works in a release build, where the module list the two helpers above
read is not available.
- `console.repl` defaults to `true`, and it is not gated on `__DEV__`. Once `init()` has run, the
- prompt is there — including in a release build, where it will run whatever is typed into it. Guard
- your `init()` call (see [Leaving it in production](/docs/expo-devtools/production)), or turn the prompt off
- explicitly with `console: { repl: false }`.
+ `console.repl` defaults to `true`, and it is not gated on `__DEV__`. Wherever the devtools are on the
+ prompt is there — including in a release build, where it will run whatever is typed into it. Ship
+ with `enabled: false` (see [Leaving it in production](/docs/expo-devtools/production)), or turn the prompt
+ off explicitly with `console: { repl: false }`.
## Limits
diff --git a/content/docs/expo-devtools/crash-reporting.mdx b/content/docs/expo-devtools/crash-reporting.mdx
index a743e48..8bae44a 100644
--- a/content/docs/expo-devtools/crash-reporting.mdx
+++ b/content/docs/expo-devtools/crash-reporting.mdx
@@ -23,24 +23,25 @@ Which tier caught a crash decides how much it can say.
## Reporting from a release build
-Crash capture has the only gate that is not `init()`. Setting one flag installs the handlers when the
-client is **constructed**, so an app can keep its usual development-only `init()` call and still report
-crashes from release:
+Crash capture has the only gate that is not `enabled`. Setting one flag installs the handlers even
+with the devtools off, so an app can keep its usual `enabled: __DEV__` and still report crashes from
+release:
```ts title="devtools.ts"
-export const devtools = createDevtoolsClient({
+export const devtoolsConfig = {
+ enabled: __DEV__,
crash: { enableWhileDevtoolsDisabled: true },
-});
+} satisfies DevtoolsConfig;
```
-On its own that captures **native exceptions only** — the crashes that end the app — and reports them
-in the compact sheet. A later `init()` upgrades it: the JS tiers install too and the full sheet takes
+With the devtools off that captures **native exceptions only** — the crashes that end the app — and
+reports them in the compact sheet. With them on, the JS tiers install too and the full sheet takes
over. It brings nothing else with it either way: no panel, no REPL, no console capture, no request
bodies.
-The JS tiers are held back before `init()` on purpose. They report errors the app survived, which is a
-developer's concern, and the sheet there is in front of somebody using the app. A fatal JS error still
-arrives, because React Native turns it into a native exception on its way to killing the process.
+The JS tiers are held back on purpose. They report errors the app survived, which is a developer's
+concern, and the sheet there is in front of somebody using the app. A fatal JS error still arrives,
+because React Native turns it into a native exception on its way to killing the process.
`popupDetail` defaults to `'auto'`, which picks between two sheets. With the devtools enabled you get
@@ -56,8 +57,8 @@ If you ship crash reporting without the panel, mount the sheet yourself:
import { CrashReportOverlay } from '@axonpack/expo-devtools';
```
-`` already mounts one, and mounting both is harmless: whichever mounted first owns
-the sheet and the other draws nothing.
+`` already mounts one, with the devtools on or off, and mounting both is harmless:
+whichever mounted first owns the sheet and the other draws nothing.
## Catching render errors
@@ -89,13 +90,14 @@ Everything you pass is attached to every record from that point on.
To rewrite or drop a record before it is stored, handed to `onCrash` or written to disk, use `redact`:
-```ts
-createDevtoolsClient({
- crash: {
- redact: (record) => (record.message.includes('token') ? null : record),
- onCrash: (record) => myBackend.send(record),
- },
-});
+```tsx
+ (record.message.includes('token') ? null : record),
+ onCrash: (record) => myBackend.send(record),
+ },
+ }}>
```
## Decisions worth knowing
@@ -128,6 +130,6 @@ createDevtoolsClient({
## Next step
-
+
diff --git a/content/docs/expo-devtools/debug.mdx b/content/docs/expo-devtools/debug.mdx
index 4132bd3..ac618b7 100644
--- a/content/docs/expo-devtools/debug.mdx
+++ b/content/docs/expo-devtools/debug.mdx
@@ -26,10 +26,10 @@ The two are not the same event, and the difference is worth seeing once. A JS th
reported before you let go of the button, while a main-thread crash ends the process and is read back
off disk at the next launch. Either way the report is waiting on the Crashes tab.
-
- They call straight into the native module, so they work whenever the panel is on screen, whether or
- not `.init()` ran. Guarding the `` mount is what keeps them out of a release —
- see [Leaving it in production](/docs/expo-devtools/production).
+
+ They call straight into the native module, so they work whenever the panel is on screen.
+ `enabled: false` is what keeps them out of a release, because it is what makes the panel
+ unreachable — see [Leaving it in production](/docs/expo-devtools/production).
There is no record button and nothing to clear, so the tab carries no toolbar.
diff --git a/content/docs/expo-devtools/example-app.mdx b/content/docs/expo-devtools/example-app.mdx
index 06bd020..a494feb 100644
--- a/content/docs/expo-devtools/example-app.mdx
+++ b/content/docs/expo-devtools/example-app.mdx
@@ -33,8 +33,8 @@ bun run ios # or: bun run android (full native build)
## A worked configuration
`example/devtools.ts` doubles as a worked configuration: a dark default theme, a custom `midnight` one,
-two declared `webviewSources`, a `console.context` you can reach from the prompt, and all four storage
-adapters registered against real AsyncStorage, MMKV, SecureStore and an in-memory `Map`.
+a `console.context` you can reach from the prompt, and all four storage adapters registered against
+real AsyncStorage, MMKV, SecureStore and an in-memory `Map`.
AsyncStorage and SecureStore ship inside Expo Go, so `bun run start` exercises them as-is. MMKV does
diff --git a/content/docs/expo-devtools/in-app-browsers.mdx b/content/docs/expo-devtools/in-app-browsers.mdx
index 92e77c5..38d4380 100644
--- a/content/docs/expo-devtools/in-app-browsers.mdx
+++ b/content/docs/expo-devtools/in-app-browsers.mdx
@@ -1,59 +1,67 @@
---
title: In-app browsers
-description: Two props on the WebView, one declared name, and the page's requests and logs join your app's.
+description: One hook on the WebView, and the page's requests and logs join your app's.
---
A `` runs its own separate JavaScript, in a separate engine, invisible to everything that
-patches `fetch` and `console` in your app. So it needs two props wired up:
+patches `fetch` and `console` in your app. So it needs wiring of its own, and one hook returns every
+prop it takes:
```tsx
+import { useDevtoolsWebView } from '@axonpack/expo-devtools';
import { WebView } from 'react-native-webview';
-import { devtools } from './devtools';
- devtools.handleWebViewMessage(event)}
-/>;
-```
-
-Declare the name up front, so a typo cannot silently swallow everything:
+export function Checkout() {
+ const devtoolsWebView = useDevtoolsWebView('checkout');
-```ts title="devtools.ts"
-export const devtools = createDevtoolsClient({
- webviewSources: ['my-webview'],
-});
+ return ;
+}
```
That covers both the page's **requests and its console output**. Rows show up tagged
-`WebView::[my-webview]` in either tab, and the Source chips can filter them apart from your app's own.
+`WebView::[checkout]` in either tab, and the Source chips can filter them apart from your app's own.
-`webviewSources` uses a TypeScript `const` type parameter, so the literal names flow into the helpers'
-parameter types: passing an undeclared name is a compile error, and at runtime a message from an
-undeclared source is dropped.
+The name is yours to pick and it is only a label. Name each WebView when the app has more than one;
+a single WebView can call the hook with no argument, which labels it `webview`.
-
- The latter runs after the page's own scripts have already fired, so their requests escape.
+
+ Setting your own replaces the instrumentation, and the page's early requests escape. Your own script
+ belongs in `injectedJavaScript`, which runs later.
-## Optional: reaching the page with throttling
+## What the props do
-Three more props, only needed if you want the connection settings to apply to the page too:
+| Prop | What it buys |
+| --------------------------------------- | ---------------------------------------------------------------------------- |
+| `injectedJavaScriptBeforeContentLoaded` | The page's `fetch`, `XMLHttpRequest`, `WebSocket`, `EventSource` and `console` |
+| `onMessage` | Receives what the page reports. Without it nothing arrives |
+| `ref` | A conditions change reaches an already-open page |
+| `userAgent` | The browser override in Network conditions applies for real |
+| `onShouldStartLoadWithRequest` | Navigation is blocked while Offline is on |
-| Prop | Value | What it buys |
-| ------------------------------ | ------------------------------------ | --------------------------------------------------------- |
-| `ref` | `devtools.getWebViewRef('my-webview')` | A speed change reaches an already-open page |
-| `userAgent` | `devtools.getWebViewUserAgent()` | The browser override applies for real |
-| `onShouldStartLoadWithRequest` | `devtools.shouldAllowWebViewRequest` | Navigation is blocked while Offline is on |
+Everything the hook returns is inert until the devtools are running: the injected script is empty, and
+with no `onMessage` behind it `react-native-webview` does not install the page bridge at all.
A page can never be *fully* throttled: images, stylesheets and scripts the browser loads by itself
still go out at full speed.
+## If the page uses postMessage for your own purposes
+
+Take the handler out and call it first. It returns `true` when the message was one of this package's:
+
+```tsx
+ {
+ if (devtoolsWebView.onMessage(event)) return;
+ handleMyOwnMessage(event);
+ }}
+/>
+```
+
## Next step
-
+
diff --git a/content/docs/expo-devtools/index.mdx b/content/docs/expo-devtools/index.mdx
index 89b3f72..cc068ad 100644
--- a/content/docs/expo-devtools/index.mdx
+++ b/content/docs/expo-devtools/index.mdx
@@ -35,8 +35,8 @@ Two more guides cover things that are not tabs: [themes](/docs/expo-devtools/the
a handful of readings dark.
- **It depends on no storage library.** The Storage tab reads the stores _you_ register, which is why
adding this package cannot drag AsyncStorage or MMKV into your app.
-- **`init()` is the only gate.** Ship the code freely: until you call it, nothing is patched,
- observed or recorded, and the overlay draws nothing.
+- **`config.enabled` is the only gate.** Ship the code freely: with it off, nothing is patched,
+ observed or recorded, and the provider draws nothing but your app.
- **It states its limits.** Every guide here ends with what the tab cannot measure and why, rather
than showing a number it had to invent.
diff --git a/content/docs/expo-devtools/meta.json b/content/docs/expo-devtools/meta.json
index 09da974..cba42c8 100644
--- a/content/docs/expo-devtools/meta.json
+++ b/content/docs/expo-devtools/meta.json
@@ -24,6 +24,7 @@
"---Reference---",
"reference",
"---Releases---",
+ "upgrading",
"changelog"
],
"defaultOpen": true
diff --git a/content/docs/expo-devtools/production.mdx b/content/docs/expo-devtools/production.mdx
index 359a259..cbd174a 100644
--- a/content/docs/expo-devtools/production.mdx
+++ b/content/docs/expo-devtools/production.mdx
@@ -1,40 +1,49 @@
---
title: Leaving it in production
-description: Shipping the code is safe. Two switches decide whether anything runs.
+description: Shipping the code is safe. One switch decides whether anything runs.
---
-Shipping the code is safe. Until `.init()` runs, nothing is patched and nothing is recorded, so the
-cost of leaving the package in a production bundle is the bundle size and nothing else.
+Shipping the code is safe. There is one switch, `config.enabled`, and with it off nothing is patched
+and nothing is recorded, so the cost of leaving the package in a production bundle is the bundle size
+and nothing else.
-There are two switches, and they do different jobs:
+```tsx
+
+
+
+```
-- **Capture** — `if (DEVTOOLS_ENABLED) devtools.init();` patches `fetch`, `XMLHttpRequest` and
- `console`. Skip it and nothing is ever recorded.
-- **Access** — `{DEVTOOLS_ENABLED && }` draws the floating button. Skip it and there
- is no way into the panel.
+The mount can stay exactly where it is. With `enabled: false` the provider renders its children and
+the crash sheet and nothing else:
-`DEVTOOLS_ENABLED` is whatever condition you want, evaluated at runtime.
+- **No capture.** The `fetch`, `XMLHttpRequest`, `WebSocket` and `console` patches are never
+ installed, and no store is ever read.
+- **No access.** There is no launcher button, and `useDevtoolsPanel().show()` opens nothing. That hook
+ reports `enabled` so your own trigger can hide itself rather than open an empty panel.
+
+`enabled` is whatever condition you want, evaluated at runtime.
`process.env.EXPO_PUBLIC_APP_ENV !== 'prod'` from the [Quick start](/docs/expo-devtools/quick-start) and
`__DEV__` are the two usual choices; anything else works too, including a value you fetch for a
specific user.
-
- It hides itself until `init()` has brought the panel up, so skipping the `init()` call alone is
- enough. Guarding both is still worth doing — it keeps the component out of the render tree entirely.
+
+ The patches are global and go in one time, so the config that first render saw is the one that
+ applies. `enabled` cannot be flipped mid-session, and rebuilding the config object later changes
+ nothing.
That also settles the [Debug tab](/docs/expo-devtools/debug), whose buttons call straight into the native
module and are **not** restricted to development builds. They live behind the panel, and the panel is
-unreachable without `init()`.
+unreachable with the devtools off.
## The two things that do run in production
- **Crash reporting**, if you asked for it. `crash: { enableWhileDevtoolsDisabled: true }` installs the
- handlers when the client is constructed rather than at `init()`. It is the one subsystem meant to
- survive into a release build — see [Crash reporting](/docs/expo-devtools/crash-reporting).
-- **The `>` prompt**, if you called `init()`. `console.repl` defaults to `true` and is not gated on
- `__DEV__`, so a build that calls `init()` gets a prompt that runs whatever is typed into it. Set
- `console: { repl: false }` for any build where that is not what you want.
+ handlers even with `enabled: false`. It is the one subsystem meant to survive into a release build —
+ see [Crash reporting](/docs/expo-devtools/crash-reporting).
+- **The `>` prompt**, in any build where the devtools are on. `console.repl` defaults to `true` and is
+ not gated on `__DEV__`, so a build with `enabled: true` gets a prompt that runs whatever is typed
+ into it. Set `console: { repl: false }` for any build where that is not what you want.
## Next step
diff --git a/content/docs/expo-devtools/quick-start.mdx b/content/docs/expo-devtools/quick-start.mdx
index 1d05733..e9f6db0 100644
--- a/content/docs/expo-devtools/quick-start.mdx
+++ b/content/docs/expo-devtools/quick-start.mdx
@@ -1,27 +1,29 @@
---
title: Quick start
-description: Create the client, call init() once at startup, and mount the overlay once at the root.
+description: Wrap your app in the provider once, at the root. That is the whole setup.
---
-Two things have to happen: `init()` runs **once at startup**, and `` is mounted
-**once at the root**. Nothing else.
+One thing has to happen: `` wraps your app, **once, at the root**. It starts the
+devtools and hosts the panel. Nothing else.
-## 1. Create the client
+## 1. Keep the config in its own file
-One shared instance the rest of your app imports, plus one flag deciding whether it runs at all:
+One object the root imports, with the flag that decides whether any of this runs:
```ts title="devtools.ts"
-import { createDevtoolsClient } from '@axonpack/expo-devtools';
+import type { DevtoolsConfig } from '@axonpack/expo-devtools';
-export const DEVTOOLS_ENABLED = process.env.EXPO_PUBLIC_APP_ENV !== 'prod';
-
-export const devtools = createDevtoolsClient();
+export const devtoolsConfig = {
+ enabled: process.env.EXPO_PUBLIC_APP_ENV !== 'prod',
+} satisfies DevtoolsConfig;
```
Set `EXPO_PUBLIC_APP_ENV=prod` for your production builds (in `eas.json`, or a `.env` file) and leave
-it unset everywhere else. Use `__DEV__` instead if a dev/release split is all you need.
+it unset everywhere else. Use `__DEV__` instead if a dev/release split is all you need. An inline
+object on the provider works just as well; a file of its own only keeps a long config out of your
+root layout.
-## 2. Wire it up
+## 2. Wrap your app
Use whichever of these matches your app. You only need one.
@@ -29,22 +31,18 @@ Use whichever of these matches your app. You only need one.
-The root layout is the place. `devtools.init()` goes at **module scope**, outside the component, so
-the `fetch` and `console` patches are installed before the first screen renders.
+The root layout is the place. One provider there covers every route.
```tsx title="app/_layout.tsx"
import { Stack } from 'expo-router';
-import { DevtoolsOverlay } from '@axonpack/expo-devtools';
-import { devtools, DEVTOOLS_ENABLED } from '../devtools';
-
-if (DEVTOOLS_ENABLED) devtools.init();
+import { DevtoolsProvider } from '@axonpack/expo-devtools';
+import { devtoolsConfig } from '../devtools';
export default function RootLayout() {
return (
- <>
+
- {DEVTOOLS_ENABLED && }
- >
+
);
}
```
@@ -53,28 +51,17 @@ export default function RootLayout() {
-`init()` goes in the entry file, before the app is registered. The overlay goes in your root
-component.
-
-```ts title="index.ts"
-import { registerRootComponent } from 'expo';
-import App from './App';
-import { devtools, DEVTOOLS_ENABLED } from './devtools';
-
-if (DEVTOOLS_ENABLED) devtools.init();
-registerRootComponent(App);
-```
+Your root component is the place.
```tsx title="App.tsx"
-import { DevtoolsOverlay } from '@axonpack/expo-devtools';
-import { DEVTOOLS_ENABLED } from './devtools';
+import { DevtoolsProvider } from '@axonpack/expo-devtools';
+import { devtoolsConfig } from './devtools';
export default function App() {
return (
- <>
+
- {DEVTOOLS_ENABLED && }
- >
+
);
}
```
@@ -90,67 +77,76 @@ the app is running.
## Things that trip people up
-- **Mount the overlay exactly once.** The root is the place, because one mount there covers every
+- **Mount the provider exactly once.** The root is the place, because one mount there covers every
route: the panel opens as a modal on top of whichever screen is showing, so nested Tabs and Drawer
- layouts are already covered and must not mount their own. A second mount gives you a second button.
-- **`init()` runs exactly once too**, at module scope rather than in a `useEffect`. Anything that
- fires before an effect would run — requests during module evaluation, logs at import time — is
- missed otherwise.
+ layouts are already covered and must not mount their own. A second provider gives you a second
+ button.
+- **The patches go in as the provider renders**, not in an effect, which is earlier than any child's
+ mount and so catches what the first screen requests. Earlier still is out of reach: anything during
+ module evaluation, before React renders at all, happens before this package can see it.
+- **The config is read once.** The first render is what configures everything, because the patches
+ are global and go in one time. Changing the object later has no effect, so `enabled` cannot be
+ flipped at runtime.
- **Performance starts paused.** Measuring is not free, so press its record button when you want it.
The other two recording tabs record from launch.
- **Expo Go works.** See [Installation](/docs/expo-devtools/installation) for the handful of readings that go
quiet there.
-- **In-app browser pages need two extra props** on the `` itself. See
+- **In-app browser pages need one hook** on the `` itself. See
[In-app browsers](/docs/expo-devtools/in-app-browsers).
-- **`init()` is the guard.** `` draws nothing until `init()` has brought the panel
- up, so an unguarded mount in a release build is harmless rather than a button over empty lists.
- Crash reports still surface, because that is the one subsystem meant to run in production.
+- **`enabled: false` is the guard**, and the mount can stay where it is: the provider then renders its
+ children and nothing else, so there is no button, no panel and nothing patched. Crash reports can
+ still surface, because that is the one subsystem meant to run in production. See
+ [Production](/docs/expo-devtools/production).
-## Optional: starting before Expo Router
+## Opening the panel without the button
-Skip this unless you need it. Step 2 is enough for normal use.
+The floating button is optional. Turn it off and open the panel from your own UI instead: a long-press
+on a header, a row in a staff-only settings screen, a gesture nobody will find by accident.
-The root layout runs after Expo Router's own entry file, so requests and logs from that window are
-missed, and the startup breakdown's *App setup* phase starts later than the app really did. You can
-move `init()` ahead of Expo Router by owning the entry file yourself.
-
-Point `main` at your own file:
-
-```json title="package.json"
-{ "main": "index.js" }
+```tsx
+
+
+
```
-Then have that file call `init()` before handing control to Expo Router. The import order is the whole
-point, so keep `init()` in a separate module rather than calling it inline: an `import` is hoisted
-above statements in the same file, which would put `expo-router/entry` first anyway.
+```tsx title="SettingsRow.tsx"
+import { useDevtoolsPanel } from '@axonpack/expo-devtools';
-```js title="index.js"
-import './devtools-init'; // a module whose only job is `devtools.init()`
-import 'expo-router/entry';
+export function SettingsRow() {
+ const panel = useDevtoolsPanel();
+ if (!panel.enabled) return null;
+
+ return ;
+}
```
-Now remove the `devtools.init()` line from `app/_layout.tsx`, keeping `` there. The
-overlay still belongs in the root layout; only the `init()` call moves.
+`useDevtoolsPanel()` gives you `visible`, `enabled`, `show()`, `hide()` and `toggle()`. `enabled` is
+`false` in a build that never started the devtools, which is what lets your trigger take itself off
+the screen rather than open an empty panel. It reads the same state the button does, so the two stay
+in step.
## The launcher button
-Nothing has to be configured: `` on its own gives you the bug glyph on a blue
-circle. Everything about its appearance is a prop, since that is where you mount it.
+Nothing has to be configured: the provider on its own gives you the bug glyph on the theme's accent
+colour. Everything about its appearance is a prop on the provider, since that is where you mount it.
-| Prop | Default | What it does |
-| --------------- | ----------- | --------------------------------------------------------------------------------- |
-| `iconComponent` | none | Renders in place of the built-in glyph. Given the resolved `size`; colour is yours |
-| `size` | `44` | Diameter of the button, in dp |
-| `color` | accent blue | Button fill |
-| `iconColor` | white | The built-in glyph only; an `iconComponent` colours itself |
-| `statusBar` | `'auto'` | Status bar icons while the panel is open: `'auto'`, `'app'`, `'light'`, `'dark'` |
+| Prop | Default | What it does |
+| -------------------- | -------- | --------------------------------------------------------------------------------- |
+| `showFloatingButton` | `true` | Draw the button at all. Off leaves the panel reachable through the hook above |
+| `iconComponent` | none | Renders in place of the built-in glyph. Given the resolved `size`; colour is yours |
+| `size` | `44` | Diameter of the button, in dp |
+| `color` | accent | Button fill |
+| `iconColor` | white | The built-in glyph only; an `iconComponent` colours itself |
+| `statusBar` | `'auto'` | Status bar icons while the panel is open: `'auto'`, `'app'`, `'light'`, `'dark'` |
```tsx
-}
size={56}
- color="#111827"
-/>
+ color="#111827">
+
+
```
A `size` under 44 still gets a 44dp touch area through `hitSlop`, so a small button stays as easy to
diff --git a/content/docs/expo-devtools/reference/console-tab.mdx b/content/docs/expo-devtools/reference/console-tab.mdx
index edb798f..e62bd2e 100644
--- a/content/docs/expo-devtools/reference/console-tab.mdx
+++ b/content/docs/expo-devtools/reference/console-tab.mdx
@@ -43,7 +43,7 @@ the Console tab, while the disclosure arrow still expands the inline stack.
## The `>` prompt
Present when the REPL is enabled through `console.repl`, which
-[defaults to `true` in every build](/docs/expo-devtools/reference/client#console).
+[defaults to `true` in every build](/docs/expo-devtools/reference/provider#console).
- Type an expression and submit: your input appears as an `input` row (`›`), the result as a `result`
row (`‹`). Objects come back as the same explorable tree.
diff --git a/content/docs/expo-devtools/reference/debug-tab.mdx b/content/docs/expo-devtools/reference/debug-tab.mdx
index 3c8cd99..869ae76 100644
--- a/content/docs/expo-devtools/reference/debug-tab.mdx
+++ b/content/docs/expo-devtools/reference/debug-tab.mdx
@@ -22,10 +22,9 @@ Blocking the JS thread shows up as a long task and drops the JS frame rate. Bloc
freezes the screen while every JS number stays healthy. That gap is the blind spot the frame-rate card
warns about, and this is how you see it for yourself.
-
- The crash paths do not go through any store, so `.init()` is not what keeps them out of a release:
- they work as soon as the panel is on screen. What gates them is whether you rendered
- `` at all.
+
+ The crash paths do not go through any store: they work as soon as the panel is on screen. What keeps
+ them out of a release is `enabled: false`, which is what makes the panel unreachable.
Both crash paths are captured by the Crashes tab when crash reporting is on. A JS crash is reported
diff --git a/content/docs/expo-devtools/reference/hooks.mdx b/content/docs/expo-devtools/reference/hooks.mdx
new file mode 100644
index 0000000..5d8f74e
--- /dev/null
+++ b/content/docs/expo-devtools/reference/hooks.mdx
@@ -0,0 +1,87 @@
+---
+title: Hooks and the devtools object
+description: useDevtoolsPanel, useDevtoolsWebView, and the module-level devtools object.
+---
+
+Three exports cover everything the provider does not: opening the panel, wiring a ``, and
+the imperative calls.
+
+## `useDevtoolsPanel()`
+
+Opens and closes the panel from your own UI, which is what makes `showFloatingButton={false}` usable
+rather than a dead end.
+
+```tsx
+import { useDevtoolsPanel } from '@axonpack/expo-devtools';
+
+const panel = useDevtoolsPanel();
+```
+
+| Member | What it is |
+| ---------- | ----------------------------------------------------------------------------------------------------------- |
+| `visible` | Whether the panel is open right now. |
+| `enabled` | Whether the devtools are running. `false` with `enabled: false`, and then `show` does nothing. |
+| `show()` | Opens the panel. |
+| `hide()` | Closes it. |
+| `toggle()` | Either way. |
+
+It reads the same state the launcher button does, so the two stay in step. Branch your own trigger on
+`enabled` and a release build has no dead button in it.
+
+## `useDevtoolsWebView(source?)`
+
+Returns the props one `` needs to report in. See
+[In-app browsers](/docs/expo-devtools/in-app-browsers) for the whole story.
+
+```tsx
+const devtoolsWebView = useDevtoolsWebView('checkout');
+
+;
+```
+
+`source` is the label the page's rows carry, defaulting to `'webview'`. Any string works and it is only
+a label: name each WebView when the app has more than one.
+
+| Prop returned | What it does |
+| --------------------------------------- | ------------------------------------------------------------------------------------------ |
+| `injectedJavaScriptBeforeContentLoaded` | Patches `fetch`, `XMLHttpRequest`, `WebSocket`, `EventSource` and `console` in the page. |
+| `onMessage` | Receives what the page reports. Returns `true` when the message was one of ours. |
+| `ref` | Lets a conditions change reach an already-loaded page. |
+| `userAgent` | The current user-agent override, so the page identifies itself the way the panel says. |
+| `onShouldStartLoadWithRequest` | Blocks navigation while Offline is on. |
+
+Everything it returns is inert until the devtools are running: the injected script is empty, and with no
+`onMessage` behind it `react-native-webview` does not install the page bridge at all.
+
+## `devtools`
+
+A module-level object, for the things the panel cannot do for you.
+
+```ts
+import { devtools } from '@axonpack/expo-devtools';
+```
+
+| Member | What it does |
+| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `mark(name, options?)` | Records a user-timing mark. `options`: `{ detail?, startTime? }`. |
+| `measure(name, startOrOptions?, endMark?)` | Records a measure. Second argument is a start-mark name or `{ start?, end?, duration?, detail? }`. Passing `start`, `end` **and** `duration` together throws, since they can disagree. |
+| `clearMarks(name?)` | Drops recorded marks, all of them or one name. |
+| `clearMeasures(name?)` | Drops recorded measures, all of them or one name. |
+| `setCrashContext(context)` | Keys attached to every crash record from here on: user id, route, feature flags. Replaces rather than merges; `null` clears it. |
+| `networkLogStore`, `networkConditionsStore`, `consoleLogStore`, `storageStore`, `crashStore` | The underlying stores, if you want to read or drive them yourself. |
+
+Nothing on it does anything until a provider has started with `enabled: true`, so call sites need no
+guard of their own.
+
+## User timing
+
+```ts
+devtools.mark('checkout');
+await buildCart();
+devtools.measure('checkout'); // measures from the mark of the same name
+```
+
+`measure` follows the [W3C User Timing](https://www.w3.org/TR/user-timing/) signatures, and calls are
+forwarded to the real `performance.mark` and `performance.measure` too, so the entries exist on the
+platform timeline as well. Nothing is *observed* from that timeline, which is why React's own internal
+measures never appear in the list.
diff --git a/content/docs/expo-devtools/reference/index.mdx b/content/docs/expo-devtools/reference/index.mdx
index a24532c..b368e6b 100644
--- a/content/docs/expo-devtools/reference/index.mdx
+++ b/content/docs/expo-devtools/reference/index.mdx
@@ -24,9 +24,9 @@ crashes without it: the control says what it needs instead.
## API
-
+
+
-
diff --git a/content/docs/expo-devtools/reference/meta.json b/content/docs/expo-devtools/reference/meta.json
index ec6f64e..eb431cd 100644
--- a/content/docs/expo-devtools/reference/meta.json
+++ b/content/docs/expo-devtools/reference/meta.json
@@ -13,9 +13,9 @@
"storage-tab",
"debug-tab",
"---API---",
- "client",
+ "provider",
+ "hooks",
"storage-adapters",
- "overlay",
"themes",
"types",
"---Platform---",
diff --git a/content/docs/expo-devtools/reference/overlay.mdx b/content/docs/expo-devtools/reference/overlay.mdx
deleted file mode 100644
index d1a081b..0000000
--- a/content/docs/expo-devtools/reference/overlay.mdx
+++ /dev/null
@@ -1,50 +0,0 @@
----
-title: DevtoolsOverlay
-description: The launcher button's props, and what mounting it means.
----
-
-```tsx
-import { DevtoolsOverlay } from '@axonpack/expo-devtools';
-```
-
-| Prop | Type | Default | What it does |
-| --------------- | --------------------------------- | ----------- | ------------------------------------------------------------------- |
-| `iconComponent` | `ComponentType<{ size: number }>` | none | Renders in place of the built-in glyph. Given the resolved `size`. |
-| `size` | `number` | `44` | Diameter of the button, in dp. |
-| `color` | `string` | accent blue | Button fill. |
-| `iconColor` | `string` | `'#ffffff'` | The built-in glyph only; an `iconComponent` colours itself. |
-| `statusBar` | `'auto' \| 'app' \| 'light' \| 'dark'` | `'auto'` | What the status bar's clock and icons do while the panel is open. |
-
-## `statusBar`
-
-The panel never paints a status bar background of its own: the header extends behind it, so that
-area is already the toolbar's colour. What this prop decides is the *content*.
-
-| Value | What happens |
-| ------------------ | --------------------------------------------------------------------------------------------------------------- |
-| `'auto'` | Follows the theme, each of which carries its own `statusBarStyle`. A dark theme gets light icons, a light one dark. |
-| `'app'` | Untouched, for an app that manages the status bar itself. |
-| `'light'`/`'dark'` | That content style whatever the theme is on. `'light'` means light icons, for a dark background. |
-
-Whatever the app had is restored when the panel closes. Without this, a light app's dark icons stay
-dark and become unreadable over a dark panel.
-
-
- React Native's `StatusBar` cannot change the style unless `UIViewControllerBasedStatusBarAppearance`
- is `false` in `Info.plist`. An Expo app's own template already sets it, so there is usually nothing
- to do. If yours does not, `statusBar` has no effect on iOS and `'app'` is the honest setting.
-
-
-The button is draggable, stays inside the screen, and keeps a 44dp touch area through `hitSlop` even at
-a smaller `size`. Mounting it is also what marks *first render* for the startup breakdown.
-
-The overlay renders whether or not `.init()` has run: it takes no `enabled` prop and reads no store to
-decide. Guard the mount itself when you do not want the panel reachable, as in the
-[Quick start](/docs/expo-devtools/quick-start).
-
-## Related components
-
-| Component | What it is for |
-| -------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
-| `` | The crash sheet on its own, for a build that ships crash reporting but not the panel. `` mounts one itself; mounting both is harmless. |
-| `` | Catches render errors — the only tier that produces a component stack. Props: `children`, `fallback(error, reset)`, `onError(error, info)`. |
diff --git a/content/docs/expo-devtools/reference/panel.mdx b/content/docs/expo-devtools/reference/panel.mdx
index f9d9801..adbd058 100644
--- a/content/docs/expo-devtools/reference/panel.mdx
+++ b/content/docs/expo-devtools/reference/panel.mdx
@@ -3,7 +3,8 @@ title: The panel
description: The header row, the toolbar row, and the two gates that are easily confused.
---
-`` renders a draggable floating button. Tapping it opens a full-screen modal; the
+`` renders a draggable floating button beside your app. Tapping it opens a
+full-screen modal; the
button itself never appears inside the modal.
## Header row
@@ -40,21 +41,19 @@ Not every tab has one:
## The two gates
-Pausing and `.init()` are different switches, and the difference matters when you ship.
+Pausing and `enabled` are different switches, and the difference matters when you ship.
-| Gate | Set by | What it does |
-| ---------- | ----------------------------- | ---------------------------------------------------------------------------------------- |
-| `.init()` | Your call, once at startup | Until it runs, nothing is patched, observed or recorded anywhere. There is no UI for it. |
-| Record | The record button in a toolbar | Pauses a tab that `.init()` already turned on. |
+| Gate | Set by | What it does |
+| --------- | ------------------------------ | ----------------------------------------------------------------------------------------- |
+| `enabled` | The provider's config, once | With it off, nothing is patched, observed or recorded anywhere. There is no UI for it. |
+| Record | The record button in a toolbar | Pauses a tab that a started client already turned on. |
-`disabledByDefault` in the config sets the **record** gate, not the `.init()` one, so a tab that starts
+`disabledByDefault` in the config sets the **record** gate, not the `enabled` one, so a tab that starts
paused can always be started from its own toolbar.
-`.init()` controls both **capture** and **access**: the overlay subscribes to whether `.init()`
-finished and draws nothing until it has, so an unguarded mount in a release build shows no button
-rather than a panel over empty lists. There is no config flag that says "off" — not calling `.init()`
-is what says it.
+`enabled` controls both **capture** and **access**: with it off the provider installs nothing and draws
+no button, so the mount can stay in a release build rather than being wrapped in a condition of its
+own.
-The one thing the overlay keeps rendering is the crash report sheet, which is
-[meant to work in production](/docs/expo-devtools/crash-reporting). Guarding the mount as well is still worth
-doing; it just is not what keeps the panel out.
+The one thing the provider keeps rendering is the crash report sheet, which is
+[meant to work in production](/docs/expo-devtools/crash-reporting).
diff --git a/content/docs/expo-devtools/reference/performance-tab.mdx b/content/docs/expo-devtools/reference/performance-tab.mdx
index 1a02e77..8b46c86 100644
--- a/content/docs/expo-devtools/reference/performance-tab.mdx
+++ b/content/docs/expo-devtools/reference/performance-tab.mdx
@@ -73,7 +73,7 @@ Process start to first render, read once at launch. Up to two blocks:
The measured block comes from the native module's real process start time, so it works where the
platform's own markers are all null. Its phase boundaries are this package's own load points, so they
-shift a little with your import order; the earlier you call `.init()`, the truer *App setup* is. The
+shift with where you mount the provider: *App setup* ends when it first renders. The
platform block is `performance.rnStartupTiming`, and a dash means the platform never reported that
marker. The whole section is hidden when neither is available.
@@ -90,7 +90,7 @@ Marks and measures you record yourself, newest first.
| Duration | The measured span; a mark shows `—`. |
This is the only list here that can point at a specific piece of your code, which makes it the answer to
-a long task you cannot explain. See [`mark` and `measure`](/docs/expo-devtools/reference/client#user-timing).
+a long task you cannot explain. See [`mark` and `measure`](/docs/expo-devtools/reference/hooks#user-timing).
## Interactions
diff --git a/content/docs/expo-devtools/reference/client.mdx b/content/docs/expo-devtools/reference/provider.mdx
similarity index 53%
rename from content/docs/expo-devtools/reference/client.mdx
rename to content/docs/expo-devtools/reference/provider.mdx
index fa9f9f5..bf38c24 100644
--- a/content/docs/expo-devtools/reference/client.mdx
+++ b/content/docs/expo-devtools/reference/provider.mdx
@@ -1,28 +1,68 @@
---
-title: createDevtoolsClient
-description: Every configuration option, and every method on the client it returns.
+title: DevtoolsProvider
+description: The provider's props, and every configuration option underneath them.
---
-```ts
-import { createDevtoolsClient } from '@axonpack/expo-devtools';
+```tsx
+import { DevtoolsProvider } from '@axonpack/expo-devtools';
-export const devtools = createDevtoolsClient(config?);
+
+
+;
```
-Call it once, at module scope, and export the instance. Everything else hangs off it. Every option is
-optional, and the defaults are what most apps want.
+Wrap your app in it once, at the root. It starts the devtools as it renders, which is before any
+child's mount, and hosts the panel. There is no `init` to call.
+
+## Props
+
+| Prop | Type | Default | What it does |
+| -------------------- | -------------------------------------- | -------- | --------------------------------------------------------------------------------- |
+| `config` | `DevtoolsConfig` | `{}` | Everything below. Read once, on the first render. |
+| `showFloatingButton` | `boolean` | `true` | Draw the launcher button. Off leaves the panel reachable through [`useDevtoolsPanel`](/docs/expo-devtools/reference/hooks). |
+| `iconComponent` | `ComponentType<{ size: number }>` | none | Renders in place of the built-in glyph. Given the resolved `size`. |
+| `size` | `number` | `44` | Diameter of the button, in dp. |
+| `color` | `string` | accent | Button fill. |
+| `iconColor` | `string` | `'#ffffff'` | The built-in glyph only; an `iconComponent` colours itself. |
+| `statusBar` | `'auto' \| 'app' \| 'light' \| 'dark'` | `'auto'` | What the status bar's clock and icons do while the panel is open. |
+
+The provider is generic over your theme names, so `config.defaultTheme` accepts a built-in id or a key
+of `config.themes` and nothing else.
+
+The button is draggable, stays inside the screen, and keeps a 44dp touch area through `hitSlop` even at
+a smaller `size`. Rendering it is also what marks *first render* for the startup breakdown.
+
+## `statusBar`
+
+The panel never paints a status bar background of its own: the header extends behind it, so that
+area is already the toolbar's colour. What this prop decides is the *content*.
+
+| Value | What happens |
+| ------------------ | --------------------------------------------------------------------------------------------------------------- |
+| `'auto'` | Follows the theme, each of which carries its own `statusBarStyle`. A dark theme gets light icons, a light one dark. |
+| `'app'` | Untouched, for an app that manages the status bar itself. |
+| `'light'`/`'dark'` | That content style whatever the theme is on. `'light'` means light icons, for a dark background. |
+
+Whatever the app had is restored when the panel closes. Without this, a light app's dark icons stay
+dark and become unreadable over a dark panel.
+
+
+ React Native's `StatusBar` cannot change the style unless `UIViewControllerBasedStatusBarAppearance`
+ is `false` in `Info.plist`. An Expo app's own template already sets it, so there is usually nothing
+ to do. If yours does not, `statusBar` has no effect on iOS and `'app'` is the honest setting.
+
## Top level
-| Option | Type | Default | What it does |
-| ---------------- | ----------------------------- | ----------- | ----------------------------------------------------------------------------------- |
-| `defaultTheme` | `ThemeId` | `'light'` | Which theme the panel opens with: a built-in or one of yours. |
-| `themes` | `Record` | `undefined` | Your own themes: a `base` to inherit and the tokens to override. |
-| `webviewSources` | `readonly string[]` | `undefined` | Names of ``s allowed to report in, for both the Network and Console tabs. |
+| Option | Type | Default | What it does |
+| -------------- | ----------------------------- | ----------- | --------------------------------------------------------------- |
+| `enabled` | `boolean` | `true` | Whether the devtools run at all. The only gate; see [Leaving it in production](/docs/expo-devtools/production). |
+| `defaultTheme` | `ThemeId` | `'light'` | Which theme the panel opens with: a built-in or one of yours. |
+| `themes` | `Record` | `undefined` | Your own themes: a `base` to inherit and the tokens to override. |
-`webviewSources` uses a `const` type parameter, so the literal names flow into the WebView helpers'
-parameter types: passing an undeclared name is a compile error, and at runtime a message from an
-undeclared source is dropped.
+With `enabled: false` the provider renders its children and the crash sheet and nothing else: no
+patches, no button, no panel. The config is read on the first render and never again, because the
+patches are global and go in one time, so `enabled` cannot be flipped mid-session.
## Network
@@ -50,8 +90,8 @@ request underneath it that anything here can see, so that one disappears entirel
| `console.disabledByDefault` | `boolean` | `false` | Open the Console tab paused. The prompt still works. |
- It defaults to `true` in every build. Once `init()` has run the prompt is there, including in a
- release build, where it runs whatever is typed into it. Guard your `init()` call, or set
+ It defaults to `true` in every build. Wherever the devtools are on the prompt is there, including in
+ a release build, where it runs whatever is typed into it. Ship with `enabled: false`, or set
`console: { repl: false }`.
@@ -77,12 +117,12 @@ See [Storage adapters](/docs/expo-devtools/reference/storage-adapters) for how t
## Crash
-Crash capture is the only part of this package that can run without `init()`.
+Crash capture is the only part of this package that runs with the devtools off.
| Option | Type | Default | What it does |
| ----------------------------------- | ------------------------------------------ | -------- | -------------------------------------------------------------------------------------------------- |
| `crash.enabled` | `boolean` | `true` | Capture at all. |
-| `crash.enableWhileDevtoolsDisabled` | `boolean` | `false` | Install the handlers when the client is **constructed**, so crashes are reported without `init()`. |
+| `crash.enableWhileDevtoolsDisabled` | `boolean` | `false` | Install the handlers even with `enabled: false`, so a release build still reports crashes. |
| `crash.handlers.jsErrors` | `boolean` | `true` | The `ErrorUtils` global handler — fatal and non-fatal JS errors. |
| `crash.handlers.unhandledRejections` | `boolean` | `true` | Unhandled promise rejections, via the Hermes rejection tracker. |
| `crash.handlers.nativeExceptions` | `boolean` | `true` | Uncaught Java/Kotlin and Objective-C exceptions, via the native module. |
@@ -94,41 +134,18 @@ Crash capture is the only part of this package that can run without `init()`.
| `crash.redact` | `(record: CrashRecord) => CrashRecord \| null` | `undefined` | Runs before the record reaches the store, the disk or `onCrash`. Return `null` to drop it. |
| `crash.onCrash` | `(record: CrashRecord) => void` | `undefined` | Your own handler, after `redact`. |
-Before `init()`, an app relying on `enableWhileDevtoolsDisabled` alone installs `nativeExceptions` only,
-whatever the other two say: the JS tiers report errors the app survived, which is a developer's concern,
-and the sheet there is in front of a user. A fatal JS error still arrives, because React Native turns it
+With the devtools off, an app relying on `enableWhileDevtoolsDisabled` alone installs `nativeExceptions`
+only, whatever the other two say: the JS tiers report errors the app survived, which is a developer's
+concern, and the sheet there is in front of a user. A fatal JS error still arrives, because React Native turns it
into a native exception on the way to killing the process.
`disableDefaultLogBox` uninstalls LogBox rather than muting it, which takes the **yellow warning toasts
with it** — LogBox is one component and the two cannot be separated. Warnings are still captured by the
Console tab. It only does anything in development; LogBox is already an empty stub in a release build.
-## Client methods
-
-| Member | What it does |
-| --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `init()` | Installs everything: the fetch/XHR patches, the console patch, the REPL context, the performance collectors, your storage adapters, and your themes. Until this runs, nothing is captured and no store is read. Call once, as early as possible. |
-| `mark(name, options?)` | Records a user-timing mark. `options`: `{ detail?, startTime? }`. |
-| `measure(name, startOrOptions?, endMark?)` | Records a measure. Second argument is a start-mark name or `{ start?, end?, duration?, detail? }`. Passing `start`, `end` **and** `duration` together throws, since they can disagree. |
-| `clearMarks(name?)` | Drops recorded marks, all of them or one name. |
-| `clearMeasures(name?)` | Drops recorded measures, all of them or one name. |
-| `setCrashContext(context)` | Extra keys attached to every crash record from here on — user id, route, feature flags. |
-| `getWebViewInjectedJavaScriptBeforeContentLoaded(source)` | The script to hand a ``'s `injectedJavaScriptBeforeContentLoaded`. Covers both requests and console output. |
-| `handleWebViewMessage(event)` | Feed a ``'s `onMessage` events here. Returns `true` when it consumed one. |
-| `getWebViewRef(source)` | A ref to attach to the ``, so a throttle change reaches an already-open page. |
-| `getWebViewUserAgent()` | The current user-agent override, for the `userAgent` prop. |
-| `shouldAllowWebViewRequest` | For `onShouldStartLoadWithRequest`. Blocks navigation while Offline is on. |
-| `networkLogStore`, `networkConditionsStore`, `consoleLogStore`, `storageStore`, `crashStore` | The underlying stores, if you want to read or drive them yourself. |
-
-## User timing
-
-```ts
-devtools.mark('checkout');
-await buildCart();
-devtools.measure('checkout'); // measures from the mark of the same name
-```
+## Related components
-`measure` follows the [W3C User Timing](https://www.w3.org/TR/user-timing/) signatures, and calls are
-forwarded to the real `performance.mark` and `performance.measure` too, so the entries exist on the
-platform timeline as well. Nothing is *observed* from that timeline, which is why React's own internal
-measures never appear in the list.
+| Component | What it is for |
+| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `` | The crash sheet on its own, for a build that ships crash reporting but not the panel. The provider mounts one itself, with the devtools on or off; mounting both is harmless. |
+| `` | Catches render errors, the only tier that produces a component stack. Props: `children`, `fallback(error, reset)`, `onError(error, info)`. |
diff --git a/content/docs/expo-devtools/reference/storage-adapters.mdx b/content/docs/expo-devtools/reference/storage-adapters.mdx
index 8a7d1fa..1d34daa 100644
--- a/content/docs/expo-devtools/reference/storage-adapters.mdx
+++ b/content/docs/expo-devtools/reference/storage-adapters.mdx
@@ -4,7 +4,7 @@ description: The four factories that tell the Storage tab which stores exist, an
---
Four factories, all built on the last one. Each returns a `StorageAdapterDefinition` for
-`storage.adapters`; ids are assigned from the names at `init()`, suffixed on collision.
+`storage.adapters`; ids are assigned from the names as the provider starts, suffixed on collision.
```ts
import AsyncStorage from '@react-native-async-storage/async-storage';
@@ -13,7 +13,7 @@ import { createMMKV } from 'react-native-mmkv';
const mmkv = createMMKV();
-createDevtoolsClient({
+const devtoolsConfig = {
storage: {
adapters: [
asyncStorageAdapter({ driver: AsyncStorage }),
@@ -33,7 +33,7 @@ createDevtoolsClient({
}),
],
},
-});
+} satisfies DevtoolsConfig;
```
| Factory | For |
diff --git a/content/docs/expo-devtools/reference/themes.mdx b/content/docs/expo-devtools/reference/themes.mdx
index e89ec06..f1fa148 100644
--- a/content/docs/expo-devtools/reference/themes.mdx
+++ b/content/docs/expo-devtools/reference/themes.mdx
@@ -5,13 +5,12 @@ description: The built-in theme ids, and all 25 palette tokens.
A theme patches a base rather than redefining everything:
-```ts
-createDevtoolsClient({
- defaultTheme: 'midnight',
- themes: {
- midnight: { base: 'dark', colors: { accent: '#a78bfa' } },
- },
-});
+```tsx
+
```
Built-in ids: `light`, `dark`, `dracula`, `nord`, `monokai`, `one-dark`, `solarized-light`. Reuse one as
@@ -56,7 +55,7 @@ themes: {
```
It is worked out from the theme's toolbar colour when you leave it out, and it is only read when the
-overlay asks for it with [`statusBar="auto"`](/docs/expo-devtools/reference/overlay).
+provider asks for it with [`statusBar="auto"`](/docs/expo-devtools/reference/provider#statusbar).
It is the background painted behind text matching the current search. Every built-in palette sets it
diff --git a/content/docs/expo-devtools/reference/types.mdx b/content/docs/expo-devtools/reference/types.mdx
index d3c9415..28a09ed 100644
--- a/content/docs/expo-devtools/reference/types.mdx
+++ b/content/docs/expo-devtools/reference/types.mdx
@@ -4,13 +4,14 @@ description: Everything the package root exports as a type.
---
```ts
-import type { DevtoolsClientConfig, Palette } from '@axonpack/expo-devtools';
+import type { DevtoolsConfig, Palette } from '@axonpack/expo-devtools';
```
## Configuration
-`DevtoolsClientConfig`, `DevtoolsNetworkConfig`, `DevtoolsConsoleConfig`, `DevtoolsPerformanceConfig`,
-`DevtoolsStorageConfig`, `DevtoolsCrashConfig`, `DevtoolsOverlayProps`, `DevtoolsErrorBoundaryProps`.
+`DevtoolsConfig`, `DevtoolsNetworkConfig`, `DevtoolsConsoleConfig`, `DevtoolsPerformanceConfig`,
+`DevtoolsStorageConfig`, `DevtoolsCrashConfig`, `DevtoolsProviderProps`, `DevtoolsPanelControls`,
+`DevtoolsWebViewProps`, `DevtoolsErrorBoundaryProps`.
## Theming
diff --git a/content/docs/expo-devtools/storage.mdx b/content/docs/expo-devtools/storage.mdx
index 3654d3c..aa8c7d4 100644
--- a/content/docs/expo-devtools/storage.mdx
+++ b/content/docs/expo-devtools/storage.mdx
@@ -21,16 +21,16 @@ import AsyncStorage from '@react-native-async-storage/async-storage';
import * as SecureStore from 'expo-secure-store';
import { createMMKV } from 'react-native-mmkv';
import {
- createDevtoolsClient,
asyncStorageAdapter,
mmkvAdapter,
secureStoreAdapter,
defineStorageAdapter,
+ type DevtoolsConfig,
} from '@axonpack/expo-devtools';
const mmkv = createMMKV();
-export const devtools = createDevtoolsClient({
+export const devtoolsConfig = {
storage: {
adapters: [
asyncStorageAdapter({ driver: AsyncStorage }),
@@ -50,7 +50,7 @@ export const devtools = createDevtoolsClient({
}),
],
},
-});
+} satisfies DevtoolsConfig;
```
The full shape of each factory is in [Storage adapters](/docs/expo-devtools/reference/storage-adapters).
diff --git a/content/docs/expo-devtools/themes.mdx b/content/docs/expo-devtools/themes.mdx
index 1e9912f..8c98af0 100644
--- a/content/docs/expo-devtools/themes.mdx
+++ b/content/docs/expo-devtools/themes.mdx
@@ -26,13 +26,12 @@ Each is the project's published colours mapped onto this panel's tokens, not an
Pick which one the panel opens with, and add your own:
-```ts
-export const devtools = createDevtoolsClient({
- defaultTheme: 'midnight',
- themes: {
- midnight: { base: 'dark', colors: { accent: '#a78bfa' } },
- },
-});
+```tsx
+
```
A theme names a `base` to inherit from (any of the seven) and overrides only the tokens it cares about,
diff --git a/content/docs/expo-devtools/upgrading.mdx b/content/docs/expo-devtools/upgrading.mdx
new file mode 100644
index 0000000..54135cc
--- /dev/null
+++ b/content/docs/expo-devtools/upgrading.mdx
@@ -0,0 +1,142 @@
+---
+title: Upgrading
+description: What moved where in 3.0, and the two lines most apps have to change.
+---
+
+## 2.x to 3.0
+
+The setup API is replaced. There is no client object any more, so nothing has to be created at module
+scope, exported, or passed to the components that need it. One provider starts the devtools and hosts
+the panel, and one flag in its config decides whether any of it runs.
+
+Nothing about the panel itself changed, and nothing inside `config` changed: `network`, `console`,
+`performance`, `storage` and `crash` take the same fields they always did, as do the storage adapters
+and custom themes. If your app does not open a `` or call `mark`, this is a two-step
+upgrade.
+
+### 1. Replace the client, the init call and the overlay
+
+```tsx
+// before
+export const devtools = createDevtoolsClient({ defaultTheme: 'dark' });
+if (__DEV__) devtools.init();
+
+export default function App() {
+ return (
+ <>
+
+ {__DEV__ && }
+ >
+ );
+}
+```
+
+```tsx
+// after
+export default function App() {
+ return (
+
+
+
+ );
+}
+```
+
+The condition you used to guard the mount with becomes `enabled`. Whatever expression it was still
+works: `__DEV__`, an environment variable, a value you fetch for one user. See
+[Leaving it in production](/docs/expo-devtools/production).
+
+A long config is easier to keep in its own file, exactly as the client used to be:
+
+```ts title="devtools.ts"
+import type { DevtoolsConfig } from '@axonpack/expo-devtools';
+
+export const devtoolsConfig = {
+ enabled: __DEV__,
+ // ...everything you passed to createDevtoolsClient
+} satisfies DevtoolsConfig;
+```
+
+### 2. Move your in-app browser wiring to the hook
+
+```tsx
+// before
+
+```
+
+```tsx
+// after
+const devtoolsWebView = useDevtoolsWebView('checkout');
+
+;
+```
+
+Delete `webviewSources` from your config. A name is no longer declared anywhere: it is whatever you
+hand the hook, and it only labels that page's rows. See
+[In-app browsers](/docs/expo-devtools/in-app-browsers).
+
+### 3. Import `devtools` where you used the client
+
+`mark`, `measure`, `setCrashContext` and the stores kept their names and signatures. They come from
+the package now instead of from an instance you made:
+
+```ts
+// before
+import { devtools } from '../devtools';
+
+// after
+import { devtools } from '@axonpack/expo-devtools';
+```
+
+## Everything that moved
+
+| 2.x | 3.0 |
+| ---------------------------------------------------------- | ---------------------------------------------------------- |
+| `createDevtoolsClient(config)` | `` |
+| `devtools.init()` | Gone. The provider starts as it renders |
+| `` | The same props, on `` |
+| Guarding the overlay mount | `config.enabled` |
+| `config.webviewSources` | Gone. Pass the name to `useDevtoolsWebView` |
+| `devtools.getWebViewInjectedJavaScriptBeforeContentLoaded` | `useDevtoolsWebView(...).injectedJavaScriptBeforeContentLoaded` |
+| `devtools.handleWebViewMessage` | `useDevtoolsWebView(...).onMessage` |
+| `devtools.getWebViewRef` | `useDevtoolsWebView(...).ref` |
+| `devtools.getWebViewUserAgent()` | `useDevtoolsWebView(...).userAgent` |
+| `devtools.shouldAllowWebViewRequest` | `useDevtoolsWebView(...).onShouldStartLoadWithRequest` |
+| `devtools.mark` / `measure` / `clearMarks` / `clearMeasures` | The same, on the exported `devtools` |
+| `devtools.setCrashContext` | The same, on the exported `devtools` |
+| `devtools.networkLogStore` and the other stores | The same, on the exported `devtools` |
+| `DevtoolsClientConfig` | `DevtoolsConfig` |
+| `DevtoolsOverlayProps` | `DevtoolsProviderProps` |
+| `StartupTiming.initCalled` | `StartupTiming.devtoolsStart` |
+
+## Two things that behave differently
+
+**Capture starts at the provider's first render**, not at module evaluation. It is still before any
+child mounts, so the first screen's requests are caught, but a request fired while modules are
+evaluating now happens too early to see. If you moved `init()` ahead of Expo Router's entry file to
+widen that window, that trick is gone: undo it and let the root layout hold the provider.
+
+**`crash.enableWhileDevtoolsDisabled` installs at that same first render**, rather than when a client
+was constructed. It still captures crashes with `enabled: false`, which is the whole point of the flag,
+and it still reports only the crashes that end the app.
+
+## New in 3.0
+
+- **[`useDevtoolsPanel()`](/docs/expo-devtools/reference/hooks)** opens and closes the panel from your own UI.
+- **`showFloatingButton={false}`** hides the launcher button and leaves the panel working.
+
+## Next step
+
+
+
+
+
diff --git a/src/content.json b/src/content.json
index 25f1c8e..f059386 100644
--- a/src/content.json
+++ b/src/content.json
@@ -98,11 +98,11 @@
}
],
"usage": {
- "title": "One factory, one call",
- "body": "No provider to wrap your tree in and no context to thread through it. Configure the client once, call init() at startup, and mount the overlay where you want it.",
- "filename": "devtools.ts",
- "code": "import { createDevtoolsClient } from '@axonpack/expo-devtools';\n\nexport const devtools = createDevtoolsClient({\n theme: 'dark',\n network: { http: true, websocket: true, sse: true },\n console: { capture: true, repl: true },\n});\n\n// Guard this one call and the whole package stays dark in production.\ndevtools.init();",
- "note": "Nothing records anywhere until init() runs, which is what makes shipping the code to production free."
+ "title": "One provider, one flag",
+ "body": "No client to create and pass around, and no init call to place correctly. Wrap the app once, configure it inline or from a file, and one flag decides whether any of it runs.",
+ "filename": "App.tsx",
+ "code": "import { DevtoolsProvider } from '@axonpack/expo-devtools';\n\nexport default function App() {\n return (\n \n \n \n );\n}",
+ "note": "The patches go in as the provider renders, so the first screen is already covered, and nothing records anywhere unless enabled says so."
},
"principles": {
"title": "How they are built",