--- url: /docs/introduction.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # Introduction Rozenite adds plug-and-play debugging panels to React Native DevTools — for React Native, web and Lynx apps. Install official plugins, open DevTools, and start debugging in minutes — no extra windows or servers. ![](/rozenite-loaded.png) It also gives teams a safe, scalable way to standardize debugging across projects. Plugins auto‑discover and load during development, are easy to configure, and are automatically disabled in production builds so no plugin code ships to your users. :::info Try it now Skip ahead to the [Getting Started guide](/docs/getting-started.md) or browse the [Plugin Directory](/plugin-directory.md). ::: ## Why we built this At [Callstack](https://callstack.com/), we work with teams building React Native apps that drive business value. But as these apps grow, debugging becomes a bottleneck that slows down development and hurts user experience. Teams spend more time fighting fires than building features. One of the biggest problems we see is the lack of good tools for monitoring and debugging React Native apps. Teams constantly ask us for ways to gain insights into their apps—to track performance, monitor network requests, debug state management, or connect to their internal monitoring systems. But here's the problem: React Native DevTools doesn't support plugins. It's a great tool, but it's closed and can't be extended. This forces teams to build their own debugging solutions from scratch. They waste weeks or months creating custom tools, setting up communication layers, and building UIs just to get the insights they need. This is expensive, time-consuming, and diverts resources from building actual features. **We built Rozenite to solve this problem by giving you a complete toolkit for extending React Native DevTools.** Instead of building everything from scratch, you can now create plugins that integrate seamlessly with React Native DevTools. You get a proven communication layer, a solid build system, and all the infrastructure you need. This means you can focus on building the insights that matter to your team, not reinventing the wheel. :::note Fun fact Rozenite is a rare mineral first described in 1960 on Ornak Mountain in the Western Tatras. Named after Polish mineralogist Zygmunt Rozen, it symbolizes exploration and discovery — the same spirit behind extending DevTools with new capabilities. ::: ## Who it’s for - **Developers who want built‑in tooling**: add network, performance, storage and state panels without building anything. - **Teams using coding agents**: let agents inspect logs, network activity, and React profiling data through Rozenite for Agents. - **Teams that need custom insights**: create tailored panels for your product, internal observability, or business logic. - **Teams on Lynx**: the same panels, plugins and CLI, over [Rozenite for Lynx](/docs/targets/rozenite-for-lynx.md) (experimental). ## Build your own Rozenite includes a type‑safe, batteries‑included development experience for creating custom plugins when you need to go beyond the official ones. Start with the [Plugin Development overview](/docs/plugin-development/overview.md) when you're ready. ## Next steps Ready to try it? Start with the [Getting Started guide](/docs/getting-started.md), explore the [Rozenite for Agents overview](/docs/agent/overview.md), see which [targets](/docs/targets/rozenite-for-lynx.md) Rozenite reaches, or browse the [Plugin Directory](/plugin-directory.md). When you need something custom, see the [Plugin Development overview](/docs/plugin-development/overview.md). --- url: /docs/prior-art.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # Prior Art Rozenite wouldn't exist without the work that came before it. We want to thank the teams behind the tools that taught us what React Native debugging could be and helped us understand what developers actually need. ## Flipper [Flipper](https://fbflipper.com/) showed us that React Native debugging didn't have to be stuck with basic logging. The idea of having plugins for debugging was pretty cool, and seeing developers build custom panels for their specific needs opened our eyes to what was missing in the ecosystem. We learned a lot from watching Flipper's journey—both what worked well and what was challenging to maintain. That experience helped us think about how to build something that would integrate better with existing tools and be easier to keep running. ## Expo Dev Tools Plugins The [Expo team](https://expo.dev/) has always been great at making complicated things simple. Their dev tools plugins showed us how debugging tools could just work without a bunch of setup. We really admire how they think about developer experience - if something is hard to use, people won't use it. That philosophy of "make it easy" is something we think about a lot when working on Rozenite. The way Expo approaches tooling has definitely influenced how we want people to feel when they use our stuff. ## Reactotron [Reactotron](https://github.com/infinitered/reactotron) was one of the first tools that let you see what was happening in your React Native app in real time. Before tools like this, debugging React Native felt pretty limited. Reactotron showed us that having a dedicated space for debugging could be really valuable. We appreciate how much thought went into making Reactotron's interface clean and easy to understand. When you're trying to debug something, the last thing you want is for your debugging tool to be confusing. ## React Native DevTools The [React Native DevTools](https://reactnative.dev/docs/debugging) team at Meta built the foundation that makes Rozenite possible. Instead of building yet another debugging app, we wanted to extend what was already there and working well. Their work on integrating with Chrome DevTools means developers can use familiar tools, and we get to build on top of something that's actively maintained and improved. We're grateful that they've created such a solid base for the community to build on. ## React Query External Sync [React Query External Sync](https://github.com/LovesWorking/react-query-external-sync) by Austin Johnson provided crucial insights into making TanStack Query work effectively in React Native environments. These tools taught us what developers need and helped us understand the problems that still need solving. We hope Rozenite can be useful to the community and maybe help other people build even better debugging tools in the future. --- url: /docs/getting-started.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # Quick start ## Pre-requisites Rozenite assumes you're comfortable with a React Native project. If you're new to React Native, start with the [React Native documentation](https://reactnative.dev/) first. On Lynx, follow [Rozenite for Lynx](/docs/targets/rozenite-for-lynx.md) instead — `rozenite init` doesn't detect rspeedy projects yet ([#493](https://github.com/callstackincubator/rozenite/issues/493)). ## Install Run the `rozenite init` command in your project. It detects your bundler, installs the right package, and updates your config for you. ```sh [npx] npx rozenite@latest init ``` ```sh [yarn] yarn dlx rozenite@latest init ``` ```sh [pnpm] pnpm dlx rozenite@latest init ``` ```sh [bunx] bunx rozenite@latest init ``` That's it — start your app as usual and open React Native DevTools. If everything worked, you'll see plugin panels for anything you've installed (see [Official Plugins](/docs/official-plugins/overview.md) to add some). If the command fails, or you'd rather wire things up yourself, follow the manual steps below. ## Manual setup ### 1. Install the package for your bundler **Metro** (the default React Native bundler): ```sh [npm] npm install -D @rozenite/metro ``` ```sh [yarn] yarn add -D @rozenite/metro ``` ```sh [pnpm] pnpm add -D @rozenite/metro ``` ```sh [bun] bun add -D @rozenite/metro ``` ```sh [deno] deno add -D npm:@rozenite/metro ``` **Re.Pack**: ```sh [npm] npm install -D @rozenite/repack ``` ```sh [yarn] yarn add -D @rozenite/repack ``` ```sh [pnpm] pnpm add -D @rozenite/repack ``` ```sh [bun] bun add -D @rozenite/repack ``` ```sh [deno] deno add -D npm:@rozenite/repack ``` ### 2. Enable Rozenite in your bundler config Rozenite is off by default, so it never runs in production by accident. Enable it explicitly, typically behind an environment variable so you can turn it on and off per run: ```bash # Enable Rozenite in development WITH_ROZENITE=true npm start ``` **Metro** — update `metro.config.js`: ```javascript title="metro.config.js" const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config'); const { withRozenite } = require('@rozenite/metro'); const defaultConfig = getDefaultConfig(__dirname); const customConfig = { // Your existing Metro configuration }; module.exports = withRozenite(mergeConfig(defaultConfig, customConfig), { enabled: process.env.WITH_ROZENITE === 'true', }); ``` **Re.Pack** — update `rspack.config.mjs`: ```javascript title="rspack.config.mjs" import { withRozenite } from '@rozenite/repack'; export default withRozenite( { // Your existing Re.Pack configuration }, { enabled: process.env.WITH_ROZENITE === 'true', }, ); ``` ### 3. Start your app ```sh [npm] npm start ``` ```sh [yarn] yarn start ``` ```sh [pnpm] pnpm start ``` ```sh [bun] bun start ``` ```sh [deno] deno start ``` Open React Native DevTools — any Rozenite plugins you've installed will show up automatically, no extra wiring needed. ## Choosing which plugins load By default, every installed plugin loads automatically. If you want to limit that, pass `include` or `exclude` to `withRozenite`: ```javascript title="metro.config.js" module.exports = withRozenite(mergeConfig(defaultConfig, customConfig), { enabled: process.env.WITH_ROZENITE === 'true', include: ['@rozenite/storage-plugin', '@rozenite/network-activity-plugin'], // only load these // or exclude: ['@rozenite/storage-plugin'], // load everything except these }); ``` The same options work in `withRozenite` for Re.Pack. If both `include` and `exclude` are set, `exclude` is applied after `include`. ## Panel layout Plugins with multiple panels are grouped together in one sidebar by default; plugins with a single panel appear directly in the list. If you'd rather give every panel its own DevTools tab, set `pluginDisplay: 'tabs'`: ```javascript title="metro.config.js" module.exports = withRozenite(mergeConfig(defaultConfig, customConfig), { enabled: process.env.WITH_ROZENITE === 'true', pluginDisplay: 'tabs', }); ``` Rozenite also keeps a plugin's UI state in memory when you switch away from its panel, so you don't lose data by navigating around. For plugins that use a lot of memory, you can opt out per plugin with `destroyOnDetachPlugins` — the trade-off is that the plugin resets and reloads each time you come back to it: ```javascript title="metro.config.js" module.exports = withRozenite(mergeConfig(defaultConfig, customConfig), { enabled: process.env.WITH_ROZENITE === 'true', destroyOnDetachPlugins: ['@rozenite/network-activity-plugin'], }); ``` ## Verifying it worked - Your bundler's server logs should mention discovering Rozenite plugins. - React Native DevTools should show a panel for each plugin you've installed. If nothing shows up, double check that `enabled` evaluates to `true` and that you restarted the bundler after changing the config. ## Using Rozenite with AI coding agents If you use AI or coding agents in your workflow, continue with the [Rozenite for Agents overview](/docs/agent/overview.md). --- url: /docs/compatibility.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # Compatibility ## Supported versions ### React Native | Rozenite | Expo SDK | React Native | Re.Pack | | ---------------- | -------- | ------------ | ------- | | 1.13.0, >= 2.0.0 | 52+ | 0.76+ | 5.2+ | Rozenite tracks Expo SDK releases as its primary compatibility baseline, since each Expo SDK pins a specific React Native version. If you're on bare React Native (no Expo), match your React Native version to the one shipped by the minimum supported Expo SDK above. ### Lynx | Rozenite | rspeedy | @lynx-js/react | | -------- | ------- | -------------- | | >= 2.4.0 | 0.16+ | 0.125+ | Lynx support is experimental. These are the versions it is developed and tested against; it has not been exercised across the full range of LynxSDK releases. ## Bundlers - **Metro** — supported via `@rozenite/metro`, using whichever Metro version ships with your React Native version. No separate Metro version requirement beyond the React Native minimum above. - **Re.Pack** — supported via `@rozenite/repack` for Re.Pack 5.2 and above. - **rspeedy / Rsbuild** — supported via `@rozenite/lynx` for rspeedy 0.16 and above. ## Platforms - iOS and Android, via React Native DevTools - Lynx, via [Rozenite for Lynx](/docs/targets/rozenite-for-lynx.md) (experimental) - Web, via [Rozenite for Web](/docs/targets/rozenite-for-web.md) (experimental) - Plugins declare which of these integrations they support in `rozenite.config.ts`; see [Plugin Development](/docs/plugin-development/plugin-development.md). ## Notes - Older Expo SDK / React Native versions may work but are untested and unsupported. - Individual plugins may declare narrower compatibility (for example, a plugin wrapping a native module with its own minimum React Native version). Check the plugin's page under [Official Plugins](/docs/official-plugins/overview.md) for details. --- url: /docs/targets/rozenite-for-lynx.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # Rozenite for Lynx :::warning Experimental Rozenite for Lynx is experimental. It is developed and tested against rspeedy 0.16 and `@lynx-js/react` 0.125, and has not been verified across the full range of LynxSDK versions. The API may change in future releases. ::: Rozenite for Lynx lets you debug a [Lynx](https://lynxjs.org) app with the same DevTools panels, the same plugin catalogue and the same `rozenite` CLI you use for React Native. You need two things: `@rozenite/lynx` in your dev server, and **Lynx DevTool turned on in the app itself** — that last one is off by default and nothing works without it. Start there. ## Turn on Lynx DevTool Lynx ships its DevTool component switched off. With it off, your app never registers a debuggable session, so Rozenite finds nothing to connect to and your target list stays empty. **In LynxExplorer**, on Android and iOS alike, open the Settings tab → **Lynx DevTool Switches**, turn **Lynx DevTool** on, then quit and reopen the app. The switch only takes effect on a fresh launch. On Android the same page also has V8 engine and PrimJS toggles — leave those alone, Rozenite works either way. **In your own app**, enable it where you initialise the Lynx environment — `LynxEnv.inst().enableDevtool(true)` on Android, `devtoolEnabled = YES` on iOS. On iOS you also need `enableAllSessions` on `LynxServiceDevToolProtocol`, or your cards won't be offered for debugging. Lynx's [Integrate Lynx DevTool](https://lynxjs.org/guide/start/integrate-lynx-devtool.html) guide has the full setup for each platform. ## Add @rozenite/lynx to your dev server ### Installation ```sh [npm] npm install -D @rozenite/lynx ``` ```sh [yarn] yarn add -D @rozenite/lynx ``` ```sh [pnpm] pnpm add -D @rozenite/lynx ``` ```sh [bun] bun add -D @rozenite/lynx ``` ```sh [deno] deno add -D npm:@rozenite/lynx ``` ### Configuration Add the plugin to your `lynx.config.ts`: ```typescript import { defineConfig } from '@lynx-js/rspeedy'; import { pluginReactLynx } from '@lynx-js/react-rsbuild-plugin'; import { rozeniteLynxPlugin } from '@rozenite/lynx/rspeedy'; export default defineConfig({ plugins: [pluginReactLynx(), rozeniteLynxPlugin()], }); ``` That's it — there is nothing to add to your app's own source. The plugin injects the device runtime for you ahead of your app's entry point, and only in development: it is inert in production builds (pass `enabled` explicitly if you want to control that yourself), and it never runs at all during `rspeedy build`. Rozenite plugins are discovered from your project's `package.json` exactly as they are for React Native — install a plugin as a dependency and it shows up. Not every plugin supports Lynx yet; see [What doesn't work yet](#what-doesnt-work-yet). ## Open DevTools 1. Connect your device over USB, or start your simulator, and open your app. 2. Start the dev server and load your bundle in the app. 3. The dev server prints a DevTools URL for every card it finds. Open the one for your card in a browser. Each Lynx card is its own target, so you get one URL per card rather than one per app. In LynxExplorer that includes LynxExplorer's own home screen — pick the entry whose title is your bundle URL. ## Pin discovery to one device Rozenite finds Android phones **and emulators** over adb, physical iPhones over usbmux, and the iOS Simulator by scanning `127.0.0.1:8901-8919`. All three are on by default, so the common cases need no configuration. Narrow it if you want: ```typescript rozeniteLynxPlugin({ enableDesktop: false, // stop scanning localhost — the iOS Simulator disappears deviceSerial: 'ABC123', // only this device (an adb serial or a udid) }); ``` Note that `enableDesktop` is an iOS Simulator concern only. An Android emulator arrives over adb with its real `emulator-5554` serial, so turning `enableDesktop` off doesn't hide it — turning `enableAndroid` off does. ## Troubleshooting **Your target list is empty.** Nine times out of ten, Lynx DevTool is off — see [Turn on Lynx DevTool](#turn-on-lynx-devtool), and remember it needs an app restart. Otherwise: check the app is a development build, and that a card is actually open (an app sitting on a native screen has no cards to debug). On Android, `adb devices` should list the phone or emulator — if it doesn't, Rozenite can't see it either. On the iOS Simulator, check you haven't turned `enableDesktop` off. **Your card shows up but panels stay disconnected.** The app is reachable but Rozenite isn't running inside it. `rozeniteLynxPlugin()` only injects the device runtime for `rspeedy dev` — never for `rspeedy build`, and not for `rspeedy preview` either, which runs with `NODE_ENV=production`. Check you're on the dev server, and that `enabled` hasn't been explicitly turned off in your `lynx.config.ts`. **A panel connects but never receives anything.** Most likely the plugin's device half is written against React Native APIs — see [What doesn't work yet](#what-doesnt-work-yet). **`lynx.getDevtool is not a function`.** Your LynxSDK predates the devtool event channel. Upgrade it. **The card renders blank.** Usually a bad bundle URL rather than anything to do with Rozenite. Check the scheme is lowercase `http://` — some on-screen keyboards autocapitalise the first letter, and Lynx won't load `Http://`. On an Android emulator, use your machine's LAN address rather than `localhost`; `localhost` there is the emulator itself. ## What doesn't work yet - Plugins whose device half is written against React Native APIs don't work on Lynx: network activity, storage, file system, performance monitor, require profiler, Redux DevTools and Expo Atlas. Plugins that only move state around do work — TanStack Query, React Hook Form, feature flags and controls. - Every plugin declares the targets it supports in its `rozenite.config.ts`, so the list above is the one the plugins themselves agree on. --- url: /docs/targets/rozenite-for-web.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # Rozenite for Web :::warning Experimental Rozenite for Web is an experimental feature. The API and behavior may change in future releases. ::: Rozenite for Web lets you debug your React Native web app from React Native DevTools — the same plugin panels you use on iOS and Android, in the browser. You'll need a Chromium-based browser, the Rozenite browser extension, and the `@rozenite/web` package in your app. It works whether Metro bundles your web app directly, or you use Webpack Dev Server for web. ## Install the Chrome extension 1. Download the latest extension from the [releases page](https://github.com/callstackincubator/rozenite/releases). 2. Open Chrome, go to `chrome://extensions`, and enable **Developer mode** (top-right toggle). 3. Install the extension. If you get stuck, see Chrome's [extension loading guide](https://developer.chrome.com/docs/extensions/get-started/tutorial/hello-world#load-an-unpacked-extension). ## Add @rozenite/web to your app ### Installation ```sh [npm] npm install -D @rozenite/web ``` ```sh [yarn] yarn add -D @rozenite/web ``` ```sh [pnpm] pnpm add -D @rozenite/web ``` ```sh [bun] bun add -D @rozenite/web ``` ```sh [deno] deno add -D npm:@rozenite/web ``` ### Metro-only web configuration Wrap your Metro config with `withRozeniteWeb`: ```javascript const { getDefaultConfig } = require('expo/metro-config'); const { withRozeniteWeb } = require('@rozenite/web/metro'); const config = getDefaultConfig(__dirname); module.exports = withRozeniteWeb(config); ``` ### Entry point Add `require('@rozenite/web')` to your web entry point — commonly `main.tsx` or your Expo Router root `_layout.tsx`. It already gates itself to development, so there's no need to wrap it in `__DEV__` yourself. ```javascript require('@rozenite/web'); ``` ### Webpack configuration Wrap your Webpack config with `withRozeniteWeb`: ```javascript const { withRozeniteWeb } = require('@rozenite/web/webpack'); module.exports = withRozeniteWeb({ // your existing webpack config }); ``` ### Web entry point Use the same entry pattern when using Webpack too. ## How to use it 1. Start your app in development mode. 2. Open the web app in a Chromium-based browser. 3. Press `j` in the terminal running your app. 4. New debugging targets should now be available. 5. Select the browser tab you want to debug. ## Troubleshooting - Make sure the extension is installed and enabled. - Make sure your app is running on `localhost`. - If you use Metro-only web, make sure `withRozeniteWeb` is added to Metro and `require('@rozenite/web')` is loaded in your web entry. - If you use Webpack, make sure your config is wrapped with `withRozeniteWeb`. --- url: /docs/agent/overview.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # Rozenite for Agents Rozenite for Agents is the agent-facing way to interact with your running app and its Rozenite plugins. It gives coding agents a reliable interface for inspecting a running app through Rozenite, including logs, network activity, React profiling data, and plugin-specific tooling. You can access this workflow through the `rozenite agent` CLI or, if you need direct programmatic access, through [`@rozenite/agent-sdk`](/docs/agent/sdk.md). For most users, the CLI is still the recommended starting point. :::warning Experimental This functionality is experimental and may not work in all cases. If you run into a bug, please [open an issue](https://github.com/callstackincubator/rozenite/issues). ::: ## What coding agents use it for - Read app logs while debugging a failure - Capture and inspect network requests during a user flow - Run React profiling when investigating slow renders - Use tools exposed by installed Rozenite plugins - Use app-defined custom tools registered directly by the running app - Work against the connected app target without inventing custom instrumentation ## Before you start Make sure: - Rozenite is installed and configured for the app - your app is running in development mode - Metro is running - at least one target is connected If you have more than one simulator, emulator, or device connected, you will need to choose which one to inspect. Rozenite for Agents requires a project with Rozenite already installed and configured. The agent skills teach your coding agent how to use that setup effectively. Rozenite for Agents is designed for AI and coding agents first. The underlying CLI can be called directly with `npx rozenite`, but the intended use is for an agent to drive it while debugging or implementing changes in your app. :::warning React Native DevTools will disconnect Rozenite for Agents acts as a debugger connection to the running app. Because of a current React Native limitation, starting an agent session will disconnect React Native DevTools if it is already attached. This is a platform limitation rather than a Rozenite-specific design choice. Support for multiple simultaneous debugger connections is being worked on, and once React Native allows that, Rozenite for Agents will be able to coexist with React Native DevTools. ::: ## How agents usually work with it The typical flow is: 1. Connect to a running app target by creating or reusing a session. 2. Discover which Rozenite domains are available for that target. 3. Use the relevant domain to inspect the app state or collect debugging data. Built-in domains include `console`, `network`, `react`, `performance`, and `memory`. Runtime domains can also come from installed Rozenite plugins. At the moment, the documented agent-capable plugin domains include: - `@rozenite/controls-plugin` - `@rozenite/file-system-plugin` - `@rozenite/react-navigation-plugin` - `@rozenite/network-activity-plugin` - `@rozenite/redux-devtools-plugin` - `@rozenite/storage-plugin` - `@rozenite/tanstack-query-plugin` Prefer the built-in `network` domain first when it is available. `@rozenite/network-activity-plugin` is the documented fallback plugin domain for apps where the built-in network domain is unavailable. Apps can also expose in-app custom tools. These are runtime tools registered directly by the app instead of by a separate Rozenite plugin package. If you want to give a coding agent this workflow directly, install the [`rozenite` skill](/docs/agent/skills.md). It routes the agent to `npx rozenite skills list` and `npx rozenite skills show ` to discover and read the CLI and SDK workflow docs on demand. ## Next steps - Install the [Agent Skill](/docs/agent/skills.md) (`rozenite`) if you want Codex or another coding agent to use Rozenite effectively. - [Adding tools to your application](/docs/agent/adding-tools-to-your-application.md) – expose custom tools from your app for agents to call. - [Making your plugin agent-enabled](/docs/agent/making-your-plugin-agent-enabled.md) – expose tools from your Rozenite plugin to agents. - [Agent SDK](/docs/agent/sdk.md) – build custom tooling or automation on top of the agent workflow. - [Tap](/docs/agent/tap.md) – stream a plugin's messages to the terminal, both directions, without opening a browser. --- url: /docs/agent/skills.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # Agent Skills If you use Codex or another coding agent that supports the Vercel `skills` CLI, you can install the Rozenite skill so the agent can use Rozenite for Agents efficiently. Rozenite ships a single skill, `rozenite`. It's a thin router — installing it just points your agent at the `rozenite skills` CLI command, which serves the actual guidance (ground truths, CLI and SDK workflows, one doc per plugin domain) straight from the `rozenite` package. That guarantees the content always matches the version of Rozenite installed in your project. Because of that, installing the skill is a convenience, not a requirement. If your coding agent doesn't use a skills CLI at all, point it straight at `npx rozenite skills list` and `npx rozenite skills show ` and it gets the exact same content. This skill complements Rozenite; it does not replace it. Your app still needs Rozenite installed and configured because the agent ultimately talks to the running app through the Rozenite agent runtime. ## Install with the Vercel `skills` CLI Install from this repository using the repo URL: ```bash npx skills add https://github.com/callstackincubator/rozenite --skill rozenite --agent codex ``` By default, the `skills` CLI installs project-local skills. For Codex, that means the skill is linked into `.agents/skills/`. To install it globally for all projects instead, use: ```bash npx skills add https://github.com/callstackincubator/rozenite --skill rozenite --agent codex --global ``` ## The `rozenite skills` command Once the skill is installed, the agent discovers and reads the actual workflow content through two CLI commands, run from the app root where Metro is started (in a monorepo, that's the app package root, not the repository root): ```bash npx rozenite skills list ``` Lists every bundled doc as an id and a one-line description, for example `core`, `cli`, `sdk`, `sdk-patterns`, and one doc per domain such as `storage`, `mmkv`, or `network`. ```bash npx rozenite skills show ``` Prints the raw content of one doc. The agent typically starts with `npx rozenite skills show core` for the ground truths shared by every workflow, then reads `cli` or `sdk` depending on whether it's driving the shell or writing code, and reads a domain doc like `storage` or `react-navigation` only when it needs that domain. An unknown id fails with a non-zero exit code and a message listing the valid ids. ## Next steps - Go back to the [Rozenite for Agents](/docs/agent/overview.md) overview. - [Adding tools to your application](/docs/agent/adding-tools-to-your-application.md) – expose custom tools from your app for agents to call. - [Making your plugin agent-enabled](/docs/agent/making-your-plugin-agent-enabled.md) – expose tools from your Rozenite plugin to agents. --- url: /docs/agent/adding-tools-to-your-application.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # Adding tools to your application You can expose custom agent tools directly from your app. These tools appear under the **app** domain so coding agents (e.g. CLI, Cursor, Codex) can discover and call them via Rozenite for Agents. Use this when you want agents to perform app-specific actions—for example, triggering a debug flow, returning build metadata, or driving in-app behavior during automated testing. ## Prerequisites - Rozenite is installed and configured in your app (see [Getting started](/docs/getting-started.md)). - Your app runs in development mode with at least one connected target so the agent bridge can communicate with the Rozenite devtools. ## Install the agent bridge Install `@rozenite/agent-bridge` as a dependency: ```sh [npm] npm install @rozenite/agent-bridge ``` ```sh [yarn] yarn add @rozenite/agent-bridge ``` ```sh [pnpm] pnpm add @rozenite/agent-bridge ``` ```sh [bun] bun add @rozenite/agent-bridge ``` ```sh [deno] deno add npm:@rozenite/agent-bridge ``` The bridge provides React hooks that register tools with the Rozenite agent plugin, handle incoming tool calls, and send results back to the agent. ## Define and register a tool Each tool has: - **name** – Unique identifier (agents will see it as `app.`). - **description** – Short explanation for the agent. - **inputSchema** – JSON Schema–style object describing the tool’s arguments. - **handler** – Function that receives the parsed arguments and returns a result (or throws). Use the `useRozeniteInAppAgentTool` hook inside a component that is mounted when your app is active. The tool is registered on mount and unregistered on unmount. ### Example: build info tool ```tsx import { useRozeniteInAppAgentTool, type AgentTool } from '@rozenite/agent-bridge'; const buildInfoTool: AgentTool = { name: 'get-build-info', description: 'Return app build metadata.', inputSchema: { type: 'object', properties: { includeNative: { type: 'boolean', description: 'Include native build info if available.', }, }, }, }; function AppAgentTools() { useRozeniteInAppAgentTool({ tool: buildInfoTool, handler: (args) => { return { version: '1.0.0', environment: __DEV__ ? 'development' : 'production', includeNative: args?.includeNative ?? false, }; }, }); return null; } ``` Mount `AppAgentTools` (or your equivalent) somewhere in your app tree—for example next to your root navigator or DevTools setup—so the tool is available whenever the app is connected. ### Example: alert tool ```tsx import { Alert } from 'react-native'; import { useRozeniteInAppAgentTool, type AgentTool } from '@rozenite/agent-bridge'; const showAlertTool: AgentTool = { name: 'show-alert', description: 'Show a native alert in the app.', inputSchema: { type: 'object', properties: { title: { type: 'string', description: 'Alert title.' }, message: { type: 'string', description: 'Alert body text.' }, }, }, }; function AppAgentTools() { useRozeniteInAppAgentTool({ tool: showAlertTool, handler: ({ title, message }) => { Alert.alert(title ?? 'Agent', message ?? ''); return { ok: true }; }, }); return null; } ``` ## Tool shape and behavior - **Name**: Use kebab-case (e.g. `get-build-info`). The qualified name seen by agents is `app.`. - **inputSchema**: Follows a JSON Schema–like shape (`type`, `properties`, `required`, etc.). Agents use this to build valid arguments and understand optional vs required fields. - **handler**: Can be async. Return a serializable value (objects, arrays, primitives). On throw, the bridge sends an error result to the agent. You can register multiple tools by calling `useRozeniteInAppAgentTool` multiple times (e.g. one call per tool) in the same or different components. ## How agents see your tools Agents discover app tools like any other domain: ```bash rozenite agent domains --session --json rozenite agent app tools --session --json rozenite agent app call --tool get-build-info --args '{}' --session --json ``` The **app** domain is created when at least one in-app tool is registered. When all such tools are unregistered (e.g. components unmount), the domain may no longer appear. ## Optional: enable/disable You can gate registration with the `enabled` option: ```tsx useRozeniteInAppAgentTool({ tool: buildInfoTool, handler: () => ({ version: '1.0.0' }), enabled: __DEV__, }); ``` When `enabled` is false, the tool is not registered and will not appear under the app domain. ## Next steps - [Making your plugin agent-enabled](/docs/agent/making-your-plugin-agent-enabled.md) – expose tools from your Rozenite plugin to agents. - Go back to the [Rozenite for Agents](/docs/agent/overview.md) overview. --- url: /docs/agent/making-your-plugin-agent-enabled.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # Making your plugin agent-enabled Rozenite plugins can expose **agent tools** so coding agents (CLI, Cursor, Codex) can inspect and control your plugin’s domain via Rozenite for Agents. Tools are grouped under a domain that matches your plugin’s ID, and agents discover them the same way they use built-in domains like `console` or `network`. This page describes what you need to expose tools from your plugin package. ## Prerequisites - You have a Rozenite plugin with an **app side** (code that runs inside the app, from its `react-native.ts` entry point). See [Plugin Development](/docs/plugin-development/overview.md) if you are new to plugins. - Your plugin is already integrated so its app entry runs when the app is in development. ## Add the agent bridge dependency In your plugin package, install `@rozenite/agent-bridge`: ```sh [npm] npm install @rozenite/agent-bridge ``` ```sh [yarn] yarn add @rozenite/agent-bridge ``` ```sh [pnpm] pnpm add @rozenite/agent-bridge ``` ```sh [bun] bun add @rozenite/agent-bridge ``` ```sh [deno] deno add npm:@rozenite/agent-bridge ``` The bridge provides the same message protocol and tool lifecycle as in-app tools, but registers tools under your **plugin ID** instead of the `app` domain. ## Use the plugin agent tool hook From your plugin’s app-side code, call `useRozenitePluginAgentTool` with: - **pluginId** – Your plugin’s public ID (e.g. `@rozenite/react-navigation-plugin`). This becomes the **domain** agents use to list and call your tools. - **tool** – An `AgentTool`: `name`, `description`, and `inputSchema` (JSON Schema–style). - **handler** – A function that receives the tool’s arguments and returns a serializable result (or throws). Each tool is qualified as `{pluginId}.{tool.name}`. The hook registers the tool when the component mounts and unregisters it on unmount. ### Example: echo tool ```tsx import { useRozenitePluginAgentTool, type AgentTool } from '@rozenite/agent-bridge'; const PLUGIN_ID = '@my-org/my-rozenite-plugin'; const echoTool: AgentTool = { name: 'echo', description: 'Return the provided value.', inputSchema: { type: 'object', properties: { value: { type: 'string' }, }, required: ['value'], }, }; export function MyPluginAgentTools() { useRozenitePluginAgentTool({ pluginId: PLUGIN_ID, tool: echoTool, handler: ({ value }: { value: string }) => ({ value }), }); return null; } ``` Mount `MyPluginAgentTools` from your plugin’s app entry (e.g. next to your DevTools panel registration) so it runs when the app is connected. Agents will see a domain equal to `PLUGIN_ID` and a tool named `echo` (qualified as `@my-org/my-rozenite-plugin.echo`). ### Example: plugin-specific tool The React Navigation plugin exposes tools such as `get-root-state`, `navigate`, and `go-back`. Each is defined as an `AgentTool` and registered with `useRozenitePluginAgentTool` using `pluginId: '@rozenite/react-navigation-plugin'`. The handler calls into the plugin’s refs and callbacks (e.g. navigation ref, state getters). For example: ```tsx useRozenitePluginAgentTool({ pluginId: '@rozenite/react-navigation-plugin', tool: getRootStateTool, handler: () => { const state = getCurrentState(); return { state, hasState: !!state }; }, }); ``` Other official plugins that expose agent tools include: - **@rozenite/storage-plugin** – Generic storage adapter inspection and mutation - **@rozenite/controls-plugin** – Controls inspection and interaction - **@rozenite/redux-devtools-plugin** – Redux store inspection and curated history control You can mirror this pattern: define one or more `AgentTool` objects, then register each with `useRozenitePluginAgentTool` in a component that is mounted when your plugin is active. ## Tool shape and lifecycle - **pluginId**: Must match the plugin ID you use in Rozenite. - **name**: Use kebab-case. The full tool name seen by agents is `{pluginId}.{name}`. - **description** and **inputSchema**: Same as for [in-app tools](/docs/agent/adding-tools-to-your-application.md). Good descriptions and schema help agents call your tools correctly. - **readOnly**, **destructive**, and **idempotent**: Optional tool traits that help agents choose and safely retry tools. Omit a trait when its value is unknown; agents must not assume an omitted trait is `false`. - **handler**: Can be async. Return serializable data; on throw, the bridge sends an error result to the agent. Tools are registered when the component that calls `useRozenitePluginAgentTool` mounts, and unregistered when it unmounts. If your plugin’s UI or logic is conditional, keep the agent tools in a component that stays mounted whenever the plugin is “active” so the domain and tools remain visible to agents. ## Optional: publish typed SDK descriptors Your plugin does **not** need this to be agent-enabled. Agents can already discover and call your tools through the plugin domain. Publishing `./sdk` is a TypeScript convenience for users of [`@rozenite/agent-sdk`](/docs/agent/sdk.md), so they can write: ```ts await session.tools.call(myPluginTools.echo, { value: 'hello' }); ``` instead of manually spelling out the plugin domain and tool name. The pattern used by Rozenite's official plugins is: 1. Put the **public tool contract** in a non-React module such as `src/shared/agent-tools.ts`. 2. Define each tool with `defineAgentToolContract(...)` from `@rozenite/agent-shared`. 3. Reuse those shared definitions both in your app-side registration hook and in `sdk.ts`. 4. In `sdk.ts`, call `defineAgentToolDescriptors(pluginId, toolDefinitions)` and export the resulting descriptor object. That split keeps one source of truth for: - the plugin ID - tool names and descriptions - input schemas - public argument/result types ### 1. Define shared tool contracts ```ts import { defineAgentToolContract, type AgentToolContract } from '@rozenite/agent-shared'; export const MY_PLUGIN_ID = '@my-org/my-rozenite-plugin'; export type EchoArgs = { value: string; }; export type EchoResult = { value: string; }; export const myToolDefinitions = { echo: defineAgentToolContract({ name: 'echo', description: 'Return the provided value.', readOnly: true, idempotent: true, inputSchema: { type: 'object', properties: { value: { type: 'string' }, }, required: ['value'], }, }), } as const satisfies Record>; ``` ### 2. Reuse the same contracts at runtime ```tsx import { useRozenitePluginAgentTool } from '@rozenite/agent-bridge'; import { MY_PLUGIN_ID, myToolDefinitions } from '../shared/agent-tools'; export function MyPluginAgentTools() { useRozenitePluginAgentTool({ pluginId: MY_PLUGIN_ID, tool: myToolDefinitions.echo, handler: ({ value }) => ({ value }), }); return null; } ``` ### 3. Publish typed descriptors from `sdk.ts` ```ts import { defineAgentToolDescriptors } from '@rozenite/agent-shared'; import { MY_PLUGIN_ID, myToolDefinitions } from './src/shared/agent-tools.js'; export { MY_PLUGIN_ID, myToolDefinitions }; export const myPluginTools = defineAgentToolDescriptors(MY_PLUGIN_ID, myToolDefinitions); export type { EchoArgs, EchoResult } from './src/shared/agent-tools.js'; ``` Recommended conventions: - Use `undefined` for tools that take no arguments, so SDK users can write `session.tools.call(myPluginTools.ping)` without passing `{}`. - Export any shared value/model types referenced by your public results from `sdk.ts`, not just the descriptor object. - Keep the tool `name`, `description`, and `inputSchema` in the shared contract module so your runtime registration and published SDK surface cannot drift. ### Paginated tools Use the shared paginated contract for tools that return cursor-paginated rows. The declaration travels with the registered tool, so the CLI can validate `--fields` and use compact columnar output without knowing your plugin ID or tool name. ```ts import { definePaginatedAgentToolContract, type PageResult } from '@rozenite/agent-bridge'; type ListEntriesArgs = { limit?: number; cursor?: string; }; type EntryRow = { key: string; type: string; size?: number; }; type ListEntriesResult = PageResult; export const listEntriesTool = definePaginatedAgentToolContract( { name: 'list-entries', description: 'List entries with cursor pagination.', inputSchema: { type: 'object', properties: { limit: { type: 'number' }, cursor: { type: 'string' }, }, }, pagination: { kind: 'cursor', fields: ['key', 'type', 'size'], defaultFields: ['key', 'type'], }, }, ); ``` Return `{items, page}` from the handler. `fields` is the stable set available to agents, while `defaultFields` is the smaller projection used unless the caller passes `--fields` or `--verbose`. Both arrays are validated against the row type, and the CLI never infers columns from observed values. Tools without pagination metadata keep their original output unchanged. :::tip You do not need to hand-maintain the `./sdk` export in `package.json`. If your plugin has `sdk.ts`, `rozenite build` will detect it and publish the `./sdk` subpath automatically. ::: ## Optional: enable/disable You can conditionally register tools with the `enabled` option: ```tsx useRozenitePluginAgentTool({ pluginId: PLUGIN_ID, tool: echoTool, handler: ({ value }) => ({ value }), enabled: isPluginReady, }); ``` When `enabled` is false, the tool is not registered and will not appear under your plugin’s domain. ## How agents see your plugin tools Agents discover domains and tools at runtime: ```bash rozenite agent domains --session --json rozenite agent my-rozenite tools --session --json rozenite agent my-rozenite call --tool echo --args '{"value":"hello"}' --session --json ``` Rozenite derives a short, stable domain name from your `pluginId` alone (for example, `@my-org/my-rozenite-plugin` becomes `my-org/my-rozenite`; an official `@rozenite/*` plugin drops the scope entirely, e.g. `@rozenite/storage-plugin` becomes `storage`). The full `pluginId` always works as a domain token too, so `rozenite agent @my-org/my-rozenite-plugin tools ...` is equally valid — the derived name is just shorter and computable by an agent without a lookup. Document your plugin’s agent tools (names, arguments, and return shapes) so agent users know what they can call. ## Summary 1. Add **@rozenite/agent-bridge** to your plugin. 2. In your plugin’s app-side code, call **useRozenitePluginAgentTool** with a stable **pluginId**, a **tool** (name, description, inputSchema), and a **handler**. 3. Mount the component that registers these tools so it runs when the plugin is active. 4. Agents will see your plugin as a domain and can list and call your tools via `rozenite agent tools|call ...`. ## Next steps - [Adding tools to your application](/docs/agent/adding-tools-to-your-application.md) – expose app-owned tools under the `app` domain. - [Rozenite for Agents](/docs/agent/overview.md) – how agents use domains and tools. - [Plugin Development](/docs/plugin-development/overview.md) for general plugin structure and app-side integration. --- url: /docs/agent/sdk.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # Agent SDK `@rozenite/agent-sdk` is the programmatic version of `rozenite agent` for Node.js and TypeScript scripts. Use it when you want to write a script, automation, or agent runtime that talks to a running app through Rozenite directly from code. :::tip For most debugging workflows, start with the `rozenite agent` CLI and the [`rozenite` skill](/docs/agent/skills.md). Reach for the SDK when you want code instead of shell commands — the skill's `npx rozenite skills show sdk` doc covers the SDK-first workflow directly. ::: :::warning Experimental Rozenite for Agents is experimental and may change. If you run into a bug, please [open an issue](https://github.com/callstackincubator/rozenite/issues). ::: ## Before you start Make sure: - Node.js 20 or newer - your app is running in development mode - Metro is running and reachable - at least one target is connected ## Install Install the SDK as a dependency: ```sh [npm] npm install @rozenite/agent-sdk ``` ```sh [yarn] yarn add @rozenite/agent-sdk ``` ```sh [pnpm] pnpm add @rozenite/agent-sdk ``` ```sh [bun] bun add @rozenite/agent-sdk ``` ```sh [deno] deno add npm:@rozenite/agent-sdk ``` ## The happy path Most scripts use the same flow: 1. Create a client with `createAgentClient()`. 2. Open a short-lived session with `client.withSession(...)`. 3. Inspect the available domains and tools. 4. Call the tool you need. ## Your first script Start with a script that opens a session and lists the domains currently available on the device: ```ts title="agent-sdk-first-script.ts" import { createAgentClient } from '@rozenite/agent-sdk'; async function inspectApp() { const client = createAgentClient(); return client.withSession(async (session) => { const domains = await session.domains.list(); return { sessionId: session.id, domains: domains.map((domain) => domain.id), }; }); } inspectApp().then(console.log).catch(console.error); ``` `withSession(...)` opens the session and closes it automatically when your callback finishes. The next step is to inspect one domain more closely and then call one of its tools. ## Inspecting domains and tools Inside a session, you can use these helpers: - `session.domains.list()` to see which domains are available on the connected app - `session.tools.list({ domain })` to see the tools inside one domain - `session.tools.getSchema({ domain, tool })` to inspect a tool's input schema before calling it For example, you can inspect the `network` domain like this: ```ts title="agent-sdk-inspect-tools.ts" import { createAgentClient } from '@rozenite/agent-sdk'; async function inspectNetworkTools() { const client = createAgentClient(); return client.withSession(async (session) => { const tools = await session.tools.list({ domain: 'network' }); const schema = await session.tools.getSchema({ domain: 'network', tool: 'listRequests', }); return { toolNames: tools.map((tool) => tool.shortName), listRequestsInput: schema.inputSchema, }; }); } ``` Once you know what you want to call, use `session.tools.call(...)`. ## Calling tools There are two common ways to call a tool. ### Call by name Use this form when you already know the domain and tool name, or when you just looked them up with `session.tools.list(...)`. If you want a typed result, pass type arguments to `session.tools.call(...)`. ```ts title="agent-sdk-call-by-name.ts" const requests = await session.tools.call<{ limit: number }, { items: Array<{ id: string }> }>({ domain: 'network', tool: 'listRequests', args: { limit: 20 }, }); ``` If you do not pass type arguments, the result is `unknown`. ### Typed calls with plugin SDKs Official agent-enabled plugins publish ready-made typed tool descriptors under `./sdk`. Use those when you want the plugin package to provide the tool name, argument type, and result type for you. Today that includes: - `@rozenite/controls-plugin/sdk` - `@rozenite/file-system-plugin/sdk` - `@rozenite/network-activity-plugin/sdk` - `@rozenite/react-navigation-plugin/sdk` - `@rozenite/redux-devtools-plugin/sdk` - `@rozenite/storage-plugin/sdk` - `@rozenite/tanstack-query-plugin/sdk` ```ts title="agent-sdk-typed-call.ts" import { createAgentClient } from '@rozenite/agent-sdk'; import { storageTools } from '@rozenite/storage-plugin/sdk'; async function readUsernameFromStorage() { const client = createAgentClient(); return client.withSession(async (session) => { const result = await session.tools.call(storageTools.readEntry, { adapterId: 'mmkv', storageId: 'user-storage', key: 'username', }); return result; }); } ``` :::tip Prefer the built-in `network` domain when it is available. Reach for `@rozenite/network-activity-plugin/sdk` when you need the typed fallback plugin surface on apps where the built-in `network` domain is missing or unavailable. ::: :::tip If a plugin only mounts after you navigate to one of its screens, do that first and then refresh the session view with `session.domains.list()` or `session.tools.list(...)` before calling its tools. ::: ## Pagination Some tools return paged results. The SDK makes one tool call and returns the plugin's page and cursor unchanged. To fetch another page, pass the returned cursor explicitly in the next call. ```ts title="agent-sdk-pagination.ts" type RequestsPage = { items: Array<{ id: string }>; page: { limit: number; hasMore: boolean; nextCursor?: string }; }; const firstPage = await session.tools.call<{ limit: number; cursor?: string }, RequestsPage>({ domain: 'network', tool: 'listRequests', args: { limit: 50 }, }); const secondPage = firstPage.page.nextCursor ? await session.tools.call<{ limit: number; cursor?: string }, RequestsPage>({ domain: 'network', tool: 'listRequests', args: { limit: 50, cursor: firstPage.page.nextCursor }, }) : undefined; ``` ## Choosing a target If only one target is connected, `withSession(...)` is usually enough. If more than one target is connected, list them first and pass `deviceId` when opening the session: ```ts title="agent-sdk-target-selection.ts" import { createAgentClient } from '@rozenite/agent-sdk'; async function inspectSpecificDevice() { const client = createAgentClient(); const targets = await client.targets.list(); return client.withSession({ deviceId: targets[0].id }, async (session) => { return { sessionId: session.id, deviceId: session.info.deviceId, }; }); } ``` ## Managing sessions - Use `withSession(...)` for short scripts and one-off tasks. It handles setup and cleanup for you. - Use `openSession()` when the session needs to stay open across multiple independent steps or function boundaries. - Use `attachSession(sessionId)` when you need to reconnect to an already-existing session instead of creating a new one. When you do need manual control, remember to stop the session yourself: ```ts title="agent-sdk-manual-session.ts" import { createAgentClient } from '@rozenite/agent-sdk'; async function inspectRozeniteManually() { const client = createAgentClient(); const session = await client.openSession(); try { const domains = await session.domains.list(); return { session: session.info, domains, }; } finally { await session.stop(); } } ``` If you already have a `sessionId`, you can reconnect like this: ```ts title="agent-sdk-attach-session.ts" import { createAgentClient } from '@rozenite/agent-sdk'; async function reconnectToSession(sessionId: string) { const client = createAgentClient(); const session = await client.attachSession(sessionId); return { sessionId: session.id, deviceId: session.info.deviceId, }; } ``` ## Next steps - [Rozenite for Agents](/docs/agent/overview.md) – how agents use domains and tools - [Adding tools to your application](/docs/agent/adding-tools-to-your-application.md) – expose app-owned tools under the `app` domain - [Plugin Development](/docs/plugin-development/overview.md) – general plugin structure and app-side integration --- url: /docs/agent/tap.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # Tap `rozenite agent tap` connects to a device and prints Rozenite plugin messages as they flow, in both directions, without opening a browser or React Native DevTools. The full loop for checking that a plugin's native side behaves — Metro, a simulator, the app, a browser running React Native DevTools, the shell, and the panel — is a lot of overhead just to watch a handful of messages when the thing you're testing lives on the device. `tap` skips the browser half entirely. :::warning Experimental Rozenite for Agents (including `tap`) is experimental and may change. If you run into a bug, please [open an issue](https://github.com/callstackincubator/rozenite/issues). ::: ## Watch a plugin's traffic ```bash npx rozenite agent tap --session --plugin @acme/sqlite-plugin ``` ``` ← 12:04:31.220 sqlite:list-tables-request {"requestId":"a1"} → 12:04:31.244 sqlite:list-tables-response {"requestId":"a1","tables":["users","posts"]} ``` Each line is a direction arrow, a local timestamp, the message type, and the JSON payload: - `←` — received from the device. - `→` — sent to the device. Omit `--plugin` to watch every plugin's traffic on the session. `tap` keeps running until you stop it with Ctrl-C, closing the underlying connection cleanly. ## Poke a plugin and watch what comes back Because the stream is bidirectional, `tap` doubles as a minimal stand-in for DevTools: send one message from the terminal, then keep watching for whatever comes back. ```bash npx rozenite agent tap --session --plugin @acme/sqlite-plugin --type sqlite:query --payload '{"sql":"select 1"}' ``` `--plugin`, `--type`, and `--payload` map directly onto the three fields of the message sent to the device (`pluginId`, `type`, `payload`). `--payload` defaults to `{}` and is validated as JSON on its own, so a malformed payload is reported against `--payload`, not against a blended argument. `--type` requires `--plugin`, since a message needs to know which plugin it's addressed to. `--plugin` also filters the stream — `--type` does not, since filtering on it would hide the very response your poke is meant to surface. ## Machine-readable output Pass `--json` for newline-delimited JSON, one message per line, so another program or agent can consume the stream directly instead of screenshotting a terminal: ```bash npx rozenite agent tap --session --json ``` ```json { "direction": "in", "timestamp": 1700000000000, "pluginId": "@acme/sqlite-plugin", "type": "list-tables-request", "payload": { "requestId": "a1" } } ``` Each line has this shape: | Field | Description | | ----------- | ------------------------------------------------------------------ | | `direction` | `"in"` (received from the device) or `"out"` (sent to the device). | | `timestamp` | Milliseconds since the Unix epoch. | | `pluginId` | The plugin the message belongs to. | | `type` | The message type. | | `payload` | The message payload, unmodified. | ## Options - `-s, --session ` — target Agent session ID (required). Create one with `npx rozenite agent session create`. - `-p, --plugin ` — filter the stream to this plugin ID; required together with `--type`. - `-t, --type ` — send one message of this type before watching. Requires `--plugin`. - `-a, --payload ` — JSON payload for the sent message. Defaults to `{}`. - `--json` — newline-delimited JSON output instead of the human-readable format. - `--host ` / `--port ` — Metro host and port, inherited from `rozenite agent`, so they go before `tap`: `npx rozenite agent --host 192.168.1.10 --port 8082 tap --session ` (defaults to `127.0.0.1:8081`). ## `tap` replaces React Native DevTools A device serves only one debugger connection at a time. `tap` rides the same connection `rozenite agent` uses, so starting a tap — like starting an agent session — disconnects React Native DevTools if it's already attached, and a tap keeps that same device from being opened in DevTools while it's running. This matches the point of `tap` (working entirely from the terminal), but it will surprise you if you don't expect it. Stop the tap (Ctrl-C) to free the connection back up. ## Next steps - [Rozenite for Agents](/docs/agent/overview.md) — the wider agent-facing workflow `tap` is part of. - [Agent SDK](/docs/agent/sdk.md) — build custom tooling on top of the same session `tap` uses. --- url: /docs/standalone-app.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # Standalone App :::warning Experimental The standalone app is an experiment in alternative DevTools workflows. It may change in future releases. ::: The standalone Rozenite app runs your plugin panels in their own window instead of inside React Native DevTools. It connects straight to the device, so it gives you the plugins you installed and nothing else. ## Before you start You need to install the optional `@rozenite/electron-app` package separately: ```sh [npm] npm install -D @rozenite/electron-app ``` ```sh [yarn] yarn add -D @rozenite/electron-app ``` ```sh [pnpm] pnpm add -D @rozenite/electron-app ``` ```sh [bun] bun add -D @rozenite/electron-app ``` ```sh [deno] deno add -D npm:@rozenite/electron-app ``` If you skip this step, `rozenite open` tells you to install it when you run the command. ## Why use it The standalone app is for two situations. **You mostly use Rozenite.** If your debugging happens in Rozenite panels rather than in React Native DevTools' built-in tools, the standalone app gives you those panels and nothing else — one window, the plugins you installed, no tab to hunt for. **You're on another target.** The app connects straight to the device rather than going through React Native DevTools, so it works the same way whichever target you're debugging. Connecting directly has a useful side effect: your panels aren't torn down when the app's JS VM reloads, so panel state survives a Fast Refresh, a crash or a manual reload. ## Launching it Run `rozenite open` in your project: ```sh [npx] npx rozenite open ``` ```sh [pnpm] pnpm dlx rozenite open ``` ```sh [yarn] yarn dlx rozenite open ``` ```sh [bunx] bunx rozenite open ``` This lists all devices currently connected to your dev server and opens the standalone app in an Electron window for the one you pick. Both supported integrations are looked up: React Native's Metro on port `8081` and Lynx's dev server on port `3000`. Whichever ones are running contribute their devices to the list, and each entry is labelled with the integration it belongs to, so a project running both is picked apart at the prompt. A port that isn't listening is simply skipped. ### Options - `--host ` — Dev server host to connect to (default `127.0.0.1`). - `--port ` — A single dev server port to look at. Without it, both `8081` and `3000` are scanned. - `--deviceId ` — Open a specific device directly, skipping the picker prompt. **Examples:** ```sh [npx] npx rozenite open --deviceId ``` ```sh [pnpm] pnpm dlx rozenite open --deviceId ``` ```sh [yarn] yarn dlx rozenite open --deviceId ``` ```sh [bunx] bunx rozenite open --deviceId ``` ```sh [npx] npx rozenite open --host 192.168.1.10 --port 8081 ``` ```sh [pnpm] pnpm dlx rozenite open --host 192.168.1.10 --port 8081 ``` ```sh [yarn] yarn dlx rozenite open --host 192.168.1.10 --port 8081 ``` ```sh [bunx] bunx rozenite open --host 192.168.1.10 --port 8081 ``` Pass `--port` when your dev server listens somewhere other than its default — it then queries that port only, instead of scanning both defaults. `rozenite open` requires an interactive terminal — it opens an Electron window and (without `--deviceId`) prompts you to pick a device, so it refuses to run in CI or a piped/non-TTY shell. ## Limitations - **A dev server must be running.** `rozenite open` discovers devices through the dev server's own device list, and the app itself is served by that dev server. If it stops after you've opened the app, the app shows a "dev server unreachable" state until it is back. - **It competes for the single debugger slot.** The standalone app connects directly to the device the same way React Native DevTools and `rozenite agent` do. Only one of these can be attached to a given device at a time — the last one to connect wins, disconnecting whichever was attached before it. ## Troubleshooting **"Could not launch the Rozenite standalone app. It requires `@rozenite/electron-app` to be installed in your project."** This error means you haven't installed the optional `@rozenite/electron-app` package. Go back to [Before you start](#before-you-start) and install it, then try `rozenite open` again. --- url: /docs/plugin-development/overview.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # Plugin Development Overview Plugins add new panels to React Native DevTools through Rozenite — custom debugging tools, performance monitors, and development utilities tailored to your app. ## How plugins work A plugin has two parts that talk to each other over a type-safe, event-based bridge: 1. **App side** — code that runs in your app. Its entry point is `react-native.ts`, on every target Rozenite supports. 2. **DevTools side** — the panel UI shown in DevTools. Changes on either side are reflected on the other in real time, and both sides can send data or commands. ## Plugin structure ``` my-plugin/ ├── src/ │ └── hello-world.tsx # Your DevTools panels ├── react-native.ts # App-side entry point ├── rozenite.config.ts # Plugin configuration ├── vite.config.ts # Build configuration ├── package.json # Dependencies and scripts └── tsconfig.json # TypeScript configuration ``` ## What you can build Anything that benefits from a live view into your running app: custom debugging tools, performance monitors, state inspectors, network tools, storage inspectors, or development-time analytics. To develop without wiring up a playground app first, `rozenite dev` opens an in-browser dev host where you can preview panels, read the message log, and dispatch commands — see the [Plugin Development guide](/docs/plugin-development/plugin-development.md#step-5-local-development-workflow). You can also define reusable presets and scripted dev flows in `rozenite.config.ts` to speed up local iteration. ## Getting started Ready to build one? Follow the [Plugin Development Guide](/docs/plugin-development/plugin-development.md) for a full walkthrough, or browse the [Official Plugins](/docs/official-plugins/overview.md) for examples of what's possible. ## Contributing We welcome contributions to both our maintained plugins and the community ecosystem. See the [Plugin Development Guide](/docs/plugin-development/plugin-development.md) to get started, or reach out to the community to discuss an idea. --- url: /docs/plugin-development/plugin-development.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # Plugin Development This guide will walk you through the complete process of creating a React Native DevTools plugin, from initial generation to building for production. > **Tip**: Before creating your own plugin, check out the [Official Plugins](/docs/official-plugins/overview.md) to see if there's already a plugin that meets your needs! ## Quick Start ```shell title="Terminal" npx rozenite generate cd my-awesome-plugin rozenite dev ``` ## Step 1: Generate Your Plugin The `rozenite generate` command creates a complete plugin project structure: ```shell title="Terminal" # Generate in current directory rozenite generate # Generate in specific directory rozenite generate my-plugin-name ``` This creates: - Complete TypeScript project setup - Vite build configuration with Rozenite plugin - Sample DevTools panel - Git repository with initial commit - All dependencies installed ## Step 2: Understanding Plugin Structure Your generated plugin has this structure: ``` my-plugin/ ├── src/ │ └── hello-world.tsx # Your DevTools panels ├── react-native.ts # App-side entry point ├── rozenite.config.ts # Plugin configuration ├── vite.config.ts # Build configuration ├── package.json # Dependencies and scripts └── tsconfig.json # TypeScript configuration ``` ## Step 3: Creating Panels Panels are React components that appear in the DevTools interface, defined in your `rozenite.config.ts` file. Your app side can use any API or library available in the app, so a panel can integrate as deeply with the runtime as you need. ### Type-safe communication The `RozeniteDevToolsClient` uses an event-based API with full TypeScript support — compile-time checks and autocomplete for event names and payloads, and one client per plugin ID so events from different plugins never collide. #### Client API ```typescript // Hook usage const client = useRozeniteDevToolsClient({ pluginId: 'your-plugin-id', }); // Client methods client.send('event-name', payload); // Send typed event client.onMessage('event-name', callback); // Listen for typed event client.close(); // Clean up connection ``` ```typescript title="rozenite.config.ts" export default { panels: [ { name: 'My Custom Panel', source: './src/my-panel.tsx', }, { name: 'Another Panel', source: './src/another-panel.tsx', }, ], }; ``` ### Dev Host Configuration `rozenite.config.ts` can also define helpers for the in-browser dev host that `rozenite dev` launches. ```typescript title="rozenite.config.ts" export default { panels: [ { name: 'Storage', source: './src/storage-panel.tsx', }, ], dev: { presets: [ { name: 'Get snapshot', type: 'get-snapshot', payload: { target: 'all' }, }, ], flows: [ { name: 'Initialize', autoRun: true, async run({ send, waitForMessage }) { await waitForMessage({ type: 'get-snapshot', direction: 'in' }); send('snapshot', { items: [] }); }, }, ], }, }; ``` - `dev.presets` adds ready-made command payloads to the **Presets** button in the Actions pane. Use presets when you want to quickly re-send common messages while iterating on your panel. - `dev.flows` adds runnable scripts to the **Flows** tab in the Actions pane. Use flows for small test routines like bootstrapping state, waiting for a request, or simulating a multi-step exchange. - Set `autoRun: true` on a flow when it should start automatically after the panel iframe loads. This is useful for initialization routines that should begin listening immediately. - These helpers are for the dev host workflow. They do not change the production plugin manifest. ### Panel Configuration Options | Property | Type | Description | | -------- | -------- | -------------------------------- | | `name` | `string` | Display name in DevTools sidebar | | `source` | `string` | Path to your React component | ### Declaring Supported Integrations `rozenite.config.ts` can also declare which environments your plugin supports: ```typescript title="rozenite.config.ts" export default { panels: [ { name: 'My Custom Panel', source: './src/my-panel.tsx', }, ], integrations: ['react-native'], }; ``` The valid ids are `react-native`, `react-native-web`, `lynx`, and `lynx-web`. Omitting `integrations` defaults to `['react-native']`, the safe assumption for a plugin that predates this field. Declaring an id no integration reports yet (currently `lynx-web`) is harmless — it just means nothing refuses to load on it today. An unknown id fails the build. To catch one while you type it instead, annotate the config: ```typescript title="rozenite.config.ts" import type { RozeniteConfig } from '@rozenite/vite-plugin'; export default { panels: [ { name: 'My Custom Panel', source: './src/my-panel.tsx', }, ], integrations: ['react-native'], } satisfies RozeniteConfig; ``` Declare an integration only if the plugin's **device-side** code actually runs there. It is the imports that decide this, not the plugin's own logic: a panel that never touches a native API still cannot declare `react-native-web` if the hook it ships imports `TurboModuleRegistry` or `DevSettings`, neither of which `react-native-web` provides. ### Creating a Panel Component Create a new panel by adding a React component: ```typescript title="src/my-panel.tsx" import React from 'react'; export default function MyPanel() { return (

My Custom Panel

This is my custom DevTools panel!

); } ``` ### Using the Plugin Bridge Connect your panel to React Native using the plugin bridge. The `RozeniteDevToolsClient` provides full TypeScript support for type-safe communication: ```typescript title="src/my-panel.tsx" import React, { useEffect, useState } from 'react'; import { useRozeniteDevToolsClient } from '@rozenite/plugin-bridge'; // Define type-safe event map interface PluginEvents { 'user-data': { id: string; name: string; email: string; }; 'request-user-data': { type: 'userInfo'; }; } export default function MyPanel() { const client = useRozeniteDevToolsClient({ pluginId: 'my-user-panel', }); const [userData, setUserData] = useState( null ); useEffect(() => { if (!client) return; // Type-safe message listener const subscription = client.onMessage('user-data', (data) => { // TypeScript knows data is PluginEvents['user-data'] setUserData(data); }); // Type-safe message sending client.send('request-user-data', { type: 'userInfo' }); return () => subscription.remove(); }, [client]); if (!client) { return
Connecting to React Native...
; } return (

User Data Panel

{userData ? (

Name: {userData.name}

Email: {userData.email}

) : (

Loading user data...

)}
); } ``` ## Step 4: App-Side Integration Add app-side functionality by creating a `react-native.ts` file. The filename is the same on every target Rozenite supports. On React Native you can reach for React Native APIs and libraries: ```typescript title="react-native.ts" import { DevToolsPluginClient } from '@rozenite/plugin-bridge'; import { Platform, Dimensions } from 'react-native'; // Use the same type-safe event map interface PluginEvents { 'user-data': { id: string; name: string; email: string; }; 'request-user-data': { type: 'userInfo'; }; } export default function setupPlugin( client: DevToolsPluginClient ) { // Handle messages from DevTools panels with full type safety client.onMessage('request-user-data', (data) => { // Access React Native APIs const deviceInfo = { platform: Platform.OS, version: Platform.Version, dimensions: Dimensions.get('window'), }; // Send type-safe response client.send('user-data', { id: 'user-123', name: 'John Doe', email: 'john@example.com', }); }); } ``` ## Step 5: Local Development Workflow ### Complete Development Setup For local plugin development, follow these steps: #### Step 1: Create and Start Your Plugin ```shell title="Terminal" # Create a new plugin rozenite generate cd my-awesome-plugin # Start the development server rozenite dev ``` This starts a development server that: - Watches for file changes - Hot reloads your panels automatically - Opens the **Rozenite dev host** in your browser (see below) - Provides real-time feedback during development #### Step 2: Develop panels in the browser (no playground app) `rozenite dev` uses [`@rozenite/vite-plugin`](https://www.npmjs.com/package/@rozenite/vite-plugin) to serve a **dev host** at the root of the dev server (by default **[http://localhost:8888/](http://localhost:8888/)**). You can iterate on DevTools panels **without** running a separate playground app: - **Panel preview** — Every entry in `rozenite.config.ts` appears as a tab. The selected panel loads inside an iframe, similar to how it is embedded in React Native DevTools. - **Message log** — Outbound messages from your panel (the same `rozenite-message` envelope the plugin bridge uses when talking to the parent) are listed with timestamps so you can see what the panel emitted. - **Dispatch message** — Send a command `type` and JSON `payload` into the iframe as if DevTools had sent it. The host fills in `pluginId` from your package **`name`** in `package.json`. That value must match the `pluginId` you pass to `useRozeniteDevToolsClient` / `getRozeniteDevToolsClient`; otherwise your handlers will not run. - **Presets** — Any `dev.presets` entries from `rozenite.config.ts` appear in the Actions pane so you can populate common command and payload combinations with one click. - **Flows** — Any `dev.flows` entries appear in a dedicated Flows tab so you can run repeatable dev routines against the panel iframe. Flows with `autoRun: true` start automatically when the preview reloads. The dev server port is aligned with Rozenite **runtime dev mode**: when you set `ROZENITE_DEV_MODE` to your plugin package name, the app loads the plugin from **[http://localhost:8888](http://localhost:8888)**, so one `rozenite dev` process can serve both the in-browser host and the in-app plugin bundle. Use this flow for rapid UI work and bridge message shapes. To exercise **`react-native.ts`** and native integration, continue with a real app (next steps). #### Step 3: Link to a React Native app (optional, for native side) 1. **Create or use a React Native project** that has Rozenite configured (for example the repository playground app). 2. **Add your plugin to the app's dependencies** (you can use `npm link`, `yarn link`, or `pnpm link` for local development). #### Step 4: Run your React Native app ```shell title="Terminal" # In your playground project directory # Set ROZENITE_DEV_MODE to your plugin package name (from package.json) to load it in dev mode ROZENITE_DEV_MODE=@scope/my-awesome-plugin npx react-native start # Or if using Expo ROZENITE_DEV_MODE=@scope/my-awesome-plugin npx expo start ``` Then run the app on your device or simulator. #### Step 5: Open DevTools 1. Open React Native DevTools in your browser 2. Your plugin panels should appear in the sidebar automatically ### Hot Reloading Your development workflow supports automatic hot reloading: - **Panel changes**: Your DevTools panels will automatically update when you make changes to your plugin code - **React Native integration changes**: Changes to your `react-native.ts` file will also hot reload - **New panels**: If you add a new panel to your `rozenite.config.ts`, restart React Native DevTools by pressing `Ctrl+R` (or `Cmd+R` on Mac) - **Configuration changes**: Most changes to `rozenite.config.ts` require a DevTools restart ### Checking Your Plugin By Hand 1. Make changes to your panel components - they should update instantly 2. Modify your React Native integration code - changes should be reflected immediately 3. Add new panels - remember to restart DevTools with `Ctrl+R` 4. Test communication between your panels and React Native code To cover the last one with automated tests instead - running your panel and your `react-native.ts` code against each other in Node, without Metro or a simulator - see [Testing](/docs/plugin-development/testing.md). ## Step 6: Building for Production Build your plugin for distribution: ```shell title="Terminal" rozenite build ``` This creates optimized bundles: - DevTools panels (minified and optimized) - App-side entry point (if `react-native.ts` exists) - Ready for distribution ### Build Output The build creates a `dist/` directory with: - `*.js` - Individual DevTools panel files (one file per panel, names reflect your config) - `react-native.js` - app-side integration (if applicable) - `rozenite.json` - Plugin manifest with metadata and configuration - Source maps for debugging ## Next Steps - Write tests for your plugin's communication with [Testing](/docs/plugin-development/testing.md) - Check out [Official Plugins](/docs/official-plugins/overview.md) to see available plugins - Join the community to share your plugins and get help --- url: /docs/plugin-development/rpc.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # RPC The `RozeniteDevToolsClient` you get from `getRozeniteDevToolsClient` (see the [Plugin Development Guide](/docs/plugin-development/plugin-development.md)) is fire-and-forget: `send(type, payload)` and `onMessage(type, listener)`. That's the right tool for events, but it isn't an answer to "call this and give me a result." Every plugin that needs a result back from the other side ends up reinventing the same thing by hand: a correlation id, a matching response event type, a `Promise` stored in a map, and an ad-hoc timeout. And none of that hand-rolled scaffolding protects you from the case that actually causes support tickets — the panel isn't mounted yet, or the device peer has died, and your caller just hangs forever with no `Promise` ever settling. `@rozenite/plugin-bridge` ships `createRozeniteRpc` so you don't have to build this yourself. It gives you an awaitable call with acknowledgement, heartbeats, timeouts, and cancellation on top of your existing client. ```typescript import { getRozeniteDevToolsClient, createRozeniteRpc } from '@rozenite/plugin-bridge'; type MyMethods = { readFile: (params: { path: string }) => Promise; listDevices: () => Promise; }; const client = await getRozeniteDevToolsClient('your-plugin-id'); const rpc = createRozeniteRpc(client); ``` The abstraction is **symmetric**: both the device and the panel can register handlers with `handle()` and call methods on the same `rpc` instance. ## Reserved message type RPC rides on top of your existing `RozeniteDevToolsClient` — there's no separate channel to set up. It reserves a single message type, `'rozenite:rpc'`, for its own use. Don't use `'rozenite:rpc'` as an event type in your own plugin's event map, or it will collide with the RPC layer. ## Declaring methods Methods are declared function-shaped, as in the example above, so params and result are both inferred from a single type. A method that takes no params is declared with no arguments. ## Registering a handler ```typescript rpc.handle('readFile', async ({ path }) => { return fs.readFileSync(path, 'utf8'); }); ``` Registering a second handler for the same method throws immediately, at registration time — a method has exactly one handler on a given peer. ## Calling a method Calling is a two-step handle: `method()` names the method and takes the call's options, and `invoke()` takes the params. ```typescript await rpc.method('readFile').invoke({ path: 'app.log' }); await rpc.method('listDevices').invoke(); await rpc.method('readFile', { timeoutMs: 60_000 }).invoke({ path }); ``` This is the only call form. A handle holds nothing but the method name and its options — no subscription, no state — so creating one per call is free, and reusing one across multiple calls is equally fine: ```typescript const readFile = rpc.method('readFile', { timeoutMs: 60_000 }); await readFile.invoke({ path: 'a' }); await readFile.invoke({ path: 'b' }); ``` ## Why a single timeout isn't enough A single constant timeout is the wrong tool here: a legitimately slow handler would trip it, while a handler that's actually gone is indistinguishable from one that's just slow. RPC splits **liveness** from **duration** — the receiver acknowledges a request immediately, then sends heartbeats while it executes. "Slow but alive" and "gone" become different, observable states. ### The three timers and their defaults Every `invoke()` call is guarded by three independent, caller-side timers: | Option | Default | Window | Fires when | | ---------------- | -------------------------- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `ackTimeoutMs` | `5_000` | `invoke()` → acknowledged | Nobody is listening, or the peer isn't mounted yet. This is the **only** retryable failure — `ACK_TIMEOUT`. | | `staleTimeoutMs` | `6_000` (3× `heartbeatMs`) | since the last sign of life | The peer died, or its event loop is blocked. `STALLED`. | | `timeoutMs` | `30_000` | `invoke()` → settle | Absolute cap. Catches a handler that never resolves — it would otherwise heartbeat forever. `TIMEOUT`. Pass `Infinity` to opt out. | `heartbeatMs` (default `2_000`) is set by the caller via `method()`'s options; it controls how often the receiver sends a heartbeat while handling that call. ### Retries `retries` (default `1`) applies **only** to `ACK_TIMEOUT` — the one failure where the handler provably never ran. `STALLED` and `TIMEOUT` never retry, because by the time either fires the handler may already have committed side effects, and a handler error never retries by construction. Because the retry budget is consumed before `timeoutMs` is re-armed for the retried attempt, `retries: 1` can cost up to `ackTimeoutMs + timeoutMs` in the worst case: the first attempt burns a full `ackTimeoutMs` before giving up, and the retry then gets its own full `timeoutMs` window. ## Cancellation Pass an `AbortSignal` to cancel a call in flight. Every caller-side give-up — `STALLED`, `TIMEOUT`, or your own `AbortSignal` — aborts the handler's `ctx.signal` on the other side, so long-running work can stop instead of running to no purpose: ```typescript rpc.handle('longRunning', async (params, ctx) => { for await (const chunk of source) { if (ctx.signal.aborted) { throw new Error('aborted'); } // ... } }); ``` ```typescript const controller = new AbortController(); const promise = rpc .method('longRunning', { signal: controller.signal }) .invoke(); controller.abort(); // -> rejects with a CANCELLED error, aborts ctx.signal ``` The caller rejects immediately on cancellation and drops any late result that arrives afterwards — a handler that settles after cancellation is silently discarded, with no error and no dangling frame. ## Known limitation: heartbeats only prove the event loop is alive A heartbeat proves the peer's **event loop** is alive, not that the handler is making progress. Synchronous work blocks the heartbeat timer too, so a 10-second synchronous loop on either side looks exactly like a dead peer. This design cannot detect a synchronous long-running block — make sure `staleTimeoutMs` comfortably exceeds the longest synchronous stretch you expect on either side, and raise it per-call for handlers you know will block. ## Errors Errors come in two shapes, discriminated by `kind`: ```typescript export type RozeniteRpcError = RozeniteProtocolError | RozeniteHandlerError; ``` - **`RozeniteProtocolError`** (`kind: 'protocol'`) — the call itself failed, not your handler. `error.code` is one of: - `ACK_TIMEOUT` — nobody acknowledged the request in time; the handler never ran. - `STALLED` — the peer stopped sending any sign of life. - `TIMEOUT` — the call didn't settle within the absolute cap. - `CANCELLED` — the caller's `AbortSignal` fired. - `METHOD_NOT_FOUND` — no handler is registered for that method on the peer. - `SERIALIZATION_ERROR` — the result (or error `data`) couldn't survive the transport. - `CLIENT_CLOSED` — `close()` was called while the call was in flight. - **`RozeniteHandlerError`** (`kind: 'handler'`) — the remote handler ran and threw. `error.message` is `` `${method} failed: ${remote.message}` `` and `error.stack` is **your own** call stack, so you always see who invoked the call. The remote's own `name`, `message`, `stack` (development builds only), and any handler-supplied `data` live under `error.remote`. Narrow on `kind`, not `instanceof` — a plugin's device code and panel code are separate bundles, so `instanceof` only happens to work when the error was constructed by the bundle doing the check. Use the exported type guards instead: ```typescript import { isProtocolError, isHandlerError } from '@rozenite/plugin-bridge'; try { await rpc.method('readFile').invoke({ path }); } catch (error) { if (isProtocolError(error)) { // error.code: ACK_TIMEOUT | STALLED | TIMEOUT | CANCELLED | // METHOD_NOT_FOUND | SERIALIZATION_ERROR | CLIENT_CLOSED } else if (isHandlerError(error)) { // error.remote.name / error.remote.message / error.remote.data } } ``` ## API reference ```typescript const rpc = createRozeniteRpc(client); rpc.method(method, options?): RpcMethodHandle; rpc.method(method, options?).invoke(params?): Promise; rpc.handle(method, handler): Subscription; rpc.close(): void; ``` - `InvokeOptions.signal?: AbortSignal` - `InvokeOptions.ackTimeoutMs?: number` — default `5_000` - `InvokeOptions.heartbeatMs?: number` — default `2_000` - `InvokeOptions.staleTimeoutMs?: number` — default `6_000` - `InvokeOptions.timeoutMs?: number` — default `30_000`, pass `Infinity` to opt out - `InvokeOptions.retries?: number` — default `1`, `ACK_TIMEOUT` only Calling `close()` removes the underlying message subscription and rejects every in-flight call with `CLIENT_CLOSED`. --- url: /docs/plugin-development/testing.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # Testing Running your plugin end to end — Metro, a simulator, the app, and React Native DevTools — is the right way to verify how a panel looks and feels. It is a slow way to answer the question you ask far more often: do the panel and the `react-native.ts` side still agree on the messages they exchange? `@rozenite/testing` answers that one in milliseconds. It gives you both ends of an in-memory channel, so your real panel code and your real React Native code run against each other in Node — no Metro, no simulator, no DevTools. ## Installation Install it as a dev dependency alongside `@rozenite/plugin-bridge`: ```shell title="Terminal" npm install --save-dev @rozenite/testing @rozenite/plugin-bridge ``` The package works with any test runner — Vitest, Jest, or `node --test`. It brings no runner globals of its own, so you keep using your runner's assertions. To render panel components you also need React and a renderer such as [React Testing Library](https://testing-library.com/docs/react-testing-library/intro), plus a DOM environment (`jsdom` or `happy-dom`) configured in your runner. The message-level and RPC tests below need neither. ## Test the messages between both sides `connectFakePair()` returns two ends of one channel: `device` and `panel`. Whatever one end sends arrives at the other. Hand each end to a client with the `channel` option, and both sides run their real communication code. ```typescript title="src/__tests__/cache-inspector.test.ts" import { getRozeniteDevToolsClient } from '@rozenite/plugin-bridge'; import { connectFakePair, waitForMessage } from '@rozenite/testing'; import { describe, expect, it } from 'vitest'; import { registerCacheHandlers } from '../react-native'; type CacheInspectorEvents = { 'cache-entries-request': Record; 'cache-entries': { entries: { key: string; sizeBytes: number }[] }; }; describe('cache inspector protocol', () => { it('answers a cache-entries-request with the current entries', async () => { const { device, panel } = connectFakePair(); const deviceClient = await getRozeniteDevToolsClient( '@acme/cache-inspector', { channel: device } ); const panelClient = await getRozeniteDevToolsClient( '@acme/cache-inspector', { channel: panel } ); // Your real device-side handlers, wired the way react-native.ts wires them. registerCacheHandlers(deviceClient, { entries: [{ key: 'user:42', sizeBytes: 1024 }], }); panelClient.send('cache-entries-request', {}); const response = await waitForMessage(panelClient, 'cache-entries', { timeoutMs: 1000, }); expect(response.entries).toEqual([{ key: 'user:42', sizeBytes: 1024 }]); deviceClient.close(); panelClient.close(); }); }); ``` Messages are always delivered asynchronously, exactly as they are on a device. A `send()` never reaches the other side's listener before the current tick finishes, so assert on what arrived with `await`, never straight after `send()`. Pass the same `pluginId` to both clients. Clients only receive messages addressed to their own plugin, so a mismatched id looks exactly like a message that never arrived. ## Test an RPC method [RPC methods](/docs/plugin-development/rpc.md) work over the same pair — register a handler on one side and call it from the other: ```typescript import { createRozeniteRpc } from '@rozenite/plugin-bridge'; type CacheMethods = { clearEntry: (params: { key: string }) => Promise<{ cleared: boolean }>; }; const deviceRpc = createRozeniteRpc(deviceClient); const panelRpc = createRozeniteRpc(panelClient); deviceRpc.handle('clearEntry', async ({ key }) => ({ cleared: cache.delete(key) })); const result = await panelRpc.method('clearEntry').invoke({ key: 'user:42' }); expect(result).toEqual({ cleared: true }); ``` Errors thrown by a handler travel back to the caller, so `expect(...).rejects` works on a failing call the same way it does in production. ## Test your panel component Panel components call `useRozeniteDevToolsClient({ pluginId })` themselves and take no channel prop — so wrap the component in `RozeniteChannelProvider` and give it one end of the pair. Every `useRozeniteDevToolsClient()` inside the provider uses that channel instead of connecting to DevTools, and the component under test stays exactly as it ships. ```tsx title="src/__tests__/cache-inspector-panel.test.tsx" import { getRozeniteDevToolsClient } from '@rozenite/plugin-bridge'; import { connectFakePair, RozeniteChannelProvider } from '@rozenite/testing'; import { render, screen } from '@testing-library/react'; import { expect, it } from 'vitest'; import CacheInspectorPanel from '../cache-inspector-panel'; it('lists the entries reported by the device', async () => { const { device, panel } = connectFakePair(); const deviceClient = await getRozeniteDevToolsClient( '@acme/cache-inspector', { channel: device } ); deviceClient.onMessage('cache-entries-request', () => { deviceClient.send('cache-entries', { entries: [{ key: 'user:42', sizeBytes: 1024 }], }); }); render( ); expect(await screen.findByText('user:42')).toBeTruthy(); deviceClient.close(); }); ``` `role` tells the provider which side of the protocol the subtree stands in for. Only the React Native side announces itself with a `plugin-mounted` message, so `role="panel"` keeps that message off the wire and `role="device"` puts it there. Set it whenever a test asserts on the exact messages exchanged. ## Test your React Native side If your React Native integration is a hook, render it the same way with the other end of the pair: ```tsx import { connectFakePair, RozeniteChannelProvider, waitForMessage } from '@rozenite/testing'; const { device, panel } = connectFakePair(); const panelClient = await getRozeniteDevToolsClient( '@acme/cache-inspector', { channel: panel } ); const CacheInspectorHost = () => { useCacheInspectorPlugin(); return null; }; render( ); panelClient.send('cache-entries-request', {}); const response = await waitForMessage(panelClient, 'cache-entries', { timeoutMs: 1000, }); ``` If your integration is a plain function that takes a client, skip the provider and pass it a client built with the `channel` option, as in the first example. ## Wait for a message Three helpers wait for something to arrive. Each takes a required `timeoutMs` and rejects with a `WaitForTimeoutError` when nothing matching shows up, so a broken protocol fails your test instead of hanging your suite. ```typescript import { waitForMessage, waitForChannelMessage, waitForRpcFrame, } from '@rozenite/testing'; // The next message of a given type, optionally filtered. const entries = await waitForMessage( panelClient, 'cache-entries', { timeoutMs: 1000 }, (payload) => payload.entries.length > 0 ); // The next raw message on a channel, before any client sorts it by plugin. const raw = await waitForChannelMessage(panel, (message) => message != null, { timeoutMs: 1000, }); // The next RPC frame, for asserting that a call was made at all. const frame = await waitForRpcFrame<{ kind: string; method: string }>( panelClient, (frame) => frame.kind === 'request' && frame.method === 'clearEntry', { timeoutMs: 1000 } ); ``` Start waiting before you trigger the exchange when the reply can be immediate: ```typescript const entries = waitForMessage(panelClient, 'cache-entries', { timeoutMs: 1000 }); panelClient.send('cache-entries-request', {}); await entries; ``` ## Simulate a slow or missing peer A pair can drop or delay messages in either direction, which is how you cover the cases that are painful to reproduce on a device — a panel that was never opened, or a device that answers slowly. ```typescript const { device, panel, dropDeviceToPanel, delayPanelToDevice } = connectFakePair(); // Nothing the device sends reaches the panel. dropDeviceToPanel(true); // The device hears the panel half a second late. delayPanelToDevice(500); ``` Both take effect immediately and stay in force until you change them, so you can drop messages part-way through a test and turn delivery back on with `dropDeviceToPanel(false)`. Delays use timers. If your test uses fake timers, advance them to let a delayed message through. ## When a test times out A `WaitForTimeoutError` means nothing matching arrived in time. The usual causes, in the order worth checking: - **The two clients use different plugin ids.** Messages are addressed by plugin id, and one that doesn't match is silently ignored. - **The message type or payload doesn't match your predicate.** Drop the predicate first to confirm the message arrives at all. - **The other side was never wired up.** Register handlers on the device client before the panel sends anything. - **A client was closed too early.** Closing a client also stops delivery for any other client sharing that same end of the pair. Give each side its own pair if they need independent lifetimes. - **A drop or delay is still in force** from an earlier step in the test. Raise `timeoutMs` last. It is rarely the answer: everything here runs in-process, so a message that hasn't arrived in a second is not on its way. ## What this doesn't cover These tests prove the two sides agree on the messages they exchange. They say nothing about how your panel looks, whether DevTools loads your plugin, or how your code behaves against a real device — for that, run the plugin in the [development workflow](/docs/plugin-development/plugin-development.md) and check it by hand before you release. --- url: /docs/official-plugins/overview.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # Official Plugins We maintain a set of plugins that cover the most common debugging needs, each one a thin bridge between a library you're probably already using and a DevTools panel. :::info Beta Rozenite and its official plugins are in beta. If something doesn't work, please [report it](https://github.com/callstackincubator/rozenite/issues) or [contribute a fix](https://github.com/callstackincubator/rozenite). ::: ## Available plugins - **[Expo Atlas](/docs/official-plugins/expo-atlas.md)** — visualize your Metro bundle to find large dependencies and duplicate code. - **[TanStack Query](/docs/official-plugins/tanstack-query.md)** — monitor and manage TanStack Query's cache and queries. - **[Network Activity Inspector](/docs/official-plugins/network-activity.md)** — a Chrome-DevTools-style network panel for your app's HTTP and WebSocket traffic. - **[Redux DevTools](/docs/official-plugins/redux-devtools.md)** — inspect state, actions, and diffs for a Redux store. - **[Performance Monitor](/docs/official-plugins/performance-monitor.md)** — track startup timing and custom performance marks. - **[Storage](/docs/official-plugins/storage.md)** — one panel for MMKV, AsyncStorage, and Expo SecureStore. - **[Feature Flags](/docs/official-plugins/feature-flags.md)** — inspect and override flags from a custom store, LaunchDarkly, or Statsig. - **[File System](/docs/official-plugins/file-system.md)** — browse app-accessible directories and preview file contents. - **[Controls](/docs/official-plugins/controls.md)** — build a custom panel of toggles, inputs, and buttons for your own app. - **[React Navigation](/docs/official-plugins/react-navigation.md)** — track navigation actions and inspect navigation state. - **[React Hook Form](/docs/official-plugins/react-hook-form.md)** — inspect field values, validation, and form state. - **[SQLite](/docs/official-plugins/sqlite.md)** — browse tables and run queries against your app's databases. - **[Overlay](/docs/official-plugins/overlay.md)** — grid and reference-image overlays for pixel-perfect UI work. - **[Require Profiler](/docs/official-plugins/require-profiler.md)** — a flame graph of your app's startup `require()` calls. ## Installing a plugin Each plugin installs as a dev dependency, since it's only needed during development: ```sh [npm] npm install -D @rozenite/network-activity-plugin ``` ```sh [yarn] yarn add -D @rozenite/network-activity-plugin ``` ```sh [pnpm] pnpm add -D @rozenite/network-activity-plugin ``` ```sh [bun] bun add -D @rozenite/network-activity-plugin ``` ```sh [deno] deno add -D npm:@rozenite/network-activity-plugin ``` Swap in the package name for the plugin you want — see its page for the exact setup steps, since most plugins also need a small hook added to your app. ## Community plugins Beyond what we maintain, the community builds and shares its own Rozenite plugins. They aren't officially supported, but many are worth a look for specific use cases. ## Building your own If none of the above fit, see the [Plugin Development guide](/docs/plugin-development/plugin-development.md) to build your own. --- url: /docs/official-plugins/expo-atlas.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # Expo Atlas Plugin ![](/expo-atlas-plugin.png) The Expo Atlas plugin brings [Expo Atlas](https://github.com/expo/expo-atlas) into React Native DevTools, so you can explore your Metro bundle, see what's taking up space, and trace dependencies without leaving DevTools. ## Installation Make sure to go through the [Getting Started guide](/docs/getting-started.md) before installing the plugin. ```sh [npm] npm install -D @rozenite/expo-atlas-plugin ``` ```sh [yarn] yarn add -D @rozenite/expo-atlas-plugin ``` ```sh [pnpm] pnpm add -D @rozenite/expo-atlas-plugin ``` ```sh [bun] bun add -D @rozenite/expo-atlas-plugin ``` ```sh [deno] deno add -D npm:@rozenite/expo-atlas-plugin ``` Update your Metro configuration to enable the plugin: ```javascript title="metro.config.js" const { withRozenite } = require('@rozenite/metro'); const { withRozeniteExpoAtlasPlugin } = require('@rozenite/expo-atlas-plugin'); const config = { // Your existing Metro configuration }; module.exports = withRozenite(config, { // Your Rozenite configuration enhanceMetroConfig: (config) => withRozeniteExpoAtlasPlugin(config), }); ``` ## Usage Once configured, "Expo Atlas" appears in your React Native DevTools sidebar. From there you can: - See total bundle size broken down by module and file type - Inspect an individual module's size, source, and dependency chain - Spot large dependencies, duplicated code, and unused code - Analyze your development bundle live, or export and analyze a production bundle **Next**: Explore other [Official Plugins](/docs/official-plugins/overview.md), or learn to build your own in the [Plugin Development guide](/docs/plugin-development/plugin-development.md). --- url: /docs/official-plugins/tanstack-query.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # TanStack Query Plugin ![](/tanstack-query-plugin.png) The TanStack Query plugin brings [TanStack Query DevTools](https://tanstack.com/query/latest/docs/react/devtools) into React Native DevTools, so you can inspect and manage your queries and cache without leaving the DevTools window. It was inspired by Austin Johnson's [react-query-external-sync](https://github.com/LovesWorking/react-query-external-sync). ## Installation Make sure to go through the [Getting Started guide](/docs/getting-started.md) before installing the plugin. ```sh [npm] npm install -D @rozenite/tanstack-query-plugin ``` ```sh [yarn] yarn add -D @rozenite/tanstack-query-plugin ``` ```sh [pnpm] pnpm add -D @rozenite/tanstack-query-plugin ``` ```sh [bun] bun add -D @rozenite/tanstack-query-plugin ``` ```sh [deno] deno add -D npm:@rozenite/tanstack-query-plugin ``` ```typescript title="App.tsx" import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { useTanStackQueryDevTools } from '@rozenite/tanstack-query-plugin'; const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 5 * 60 * 1000, // 5 minutes retry: 3, }, }, }); function App() { useTanStackQueryDevTools(queryClient); return ( {/* Your app components */} ); } ``` ## Web (React Native for Web) With [Rozenite for Web](/docs/targets/rozenite-for-web.md), this plugin is also available when debugging your React Native web app. ## Usage Once configured, "TanStack Query" appears in your React Native DevTools sidebar with the standard TanStack Query DevTools UI: live queries and mutations, cache inspection, and actions to refetch, invalidate, reset, or remove entries. **Next**: Learn about [Plugin Development](/docs/plugin-development/plugin-development.md), or explore other [Official Plugins](/docs/official-plugins/overview.md). --- url: /docs/official-plugins/react-navigation.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # React Navigation Plugin ![](/react-navigation-plugin.png) The React Navigation plugin brings navigation debugging to React Native DevTools for React Navigation v7: a live action timeline, navigation state at any point in time, and deep link testing. ## Installation Make sure to go through the [Getting Started guide](/docs/getting-started.md) before installing the plugin. ```sh [npm] npm install -D @rozenite/react-navigation-plugin ``` ```sh [yarn] yarn add -D @rozenite/react-navigation-plugin ``` ```sh [pnpm] pnpm add -D @rozenite/react-navigation-plugin ``` ```sh [bun] bun add -D @rozenite/react-navigation-plugin ``` ```sh [deno] deno add -D npm:@rozenite/react-navigation-plugin ``` ### With react-navigation ```typescript title="App.tsx" import React, { useRef } from 'react'; import { NavigationContainer } from '@react-navigation/native'; import { useReactNavigationDevTools } from '@rozenite/react-navigation-plugin'; function App() { const navigationRef = useRef(null); useReactNavigationDevTools({ ref: navigationRef }); return ( ); } ``` ### With expo-router ```typescript title="_layout.tsx" import { Stack, useNavigationContainerRef } from 'expo-router'; import { useReactNavigationDevTools } from '@rozenite/react-navigation-plugin'; function App() { const navigationRef = useNavigationContainerRef(); useReactNavigationDevTools({ ref: navigationRef }); return ; } ``` ## Web (React Native for Web) With [Rozenite for Web](/docs/targets/rozenite-for-web.md), this plugin is also available when debugging your React Native web app. ## Usage Once configured, "React Navigation" appears in your React Native DevTools sidebar with two main things: - **Action timeline** — every navigation action, in order, with the ability to jump back to any previous state or reset to it. - **Dispatch origin** — click any action to see where in your code it was dispatched from, including a code snippet and the full call stack. This only resolves in development builds; release builds show "symbolication unavailable" but keep the raw stack available. ## Agent Integration This plugin exposes agent tools under the `@rozenite/react-navigation-plugin` domain: `navigate` and `go-back` for routine navigation, plus lower-level tools (`get-root-state`, `get-focused-route`, `list-actions`, `reset-root`, `open-link`, `dispatch-action`) for anything `navigate`/`go-back` can't do. **Next**: Learn about [Plugin Development](/docs/plugin-development/plugin-development.md), or explore other [Official Plugins](/docs/official-plugins/overview.md). --- url: /docs/official-plugins/network-activity.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # Network Activity Plugin ![](/network-activity-plugin.png) The Network Activity plugin brings a Chrome-DevTools-style network inspector into React Native DevTools: every HTTP/HTTPS request, WebSocket connection, and Server-Sent Event your app makes, in real time. :::warning JavaScript thread only Built-in React Native inspectors only see traffic from the JavaScript thread. If your app uses `react-native-nitro-fetch`, install it alongside this plugin and its traffic shows up in the same panel. Other native networking stacks won't appear automatically. ::: ## Installation Make sure to go through the [Getting Started guide](/docs/getting-started.md) before installing the plugin. ```sh [npm] npm install -D @rozenite/network-activity-plugin ``` ```sh [yarn] yarn add -D @rozenite/network-activity-plugin ``` ```sh [pnpm] pnpm add -D @rozenite/network-activity-plugin ``` ```sh [bun] bun add -D @rozenite/network-activity-plugin ``` ```sh [deno] deno add -D npm:@rozenite/network-activity-plugin ``` ```typescript title="App.tsx" import { useNetworkActivityDevTools } from '@rozenite/network-activity-plugin'; function App() { useNetworkActivityDevTools(); return ; } ``` To also capture requests made before your app finishes initializing, add this to your entry point: ```typescript title="index.js" import { withOnBootNetworkActivityRecording } from '@rozenite/network-activity-plugin'; withOnBootNetworkActivityRecording(); ``` If your app uses nitro networking, install it too, and its HTTP and WebSocket traffic merges into the same panel automatically: ```bash npm install react-native-nitro-fetch ``` ## Usage Once configured, "Network Activity" appears in your React Native DevTools sidebar. The request list shows method, status, timing, and a `Built-in` / `Nitro` badge for where each request came from. Click any request for full details: headers, query parameters, and request/response bodies. ### Viewing responses The response body view adapts to the content type, with a Preview / Raw toggle when both are useful: - **Images and SVGs** render inline; other binary formats (PDF, zip, audio, video, fonts) show a byte-level view instead, since there's nothing to preview. - **JSON and XML** render as a collapsible, copyable tree, with the raw body pretty-printed in the Raw tab. - **HTML** renders in a sandboxed preview that can't make outbound requests, with the source available in Raw. - **Text formats** (plain text, CSS, JS) render as monospace text. - Every binary response has a **Download** button; very large or empty responses show a placeholder instead of a preview. ## Configuration By default all traffic types are monitored. Disable ones you don't need — useful when a type is noisy or expensive to capture: ```typescript title="App.tsx" useNetworkActivityDevTools({ inspectors: { http: true, websocket: false, sse: false, // requires http to be enabled }, }); ``` **Next**: Learn about [Plugin Development](/docs/plugin-development/plugin-development.md), or explore other [Official Plugins](/docs/official-plugins/overview.md). --- url: /docs/official-plugins/overlay.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # Overlay Plugin The Overlay plugin draws grids and reference images over your running app, so you can check spacing, alignment, and how closely your UI matches a design. It was inspired by [RocketSim](https://www.rocketsim.app/). ## Installation Make sure to go through the [Getting Started guide](/docs/getting-started.md) before installing the plugin. ```sh [npm] npm install -D @rozenite/overlay-plugin react-native-svg ``` ```sh [yarn] yarn add -D @rozenite/overlay-plugin react-native-svg ``` ```sh [pnpm] pnpm add -D @rozenite/overlay-plugin react-native-svg ``` ```sh [bun] bun add -D @rozenite/overlay-plugin react-native-svg ``` ```sh [deno] deno add -D npm:@rozenite/overlay-plugin npm:react-native-svg ``` Add the overlay component at the root of your app: ```typescript title="App.tsx" import { RozeniteOverlay } from '@rozenite/overlay-plugin'; function App() { return ( <> {/* Add the overlay component at the root level */} ); } ``` ## Web (React Native for Web) With [Rozenite for Web](/docs/targets/rozenite-for-web.md), this plugin is also available when debugging your React Native web app. ## Usage Once configured, "Overlay" appears in your React Native DevTools sidebar with two controls: ### Grid Toggle a grid over your app to check spacing and alignment. Adjust cell size (4–100px), line color, and opacity. Off by default; when enabled, starts at an 8px red grid at 70% opacity. ### Image overlay Overlay a reference image over your app to compare it against a design — either as a plain overlay or as a slider you drag to reveal each side. Adjust the image URL, resize mode (cover, contain, stretch, center), and opacity. Settings persist for your development session but aren't saved between app restarts. :::warning Positioning Place `RozeniteOverlay` at the root of your app, after everything else, so overlays render on top. ::: :::info Development only The overlay never renders in production builds. ::: **Next**: Explore other [Official Plugins](/docs/official-plugins/overview.md), or learn to build your own in the [Plugin Development guide](/docs/plugin-development/plugin-development.md). --- url: /docs/official-plugins/redux-devtools.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # Redux DevTools Plugin ![](/redux-devtools-plugin.png) The Redux DevTools plugin brings the familiar Redux DevTools experience into React Native DevTools: state inspection, action history, and state diffs for your store. ## Installation Make sure to go through the [Getting Started guide](/docs/getting-started.md) before installing the plugin. ```sh [npm] npm install -D @rozenite/redux-devtools-plugin ``` ```sh [yarn] yarn add -D @rozenite/redux-devtools-plugin ``` ```sh [pnpm] pnpm add -D @rozenite/redux-devtools-plugin ``` ```sh [bun] bun add -D @rozenite/redux-devtools-plugin ``` ```sh [deno] deno add -D npm:@rozenite/redux-devtools-plugin ``` Add the enhancer to your store: #### Redux Toolkit (recommended) ```typescript title="store.ts" import { configureStore } from '@reduxjs/toolkit'; import { rozeniteDevToolsEnhancer } from '@rozenite/redux-devtools-plugin'; import rootReducer from './reducers'; const store = configureStore({ reducer: rootReducer, enhancers: (getDefaultEnhancers) => getDefaultEnhancers().concat(rozeniteDevToolsEnhancer()), }); export default store; ``` #### Classic Redux ```typescript title="store.ts" import { createStore, applyMiddleware } from 'redux'; import { rozeniteDevToolsEnhancer } from '@rozenite/redux-devtools-plugin'; import rootReducer from './reducers'; const store = createStore( rootReducer, applyMiddleware(/* your middleware */), rozeniteDevToolsEnhancer(), ); export default store; ``` #### Rematch Follow the [Rematch documentation](https://rematchjs.org/docs/guides/devtools/) and use `composeWithRozeniteDevTools` as the `devtoolComposer`: ```typescript title="store.ts" import { init } from '@rematch/core'; import { composeWithRozeniteDevTools } from '@rozenite/redux-devtools-plugin'; export const store = init({ models: { // your models }, redux: { devtoolComposer: composeWithRozeniteDevTools(), }, }); export default store; ``` ## Web (React Native for Web) With [Rozenite for Web](/docs/targets/rozenite-for-web.md), this plugin is also available when debugging your React Native web app. ## Usage Once configured, "Redux DevTools" appears in your React Native DevTools sidebar. ### Multiple stores Give each store a distinct name so you can tell them apart in the panel: ```ts const appStoreEnhancer = rozeniteDevToolsEnhancer({ name: 'app-store' }); const sessionStoreEnhancer = rozeniteDevToolsEnhancer({ name: 'session-store' }); ``` ### Large stores State is serialized and sent to DevTools over the device bridge, so a large store combined with a lot of action history can use a lot of memory — on Android this can even crash the app when you open the panel. If your store is large (RTK Query caches, big entity maps), lower `maxAge` (default `50`) and strip bulky slices with `stateSanitizer` / `actionSanitizer`: ```ts rozeniteDevToolsEnhancer({ maxAge: 20, stateSanitizer: (state) => ({ ...(state as Record), api: '[omitted]', }), actionSanitizer: (action) => ({ ...action, payload: action.type === 'large/payload' ? '[omitted]' : action.payload, }), }); ``` ### Dispatch traces Set `trace: true` to capture where each action was dispatched from, symbolicated back to your source files through Metro: ```ts rozeniteDevToolsEnhancer({ trace: true, traceLimit: 25, }); ``` Pass `traceSymbolication: false` to keep raw stacks without the Metro round-trip. ## Agent Integration Agent tools are a separate, manual step — instrumenting your store with the enhancer doesn't register them on its own. Mount this once near your app root: ```tsx title="App.tsx" import { useReduxDevToolsAgentTools } from '@rozenite/redux-devtools-plugin'; function App() { useReduxDevToolsAgentTools(); return ; } ``` This registers curated tools under the `@rozenite/redux-devtools-plugin` domain for listing stores/actions, dispatching actions, and driving history (jump/rollback/commit) — rather than exposing raw eval-based commands. **Next**: Learn about [Plugin Development](/docs/plugin-development/plugin-development.md), or explore other [Official Plugins](/docs/official-plugins/overview.md). --- url: /docs/official-plugins/performance-monitor.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # Performance Monitor Plugin ![](/performance-monitor-plugin.png) The Performance Monitor plugin shows startup timing and performance marks in React Native DevTools. It's built on the [`react-native-performance`](https://github.com/oblador/react-native-performance) library, so anything you already mark or measure with it shows up automatically. ## Installation Make sure to go through the [Getting Started guide](/docs/getting-started.md) before installing the plugin. ```sh [npm] npm install -D @rozenite/performance-monitor-plugin react-native-performance ``` ```sh [yarn] yarn add -D @rozenite/performance-monitor-plugin react-native-performance ``` ```sh [pnpm] pnpm add -D @rozenite/performance-monitor-plugin react-native-performance ``` ```sh [bun] bun add -D @rozenite/performance-monitor-plugin react-native-performance ``` ```sh [deno] deno add -D npm:@rozenite/performance-monitor-plugin npm:react-native-performance ``` ```typescript title="App.tsx" import { usePerformanceMonitorDevTools } from '@rozenite/performance-monitor-plugin'; function App() { usePerformanceMonitorDevTools(); return ; } ``` ## Web (React Native for Web) With [Rozenite for Web](/docs/targets/rozenite-for-web.md), this plugin is also available when debugging your React Native web app. ## Usage Once configured, "Performance Monitor" appears in your React Native DevTools sidebar. ### Startup The first tab you see breaks down your app's launch time: native initialization, JS bundle parse/execute, and the first React render. It populates automatically as soon as you start a session — no instrumentation needed. A phase shows "In progress…" while it's still running, or "—" if your app's architecture doesn't report it. ### Custom marks and measures Add your own marks, measures, and metrics from anywhere in your app using `react-native-performance` directly — they show up in the panel in real time: ```typescript import performance from 'react-native-performance'; performance.mark('app-start'); performance.mark('data-loaded'); performance.measure('app-initialization', 'app-start', 'data-loaded'); performance.metric('custom-metric', 42, { detail: 'Additional info' }); ``` - **Marks** are named points in time — e.g. `user-login-complete`. - **Measures** are durations between two marks — e.g. the time between `login-start` and `login-end`. - **Metrics** are arbitrary values you want to track, e.g. `performance.metric('memory-usage', 1024, { unit: 'MB' })`. ### Sessions Start and stop a monitoring session from the panel, and export what it captured for further analysis. **Next**: Learn about [Plugin Development](/docs/plugin-development/plugin-development.md), or explore other [Official Plugins](/docs/official-plugins/overview.md). --- url: /docs/official-plugins/storage.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # Storage Plugin The Storage plugin is a single DevTools panel for MMKV, AsyncStorage, and Expo SecureStore. Register whichever adapters your app uses and inspect them all from one place. ## Installation Make sure to go through the [Getting Started guide](/docs/getting-started.md) before installing the plugin. ```sh [npm] npm install -D @rozenite/storage-plugin ``` ```sh [yarn] yarn add -D @rozenite/storage-plugin ``` ```sh [pnpm] pnpm add -D @rozenite/storage-plugin ``` ```sh [bun] bun add -D @rozenite/storage-plugin ``` ```sh [deno] deno add -D npm:@rozenite/storage-plugin ``` Install the peer dependencies for the storages you use: ```sh [npm] npm install -D react-native-mmkv @react-native-async-storage/async-storage expo-secure-store ``` ```sh [yarn] yarn add -D react-native-mmkv @react-native-async-storage/async-storage expo-secure-store ``` ```sh [pnpm] pnpm add -D react-native-mmkv @react-native-async-storage/async-storage expo-secure-store ``` ```sh [bun] bun add -D react-native-mmkv @react-native-async-storage/async-storage expo-secure-store ``` ```sh [deno] deno add -D npm:react-native-mmkv npm:@react-native-async-storage/async-storage npm:expo-secure-store ``` ## Setup ```ts title="App.tsx" import { createAsyncStorageAdapter, createExpoSecureStorageAdapter, createMMKVStorageAdapter, useRozeniteStoragePlugin, } from '@rozenite/storage-plugin'; const storages = [ createMMKVStorageAdapter({ storages: { user: userStorage, cache: cacheStorage }, }), createAsyncStorageAdapter({ storage: AsyncStorage, }), createExpoSecureStorageAdapter({ storage: SecureStore, keys: ['token', 'session'], }), ]; function App() { useRozeniteStoragePlugin({ storages }); return ; } ``` ## Web (React Native for Web) With [Rozenite for Web](/docs/targets/rozenite-for-web.md), this plugin is also available when debugging your React Native web app — showing entries from whichever Async Storage / Expo Secure Store adapters you configure for the browser. ## Adapters ### MMKV ```ts title="App.tsx" createMMKVStorageAdapter({ storages: { 'user-storage': userStorage, 'settings-storage': settingsStorage }, blacklist: { 'user-storage': /token|secret|password/ }, }); ``` MMKV v4 arrays aren't supported — pass a record (`{ id: instance }`) instead. ### AsyncStorage ```ts title="App.tsx" // v2 style createAsyncStorageAdapter({ storage: AsyncStorage }); // v3 style (instance-based) createAsyncStorageAdapter({ storages: { auth: authStorageInstance, cache: { storage: cacheStorageInstance, name: 'Cache Instance', blacklist: /debug|temp/ }, }, }); ``` ### Expo SecureStore ```ts title="App.tsx" createExpoSecureStorageAdapter({ storage: SecureStore, keys: async () => ['token', 'session', 'refreshToken'], storageName: 'Auth Secure Storage', }); ``` SecureStore doesn't support key enumeration, so you provide known keys via `keys`. ## Binary values MMKV storages that hold binary values render and edit them through a hex viewer with a Base64 mode for copy/paste. AsyncStorage and Expo SecureStore don't support binary values. ## Hiding sensitive keys `blacklist` is configured per storage and matched against the key in that storage: ```ts title="App.tsx" createAsyncStorageAdapter({ storages: { cache: { storage: cacheStorageInstance, blacklist: /temp|debug|internal/ }, }, }); ``` ## Large storages The panel loads entries in bounded, key-sorted pages with value previews only — it doesn't read your whole storage up front. Search runs on the device before pagination. Open an entry to load its full value; closing it, switching storage, or refreshing discards that loaded value again. Storages with native subscriptions (like MMKV) update live. Others don't poll — use **Refresh** to pick up external changes. ## Import / export Export the currently selected storage to a JSON snapshot, and import one back — handy for reproducing a bug from a captured state or sharing a scenario with a teammate. - Import and export always target the **currently selected storage** — switch the dropdown first to target a different one. - Import is an upsert: existing keys are overwritten, missing keys are created, keys not in the file are left alone. There's no "replace all". - The file is validated before anything is written. If validation fails, nothing changes. - Keys matching the target storage's `blacklist` are skipped, and the preview shows you which ones before you apply. Exports are versioned (`version: 1` today); a mismatched version is rejected rather than silently coerced. **Next**: See [Network Activity](/docs/official-plugins/network-activity.md) and [Plugin Development](/docs/plugin-development/plugin-development.md). --- url: /docs/official-plugins/feature-flags.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # Feature Flags Plugin The Feature Flags plugin lets you list, inspect, and force-override feature flags on a running device from React Native DevTools. It supports a homegrown custom store, LaunchDarkly, and Statsig through adapters. ## Tier A vs. Tier B: read this before picking an adapter Whether overriding a flag needs a code change depends on **who owns the override store**: - **Tier A — the provider owns the store.** The Statsig adapter points at a `LocalOverrideAdapter` you already construct for Statsig. **No call-site change** — overrides work as soon as the adapter is registered. The override lifetime is whatever that `LocalOverrideAdapter` instance's lifetime is; this plugin doesn't manage it. - **Tier B — the plugin owns the store.** The custom adapter and the LaunchDarkly adapter have no native override mechanism, so the plugin keeps its own in-memory override map. For LaunchDarkly this means passing a **wrapped client** to `` instead of the raw SDK client — the one line you have to change. Overrides are in-memory and reset on app restart unless you wire persistence yourself. This is also why **"Reset all overrides" in the panel is durable on Tier A and ephemeral on Tier B** — restarting the app clears a Tier B reset's effect but not a Tier A one. Neither is a bug. ## Installation Make sure to go through the [Getting Started guide](/docs/getting-started.md) before installing the plugin. ```sh [npm] npm install -D @rozenite/feature-flags-plugin ``` ```sh [yarn] yarn add -D @rozenite/feature-flags-plugin ``` ```sh [pnpm] pnpm add -D @rozenite/feature-flags-plugin ``` ```sh [bun] bun add -D @rozenite/feature-flags-plugin ``` ```sh [deno] deno add -D npm:@rozenite/feature-flags-plugin ``` Install the peer dependency for whichever adapter you use: ```sh [npm] npm install @launchdarkly/react-native-client-sdk ``` ```sh [yarn] yarn add @launchdarkly/react-native-client-sdk ``` ```sh [pnpm] pnpm add @launchdarkly/react-native-client-sdk ``` ```sh [bun] bun add @launchdarkly/react-native-client-sdk ``` ```sh [deno] deno add npm:@launchdarkly/react-native-client-sdk ``` ```sh [npm] npm install @statsig/js-client @statsig/react-native-bindings @statsig/js-local-overrides ``` ```sh [yarn] yarn add @statsig/js-client @statsig/react-native-bindings @statsig/js-local-overrides ``` ```sh [pnpm] pnpm add @statsig/js-client @statsig/react-native-bindings @statsig/js-local-overrides ``` ```sh [bun] bun add @statsig/js-client @statsig/react-native-bindings @statsig/js-local-overrides ``` ```sh [deno] deno add npm:@statsig/js-client npm:@statsig/react-native-bindings npm:@statsig/js-local-overrides ``` ## Adapter: Custom / local (Tier B) For a homegrown flag store, or before wiring a real provider: ```ts title="App.tsx" import { createCustomFlagsAdapter, useRozeniteFeatureFlagsPlugin, } from '@rozenite/feature-flags-plugin'; // Module-level, like storage/sqlite adapters elsewhere in the docs. The // hook tracks `providers` by content, so a fresh array literal on every // render works too -- hoisting just avoids rebuilding provider state for // nothing. const featureFlagsProviders = [ createCustomFlagsAdapter({ id: 'app', name: 'App flags', listFlags: () => flagStore.getAll(), }), ]; function App() { useRozeniteFeatureFlagsPlugin({ providers: featureFlagsProviders }); return ; } ``` `setOverride` throws for a key not present in `listFlags()` — nothing is written for a typo'd or unknown key. Overrides default to an in-memory `Map`. Bring your own store to persist them across restarts: ```ts title="App.tsx" import { createFlagOverrides } from '@rozenite/feature-flags-plugin'; const overrides = createFlagOverrides({ initial: JSON.parse(storage.getString('flag-overrides') ?? '{}'), onChange: (all) => storage.set('flag-overrides', JSON.stringify(all)), }); createCustomFlagsAdapter({ id: 'app', name: 'App flags', listFlags, overrides }); ``` ## Adapter: LaunchDarkly (Tier B) `createLaunchDarklyFlagsAdapter` returns `{ provider, client }`. Pass `client` — not your raw `ReactNativeLDClient` — to ``. Every LD hook (`useBoolVariation`, `useLDClient`, ...) reads through it from there automatically, because LD's own hooks are a thin read off the context client. ```ts title="App.tsx" import { ReactNativeLDClient, AutoEnvAttributes, LDProvider } from '@launchdarkly/react-native-client-sdk'; import { createLaunchDarklyFlagsAdapter, useRozeniteFeatureFlagsPlugin, } from '@rozenite/feature-flags-plugin'; const rawClient = new ReactNativeLDClient(LD_MOBILE_KEY, AutoEnvAttributes.Enabled); const { provider, client } = createLaunchDarklyFlagsAdapter({ client: rawClient }); const featureFlagsProviders = [provider]; function App() { useRozeniteFeatureFlagsPlugin({ providers: featureFlagsProviders }); return {/* ... */}; } ``` Notes: - Overrides are in-memory by default; pass `overrides: createFlagOverrides(...)` to persist them, same as the custom adapter. - There is no `refresh()` on this adapter — LD has no documented, version-stable way to force one client-side. - Anything holding a direct reference to the raw (unwrapped) client bypasses overrides. ## Adapter: Statsig (Tier A) You construct `StatsigClient` and `LocalOverrideAdapter` yourself; the adapter only takes references. ```ts title="App.tsx" import { StatsigClient } from '@statsig/js-client'; import { LocalOverrideAdapter } from '@statsig/js-local-overrides'; import { createStatsigFlagsAdapter, useRozeniteFeatureFlagsPlugin, } from '@rozenite/feature-flags-plugin'; const overrideAdapter = new LocalOverrideAdapter(); const client = new StatsigClient(STATSIG_CLIENT_KEY, { userID: 'user-123' }, { overrideAdapter }); await client.initializeAsync(); const featureFlagsProviders = [ createStatsigFlagsAdapter({ client, overrideAdapter, flags: [ { key: 'new-onboarding' }, // boolean gate (default type) { key: 'checkout-copy', type: 'string' }, { key: 'max-items', type: 'number' }, { key: 'layout-config', type: 'json' }, ], }), ]; function App() { useRozeniteFeatureFlagsPlugin({ providers: featureFlagsProviders }); return ; } ``` Notes: - Statsig has no client-side way to enumerate gates or dynamic configs, so you declare which flags exist via `flags`. Reading or overriding an undeclared key throws. - `type: 'boolean'` (the default) maps to `checkGate`/`overrideGate`. - Dynamic configs hold a parameter map, not a single scalar. `string`/`number` flags read and write a single parameter named `value` within the config by convention — this is a convention of this adapter, not part of the Statsig API. `json` flags read/write the config's full parameter map instead. - There is no `refresh()` — the closest SDK method re-identifies the user rather than refetching specs, so this adapter doesn't invent one. - "Reset all overrides" only clears the flags declared in `flags`, not every override in the shared `LocalOverrideAdapter` — overrides for gates/configs this adapter doesn't declare (e.g. from other debug tooling) are left alone. ## Agent Tools (LLM Integration) When this plugin is active, it registers agent tools under the `@rozenite/feature-flags-plugin` domain: - `list-flags` — list flags across providers, including type, effective value, and override state. - `get-flag` — read a single flag by key. - `override-flag` — force a flag to a value, useful for reproducing a bug report that only happens with a flag on. - `clear-overrides` — clear one override or every override for a provider. `providerId` is optional on every tool and resolves to the sole registered provider when there's only one. ## Usage Once registered, a "Feature Flags" panel appears in DevTools showing every flag's key, type, effective value, and whether it's overridden. Flip a boolean with one click, edit strings and numbers inline, or open a dialog to edit a `json` flag. Overridden rows can be reset individually, or all at once from the toolbar (subject to the Tier A/Tier B lifetime difference above). **Next**: Explore other [Official Plugins](/docs/official-plugins/overview.md) or learn how to build your own in the [Plugin Development guide](/docs/plugin-development/plugin-development.md). --- url: /docs/official-plugins/sqlite.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # SQLite Plugin ![](/sqlite-plugin.png) The SQLite plugin provides a query-first database inspector for React Native DevTools. It works with registered SQLite adapters, ships with an `expo-sqlite` adapter out of the box, and derives tables, schema details, and browse views entirely through SQL and `PRAGMA` queries. ## Installation Make sure to go through the [Getting Started guide](/docs/getting-started.md) before installing the plugin. Install the plugin: ```sh [npm] npm install -D @rozenite/sqlite-plugin ``` ```sh [yarn] yarn add -D @rozenite/sqlite-plugin ``` ```sh [pnpm] pnpm add -D @rozenite/sqlite-plugin ``` ```sh [bun] bun add -D @rozenite/sqlite-plugin ``` ```sh [deno] deno add -D npm:@rozenite/sqlite-plugin ``` Install the adapter peer dependency if you use Expo SQLite: ```sh [npm] npm install -D expo-sqlite ``` ```sh [yarn] yarn add -D expo-sqlite ``` ```sh [pnpm] pnpm add -D expo-sqlite ``` ```sh [bun] bun add -D expo-sqlite ``` ```sh [deno] deno add -D npm:expo-sqlite ``` ## Base Setup ```ts title="App.tsx" import * as SQLite from 'expo-sqlite'; import { createExpoSqliteAdapter, useRozeniteSqlitePlugin, } from '@rozenite/sqlite-plugin'; const appDb = SQLite.openDatabaseSync('app.db'); const analyticsDb = SQLite.openDatabaseSync('analytics.db'); const adapters = [ createExpoSqliteAdapter({ databases: { app: { name: 'app.db', database: appDb, }, analytics: { name: 'analytics.db', database: analyticsDb, }, }, }), ]; function App() { useRozeniteSqlitePlugin({ adapters }); return ; } ``` ## What the panel provides - Browse registered databases, tables, and views. - Inspect columns, defaults, primary keys, indexes, and foreign keys. - Run SQL scripts and inspect normalized metadata for each executed statement. - Preview result cells with structured JSON and blob-like payload support. ## Agent Integration This plugin exposes agent tools under the `@rozenite/sqlite-plugin` domain for LLM workflows. No additional setup is required — tools are registered automatically when `useRozeniteSqlitePlugin` is called. - `list-databases` — list all registered databases with their adapter info - `execute-sql` — execute one or more semicolon-separated SQL statements against a database; returns per-statement rows, columns, and metadata, with `failedStatementIndex` pointing to the first failure in a batch Use `list-databases` first to discover available database IDs, then pass the ID to `execute-sql`. A single `execute-sql` call handles everything: reads, writes, DDL, PRAGMAs, and multi-statement scripts. ## Adapter: Expo SQLite ```ts title="App.tsx" createExpoSqliteAdapter({ adapterId: 'expo-sqlite', adapterName: 'Expo SQLite', databases: { app: { name: 'app.db', database: SQLite.openDatabaseSync('app.db'), }, cache: { name: 'cache.db', database: SQLite.openDatabaseSync('cache.db'), }, }, }); ``` ### Notes - The display name is shown in the UI, while the plugin generates an internal opaque ID for bridge traffic. - The SQL editor executes statements in order, stops on the first error, and preserves explicit `BEGIN`, `COMMIT`, and `ROLLBACK` statements. - Custom adapters receive the full ordered statement array for scripts. To preserve per-statement failure details, throw an error enriched with `completedResults` and `failedStatementIndex`. - Schema and browse features are implemented with SQL and `PRAGMA`, so custom adapters only need to normalize statement execution. ## Custom adapters You can support any sqlite-like runtime by creating a generic adapter with an `executeStatements()` function per database: ```ts title="App.tsx" import { createSqliteAdapter } from '@rozenite/sqlite-plugin'; const adapters = [ createSqliteAdapter({ adapterName: 'Custom SQLite Driver', databases: { main: { name: 'main.db', executeStatements: async (statements) => { const results = []; for (const statement of statements) { const result = await driver.execute(statement.sql, statement.params); results.push({ rows: result.rows, columns: result.columns, metadata: { statementType: result.statementType, rowCount: result.rows.length, changes: result.changes, lastInsertRowId: result.lastInsertRowId, durationMs: result.durationMs, }, }); } return results; }, }, }, }), ]; ``` **Next**: See [Storage](/docs/official-plugins/storage.md), [File System](/docs/official-plugins/file-system.md), and [Plugin Development](/docs/plugin-development/plugin-development.md). --- url: /docs/official-plugins/file-system.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # File System Plugin The File System plugin adds a file explorer to React Native DevTools, so you can browse your app's directories and preview files without leaving the DevTools window. ## Installation Make sure to go through the [Getting Started guide](/docs/getting-started.md) before installing the plugin. ```sh [npm] npm install -D @rozenite/file-system-plugin ``` ```sh [yarn] yarn add -D @rozenite/file-system-plugin ``` ```sh [pnpm] pnpm add -D @rozenite/file-system-plugin ``` ```sh [bun] bun add -D @rozenite/file-system-plugin ``` ```sh [deno] deno add -D npm:@rozenite/file-system-plugin ``` Install whichever filesystem library your app already uses: ```sh [npm] npm install -D expo-file-system ``` ```sh [yarn] yarn add -D expo-file-system ``` ```sh [pnpm] pnpm add -D expo-file-system ``` ```sh [bun] bun add -D expo-file-system ``` ```sh [deno] deno add -D npm:expo-file-system ``` ```sh [npm] npm install -D @dr.pogodin/react-native-fs ``` ```sh [yarn] yarn add -D @dr.pogodin/react-native-fs ``` ```sh [pnpm] pnpm add -D @dr.pogodin/react-native-fs ``` ```sh [bun] bun add -D @dr.pogodin/react-native-fs ``` ```sh [deno] deno add -D npm:@dr.pogodin/react-native-fs ``` ## Usage ### With Expo FileSystem ```ts title="App.tsx" import * as FileSystem from 'expo-file-system'; import { createExpoFileSystemAdapter, useFileSystemDevTools, } from '@rozenite/file-system-plugin'; function App() { useFileSystemDevTools({ adapter: createExpoFileSystemAdapter(FileSystem), }); return ; } ``` ### With RNFS ```ts title="App.tsx" import RNFS from '@dr.pogodin/react-native-fs'; import { createRNFSAdapter, useFileSystemDevTools, } from '@rozenite/file-system-plugin'; function App() { useFileSystemDevTools({ adapter: createRNFSAdapter(RNFS), }); return ; } ``` Once configured, the plugin appears in DevTools as "File System". You can jump between roots (document, cache, bundle, and others depending on your library), navigate folders, and preview text and image files inline. Files that can't be decoded as text fall back to a hex-style preview. ## File transfer Importing and exporting files is off by default. Turn it on with `fileTransfer` when you want the panel to move files in or out of your app: ```ts title="App.tsx" useFileSystemDevTools({ adapter: createRNFSAdapter(RNFS), fileTransfer: { import: true, export: true, }, }); ``` Imports keep the original filename and ask before overwriting an existing file. If you also want coding agents to import or export files through Rozenite for Agents, opt in separately: ```ts title="App.tsx" useFileSystemDevTools({ adapter: createRNFSAdapter(RNFS), fileTransfer: { import: true, export: true, agent: { import: true, export: true }, }, }); ``` ## Notes :::warning Provider required The plugin doesn't auto-detect a filesystem library — you always need to pass an `adapter`. ::: :::info Preview limits Previews are size-limited to keep DevTools responsive. Very large files may not preview, and very large directories are truncated in the listing. ::: - `createExpoFileSystemAdapter` works with both the modern `expo-file-system` API and `expo-file-system/legacy`. - `createRNFSAdapter` supports both `react-native-fs` and `@dr.pogodin/react-native-fs`. - File transfer handles one file at a time, within visible filesystem roots. ## Agent Tools (LLM Integration) When active, this plugin registers agent tools under the `@rozenite/file-system-plugin` domain: `list-roots`, `list-entries`, `read-entry`, `read-text-file`, `read-image-file`, plus `export-file` / `import-file` when the matching `fileTransfer.agent` option is enabled. **Next**: Learn about [Plugin Development](/docs/plugin-development/plugin-development.md), or explore other [Official Plugins](/docs/official-plugins/overview.md). --- url: /docs/official-plugins/react-hook-form.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # React Hook Form Plugin ![](/rhf-plugin.png) The React Hook Form plugin lets you inspect your forms in real time from React Native DevTools. For every mounted form you can see field values, validation errors, dirty and touched states, native input types, and overall form status — without adding any logging or breakpoints. ## Installation Make sure to go through the [Getting Started guide](/docs/getting-started.md) before installing the plugin. ```sh [npm] npm install -D @rozenite/rhf-plugin ``` ```sh [yarn] yarn add -D @rozenite/rhf-plugin ``` ```sh [pnpm] pnpm add -D @rozenite/rhf-plugin ``` ```sh [bun] bun add -D @rozenite/rhf-plugin ``` ```sh [deno] deno add -D npm:@rozenite/rhf-plugin ``` ## Setup Call `useRozeniteRHFPlugin` inside any component that has access to a `react-hook-form` `control` object: ```typescript title="MyForm.tsx" import { useForm } from 'react-hook-form'; import { useRozeniteRHFPlugin } from '@rozenite/rhf-plugin'; function MyForm() { const { control, handleSubmit } = useForm(); useRozeniteRHFPlugin({ control }); return ( // your form JSX ); } ``` That's all. The panel appears automatically in React Native DevTools under **React Hook Form**. ## Multiple Forms Each `useRozeniteRHFPlugin` call registers an independent entry in the panel. By default the panel uses React's internal ID to label each form. Pass an explicit `id` to make the label readable: ```typescript useRozeniteRHFPlugin({ control, id: 'checkout-form' }); ``` When a form unmounts it stays visible in the panel marked as **disconnected**, so you can still inspect its last snapshot. ## What the Panel Shows | Column | Description | | --------- | ---------------------------------------------------------------------------- | | **Field** | Registered field name. Nested objects are grouped into collapsible sections. | | **Type** | Native input type (`text`, `email`, …) when available. | | **Value** | Current field value. | | **State** | `dirty` and `touched` badges. | | **Error** | Validation error type and message. | The header bar shows global form status: `valid`, `invalid`, `dirty`, `submitting`, `submitted`, `submitSuccessful`, `validating`, and the submit count. Use the search box to filter fields by name. ## Peer Dependencies This plugin requires `react-hook-form` v7: ```json "react-hook-form": "^7.33.1" ``` --- url: /docs/official-plugins/controls.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # Controls Plugin ![](/controls-plugin.png) The Controls plugin lets you build your own DevTools panel out of app-defined controls — status fields, toggles, selects, text inputs, and buttons — instead of adding temporary debug screens or hidden menus to your app. ## Installation Make sure to go through the [Getting Started guide](/docs/getting-started.md) before installing the plugin. Install the Controls plugin as a development dependency: ```sh [npm] npm install -D @rozenite/controls-plugin ``` ```sh [yarn] yarn add -D @rozenite/controls-plugin ``` ```sh [pnpm] pnpm add -D @rozenite/controls-plugin ``` ```sh [bun] bun add -D @rozenite/controls-plugin ``` ```sh [deno] deno add -D npm:@rozenite/controls-plugin ``` ## Base Setup ```ts title="App.tsx" import { createSection, useRozeniteControlsPlugin } from '@rozenite/controls-plugin'; import { useMemo, useState } from 'react'; function App() { const [verboseLogging, setVerboseLogging] = useState(false); const [environment, setEnvironment] = useState('local'); const [releaseLabel, setReleaseLabel] = useState('build-001'); const sections = useMemo( () => [ createSection({ id: 'runtime-status', title: 'Runtime Status', items: [ { id: 'environment-label', type: 'text', title: 'Environment', value: environment, }, { id: 'verbose-logging', type: 'toggle', title: 'Verbose Logging', value: verboseLogging, onUpdate: setVerboseLogging, }, { id: 'environment-selector', type: 'select', title: 'Environment', value: environment, options: [ { label: 'Local', value: 'local' }, { label: 'Staging', value: 'staging' }, { label: 'Production', value: 'production' }, ], onUpdate: setEnvironment, }, { id: 'release-label', type: 'input', title: 'Release Label', value: releaseLabel, placeholder: 'build-001', applyLabel: 'Apply', onUpdate: setReleaseLabel, }, { id: 'reset-session', type: 'button', title: 'Reset Session', actionLabel: 'Reset', onPress: () => { setVerboseLogging(false); setEnvironment('local'); setReleaseLabel('build-001'); }, }, ], }), ], [environment, releaseLabel, verboseLogging] ); useRozeniteControlsPlugin({ sections }); return ; } ``` ## Web (React Native for Web) With [Rozenite for Web](/docs/targets/rozenite-for-web.md), this plugin is available when you debug your React Native web app. ## Usage Once configured, the Controls plugin appears in your React Native DevTools sidebar as `Controls`. Typical uses: flipping feature flags during manual testing, switching between local/staging/production, resetting temporary app state, or triggering checkpoints for demos and QA — all without adding a debug screen to your app. ## Control types - **`text`** — a read-only value you want to observe, like status, counters, or a timestamp. - **`toggle`** — a boolean setting, like a feature flag or logging switch. - **`select`** — a choice from a fixed list of options. - **`input`** — an editable text value, like a release label or test ID. - **`button`** — a one-off action, like reset, sync, or retry. ## Organizing sections Keep diagnostics and editable controls in separate sections, use short titles, and add a description when a control has side effects. Keep section and item IDs stable across reloads so the panel doesn't jump around. ## Validation Use `validate` to reject invalid input with a clear message, `disabled` to keep a control visible but unavailable, and `applyLabel` to customize a text input's action label. **Next**: Explore other [Official Plugins](/docs/official-plugins/overview.md) or learn how to build your own in the [Plugin Development guide](/docs/plugin-development/plugin-development.md). --- url: /docs/official-plugins/require-profiler.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # Require Profiler Plugin ![](/require-profiler-plugin.png) The Require Profiler plugin tracks how long each `require()` call takes while your app starts up, and shows the result as a flame graph — so you can see exactly which modules are slowing down your app's Time to Interactive. ## Installation Make sure to go through the [Getting Started guide](/docs/getting-started.md) before installing the plugin. ```sh [npm] npm install -D @rozenite/require-profiler-plugin ``` ```sh [yarn] yarn add -D @rozenite/require-profiler-plugin ``` ```sh [pnpm] pnpm add -D @rozenite/require-profiler-plugin ``` ```sh [bun] bun add -D @rozenite/require-profiler-plugin ``` ```sh [deno] deno add -D npm:@rozenite/require-profiler-plugin ``` Enable the Metro instrumentation: ```javascript title="metro.config.js" const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config'); const { withRozenite } = require('@rozenite/metro'); const { withRozeniteRequireProfiler } = require('@rozenite/require-profiler-plugin/metro'); const defaultConfig = getDefaultConfig(__dirname); module.exports = withRozenite( mergeConfig(defaultConfig, { // Your existing Metro configuration }), { enabled: process.env.WITH_ROZENITE === 'true', enhanceMetroConfig: (config) => withRozeniteRequireProfiler(config), }, ); ``` Keep `withRozenite`'s `enabled` option conditional as above — when it is false, `enhanceMetroConfig` never runs and nothing is instrumented. The profiler also defends itself for the cases outside that gate: it skips instrumentation when `process.env.NODE_ENV` is `production`, and the polyfill it injects is guarded by `__DEV__`, which Metro strips from release bundles. Pass `enabled` to override the default: ```javascript withRozeniteRequireProfiler(config, { enabled: process.env.PROFILE_REQUIRES === 'true' }); ``` Add the DevTools hook to your app: ```typescript title="App.tsx" import { useRequireProfilerDevTools } from '@rozenite/require-profiler-plugin'; function App() { useRequireProfilerDevTools(); return ; } ``` ## Usage Once configured, "Metro Require Profiler" appears in your React Native DevTools sidebar, showing a flame graph of every module loaded during startup. - **Color** reflects self time: red modules spent over 70% of the heaviest module's own time; grey modules have no own time at all. - **Click** a frame to zoom into that part of the tree, and press `Escape` to zoom back out. The detail panel shows self time, total time, dependency count, and the full path. - **Top modules** shows the same chain as a table ranked by self time — usually the fastest way to find the module worth fixing. Switch **Group by** to _Packages_ to roll it up per npm package, which is the granularity you actually make decisions at: one row reading `lodash — 340ms across 87 modules` beats 87 rows of 4ms each. Each package reports its own evaluation time and, separately, its cost including everything it pulled in. - **Selecting a module** shows the require chain that pulled it in, root-first and clickable — the answer to "why is this even loaded?". - Packages evaluated from **more than one install location** are flagged. Two copies of a dependency cost evaluation time and bundle bytes twice, and with a stateful library they can break outright. - **Filter modules** highlights matching frames in the graph and narrows the table. - The sidebar lists every recorded chain with its duration and module count. Use the threshold selector to hide short chains — start around 100ms to focus on what actually matters for startup time. ## What to look for Wide, red, or deeply nested modules are your best candidates for optimization. Once you've found one that isn't needed immediately, defer it with a conditional or lazy `require()`: ```typescript // Instead of loading it up front: const HeavyModule = require('./HeavyModule'); // Load it only when needed: let HeavyModule; const loadHeavyModule = () => { if (!HeavyModule) { HeavyModule = require('./HeavyModule'); } return HeavyModule; }; ``` Re-run the profiler after each change to confirm the improvement and catch regressions. **Next**: Learn about [Plugin Development](/docs/plugin-development/plugin-development.md), or explore other [Official Plugins](/docs/official-plugins/overview.md). --- url: /plugin-directory.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. Plugin directory # Tools for the panels you need. Extend React Native DevTools with ready-made plugins for the state, storage, and diagnostics your app depends on. ## Explore plugins 12 plugins on this page v1.2.3Official ### @rozenite/redux-devtools-plugin Redux DevTools integration for React Native development with Rozenite 156 [GitHub](https://github.com/callstackincubator/rozenite/tree/main/packages/redux-devtools-plugin)[npm](https://www.npmjs.com/package/@rozenite/redux-devtools-plugin) v2.1.0Official ### @rozenite/network-activity-plugin Network activity monitoring and debugging for React Native apps 89 [GitHub](https://github.com/callstackincubator/rozenite/tree/main/packages/network-activity-plugin)[npm](https://www.npmjs.com/package/@rozenite/network-activity-plugin) v1.8.2Official ### @rozenite/tanstack-query-plugin TanStack Query cache inspection and debugging utilities 178 [GitHub](https://github.com/callstackincubator/rozenite/tree/main/packages/tanstack-query-plugin)[npm](https://www.npmjs.com/package/@rozenite/tanstack-query-plugin) v1.5.1Official ### @rozenite/expo-atlas-plugin Expo Atlas integration for React Native development tools 67 [GitHub](https://github.com/callstackincubator/rozenite/tree/main/packages/expo-atlas-plugin)[npm](https://www.npmjs.com/package/@rozenite/expo-atlas-plugin) v0.0.0 ### @rozenite/react-navigation-plugin Plugin information not available 0 [GitHub](https://github.com/callstackincubator/rozenite/tree/main/packages/react-navigation-plugin)[npm](https://www.npmjs.com/package/@rozenite/react-navigation-plugin) v0.0.0 ### @rozenite/overlay-plugin Plugin information not available 0 [GitHub](https://github.com/callstackincubator/rozenite/tree/main/packages/overlay-plugin)[npm](https://www.npmjs.com/package/@rozenite/overlay-plugin) v0.0.0 ### @rozenite/controls-plugin Plugin information not available 0 [GitHub](https://github.com/callstackincubator/rozenite/tree/main/packages/controls-plugin)[npm](https://www.npmjs.com/package/@rozenite/controls-plugin) v0.0.0 ### rozenite-preview Plugin information not available 0 [GitHub](https://github.com/matinzd/rozenite-preview)[npm](https://www.npmjs.com/package/rozenite-preview) v0.0.0 ### @rozenite/performance-monitor-plugin Plugin information not available 0 [GitHub](https://github.com/callstackincubator/rozenite/tree/main/packages/performance-monitor-plugin)[npm](https://www.npmjs.com/package/@rozenite/performance-monitor-plugin) v0.0.0 ### @rozenite/storage-plugin Plugin information not available 0 [GitHub](https://github.com/callstackincubator/rozenite/tree/main/packages/storage-plugin)[npm](https://www.npmjs.com/package/@rozenite/storage-plugin) v0.0.0 ### @rozenite/feature-flags-plugin Plugin information not available 0 [GitHub](https://github.com/callstackincubator/rozenite/tree/main/packages/feature-flags-plugin)[npm](https://www.npmjs.com/package/@rozenite/feature-flags-plugin) v0.0.0 ### @rozenite/sqlite-plugin Plugin information not available 0 [GitHub](https://github.com/callstackincubator/rozenite/tree/main/packages/sqlite-plugin)[npm](https://www.npmjs.com/package/@rozenite/sqlite-plugin) ## Built something useful? Add your Rozenite plugin to the directory with a pull request. [View repository](https://github.com/callstackincubator/rozenite) [Previous](#) [1](/plugin-directory/1)[2](/plugin-directory/2) [Next](/plugin-directory/2) --- url: /plugin-directory/1.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. Plugin directory # Tools for the panels you need. Extend React Native DevTools with ready-made plugins for the state, storage, and diagnostics your app depends on. ## Explore plugins 12 plugins on this page v1.2.3Official ### @rozenite/redux-devtools-plugin Redux DevTools integration for React Native development with Rozenite 156 [GitHub](https://github.com/callstackincubator/rozenite/tree/main/packages/redux-devtools-plugin)[npm](https://www.npmjs.com/package/@rozenite/redux-devtools-plugin) v2.1.0Official ### @rozenite/network-activity-plugin Network activity monitoring and debugging for React Native apps 89 [GitHub](https://github.com/callstackincubator/rozenite/tree/main/packages/network-activity-plugin)[npm](https://www.npmjs.com/package/@rozenite/network-activity-plugin) v1.8.2Official ### @rozenite/tanstack-query-plugin TanStack Query cache inspection and debugging utilities 178 [GitHub](https://github.com/callstackincubator/rozenite/tree/main/packages/tanstack-query-plugin)[npm](https://www.npmjs.com/package/@rozenite/tanstack-query-plugin) v1.5.1Official ### @rozenite/expo-atlas-plugin Expo Atlas integration for React Native development tools 67 [GitHub](https://github.com/callstackincubator/rozenite/tree/main/packages/expo-atlas-plugin)[npm](https://www.npmjs.com/package/@rozenite/expo-atlas-plugin) v0.0.0 ### @rozenite/react-navigation-plugin Plugin information not available 0 [GitHub](https://github.com/callstackincubator/rozenite/tree/main/packages/react-navigation-plugin)[npm](https://www.npmjs.com/package/@rozenite/react-navigation-plugin) v0.0.0 ### @rozenite/overlay-plugin Plugin information not available 0 [GitHub](https://github.com/callstackincubator/rozenite/tree/main/packages/overlay-plugin)[npm](https://www.npmjs.com/package/@rozenite/overlay-plugin) v0.0.0 ### @rozenite/controls-plugin Plugin information not available 0 [GitHub](https://github.com/callstackincubator/rozenite/tree/main/packages/controls-plugin)[npm](https://www.npmjs.com/package/@rozenite/controls-plugin) v0.0.0 ### rozenite-preview Plugin information not available 0 [GitHub](https://github.com/matinzd/rozenite-preview)[npm](https://www.npmjs.com/package/rozenite-preview) v0.0.0 ### @rozenite/performance-monitor-plugin Plugin information not available 0 [GitHub](https://github.com/callstackincubator/rozenite/tree/main/packages/performance-monitor-plugin)[npm](https://www.npmjs.com/package/@rozenite/performance-monitor-plugin) v0.0.0 ### @rozenite/storage-plugin Plugin information not available 0 [GitHub](https://github.com/callstackincubator/rozenite/tree/main/packages/storage-plugin)[npm](https://www.npmjs.com/package/@rozenite/storage-plugin) v0.0.0 ### @rozenite/feature-flags-plugin Plugin information not available 0 [GitHub](https://github.com/callstackincubator/rozenite/tree/main/packages/feature-flags-plugin)[npm](https://www.npmjs.com/package/@rozenite/feature-flags-plugin) v0.0.0 ### @rozenite/sqlite-plugin Plugin information not available 0 [GitHub](https://github.com/callstackincubator/rozenite/tree/main/packages/sqlite-plugin)[npm](https://www.npmjs.com/package/@rozenite/sqlite-plugin) ## Built something useful? Add your Rozenite plugin to the directory with a pull request. [View repository](https://github.com/callstackincubator/rozenite) [Previous](#) [1](/plugin-directory/1)[2](/plugin-directory/2) [Next](/plugin-directory/2) --- url: /plugin-directory/2.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. Plugin directory # Tools for the panels you need. Extend React Native DevTools with ready-made plugins for the state, storage, and diagnostics your app depends on. ## Explore plugins 11 plugins on this page v0.0.0 ### @rozenite/file-system-plugin Plugin information not available 0 [GitHub](https://github.com/callstackincubator/rozenite/tree/main/packages/file-system-plugin)[npm](https://www.npmjs.com/package/@rozenite/file-system-plugin) v0.0.0 ### @rozenite/rhf-plugin Plugin information not available 0 [GitHub](https://github.com/callstackincubator/rozenite/tree/main/packages/rhf-plugin)[npm](https://www.npmjs.com/package/@rozenite/rhf-plugin) v0.0.0 ### @rozenite/require-profiler-plugin Plugin information not available 0 [GitHub](https://github.com/callstackincubator/rozenite/tree/main/packages/require-profiler-plugin)[npm](https://www.npmjs.com/package/@rozenite/require-profiler-plugin) v0.0.0 ### @ilteoood/zorro Plugin information not available 0 [GitHub](https://github.com/ilteoood/zorro)[npm](https://www.npmjs.com/package/@ilteoood/zorro) v0.0.0 ### @react-native-nitro-geolocation/rozenite-plugin Plugin information not available 0 [GitHub](https://github.com/jingjing2222/react-native-nitro-geolocation/tree/main/packages/rozenite-devtools-plugin)[npm](https://www.npmjs.com/package/@react-native-nitro-geolocation/rozenite-plugin) v0.0.0 ### rozenite-graphql-client-devtool Plugin information not available 0 [GitHub](https://github.com/CodeByRahulSaini/rozenite-graphql-client-devtool)[npm](https://www.npmjs.com/package/rozenite-graphql-client-devtool) v0.0.0 ### rozenite-assets-viewer Plugin information not available 0 [GitHub](https://github.com/CodeByRahulSaini/rozenite-assets-viewer)[npm](https://www.npmjs.com/package/rozenite-assets-viewer) v0.0.0 ### rozenite-growthbook-plugin Plugin information not available 0 [GitHub](https://github.com/valeriobelli/rozenite-growthbook-plugin)[npm](https://www.npmjs.com/package/rozenite-growthbook-plugin) v0.0.0 ### rozenite-zustand-devtools Plugin information not available 0 [GitHub](https://github.com/IronTony/rozenite-zustand-devtools)[npm](https://www.npmjs.com/package/rozenite-zustand-devtools) v0.0.0 ### rozenite-navigation-inspector Plugin information not available 0 [GitHub](https://github.com/IronTony/rozenite-navigation-inspector)[npm](https://www.npmjs.com/package/rozenite-navigation-inspector) v0.0.0 ### @avasapp/rozenite-plugin-ably Plugin information not available 0 [GitHub](https://github.com/avas-app/rozenite-plugin-ably)[npm](https://www.npmjs.com/package/@avasapp/rozenite-plugin-ably) ## Built something useful? Add your Rozenite plugin to the directory with a pull request. [View repository](https://github.com/callstackincubator/rozenite) [Previous](/plugin-directory/1) [1](/plugin-directory/1)[2](/plugin-directory/2) [Next](#) --- url: /404.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. 404 # PAGE NOT FOUND [Take me home](/) --- url: /index.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # DevTools panels for React Native and Lynx.Runtime tools for agents. Rozenite adds debugging panels to React Native DevTools — for React Native, web and Lynx apps. Agents read the same runtime data. - React Native - Expo - Re.Pack - React Native Web - Lynx [Get started](/docs/getting-started)[Browse plugins](/plugin-directory) $`npx rozenite@latest init` Official plugins ## Panels for common React Native work Install the plugin you need and follow its setup guide. Each one adds a panel to React Native DevTools. [Agent tools### Network Activity Requests, headers, payloads and timings, including Expo fetch and nitro-fetch traffic. @rozenite/network-activity-plugin](/docs/official-plugins/network-activity)[### Performance Monitor Live marks, measures and metrics per session, exportable for offline analysis. @rozenite/performance-monitor-plugin](/docs/official-plugins/performance-monitor)[Agent tools### Redux DevTools Store state, dispatched actions and a diff for each one. @rozenite/redux-devtools-plugin](/docs/official-plugins/redux-devtools)[Agent tools### TanStack Query Queries, mutations and cache entries, with refetch, invalidate and reset. @rozenite/tanstack-query-plugin](/docs/official-plugins/tanstack-query)[Agent tools### React Navigation Action timeline with dispatch origins, time travel and deep link testing. @rozenite/react-navigation-plugin](/docs/official-plugins/react-navigation)[Agent tools### Storage MMKV, AsyncStorage and SecureStore in one panel, with per-key blacklists. @rozenite/storage-plugin](/docs/official-plugins/storage) [+7 more official plugins for other libraries and development tasks.Browse plugins](/plugin-directory) Rozenite for Agents ## Give your agent access to the running app Rozenite creates a session for the running app. An agent can inspect its runtime domains and call registered app or plugin tools. [Agent documentation](/docs/agent/overview) terminalrozenite agent ```` npx rozenite agent session create npx rozenite agent console call \ --tool getMessages \ --args '{"levels":["error"]}' \ --session ```` ### Inspect runtime data Read console logs, network requests, React profiles, and memory snapshots from the selected app session. ### Use plugin tools Plugins can register agent tools. Storage, React Navigation, Redux, and TanStack Query expose tools when their integration is enabled. ### Add app-specific tools Register tools for product-specific state or actions that do not belong to a library plugin. Rozenite for LynxExperimental ## Same panels, same CLI, now on Lynx Rozenite discovers Lynx apps over DebugRouter and bridges them to the Chrome DevTools Protocol, so the DevTools frontend React Native uses connects to a Lynx card unmodified. 1. 01### Turn on Lynx DevTool Lynx ships its DevTool component switched off. Flip it on in the app, then relaunch — nothing is discoverable until you do. 1. 02### Add the plugin One entry in lynx.config.ts. The plugin injects the device runtime for you, in development only, so there is nothing to import in your app. 1. 03### Open the printed URL The dev server prints a DevTools URL per card. Plugins resolve from your package.json, exactly as they do on Metro. $`npm install -D @rozenite/lynx` [Lynx setup guide](/docs/targets/rozenite-for-lynx) Plugins that move state work today — controls, feature flags, React Hook Form and TanStack Query. Ones built on React Native APIs do not yet. Rozenite for Web ## Debug React Native web in DevTools Use supported Rozenite panels with browser targets from your React Native web app. - Select a browser targetOpen your web app in a supported browser, then select that target in React Native DevTools. - Use Metro or WebpackUse the matching withRozeniteWeb wrapper: Metro can bundle native and web, while Webpack Dev Server can serve the web app. - Install the extension and packageInstall the Rozenite browser extension and load @rozenite/web from the web entry point. It runs only in development. $`npm install -D @rozenite/web` [Web setup guide](/docs/targets/rozenite-for-web) React Native Web / Expo Web ![Rozenite](/logo.svg)Rozenite / React Native DevTools ## Build the panel your app needs Use a plugin for app-specific state, controls, or diagnostics. Rozenite provides the DevTools connection and plugin build setup. ### The app side Runs in your app and can use React Native APIs and your existing dependencies. ### The DevTools UI side A React panel in DevTools, connected to the app through a typed event bridge. panel.tsxtypescript ```` const client = useRozeniteDevToolsClient({ pluginId: 'checkout-inspector', }); client.onMessage('cart-updated', (cart) => { setItems(cart.items); }); client.send('clear-cart', { reason: 'devtools' }); ```` ### Create a plugin in two commands $`npx rozenite generate my-plugin` $`cd my-plugin && rozenite dev` [Plugin development guide](/docs/plugin-development/overview) ## Rozenite on its own If Rozenite panels are where you spend your debugging time, run just those. `rozenite open` connects straight to the device, so your panels stay put across app reloads — and they work the same way whichever target you are debugging. $`npx rozenite open` [Standalone app guide](/docs/standalone-app) ![The Rozenite standalone app in its own window, showing the File System panel browsing an iOS app's Library directory with a PNG previewed.](/standalone-rozenite.png) ## Come see what your app is doing Set up Rozenite, then add the plugins your app needs. Browse the directory, report a missing integration, or publish your own plugin. [Get started](/docs/getting-started)[View on GitHub](https://github.com/callstackincubator/rozenite)[Join the Discord](https://discord.gg/xgGt7KAjxv) [](https://www.callstack.com)Copyright © 2026 Callstack [](https://github.com/callstackincubator/rozenite)[](https://discord.gg/xgGt7KAjxv) --- url: /welcome.md --- > For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt. # Rozenite loaded successfully! You are now ready to install your first plugin. Go to plugin directory to find a plugin to install. [Documentation](/docs/getting-started)[Plugin directory](/plugin-directory)[Want to shape the future of Rozenite? Share your feedback!](https://forms.gle/vUCVUmnZ883sfzoS8)