{"_id":"@0xhex/react-xray","name":"@0xhex/react-xray","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@0xhex/react-xray","version":"0.1.0","description":"Inspect React Fiber state from the browser console after an app has rendered.","type":"module","sideEffects":false,"main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./iife":"./dist/index.iife.js"},"scripts":{"build":"tsup","dev":"tsup --watch","typecheck":"tsc --noEmit","test:browser":"node test/browser.mjs","test:package":"node scripts/verify-package.mjs","test":"npm run typecheck && npm run build && npm run test:browser && npm run test:package","release:check":"npm test && npm pack --dry-run","prepack":"npm run typecheck && npm run build"},"keywords":["react","devtools","inspector","debug","fiber","hooks","console"],"author":{"name":"Xayhem LLC"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/xayhemLLC/react-xray.git"},"homepage":"https://github.com/xayhemLLC/react-xray#readme","bugs":{"url":"https://github.com/xayhemLLC/react-xray/issues"},"engines":{"node":">=18"},"publishConfig":{"access":"public"},"devDependencies":{"playwright":"^1.58.2","react":"^18.3.1","react-dom":"^18.3.1","tsup":"^8.0.0","typescript":"^5.4.0"},"overrides":{"esbuild":"^0.28.1"},"_id":"@0xhex/react-xray@0.1.0","gitHead":"492e31b7823e7781f1b57cc6295916735d6d1e29","_nodeVersion":"24.7.0","_npmVersion":"11.5.1","dist":{"integrity":"sha512-AMsS+dYCjWkK5NZ8MIqNF/IFlOCN14Xl6afcg/QP6vib7x/wKjYXuAHTeUDGsufNbB+pMcNHSKP1baf5VcLj7A==","shasum":"c8e2a98fb81028364d54ae9f3eeb9b4c9b772b95","tarball":"https://registry.npmjs.org/@0xhex/react-xray/-/react-xray-0.1.0.tgz","fileCount":13,"unpackedSize":219371,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIHk21DNhNDsbwbioZzEiOXNTtvsS2iXLuG0An8RGCOTAAiAKLHYPUTjZSygV8FAMzoFmg+DX+ApPFKABUDmkCWwq7Q=="}]},"_npmUser":{"name":"0xhex","email":"longgonelegacies@gmail.com"},"directories":{},"maintainers":[{"name":"0xhex","email":"longgonelegacies@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/react-xray_0.1.0_1787908019582_0.12111020889264501"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-28T09:06:59.351Z","0.1.0":"2026-08-28T09:06:59.717Z","modified":"2026-08-28T09:07:00.077Z"},"maintainers":[{"name":"0xhex","email":"longgonelegacies@gmail.com"}],"description":"Inspect React Fiber state from the browser console after an app has rendered.","homepage":"https://github.com/xayhemLLC/react-xray#readme","keywords":["react","devtools","inspector","debug","fiber","hooks","console"],"repository":{"type":"git","url":"git+https://github.com/xayhemLLC/react-xray.git"},"author":{"name":"Xayhem LLC"},"bugs":{"url":"https://github.com/xayhemLLC/react-xray/issues"},"license":"MIT","readme":"# react-xray\n\nInspect React 18 applications from the browser console after they have already\nrendered. No extension or pre-load integration is required.\n\n> Use react-xray only on applications you own or are explicitly authorized to\n> inspect. React Fiber is private API, so compatibility is version-specific.\n\n```js\n// Run on an authorized page using the supported React version:\nconst x = ReactXRay.xray({ verbose: true });\nx.tree();\n```\n\n## Why?\n\nExisting tools like React DevTools require a browser extension. Libraries like\n[bippy](https://github.com/nickytonline/bippy) need to load *before* React.\n**react-xray's verified read path works after React has already rendered**: it\ndiscovers the existing root, finds components, reads props and hook state, and\nmaps DOM elements back to components.\n\n## Verified 0.1.0 contract\n\nThe release check proves, against React 18.3, that:\n\n- ESM and CommonJS imports expose `xray`.\n- The browser IIFE exposes `ReactXRay.xray`.\n- Post-render injection discovers a root and named components.\n- DOM inspection and state reads return the expected component.\n- State dispatch, event interception, highlighting, and cleanup work against a\n  deterministic local fixture.\n- The npm tarball works from a clean temporary consumer project.\n\n## Quick Start\n\nThe npm package is published as `@0xhex/react-xray`; the project and global\nbrowser API remain named react-xray and `ReactXRay`.\n\n### Console (one-liner)\n\n```js\nconst s=document.createElement('script');s.src='https://unpkg.com/@0xhex/react-xray@0.1.0/dist/index.iife.js';s.onload=()=>{window.x=ReactXRay.xray({verbose:true});x.tree()};document.head.append(s);\n```\n\n### CDN Script Tag\n\n```html\n<script src=\"https://unpkg.com/@0xhex/react-xray@0.1.0/dist/index.iife.js\"></script>\n<script>\n  const x = ReactXRay.xray({ verbose: true });\n</script>\n```\n\n### npm\n\n```bash\nnpm install @0xhex/react-xray\n```\n\n```js\nimport { xray } from '@0xhex/react-xray';\nconst x = xray({ verbose: true });\n```\n\n### Bookmarklet\n\nSee `demo/bookmarklet.js` — drag it to your bookmarks bar and click on any React page.\n\n### Reproducible local demo\n\n```bash\nnpm ci\nnpm run build\npython3 -m http.server 4173\n```\n\nOpen `http://localhost:4173/demo/`. The page renders React first, injects the\nlocal IIFE afterward, and displays the discovered components and counter state.\n\n## API Reference\n\n### `xray(options?)`\n\nInitialize react-xray. Returns an `XRayInstance`.\n\n| Option    | Type              | Default      | Description                              |\n|-----------|-------------------|--------------|------------------------------------------|\n| `global`  | `string \\| false` | `'__XRAY__'` | Attach instance to `window[global]`     |\n| `verbose` | `boolean`         | `false`       | Log lifecycle events to console         |\n\n### Finding Components\n\n```js\nx.find('Counter')         // all components named \"Counter\"\nx.find(/^App/)            // regex matching\nx.findOne('TodoItem')     // first match or null\n```\n\nReturns `ComponentInfo` objects:\n\n```ts\ninterface ComponentInfo {\n  name: string;\n  fiber: Fiber;\n  props: Record<string, any>;\n  hooks: HookInfo[];\n  element: Element | null;\n  key: string | null;\n  children: string[];\n}\n```\n\n### Inspecting Elements\n\n```js\n// Click an element in Elements panel, then:\nx.inspectSelected()\n\n// Or pass a DOM element directly:\nx.inspect(document.querySelector('.my-component'))\n```\n\n### Reading State\n\n```js\nconst counter = x.findOne('Counter');\nx.hooks(counter.fiber)           // all hooks\nx.props(counter.fiber)           // current props\nx.stateAt(counter.fiber, 0)     // first useState value\n```\n\n`HookInfo` shape:\n\n```ts\ninterface HookInfo {\n  index: number;\n  type: HookType;     // 'useState' | 'useEffect' | 'useRef' | ...\n  value: any;\n  dispatch?: Function; // present for useState/useReducer\n}\n```\n\n### Modifying State\n\n```js\nconst counter = x.findOne('Counter');\n\n// Set the first useState to 42:\nx.setState(counter.fiber, 0, 42);\n\n// Experimental; may not trigger a render:\nx.setProps(counter.fiber, { label: 'Modified!' });\n```\n\n### Watching Renders\n\nRender subscriptions require a compatible DevTools hook that React already\nknows about; injecting only after the initial render does not create that\nconnection retroactively.\n\n```js\n// Watch all renders:\nconst unsub = x.onRender((event) => {\n  console.log(`${event.name} ${event.phase}d`);\n});\n\n// Watch specific component:\nx.onRender('Counter', (event) => {\n  console.log('Counter rendered!', event.phase);\n});\n\n// Stop watching:\nunsub();\n```\n\n### Intercepting Events\n\n```js\n// Log all clicks:\nx.intercept('click', (e) => {\n  console.log('Click intercepted:', e.target);\n});\n\n// Block all clicks:\nx.intercept('click', (e) => {\n  console.log('Blocked click on', e.target);\n  return false; // stops propagation\n});\n```\n\n### Component Tree\n\n```js\nx.tree()      // print full tree\nx.tree(3)     // max depth of 3\n```\n\n### Visual Highlighting\n\n```js\nconst counter = x.findOne('Counter');\nx.highlight(counter.fiber);                    // default blue\nx.highlight(counter.fiber, 'rgba(255,0,0,0.3)'); // custom color\nx.unhighlight();                               // remove all\n```\n\n### Fiber Roots\n\n```js\nx.roots()  // array of root fibers\n```\n\n### Cleanup\n\n```js\nx.destroy()  // remove all listeners, overlays, global reference\n```\n\n## Recipes\n\n### 1. Explore an unfamiliar React app\n\n```js\nconst x = ReactXRay.xray({ verbose: true });\nx.tree();\nx.find(/./).forEach(c => console.log(c.name, c.props));\n```\n\n### 2. Find and modify a counter\n\n```js\nconst c = x.findOne('Counter');\nconsole.log('Current value:', x.stateAt(c.fiber, 0));\nx.setState(c.fiber, 0, 999);\n```\n\n### 3. Monitor re-renders when a compatible hook is connected\n\n```js\nx.onRender((e) => {\n  console.log(`[${e.phase}] ${e.name}`, e.fiber.memoizedProps);\n});\n```\n\n### 4. Intercept and log form submissions\n\n```js\nx.intercept('submit', (e) => {\n  const form = e.target;\n  const data = new FormData(form);\n  console.table(Object.fromEntries(data));\n  return false; // prevent submission\n});\n```\n\n### 5. Highlight all instances of a component\n\n```js\nx.find('Button').forEach(b => x.highlight(b.fiber, 'rgba(255,165,0,0.4)'));\n// Later: x.unhighlight()\n```\n\n### 6. Interactive element picker\n\n```js\ndocument.addEventListener('click', function pick(e) {\n  e.preventDefault();\n  e.stopPropagation();\n  const info = x.inspect(e.target);\n  if (info) {\n    console.log(info.name, info.props, info.hooks);\n    x.highlight(info.fiber);\n  }\n  document.removeEventListener('click', pick, true);\n}, true);\n```\n\n### 7. Dump all state in the app\n\n```js\nx.find(/./).forEach(c => {\n  const stateHooks = c.hooks.filter(h => h.type === 'useState');\n  if (stateHooks.length > 0) {\n    console.group(c.name);\n    stateHooks.forEach(h => console.log(`  state[${h.index}]:`, h.value));\n    console.groupEnd();\n  }\n});\n```\n\n## How It Works\n\nreact-xray uses three React internals techniques:\n\n### 1. DOM → Fiber Bridge\n\nReact stamps every host DOM node with a `__reactFiber$<hash>` property pointing to its fiber node. We discover this key once and cache it, giving us instant fiber access from any DOM element.\n\n### 2. DevTools Global Hook\n\nReact looks for `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` on load and calls `hook.inject(renderer)` + `hook.onCommitFiberRoot(...)` on every commit. If the hook already exists (DevTools extension), we wrap it. If not, we create one and discover existing roots from the DOM.\n\n### 3. Fiber Tree Traversal\n\nReact's fiber tree is a linked list: each fiber has `child` (first child), `sibling` (next peer), and `return` (parent). Walking this structure gives us the full component hierarchy with props, hooks state (`memoizedState` linked list), and DOM references.\n\n## Compatibility\n\n| React Version | 0.1.0 status |\n|---------------|--------------|\n| 18.3          | Verified release contract |\n| 19.x          | Unverified |\n| 17.x          | Unverified, best effort |\n| 16.8+         | Unverified, best effort |\n| < 16.8        | Unsupported |\n\n## Limitations\n\n- **Private API**: React can change Fiber fields without a semver guarantee.\n- **Production builds**: Hook type detection falls back to heuristics (no `_debugHookTypes`). Types may be reported as `unknown`.\n- **Render subscriptions**: post-render root discovery supports reads, but `onRender` needs a compatible DevTools hook that React already knows about to observe future commits.\n- **State mutation**: `setState` is best effort. It prefers the DevTools renderer API and otherwise dispatches through the hook queue.\n- **Props mutation**: `setProps` depends on renderer support or direct Fiber mutation and is experimental.\n- **Minified names**: Production builds minify component names. Use regex patterns or inspect from DOM elements instead.\n- **Server components**: React Server Components don't have client-side fibers.\n- **Strict mode**: Double-rendering in dev mode may cause duplicate render events.\n\n## Development and release verification\n\n```bash\nnpm ci\nnpx playwright install chromium\nnpm test\nnpm run release:check\n```\n\nSee [`RELEASE.md`](./RELEASE.md) for the exact publication and independent\nverification checklist. `SECURITY.md` documents the authorized-use boundary.\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-4eae458a8b60d5e81cb1c906e9ae034e"}