agent-react-devtools 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # agent-react-devtools
2
2
 
3
+ ## 0.2.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 370ef23: fix: prevent race condition where React loads before devtools hook is installed
8
+
9
+ The connect module's dynamic `import('react-devtools-core')` yielded control before `initialize()` could install the hook, allowing react-dom to load first and miss the connection. Added top-level `await` to block dependent modules until the hook is ready, and updated the Vite plugin to enable `top-level-await` in esbuild's dep optimizer.
10
+
3
11
  ## 0.2.1
4
12
 
5
13
  ### Patch Changes
package/README.md CHANGED
@@ -198,6 +198,15 @@ For Expo, the connection works automatically with the Expo dev client.
198
198
 
199
199
  To use a custom port, set the `REACT_DEVTOOLS_PORT` environment variable.
200
200
 
201
+ ## Using with agent-browser
202
+
203
+ When using `agent-browser` to drive the app (e.g. for profiling interactions), you **must use headed mode**. Headless Chromium does not properly execute the devtools connect script:
204
+
205
+ ```sh
206
+ agent-browser --session devtools --headed open http://localhost:5173/
207
+ agent-react-devtools status # Should show "Apps: 1 connected"
208
+ ```
209
+
201
210
  ## Using with AI Coding Assistants
202
211
 
203
212
  Add the skill to your AI coding assistant for richer context:
package/dist/connect.d.ts CHANGED
@@ -5,9 +5,19 @@
5
5
  *
6
6
  * This must be imported before React loads. It:
7
7
  * 1. Removes the Vite plugin-react hook stub
8
- * 2. Initializes react-devtools-core
8
+ * 2. Initializes react-devtools-core (installs the real __REACT_DEVTOOLS_GLOBAL_HOOK__)
9
9
  * 3. Connects via WebSocket to the agent-react-devtools daemon
10
10
  *
11
+ * Steps 1–2 run synchronously at module evaluation time via a static import
12
+ * of react-devtools-core. This is critical — a dynamic import would yield
13
+ * control and let React load before the hook is installed. The static import
14
+ * also ensures esbuild's CJS-to-ESM interop provides proper named exports
15
+ * (dynamic imports to CJS chunks only expose a default export).
16
+ *
17
+ * react-devtools-core is a required peer dependency. If not installed, this
18
+ * module will fail to load — which is the correct behavior since there's
19
+ * nothing useful it can do without it.
20
+ *
11
21
  * Export `ready` — a promise that resolves once the WebSocket opens
12
22
  * (or after a 2s timeout / error, so the app is never blocked).
13
23
  */
package/dist/connect.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // src/connect.ts
2
+ import { initialize, connectToDevTools } from "react-devtools-core";
2
3
  function getMeta(name) {
3
4
  if (typeof document === "undefined") return null;
4
5
  const meta = document.querySelector(`meta[name="${name}"]`);
@@ -16,17 +17,18 @@ function noop() {
16
17
  }
17
18
  var isSSR = typeof window === "undefined";
18
19
  var isProd = typeof import.meta !== "undefined" && import.meta.env?.PROD === true || typeof process !== "undefined" && false;
20
+ if (!isSSR && !isProd) {
21
+ try {
22
+ delete window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
23
+ } catch {
24
+ }
25
+ initialize();
26
+ }
19
27
  var ready = isSSR || isProd ? noop() : connect();
20
- async function connect() {
28
+ function connect() {
21
29
  try {
22
30
  const port = getPort();
23
31
  const host = getHost();
24
- try {
25
- delete window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
26
- } catch {
27
- }
28
- const { initialize, connectToDevTools } = await import("react-devtools-core");
29
- initialize();
30
32
  return new Promise((resolve) => {
31
33
  try {
32
34
  const ws = new WebSocket(`ws://${host}:${port}`);
@@ -39,6 +41,7 @@ async function connect() {
39
41
  }
40
42
  });
41
43
  } catch {
44
+ return Promise.resolve();
42
45
  }
43
46
  }
44
47
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/connect.ts"],"sourcesContent":["/**\n * Browser-side connection module for agent-react-devtools.\n *\n * Usage: `import 'agent-react-devtools/connect'`\n *\n * This must be imported before React loads. It:\n * 1. Removes the Vite plugin-react hook stub\n * 2. Initializes react-devtools-core\n * 3. Connects via WebSocket to the agent-react-devtools daemon\n *\n * Export `ready` — a promise that resolves once the WebSocket opens\n * (or after a 2s timeout / error, so the app is never blocked).\n */\n\nfunction getMeta(name: string): string | null {\n if (typeof document === 'undefined') return null;\n const meta = document.querySelector(`meta[name=\"${name}\"]`);\n return meta?.getAttribute('content') || null;\n}\n\nfunction getPort(): number {\n const val = parseInt(getMeta('agent-react-devtools-port') || '', 10);\n return isNaN(val) ? 8097 : val;\n}\n\nfunction getHost(): string {\n return getMeta('agent-react-devtools-host') || 'localhost';\n}\n\nfunction noop(): Promise<void> {\n return Promise.resolve();\n}\n\n// SSR guard\nconst isSSR = typeof window === 'undefined';\n\n// Production guard — check bundler-injected signals first, then Node.js process.env\nconst isProd =\n (typeof import.meta !== 'undefined' &&\n (import.meta as any).env?.PROD === true) ||\n (typeof process !== 'undefined' &&\n process.env?.NODE_ENV === 'production');\n\nexport const ready: Promise<void> = isSSR || isProd ? noop() : connect();\n\nasync function connect(): Promise<void> {\n try {\n const port = getPort();\n const host = getHost();\n\n // Remove Vite's plugin-react hook stub so react-devtools-core can install the full hook\n try {\n delete (window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__;\n } catch {\n // Property may be non-configurable (browser extension) — ignore\n }\n\n const { initialize, connectToDevTools } = await import('react-devtools-core');\n initialize();\n\n return new Promise<void>((resolve) => {\n try {\n const ws = new WebSocket(`ws://${host}:${port}`);\n connectToDevTools({ port, websocket: ws });\n ws.addEventListener('open', () => resolve());\n ws.addEventListener('error', () => resolve());\n setTimeout(resolve, 2000);\n } catch {\n resolve();\n }\n });\n } catch {\n // react-devtools-core not installed or other error — silently skip\n }\n}\n"],"mappings":";AAcA,SAAS,QAAQ,MAA6B;AAC5C,MAAI,OAAO,aAAa,YAAa,QAAO;AAC5C,QAAM,OAAO,SAAS,cAAc,cAAc,IAAI,IAAI;AAC1D,SAAO,MAAM,aAAa,SAAS,KAAK;AAC1C;AAEA,SAAS,UAAkB;AACzB,QAAM,MAAM,SAAS,QAAQ,2BAA2B,KAAK,IAAI,EAAE;AACnE,SAAO,MAAM,GAAG,IAAI,OAAO;AAC7B;AAEA,SAAS,UAAkB;AACzB,SAAO,QAAQ,2BAA2B,KAAK;AACjD;AAEA,SAAS,OAAsB;AAC7B,SAAO,QAAQ,QAAQ;AACzB;AAGA,IAAM,QAAQ,OAAO,WAAW;AAGhC,IAAM,SACH,OAAO,gBAAgB,eACrB,YAAoB,KAAK,SAAS,QACpC,OAAO,YAAY,eAClB;AAEG,IAAM,QAAuB,SAAS,SAAS,KAAK,IAAI,QAAQ;AAEvE,eAAe,UAAyB;AACtC,MAAI;AACF,UAAM,OAAO,QAAQ;AACrB,UAAM,OAAO,QAAQ;AAGrB,QAAI;AACF,aAAQ,OAAe;AAAA,IACzB,QAAQ;AAAA,IAER;AAEA,UAAM,EAAE,YAAY,kBAAkB,IAAI,MAAM,OAAO,qBAAqB;AAC5E,eAAW;AAEX,WAAO,IAAI,QAAc,CAAC,YAAY;AACpC,UAAI;AACF,cAAM,KAAK,IAAI,UAAU,QAAQ,IAAI,IAAI,IAAI,EAAE;AAC/C,0BAAkB,EAAE,MAAM,WAAW,GAAG,CAAC;AACzC,WAAG,iBAAiB,QAAQ,MAAM,QAAQ,CAAC;AAC3C,WAAG,iBAAiB,SAAS,MAAM,QAAQ,CAAC;AAC5C,mBAAW,SAAS,GAAI;AAAA,MAC1B,QAAQ;AACN,gBAAQ;AAAA,MACV;AAAA,IACF,CAAC;AAAA,EACH,QAAQ;AAAA,EAER;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/connect.ts"],"sourcesContent":["/**\n * Browser-side connection module for agent-react-devtools.\n *\n * Usage: `import 'agent-react-devtools/connect'`\n *\n * This must be imported before React loads. It:\n * 1. Removes the Vite plugin-react hook stub\n * 2. Initializes react-devtools-core (installs the real __REACT_DEVTOOLS_GLOBAL_HOOK__)\n * 3. Connects via WebSocket to the agent-react-devtools daemon\n *\n * Steps 1–2 run synchronously at module evaluation time via a static import\n * of react-devtools-core. This is critical — a dynamic import would yield\n * control and let React load before the hook is installed. The static import\n * also ensures esbuild's CJS-to-ESM interop provides proper named exports\n * (dynamic imports to CJS chunks only expose a default export).\n *\n * react-devtools-core is a required peer dependency. If not installed, this\n * module will fail to load — which is the correct behavior since there's\n * nothing useful it can do without it.\n *\n * Export `ready` — a promise that resolves once the WebSocket opens\n * (or after a 2s timeout / error, so the app is never blocked).\n */\n\nimport { initialize, connectToDevTools } from 'react-devtools-core';\n\nfunction getMeta(name: string): string | null {\n if (typeof document === 'undefined') return null;\n const meta = document.querySelector(`meta[name=\"${name}\"]`);\n return meta?.getAttribute('content') || null;\n}\n\nfunction getPort(): number {\n const val = parseInt(getMeta('agent-react-devtools-port') || '', 10);\n return isNaN(val) ? 8097 : val;\n}\n\nfunction getHost(): string {\n return getMeta('agent-react-devtools-host') || 'localhost';\n}\n\nfunction noop(): Promise<void> {\n return Promise.resolve();\n}\n\n// SSR guard\nconst isSSR = typeof window === 'undefined';\n\n// Production guard — check bundler-injected signals first, then Node.js process.env\nconst isProd =\n (typeof import.meta !== 'undefined' &&\n (import.meta as any).env?.PROD === true) ||\n (typeof process !== 'undefined' &&\n process.env?.NODE_ENV === 'production');\n\n// Install the devtools hook synchronously before React loads.\n// This MUST happen at module evaluation time — if deferred to an async\n// callback, react-dom may initialize first and miss the hook entirely.\nif (!isSSR && !isProd) {\n // Remove Vite's plugin-react hook stub so react-devtools-core can install the full hook\n try {\n delete (window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__;\n } catch {\n // Property may be non-configurable (browser extension) — ignore\n }\n\n initialize();\n}\n\nexport const ready: Promise<void> = isSSR || isProd ? noop() : connect();\n\nfunction connect(): Promise<void> {\n try {\n const port = getPort();\n const host = getHost();\n\n return new Promise<void>((resolve) => {\n try {\n const ws = new WebSocket(`ws://${host}:${port}`);\n connectToDevTools({ port, websocket: ws });\n ws.addEventListener('open', () => resolve());\n ws.addEventListener('error', () => resolve());\n setTimeout(resolve, 2000);\n } catch {\n resolve();\n }\n });\n } catch {\n return Promise.resolve();\n }\n}\n"],"mappings":";AAwBA,SAAS,YAAY,yBAAyB;AAE9C,SAAS,QAAQ,MAA6B;AAC5C,MAAI,OAAO,aAAa,YAAa,QAAO;AAC5C,QAAM,OAAO,SAAS,cAAc,cAAc,IAAI,IAAI;AAC1D,SAAO,MAAM,aAAa,SAAS,KAAK;AAC1C;AAEA,SAAS,UAAkB;AACzB,QAAM,MAAM,SAAS,QAAQ,2BAA2B,KAAK,IAAI,EAAE;AACnE,SAAO,MAAM,GAAG,IAAI,OAAO;AAC7B;AAEA,SAAS,UAAkB;AACzB,SAAO,QAAQ,2BAA2B,KAAK;AACjD;AAEA,SAAS,OAAsB;AAC7B,SAAO,QAAQ,QAAQ;AACzB;AAGA,IAAM,QAAQ,OAAO,WAAW;AAGhC,IAAM,SACH,OAAO,gBAAgB,eACrB,YAAoB,KAAK,SAAS,QACpC,OAAO,YAAY,eAClB;AAKJ,IAAI,CAAC,SAAS,CAAC,QAAQ;AAErB,MAAI;AACF,WAAQ,OAAe;AAAA,EACzB,QAAQ;AAAA,EAER;AAEA,aAAW;AACb;AAEO,IAAM,QAAuB,SAAS,SAAS,KAAK,IAAI,QAAQ;AAEvE,SAAS,UAAyB;AAChC,MAAI;AACF,UAAM,OAAO,QAAQ;AACrB,UAAM,OAAO,QAAQ;AAErB,WAAO,IAAI,QAAc,CAAC,YAAY;AACpC,UAAI;AACF,cAAM,KAAK,IAAI,UAAU,QAAQ,IAAI,IAAI,IAAI,EAAE;AAC/C,0BAAkB,EAAE,MAAM,WAAW,GAAG,CAAC;AACzC,WAAG,iBAAiB,QAAQ,MAAM,QAAQ,CAAC;AAC3C,WAAG,iBAAiB,SAAS,MAAM,QAAQ,CAAC;AAC5C,mBAAW,SAAS,GAAI;AAAA,MAC1B,QAAQ;AACN,gBAAQ;AAAA,MACV;AAAA,IACF,CAAC;AAAA,EACH,QAAQ;AACN,WAAO,QAAQ,QAAQ;AAAA,EACzB;AACF;","names":[]}
package/dist/vite.js CHANGED
@@ -5,6 +5,17 @@ function reactDevtools(options) {
5
5
  return {
6
6
  name: "agent-react-devtools",
7
7
  apply: "serve",
8
+ config() {
9
+ return {
10
+ optimizeDeps: {
11
+ esbuildOptions: {
12
+ supported: {
13
+ "top-level-await": true
14
+ }
15
+ }
16
+ }
17
+ };
18
+ },
8
19
  transformIndexHtml: {
9
20
  order: "pre",
10
21
  handler() {
package/dist/vite.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/vite-plugin.ts"],"sourcesContent":["import type { Plugin, HtmlTagDescriptor } from 'vite';\n\nexport interface ReactDevtoolsOptions {\n /** WebSocket port the daemon listens on. Default: 8097 */\n port?: number;\n /** WebSocket host. Default: 'localhost' */\n host?: string;\n}\n\nexport function reactDevtools(options?: ReactDevtoolsOptions): Plugin {\n const port = options?.port ?? 8097;\n const host = options?.host ?? 'localhost';\n\n return {\n name: 'agent-react-devtools',\n apply: 'serve',\n transformIndexHtml: {\n order: 'pre',\n handler() {\n const tags: HtmlTagDescriptor[] = [];\n\n if (host !== 'localhost') {\n tags.push({\n tag: 'meta',\n attrs: { name: 'agent-react-devtools-host', content: host },\n injectTo: 'head-prepend',\n });\n }\n\n if (port !== 8097) {\n tags.push({\n tag: 'meta',\n attrs: { name: 'agent-react-devtools-port', content: String(port) },\n injectTo: 'head-prepend',\n });\n }\n\n tags.push({\n tag: 'script',\n attrs: { type: 'module' },\n children: `import 'agent-react-devtools/connect';`,\n injectTo: 'head-prepend',\n });\n\n return tags;\n },\n },\n };\n}\n"],"mappings":";AASO,SAAS,cAAc,SAAwC;AACpE,QAAM,OAAO,SAAS,QAAQ;AAC9B,QAAM,OAAO,SAAS,QAAQ;AAE9B,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,IACP,oBAAoB;AAAA,MAClB,OAAO;AAAA,MACP,UAAU;AACR,cAAM,OAA4B,CAAC;AAEnC,YAAI,SAAS,aAAa;AACxB,eAAK,KAAK;AAAA,YACR,KAAK;AAAA,YACL,OAAO,EAAE,MAAM,6BAA6B,SAAS,KAAK;AAAA,YAC1D,UAAU;AAAA,UACZ,CAAC;AAAA,QACH;AAEA,YAAI,SAAS,MAAM;AACjB,eAAK,KAAK;AAAA,YACR,KAAK;AAAA,YACL,OAAO,EAAE,MAAM,6BAA6B,SAAS,OAAO,IAAI,EAAE;AAAA,YAClE,UAAU;AAAA,UACZ,CAAC;AAAA,QACH;AAEA,aAAK,KAAK;AAAA,UACR,KAAK;AAAA,UACL,OAAO,EAAE,MAAM,SAAS;AAAA,UACxB,UAAU;AAAA,UACV,UAAU;AAAA,QACZ,CAAC;AAED,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/vite-plugin.ts"],"sourcesContent":["import type { Plugin, HtmlTagDescriptor } from 'vite';\n\nexport interface ReactDevtoolsOptions {\n /** WebSocket port the daemon listens on. Default: 8097 */\n port?: number;\n /** WebSocket host. Default: 'localhost' */\n host?: string;\n}\n\nexport function reactDevtools(options?: ReactDevtoolsOptions): Plugin {\n const port = options?.port ?? 8097;\n const host = options?.host ?? 'localhost';\n\n return {\n name: 'agent-react-devtools',\n apply: 'serve',\n config() {\n // The connect module uses top-level await to block React from loading\n // before the devtools hook is installed. Vite's dep optimizer uses\n // esbuild which defaults to es2020 (no TLA support), so we enable it.\n return {\n optimizeDeps: {\n esbuildOptions: {\n supported: {\n 'top-level-await': true,\n },\n },\n },\n };\n },\n transformIndexHtml: {\n order: 'pre',\n handler() {\n const tags: HtmlTagDescriptor[] = [];\n\n if (host !== 'localhost') {\n tags.push({\n tag: 'meta',\n attrs: { name: 'agent-react-devtools-host', content: host },\n injectTo: 'head-prepend',\n });\n }\n\n if (port !== 8097) {\n tags.push({\n tag: 'meta',\n attrs: { name: 'agent-react-devtools-port', content: String(port) },\n injectTo: 'head-prepend',\n });\n }\n\n tags.push({\n tag: 'script',\n attrs: { type: 'module' },\n children: `import 'agent-react-devtools/connect';`,\n injectTo: 'head-prepend',\n });\n\n return tags;\n },\n },\n };\n}\n"],"mappings":";AASO,SAAS,cAAc,SAAwC;AACpE,QAAM,OAAO,SAAS,QAAQ;AAC9B,QAAM,OAAO,SAAS,QAAQ;AAE9B,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,IACP,SAAS;AAIP,aAAO;AAAA,QACL,cAAc;AAAA,UACZ,gBAAgB;AAAA,YACd,WAAW;AAAA,cACT,mBAAmB;AAAA,YACrB;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IACA,oBAAoB;AAAA,MAClB,OAAO;AAAA,MACP,UAAU;AACR,cAAM,OAA4B,CAAC;AAEnC,YAAI,SAAS,aAAa;AACxB,eAAK,KAAK;AAAA,YACR,KAAK;AAAA,YACL,OAAO,EAAE,MAAM,6BAA6B,SAAS,KAAK;AAAA,YAC1D,UAAU;AAAA,UACZ,CAAC;AAAA,QACH;AAEA,YAAI,SAAS,MAAM;AACjB,eAAK,KAAK;AAAA,YACR,KAAK;AAAA,YACL,OAAO,EAAE,MAAM,6BAA6B,SAAS,OAAO,IAAI,EAAE;AAAA,YAClE,UAAU;AAAA,UACZ,CAAC;AAAA,QACH;AAEA,aAAK,KAAK;AAAA,UACR,KAAK;AAAA,UACL,OAAO,EAAE,MAAM,SAAS;AAAA,UACxB,UAAU;AAAA,UACV,UAAU;AAAA,QACZ,CAAC;AAED,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-react-devtools",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "CLI tool for AI agents to inspect React component trees and profile performance",
5
5
  "type": "module",
6
6
  "bin": {
@@ -124,10 +124,20 @@ agent-react-devtools profile slow --limit 5
124
124
  # Compare render counts and durations to the previous run
125
125
  ```
126
126
 
127
+ ## Using with agent-browser
128
+
129
+ When using `agent-browser` to drive the app while profiling or debugging, you **must use headed mode** (`--headed`). Headless Chromium does not execute ES module scripts the same way as a real browser, which prevents the devtools connect script from running properly.
130
+
131
+ ```bash
132
+ agent-browser --session devtools --headed open http://localhost:5173/
133
+ agent-react-devtools status # Should show 1 connected app
134
+ ```
135
+
127
136
  ## Important Rules
128
137
 
129
138
  - **Labels reset** when the app reloads or components unmount/remount. Always re-check with `get tree` or `find` after a page reload.
130
139
  - **`status` first** — if status shows 0 connected apps, the React app is not connected. The user may need to run `npx agent-react-devtools init` in their project first.
140
+ - **Headed browser required** — if using `agent-browser`, always use `--headed` mode. Headless Chromium does not properly load the devtools connect script.
131
141
  - **Profile while interacting** — profiling only captures renders that happen between `profile start` and `profile stop`. Make sure the relevant interaction happens during that window.
132
142
  - **Use `--depth`** on large trees — a deep tree can produce a lot of output. Start with `--depth 3` or `--depth 4` and go deeper only on the subtree you care about.
133
143
 
@@ -98,3 +98,16 @@ If `Apps: 0 connected`:
98
98
  1. Check the app is running in dev mode
99
99
  2. Check the console for WebSocket connection errors
100
100
  3. Ensure no other DevTools instance is using port 8097
101
+ 4. If using `agent-browser`, make sure you're using **headed mode** (`--headed`) — headless Chromium does not properly execute the devtools connect script
102
+
103
+ ## Using with agent-browser
104
+
105
+ When automating the browser with `agent-browser`, you must use headed mode. Headless Chromium handles ES module script execution differently, which prevents the connect script from installing the devtools hook before React loads.
106
+
107
+ ```bash
108
+ # Headed mode is required for devtools to connect
109
+ agent-browser --session devtools --headed open http://localhost:5173/
110
+
111
+ # Verify connection
112
+ agent-react-devtools status
113
+ ```