[
  {
    "path": ".changeset/README.md",
    "content": "# Changesets\n\nHello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works\nwith multi-package repos, or single-package repos to help you version and publish your code. You can\nfind the full documentation for it [in our repository](https://github.com/changesets/changesets)\n\nWe have a quick list of common questions to get you started engaging with this project in\n[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)\n"
  },
  {
    "path": ".changeset/config.json",
    "content": "{\n  \"$schema\": \"https://unpkg.com/@changesets/config@3.0.5/schema.json\",\n  \"changelog\": \"@changesets/cli/changelog\",\n  \"commit\": false,\n  \"fixed\": [],\n  \"linked\": [[\"react-scan\", \"@react-scan/extension\"]],\n  \"access\": \"public\",\n  \"baseBranch\": \"main\",\n  \"updateInternalDependencies\": \"patch\",\n  \"ignore\": [\"@react-scan/website\"]\n}\n"
  },
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, sex characteristics, gender identity and expression,\nlevel of experience, education, socio-economic status, nationality, personal\nappearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n- Using welcoming and inclusive language\n- Being respectful of differing viewpoints and experiences\n- Gracefully accepting constructive criticism\n- Focusing on what is best for the community\n- Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n- The use of sexualized language or imagery and unwelcome sexual attention or\n  advances\n- Trolling, insulting/derogatory comments, and personal or political attacks\n- Public or private harassment\n- Publishing others' private information, such as a physical or electronic\n  address, without explicit permission\n- Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at aiden.bai05@gmail.com. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4,\navailable at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html\n\nFor answers to common questions about this code of conduct, see\nhttps://www.contributor-covenant.org/faq"
  },
  {
    "path": ".github/workflows/build-extension.yml",
    "content": "name: Build Extension\n\non:\n  push:\n    branches:\n      - main\n    paths:\n      - 'packages/extension/**'\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n          ref: main\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: '20'\n\n      - name: Setup PNPM\n        uses: pnpm/action-setup@v2\n        with:\n          version: 9.x\n\n      - name: Install dependencies\n        run: pnpm install\n\n      - name: Get package info\n        id: package\n        run: |\n          echo \"name=$(node -p \"require('./packages/extension/package.json').name\")\" >> $GITHUB_OUTPUT\n          echo \"version=$(node -p \"require('./packages/extension/package.json').version\")\" >> $GITHUB_OUTPUT\n\n      - name: Build extensions\n        run: |\n          pnpm build\n          cd packages/extension\n          rm -rf build\n          pnpm pack:all\n\n      - name: Commit changes\n        if: github.ref == 'refs/heads/main'\n        run: |\n          git checkout main\n          git config --local user.email \"github-actions[bot]@users.noreply.github.com\"\n          git config --local user.name \"github-actions[bot]\"\n          git add -f packages/extension/build/*.zip\n          git diff --staged --quiet || (git commit -m \"chore: update extension builds [skip ci]\" && git push)\n"
  },
  {
    "path": ".github/workflows/pkg-pr-new.yaml",
    "content": "name: Publish Any Commit\non:\n  push:\n    branches:\n      - \"**\"\n  pull_request:\n    branches:\n      - \"**\"\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        node-version: [18]\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Install pnpm\n        uses: pnpm/action-setup@v4\n        with:\n          version: 9.1.0\n\n      - name: Use Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v4\n        with:\n          node-version: ${{ matrix.node-version }}\n          cache: \"pnpm\"\n\n      - name: Install dependencies\n        run: pnpm install --frozen-lockfile --strict-peer-dependencies=false\n\n      - name: Build\n        run: |\n          cd packages/scan\n          NODE_ENV=production pnpm build\n        env:\n          NODE_ENV: production\n\n      - name: Publish NPM Package to pkg-pr-new\n        run: pnpx pkg-pr-new publish ./packages/scan\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\n.DS_Store\n.env\ndist\n**/*.tgz\n*.log\nbuild\n!packages/extension/build/\nplaygrounds\n# SSL Certificates\nbin/certs/*.pem\nbin/certs/*.key\n.cursor\n# Playwright\ntest-results/\nplaywright-report/\n"
  },
  {
    "path": ".npmrc",
    "content": "prefer-workspace-packages=true\nlink-workspace-packages=true"
  },
  {
    "path": ".oxlintrc.json",
    "content": "{\n  \"$schema\": \"./node_modules/oxlint/configuration_schema.json\",\n  \"plugins\": [\"typescript\", \"react\", \"import\"],\n  \"ignorePatterns\": [\n    \"dist\",\n    \"build\",\n    \"node_modules\",\n    \"**/*.css\",\n    \"**/*.astro\"\n  ],\n  \"categories\": {},\n  \"rules\": {\n    \"no-unused-vars\": [\n      \"warn\",\n      {\n        \"vars\": \"all\",\n        \"args\": \"all\",\n        \"argsIgnorePattern\": \"^_\",\n        \"varsIgnorePattern\": \"^_\",\n        \"caughtErrors\": \"none\"\n      }\n    ],\n    \"no-unused-labels\": \"warn\",\n    \"no-unused-private-class-members\": \"warn\",\n    \"no-console\": \"warn\",\n    \"typescript/no-explicit-any\": \"warn\",\n    \"typescript/no-non-null-assertion\": \"warn\",\n    \"react/no-danger\": \"error\",\n    \"react-hooks/exhaustive-deps\": \"warn\"\n  }\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"editor.formatOnSave\": true,\n  \"css.lint.unknownAtRules\": \"ignore\",\n  \"oxc.lint.enable\": true,\n  \"[markdown]\": {\n    \"editor.defaultFormatter\": \"esbenp.prettier-vscode\"\n  },\n  \"[html]\": {\n    \"editor.defaultFormatter\": \"esbenp.prettier-vscode\"\n  },\n  \"typescript.tsdk\": \"node_modules/typescript/lib\"\n}\n"
  },
  {
    "path": "AGENTS.md",
    "content": "## General Rules\n\n- MUST: Use TypeScript interfaces over types.\n- MUST: Keep all types in the global scope.\n- MUST: Use arrow functions over function declarations\n- MUST: Never comment unless absolutely necessary.\n  - If the code is a hack (like a setTimeout or potentially confusing code), it must be prefixed with // HACK: reason for hack\n- MUST: Use kebab-case for files\n- MUST: Use descriptive names for variables (avoid shorthands, or 1-2 character names).\n  - Example: for .map(), you can use `innerX` instead of `x`\n  - Example: instead of `moved` use `didPositionChange`\n- MUST: Frequently re-evaluate and refactor variable names to be more accurate and descriptive.\n- MUST: Do not type cast (\"as\") unless absolutely necessary\n- MUST: Remove unused code and don't repeat yourself.\n- MUST: Always search the codebase, think of many solutions, then implement the most _elegant_ solution.\n- MUST: Put all magic numbers in `constants.ts` using `SCREAMING_SNAKE_CASE` with unit suffixes (`_MS`, `_PX`).\n- MUST: Put small, focused utility functions in `utils/` with one utility per file.\n- MUST: Use Boolean over !!.\n\n## Testing\n\nRun checks always before committing with:\n\n```bash\npnpm build\npnpm lint\npnpm format\n```\n"
  },
  {
    "path": "BROWSER_EXTENSION_GUIDE.md",
    "content": "# Browser Extension Installation Guide\n\n> [!NOTE]\n> The React Scan browser extension currently uses `react-scan@0.4.3`\n\n## Chrome\n\n> You can download the Chrome browser extension from the [Chrome Web Store](https://chromewebstore.google.com/detail/react-scan/anmmhkomejbdklkhoiloeaehppaffmdf). Below is a guide to installing the extension manually.\n\n1. Download the [`chrome-extension-v1.1.4.zip`](https://github.com/aidenybai/react-scan/tree/main/packages/extension/build) file.\n2. Unzip the file.\n3. Open Chrome and navigate to `chrome://extensions/`.\n4. Enable \"Developer mode\" if it is not already enabled.\n5. Click \"Load unpacked\" and select the unzipped folder (or drag the folder into the page).\n\n## Firefox\n\n> React Scan's Browser extension is still pending approvals from Firefox Add-ons. Below is a guide to installing the extension manually.\n\n1. Download the [`firefox-extension-v1.1.4.zip`](https://github.com/aidenybai/react-scan/tree/main/packages/extension/build) file.\n2. Unzip the file.\n3. Open Firefox and navigate to `about:debugging#/runtime/this-firefox`.\n4. Click \"Load Temporary Add-on...\"\n5. Select `manifest.json` from the unzipped folder\n\n## Brave\n\n> React Scan's Browser extension is still pending approvals from Brave Browser. Below is a guide to installing the extension manually.\n\n1. Download the [`brave-extension-v1.1.4.zip`](https://github.com/aidenybai/react-scan/tree/main/packages/extension/build) file.\n2. Unzip the file.\n3. Open Brave and navigate to `brave://extensions`.\n4. Click \"Load unpacked\" and select the unzipped folder (or drag the folder into the page).\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to React Scan\n\nFirst off, thanks for taking the time to contribute! ❤️\n\n## Table of Contents\n\n- [Contributing to React Scan](#contributing-to-react-scan)\n  - [Table of Contents](#table-of-contents)\n  - [Project Structure](#project-structure)\n  - [Development Setup](#development-setup)\n  - [Contributing Guidelines](#contributing-guidelines)\n    - [Commits](#commits)\n    - [Pull Request Process](#pull-request-process)\n    - [Development Workflow](#development-workflow)\n  - [Getting Help](#getting-help)\n\n## Project Structure\n\nThis is a monorepo containing several packages:\n\n- `packages/scan` - Core React Scan package\n- `packages/vite-plugin-react-scan` - Vite plugin for React Scan\n- `packages/extension` - VS Code extension\n\n## Development Setup\n\n1. **Clone and Install**\n   ```bash\n   git clone https://github.com/aidenybai/react-scan.git\n   cd react-scan\n   pnpm install\n   ```\n\n2. **Build all packages**\n   ```bash\n   pnpm build\n   ```\n\n3. **Testing React Scan**\n   ```bash\n   cd packages/scan\n   pnpm build:copy\n   ```\n   - This will build the package and then copy it to your clipboard as an IIFE (immedietely invoked function expression). This will allow you to paste it into the browser console to test it on any website\n\nhttps://github.com/user-attachments/assets/f279e664-479f-4e39-bff4-1bbfee30af22\n\n## Contributing Guidelines\n\n### Commits\n\nWe use conventional commits to ensure consistent commit messages:\n\n- `feat:` New features\n- `fix:` Bug fixes\n- `docs:` Documentation changes\n- `chore:` Maintenance tasks\n- `test:` Adding or updating tests\n- `refactor:` Code changes that neither fix bugs nor add features\n\nExample: `fix(scan): fix a typo`\n\n### Pull Request Process\n\n1. Fork the repository\n2. Create your feature branch (`git checkout -b feat/amazing-feature`)\n3. Commit your changes using conventional commits\n4. Push to your branch\n5. Open a Pull Request\n6. Ask for reviews (@pivanov, @RobPruzan are your friends in this journey)\n\n### Development Workflow\n\n1. **TypeScript**\n   - All code must be written in TypeScript\n   - Ensure strict type checking passes\n   - No `any` types unless absolutely necessary\n\n2. **Code Style**\n   - We use Biome for formatting and linting\n   - Run `pnpm format` to format code\n   - Run `pnpm lint` to check for issues\n\n3. **Documentation**\n   - Update relevant documentation\n   - Add JSDoc comments for public APIs\n   - Update README if needed\n\n## Getting Help\n- Check existing issues\n- Create a new issue\n\n<br />\n\n⚛️ Happy coding! 🚀\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright 2025 Aiden Bai, Million Software, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# <img src=\"https://github.com/aidenybai/react-scan/blob/main/.github/assets/logo.svg\" width=\"30\" height=\"30\" align=\"center\" /> React Scan\n\nReact Scan automatically detects performance issues in your React app.\n\n- Requires no code changes -- just drop it in\n- Highlights exactly the components you need to optimize\n- Always accessible through a toolbar on page\n\n### Quick Start\n\n```bash\nnpx -y react-scan@latest init\n```\n\n### [**Try out a demo! →**](https://react-scan.million.dev)\n<img\n  src=\"https://github.com/user-attachments/assets/c21b3afd-c7e8-458a-a760-9a027be7dc02\"\n  alt=\"React Scan in action\"\n  width=\"600\"\n/>\n\n## Install\n\nThe `init` command will automatically detect your framework, install `react-scan` via npm, and set up your project.\n\n```bash\nnpx -y react-scan@latest init\n```\n\n### Manual Installation\n\nInstall the package:\n\n```bash\nnpm install -D react-scan\n```\n\nThen add the script tag to your app. Pick the guide for your framework:\n\n#### Script Tag\n\nPaste this before any scripts in your `index.html`:\n\n```html\n<!-- paste this BEFORE any scripts -->\n<script\n  crossOrigin=\"anonymous\"\n  src=\"//unpkg.com/react-scan/dist/auto.global.js\"\n></script>\n```\n\n#### Next.js (App Router)\n\nAdd this inside of your `app/layout.tsx`:\n\n```tsx\nimport Script from \"next/script\";\n\nexport default function RootLayout({ children }) {\n  return (\n    <html>\n      <head>\n        <Script\n          src=\"//unpkg.com/react-scan/dist/auto.global.js\"\n          crossOrigin=\"anonymous\"\n          strategy=\"beforeInteractive\"\n        />\n      </head>\n      <body>{children}</body>\n    </html>\n  );\n}\n```\n\n#### Next.js (Pages Router)\n\nAdd this into your `pages/_document.tsx`:\n\n```tsx\nimport { Html, Head, Main, NextScript } from \"next/document\";\nimport Script from \"next/script\";\n\nexport default function Document() {\n  return (\n    <Html lang=\"en\">\n      <Head>\n        <Script\n          src=\"//unpkg.com/react-scan/dist/auto.global.js\"\n          crossOrigin=\"anonymous\"\n          strategy=\"beforeInteractive\"\n        />\n      </Head>\n      <body>\n        <Main />\n        <NextScript />\n      </body>\n    </Html>\n  );\n}\n```\n\n#### Vite\n\nExample `index.html` with React Scan enabled:\n\n```html\n<!doctype html>\n<html lang=\"en\">\n  <head>\n    <script\n      crossOrigin=\"anonymous\"\n      src=\"//unpkg.com/react-scan/dist/auto.global.js\"\n    ></script>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>\n```\n\n#### Remix\n\nAdd this inside your `app/root.tsx`:\n\n```tsx\nimport { Links, Meta, Outlet, Scripts } from \"@remix-run/react\";\n\nexport default function App() {\n  return (\n    <html>\n      <head>\n        <Meta />\n        <script\n          crossOrigin=\"anonymous\"\n          src=\"//unpkg.com/react-scan/dist/auto.global.js\"\n        />\n        <Links />\n      </head>\n      <body>\n        <Outlet />\n        <Scripts />\n      </body>\n    </html>\n  );\n}\n```\n\n### Browser Extension\n\nInstall the extension by following the guide [here](https://github.com/aidenybai/react-scan/blob/main/BROWSER_EXTENSION_GUIDE.md).\n\n## API Reference\n\n<details>\n<summary><code>Options</code></summary>\n\n<br />\n\n```tsx\nexport interface Options {\n  /**\n   * Enable/disable scanning\n   * @default true\n   */\n  enabled?: boolean;\n\n  /**\n   * Force React Scan to run in production (not recommended)\n   * @default false\n   */\n  dangerouslyForceRunInProduction?: boolean;\n\n  /**\n   * Log renders to the console\n   * @default false\n   */\n  log?: boolean;\n\n  /**\n   * Show toolbar bar\n   * @default true\n   */\n  showToolbar?: boolean;\n\n  /**\n   * Animation speed\n   * @default \"fast\"\n   */\n  animationSpeed?: \"slow\" | \"fast\" | \"off\";\n\n  onCommitStart?: () => void;\n  onRender?: (fiber: Fiber, renders: Array<Render>) => void;\n  onCommitFinish?: () => void;\n}\n```\n\n</details>\n\n- `scan(options: Options)`: Imperative API to start scanning\n- `useScan(options: Options)`: Hook API to start scanning\n- `setOptions(options: Options): void`: Set options at runtime\n- `getOptions()`: Get the current options\n- `onRender(Component, onRender: (fiber: Fiber, render: Render) => void)`: Hook into a specific component's renders\n\n## Why React Scan?\n\nReact can be tricky to optimize.\n\nThe issue is that component props are compared by reference, not value. This is intentional -- rendering can be cheap to run.\n\nHowever, this makes it easy to accidentally cause unnecessary renders, making the app slow. Even production apps with hundreds of engineers can't fully optimize their apps (see [GitHub](https://github.com/aidenybai/react-scan/blob/main/.github/assets/github.mp4), [Twitter](https://github.com/aidenybai/react-scan/blob/main/.github/assets/twitter.mp4), and [Instagram](https://github.com/aidenybai/react-scan/blob/main/.github/assets/instagram.mp4)).\n\n```jsx\n<ExpensiveComponent onClick={() => alert(\"hi\")} style={{ color: \"purple\" }} />\n```\n\nReact Scan helps you identify these issues by automatically detecting and highlighting renders that cause performance issues.\n\n## Resources & Contributing\n\nWant to try it out? Check the [demo](https://react-scan.million.dev).\n\nLooking to contribute? Check the [Contributing Guide](https://github.com/aidenybai/react-scan/blob/main/CONTRIBUTING.md).\n\nWant to talk to the community? Join our [Discord](https://discord.gg/X9yFbcV2rF).\n\nFind a bug? Head to our [issue tracker](https://github.com/aidenybai/react-scan/issues).\n\n[**→ Start contributing on GitHub**](https://github.com/aidenybai/react-scan/blob/main/CONTRIBUTING.md)\n\n## Acknowledgments\n\n- [React Devtools](https://react.dev/learn/react-developer-tools) for the initial idea of highlighting renders\n- [Million Lint](https://million.dev) for scanning and linting approaches\n- [Why Did You Render?](https://github.com/welldone-software/why-did-you-render) for the concept of detecting unnecessary renders\n\n## License\n\nReact Scan is [MIT-licensed](LICENSE) open-source software by Aiden Bai, [Million Software, Inc.](https://million.dev), and [contributors](https://github.com/aidenybai/react-scan/graphs/contributors).\n"
  },
  {
    "path": "bin/generate-certs.sh",
    "content": "#!/bin/bash\n\nmkdir -p bin/certs\nopenssl req -x509 -newkey rsa:2048 -keyout bin/certs/key.pem -out bin/certs/cert.pem -days 365 -nodes -subj \"/CN=127.0.0.1\"\n"
  },
  {
    "path": "bin/serve-scan.sh",
    "content": "#!/bin/bash\n\n# Determine the directory of the script\nSCRIPT_DIR=$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\n\n# Default values\nDEFAULT_PATH=\"./packages/scan/dist\"\nDEFAULT_PORT=\"4000\"\nDEFAULT_CERT=\"$SCRIPT_DIR/certs/cert.pem\"\nDEFAULT_KEY=\"$SCRIPT_DIR/certs/key.pem\"\n\n# Positional arguments\nSERVE_PATH=\"$1\" # First argument is the path\n\n# Get optional flags\nshift # Remove the first argument from the list\nwhile [[ \"$#\" -gt 0 ]]; do\n    case $1 in\n        --port) PORT_ARG=\"$2\"; shift ;;\n        --cert) CERT_ARG=\"$2\"; shift ;;\n        --key) KEY_ARG=\"$2\"; shift ;;\n        *) echo \"Unknown parameter: $1\" >&2; exit 1 ;;\n    esac\n    shift\ndone\n\n# Use provided arguments or defaults\nSERVE_PATH=\"${SERVE_PATH:-$DEFAULT_PATH}\"\nSERVE_PORT=\"${PORT_ARG:-$DEFAULT_PORT}\"\nSERVE_CERT=\"${CERT_ARG:-$DEFAULT_CERT}\"\nSERVE_KEY=\"${KEY_ARG:-$DEFAULT_KEY}\"\n\n# Run the server with CORS enabled\nhttp-server \"$SERVE_PATH\" -p \"$SERVE_PORT\" --ssl --cert \"$SERVE_CERT\" --key \"$SERVE_KEY\" --cors\n"
  },
  {
    "path": "docs/installation/astro.md",
    "content": "# Astro Guide\n\n## As a script tag\n\nAdd the script tag to your root layout.\n\nRefer to the [CDN Guide](https://github.com/aidenybai/react-scan/blob/main/docs/installation/cdn.md) for the available URLs.\n\n```astro\n<!doctype html>\n<html lang=\"en\">\n  <head>\n    <script is:inline src=\"https://unpkg.com/react-scan/dist/auto.global.js\" />\n\n    <!-- rest of your scripts go under -->\n  </head>\n  <body>\n    <!-- ... -->\n  </body>\n</html>\n```\n\n## As a module import\n\nAdd the script to your root layout\n\n```astro\n<!doctype html>\n<html lang=\"en\">\n  <head>\n    <script>\n      import { scan } from 'react-scan';\n\n      scan({\n        enabled: true,\n      });\n    </script>\n    <!-- rest of your scripts go under -->\n  </head>\n  <body>\n    <!-- ... -->\n  </body>\n</html>\n```\n\nIf you want react-scan to also run in production, use the react-scan/all-environments import path\n```diff\n- import { scan } from \"react-scan\";\n+ import { scan } from \"react-scan/all-environments\";\n```\n"
  },
  {
    "path": "docs/installation/cdn.md",
    "content": "# CDN\n\nYou can choose one of the following URLs to initialize React Scan via CDN.\n\n## Usage\n\n```html\n<script src=\"https://cdn.jsdelivr.net/npm/react-scan/dist/auto.global.js\"></script>\n```\n\n## Available URLs\n\n### JSDelivr\n\n```txt\nhttps://cdn.jsdelivr.net/npm/react-scan/dist/auto.global.js\n```\n\n### UNPKG\n\n```txt\nhttps://unpkg.com/react-scan/dist/auto.global.js\n```\n"
  },
  {
    "path": "docs/installation/create-react-app.md",
    "content": "# Create React App (CRA) Guide\n\n## As a script tag\n\nAdd the script tag to your `index.html`.\n\nRefer to the [CDN Guide](https://github.com/aidenybai/react-scan/blob/main/docs/installation/cdn.md) for the available URLs.\n\n```html\n<!doctype html>\n<html lang=\"en\">\n  <head>\n    <script src=\"https://unpkg.com/react-scan/dist/auto.global.js\"></script>\n\n    <!-- rest of your scripts go under -->\n  </head>\n  <body>\n    <!-- ... -->\n  </body>\n</html>\n```\n\n## As a module import\n\nIn your project entrypoint (e.g. `src/index`, `src/main`):\n\n```jsx\n// src/index.jsx\n\n// must be imported before React and React DOM\nimport { scan } from \"react-scan\";\nimport React from \"react\";\n\nscan({\n  enabled: true,\n});\n```\nIf you want react-scan to also run in production, use the react-scan/all-environments import path\n```diff\n- import { scan } from \"react-scan\";\n+ import { scan } from \"react-scan/all-environments\";\n```\n\n> [!CAUTION]\n> React Scan must be imported before React (and other React renderers like React DOM) in your entire project, as it needs to hijack React DevTools before React gets to access it.\n"
  },
  {
    "path": "docs/installation/next-js-app-router.md",
    "content": "# NextJS App Router Guide\n\n## As a script tag\n\nAdd the script tag to your `app/layout`.\n\nRefer to the [CDN Guide](https://github.com/aidenybai/react-scan/blob/main/docs/installation/cdn.md) for the available URLs.\n\n```jsx\n// app/layout\nexport default function RootLayout({ children }) {\n  return (\n    <html lang=\"en\">\n      <head>\n        <script src=\"https://unpkg.com/react-scan/dist/auto.global.js\" />\n        {/* rest of your scripts go under */}\n      </head>\n      <body>{children}</body>\n    </html>\n  );\n}\n```\n\n## As a module import\n\nCreate a `<ReactScan>` client component:\n\n```jsx\n// path/to/ReactScanComponent\n\n\"use client\";\n// react-scan must be imported before react\nimport { scan } from \"react-scan\";\nimport { JSX, useEffect } from \"react\";\n\nexport function ReactScan(): JSX.Element {\n  useEffect(() => {\n    scan({\n      enabled: true,\n    });\n  }, []);\n\n  return <></>;\n}\n```\n\nImport the `<ReactScan>` component into `app/layout`:\n\n```jsx\n// app/layout\n\n// This component must be the top-most import in this file!\nimport { ReactScan } from \"path/to/ReactScanComponent\";\n\n// ...\n\nexport default function RootLayout({ children }) {\n  return (\n    <html lang=\"en\">\n      <ReactScan />\n      <body>{children}</body>\n    </html>\n  );\n}\n```\n\nIf you want react-scan to also run in production, use the react-scan/all-environments import path\n```diff\n- import { scan } from \"react-scan\";\n+ import { scan } from \"react-scan/all-environments\";\n```"
  },
  {
    "path": "docs/installation/next-js-page-router.md",
    "content": "# NextJS Page Router Guide\n\n## As a script tag\n\nAdd the script tag to your `pages/_document`\n\nRefer to the [CDN Guide](https://github.com/aidenybai/react-scan/blob/main/docs/installation/cdn.md) for the available URLs.\n\n```jsx\n// pages/_document\nimport { Html, Head, Main, NextScript } from \"next/document\";\n\nexport default function Document() {\n  return (\n    <Html lang=\"en\">\n      <Head>\n        <script src=\"https://unpkg.com/react-scan/dist/auto.global.js\" />\n\n        {/* rest of your scripts go under */}\n      </Head>\n      <body>\n        <Main />\n        <NextScript />\n      </body>\n    </Html>\n  );\n}\n```\n\n## As a module import\n\nAdd the following code to your `App` component in `pages/_app`:\n\n```jsx\n// pages/_app\n\n// react-scan must be the top-most import\nimport { scan } from \"react-scan\";\nimport { useEffect } from \"react\";\n\nexport default function App({ Component, pageProps }) {\n  useEffect(() => {\n    // Make sure to run React Scan after hydration\n    scan({\n      enabled: true,\n    });\n  }, []);\n  return <Component {...pageProps} />;\n}\n```\n\n\nIf you want react-scan to also run in production, use the react-scan/all-environments import path\n```diff\n- import { scan } from \"react-scan\";\n+ import { scan } from \"react-scan/all-environments\";\n```"
  },
  {
    "path": "docs/installation/parcel.md",
    "content": "# Parcel Guide\n\n## As a script tag\n\nAdd the script tag to your `index.html`\n\nRefer to the [CDN Guide](https://github.com/aidenybai/react-scan/blob/main/docs/installation/cdn.md) for the available URLs.\n\n```html\n<!doctype html>\n<html lang=\"en\">\n  <head>\n    <script src=\"https://unpkg.com/react-scan/dist/auto.global.js\"></script>\n\n    <!-- rest of your scripts go under -->\n  </head>\n  <body>\n    <!-- ... -->\n  </body>\n</html>\n```\n"
  },
  {
    "path": "docs/installation/react-router.md",
    "content": "# React Router v7 Guide\n\n## As a script tag\n\nAdd the script tag to your `Layout` component in the `app/root`.\n\nRefer to the [CDN Guide](https://github.com/aidenybai/react-scan/blob/main/docs/installation/cdn.md) for the available URLs.\n\n```jsx\n// app/root\n// ...\nexport function Layout({ children }: { children: React.ReactNode }) {\n  return (\n    <html lang=\"en\">\n      <head>\n        <script src=\"https://unpkg.com/react-scan/dist/auto.global.js\" />\n        <meta charSet=\"utf-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n        <Meta />\n        <Links />\n      </head>\n      <body>\n        {children}\n        <ScrollRestoration />\n        <Scripts />\n      </body>\n    </html>\n  );\n}\n// ...\n```\n\n> [!CAUTION]\n> This only works for React 19\n\n## As an import\n\nAdd the following code to your `app/root`\n\n```jsx\n// app/root\n\n// Must be imported before React Router\nimport { scan } from \"react-scan\"; \nimport { Links, Meta, Scripts, ScrollRestoration } from \"react-router\";\nimport { useEffect } from \"react\";\n\nexport function Layout({ children }) {\n  useEffect(() => {\n    // Make sure to run react-scan only after hydration\n    scan({\n      enabled: true,\n    });\n  }, []);\n\n  return (\n    <html lang=\"en\">\n      <head>\n        <meta charSet=\"utf-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n        <Meta />\n        <Links />\n      </head>\n      <body>\n        {children}\n        <ScrollRestoration />\n        <Scripts />\n      </body>\n    </html>\n  );\n}\n\n// ...\n```\n\nIf you want react-scan to also run in production, use the react-scan/all-environments import path\n```diff\n- import { scan } from \"react-scan\";\n+ import { scan } from \"react-scan/all-environments\";\n```\n\n> [!CAUTION]\n> React Scan must be imported before React (and other React renderers like React DOM), as well as React Router, in your entire project, as it needs to hijack React DevTools before React gets to access it.\n"
  },
  {
    "path": "docs/installation/remix.md",
    "content": "# Remix Guide\n\n## As a script tag\n\nAdd the script tag to your `<Layout>` component in `app/root`.\n\nRefer to the [CDN Guide](https://github.com/aidenybai/react-scan/blob/main/docs/installation/cdn.md) for the available URLs.\n\n```jsx\n// app/root.jsx\nimport {\n  Links,\n  Meta,\n  Scripts,\n  ScrollRestoration,\n} from \"@remix-run/react\";\n\nexport function Layout({ children }: { children: React.ReactNode }) {\n  return (\n    <html lang=\"en\">\n      <head>\n        {/* Must run before any of your scripts */}\n        <script src=\"https://unpkg.com/react-scan/dist/auto.global.js\" />\n        <meta charSet=\"utf-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n        <Meta />\n        <Links />\n      </head>\n      <body>\n        {children}\n        <ScrollRestoration />\n        <Scripts />\n      </body>\n    </html>\n  );\n}\n\n// ...\n```\n\n> [!CAUTION]\n> This only works for React 19\n\n## As a module import\n\nAdd the following code to your `app/root`:\n\n```jsx\n// app/root.jsx\nimport { scan } from \"react-scan\"; // Must be imported before Remix\nimport {\n  Links,\n  Meta,\n  Outlet,\n  Scripts,\n  ScrollRestoration,\n} from \"@remix-run/react\";\n\nexport function Layout({ children }) {\n  useEffect(() => {\n    // Make sure to run React Scan after hydration\n    scan({\n      enabled: true,\n    });\n  }, []);\n\n  return (\n    <html lang=\"en\">\n      <head>\n        <meta charSet=\"utf-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n        <Meta />\n        <Links />\n      </head>\n      <body>\n        {children}\n        <ScrollRestoration />\n        <Scripts />\n      </body>\n    </html>\n  );\n}\n\nexport default function App() {\n  return <Outlet />;\n}\n```\n\nIf you want react-scan to also run in production, use the react-scan/all-environments import path\n```diff\n- import { scan } from \"react-scan\";\n+ import { scan } from \"react-scan/all-environments\";\n```\n\n> [!CAUTION]\n> React Scan must be imported before React (and other React renderers like React DOM), as well as Remix, in your entire project, as it needs to hijack React DevTools before React gets to access it.\n\nAlternatively you can also do the following code in `app/entry.client`:\n\n```jsx\n// app/entry.client.jsx\nimport { RemixBrowser } from \"@remix-run/react\";\nimport { StrictMode, startTransition } from \"react\";\nimport { hydrateRoot } from \"react-dom/client\";\nimport { scan } from \"react-scan\";\n\nscan({\n  enabled: true,\n});\n\n// Hydration must happen in sync!\n// startTransition(() => {\nhydrateRoot(\n  document,\n  <StrictMode>\n    <RemixBrowser />\n  </StrictMode>\n);\n// });\n```\n\n> [!CAUTION]\n> This only works for React 19\n"
  },
  {
    "path": "docs/installation/rsbuild.md",
    "content": "# Rsbuild Guide\n\n## As a script tag\n\nIf you are using Rsbuild's default HTML template, add the script tag via [html.tags](https://rsbuild.dev/config/html/tags).\n\nRefer to the [CDN Guide](https://github.com/aidenybai/react-scan/blob/main/docs/installation/cdn.md) for the available URLs.\n\n```ts\n// rsbuild.config.ts\nexport default {\n  html: {\n    tags: [\n      {\n        tag: \"script\",\n        attrs: {\n          src: \"https://cdn.jsdelivr.net/npm/react-scan/dist/auto.global.js\",\n        },\n        append: false,\n      },\n    ],\n  },\n};\n```\n\nIf you are using a custom HTML template, add the script tag to your template file.\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <script src=\"https://cdn.jsdelivr.net/npm/react-scan/dist/auto.global.js\"></script>\n\n    <!-- rest of your scripts go under -->\n  </head>\n  <body>\n    <!-- ... -->\n  </body>\n</html>\n```\n\n## As a module import\n\nIn your project entrypoint (e.g. `src/index`, `src/main`):\n\n```jsx\n// src/index.jsx\n\n// must be imported before React and React DOM\nimport { scan } from \"react-scan\";\nimport React from \"react\";\n\nscan({\n  enabled: true,\n});\n```\n\nIf you want react-scan to also run in production, use the react-scan/all-environments import path\n```diff\n- import { scan } from \"react-scan\";\n+ import { scan } from \"react-scan/all-environments\";\n```\n\n> [!CAUTION]\n> React Scan must be imported before React (and other React renderers like React DOM) in your entire project, as it needs to hijack React DevTools before React gets to access it.\n"
  },
  {
    "path": "docs/installation/tanstack-start.md",
    "content": "# TanStack Router Guide\n\n## As a script tag\n\nAdd the script tag to your `<RootDocument>` component at `app/routes/__root`.\n\nRefer to the [CDN Guide](https://github.com/aidenybai/react-scan/blob/main/docs/installation/cdn.md) for the available URLs.\n\n```jsx\n// app/routes/__root\nimport { Meta, Scripts } from \"@tanstack/start\";\n// ...\n\nfunction RootDocument({ children }) {\n  return (\n    <html>\n      <head>\n        <script src=\"https://unpkg.com/react-scan/dist/auto.global.js\" />\n        <Meta />\n      </head>\n      <body>\n        {children}\n        <Scripts />\n      </body>\n    </html>\n  );\n}\n\n// ...\n```\n\n> [!CAUTION]\n> This only works for React 19\n\n## As a module import\n\nAdd the following code to your `<RootDocument>` component at `app/routes/__root`:\n\n```jsx\n// app/routes/__root\n\n// react-scan must be imported before React and TanStack Start\nimport { scan } from \"react-scan\";\nimport { Meta, Scripts } from \"@tanstack/start\";\nimport { useEffect } from \"react\";\n\n// ...\n\nfunction RootDocument({ children }) {\n  useEffect(() => {\n    // Make sure to run this only after hydration\n    scan({\n      enabled: true,\n    });\n  }, []);\n  return (\n    <html>\n      <head>\n        <Meta />\n      </head>\n      <body>\n        {children}\n        <Scripts />\n      </body>\n    </html>\n  );\n}\n```\n\n> [!CAUTION]\n> React Scan must be imported before React (and other React renderers like React DOM) in your entire project, as it needs to hijack React DevTools before React gets to access it.\n\nAlternatively you can also do the following code in `app/client`:\n\n```jsx\n// app/client\nimport { scan } from \"react-scan\"; // must be imported before React and React DOM\nimport { hydrateRoot } from \"react-dom/client\";\nimport { StartClient } from \"@tanstack/start\";\nimport { createRouter } from \"./router\";\n\nscan({\n  enabled: true,\n});\n\nconst router = createRouter();\n\nhydrateRoot(document, <StartClient router={router} />);\n```\n\n> [!CAUTION]\n> This only works for React 19\n\nIf you want react-scan to also run in production, use the react-scan/all-environments import path\n\n```diff\n- import { scan } from \"react-scan\";\n+ import { scan } from \"react-scan/all-environments\";\n```\n"
  },
  {
    "path": "docs/installation/vite.md",
    "content": "# Vite Guide\n\n## As a script tag\n\nAdd the script tag to your `index.html`.\n\nRefer to the [CDN Guide](https://github.com/aidenybai/react-scan/blob/main/docs/installation/cdn.md) for the available URLs.\n\n```html\n<!doctype html>\n<html lang=\"en\">\n  <head>\n    <script src=\"https://unpkg.com/react-scan/dist/auto.global.js\"></script>\n\n    <!-- rest of your scripts go under -->\n  </head>\n  <body>\n    <!-- ... -->\n  </body>\n</html>\n```\n\n## As a module import\n\nIn your project entrypoint (e.g. `src/index`, `src/main`):\n\n```jsx\n// src/index\nimport { scan } from \"react-scan\"; // must be imported before React and React DOM\nimport React from \"react\";\n\nscan({\n  enabled: true,\n});\n```\n\nIf you want react-scan to also run in production, use the react-scan/all-environments import path\n\n```diff\n- import { scan } from \"react-scan\";\n+ import { scan } from \"react-scan/all-environments\";\n```\n\n\n> [!CAUTION]\n> React Scan must be imported before React (and other React renderers like React DOM) in your entire project, as it needs to hijack React DevTools before React gets to access it.\n\n## Vite plugin\n\nTODO\n\n## Preserving component names\n\nTODO\n"
  },
  {
    "path": "e2e/helpers.ts",
    "content": "import { type Page } from '@playwright/test';\n\nexport const FIXTURE_URL = '/?example=e2e-fixture';\n\nexport async function gotoFixture(page: Page): Promise<void> {\n  await page.goto(FIXTURE_URL);\n  await page.waitForSelector('[data-testid=\"heading\"]', { timeout: 10_000 });\n  // Wait for React Scan to boot and expose __REACT_SCAN__\n  await page.waitForFunction(\n    () => typeof (window as any).__REACT_SCAN__?.ReactScanInternals !== 'undefined',\n    { timeout: 15_000 },\n  );\n  // Install a render counter by patching the onRender option on the signal\n  await page.evaluate(() => {\n    (window as any).__E2E_RENDER_COUNT__ = 0;\n    const internals = (window as any).__REACT_SCAN__?.ReactScanInternals;\n    if (internals?.options) {\n      const prev = internals.options.value;\n      const prevOnRender = prev.onRender;\n      internals.options.value = {\n        ...prev,\n        onRender: (...args: any[]) => {\n          (window as any).__E2E_RENDER_COUNT__++;\n          if (prevOnRender) prevOnRender(...args);\n        },\n      };\n    }\n  });\n  // Wait for initial mount renders to settle then reset\n  await page.waitForTimeout(500);\n  await page.evaluate(() => {\n    (window as any).__E2E_RENDER_COUNT__ = 0;\n  });\n}\n\nexport async function getRenderCount(page: Page): Promise<number> {\n  return page.evaluate(() => (window as any).__E2E_RENDER_COUNT__ ?? 0);\n}\n\nexport async function waitForRenders(\n  page: Page,\n  timeout = 5000,\n): Promise<number> {\n  const startCount = await getRenderCount(page);\n  return page.evaluate(\n    ({ start, t }) => {\n      return new Promise<number>((resolve) => {\n        const check = () => {\n          const current = (window as any).__E2E_RENDER_COUNT__ ?? 0;\n          if (current > start) {\n            resolve(current - start);\n            return true;\n          }\n          return false;\n        };\n        if (check()) return;\n        const interval = setInterval(() => {\n          if (check()) clearInterval(interval);\n        }, 50);\n        setTimeout(() => {\n          clearInterval(interval);\n          resolve(0);\n        }, t);\n      });\n    },\n    { start: startCount, t: timeout },\n  );\n}\n\nexport async function isReactScanActive(page: Page): Promise<boolean> {\n  return page.evaluate(() => {\n    return typeof (window as any).__REACT_SCAN__ !== 'undefined';\n  });\n}\n\nexport async function hasShadowRoot(page: Page): Promise<boolean> {\n  return page.evaluate(() => {\n    return document.getElementById('react-scan-root')?.shadowRoot != null;\n  });\n}\n"
  },
  {
    "path": "e2e/inspector.spec.ts",
    "content": "import { test, expect } from '@playwright/test';\nimport { gotoFixture } from './helpers';\n\ntest.describe('Inspector', () => {\n  test.beforeEach(async ({ page }) => {\n    await gotoFixture(page);\n  });\n\n  test('inspect state is available in React Scan internals', async ({ page }) => {\n    const hasInspectState = await page.evaluate(() => {\n      const scan = (window as any).__REACT_SCAN__;\n      if (!scan?.ReactScanInternals?.Store) return false;\n      const inspectState = scan.ReactScanInternals.Store.inspectState;\n      return inspectState !== undefined && inspectState !== null;\n    });\n\n    expect(hasInspectState).toBe(true);\n  });\n\n  test('inspect state starts as inspect-off', async ({ page }) => {\n    const kind = await page.evaluate(() => {\n      const scan = (window as any).__REACT_SCAN__;\n      return scan?.ReactScanInternals?.Store?.inspectState?.value?.kind ?? null;\n    });\n\n    expect(kind).toBe('inspect-off');\n  });\n\n  test('shadow DOM contains toolbar elements', async ({ page }) => {\n    const elementCount = await page.evaluate(() => {\n      const root = document.getElementById('react-scan-root');\n      return root?.shadowRoot?.querySelectorAll('*').length ?? 0;\n    });\n    expect(elementCount).toBeGreaterThan(5);\n  });\n\n  test('inspect state can be set programmatically', async ({ page }) => {\n    const activated = await page.evaluate(() => {\n      const scan = (window as any).__REACT_SCAN__;\n      if (!scan?.ReactScanInternals?.Store?.inspectState) return false;\n      scan.ReactScanInternals.Store.inspectState.value = { kind: 'focused', focusedDomElement: null };\n      return scan.ReactScanInternals.Store.inspectState.value.kind === 'focused';\n    });\n\n    expect(activated).toBe(true);\n  });\n});\n"
  },
  {
    "path": "e2e/notifications.spec.ts",
    "content": "import { test, expect } from '@playwright/test';\nimport { gotoFixture } from './helpers';\n\ntest.describe('Notifications', () => {\n  test.beforeEach(async ({ page }) => {\n    await gotoFixture(page);\n  });\n\n  test('slow interaction is detected and recorded', async ({ page }) => {\n    await page.click('[data-testid=\"trigger-slow\"]');\n    await page.waitForTimeout(2000);\n\n    const hasActiveStore = await page.evaluate(() => {\n      const scan = (window as any).__REACT_SCAN__;\n      if (!scan?.ReactScanInternals?.Store) return false;\n      // Verify the notification system is wired up (interactionListeningForRenders is a function when active)\n      return typeof scan.ReactScanInternals.Store.interactionListeningForRenders === 'function';\n    });\n\n    expect(hasActiveStore).toBe(true);\n  });\n\n  test('notification system initializes with the toolbar', async ({ page }) => {\n    const hasCanvas = await page.evaluate(() => {\n      return document.querySelectorAll('canvas').length > 0;\n    });\n    expect(hasCanvas).toBe(true);\n  });\n\n  test('repeated slow interactions do not break the toolbar', async ({ page }) => {\n    for (let i = 0; i < 3; i++) {\n      await page.click('[data-testid=\"trigger-slow\"]');\n      await page.waitForTimeout(500);\n    }\n    await page.waitForTimeout(2000);\n\n    const shadowContent = await page.evaluate(() => {\n      const root = document.getElementById('react-scan-root');\n      return root?.shadowRoot?.innerHTML ?? '';\n    });\n    expect(shadowContent.length).toBeGreaterThan(100);\n  });\n});\n"
  },
  {
    "path": "e2e/outlines.spec.ts",
    "content": "import { test, expect, type Page } from '@playwright/test';\nimport { gotoFixture, getRenderCount } from './helpers';\n\nasync function clickAndCountRenders(\n  page: Page,\n  selector: string,\n  waitMs = 1000,\n): Promise<number> {\n  await page.evaluate(() => {\n    (window as any).__E2E_RENDER_COUNT__ = 0;\n  });\n  await page.click(selector);\n  await page.waitForTimeout(waitMs);\n  return getRenderCount(page);\n}\n\ntest.describe('Render Outlines', () => {\n  test.beforeEach(async ({ page }) => {\n    await gotoFixture(page);\n  });\n\n  test('state update triggers render tracking', async ({ page }) => {\n    const count = await clickAndCountRenders(page, '[data-testid=\"increment\"]');\n    expect(count).toBeGreaterThan(0);\n  });\n\n  test('rapid updates produce multiple tracked renders', async ({ page }) => {\n    const count = await clickAndCountRenders(page, '[data-testid=\"trigger-rapid\"]', 2000);\n    expect(count).toBeGreaterThan(5);\n  });\n\n  test('outline canvas exists on the page', async ({ page }) => {\n    const hasCanvas = await page.evaluate(() => {\n      return document.querySelectorAll('canvas').length > 0;\n    });\n    expect(hasCanvas).toBe(true);\n  });\n\n  test('context change triggers render tracking', async ({ page }) => {\n    const count = await clickAndCountRenders(page, '[data-testid=\"toggle-theme\"]');\n    expect(count).toBeGreaterThan(0);\n  });\n\n  test('unstable props on memo components trigger render tracking', async ({ page }) => {\n    const count = await clickAndCountRenders(page, '[data-testid=\"trigger-unstable\"]');\n    expect(count).toBeGreaterThan(0);\n  });\n\n  test('render count accumulates with repeated clicks', async ({ page }) => {\n    await page.evaluate(() => { (window as any).__E2E_RENDER_COUNT__ = 0; });\n\n    await page.click('[data-testid=\"increment\"]');\n    await page.waitForTimeout(300);\n    const after1 = await getRenderCount(page);\n\n    await page.click('[data-testid=\"increment\"]');\n    await page.waitForTimeout(300);\n    const after2 = await getRenderCount(page);\n\n    await page.click('[data-testid=\"increment\"]');\n    await page.waitForTimeout(300);\n    const after3 = await getRenderCount(page);\n\n    expect(after1).toBeGreaterThan(0);\n    expect(after2).toBeGreaterThan(after1);\n    expect(after3).toBeGreaterThan(after2);\n  });\n});\n"
  },
  {
    "path": "e2e/toolbar.spec.ts",
    "content": "import { test, expect } from '@playwright/test';\nimport { gotoFixture, isReactScanActive, hasShadowRoot } from './helpers';\n\ntest.describe('Toolbar', () => {\n  test.beforeEach(async ({ page }) => {\n    await gotoFixture(page);\n  });\n\n  test('React Scan initializes and attaches to the page', async ({ page }) => {\n    const active = await isReactScanActive(page);\n    expect(active).toBe(true);\n  });\n\n  test('React Scan internals are accessible', async ({ page }) => {\n    const hasInternals = await page.evaluate(() => {\n      const scan = (window as any).__REACT_SCAN__;\n      return (\n        scan?.ReactScanInternals !== undefined &&\n        scan.ReactScanInternals.options !== undefined &&\n        scan.ReactScanInternals.Store !== undefined\n      );\n    });\n    expect(hasInternals).toBe(true);\n  });\n\n  test('options are set correctly', async ({ page }) => {\n    const options = await page.evaluate(() => {\n      const scan = (window as any).__REACT_SCAN__;\n      const opts = scan?.ReactScanInternals?.options?.value;\n      if (!opts) return null;\n      return {\n        enabled: opts.enabled,\n        dangerouslyForceRunInProduction: opts.dangerouslyForceRunInProduction,\n        showToolbar: opts.showToolbar,\n      };\n    });\n    expect(options).toEqual({\n      enabled: true,\n      dangerouslyForceRunInProduction: true,\n      showToolbar: true,\n    });\n  });\n\n  test('shadow DOM root is created', async ({ page }) => {\n    await page.waitForTimeout(1000);\n    expect(await hasShadowRoot(page)).toBe(true);\n  });\n\n  test('toolbar has content in shadow DOM', async ({ page }) => {\n    await page.waitForTimeout(1000);\n    const childCount = await page.evaluate(() => {\n      const root = document.getElementById('react-scan-root');\n      return root?.shadowRoot?.children.length ?? 0;\n    });\n    expect(childCount).toBeGreaterThan(0);\n  });\n\n  test('toolbar persists across interactions', async ({ page }) => {\n    await page.click('[data-testid=\"increment\"]');\n    await page.waitForTimeout(500);\n\n    const active = await isReactScanActive(page);\n    expect(active).toBe(true);\n\n    const options = await page.evaluate(() => {\n      return (window as any).__REACT_SCAN__?.ReactScanInternals?.options?.value?.enabled;\n    });\n    expect(options).toBe(true);\n  });\n});\n"
  },
  {
    "path": "kitchen-sink/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n\n    <title>React Scan</title>\n    <meta name=\"title\" content=\"React Scan\" />\n    <meta\n      name=\"description\"\n      content=\"React Scan automatically detects and highlights components that cause performance issues in your React app. Drop it in anywhere – script tag, npm, you name it!\"\n    />\n\n    <meta property=\"og:type\" content=\"website\" />\n    <meta property=\"og:url\" content=\"https://react-scan.million.dev\" />\n    <meta property=\"og:title\" content=\"React Scan\" />\n    <meta\n      property=\"og:description\"\n      content=\"React Scan automatically detects and highlights components that cause performance issues in your React app. Drop it in anywhere – script tag, npm, you name it!\"\n    />\n    <meta\n      property=\"og:image\"\n      content=\"https://react-scan.million.dev/banner.png\"\n    />\n\n    <meta property=\"twitter:card\" content=\"summary_large_image\" />\n    <meta property=\"twitter:url\" content=\"https://react-scan.million.dev\" />\n    <meta property=\"twitter:title\" content=\"React Scan\" />\n    <meta\n      property=\"twitter:description\"\n      content=\"React Scan automatically detects and highlights components that cause performance issues in your React app. Drop it in anywhere – script tag, npm, you name it!\"\n    />\n    <meta\n      property=\"twitter:image\"\n      content=\"https://react-scan.million.dev/banner.png\"\n    />\n\n    <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Geist+Mono:wght@100..900&family=Geist:wght@100..900&display=swap\"\n      rel=\"stylesheet\"\n    />\n\n    <link rel=\"icon\" href=\"/logo.svg\" type=\"image/svg+xml\" />\n\n    <meta\n      name=\"keywords\"\n      content=\"react, performance, debugging, developer tools, web development, javascript\"\n    />\n    <meta name=\"author\" content=\"Aiden Bai\" />\n    <meta name=\"theme-color\" content=\"#8b5cf6\" />\n    <link rel=\"canonical\" href=\"https://react-scan.million.dev\" />\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/src/index.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "kitchen-sink/package.json",
    "content": "{\n  \"name\": \"@react-scan/kitchen-sink\",\n  \"type\": \"module\",\n  \"private\": true,\n  \"publishConfig\": {\n    \"access\": \"restricted\"\n  },\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"serve\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"react\": \"^19.0.0\",\n    \"react-dom\": \"^19.0.0\",\n    \"react-scan\": \"workspace:*\",\n    \"tailwindcss\": \"^3.4.17\"\n  },\n  \"devDependencies\": {\n    \"@types/react\": \"^19.0.8\",\n    \"@types/react-dom\": \"^19.0.3\",\n    \"@vitejs/plugin-react\": \"^4.3.4\",\n    \"postcss\": \"^8.5.3\",\n    \"typescript\": \"^5.7.3\",\n    \"vite\": \"^6.3.0\"\n  }\n}\n"
  },
  {
    "path": "kitchen-sink/postcss.config.mjs",
    "content": "export default {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n};\n"
  },
  {
    "path": "kitchen-sink/src/examples/e2e-fixture/index.tsx",
    "content": "import { useState, useContext, createContext, memo } from 'react';\nimport { scan, Store } from 'react-scan';\n\nStore.isInIframe.value = false;\nscan({\n  enabled: true,\n  dangerouslyForceRunInProduction: true,\n});\n\nconst ThemeContext = createContext('light');\n\nfunction Counter(): JSX.Element {\n  const [count, setCount] = useState(0);\n  return (\n    <div data-testid=\"counter\">\n      <span data-testid=\"count\">{count}</span>\n      <button data-testid=\"increment\" type=\"button\" onClick={() => setCount((c) => c + 1)}>\n        Increment\n      </button>\n    </div>\n  );\n}\n\nfunction UnstableProps(): JSX.Element {\n  const [tick, setTick] = useState(0);\n  return (\n    <div data-testid=\"unstable-section\">\n      <button data-testid=\"trigger-unstable\" type=\"button\" onClick={() => setTick((t) => t + 1)}>\n        Trigger ({tick})\n      </button>\n      <MemoChild style={{ color: 'red' }} onClick={() => {}} label=\"unstable\" />\n    </div>\n  );\n}\n\nconst MemoChild = memo(function MemoChild({\n  style,\n  onClick,\n  label,\n}: {\n  style: { color: string };\n  onClick: () => void;\n  label: string;\n}): JSX.Element {\n  return (\n    <div data-testid=\"memo-child\" style={style} onClick={onClick}>\n      MemoChild: {label}\n    </div>\n  );\n});\n\nfunction ContextConsumer(): JSX.Element {\n  const theme = useContext(ThemeContext);\n  return <div data-testid=\"context-value\">Theme: {theme}</div>;\n}\n\nfunction ThemeToggle(): JSX.Element {\n  const [theme, setTheme] = useState('light');\n  return (\n    <ThemeContext.Provider value={theme}>\n      <div data-testid=\"theme-section\">\n        <button\n          data-testid=\"toggle-theme\"\n          type=\"button\"\n          onClick={() => setTheme((t) => (t === 'light' ? 'dark' : 'light'))}\n        >\n          Toggle Theme\n        </button>\n        <ContextConsumer />\n      </div>\n    </ThemeContext.Provider>\n  );\n}\n\nfunction SlowComponent(): JSX.Element {\n  const [rendering, setRendering] = useState(false);\n\n  const triggerSlowRender = () => {\n    setRendering(true);\n    const start = performance.now();\n    while (performance.now() - start < 100) {\n      // block for 100ms to simulate slow render\n    }\n    setRendering(false);\n  };\n\n  return (\n    <div data-testid=\"slow-section\">\n      <button data-testid=\"trigger-slow\" type=\"button\" onClick={triggerSlowRender}>\n        Trigger Slow Render\n      </button>\n      <span data-testid=\"slow-status\">{rendering ? 'Rendering...' : 'Idle'}</span>\n    </div>\n  );\n}\n\nfunction RapidUpdater(): JSX.Element {\n  const [count, setCount] = useState(0);\n\n  const triggerRapid = () => {\n    for (let i = 0; i < 50; i++) {\n      setTimeout(() => setCount((c) => c + 1), i * 16);\n    }\n  };\n\n  return (\n    <div data-testid=\"rapid-section\">\n      <button data-testid=\"trigger-rapid\" type=\"button\" onClick={triggerRapid}>\n        Rapid Updates\n      </button>\n      <span data-testid=\"rapid-count\">{count}</span>\n    </div>\n  );\n}\n\nexport default function E2EFixture(): JSX.Element {\n  return (\n    <div style={{ padding: 24, fontFamily: 'sans-serif' }}>\n      <h1 data-testid=\"heading\">React Scan E2E Fixture</h1>\n      <hr />\n      <section>\n        <h2>Counter</h2>\n        <Counter />\n      </section>\n      <hr />\n      <section>\n        <h2>Unstable Props (memo bypass)</h2>\n        <UnstableProps />\n      </section>\n      <hr />\n      <section>\n        <h2>Context</h2>\n        <ThemeToggle />\n      </section>\n      <hr />\n      <section>\n        <h2>Slow Render</h2>\n        <SlowComponent />\n      </section>\n      <hr />\n      <section>\n        <h2>Rapid Updates</h2>\n        <RapidUpdater />\n      </section>\n    </div>\n  );\n}\n"
  },
  {
    "path": "kitchen-sink/src/examples/sierpinski/index.tsx",
    "content": "/**\n *  Modified version of https://github.com/ryansolid/solid-sierpinski-triangle-demo\n **/\n// import { Analytics } from '@vercel/analytics/react';\nimport { useEffect, useMemo, useState } from 'react';\nimport { scan, Store } from 'react-scan';\n\nimport './styles.css';\n\n\nStore.isInIframe.value = false;\nscan({\n  enabled: true,\n  dangerouslyForceRunInProduction: true,\n});\n\nconst TARGET = 50;\n\nconst TriangleDemo = () => {\n  const [elapsed, setElapsed] = useState(0);\n  const [seconds, setSeconds] = useState(0);\n  const scale = useMemo(() => {\n    const e = (elapsed / 1000) % 10;\n    return 1 + (e > 5 ? 10 - e : e) / 10;\n  }, [elapsed]);\n\n  useEffect(() => {\n    const t = setInterval(() => setSeconds((s) => (s % 10) + 1), 1000);\n\n    let f: number;\n    const start = Date.now();\n    const update = () => {\n      setElapsed(Date.now() - start);\n      f = requestAnimationFrame(update);\n    };\n    f = requestAnimationFrame(update);\n\n    return () => {\n      clearInterval(t);\n      cancelAnimationFrame(f);\n    };\n  }, []);\n\n  return (\n    <div\n      className=\"container\"\n      style={{\n        transform: 'scaleX(' + scale / 2.1 + ') scaleY(0.7) translateZ(0.1px)',\n      }}\n    >\n      <Triangle x={0} y={0} s={1000} seconds={seconds} />\n    </div>\n  );\n};\n\ninterface SlowTriangleProps {\n  x: number;\n  y: number;\n  s: number;\n  seconds: number;\n}\n\nconst SlowTriangle = ({ x, y, s, seconds }: SlowTriangleProps) => {\n  s = s / 2;\n\n  const slow = useMemo(() => {\n    const e = performance.now() + 0.8;\n    // Artificially long execution time.\n    while (performance.now() < e) {}\n    return seconds;\n  }, [seconds]);\n\n  return (\n    <>\n      <Triangle x={x} y={y - s / 2} s={s} seconds={slow} />\n      <Triangle x={x - s} y={y + s / 2} s={s} seconds={slow} />\n      <Triangle x={x + s} y={y + s / 2} s={s} seconds={slow} />\n    </>\n  );\n};\n\ninterface TriangleProps {\n\n  x: number;\n  y: number;\n  s: number;\n  seconds: number;\n}\n\nconst Triangle = ({ x, y, s, seconds }: TriangleProps) => {\n  if (s <= TARGET) {\n    return (\n      <Dot x={x - TARGET / 2} y={y - TARGET / 2} s={TARGET} text={seconds} />\n    );\n  }\n  return <SlowTriangle x={x} y={y} s={s} seconds={seconds} />;\n};\n\ninterface DotProps {\n  x: number;\n  y: number;\n  s: number;\n  text: number;\n}\n\nconst Dot = ({ x, y, s, text }: DotProps) => {\n  const [hover, setHover] = useState(false);\n  const onEnter = () => setHover(true);\n  const onExit = () => setHover(false);\n\n  return (\n    <div\n      className=\"dot\"\n      style={{\n        width: s + 'px',\n        height: s + 'px',\n        left: x + 'px',\n        top: y + 'px',\n        borderRadius: s / 2 + 'px',\n        lineHeight: s + 'px',\n        background: hover ? '#ff0' : '#61dafb',\n      }}\n      onMouseEnter={onEnter}\n      onMouseLeave={onExit}\n    >\n      {hover ? '**' + text + '**' : text}\n    </div>\n  );\n};\n\nexport default function App(): JSX.Element {\n  return (\n    <>\n      {/* <Analytics /> */}\n      <TriangleDemo />\n    </>\n  );\n}\n"
  },
  {
    "path": "kitchen-sink/src/examples/sierpinski/styles.css",
    "content": "body {\n  background: #fff;\n  font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n  font-size: 15px;\n  line-height: 1.7;\n  margin: 0;\n  padding: 30px;\n}\n\na {\n  color: #4183c4;\n  text-decoration: none;\n}\n\na:hover {\n  text-decoration: underline;\n}\n\ncode {\n  background-color: #f8f8f8;\n  border: 1px solid #ddd;\n  border-radius: 3px;\n  font-family: \"Bitstream Vera Sans Mono\", Consolas, Courier, monospace;\n  font-size: 12px;\n  margin: 0 2px;\n  padding: 0px 5px;\n}\n\nh1, h2, h3, h4 {\n  font-weight: bold;\n  margin: 0 0 15px;\n  padding: 0;\n}\n\nh1 {\n  border-bottom: 1px solid #ddd;\n  font-size: 2.5em;\n  font-weight: bold;\n  margin: 0 0 15px;\n  padding: 0;\n}\n\nh2 {\n  border-bottom: 1px solid #eee;\n  font-size: 2em;\n}\n\nh3 {\n  font-size: 1.5em;\n}\n\nh4 {\n  font-size: 1.2em;\n}\n\np, ul {\n  margin: 15px 0;\n}\n\nul {\n  padding-left: 30px;\n}\n\n.container {\n  position: absolute;\n  transform-origin: 0 0;\n  left: 50%;\n  top: 50%;\n  width: 10px;\n  height: 10px;\n  background: #eee;\n}\n\n.dot {\n  position: absolute;\n  font: normal 15px sans-serif;\n  text-align: center;\n  cursor: pointer;\n}"
  },
  {
    "path": "kitchen-sink/src/examples/todo-list/index.tsx",
    "content": "import { useState } from 'react';\nimport { scan, Store } from 'react-scan';\nimport './styles.css';\n\n\nStore.isInIframe.value = false;\nscan({\n  enabled: true,\n  dangerouslyForceRunInProduction: true,\n});\n\ninterface TodoItem {\n  id: number;\n  message: string;\n  done: boolean;\n}\n\ninterface TodoListItemProps {\n  setList: (action: (list: TodoItem[]) => TodoItem[]) => void;\n  item: TodoItem;\n}\n\nfunction TodoListItem({ item, setList }: TodoListItemProps): JSX.Element {\n  return (\n    <div className={`todo-item ${item.done ? 'complete' : 'pending'}`}>\n      <div className=\"todo-item-content\">{item.message}</div>\n      <div className=\"todo-item-actions\">\n        <button\n          type=\"button\"\n          className={`todo-item-toggle ${item.done ? 'complete' : 'pending'}`}\n          onClick={(): void => {\n            setList(list =>\n              list.map(value => {\n                if (value === item) {\n                  return {\n                    ...value,\n                    done: !item.done,\n                  };\n                }\n                return value;\n              }),\n            );\n          }}\n        >\n          {item.done ? 'Completed' : 'Pending'}\n        </button>\n        <button\n          type=\"button\"\n          className=\"todo-item-delete\"\n          onClick={(): void => {\n            setList(list => list.filter(value => value.id !== item.id));\n          }}\n        >\n          Delete\n        </button>\n      </div>\n    </div>\n  );\n}\n\ninterface TodoListFormProps {\n  index: number;\n  setIndex: (update: number) => void;\n  setList: (action: (list: TodoItem[]) => TodoItem[]) => void;\n}\n\nfunction TodoListForm({\n  setList,\n  index,\n  setIndex,\n}: TodoListFormProps): JSX.Element {\n  const [message, setMessage] = useState('');\n\n  return (\n    <form\n      className=\"todo-list-form\"\n      onSubmit={(e): void => {\n        e.preventDefault();\n\n        setList(list => [\n          ...list,\n          {\n            done: false,\n            message,\n            id: index,\n          },\n        ]);\n        setIndex(index + 1);\n        setMessage('');\n      }}\n    >\n      <input\n        type=\"text\"\n        value={message}\n        onInput={(e): void => {\n          setMessage((e.target as HTMLInputElement).value);\n        }}\n      />\n      <button type=\"submit\" disabled={message === ''}>\n        Add\n      </button>\n    </form>\n  );\n}\n\nfunction TodoList(): JSX.Element {\n  const [list, setList] = useState<TodoItem[]>([]);\n  const [index, setIndex] = useState(0);\n  return (\n    <>\n      <TodoListForm setList={setList} index={index} setIndex={setIndex} />\n      <div className=\"todo-list\">\n        {list.map(item => (\n          <TodoListItem key={item.id} item={item} setList={setList} />\n        ))}\n      </div>\n    </>\n  );\n}\n\nexport default function App(): JSX.Element {\n  return (\n    <div className=\"app\">\n      <h1>Todo List</h1>\n      <TodoList />\n    </div>\n  );\n}\n"
  },
  {
    "path": "kitchen-sink/src/examples/todo-list/styles.css",
    "content": "* {\n  font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n}\n\nbutton {\n  margin: 0.5rem;\n  padding: 0.5rem 1rem;\n  border-radius: 0.5rem;\n  outline: none;\n  color: white;\n  border-style: none;\n  cursor: pointer;\n  transition: background-color 200ms;\n}\n\nbutton:disabled {\n  background-color: rgba(156, 163, 175, 255);\n  color: rgba(229, 231, 235, 255);\n}\n\n.app {\n  margin: 5%;\n}\n\n.todo-list-form {\n  width: 100%;\n\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n}\n\n.todo-list-form input {\n  width: 100%;\n  margin: 0.5rem;\n  padding: 0.5rem;\n  border-radius: 0.5rem;\n  outline: none;\n  border-width: 1px;\n  border-color: black;\n  border-style: solid;\n}\n\n.todo-list-form button:not(:disabled) {\n  background-color: rgba(99, 102, 241, 255);\n}\n\n.todo-list-form button:not(:disabled):hover {\n  background-color: rgba(67, 56, 202, 255);\n}\n\n.todo-list-form button:not(:disabled):active {\n  background-color: rgba(79, 70, 229, 255);\n}\n\n.todo-item-toggle.pending {\n  background-color: rgba(245, 158, 11, 255);\n}\n\n.todo-item-toggle.pending:hover {\n  background-color: rgba(180, 83, 9, 255);\n}\n\n.todo-item-toggle.pending:active {\n  background-color: rgba(217, 119, 6, 255);\n}\n\n.todo-item-toggle.complete {\n  background-color: rgba(16, 185, 129, 255);\n}\n\n.todo-item-toggle.complete:hover {\n  background-color: rgba(4, 120, 87, 255);\n}\n\n.todo-item-toggle.complete:active {\n  background-color: rgba(5, 150, 105, 255);\n}\n\n.todo-item-delete:not(:disabled) {\n  background-color: rgba(239, 68, 68, 255);\n}\n\n.todo-item-delete:not(:disabled):hover {\n  background-color: rgba(185, 28, 28, 255);\n}\n\n.todo-item-delete:not(:disabled):active {\n  background-color: rgba(220, 38, 38, 255);\n}\n\n.todo-item {\n  margin: 0.5rem;\n  padding: 0.5rem 1rem;\n  border-radius: 0.5rem;\n\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  transition: background-color 200ms;\n}\n\n.todo-item.loading {\n  background-color: rgba(17, 24, 39, 255);\n  color: white;\n}\n\n\n.todo-item.complete {\n  background-color: rgba(209, 250, 229, 255);\n}\n\n.todo-item.pending {\n  background-color: rgba(254, 243, 199, 255);\n}\n"
  },
  {
    "path": "kitchen-sink/src/index.css",
    "content": "body {\n  margin: 0;\n}\n"
  },
  {
    "path": "kitchen-sink/src/index.tsx",
    "content": "import 'react-scan';\n\nimport { FC, lazy } from 'react';\nimport { createRoot } from 'react-dom/client';\nimport Home from './main';\n\nimport './index.css';\n\nconst examples = import.meta.glob<false, string, { default: FC<unknown> }>(\n  './examples/**/index.tsx',\n  {\n    eager: false,\n  },\n);\n\nconst root = document.getElementById('root');\n\nif (root) {\n  const embedded = new URLSearchParams(window.location.search);\n  const page = embedded.get('example');\n  const target = `./examples/${page}/index.tsx`;\n  if (page && target in examples) {\n    const App = lazy(examples[target]);\n    createRoot(root).render(<App />);\n  } else {\n    createRoot(root).render(<Home />);\n  }\n}\n"
  },
  {
    "path": "kitchen-sink/src/main.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\nbody {\n  overflow: hidden;\n}\n"
  },
  {
    "path": "kitchen-sink/src/main.tsx",
    "content": "import { type JSX, useState } from 'react';\nimport './main.css';\n\ninterface Example {\n  title: string;\n  url: string;\n}\n\nconst examples: Example[] = [\n  { title: 'Sierpinski Triangle', url: '/?example=sierpinski' },\n  { title: 'Todo List', url: '/?example=todo-list' },\n];\n\nexport default function Home(): JSX.Element {\n  const [example, setExample] = useState(0);\n\n  return (\n    <div className=\"flex flex-col w-screen h-screen\">\n      <div className=\"flex flex-none border-b border-gray-950\">\n        <h1 className=\"m-8 font-bold text-3xl\">react-scan</h1>\n      </div>\n      <div className=\"flex flex-1\">\n        {/* content */}\n        <div className=\"flex flex-none flex-col border-r border-gray-950\">\n          {/* sidebar */}\n          {examples.map((item, index) => (\n            <button\n              key={item.url}\n              className=\"px-8 py-4 border-b border-gray-950\"\n              type=\"button\"\n              onClick={() => setExample(index)}\n            >\n              {item.title}\n            </button>\n          ))}\n        </div>\n        <div className=\"flex-1 flex items-center justify-center\">\n          {/* iframe */}\n          <iframe className=\"flex-1 h-full\" src={examples[example].url} />\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "kitchen-sink/tailwind.config.mjs",
    "content": "export default {\n  content: ['./src/**/*.{js,jsx,ts,tsx}'],\n};\n"
  },
  {
    "path": "kitchen-sink/tsconfig.json",
    "content": "{\n  \"exclude\": [\"node_modules\"],\n  \"include\": [\"src\", \"types\"],\n  \"compilerOptions\": {\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"types\": [\"vite/client\"],\n    \"importHelpers\": true,\n    \"declaration\": true,\n    \"sourceMap\": true,\n    \"rootDir\": \"./src\",\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"moduleResolution\": \"node\",\n    \"jsx\": \"react-jsx\",\n    \"jsxImportSource\": \"react\",\n    \"esModuleInterop\": true,\n    \"target\": \"ES2017\"\n  }\n}\n"
  },
  {
    "path": "kitchen-sink/vite.config.ts",
    "content": "import react from '@vitejs/plugin-react';\nimport { defineConfig } from 'vite';\n\nexport default defineConfig({\n  plugins: [react()],\n  css: {\n    modules: {\n      localsConvention: 'camelCaseOnly',\n    },\n  },\n});\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"root\",\n  \"private\": true,\n  \"scripts\": {\n    \"build\": \"node scripts/workspace.mjs build\",\n    \"postbuild\": \"node scripts/version-warning.mjs\",\n    \"dev\": \"node scripts/workspace.mjs dev\",\n    \"pack\": \"node scripts/workspace.mjs pack\",\n    \"pack:bump\": \"pnpm --filter scan pack:bump\",\n    \"lint\": \"pnpm -r lint\",\n    \"lint:all\": \"oxlint .\",\n    \"changeset\": \"changeset add\",\n    \"version\": \"changeset version\",\n    \"release\": \"changeset publish\",\n    \"test:e2e\": \"playwright test\",\n    \"test:e2e:ui\": \"playwright test --ui\"\n  },\n  \"devDependencies\": {\n    \"@changesets/cli\": \"^2.27.12\",\n    \"@playwright/test\": \"^1.58.2\",\n    \"@types/node\": \"^22.10.2\",\n    \"autoprefixer\": \"^10.4.20\",\n    \"boxen\": \"^8.0.1\",\n    \"chalk\": \"^5.3.0\",\n    \"oxlint\": \"latest\",\n    \"postcss\": \"^8.5.3\",\n    \"rimraf\": \"^6.0.1\",\n    \"tailwindcss\": \"^3.4.17\",\n    \"typescript\": \"latest\",\n    \"vite-tsconfig-paths\": \"^5.1.4\"\n  },\n  \"packageManager\": \"pnpm@9.1.0\",\n  \"dependencies\": {\n    \"@vercel/speed-insights\": \"^1.1.0\"\n  },\n  \"pnpm\": {\n    \"overrides\": {\n      \"@jridgewell/gen-mapping\": \"0.3.2\",\n      \"@jridgewell/sourcemap-codec\": \"1.4.15\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/extension/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.DS_Store\n.idea\n*.suo\n*.ntvs*\n*.njsproj\n*.sw?\n*.sln\n\n# Config files\n.webextrc\n.webextrc.*\n"
  },
  {
    "path": "packages/extension/CHANGELOG.md",
    "content": "# @react-scan/extension\n\n## 0.5.3\n\n### Patch Changes\n\n- fix\n- Updated dependencies\n  - react-scan@0.5.3\n\n## 0.5.2\n\n### Patch Changes\n\n- fix\n- Updated dependencies\n  - react-scan@0.5.2\n\n## 0.5.1\n\n### Patch Changes\n\n- fix: infinite mounting\n- Updated dependencies\n  - react-scan@0.5.1\n\n## 0.5.0\n\n### Minor Changes\n\n- cleanup\n\n### Patch Changes\n\n- Updated dependencies\n- Updated dependencies [9d38ffe]\n  - react-scan@0.5.0\n"
  },
  {
    "path": "packages/extension/README.md",
    "content": "# React Scanner Extension\n\nBrowser extension for scanning React applications and identifying performance issues.\n\n\n### Environment Variables\n\nWhen developing with Brave, you need to set the `BRAVE_BINARY` environment variable. Create a `.env` file (copy from `.env.example`):\n\n```env\n# For macOS\nBRAVE_BINARY=\"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser\"\n\n# For Windows\nBRAVE_BINARY=\"C:\\\\Program Files\\\\BraveSoftware\\\\Brave-Browser\\\\Application\\\\brave.exe\"\n\n# For Linux\nBRAVE_BINARY=\"/usr/bin/brave\"\n```\n\n### Development Setup\n#### For Chrome\n1. Run development server:\n   ```bash\n   pnpm dev\n   ```\n3. This will automatically open Chrome with the extension loaded.\n\n<i>If you need to inspect the extension, open `chrome://extensions` in Chrome</i>\n#### For Firefox\n\n<br />\n\n#### For Firefox\n1. Run development server:\n   ```bash\n   pnpm dev:firefox\n   ```\n2. This will automatically open Firefox with the extension loaded.\n\n<i>If you need to inspect the extension, open `about:debugging#/runtime/this-firefox` in Firefox</i>\n\n<br />\n\n#### For Brave\n\n1. Run development server:\n   ```bash\n   pnpm dev:brave\n   ```\n\n2. This will automatically open Brave with the extension loaded.\n\n<i>If you need to inspect the extension, open `brave://extensions` in Brave</i>\n\n<br />\n\n### Building for Production\n\nTo build the extension for all browsers:\n\n```bash\npnpm pack:all\n```\n\nThis will create:\n- `chrome-extension-v1.0.8.zip`\n- `firefox-extension-v1.0.8.zip`\n- `brave-extension-v1.0.8.zip`\n\nin the `build` directory.\n"
  },
  {
    "path": "packages/extension/package.json",
    "content": "{\n  \"name\": \"@react-scan/extension\",\n  \"version\": \"0.5.3\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"clean\": \"rimraf dist\",\n    \"build\": \"vite build\",\n    \"postbuild\": \"node ../../scripts/version-warning.mjs\",\n    \"dev\": \"pnpm dev:chrome\",\n    \"dev:chrome\": \"cross-env BROWSER=chrome vite\",\n    \"dev:firefox\": \"cross-env BROWSER=firefox vite\",\n    \"dev:brave\": \"cross-env BROWSER=brave vite\",\n    \"mkdir\": \"mkdir -p build\",\n    \"pack:chrome\": \"pnpm clean && pnpm build && pnpm mkdir && cd dist && zip -r \\\"../build/chrome-extension-v$npm_package_version.zip\\\" .\",\n    \"pack:firefox\": \"pnpm clean && BROWSER=firefox pnpm build && pnpm mkdir && cd dist && zip -r \\\"../build/firefox-extension-v$npm_package_version.zip\\\" .\",\n    \"pack:brave\": \"pnpm clean && BROWSER=brave pnpm build && pnpm mkdir && cd dist && zip -r \\\"../build/brave-extension-v$npm_package_version.zip\\\" .\",\n    \"pack:all\": \"rimraf build && pnpm pack:chrome && pnpm pack:firefox && pnpm pack:brave\",\n    \"lint\": \"oxlint src && pnpm typecheck\",\n    \"typecheck\": \"tsc --noEmit\"\n  },\n  \"dependencies\": {\n    \"bippy\": \"0.3.8\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"react-scan\": \"workspace:*\",\n    \"zod\": \"^3.23.8\"\n  },\n  \"devDependencies\": {\n    \"@types/chrome\": \"^0.0.281\",\n    \"@types/react\": \"^18.0.26\",\n    \"@types/react-dom\": \"^18.0.9\",\n    \"@types/semver\": \"^7.5.8\",\n    \"@types/webextension-polyfill\": \"^0.12.0\",\n    \"@vitejs/plugin-react\": \"^4.2.1\",\n    \"bestzip\": \"^2.2.1\",\n    \"cross-env\": \"^7.0.3\",\n    \"semver\": \"^7.7.1\",\n    \"vite\": \"^6.3.0\",\n    \"vite-plugin-web-extension\": \"^4.4.3\",\n    \"vite-tsconfig-paths\": \"^5.1.4\",\n    \"webextension-polyfill\": \"^0.12.0\"\n  }\n}\n"
  },
  {
    "path": "packages/extension/src/assets/css/no-react.css",
    "content": "html.freeze > body {\n  pointer-events: none;\n}\n\nhtml.freeze {\n  overflow: auto;\n  overscroll-behavior-x: contain;\n}\n\nhtml.freeze svg {\n  pointer-events: none;\n}\n\nhtml.freeze #react-scan-toast {\n  pointer-events: auto;\n}\n\n#react-scan-backdrop {\n  position: fixed;\n  inset: 0;\n  background-color: rgba(0, 0, 0, 0.01);\n  backdrop-filter: blur(94px) saturate(180%);\n  animation-duration: 300ms;\n  opacity: 0;\n  pointer-events: none;\n  transition: opacity 500ms;\n  z-index: 2147483650;\n}\n\n#react-scan-toast {\n  position: fixed;\n  top: 50%;\n  left: 50%;\n  display: flex;\n  flex-direction: column;\n  padding: 12px 40px 12px 16px;\n  min-width: 320px;\n  max-width: 480px;\n  color: #fff;\n  font-size: 12px;\n  font-family: Menlo, Consolas, Monaco, 'Liberation Mono', 'Lucida Console', monospace;\n  line-height: 1.5;\n  background: rgba(0, 0, 0, 0.95);\n  border: 1px solid rgba(255, 255, 255, 0.1);\n  border-radius: 8px;\n  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);\n  transform: translate(-50%, -50%);\n  transition: opacity 300ms ease-in-out;\n  z-index: 2147483651;\n}\n\n#react-scan-toast-title {\n  margin: 0 0 8px 0;\n}\n\n#react-scan-toast-message {\n  display: flex;\n  align-items: flex-start;\n}\n\n#react-scan-toast-message span {\n  white-space: pre-line;\n}\n\n#react-scan-toast .icon {\n  font-size: .75rem;\n  margin-right: 8px;\n}\n\n@media (max-width: 320px) {\n  #react-scan-toast {\n    flex-direction: column;\n    min-width: auto;\n    width: calc(100% - 36px);\n  }\n}\n\n@media (max-width: 720px) {\n  #react-scan-toast {\n    br {\n      display: none;\n    }\n  }\n}\n\n#react-scan-toast-close-button {\n  position: absolute;\n  top: 10px;\n  right: 8px;\n  display: flex;\n  align-items: center;\n  width: 23px;\n  height: 23px;\n  padding: 4px;\n  border: none;\n  border-radius: 4px;\n  color: #fff;\n  cursor: pointer;\n  background: rgba(255, 255, 255, 0.01);\n  transition: background-color 300ms ease;\n}\n\n#react-scan-toast-close-button:hover {\n  background-color: rgba(255, 255, 255, 0.15);\n}\n"
  },
  {
    "path": "packages/extension/src/background/icon.ts",
    "content": "import browser from 'webextension-polyfill';\n\nexport enum IconState {\n  DISABLED = 'disabled',\n  ENABLED = 'enabled',\n}\n\nconst browserAction = browser.action || browser.browserAction;\n\nconst cachedIcons = {\n  [IconState.ENABLED]: {\n    path: {\n      16: browser.runtime.getURL('icons/enabled/16.png'),\n      32: browser.runtime.getURL('icons/enabled/32.png'),\n      48: browser.runtime.getURL('icons/enabled/48.png'),\n      128: browser.runtime.getURL('icons/enabled/128.png'),\n    },\n  },\n  [IconState.DISABLED]: {\n    path: {\n      16: browser.runtime.getURL('icons/disabled/16.png'),\n      32: browser.runtime.getURL('icons/disabled/32.png'),\n      48: browser.runtime.getURL('icons/disabled/48.png'),\n      128: browser.runtime.getURL('icons/disabled/128.png'),\n    },\n  },\n};\n\nexport const updateIconForTab = async (\n  tab: browser.Tabs.Tab,\n  state: IconState,\n  badgeText = 'on',\n): Promise<void> => {\n  try {\n    switch (state) {\n      case IconState.ENABLED:\n        await browserAction.setIcon({\n          tabId: tab.id,\n          path: cachedIcons[IconState.ENABLED].path,\n        });\n        if (badgeText) {\n          await browserAction.setBadgeText({ text: badgeText, tabId: tab.id });\n          await browserAction.setBadgeBackgroundColor({\n            color: '#A295EE',\n            tabId: tab.id,\n          });\n        }\n        break;\n\n      default:\n        await browserAction.setIcon({\n          tabId: tab.id,\n          path: cachedIcons[IconState.DISABLED].path,\n        });\n        await browserAction.setBadgeText({ text: '', tabId: tab.id });\n        break;\n    }\n  } catch {}\n};\n"
  },
  {
    "path": "packages/extension/src/background/index.ts",
    "content": "import browser from 'webextension-polyfill';\nimport { isInternalUrl } from '~utils/helpers';\nimport { IconState, updateIconForTab } from './icon';\nimport { BroadcastMessage } from '~types/messages';\n\nconst browserAction = browser.action || browser.browserAction;\n\nconst injectScripts = async (tabId: number) => {\n  try {\n    await browser.scripting.executeScript({\n      target: { tabId },\n      files: ['src/content/index.js', 'src/inject/index.js'],\n    });\n\n    await browser.tabs.sendMessage(tabId, {\n      type: 'react-scan:page-reload',\n    });\n  } catch (e) {\n    // oxlint-disable-next-line no-console\n    console.error('Script injection error:', e);\n  }\n};\n\nconst isScriptsLoaded = async (tabId: number): Promise<boolean> => {\n  try {\n    await browser.tabs.sendMessage(tabId, { type: 'react-scan:ping' });\n    return true;\n  } catch {\n    return false;\n  }\n};\n\nconst init = async (tab: browser.Tabs.Tab) => {\n  if (!tab.id || !tab.url || isInternalUrl(tab.url)) {\n    if (tab.id) {\n      await updateIconForTab(tab, IconState.DISABLED);\n    }\n    return;\n  }\n\n  const isLoaded = await isScriptsLoaded(tab.id);\n\n  if (!isLoaded) {\n    await injectScripts(tab.id);\n  }\n\n  if (!isLoaded) {\n    await updateIconForTab(tab, IconState.DISABLED);\n  }\n};\n\nbrowser.tabs.onUpdated.addListener((_tabId, changeInfo, tab) => {\n  if (changeInfo.status === 'complete') {\n    void init(tab);\n  }\n});\n\nbrowser.tabs.onActivated.addListener(async ({ tabId }) => {\n  const tab = await browser.tabs.get(tabId);\n  void init(tab);\n});\n\nbrowser.windows.onFocusChanged.addListener(async (windowId) => {\n  if (windowId !== browser.windows.WINDOW_ID_NONE) {\n    const [tab] = await browser.tabs.query({ active: true, windowId });\n    if (tab) {\n      void init(tab);\n    }\n  }\n});\n\nbrowser.tabs.query({ active: true, currentWindow: true }).then(([tab]) => {\n  if (tab) {\n    void init(tab);\n  }\n});\n\nbrowserAction.onClicked.addListener(async (tab) => {\n  if (!tab.id || !tab.url || isInternalUrl(tab.url)) {\n    if (tab.id) {\n      await updateIconForTab(tab, IconState.DISABLED);\n    }\n    return;\n  }\n\n  try {\n    await browser.tabs.sendMessage(tab.id, {\n      type: 'react-scan:toggle-state',\n    });\n\n    await updateIconForTab(tab, IconState.DISABLED);\n  } catch {\n    if (tab.id) {\n      await updateIconForTab(tab, IconState.DISABLED);\n    }\n  }\n});\n\nbrowser.runtime.onMessage.addListener(\n  (message: unknown, sender: browser.Runtime.MessageSender) => {\n    const msg = message as BroadcastMessage;\n    if (!sender.tab?.id) return;\n    if (msg.type === 'react-scan:is-enabled') {\n      void updateIconForTab(\n        sender.tab,\n        msg.data?.state ? IconState.ENABLED : IconState.DISABLED,\n      );\n    }\n  },\n);\n"
  },
  {
    "path": "packages/extension/src/content/index.ts",
    "content": "import browser from 'webextension-polyfill';\nimport {\n  type BroadcastMessage,\n  BroadcastSchema,\n  type IEvents,\n} from '~types/messages';\nimport { busDispatch, busSubscribe } from '~utils/helpers';\n\nchrome.runtime.onMessage.addListener(\n  async (message: unknown, _sender, sendResponse) => {\n    const result = BroadcastSchema.safeParse(message);\n    if (!result.success) {\n      return false;\n    }\n\n    const data = result.data;\n\n    if (data.type === 'react-scan:ping') {\n      sendResponse({ pong: true });\n      return false;\n    }\n\n    if (data.type === 'react-scan:page-reload') {\n      window.location.reload();\n      return false;\n    }\n\n    if (data.type === 'react-scan:toggle-state') {\n      busDispatch<IEvents['react-scan:toggle-state']>(\n        'react-scan:toggle-state',\n        {\n          topic: 'react-scan:toggle-state',\n          message: undefined,\n        },\n      );\n      return false;\n    }\n\n    return false;\n  },\n);\n\nconst sendMessageToBackground = ({ type, data }: BroadcastMessage) => {\n  try {\n    return browser.runtime.sendMessage({ type, data });\n  } catch {\n    return Promise.resolve();\n  }\n};\n\nbusSubscribe<IEvents['react-scan:send-to-background']>(\n  'react-scan:send-to-background',\n  (event) => {\n    sendMessageToBackground(event.message);\n  },\n);\n"
  },
  {
    "path": "packages/extension/src/inject/index.ts",
    "content": "import * as reactScan from 'react-scan';\nimport { gt } from 'semver';\nimport type { IEvents } from '~types/messages';\nimport { EXTENSION_STORAGE_KEY, STORAGE_KEY } from '~utils/constants';\nimport {\n  busDispatch,\n  busSubscribe,\n  canLoadReactScan,\n  hasReactFiber,\n  readLocalStorage,\n  saveLocalStorage,\n  sleep,\n  storageGetItem,\n  storageSetItem,\n} from '~utils/helpers';\nimport { createNotificationUI, toggleNotification } from './notification';\n\nconst reactScanExtensionVersion = 'version' in reactScan.ReactScanInternals ? (reactScan.ReactScanInternals as any).version  : undefined;\nconst isTargetPageAlreadyUsedReactScan = () => {\n  const currentReactScanVersion = window.__REACT_SCAN_VERSION__;\n\n  if (\n    window.__REACT_SCAN__?.ReactScanInternals?.Store?.monitor?.value &&\n    !currentReactScanVersion\n  ) {\n    return true;\n  }\n\n  if (!reactScanExtensionVersion || !currentReactScanVersion) {\n    return false;\n  }\n\n  return gt(currentReactScanVersion, reactScanExtensionVersion);\n};\n\nconst getInitialOptions = async (): Promise<reactScan.Options> => {\n  const storedOptions = readLocalStorage<reactScan.Options>(STORAGE_KEY);\n  let isEnabled = false;\n\n  try {\n    const storedEnabled = await storageGetItem<boolean>(\n      EXTENSION_STORAGE_KEY,\n      'isEnabled',\n    );\n    isEnabled = storedEnabled ?? false;\n  } catch {}\n\n  return {\n    ...storedOptions,\n    enabled: isEnabled,\n    showToolbar: isEnabled,\n    dangerouslyForceRunInProduction: true,\n  };\n};\n\nconst initializeReactScan = async () => {\n  const options = await getInitialOptions();\n\n  window.__REACT_SCAN_EXTENSION__ = true;\n  if (options.enabled) {\n    window.hideIntro = true;\n    reactScan.scan(options);\n    window.reactScan = undefined;\n  }\n};\n\nlet timer: number | undefined;\nconst updateReactScanState = async (isEnabled: boolean | null) => {\n  clearTimeout(timer);\n  const toggledState = isEnabled === null ? true : !isEnabled;\n\n  try {\n    await storageSetItem(EXTENSION_STORAGE_KEY, 'isEnabled', toggledState);\n  } catch {}\n\n  const storedOptions = readLocalStorage<reactScan.Options>(STORAGE_KEY) ?? {};\n  const updatedOptions = {\n    ...storedOptions,\n    enabled: toggledState,\n    showToolbar: toggledState,\n    dangerouslyForceRunInProduction: true,\n  };\n\n  saveLocalStorage(STORAGE_KEY, updatedOptions);\n\n  window.location.reload();\n};\n\nvoid initializeReactScan();\n\nwindow.addEventListener('DOMContentLoaded', async () => {\n  if (!canLoadReactScan) {\n    return;\n  }\n\n  let isReactAvailable = false;\n\n  await sleep(1000);\n  isReactAvailable = await hasReactFiber();\n\n  if (!isReactAvailable) {\n    createNotificationUI({\n      title: 'React Not Detected',\n      content:\n        \"React is not detected on this page.\\nPlease ensure you're visiting a React application.\",\n    });\n\n    busDispatch<IEvents['react-scan:send-to-background']>(\n      'react-scan:send-to-background',\n      {\n        topic: 'react-scan:send-to-background',\n        message: {\n          type: 'react-scan:is-enabled',\n          data: {\n            state: false,\n          },\n        },\n      },\n    );\n\n    busSubscribe<IEvents['react-scan:toggle-state']>(\n      'react-scan:toggle-state',\n      async () => {\n        toggleNotification();\n      },\n    );\n\n    return;\n  }\n\n  if (isTargetPageAlreadyUsedReactScan()) {\n    createNotificationUI({\n      title: 'Already Initialized',\n      content: 'React Scan is already initialized on this page.',\n    });\n\n    busDispatch<IEvents['react-scan:send-to-background']>(\n      'react-scan:send-to-background',\n      {\n        topic: 'react-scan:send-to-background',\n        message: {\n          type: 'react-scan:is-enabled',\n          data: {\n            state: false,\n          },\n        },\n      },\n    );\n\n    busSubscribe<IEvents['react-scan:toggle-state']>(\n      'react-scan:toggle-state',\n      async () => {\n        toggleNotification();\n      },\n    );\n\n    return;\n  }\n\n  const storedOptions = readLocalStorage<reactScan.Options>(STORAGE_KEY);\n  if (storedOptions !== null) {\n    busDispatch<IEvents['react-scan:send-to-background']>(\n      'react-scan:send-to-background',\n      {\n        topic: 'react-scan:send-to-background',\n        message: {\n          type: 'react-scan:is-enabled',\n          data: {\n            state: storedOptions.showToolbar,\n          },\n        },\n      },\n    );\n  }\n\n  if (!isTargetPageAlreadyUsedReactScan()) {\n    window.reactScan = reactScan.setOptions;\n  }\n\n  busSubscribe<IEvents['react-scan:toggle-state']>(\n    'react-scan:toggle-state',\n    async () => {\n      if (!isReactAvailable || isTargetPageAlreadyUsedReactScan()) {\n        toggleNotification();\n        return;\n      }\n\n      try {\n        const isEnabled = await storageGetItem<boolean>(\n          EXTENSION_STORAGE_KEY,\n          'isEnabled',\n        );\n        await updateReactScanState(!!isEnabled);\n      } catch {\n        await updateReactScanState(null);\n      }\n    },\n  );\n});\n"
  },
  {
    "path": "packages/extension/src/inject/notification.ts",
    "content": "import noReactStyles from '~assets/css/no-react.css?inline';\nimport type { IEvents } from '~types/messages';\nimport { busDispatch } from '~utils/helpers';\n\nlet backdrop: HTMLDivElement | null = null;\nlet isAnimating = false;\n\nconst defaultTitle = 'React Not Detected';\nconst defaultContent =\n  \"React is not detected on this page. \\nPlease ensure you're visiting a React application!\";\n\nexport const createNotificationUI = ({\n  title = defaultTitle,\n  content = defaultContent,\n}) => {\n  busDispatch<IEvents['react-scan:send-to-background']>(\n    'react-scan:send-to-background',\n    {\n      topic: 'react-scan:send-to-background',\n      message: {\n        type: 'react-scan:is-enabled',\n        data: {\n          state: false,\n        },\n      },\n    },\n  );\n\n  if (backdrop) {\n    return;\n  }\n\n  backdrop = document.createElement('div');\n  backdrop.id = 'react-scan-backdrop';\n  backdrop.style.opacity = '0';\n  backdrop.style.pointerEvents = 'none';\n\n  const toast = document.createElement('div');\n  toast.id = 'react-scan-toast';\n  toast.onclick = (e) => {\n    e.stopPropagation();\n  };\n\n  // Create title element\n  const titleElement = document.createElement('div');\n  titleElement.id = 'react-scan-toast-title';\n\n  const icon = document.createElement('span');\n  icon.className = 'icon';\n  icon.textContent = '⚛️';\n  titleElement.appendChild(icon);\n\n  const titleText = document.createElement('span');\n  titleText.textContent = title;\n  titleElement.appendChild(titleText);\n\n  toast.appendChild(titleElement);\n\n  // Create message element\n  const messageElement = document.createElement('div');\n  messageElement.id = 'react-scan-toast-message';\n\n  const text = document.createElement('span');\n  text.textContent = content;\n  text.style.whiteSpace = 'pre-line'; // Preserve line breaks\n  messageElement.appendChild(text);\n\n  toast.appendChild(messageElement);\n\n  const button = document.createElement('button');\n  button.id = 'react-scan-toast-close-button';\n  button.type = 'button';\n  button.onclick = toggleNotification;\n\n  const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n  svg.setAttribute('width', '15');\n  svg.setAttribute('height', '15');\n  svg.setAttribute('viewBox', '0 0 24 24');\n  svg.setAttribute('fill', 'none');\n  svg.setAttribute('stroke', 'currentColor');\n  svg.setAttribute('stroke-width', '2');\n  svg.setAttribute('stroke-linecap', 'round');\n  svg.setAttribute('stroke-linejoin', 'round');\n\n  const line1 = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n  line1.setAttribute('x1', '18');\n  line1.setAttribute('y1', '6');\n  line1.setAttribute('x2', '6');\n  line1.setAttribute('y2', '18');\n\n  const line2 = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n  line2.setAttribute('x1', '6');\n  line2.setAttribute('y1', '6');\n  line2.setAttribute('x2', '18');\n  line2.setAttribute('y2', '18');\n\n  svg.appendChild(line1);\n  svg.appendChild(line2);\n  button.appendChild(svg);\n\n  toast.appendChild(button);\n\n  backdrop.appendChild(toast);\n  backdrop.onclick = toggleNotification;\n\n  const style = document.createElement('style');\n  style.id = 'react-scan-no-react-styles';\n  style.appendChild(document.createTextNode(noReactStyles));\n\n  const fragment = document.createDocumentFragment();\n  fragment.appendChild(style);\n  fragment.appendChild(backdrop);\n\n  document.documentElement.appendChild(fragment);\n};\n\nexport const toggleNotification = () => {\n  if (!backdrop || isAnimating) return;\n  isAnimating = true;\n\n  const handleTransitionEnd = () => {\n    isAnimating = false;\n    backdrop?.removeEventListener('transitionend', handleTransitionEnd);\n  };\n\n  backdrop.addEventListener('transitionend', handleTransitionEnd);\n\n  const isVisible = backdrop.style.opacity === '1';\n  backdrop.style.opacity = isVisible ? '0' : '1';\n  backdrop.style.pointerEvents = isVisible ? 'none' : 'auto';\n  document.documentElement.classList.toggle('freeze', !isVisible);\n};\n"
  },
  {
    "path": "packages/extension/src/inject/react-scan.ts",
    "content": "// Bippy has a side-effect that installs the hook.\nimport 'bippy';\n"
  },
  {
    "path": "packages/extension/src/manifest.chrome.json",
    "content": "{\n  \"manifest_version\": 3,\n  \"name\": \"React Scan\",\n  \"version\": \"0.0.0\",\n  \"description\": \"Scan React apps for performance problems\",\n  \"icons\": {\n    \"16\": \"icons/disabled/16.png\",\n    \"32\": \"icons/disabled/32.png\",\n    \"48\": \"icons/disabled/48.png\",\n    \"128\": \"icons/disabled/128.png\"\n  },\n  \"action\": {\n    \"default_icon\": {\n      \"16\": \"icons/disabled/16.png\",\n      \"32\": \"icons/disabled/32.png\",\n      \"48\": \"icons/disabled/48.png\",\n      \"128\": \"icons/disabled/128.png\"\n    }\n  },\n  \"background\": {\n    \"service_worker\": \"src/background/index.ts\"\n  },\n  \"content_security_policy\": {\n    \"extension_pages\": \"script-src 'self'; object-src 'self'\"\n  },\n  \"permissions\": [\"activeTab\", \"tabs\", \"scripting\"],\n  \"host_permissions\": [\"<all_urls>\"],\n  \"content_scripts\": [\n    {\n      \"matches\": [\"<all_urls>\"],\n      \"js\": [\"src/inject/react-scan.ts\"],\n      \"run_at\": \"document_start\",\n      \"world\": \"MAIN\"\n    },\n    {\n      \"matches\": [\"<all_urls>\"],\n      \"js\": [\"src/inject/index.ts\"],\n      \"run_at\": \"document_start\",\n      \"world\": \"MAIN\"\n    },\n    {\n      \"matches\": [\"<all_urls>\"],\n      \"js\": [\"src/content/index.ts\"],\n      \"run_at\": \"document_start\"\n    }\n  ],\n  \"web_accessible_resources\": [\n    {\n      \"resources\": [\"icons/*\"],\n      \"matches\": [\"<all_urls>\"]\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/extension/src/manifest.firefox.json",
    "content": "{\n  \"manifest_version\": 2,\n  \"name\": \"React Scan\",\n  \"version\": \"0.0.0\",\n  \"description\": \"Scan React apps for performance problems\",\n  \"icons\": {\n    \"16\": \"icons/disabled/16.png\",\n    \"32\": \"icons/disabled/32.png\",\n    \"48\": \"icons/disabled/48.png\",\n    \"128\": \"icons/disabled/128.png\"\n  },\n  \"browser_specific_settings\": {\n    \"gecko\": {\n      \"id\": \"react-scan@million.dev\",\n      \"strict_min_version\": \"57.0\"\n    }\n  },\n  \"browser_action\": {\n    \"default_icon\": {\n      \"16\": \"icons/disabled/16.png\",\n      \"32\": \"icons/disabled/32.png\",\n      \"48\": \"icons/disabled/48.png\",\n      \"128\": \"icons/disabled/128.png\"\n    }\n  },\n  \"background\": {\n    \"scripts\": [\"src/background/index.ts\"]\n  },\n  \"permissions\": [\"activeTab\", \"tabs\", \"scripting\", \"<all_urls>\"],\n  \"content_security_policy\": \"script-src 'self' 'unsafe-eval'; object-src 'self'\",\n  \"content_scripts\": [\n    {\n      \"matches\": [\"<all_urls>\"],\n      \"js\": [\"src/inject/react-scan.ts\"],\n      \"run_at\": \"document_start\"\n    },\n    {\n      \"matches\": [\"<all_urls>\"],\n      \"js\": [\"src/inject/index.ts\"],\n      \"run_at\": \"document_start\"\n    },\n    {\n      \"matches\": [\"<all_urls>\"],\n      \"js\": [\"src/content/index.ts\"],\n      \"run_at\": \"document_start\"\n    }\n  ],\n  \"web_accessible_resources\": [\"icons/*\"]\n}\n"
  },
  {
    "path": "packages/extension/src/types/global.d.ts",
    "content": "import type * as reactScan from 'react-scan';\n\ndeclare global {\n  interface Window {\n    __REACT_SCAN__?: {\n      ReactScanInternals: {\n        version: string;\n        Store: {\n          monitor: {\n            value: boolean;\n          };\n        };\n      };\n    };\n    __REACT_SCAN_EXTENSION__?: boolean;\n    __REACT_SCAN_VERSION__?: string;\n    __REACT_DEVTOOLS_GLOBAL_HOOK__?: {\n      checkDCE: (fn: unknown) => void;\n      supportsFiber: boolean;\n      supportsFlight: boolean;\n      renderers: Map<number, ReactRenderer>;\n      hasUnsupportedRendererAttached: boolean;\n      onCommitFiberRoot: (\n        rendererID: number,\n        root: FiberRoot,\n        priority: void | number,\n      ) => void;\n      onCommitFiberUnmount: (rendererID: number, fiber: Fiber) => void;\n      onPostCommitFiberRoot: (rendererID: number, root: FiberRoot) => void;\n      inject: (renderer: ReactRenderer) => number;\n      _instrumentationSource?: string;\n      _instrumentationIsActive?: boolean;\n    };\n    hideIntro: boolean;\n    reactScan: typeof reactScan.setOptions | undefined;\n  }\n}\n"
  },
  {
    "path": "packages/extension/src/types/messages.ts",
    "content": "import { z } from 'zod';\n\nexport const BroadcastSchema = z.object({\n  type: z.enum([\n    'react-scan:ping',\n    'react-scan:is-enabled',\n    'react-scan:toggle-state',\n    'react-scan:page-reload',\n  ]),\n  data: z.any().optional(),\n});\n\nexport type BroadcastMessage = z.infer<typeof BroadcastSchema>;\n\nexport interface IEvents {\n  'react-scan:toggle-state': {\n    topic: 'react-scan:toggle-state';\n    message: undefined;\n  };\n  'react-scan:send-to-background': {\n    topic: 'react-scan:send-to-background';\n    message: BroadcastMessage;\n  };\n}\n"
  },
  {
    "path": "packages/extension/src/utils/constants.ts",
    "content": "export const STORAGE_KEY = 'react-scan-options';\nexport const EXTENSION_STORAGE_KEY = 'react-scan-extension';\n"
  },
  {
    "path": "packages/extension/src/utils/helpers.ts",
    "content": "export const isIframe = window !== window.top;\nexport const isPopup = window.opener !== null;\nexport const canLoadReactScan = !isIframe && !isPopup;\n\nexport const IS_CLIENT = typeof window !== 'undefined';\n\nexport const isInternalUrl = (url: string): boolean => {\n  if (!url) return false;\n\n  const allowedProtocols = ['http:', 'https:', 'file:'];\n  return !allowedProtocols.includes(new URL(url).protocol);\n};\n\ninterface ReactRootContainer {\n  _reactRootContainer?: {\n    _internalRoot?: {\n      current?: {\n        child: unknown;\n      };\n    };\n  };\n  __reactContainer$?: unknown;\n}\n\nconst ReactDetection = {\n  limits: {\n    MAX_DEPTH: 10,\n    MAX_ELEMENTS: 30,\n    ELEMENTS_PER_LEVEL: 5\n  },\n  nonVisualTags: new Set([\n    // Document level\n    'HTML', 'HEAD', 'META', 'TITLE', 'BASE',\n    // Scripts and styles\n    'SCRIPT', 'STYLE', 'LINK', 'NOSCRIPT',\n    // Media and embeds\n    'SOURCE', 'TRACK', 'EMBED', 'OBJECT', 'PARAM',\n    // Special elements\n    'TEMPLATE', 'PORTAL', 'SLOT',\n    // Others\n    'AREA', 'XML', 'DOCTYPE', 'COMMENT'\n  ]),\n  reactMarkers: {\n    root: '_reactRootContainer',\n    fiber: '__reactFiber',\n    instance: '__reactInternalInstance$',\n    container: '__reactContainer$'\n  }\n} as const;\n\nconst childrenCache = new WeakMap<Element, Element[]>();\n\nexport const hasReactFiber = (): boolean => {\n  const rootElement = document.body;\n  let elementsChecked = 0;\n\n  const getChildren = (element: Element): Element[] => {\n    let children = childrenCache.get(element);\n    if (!children) {\n      const childNodes = element.children;\n      children = [];\n      for (let i = 0; i < childNodes.length; i++) {\n        const child = childNodes[i];\n        if (!ReactDetection.nonVisualTags.has(child.tagName)) {\n          children.push(child);\n        }\n      }\n      childrenCache.set(element, children);\n    }\n    return children;\n  };\n\n  const checkElement = (element: Element, depth: number): boolean => {\n    if (elementsChecked >= ReactDetection.limits.MAX_ELEMENTS) return false;\n    elementsChecked++;\n\n    const props = Object.getOwnPropertyNames(element);\n\n    if (ReactDetection.reactMarkers.root in element) {\n      const elementWithRoot = element as unknown as ReactRootContainer;\n      const rootContainer = elementWithRoot._reactRootContainer;\n\n      const hasLegacyRoot = rootContainer?._internalRoot?.current?.child != null;\n      const hasContainerRoot = Object.keys(elementWithRoot).some(key =>\n        key.startsWith(ReactDetection.reactMarkers.container)\n      );\n\n      return hasLegacyRoot || hasContainerRoot;\n    }\n\n    for (const key of props) {\n      if (\n        key.startsWith(ReactDetection.reactMarkers.fiber) ||\n        key.startsWith(ReactDetection.reactMarkers.instance)\n      ) {\n        return true;\n      }\n    }\n\n    if (depth < ReactDetection.limits.MAX_DEPTH) {\n      const children = getChildren(element);\n      const maxCheck = Math.min(children.length, ReactDetection.limits.ELEMENTS_PER_LEVEL);\n\n      for (let i = 0; i < maxCheck; i++) {\n        if (checkElement(children[i], depth + 1)) {\n          return true;\n        }\n      }\n    }\n\n    return false;\n  };\n\n  return checkElement(rootElement, 0);\n};\n\nexport const readLocalStorage = <T>(storageKey: string): T | null => {\n  if (!IS_CLIENT) return null;\n\n  try {\n    const stored = localStorage.getItem(storageKey);\n    return stored ? JSON.parse(stored) : null;\n  } catch {\n    return null;\n  }\n};\n\nexport const saveLocalStorage = <T>(storageKey: string, state: T): void => {\n  if (!IS_CLIENT) return;\n\n  try {\n    window.localStorage.setItem(storageKey, JSON.stringify(state));\n  } catch {}\n};\n\nexport const removeLocalStorage = (storageKey: string): void => {\n  if (!IS_CLIENT) return;\n\n  try {\n    window.localStorage.removeItem(storageKey);\n  } catch {}\n};\n\nexport const debounce = <T extends (enabled: boolean | null) => Promise<void>>(\n  fn: T,\n  wait: number,\n  options: { leading?: boolean; trailing?: boolean } = {},\n) => {\n  let timeoutId: number | undefined;\n  let lastArg: boolean | null | undefined;\n  let isLeadingInvoked = false;\n\n  const debounced = (enabled: boolean | null) => {\n    lastArg = enabled;\n\n    if (options.leading && !isLeadingInvoked) {\n      isLeadingInvoked = true;\n      fn(enabled);\n      return;\n    }\n\n    if (timeoutId !== undefined) {\n      clearTimeout(timeoutId);\n    }\n\n    if (options.trailing !== false) {\n      timeoutId = setTimeout(() => {\n        isLeadingInvoked = false;\n        timeoutId = undefined;\n        if (lastArg !== undefined) {\n          fn(lastArg);\n        }\n      }, wait);\n    }\n  };\n\n  debounced.cancel = () => {\n    if (timeoutId !== undefined) {\n      clearTimeout(timeoutId);\n      timeoutId = undefined;\n      isLeadingInvoked = false;\n      lastArg = undefined;\n    }\n  };\n\n  return debounced;\n};\n\ntype EventCallback<T = unknown> = (data: T) => void;\nconst eventBus = new Map<string, Set<EventCallback>>();\n\nexport const busSubscribe = <T = unknown>(\n  event: string,\n  callback: EventCallback<T>,\n): (() => void) => {\n  if (!eventBus.has(event)) {\n    eventBus.set(event, new Set());\n  }\n  eventBus.get(event)!.add(callback as EventCallback);\n\n  return () => {\n    const callbacks = eventBus.get(event);\n    if (callbacks) {\n      callbacks.delete(callback as EventCallback);\n      if (callbacks.size === 0) {\n        eventBus.delete(event);\n      }\n    }\n  };\n};\n\nexport const busDispatch = <T = unknown>(event: string, data: T): void => {\n  const callbacks = eventBus.get(event);\n  if (callbacks) {\n    callbacks.forEach((callback) => callback(data));\n  }\n};\n\nexport const sleep = (ms: number): Promise<void> => {\n  return new Promise((resolve) => setTimeout(resolve, ms));\n};\n\nexport const storageGetItem = async <T>(\n  storageKey: string,\n  key: string,\n): Promise<T | null> => {\n  try {\n    const result = await chrome.storage.local.get(storageKey);\n    const data = result[storageKey];\n    return data?.[key] ?? null;\n  } catch {\n    return null;\n  }\n};\n\nexport const storageSetItem = async <T>(\n  storageKey: string,\n  key: string,\n  value: T,\n): Promise<void> => {\n  try {\n    const result = await chrome.storage.local.get(storageKey);\n    const data = result[storageKey] || {};\n    data[key] = value;\n    await chrome.storage.local.set({ [storageKey]: data });\n  } catch {\n  }\n};\n"
  },
  {
    "path": "packages/extension/src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "packages/extension/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"DOM\", \"DOM.Iterable\", \"ESNext\"],\n    \"allowJs\": false,\n    \"skipLibCheck\": true,\n    \"esModuleInterop\": false,\n    \"allowSyntheticDefaultImports\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Node\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n    \"paths\": {\n      \"react-scan\": [\n        \"../scan/dist\"\n      ],\n      \"~utils/*\": [\n        \"./src/utils/*\"\n      ],\n      \"~types/*\": [\n        \"./src/types/*\"\n      ],\n      \"~assets/*\": [\n        \"./src/assets/*\"\n      ]\n    },\n    \"types\": [\n      \"chrome\"\n    ]\n  },\n  \"include\": [\n    \"src\"\n  ],\n  \"references\": [{ \"path\": \"./tsconfig.node.json\" }]\n}\n"
  },
  {
    "path": "packages/extension/tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Node\",\n    \"allowSyntheticDefaultImports\": true\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "packages/extension/vite.config.ts",
    "content": "import react from '@vitejs/plugin-react';\nimport { type UserConfig, defineConfig, loadEnv } from 'vite';\nimport webExtension, { readJsonFile } from 'vite-plugin-web-extension';\nimport tsconfigPaths from 'vite-tsconfig-paths';\n\n// Browser types\nconst BROWSER_TYPES = {\n  CHROME: 'chrome',\n  FIREFOX: 'firefox',\n  BRAVE: 'brave',\n} as const;\n\ntype BrowserType = (typeof BROWSER_TYPES)[keyof typeof BROWSER_TYPES];\n\nexport default defineConfig(({ mode }): UserConfig => {\n  const env = loadEnv(mode, process.cwd(), '');\n  const browser = (env.BROWSER || BROWSER_TYPES.CHROME) as BrowserType;\n\n  const isBrave = browser === BROWSER_TYPES.BRAVE;\n\n  // Validate Brave binary\n  if (env.NODE_ENV === 'development' && isBrave && !env.BRAVE_BINARY) {\n    // oxlint-disable-next-line no-console\n    console.error(`\n    ⚛️  React Scan\n    ==============\n    🚫 Error: BRAVE_BINARY environment variable is missing\n\n    This is required for Brave browser development.\n    Please check .env.example and set up your .env file with the correct path:\n\n    📍 For macOS:\n       BRAVE_BINARY=\"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser\"\n\n    📍 For Windows:\n       BRAVE_BINARY=\"C:\\\\Program Files\\\\BraveSoftware\\\\Brave-Browser\\\\Application\\\\brave.exe\"\n\n    📍 For Linux:\n       BRAVE_BINARY=\"/usr/bin/brave\"\n    ===============\n    `);\n    process.exit(0);\n  }\n\n  // Get browser binary based on type\n  const getBrowserBinary = () => {\n    switch (browser) {\n      case BROWSER_TYPES.FIREFOX:\n        return env.FIREFOX_BINARY;\n      case BROWSER_TYPES.BRAVE:\n        return env.BRAVE_BINARY || env.CHROME_BINARY;\n      case BROWSER_TYPES.CHROME:\n        return env.CHROME_BINARY;\n      default:\n        return env.CHROME_BINARY;\n    }\n  };\n\n  // Generate manifest with package info\n  const generateManifest = () => {\n    const manifestPath =\n      browser === BROWSER_TYPES.FIREFOX\n        ? 'src/manifest.firefox.json'\n        : 'src/manifest.chrome.json';\n\n    const manifest = readJsonFile(manifestPath);\n    const pkg = readJsonFile('package.json');\n\n    return {\n      name: pkg.name,\n      description: pkg.description,\n      ...manifest,\n      version: pkg.version,\n    };\n  };\n\n  // Vite configuration\n  return {\n    build: {\n      minify: 'esbuild' as const,\n    },\n    esbuild: {\n      keepNames: true,\n      minifyIdentifiers: false,\n    },\n    plugins: [\n      react(),\n      tsconfigPaths(),\n      webExtension({\n        manifest: generateManifest,\n        // Use Chrome config for Brave\n        webExtConfig: {\n          target: isBrave\n            ? 'chromium'\n            : browser === 'firefox'\n              ? 'firefox-desktop'\n              : 'chromium',\n          chromiumBinary: getBrowserBinary(),\n          firefoxBinary: env.FIREFOX_BINARY,\n          startUrl: ['https://github.com/aidenybai/react-scan'],\n        },\n      }),\n    ],\n    optimizeDeps: {\n      exclude: ['react-scan'],\n    },\n  };\n});\n"
  },
  {
    "path": "packages/scan/.gitignore",
    "content": "src/web/assets/css/styles.css\n"
  },
  {
    "path": "packages/scan/CHANGELOG.md",
    "content": "# react-scan\n\n## 0.5.3\n\n### Patch Changes\n\n- fix\n\n## 0.5.2\n\n### Patch Changes\n\n- fix\n\n## 0.5.1\n\n### Patch Changes\n\n- fix: infinite mounting\n\n## 0.5.0\n\n### Minor Changes\n\n- cleanup\n- 9d38ffe: Remove monitoring module, replace Playwright CLI with interactive init command, clean up dead code\n\n  - Removed the entire monitoring system (`packages/scan/src/core/monitor/`) and all related exports, types, and build entries\n  - Replaced the Playwright-based proxy CLI (`npx react-scan <url>`) with an interactive `npx react-scan init` command that auto-detects your framework and sets up React Scan\n  - Removed unused code: old outline system, LRU cache, lazy refs, commented-out code blocks, and unused exports\n  - Consolidated duplicate utilities (safeGetValue, RenderPhase types)\n  - Simplified README to focus on the new init command\n  - Added CLI quick-start command to the website homepage\n"
  },
  {
    "path": "packages/scan/README.md",
    "content": "# <img src=\"https://github.com/aidenybai/react-scan/blob/main/.github/assets/logo.svg\" width=\"30\" height=\"30\" align=\"center\" /> React Scan\n\nReact Scan automatically detects performance issues in your React app.\n\nPreviously, tools like:\n\n- [`<Profiler />`](https://react.dev/reference/react/Profiler) required lots of manual changes\n- [Why Did You Render?](https://github.com/welldone-software/why-did-you-render) lacked simple visual cues\n- [React Devtools](https://legacy.reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html) didn't have a simple, portable, and programmatic API\n\nReact Scan attempts to solve these problems:\n\n- It requires no code changes – just drop it in\n- It highlights exactly the components you need to optimize\n- Use it via script tag, npm, CLI, you name it!\n\nTrusted by engineering teams at:\n\nAirbnb&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href=\"https://polaris.shopify.com/\"><img src=\"https://raw.githubusercontent.com/aidenybai/react-scan/refs/heads/main/.github/assets/shopify-logo.png\" height=\"30\" align=\"center\" /></a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href=\"https://www.faire.com/\"><img src=\"https://raw.githubusercontent.com/aidenybai/react-scan/refs/heads/main/.github/assets/faire-logo.svg\" height=\"20\" align=\"center\" /></a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href=\"https://perplexity.com/\"><img src=\"https://raw.githubusercontent.com/aidenybai/react-scan/refs/heads/main/.github/assets/perplexity-logo.png\" height=\"30\" align=\"center\" /></a>\n\n### [**Try it out! →**](https://react-scan.million.dev)\n\n![React Scan in action](https://raw.githubusercontent.com/aidenybai/react-scan/refs/heads/main/.github/assets/demo.gif)\n\n> [!IMPORTANT]\n> Want to monitor issues in production? Check out [React Scan Monitoring](https://react-scan.com/monitoring)!\n\n## Install\n\n### Package managers\n\n```bash\nnpm i react-scan\n```\n\n```bash\npnpm add react-scan\n```\n\n```bash\nyarn add react-scan\n```\n\n### CDN\n\n```html\n<!-- import this BEFORE any scripts -->\n<script src=\"https://unpkg.com/react-scan/dist/auto.global.js\"></script>\n```\n\n## Usage\n\n- [NextJS App Router](https://github.com/aidenybai/react-scan/blob/main/docs/installation/next-js-app-router.md)\n- [NextJS Page Router](https://github.com/aidenybai/react-scan/blob/main/docs/installation/next-js-page-router.md)\n- [Create React App](https://github.com/aidenybai/react-scan/blob/main/docs/installation/create-react-app.md)\n- [Vite](https://github.com/aidenybai/react-scan/blob/main/docs/installation/vite.md)\n- [Parcel](https://github.com/aidenybai/react-scan/blob/main/docs/installation/parcel.md)\n- [Remix](https://github.com/aidenybai/react-scan/blob/main/docs/installation/remix.md)\n- [React Router](https://github.com/aidenybai/react-scan/blob/main/docs/installation/react-router.md)\n- [Astro](https://github.com/aidenybai/react-scan/blob/main/docs/installation/astro.md)\n- [TanStack Start](https://github.com/aidenybai/react-scan/blob/main/docs/installation/tanstack-start.md)\n\n### CLI\n\nIf you don't have a local version of the site or you want to test a React app remotely, you can use the CLI. This will spin up an isolated browser instance which you can interact or use React Scan with.\n\n```bash\nnpx react-scan@latest http://localhost:3000\n# you can technically scan ANY website on the web:\n# npx react-scan@latest https://react.dev\n```\n\nYou can add it to your existing dev process as well. Here's an example for Next.js:\n\n```json\n{\n  \"scripts\": {\n    \"dev\": \"next dev\",\n    \"scan\": \"next dev & npx react-scan@latest localhost:3000\"\n  }\n}\n```\n\n### Browser Extension\n\nIf you want to install the extension, follow the guide [here](https://github.com/aidenybai/react-scan/blob/main/BROWSER_EXTENSION_GUIDE.md).\n\n### React Native\n\nSee [discussion](https://github.com/aidenybai/react-scan/pull/23)\n\n## API Reference\n\n<details>\n<summary><code>Options</code></summary>\n\n<br />\n\n```tsx\nexport interface Options {\n  /**\n   * Enable/disable scanning\n   *\n   * Please use the recommended way:\n   * enabled: process.env.NODE_ENV === 'development',\n   *\n   * @default true\n   */\n  enabled?: boolean;\n\n  /**\n   * Force React Scan to run in production (not recommended)\n   *\n   * @default false\n   */\n  dangerouslyForceRunInProduction?: boolean;\n  /**\n   * Log renders to the console\n   *\n   * WARNING: This can add significant overhead when the app re-renders frequently\n   *\n   * @default false\n   */\n  log?: boolean;\n\n  /**\n   * Show toolbar bar\n   *\n   * If you set this to true, and set {@link enabled} to false, the toolbar will still show, but scanning will be disabled.\n   *\n   * @default true\n   */\n  showToolbar?: boolean;\n\n  /**\n   * Animation speed\n   *\n   * @default \"fast\"\n   */\n  animationSpeed?: \"slow\" | \"fast\" | \"off\";\n\n  /**\n   * Track unnecessary renders, and mark their outlines gray when detected\n   *\n   * An unnecessary render is defined as the component re-rendering with no change to the component's\n   * corresponding dom subtree\n   *\n   *  @default false\n   *  @warning tracking unnecessary renders can add meaningful overhead to react-scan\n   */\n  trackUnnecessaryRenders?: boolean;\n\n  onCommitStart?: () => void;\n  onRender?: (fiber: Fiber, renders: Array<Render>) => void;\n  onCommitFinish?: () => void;\n  onPaintStart?: (outlines: Array<Outline>) => void;\n  onPaintFinish?: (outlines: Array<Outline>) => void;\n}\n```\n\n</details>\n\n- `scan(options: Options)`: Imperative API to start scanning\n- `useScan(options: Options)`: Hook API to start scanning\n- `getReport()`: Get a report of all the renders\n- `setOptions(options: Options): void`: Set options at runtime\n- `getOptions()`: Get the current options\n- `onRender(Component, onRender: (fiber: Fiber, render: Render) => void)`: Hook into a specific component's renders\n\n## Why React Scan?\n\nReact can be tricky to optimize.\n\nThe issue is that component props are compared by reference, not value. This is intentional – this way rendering can be cheap to run.\n\nHowever, this makes it easy to accidentally cause unnecessary renders, making the app slow. Even in production apps, with hundreds of engineers, can't fully optimize their apps (see [GitHub](https://github.com/aidenybai/react-scan/blob/main/.github/assets/github.mp4), [Twitter](https://github.com/aidenybai/react-scan/blob/main/.github/assets/twitter.mp4), and [Instagram](https://github.com/aidenybai/react-scan/blob/main/.github/assets/instagram.mp4)).\n\nThis often comes down to props that update in reference, like callbacks or object values. For example, the `onClick` function and `style` object are re-created on every render, causing `ExpensiveComponent` to slow down the app:\n\n```jsx\n<ExpensiveComponent onClick={() => alert(\"hi\")} style={{ color: \"purple\" }} />\n```\n\nReact Scan helps you identify these issues by automatically detecting and highlighting renders that cause performance issues. Now, instead of guessing, you can see exactly which components you need to fix.\n\n> Want monitor issues in production? Check out [React Scan Monitoring](https://react-scan.com/monitoring)!\n\n### FAQ\n\n**Q: Why this instead of React Devtools?**\n\nReact Devtools aims to be a general purpose tool for React. However, I deal with React performance issues every day, and React Devtools doesn't fix my problems well. There's a lot of noise (no obvious distinction between unnecessary and necessary renders), and there's no programmatic API. If it sounds like you have the same problems, then React Scan may be a better choice.\n\nAlso, some personal complaints about React Devtools' highlight feature:\n\n- React Devtools \"batches\" paints, so if a component renders too fast, it will lag behind and only show 1 every second or so\n- When you scroll/resize the boxes don't update position\n- No count of how many renders there are\n- I don't know what the bad/slow renders are without inspecting\n- The menu is hidden away so it's annoying to turn on/off, user experience should be specifically tuned for debugging performance, instead of hidden behind a profiler/component tree\n- No programmatic API\n- It's stuck in a chrome extension, I want to run it anywhere on the web\n- It looks subjectively ugly (lines look fuzzy, feels sluggish)\n- I'm more ambitious with react-scan\n\n## Resources & Contributing Back\n\nWant to try it out? Check the [our demo](https://react-scan.million.dev).\n\nLooking to contribute back? Check the [Contributing Guide](https://github.com/aidenybai/react-scan/blob/main/CONTRIBUTING.md) out.\n\nWant to talk to the community? Hop in our [Discord](https://discord.gg/X9yFbcV2rF) and share your ideas and what you've build with React Scan.\n\nFind a bug? Head over to our [issue tracker](https://github.com/aidenybai/react-scan/issues) and we'll do our best to help. We love pull requests, too!\n\nWe expect all contributors to abide by the terms of our [Code of Conduct](https://github.com/aidenybai/react-scan/blob/main/.github/CODE_OF_CONDUCT.md).\n\n[**→ Start contributing on GitHub**](https://github.com/aidenybai/react-scan/blob/main/CONTRIBUTING.md)\n\n## Acknowledgments\n\nReact Scan takes inspiration from the following projects:\n\n- [React Devtools](https://react.dev/learn/react-developer-tools) for the initial idea of [highlighting renders](https://medium.com/dev-proto/highlight-react-components-updates-1b2832f2ce48). We chose to diverge from this to provide a [better developer experience](https://x.com/aidenybai/status/1857122670929969551)\n- [Million Lint](https://million.dev) for scanning and linting approaches\n- [Why Did You Render?](https://github.com/welldone-software/why-did-you-render) for the concept of hijacking internals to detect unnecessary renders caused by \"unstable\" props\n\n## License\n\nReact Scan is [MIT-licensed](LICENSE) open-source software by Aiden Bai, [Million Software, Inc.](https://million.dev), and [contributors](https://github.com/aidenybai/react-scan/graphs/contributors).\n"
  },
  {
    "path": "packages/scan/auto.d.ts",
    "content": "export * from './dist/auto';\n"
  },
  {
    "path": "packages/scan/bin/cli.js",
    "content": "#! /usr/bin/env node\nrequire('../dist/cli');\n"
  },
  {
    "path": "packages/scan/global.d.ts",
    "content": "declare module '*.css' {\n  const content: string;\n  export default content;\n}\n\ndeclare module '*.astro' {\n  const Component: unknown;\n  export default Component;\n}\n"
  },
  {
    "path": "packages/scan/package.json",
    "content": "{\n  \"name\": \"react-scan\",\n  \"version\": \"0.5.3\",\n  \"description\": \"Scan your React app for renders\",\n  \"keywords\": [\n    \"react\",\n    \"react-scan\",\n    \"react scan\",\n    \"render\",\n    \"performance\"\n  ],\n  \"homepage\": \"https://react-scan.million.dev\",\n  \"bugs\": {\n    \"url\": \"https://github.com/aidenybai/react-scan/issues\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/aidenybai/react-scan.git\"\n  },\n  \"license\": \"MIT\",\n  \"author\": {\n    \"name\": \"Aiden Bai\",\n    \"email\": \"aiden@million.dev\",\n    \"url\": \"https://million.dev\"\n  },\n  \"scripts\": {\n    \"build\": \"npm run build:css && NODE_ENV=production tsup\",\n    \"postbuild\": \"node ../../scripts/version-warning.mjs\",\n    \"build:copy\": \"npm run build:css && NODE_ENV=production tsup && cat dist/auto.global.js | pbcopy\",\n    \"dev:css\": \"postcss ./src/web/assets/css/styles.tailwind.css -o ./src/web/assets/css/styles.css --watch\",\n    \"dev:tsup\": \"NODE_ENV=development tsup --watch\",\n    \"dev\": \"pnpm run --parallel \\\"/^dev:(css|tsup)/\\\"\",\n    \"build:css\": \"postcss ./src/web/assets/css/styles.tailwind.css -o ./src/web/assets/css/styles.css\",\n    \"pack\": \"npm version patch && pnpm build && npm pack\",\n    \"pack:bump\": \"bun scripts/bump-version.js && nr pack && echo $(pwd)/react-scan-$(node -p \\\"require('./package.json').version\\\").tgz | pbcopy\",\n    \"publint\": \"publint\",\n    \"test\": \"vitest\",\n    \"lint\": \"oxlint src && pnpm typecheck\",\n    \"typecheck\": \"tsc --noEmit\"\n  },\n  \"exports\": {\n    \"./package.json\": \"./package.json\",\n    \".\": {\n      \"production\": {\n        \"import\": {\n          \"types\": \"./dist/index.d.mts\",\n          \"react-server\": \"./dist/rsc-shim.mjs\",\n          \"default\": \"./dist/index.mjs\"\n        },\n        \"require\": {\n          \"types\": \"./dist/index.d.mts\",\n          \"react-server\": \"./dist/rsc-shim.js\",\n          \"default\": \"./dist/index.mjs\"\n        }\n      },\n      \"development\": {\n        \"import\": {\n          \"types\": \"./dist/index.d.mts\",\n          \"react-server\": \"./dist/rsc-shim.mjs\",\n          \"default\": \"./dist/index.mjs\"\n        },\n        \"require\": {\n          \"types\": \"./dist/index.d.ts\",\n          \"react-server\": \"./dist/rsc-shim.js\",\n          \"default\": \"./dist/index.js\"\n        }\n      },\n      \"default\": {\n        \"import\": {\n          \"types\": \"./dist/index.d.mts\",\n          \"react-server\": \"./dist/rsc-shim.mjs\",\n          \"default\": \"./dist/index.mjs\"\n        },\n        \"require\": {\n          \"types\": \"./dist/index.d.ts\",\n          \"react-server\": \"./dist/rsc-shim.js\",\n          \"default\": \"./dist/index.js\"\n        }\n      }\n    },\n    \"./all-environments\": {\n      \"types\": \"./dist/core/all-environments.d.ts\",\n      \"import\": \"./dist/core/all-environments.mjs\",\n      \"require\": \"./dist/core/all-environments.js\"\n    },\n    \"./install-hook\": {\n      \"types\": \"./dist/install-hook.d.ts\",\n      \"import\": \"./dist/install-hook.mjs\",\n      \"require\": \"./dist/install-hook.js\"\n    },\n    \"./auto\": {\n      \"production\": {\n        \"import\": {\n          \"types\": \"./dist/rsc-shim.d.mts\",\n          \"react-server\": \"./dist/rsc-shim.mjs\",\n          \"default\": \"./dist/rsc-shim.mjs\"\n        },\n        \"require\": {\n          \"types\": \"./dist/rsc-shim.d.ts\",\n          \"react-server\": \"./dist/rsc-shim.js\",\n          \"default\": \"./dist/rsc-shim.js\"\n        }\n      },\n      \"development\": {\n        \"import\": {\n          \"types\": \"./dist/auto.d.mts\",\n          \"react-server\": \"./dist/rsc-shim.mjs\",\n          \"default\": \"./dist/auto.mjs\"\n        },\n        \"require\": {\n          \"types\": \"./dist/auto.d.ts\",\n          \"react-server\": \"./dist/rsc-shim.js\",\n          \"default\": \"./dist/auto.js\"\n        }\n      }\n    },\n    \"./dist/*\": \"./dist/*.js\",\n    \"./dist/*.js\": \"./dist/*.js\",\n    \"./dist/*.mjs\": \"./dist/*.mjs\",\n    \"./react-component-name/vite\": {\n      \"types\": \"./dist/react-component-name/vite.d.ts\",\n      \"import\": \"./dist/react-component-name/vite.mjs\",\n      \"require\": \"./dist/react-component-name/vite.js\"\n    },\n    \"./react-component-name/webpack\": {\n      \"types\": \"./dist/react-component-name/webpack.d.ts\",\n      \"import\": \"./dist/react-component-name/webpack.mjs\",\n      \"require\": \"./dist/react-component-name/webpack.js\"\n    },\n    \"./react-component-name/esbuild\": {\n      \"types\": \"./dist/react-component-name/esbuild.d.ts\",\n      \"import\": \"./dist/react-component-name/esbuild.mjs\",\n      \"require\": \"./dist/react-component-name/esbuild.js\"\n    },\n    \"./react-component-name/rspack\": {\n      \"types\": \"./dist/react-component-name/rspack.d.ts\",\n      \"import\": \"./dist/react-component-name/rspack.mjs\",\n      \"require\": \"./dist/react-component-name/rspack.js\"\n    },\n    \"./react-component-name/rolldown\": {\n      \"types\": \"./dist/react-component-name/rolldown.d.ts\",\n      \"import\": \"./dist/react-component-name/rolldown.mjs\",\n      \"require\": \"./dist/react-component-name/rolldown.js\"\n    },\n    \"./react-component-name/rollup\": {\n      \"types\": \"./dist/react-component-name/rollup.d.ts\",\n      \"import\": \"./dist/react-component-name/rollup.mjs\",\n      \"require\": \"./dist/react-component-name/rollup.js\"\n    },\n    \"./react-component-name/astro\": {\n      \"types\": \"./dist/react-component-name/astro.d.ts\",\n      \"import\": \"./dist/react-component-name/astro.mjs\",\n      \"require\": \"./dist/react-component-name/astro.js\"\n    },\n    \"./react-component-name/loader\": {\n      \"types\": \"./dist/react-component-name/loader.d.ts\",\n      \"import\": \"./dist/react-component-name/loader.mjs\",\n      \"require\": \"./dist/react-component-name/loader.js\"\n    }\n  },\n  \"main\": \"dist/index.js\",\n  \"module\": \"dist/index.mjs\",\n  \"browser\": \"dist/auto.global.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"typesVersions\": {\n    \"*\": {\n      \"react-component-name/vite\": [\n        \"./dist/react-component-name/vite.d.ts\"\n      ],\n      \"react-component-name/webpack\": [\n        \"./dist/react-component-name/webpack.d.ts\"\n      ],\n      \"react-component-name/esbuild\": [\n        \"./dist/react-component-name/esbuild.d.ts\"\n      ],\n      \"react-component-name/rspack\": [\n        \"./dist/react-component-name/rspack.d.ts\"\n      ],\n      \"react-component-name/rolldown\": [\n        \"./dist/react-component-name/rolldown.d.ts\"\n      ],\n      \"react-component-name/rollup\": [\n        \"./dist/react-component-name/rollup.d.ts\"\n      ],\n      \"react-component-name/astro\": [\n        \"./dist/react-component-name/astro.d.ts\"\n      ],\n      \"react-component-name/loader\": [\n        \"./dist/react-component-name/loader.d.ts\"\n      ]\n    }\n  },\n  \"bin\": \"bin/cli.js\",\n  \"files\": [\n    \"dist\",\n    \"bin\",\n    \"package.json\",\n    \"README.md\",\n    \"LICENSE\",\n    \"auto.d.ts\"\n  ],\n  \"dependencies\": {\n    \"@babel/core\": \"^7.26.0\",\n    \"@babel/generator\": \"^7.26.2\",\n    \"@babel/types\": \"^7.26.0\",\n    \"@preact/signals\": \"^1.3.1\",\n    \"@rollup/pluginutils\": \"^5.1.3\",\n    \"@types/node\": \"^20.17.9\",\n    \"bippy\": \"^0.5.30\",\n    \"commander\": \"^14.0.0\",\n    \"esbuild\": \"^0.25.0\",\n    \"estree-walker\": \"^3.0.3\",\n    \"picocolors\": \"^1.1.1\",\n    \"preact\": \"^10.25.1\",\n    \"prompts\": \"^2.4.2\"\n  },\n  \"devDependencies\": {\n    \"@esbuild-plugins/tsconfig-paths\": \"^0.1.2\",\n    \"@remix-run/react\": \"*\",\n    \"@types/babel__core\": \"^7.20.5\",\n    \"@types/prompts\": \"^2.4.9\",\n    \"@types/react\": \"^18.0.0\",\n    \"@types/react-router\": \"^5.1.0\",\n    \"clsx\": \"^2.1.1\",\n    \"es-module-lexer\": \"^1.5.4\",\n    \"next\": \"*\",\n    \"postcss-cli\": \"^11.0.0\",\n    \"publint\": \"^0.2.12\",\n    \"react\": \"*\",\n    \"react-dom\": \"*\",\n    \"react-router\": \"^5.0.0\",\n    \"react-router-dom\": \"^5.0.0 || ^6.0.0 || ^7.0.0\",\n    \"tailwind-merge\": \"^2.5.5\",\n    \"terser\": \"^5.36.0\",\n    \"tsup\": \"^8.0.0\",\n    \"vitest\": \"^3.0.0\"\n  },\n  \"peerDependencies\": {\n    \"react\": \"^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0\",\n    \"react-dom\": \"^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0\"\n  },\n  \"optionalDependencies\": {\n    \"unplugin\": \"2.1.0\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  }\n}\n"
  },
  {
    "path": "packages/scan/postcss.config.mjs",
    "content": "import autoprefixer from 'autoprefixer';\nimport tailwindcss from 'tailwindcss';\nimport remToPx from './postcss.rem2px.mjs';\n\nexport default {\n  plugins: [remToPx({ baseValue: 16 }), tailwindcss, autoprefixer],\n};\n"
  },
  {
    "path": "packages/scan/postcss.rem2px.mjs",
    "content": "const remToPx = (options = {}) => {\n  const baseValue = options.baseValue || 16;\n\n  // Improved regex that handles all rem cases including negatives\n  const remRegex = /(?<![-\\w])(-)?((?:\\d*\\.)?\\d+)rem\\b/g;\n\n  const convertRemToPx = (value) => {\n    // Handle all cases: calc(), min(), max(), clamp(), and regular values\n    return value.replace(remRegex, (_match, negative, num) => {\n      const pixels = Number.parseFloat(num) * baseValue;\n      return `${negative ? '-' : ''}${pixels}px`;\n    });\n  };\n\n  return {\n    postcssPlugin: 'postcss-rem-to-px',\n    prepare() {\n      return {\n        Once(root) {\n          root.walkDecls((decl) => {\n            if (decl.value?.includes('rem')) {\n              decl.value = convertRemToPx(decl.value);\n            }\n          });\n        },\n        Declaration(decl) {\n          if (decl.value?.includes('rem')) {\n            decl.value = convertRemToPx(decl.value);\n          }\n        },\n        AtRule: {\n          media: (atRule) => {\n            if (atRule.params?.includes('rem')) {\n              atRule.params = convertRemToPx(atRule.params);\n            }\n          },\n        },\n      };\n    },\n  };\n};\n\nremToPx.postcss = true;\n\nexport default remToPx;\n"
  },
  {
    "path": "packages/scan/scripts/bump-version.js",
    "content": "import { execSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\n// Read the current version from scan package.json\nconst scanPackagePath = path.join(__dirname, '../package.json');\nconst scanPackage = JSON.parse(fs.readFileSync(scanPackagePath, 'utf8'));\n\n// Bump patch version\nconst version = scanPackage.version.split('.');\nversion[2] = Number.parseInt(version[2]) + 1;\nconst newVersion = version.join('.');\n\n// Update the version in package.json\nscanPackage.version = newVersion;\n\n// Write back to package.json\nfs.writeFileSync(scanPackagePath, `${JSON.stringify(scanPackage, null, 2)}\\n`);\n\n// Get the tar file path\nconst tarFileName = `react-scan-${newVersion}.tgz`;\nconst tarFilePath = path.join(__dirname, '..', tarFileName);\n\n// Copy to clipboard\nexecSync(`echo \"${tarFilePath}\" | pbcopy`);\n\n// oxlint-disable-next-line no-console\nconsole.log(`Bumped version to ${newVersion}`);\n// oxlint-disable-next-line no-console\nconsole.log(`Tar file path copied to clipboard: ${tarFilePath}`);\n"
  },
  {
    "path": "packages/scan/src/auto.ts",
    "content": "import './polyfills';\n// Prioritize bippy side-effect\nimport 'bippy';\n\nimport { IS_CLIENT } from '~web/utils/constants';\nimport { scan } from './index';\n\nif (IS_CLIENT) {\n  scan();\n  window.reactScan = scan;\n}\n\nexport * from './core';\n"
  },
  {
    "path": "packages/scan/src/cli-utils.mts",
    "content": "import { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\ntype PackageManager = 'npm' | 'yarn' | 'pnpm' | 'bun';\ntype Framework = 'next' | 'vite' | 'tanstack' | 'webpack' | 'unknown';\ntype NextRouterType = 'app' | 'pages' | 'unknown';\n\ninterface ProjectInfo {\n  packageManager: PackageManager;\n  framework: Framework;\n  nextRouterType: NextRouterType;\n  projectRoot: string;\n  hasReactScan: boolean;\n}\n\ninterface TransformResult {\n  success: boolean;\n  filePath: string;\n  message: string;\n  originalContent?: string;\n  newContent?: string;\n  noChanges?: boolean;\n}\n\ninterface DiffLine {\n  type: 'added' | 'removed' | 'unchanged';\n  content: string;\n}\n\nconst FRAMEWORK_NAMES: Record<Framework, string> = {\n  next: 'Next.js',\n  vite: 'Vite',\n  tanstack: 'TanStack Start',\n  webpack: 'Webpack',\n  unknown: 'Unknown',\n};\n\nconst INSTALL_COMMANDS: Record<PackageManager, string> = {\n  npm: 'npm install -D',\n  yarn: 'yarn add -D',\n  pnpm: 'pnpm add -D',\n  bun: 'bun add -D',\n};\n\n// --- Templates ---\n\nconst REACT_SCAN_SCRIPT_TAG = '<script src=\"https://unpkg.com/react-scan/dist/auto.global.js\" crossorigin=\"anonymous\"></script>';\n\nconst NEXT_APP_ROUTER_SCRIPT = `{process.env.NODE_ENV === \"development\" && (\n          <script src=\"https://unpkg.com/react-scan/dist/auto.global.js\" crossOrigin=\"anonymous\" />\n        )}`;\n\nconst NEXT_PAGES_ROUTER_SCRIPT = `{process.env.NODE_ENV === \"development\" && (\n          <script src=\"https://unpkg.com/react-scan/dist/auto.global.js\" crossOrigin=\"anonymous\" />\n        )}`;\n\nconst VITE_SCRIPT = `<script src=\"https://unpkg.com/react-scan/dist/auto.global.js\" crossorigin=\"anonymous\"></script>`;\n\nconst WEBPACK_IMPORT = `if (process.env.NODE_ENV === \"development\") {\n  import(\"react-scan\");\n}`;\n\n// --- Detection ---\n\nconst detectPackageManager = (projectRoot: string): PackageManager => {\n  if (existsSync(join(projectRoot, 'bun.lockb')) || existsSync(join(projectRoot, 'bun.lock'))) return 'bun';\n  if (existsSync(join(projectRoot, 'pnpm-lock.yaml'))) return 'pnpm';\n  if (existsSync(join(projectRoot, 'yarn.lock'))) return 'yarn';\n  return 'npm';\n};\n\nconst detectFramework = (projectRoot: string): Framework => {\n  const packageJsonPath = join(projectRoot, 'package.json');\n  if (!existsSync(packageJsonPath)) return 'unknown';\n\n  try {\n    const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n    const allDeps = {\n      ...packageJson.dependencies,\n      ...packageJson.devDependencies,\n    };\n\n    if (allDeps['next']) return 'next';\n    if (allDeps['@tanstack/react-start']) return 'tanstack';\n    if (allDeps['vite']) return 'vite';\n    if (allDeps['webpack'] || allDeps['react-scripts']) return 'webpack';\n\n    return 'unknown';\n  } catch {\n    return 'unknown';\n  }\n};\n\nconst detectNextRouterType = (projectRoot: string): NextRouterType => {\n  if (existsSync(join(projectRoot, 'app')) || existsSync(join(projectRoot, 'src', 'app'))) return 'app';\n  if (existsSync(join(projectRoot, 'pages')) || existsSync(join(projectRoot, 'src', 'pages'))) return 'pages';\n  return 'unknown';\n};\n\nconst detectProject = (cwd: string): ProjectInfo => {\n  const packageManager = detectPackageManager(cwd);\n  const framework = detectFramework(cwd);\n  const nextRouterType = framework === 'next' ? detectNextRouterType(cwd) : 'unknown';\n\n  const packageJsonPath = join(cwd, 'package.json');\n  let hasReactScan = false;\n  if (existsSync(packageJsonPath)) {\n    try {\n      const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n      const allDeps = {\n        ...packageJson.dependencies,\n        ...packageJson.devDependencies,\n      };\n      hasReactScan = Boolean(allDeps['react-scan']);\n    } catch { /* */ }\n  }\n\n  return {\n    packageManager,\n    framework,\n    nextRouterType,\n    projectRoot: cwd,\n    hasReactScan,\n  };\n};\n\n// --- File Finding ---\n\nconst findLayoutFile = (\n  projectRoot: string,\n  routerType: NextRouterType,\n): string | null => {\n  if (routerType === 'app') {\n    const candidates = [\n      join(projectRoot, 'app', 'layout.tsx'),\n      join(projectRoot, 'app', 'layout.jsx'),\n      join(projectRoot, 'app', 'layout.js'),\n      join(projectRoot, 'src', 'app', 'layout.tsx'),\n      join(projectRoot, 'src', 'app', 'layout.jsx'),\n      join(projectRoot, 'src', 'app', 'layout.js'),\n    ];\n    return candidates.find(existsSync) ?? null;\n  }\n\n  if (routerType === 'pages') {\n    const candidates = [\n      join(projectRoot, 'pages', '_document.tsx'),\n      join(projectRoot, 'pages', '_document.jsx'),\n      join(projectRoot, 'pages', '_document.js'),\n      join(projectRoot, 'src', 'pages', '_document.tsx'),\n      join(projectRoot, 'src', 'pages', '_document.jsx'),\n      join(projectRoot, 'src', 'pages', '_document.js'),\n    ];\n    return candidates.find(existsSync) ?? null;\n  }\n\n  return null;\n};\n\nconst findIndexHtml = (projectRoot: string): string | null => {\n  const candidates = [\n    join(projectRoot, 'index.html'),\n    join(projectRoot, 'public', 'index.html'),\n    join(projectRoot, 'src', 'index.html'),\n  ];\n  return candidates.find(existsSync) ?? null;\n};\n\nconst findEntryFile = (projectRoot: string): string | null => {\n  const candidates = [\n    join(projectRoot, 'src', 'index.tsx'),\n    join(projectRoot, 'src', 'index.ts'),\n    join(projectRoot, 'src', 'index.jsx'),\n    join(projectRoot, 'src', 'index.js'),\n    join(projectRoot, 'src', 'main.tsx'),\n    join(projectRoot, 'src', 'main.ts'),\n    join(projectRoot, 'src', 'main.jsx'),\n    join(projectRoot, 'src', 'main.js'),\n  ];\n  return candidates.find(existsSync) ?? null;\n};\n\nconst hasReactScanCode = (content: string): boolean => {\n  return content.includes('react-scan') || content.includes('react_scan');\n};\n\n// --- Transform ---\n\nconst transformNextAppRouter = (\n  projectRoot: string,\n  routerType: NextRouterType,\n): TransformResult => {\n  const layoutPath = findLayoutFile(projectRoot, routerType);\n  if (!layoutPath) {\n    return {\n      success: false,\n      filePath: '',\n      message: 'Could not find app/layout.tsx',\n    };\n  }\n\n  const originalContent = readFileSync(layoutPath, 'utf-8');\n\n  if (hasReactScanCode(originalContent)) {\n    return {\n      success: true,\n      filePath: layoutPath,\n      message: 'React Scan is already installed.',\n      noChanges: true,\n    };\n  }\n\n  let newContent = originalContent;\n\n  const headOpenMatch = newContent.match(/<head[^>]*>/);\n  if (headOpenMatch) {\n    const injection = `\\n        ${NEXT_APP_ROUTER_SCRIPT}\\n`;\n    newContent = newContent.replace(\n      headOpenMatch[0],\n      `${headOpenMatch[0]}${injection}`,\n    );\n  } else {\n    const bodyMatch = newContent.match(/<body[\\s\\S]*?>/);\n    if (bodyMatch) {\n      const injection = `\\n        ${NEXT_APP_ROUTER_SCRIPT}`;\n      newContent = newContent.replace(\n        bodyMatch[0],\n        `${bodyMatch[0]}${injection}`,\n      );\n    }\n  }\n\n  return {\n    success: true,\n    filePath: layoutPath,\n    message: 'Success',\n    originalContent,\n    newContent,\n  };\n};\n\nconst transformNextPagesRouter = (\n  projectRoot: string,\n  routerType: NextRouterType,\n): TransformResult => {\n  const documentPath = findLayoutFile(projectRoot, routerType);\n  if (!documentPath) {\n    return {\n      success: false,\n      filePath: '',\n      message: 'Could not find pages/_document.tsx',\n    };\n  }\n\n  const originalContent = readFileSync(documentPath, 'utf-8');\n\n  if (hasReactScanCode(originalContent)) {\n    return {\n      success: true,\n      filePath: documentPath,\n      message: 'React Scan is already installed.',\n      noChanges: true,\n    };\n  }\n\n  let newContent = originalContent;\n  const injection = `\\n        ${NEXT_PAGES_ROUTER_SCRIPT}`;\n\n  const headMatch = newContent.match(/<Head>([\\s\\S]*?)<\\/Head>/);\n  if (headMatch) {\n    newContent = newContent.replace('<Head>', `<Head>${injection}`);\n  } else {\n    const selfClosingHeadMatch = newContent.match(/<Head\\s*\\/>/);\n    if (selfClosingHeadMatch) {\n      newContent = newContent.replace(\n        selfClosingHeadMatch[0],\n        `<Head>${injection}\\n      </Head>`,\n      );\n    }\n  }\n\n  if (newContent === originalContent) {\n    return {\n      success: false,\n      filePath: documentPath,\n      message:\n        'Could not find <Head> component in _document file to inject React Scan script.',\n    };\n  }\n\n  return {\n    success: true,\n    filePath: documentPath,\n    message: 'Success',\n    originalContent,\n    newContent,\n  };\n};\n\nconst transformVite = (projectRoot: string): TransformResult => {\n  const indexHtml = findIndexHtml(projectRoot);\n  if (!indexHtml) {\n    return {\n      success: false,\n      filePath: '',\n      message: 'Could not find index.html',\n    };\n  }\n\n  const originalContent = readFileSync(indexHtml, 'utf-8');\n\n  if (hasReactScanCode(originalContent)) {\n    return {\n      success: true,\n      filePath: indexHtml,\n      message: 'React Scan is already installed.',\n      noChanges: true,\n    };\n  }\n\n  const headOpenMatch = originalContent.match(/<head[^>]*>/);\n  if (!headOpenMatch) {\n    return {\n      success: false,\n      filePath: indexHtml,\n      message: 'Could not find <head> tag in index.html',\n    };\n  }\n\n  const newContent = originalContent.replace(\n    headOpenMatch[0],\n    `${headOpenMatch[0]}\\n    ${VITE_SCRIPT}`,\n  );\n\n  return {\n    success: true,\n    filePath: indexHtml,\n    message: 'Success',\n    originalContent,\n    newContent,\n  };\n};\n\nconst transformWebpack = (projectRoot: string): TransformResult => {\n  const indexHtml = findIndexHtml(projectRoot);\n  if (indexHtml) {\n    const originalContent = readFileSync(indexHtml, 'utf-8');\n    if (hasReactScanCode(originalContent)) {\n      return {\n        success: true,\n        filePath: indexHtml,\n        message: 'React Scan is already installed.',\n        noChanges: true,\n      };\n    }\n\n    const headOpenMatch = originalContent.match(/<head[^>]*>/);\n    if (!headOpenMatch) {\n      return {\n        success: false,\n        filePath: indexHtml,\n        message: 'Could not find <head> tag in index.html',\n      };\n    }\n\n    const newContent = originalContent.replace(\n      headOpenMatch[0],\n      `${headOpenMatch[0]}\\n    ${REACT_SCAN_SCRIPT_TAG}`,\n    );\n\n    return {\n      success: true,\n      filePath: indexHtml,\n      message: 'Success',\n      originalContent,\n      newContent,\n    };\n  }\n\n  const entryFile = findEntryFile(projectRoot);\n  if (!entryFile) {\n    return {\n      success: false,\n      filePath: '',\n      message: 'Could not find entry file or index.html',\n    };\n  }\n\n  const originalContent = readFileSync(entryFile, 'utf-8');\n  if (hasReactScanCode(originalContent)) {\n    return {\n      success: true,\n      filePath: entryFile,\n      message: 'React Scan is already installed.',\n      noChanges: true,\n    };\n  }\n\n  const newContent = `${WEBPACK_IMPORT}\\n\\n${originalContent}`;\n\n  return {\n    success: true,\n    filePath: entryFile,\n    message: 'Success',\n    originalContent,\n    newContent,\n  };\n};\n\nconst previewTransform = (\n  projectRoot: string,\n  framework: Framework,\n  nextRouterType: NextRouterType,\n): TransformResult => {\n  switch (framework) {\n    case 'next':\n      return nextRouterType === 'pages'\n        ? transformNextPagesRouter(projectRoot, nextRouterType)\n        : transformNextAppRouter(projectRoot, nextRouterType);\n    case 'vite':\n      return transformVite(projectRoot);\n    case 'webpack':\n      return transformWebpack(projectRoot);\n    case 'tanstack':\n    case 'unknown':\n    default:\n      return {\n        success: false,\n        filePath: '',\n        message: `Framework \"${framework}\" is not yet supported by automatic setup. Visit https://github.com/aidenybai/react-scan#install for manual setup.`,\n      };\n  }\n};\n\n// --- Diff ---\n\nconst generateDiff = (original: string, updated: string): DiffLine[] => {\n  const originalLines = original.split('\\n');\n  const newLines = updated.split('\\n');\n  const diff: DiffLine[] = [];\n\n  let originalIdx = 0;\n  let newIdx = 0;\n\n  while (originalIdx < originalLines.length || newIdx < newLines.length) {\n    const originalLine = originalLines[originalIdx];\n    const newLine = newLines[newIdx];\n\n    if (originalLine === newLine) {\n      diff.push({ type: 'unchanged', content: originalLine });\n      originalIdx++;\n      newIdx++;\n    } else if (originalLine === undefined) {\n      diff.push({ type: 'added', content: newLine });\n      newIdx++;\n    } else if (newLine === undefined) {\n      diff.push({ type: 'removed', content: originalLine });\n      originalIdx++;\n    } else {\n      const originalInNew = newLines.indexOf(originalLine, newIdx);\n      const newInOriginal = originalLines.indexOf(newLine, originalIdx);\n\n      if (originalInNew !== -1 && (newInOriginal === -1 || originalInNew - newIdx < newInOriginal - originalIdx)) {\n        while (newIdx < originalInNew) {\n          diff.push({ type: 'added', content: newLines[newIdx] });\n          newIdx++;\n        }\n      } else if (newInOriginal !== -1) {\n        while (originalIdx < newInOriginal) {\n          diff.push({ type: 'removed', content: originalLines[originalIdx] });\n          originalIdx++;\n        }\n      } else {\n        diff.push({ type: 'removed', content: originalLine });\n        diff.push({ type: 'added', content: newLine });\n        originalIdx++;\n        newIdx++;\n      }\n    }\n  }\n\n  return diff;\n};\n\nexport {\n  type DiffLine,\n  type Framework,\n  type NextRouterType,\n  type PackageManager,\n  type ProjectInfo,\n  type TransformResult,\n  FRAMEWORK_NAMES,\n  INSTALL_COMMANDS,\n  NEXT_APP_ROUTER_SCRIPT,\n  NEXT_PAGES_ROUTER_SCRIPT,\n  REACT_SCAN_SCRIPT_TAG,\n  VITE_SCRIPT,\n  WEBPACK_IMPORT,\n  detectFramework,\n  detectNextRouterType,\n  detectPackageManager,\n  detectProject,\n  findEntryFile,\n  findIndexHtml,\n  findLayoutFile,\n  generateDiff,\n  hasReactScanCode,\n  previewTransform,\n  transformNextAppRouter,\n  transformNextPagesRouter,\n  transformVite,\n  transformWebpack,\n};\n"
  },
  {
    "path": "packages/scan/src/cli-utils.test.mts",
    "content": "import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { afterEach, beforeEach, describe, expect, it } from 'vitest';\nimport {\n  NEXT_APP_ROUTER_SCRIPT,\n  NEXT_PAGES_ROUTER_SCRIPT,\n  REACT_SCAN_SCRIPT_TAG,\n  VITE_SCRIPT,\n  WEBPACK_IMPORT,\n  detectFramework,\n  detectNextRouterType,\n  detectPackageManager,\n  detectProject,\n  findEntryFile,\n  findIndexHtml,\n  findLayoutFile,\n  generateDiff,\n  hasReactScanCode,\n  previewTransform,\n  transformNextAppRouter,\n  transformNextPagesRouter,\n  transformVite,\n  transformWebpack,\n} from './cli-utils.mjs';\n\nlet tempDirectory: string;\n\nbeforeEach(() => {\n  tempDirectory = mkdtempSync(join(tmpdir(), 'react-scan-cli-test-'));\n});\n\nafterEach(() => {\n  rmSync(tempDirectory, { recursive: true, force: true });\n});\n\nconst writePackageJson = (\n  directory: string,\n  dependencies: Record<string, string> = {},\n  devDependencies: Record<string, string> = {},\n) => {\n  writeFileSync(\n    join(directory, 'package.json'),\n    JSON.stringify({ dependencies, devDependencies }),\n  );\n};\n\n// --- detectPackageManager ---\n\ndescribe('detectPackageManager', () => {\n  it('returns bun when bun.lockb exists', () => {\n    writeFileSync(join(tempDirectory, 'bun.lockb'), '');\n    expect(detectPackageManager(tempDirectory)).toBe('bun');\n  });\n\n  it('returns bun when bun.lock exists', () => {\n    writeFileSync(join(tempDirectory, 'bun.lock'), '');\n    expect(detectPackageManager(tempDirectory)).toBe('bun');\n  });\n\n  it('returns pnpm when pnpm-lock.yaml exists', () => {\n    writeFileSync(join(tempDirectory, 'pnpm-lock.yaml'), '');\n    expect(detectPackageManager(tempDirectory)).toBe('pnpm');\n  });\n\n  it('returns yarn when yarn.lock exists', () => {\n    writeFileSync(join(tempDirectory, 'yarn.lock'), '');\n    expect(detectPackageManager(tempDirectory)).toBe('yarn');\n  });\n\n  it('defaults to npm when no lock file exists', () => {\n    expect(detectPackageManager(tempDirectory)).toBe('npm');\n  });\n\n  it('prefers bun over pnpm when both lock files exist', () => {\n    writeFileSync(join(tempDirectory, 'bun.lockb'), '');\n    writeFileSync(join(tempDirectory, 'pnpm-lock.yaml'), '');\n    expect(detectPackageManager(tempDirectory)).toBe('bun');\n  });\n\n  it('prefers pnpm over yarn when both lock files exist', () => {\n    writeFileSync(join(tempDirectory, 'pnpm-lock.yaml'), '');\n    writeFileSync(join(tempDirectory, 'yarn.lock'), '');\n    expect(detectPackageManager(tempDirectory)).toBe('pnpm');\n  });\n});\n\n// --- detectFramework ---\n\ndescribe('detectFramework', () => {\n  it('detects Next.js from dependencies', () => {\n    writePackageJson(tempDirectory, { next: '^14.0.0' });\n    expect(detectFramework(tempDirectory)).toBe('next');\n  });\n\n  it('detects Next.js from devDependencies', () => {\n    writePackageJson(tempDirectory, {}, { next: '^14.0.0' });\n    expect(detectFramework(tempDirectory)).toBe('next');\n  });\n\n  it('detects Vite from dependencies', () => {\n    writePackageJson(tempDirectory, {}, { vite: '^5.0.0' });\n    expect(detectFramework(tempDirectory)).toBe('vite');\n  });\n\n  it('detects TanStack Start from dependencies', () => {\n    writePackageJson(tempDirectory, { '@tanstack/react-start': '^1.0.0' });\n    expect(detectFramework(tempDirectory)).toBe('tanstack');\n  });\n\n  it('detects Webpack from dependencies', () => {\n    writePackageJson(tempDirectory, {}, { webpack: '^5.0.0' });\n    expect(detectFramework(tempDirectory)).toBe('webpack');\n  });\n\n  it('detects Webpack via react-scripts', () => {\n    writePackageJson(tempDirectory, { 'react-scripts': '^5.0.0' });\n    expect(detectFramework(tempDirectory)).toBe('webpack');\n  });\n\n  it('returns unknown when no framework is detected', () => {\n    writePackageJson(tempDirectory, { react: '^18.0.0' });\n    expect(detectFramework(tempDirectory)).toBe('unknown');\n  });\n\n  it('returns unknown when no package.json exists', () => {\n    expect(detectFramework(tempDirectory)).toBe('unknown');\n  });\n\n  it('returns unknown when package.json is malformed', () => {\n    writeFileSync(join(tempDirectory, 'package.json'), 'not-json');\n    expect(detectFramework(tempDirectory)).toBe('unknown');\n  });\n\n  it('prefers Next.js over Vite when both are present', () => {\n    writePackageJson(tempDirectory, { next: '^14.0.0' }, { vite: '^5.0.0' });\n    expect(detectFramework(tempDirectory)).toBe('next');\n  });\n});\n\n// --- detectNextRouterType ---\n\ndescribe('detectNextRouterType', () => {\n  it('detects app router from root app directory', () => {\n    mkdirSync(join(tempDirectory, 'app'));\n    expect(detectNextRouterType(tempDirectory)).toBe('app');\n  });\n\n  it('detects app router from src/app directory', () => {\n    mkdirSync(join(tempDirectory, 'src', 'app'), { recursive: true });\n    expect(detectNextRouterType(tempDirectory)).toBe('app');\n  });\n\n  it('detects pages router from root pages directory', () => {\n    mkdirSync(join(tempDirectory, 'pages'));\n    expect(detectNextRouterType(tempDirectory)).toBe('pages');\n  });\n\n  it('detects pages router from src/pages directory', () => {\n    mkdirSync(join(tempDirectory, 'src', 'pages'), { recursive: true });\n    expect(detectNextRouterType(tempDirectory)).toBe('pages');\n  });\n\n  it('returns unknown when no router directories exist', () => {\n    expect(detectNextRouterType(tempDirectory)).toBe('unknown');\n  });\n\n  it('prefers app router when both app and pages directories exist', () => {\n    mkdirSync(join(tempDirectory, 'app'));\n    mkdirSync(join(tempDirectory, 'pages'));\n    expect(detectNextRouterType(tempDirectory)).toBe('app');\n  });\n});\n\n// --- detectProject ---\n\ndescribe('detectProject', () => {\n  it('detects a Next.js app router project with pnpm', () => {\n    writePackageJson(tempDirectory, { next: '^14.0.0', react: '^18.0.0' });\n    writeFileSync(join(tempDirectory, 'pnpm-lock.yaml'), '');\n    mkdirSync(join(tempDirectory, 'app'));\n\n    const project = detectProject(tempDirectory);\n    expect(project.packageManager).toBe('pnpm');\n    expect(project.framework).toBe('next');\n    expect(project.nextRouterType).toBe('app');\n    expect(project.projectRoot).toBe(tempDirectory);\n    expect(project.hasReactScan).toBe(false);\n  });\n\n  it('detects hasReactScan from dependencies', () => {\n    writePackageJson(tempDirectory, { 'react-scan': '^0.4.0', vite: '^5.0.0' });\n    const project = detectProject(tempDirectory);\n    expect(project.hasReactScan).toBe(true);\n  });\n\n  it('detects hasReactScan from devDependencies', () => {\n    writePackageJson(tempDirectory, { vite: '^5.0.0' }, { 'react-scan': '^0.4.0' });\n    const project = detectProject(tempDirectory);\n    expect(project.hasReactScan).toBe(true);\n  });\n\n  it('sets nextRouterType to unknown for non-Next.js frameworks', () => {\n    writePackageJson(tempDirectory, {}, { vite: '^5.0.0' });\n    mkdirSync(join(tempDirectory, 'app'));\n    const project = detectProject(tempDirectory);\n    expect(project.nextRouterType).toBe('unknown');\n  });\n});\n\n// --- hasReactScanCode ---\n\ndescribe('hasReactScanCode', () => {\n  it('detects react-scan in content', () => {\n    expect(hasReactScanCode('import(\"react-scan\")')).toBe(true);\n  });\n\n  it('detects react_scan in content', () => {\n    expect(hasReactScanCode('window.react_scan = true')).toBe(true);\n  });\n\n  it('returns false when not present', () => {\n    expect(hasReactScanCode('import React from \"react\"')).toBe(false);\n  });\n\n  it('detects react-scan in script tag', () => {\n    expect(hasReactScanCode('<script src=\"https://unpkg.com/react-scan/dist/auto.global.js\"></script>')).toBe(true);\n  });\n});\n\n// --- findLayoutFile ---\n\ndescribe('findLayoutFile', () => {\n  it('finds app/layout.tsx for app router', () => {\n    mkdirSync(join(tempDirectory, 'app'));\n    const layoutPath = join(tempDirectory, 'app', 'layout.tsx');\n    writeFileSync(layoutPath, '');\n    expect(findLayoutFile(tempDirectory, 'app')).toBe(layoutPath);\n  });\n\n  it('finds src/app/layout.tsx for app router', () => {\n    mkdirSync(join(tempDirectory, 'src', 'app'), { recursive: true });\n    const layoutPath = join(tempDirectory, 'src', 'app', 'layout.tsx');\n    writeFileSync(layoutPath, '');\n    expect(findLayoutFile(tempDirectory, 'app')).toBe(layoutPath);\n  });\n\n  it('finds app/layout.jsx for app router', () => {\n    mkdirSync(join(tempDirectory, 'app'));\n    const layoutPath = join(tempDirectory, 'app', 'layout.jsx');\n    writeFileSync(layoutPath, '');\n    expect(findLayoutFile(tempDirectory, 'app')).toBe(layoutPath);\n  });\n\n  it('finds pages/_document.tsx for pages router', () => {\n    mkdirSync(join(tempDirectory, 'pages'));\n    const documentPath = join(tempDirectory, 'pages', '_document.tsx');\n    writeFileSync(documentPath, '');\n    expect(findLayoutFile(tempDirectory, 'pages')).toBe(documentPath);\n  });\n\n  it('finds src/pages/_document.tsx for pages router', () => {\n    mkdirSync(join(tempDirectory, 'src', 'pages'), { recursive: true });\n    const documentPath = join(tempDirectory, 'src', 'pages', '_document.tsx');\n    writeFileSync(documentPath, '');\n    expect(findLayoutFile(tempDirectory, 'pages')).toBe(documentPath);\n  });\n\n  it('returns null when no layout file exists for app router', () => {\n    expect(findLayoutFile(tempDirectory, 'app')).toBeNull();\n  });\n\n  it('returns null when no document file exists for pages router', () => {\n    expect(findLayoutFile(tempDirectory, 'pages')).toBeNull();\n  });\n\n  it('returns null for unknown router type', () => {\n    expect(findLayoutFile(tempDirectory, 'unknown')).toBeNull();\n  });\n});\n\n// --- findIndexHtml ---\n\ndescribe('findIndexHtml', () => {\n  it('finds root index.html', () => {\n    const indexPath = join(tempDirectory, 'index.html');\n    writeFileSync(indexPath, '');\n    expect(findIndexHtml(tempDirectory)).toBe(indexPath);\n  });\n\n  it('finds public/index.html', () => {\n    mkdirSync(join(tempDirectory, 'public'));\n    const indexPath = join(tempDirectory, 'public', 'index.html');\n    writeFileSync(indexPath, '');\n    expect(findIndexHtml(tempDirectory)).toBe(indexPath);\n  });\n\n  it('finds src/index.html', () => {\n    mkdirSync(join(tempDirectory, 'src'));\n    const indexPath = join(tempDirectory, 'src', 'index.html');\n    writeFileSync(indexPath, '');\n    expect(findIndexHtml(tempDirectory)).toBe(indexPath);\n  });\n\n  it('prefers root index.html over public/index.html', () => {\n    const rootPath = join(tempDirectory, 'index.html');\n    writeFileSync(rootPath, '');\n    mkdirSync(join(tempDirectory, 'public'));\n    writeFileSync(join(tempDirectory, 'public', 'index.html'), '');\n    expect(findIndexHtml(tempDirectory)).toBe(rootPath);\n  });\n\n  it('returns null when no index.html exists', () => {\n    expect(findIndexHtml(tempDirectory)).toBeNull();\n  });\n});\n\n// --- findEntryFile ---\n\ndescribe('findEntryFile', () => {\n  it('finds src/index.tsx', () => {\n    mkdirSync(join(tempDirectory, 'src'));\n    const entryPath = join(tempDirectory, 'src', 'index.tsx');\n    writeFileSync(entryPath, '');\n    expect(findEntryFile(tempDirectory)).toBe(entryPath);\n  });\n\n  it('finds src/main.tsx', () => {\n    mkdirSync(join(tempDirectory, 'src'));\n    const entryPath = join(tempDirectory, 'src', 'main.tsx');\n    writeFileSync(entryPath, '');\n    expect(findEntryFile(tempDirectory)).toBe(entryPath);\n  });\n\n  it('finds src/index.js', () => {\n    mkdirSync(join(tempDirectory, 'src'));\n    const entryPath = join(tempDirectory, 'src', 'index.js');\n    writeFileSync(entryPath, '');\n    expect(findEntryFile(tempDirectory)).toBe(entryPath);\n  });\n\n  it('prefers src/index.tsx over src/main.tsx', () => {\n    mkdirSync(join(tempDirectory, 'src'));\n    const indexPath = join(tempDirectory, 'src', 'index.tsx');\n    writeFileSync(indexPath, '');\n    writeFileSync(join(tempDirectory, 'src', 'main.tsx'), '');\n    expect(findEntryFile(tempDirectory)).toBe(indexPath);\n  });\n\n  it('returns null when no entry file exists', () => {\n    expect(findEntryFile(tempDirectory)).toBeNull();\n  });\n});\n\n// --- transformNextAppRouter ---\n\ndescribe('transformNextAppRouter', () => {\n  const LAYOUT_WITH_BODY = `export default function RootLayout({ children }) {\n  return (\n    <html lang=\"en\">\n      <body>{children}</body>\n    </html>\n  );\n}`;\n\n  const LAYOUT_WITH_HEAD = `export default function RootLayout({ children }) {\n  return (\n    <html lang=\"en\">\n      <head><title>App</title></head>\n      <body>{children}</body>\n    </html>\n  );\n}`;\n\n  it('returns failure when no layout file exists', () => {\n    const result = transformNextAppRouter(tempDirectory, 'app');\n    expect(result.success).toBe(false);\n    expect(result.message).toContain('Could not find');\n  });\n\n  it('injects script after body tag when no head tag exists', () => {\n    mkdirSync(join(tempDirectory, 'app'));\n    writeFileSync(join(tempDirectory, 'app', 'layout.tsx'), LAYOUT_WITH_BODY);\n\n    const result = transformNextAppRouter(tempDirectory, 'app');\n    expect(result.success).toBe(true);\n    expect(result.newContent).toContain('react-scan');\n    expect(result.newContent).toContain('<body>');\n  });\n\n  it('reports already installed when react-scan is in content', () => {\n    mkdirSync(join(tempDirectory, 'app'));\n    writeFileSync(\n      join(tempDirectory, 'app', 'layout.tsx'),\n      'import \"react-scan\";\\n' + LAYOUT_WITH_BODY,\n    );\n\n    const result = transformNextAppRouter(tempDirectory, 'app');\n    expect(result.success).toBe(true);\n    expect(result.noChanges).toBe(true);\n    expect(result.message).toContain('already installed');\n  });\n\n  it('preserves original content', () => {\n    mkdirSync(join(tempDirectory, 'app'));\n    writeFileSync(join(tempDirectory, 'app', 'layout.tsx'), LAYOUT_WITH_BODY);\n\n    const result = transformNextAppRouter(tempDirectory, 'app');\n    expect(result.originalContent).toBe(LAYOUT_WITH_BODY);\n  });\n});\n\n// --- transformNextPagesRouter ---\n\ndescribe('transformNextPagesRouter', () => {\n  const DOCUMENT_WITH_HEAD = `import { Html, Head, Main, NextScript } from 'next/document';\n\nexport default function Document() {\n  return (\n    <Html>\n      <Head></Head>\n      <body>\n        <Main />\n        <NextScript />\n      </body>\n    </Html>\n  );\n}`;\n\n  it('returns failure when no _document file exists', () => {\n    const result = transformNextPagesRouter(tempDirectory, 'pages');\n    expect(result.success).toBe(false);\n    expect(result.message).toContain('Could not find');\n  });\n\n  it('injects script inside Head tag', () => {\n    mkdirSync(join(tempDirectory, 'pages'));\n    writeFileSync(join(tempDirectory, 'pages', '_document.tsx'), DOCUMENT_WITH_HEAD);\n\n    const result = transformNextPagesRouter(tempDirectory, 'pages');\n    expect(result.success).toBe(true);\n    expect(result.newContent).toContain('react-scan');\n    expect(result.newContent).toContain('<Head>');\n  });\n\n  it('reports already installed when react-scan is in content', () => {\n    mkdirSync(join(tempDirectory, 'pages'));\n    writeFileSync(\n      join(tempDirectory, 'pages', '_document.tsx'),\n      DOCUMENT_WITH_HEAD.replace('<Head>', '<Head><script src=\"react-scan\" />'),\n    );\n\n    const result = transformNextPagesRouter(tempDirectory, 'pages');\n    expect(result.success).toBe(true);\n    expect(result.noChanges).toBe(true);\n  });\n});\n\n// --- transformVite ---\n\ndescribe('transformVite', () => {\n  const VITE_INDEX_HTML = `<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <title>Vite App</title>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>`;\n\n  it('returns failure when no index.html exists', () => {\n    const result = transformVite(tempDirectory);\n    expect(result.success).toBe(false);\n    expect(result.message).toContain('Could not find index.html');\n  });\n\n  it('injects script inside head tag', () => {\n    writeFileSync(join(tempDirectory, 'index.html'), VITE_INDEX_HTML);\n\n    const result = transformVite(tempDirectory);\n    expect(result.success).toBe(true);\n    expect(result.newContent).toContain(VITE_SCRIPT);\n    expect(result.newContent).toContain('<head>');\n  });\n\n  it('reports already installed when react-scan is in content', () => {\n    writeFileSync(\n      join(tempDirectory, 'index.html'),\n      VITE_INDEX_HTML.replace('<head>', `<head>\\n    ${VITE_SCRIPT}`),\n    );\n\n    const result = transformVite(tempDirectory);\n    expect(result.success).toBe(true);\n    expect(result.noChanges).toBe(true);\n  });\n\n  it('preserves rest of the html', () => {\n    writeFileSync(join(tempDirectory, 'index.html'), VITE_INDEX_HTML);\n\n    const result = transformVite(tempDirectory);\n    expect(result.newContent).toContain('<div id=\"root\"></div>');\n    expect(result.newContent).toContain('src=\"/src/main.tsx\"');\n  });\n});\n\n// --- transformWebpack ---\n\ndescribe('transformWebpack', () => {\n  const WEBPACK_INDEX_HTML = `<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>React App</title>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n  </body>\n</html>`;\n\n  const WEBPACK_ENTRY = `import React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport App from './App';\n\nReactDOM.createRoot(document.getElementById('root')).render(<App />);`;\n\n  it('injects script tag into index.html when it exists', () => {\n    mkdirSync(join(tempDirectory, 'public'));\n    writeFileSync(join(tempDirectory, 'public', 'index.html'), WEBPACK_INDEX_HTML);\n\n    const result = transformWebpack(tempDirectory);\n    expect(result.success).toBe(true);\n    expect(result.newContent).toContain(REACT_SCAN_SCRIPT_TAG);\n  });\n\n  it('falls back to entry file import when no index.html exists', () => {\n    mkdirSync(join(tempDirectory, 'src'));\n    writeFileSync(join(tempDirectory, 'src', 'index.tsx'), WEBPACK_ENTRY);\n\n    const result = transformWebpack(tempDirectory);\n    expect(result.success).toBe(true);\n    expect(result.newContent).toContain(WEBPACK_IMPORT);\n    expect(result.newContent).toContain(WEBPACK_ENTRY);\n  });\n\n  it('returns failure when no index.html or entry file exists', () => {\n    const result = transformWebpack(tempDirectory);\n    expect(result.success).toBe(false);\n    expect(result.message).toContain('Could not find');\n  });\n\n  it('reports already installed via index.html', () => {\n    mkdirSync(join(tempDirectory, 'public'));\n    writeFileSync(\n      join(tempDirectory, 'public', 'index.html'),\n      WEBPACK_INDEX_HTML.replace('<head>', `<head>\\n    ${REACT_SCAN_SCRIPT_TAG}`),\n    );\n\n    const result = transformWebpack(tempDirectory);\n    expect(result.success).toBe(true);\n    expect(result.noChanges).toBe(true);\n  });\n\n  it('reports already installed via entry file', () => {\n    mkdirSync(join(tempDirectory, 'src'));\n    writeFileSync(\n      join(tempDirectory, 'src', 'index.tsx'),\n      `import(\"react-scan\");\\n${WEBPACK_ENTRY}`,\n    );\n\n    const result = transformWebpack(tempDirectory);\n    expect(result.success).toBe(true);\n    expect(result.noChanges).toBe(true);\n  });\n});\n\n// --- previewTransform ---\n\ndescribe('previewTransform', () => {\n  it('routes to Next.js app router transform', () => {\n    mkdirSync(join(tempDirectory, 'app'));\n    writeFileSync(\n      join(tempDirectory, 'app', 'layout.tsx'),\n      '<html><body></body></html>',\n    );\n\n    const result = previewTransform(tempDirectory, 'next', 'app');\n    expect(result.success).toBe(true);\n    expect(result.newContent).toContain('react-scan');\n  });\n\n  it('routes to Next.js pages router transform', () => {\n    mkdirSync(join(tempDirectory, 'pages'));\n    writeFileSync(\n      join(tempDirectory, 'pages', '_document.tsx'),\n      '<Html><Head></Head><body></body></Html>',\n    );\n\n    const result = previewTransform(tempDirectory, 'next', 'pages');\n    expect(result.success).toBe(true);\n    expect(result.newContent).toContain('react-scan');\n  });\n\n  it('routes to Vite transform', () => {\n    writeFileSync(\n      join(tempDirectory, 'index.html'),\n      '<html><head></head><body></body></html>',\n    );\n\n    const result = previewTransform(tempDirectory, 'vite', 'unknown');\n    expect(result.success).toBe(true);\n    expect(result.newContent).toContain('react-scan');\n  });\n\n  it('routes to Webpack transform', () => {\n    mkdirSync(join(tempDirectory, 'public'));\n    writeFileSync(\n      join(tempDirectory, 'public', 'index.html'),\n      '<html><head></head><body></body></html>',\n    );\n\n    const result = previewTransform(tempDirectory, 'webpack', 'unknown');\n    expect(result.success).toBe(true);\n    expect(result.newContent).toContain('react-scan');\n  });\n\n  it('returns failure for tanstack framework', () => {\n    const result = previewTransform(tempDirectory, 'tanstack', 'unknown');\n    expect(result.success).toBe(false);\n    expect(result.message).toContain('not yet supported');\n  });\n\n  it('returns failure for unknown framework', () => {\n    const result = previewTransform(tempDirectory, 'unknown', 'unknown');\n    expect(result.success).toBe(false);\n    expect(result.message).toContain('not yet supported');\n  });\n});\n\n// --- generateDiff ---\n\ndescribe('generateDiff', () => {\n  it('returns empty diff for identical strings', () => {\n    const diff = generateDiff('hello\\nworld', 'hello\\nworld');\n    expect(diff).toEqual([\n      { type: 'unchanged', content: 'hello' },\n      { type: 'unchanged', content: 'world' },\n    ]);\n  });\n\n  it('detects added lines', () => {\n    const diff = generateDiff('line1\\nline3', 'line1\\nline2\\nline3');\n    const addedLines = diff.filter((diffLine) => diffLine.type === 'added');\n    expect(addedLines.length).toBeGreaterThan(0);\n    expect(addedLines.some((diffLine) => diffLine.content === 'line2')).toBe(true);\n  });\n\n  it('detects removed lines', () => {\n    const diff = generateDiff('line1\\nline2\\nline3', 'line1\\nline3');\n    const removedLines = diff.filter((diffLine) => diffLine.type === 'removed');\n    expect(removedLines.length).toBeGreaterThan(0);\n    expect(removedLines.some((diffLine) => diffLine.content === 'line2')).toBe(true);\n  });\n\n  it('detects replaced lines', () => {\n    const diff = generateDiff('hello', 'goodbye');\n    expect(diff).toEqual([\n      { type: 'removed', content: 'hello' },\n      { type: 'added', content: 'goodbye' },\n    ]);\n  });\n\n  it('handles empty original', () => {\n    const diff = generateDiff('', 'new line');\n    expect(diff).toEqual([\n      { type: 'removed', content: '' },\n      { type: 'added', content: 'new line' },\n    ]);\n  });\n\n  it('handles empty updated', () => {\n    const diff = generateDiff('old line', '');\n    expect(diff).toEqual([\n      { type: 'removed', content: 'old line' },\n      { type: 'added', content: '' },\n    ]);\n  });\n\n  it('handles multi-line additions in the middle', () => {\n    const original = '<head>\\n</head>';\n    const updated = '<head>\\n  <script src=\"react-scan\"></script>\\n</head>';\n    const diff = generateDiff(original, updated);\n\n    const addedLines = diff.filter((diffLine) => diffLine.type === 'added');\n    expect(addedLines.length).toBe(1);\n    expect(addedLines[0].content).toContain('react-scan');\n  });\n});\n"
  },
  {
    "path": "packages/scan/src/cli.mts",
    "content": "import { execSync } from 'node:child_process';\nimport { existsSync, writeFileSync } from 'node:fs';\nimport { join, relative, resolve } from 'node:path';\nimport { Command } from 'commander';\nimport pc from 'picocolors';\nimport prompts from 'prompts';\nimport {\n  type DiffLine,\n  type PackageManager,\n  FRAMEWORK_NAMES,\n  INSTALL_COMMANDS,\n  detectProject,\n  generateDiff,\n  previewTransform,\n} from './cli-utils.mjs';\n\nconst VERSION = process.env.NPM_PACKAGE_VERSION ?? '0.0.0';\n\n// --- Diff ---\n\nconst printDiff = (filePath: string, original: string, updated: string): void => {\n  const diff = generateDiff(original, updated);\n  const contextLines = 3;\n  const changedIndices = diff\n    .map((line: DiffLine, i: number) => (line.type !== 'unchanged' ? i : -1))\n    .filter((i: number) => i !== -1);\n\n  if (changedIndices.length === 0) {\n    console.log(pc.dim('  No changes'));\n    return;\n  }\n\n  console.log(`\\n${pc.bold(`File: ${filePath}`)}`);\n  console.log(pc.dim('─'.repeat(60)));\n\n  let lastPrintedIdx = -1;\n\n  for (const changedIdx of changedIndices) {\n    const start = Math.max(0, changedIdx - contextLines);\n    const end = Math.min(diff.length - 1, changedIdx + contextLines);\n\n    if (start > lastPrintedIdx + 1 && lastPrintedIdx !== -1) {\n      console.log(pc.dim('  ...'));\n    }\n\n    for (let i = Math.max(start, lastPrintedIdx + 1); i <= end; i++) {\n      const line = diff[i];\n      if (line.type === 'added') {\n        console.log(pc.green(`+ ${line.content}`));\n      } else if (line.type === 'removed') {\n        console.log(pc.red(`- ${line.content}`));\n      } else {\n        console.log(pc.dim(`  ${line.content}`));\n      }\n      lastPrintedIdx = i;\n    }\n  }\n\n  console.log(pc.dim('─'.repeat(60)));\n};\n\n// --- Install ---\n\nconst installPackages = (\n  packages: string[],\n  packageManager: PackageManager,\n  projectRoot: string,\n): void => {\n  if (packages.length === 0) return;\n\n  const command = `${INSTALL_COMMANDS[packageManager]} ${packages.join(' ')}`;\n  console.log(pc.dim(`  Running: ${command}\\n`));\n\n  execSync(command, {\n    cwd: projectRoot,\n    stdio: 'inherit',\n  });\n};\n\n// --- Main ---\n\nconst program = new Command()\n  .name('react-scan')\n  .description('React Scan CLI')\n  .version(VERSION);\n\nprogram\n  .command('init')\n  .description('Set up React Scan in your project')\n  .option('-y, --yes', 'skip confirmation prompts', false)\n  .option('-c, --cwd <cwd>', 'working directory', process.cwd())\n  .option('--skip-install', 'skip package installation', false)\n  .action(async (opts) => {\n    console.log(`\\n${pc.magenta('[·]')} ${pc.bold('React Scan')} ${pc.dim(`v${VERSION}`)}\\n`);\n\n    try {\n      const cwd = resolve(opts.cwd);\n\n      if (!existsSync(cwd)) {\n        console.error(pc.red(`Directory does not exist: ${cwd}`));\n        process.exit(1);\n      }\n\n      if (!existsSync(join(cwd, 'package.json'))) {\n        console.error(pc.red('No package.json found. Run this command from a project root.'));\n        process.exit(1);\n      }\n\n      console.log(pc.dim('  Detecting project...\\n'));\n\n      const project = detectProject(cwd);\n\n      if (project.framework === 'unknown') {\n        console.error(pc.red('  Could not detect a supported framework.'));\n        console.log(pc.dim('  React Scan supports Next.js, Vite, and Webpack projects.'));\n        console.log(pc.dim('  Visit https://github.com/aidenybai/react-scan#install for manual setup.\\n'));\n        process.exit(1);\n      }\n\n      console.log(`  Framework:       ${pc.cyan(FRAMEWORK_NAMES[project.framework])}`);\n      if (project.framework === 'next') {\n        console.log(`  Router:          ${pc.cyan(project.nextRouterType === 'app' ? 'App Router' : 'Pages Router')}`);\n      }\n      console.log(`  Package manager: ${pc.cyan(project.packageManager)}`);\n      console.log();\n\n      if (project.hasReactScan) {\n        console.log(pc.green('  React Scan is already installed in package.json.'));\n        console.log(pc.dim('  Checking if code setup is needed...\\n'));\n      }\n\n      const result = previewTransform(cwd, project.framework, project.nextRouterType);\n\n      if (!result.success) {\n        console.error(pc.red(`  ${result.message}\\n`));\n        process.exit(1);\n      }\n\n      const hasCodeChanges = !result.noChanges && result.originalContent && result.newContent;\n\n      if (hasCodeChanges) {\n        printDiff(\n          relative(cwd, result.filePath),\n          result.originalContent!,\n          result.newContent!,\n        );\n\n        console.log();\n        console.log(pc.yellow('  Auto-detection may not be 100% accurate.'));\n        console.log(pc.yellow('  Please verify the changes before committing.\\n'));\n\n        if (!opts.yes) {\n          const { proceed } = await prompts({\n            type: 'confirm',\n            name: 'proceed',\n            message: 'Apply these changes?',\n            initial: true,\n          });\n\n          if (!proceed) {\n            console.log(pc.dim('\\n  Changes cancelled.\\n'));\n            process.exit(0);\n          }\n        }\n      }\n\n      if (!opts.skipInstall && !project.hasReactScan) {\n        console.log(pc.dim('\\n  Installing react-scan...\\n'));\n        installPackages(['react-scan'], project.packageManager, cwd);\n        console.log();\n      }\n\n      if (hasCodeChanges) {\n        writeFileSync(result.filePath, result.newContent!, 'utf-8');\n        console.log(pc.green(`  Updated ${relative(cwd, result.filePath)}`));\n      }\n\n      if (!hasCodeChanges && project.hasReactScan) {\n        console.log(pc.green('  React Scan is already set up in your project.\\n'));\n        process.exit(0);\n      }\n\n      console.log();\n      console.log(`${pc.green('  Success!')} React Scan has been installed.`);\n      console.log(pc.dim('  You may now start your development server.\\n'));\n    } catch (error) {\n      console.error(pc.red(`\\n  Error: ${error instanceof Error ? error.message : String(error)}\\n`));\n      process.exit(1);\n    }\n  });\n\nprogram.parse();\n"
  },
  {
    "path": "packages/scan/src/core/all-environments.ts",
    "content": "import { ReactScanInternals, scan as innerScan } from '.';\n\nexport const scan = /*#__PURE__*/ (...params: Parameters<typeof innerScan>) => {\n  if (typeof window !== 'undefined') {\n    ReactScanInternals.runInAllEnvironments = true;\n    innerScan(...params);\n  }\n};\n"
  },
  {
    "path": "packages/scan/src/core/fast-serialize.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\nimport { fastSerialize } from '~core/instrumentation';\n\ndescribe('fastSerialize', () => {\n  it('serializes null', () => {\n    expect(fastSerialize(null)).toBe('null');\n  });\n\n  it('serializes undefined', () => {\n    expect(fastSerialize(undefined)).toBe('undefined');\n  });\n\n  it('serializes strings', () => {\n    expect(fastSerialize('hello')).toBe('hello');\n    expect(fastSerialize('')).toBe('');\n  });\n\n  it('serializes numbers', () => {\n    expect(fastSerialize(42)).toBe('42');\n    expect(fastSerialize(0)).toBe('0');\n    expect(fastSerialize(Number.NaN)).toBe('NaN');\n  });\n\n  it('serializes booleans', () => {\n    expect(fastSerialize(true)).toBe('true');\n    expect(fastSerialize(false)).toBe('false');\n  });\n\n  it('serializes functions', () => {\n    const testFunc = (_x: 2) => 3;\n    expect(fastSerialize(testFunc)).toBe('(_x) => 3');\n  });\n\n  it('serializes arrays', () => {\n    expect(fastSerialize([])).toBe('[]');\n    expect(fastSerialize([1, 2, 3])).toBe('[3]');\n  });\n\n  it('serializes plain objects', () => {\n    expect(fastSerialize({})).toBe('{}');\n    expect(fastSerialize({ a: 1, b: 2 })).toBe('{2}');\n  });\n\n  it('serializes deeply nested objects with depth limit', () => {\n    const nested = { a: { b: { c: 1 } } };\n    expect(fastSerialize(nested, 0)).toBe('{1}');\n    expect(fastSerialize(nested, -1)).toBe('…');\n  });\n\n  it('serializes objects with custom constructors', () => {\n    class CustomClass {}\n    const instance = new CustomClass();\n    expect(fastSerialize(instance)).toBe('CustomClass{…}');\n  });\n\n  it('serializes unknown objects gracefully', () => {\n    const date = new Date();\n    const serialized = fastSerialize(date);\n    expect(serialized.includes('Date')).toBe(true);\n  });\n});\n"
  },
  {
    "path": "packages/scan/src/core/index.ts",
    "content": "import { type Signal, signal } from '@preact/signals';\nimport {\n  type Fiber,\n  detectReactBuildType,\n  getRDTHook,\n  getType,\n  isInstrumentationActive,\n} from 'bippy';\nimport type { ComponentType } from 'preact';\nimport type { ReactNode } from 'preact/compat';\nimport type { RenderData } from 'src/core/utils';\nimport { initReactScanInstrumentation } from 'src/new-outlines';\nimport styles from '~web/assets/css/styles.css';\nimport { createToolbar } from '~web/toolbar';\nimport { IS_CLIENT } from '~web/utils/constants';\nimport { readLocalStorage, saveLocalStorage } from '~web/utils/helpers';\nimport type { States } from '~web/views/inspector/utils';\nimport type {\n  ChangeReason,\n  Render,\n  createInstrumentation,\n} from './instrumentation';\nimport { startTimingTracking } from './notifications/event-tracking';\nimport { createHighlightCanvas } from './notifications/outline-overlay';\nimport packageJson from '../../package.json';\n\nlet rootContainer: HTMLDivElement | null = null;\nlet shadowRoot: ShadowRoot | null = null;\n\ninterface RootContainer {\n  rootContainer: HTMLDivElement;\n  shadowRoot: ShadowRoot;\n}\n\nconst initRootContainer = (): RootContainer => {\n  if (rootContainer && shadowRoot) {\n    return { rootContainer, shadowRoot };\n  }\n\n  rootContainer = document.createElement('div');\n  rootContainer.id = 'react-scan-root';\n\n  shadowRoot = rootContainer.attachShadow({ mode: 'open' });\n\n  const cssStyles = document.createElement('style');\n  cssStyles.textContent = styles;\n\n  shadowRoot.appendChild(cssStyles);\n\n  document.documentElement.appendChild(rootContainer);\n\n  return { rootContainer, shadowRoot };\n};\n\nexport interface Options {\n  /**\n   * Enable/disable scanning\n   *\n   * Please use the recommended way:\n   * enabled: process.env.NODE_ENV === 'development',\n   *\n   * @default true\n   */\n  enabled?: boolean;\n\n  /**\n   * Force React Scan to run in production (not recommended)\n   *\n   * @default false\n   */\n  dangerouslyForceRunInProduction?: boolean;\n  /**\n   * Log renders to the console\n   *\n   * WARNING: This can add significant overhead when the app re-renders frequently\n   *\n   * @default false\n   */\n  log?: boolean;\n\n  /**\n   * Show toolbar bar\n   *\n   * If you set this to true, and set {@link enabled} to false, the toolbar will still show, but scanning will be disabled.\n   *\n   * @default true\n   */\n  showToolbar?: boolean;\n\n  /**\n   * Animation speed\n   *\n   * @default \"fast\"\n   */\n  animationSpeed?: 'slow' | 'fast' | 'off';\n\n  /**\n   * Track unnecessary renders, and mark their outlines gray when detected\n   *\n   * An unnecessary render is defined as the component re-rendering with no change to the component's\n   * corresponding dom subtree\n   *\n   *  @default false\n   *  @warning tracking unnecessary renders can add meaningful overhead to react-scan\n   */\n  trackUnnecessaryRenders?: boolean;\n\n  /**\n   * Should the FPS meter show in the toolbar\n   *\n   *  @default true\n   */\n  showFPS?: boolean;\n\n  /**\n   * Should the number of slowdown notifications be shown in the toolbar\n   *\n   *  @default true\n   */\n  showNotificationCount?: boolean;\n\n  /**\n   * Allow React Scan to run inside iframes\n   *\n   * @default false\n   */\n  allowInIframe?: boolean;\n\n  /**\n   * Should react scan log internal errors to the console.\n   *\n   * Useful if react scan is not behaving expected and you want to provide information to maintainers when submitting an issue https://github.com/aidenybai/react-scan/issues\n   *\n   *  @default false\n   */\n  _debug?: 'verbose' | false;\n\n  onCommitStart?: () => void;\n  onRender?: (fiber: Fiber, renders: Array<Render>) => void;\n  onCommitFinish?: () => void;\n}\n\nexport interface StoreType {\n  inspectState: Signal<States>;\n  wasDetailsOpen: Signal<boolean>;\n  lastReportTime: Signal<number>;\n  isInIframe: Signal<boolean>;\n  fiberRoots: WeakSet<Fiber>;\n  reportData: Map<number, RenderData>;\n  legacyReportData: Map<string, RenderData>;\n  changesListeners: Map<number, Array<ChangesListener>>;\n  interactionListeningForRenders:\n    | ((fiber: Fiber, renders: Array<Render>) => void)\n    | null;\n}\n\nexport type OutlineKey = `${string}-${string}`;\n\nexport interface Internals {\n  instrumentation: ReturnType<typeof createInstrumentation> | null;\n  componentAllowList: WeakMap<ComponentType<unknown>, Options> | null;\n  options: Signal<Options>;\n  onRender: ((fiber: Fiber, renders: Array<Render>) => void) | null;\n  Store: StoreType;\n  version: string;\n  runInAllEnvironments: boolean;\n}\n\nexport type FunctionalComponentStateChange = {\n  type: ChangeReason.FunctionalState;\n  value: unknown;\n  prevValue?: unknown;\n  count?: number | undefined;\n  name: string;\n};\nexport type ClassComponentStateChange = {\n  type: ChangeReason.ClassState;\n  value: unknown;\n  prevValue?: unknown;\n  count?: number | undefined;\n  name: 'state';\n};\n\nexport type StateChange =\n  | FunctionalComponentStateChange\n  | ClassComponentStateChange;\nexport type PropsChange = {\n  type: ChangeReason.Props;\n  name: string;\n  value: unknown;\n  prevValue?: unknown;\n  count?: number | undefined;\n};\nexport type ContextChange = {\n  type: ChangeReason.Context;\n  name: string;\n  value: unknown;\n  prevValue?: unknown;\n  count?: number | undefined;\n  contextType: number;\n};\n\nexport type Change = StateChange | PropsChange | ContextChange;\n\nexport type ChangesPayload = {\n  propsChanges: Array<PropsChange>;\n  stateChanges: Array<\n    FunctionalComponentStateChange | ClassComponentStateChange\n  >;\n  contextChanges: Array<ContextChange>;\n};\nexport type ChangesListener = (changes: ChangesPayload) => void;\n\nexport const Store: StoreType = {\n  wasDetailsOpen: signal(true),\n  isInIframe: signal(IS_CLIENT && window.self !== window.top),\n  inspectState: signal<States>({\n    kind: 'uninitialized',\n  }),\n  fiberRoots: new Set<Fiber>(),\n  reportData: new Map<number, RenderData>(),\n  legacyReportData: new Map<string, RenderData>(),\n  lastReportTime: signal(0),\n  interactionListeningForRenders: null,\n  changesListeners: new Map(),\n};\n\nexport const ReactScanInternals: Internals = {\n  instrumentation: null,\n  componentAllowList: null,\n  options: signal({\n    enabled: true,\n    log: false,\n    showToolbar: true,\n    animationSpeed: 'fast',\n    dangerouslyForceRunInProduction: false,\n    showFPS: true,\n    showNotificationCount: true,\n    allowInIframe: false,\n  }),\n  runInAllEnvironments: false,\n  onRender: null,\n  Store,\n  version: packageJson.version,\n};\n\nif (IS_CLIENT && window.__REACT_SCAN_EXTENSION__) {\n  window.__REACT_SCAN_VERSION__ = ReactScanInternals.version;\n}\n\nexport type LocalStorageOptions = Omit<\n  Options,\n  'onCommitStart' | 'onRender' | 'onCommitFinish'\n>;\n\nconst applyLocalStorageOptions = (options: Options): LocalStorageOptions => {\n  const {\n    onCommitStart,\n    onRender,\n    onCommitFinish,\n    ...rest\n  } = options;\n  return rest;\n};\n\nconst validateOptions = (options: Partial<Options>): Partial<Options> => {\n  const errors: Array<string> = [];\n  const validOptions: Partial<Options> = {};\n\n  for (const key in options) {\n    const value = options[key as keyof Options];\n    switch (key) {\n      case 'enabled':\n      case 'log':\n      case 'showToolbar':\n      case 'showNotificationCount':\n      case 'dangerouslyForceRunInProduction':\n      case 'showFPS':\n      case 'allowInIframe':\n        if (typeof value !== 'boolean') {\n          errors.push(`- ${key} must be a boolean. Got \"${value}\"`);\n        } else {\n          validOptions[key] = value;\n        }\n        break;\n      case 'animationSpeed':\n        if (!['slow', 'fast', 'off'].includes(value as string)) {\n          errors.push(\n            `- Invalid animation speed \"${value}\". Using default \"fast\"`,\n          );\n        } else {\n          validOptions[key] = value as 'slow' | 'fast' | 'off';\n        }\n        break;\n      case 'onCommitStart':\n        if (typeof value !== 'function') {\n          errors.push(`- ${key} must be a function. Got \"${value}\"`);\n        } else {\n          validOptions.onCommitStart = value as () => void;\n        }\n        break;\n      case 'onCommitFinish':\n        if (typeof value !== 'function') {\n          errors.push(`- ${key} must be a function. Got \"${value}\"`);\n        } else {\n          validOptions.onCommitFinish = value as () => void;\n        }\n        break;\n      case 'onRender':\n        if (typeof value !== 'function') {\n          errors.push(`- ${key} must be a function. Got \"${value}\"`);\n        } else {\n          validOptions.onRender = value as (\n            fiber: Fiber,\n            renders: Array<Render>,\n          ) => void;\n        }\n        break;\n      default:\n        errors.push(`- Unknown option \"${key}\"`);\n    }\n  }\n\n  if (errors.length > 0) {\n    // oxlint-disable-next-line no-console\n    console.warn(`[React Scan] Invalid options:\\n${errors.join('\\n')}`);\n  }\n\n  return validOptions;\n};\n\nexport const getReport = (type?: ComponentType<unknown>) => {\n  if (type) {\n    for (const reportData of Array.from(Store.legacyReportData.values())) {\n      if (reportData.type === type) {\n        return reportData;\n      }\n    }\n    return null;\n  }\n  return Store.legacyReportData;\n};\n\nexport const setOptions = (userOptions: Partial<Options>) => {\n  try {\n    const validOptions = validateOptions(userOptions);\n\n    if (Object.keys(validOptions).length === 0) {\n      return;\n    }\n\n    const shouldInitToolbar =\n      'showToolbar' in validOptions && validOptions.showToolbar !== undefined;\n\n    const newOptions = {\n      ...ReactScanInternals.options.value,\n      ...validOptions,\n    };\n\n    const { instrumentation } = ReactScanInternals;\n    if (instrumentation && 'enabled' in validOptions) {\n      instrumentation.isPaused.value = validOptions.enabled === false;\n    }\n\n    ReactScanInternals.options.value = newOptions;\n\n    // temp hack since defaults override stored local storage values\n    // we actually don't care about any other local storage option other than enabled, we should not be syncing those to local storage\n    try {\n      const existing = readLocalStorage<undefined | Record<string, unknown>>(\n        'react-scan-options',\n      )?.enabled;\n\n      if (typeof existing === 'boolean') {\n        newOptions.enabled = existing;\n      }\n    } catch (e) {\n      if (ReactScanInternals.options.value._debug === 'verbose') {\n        // oxlint-disable-next-line no-console\n        console.error(\n          '[React Scan Internal Error]',\n          'Failed to create notifications outline canvas',\n          e,\n        );\n      }\n      /** */\n    }\n\n    saveLocalStorage<LocalStorageOptions>(\n      'react-scan-options',\n      applyLocalStorageOptions(newOptions),\n    );\n\n    if (shouldInitToolbar) {\n      initToolbar(!!newOptions.showToolbar);\n    }\n\n    return newOptions;\n  } catch (e) {\n    if (ReactScanInternals.options.value._debug === 'verbose') {\n      // oxlint-disable-next-line no-console\n      console.error(\n        '[React Scan Internal Error]',\n        'Failed to create notifications outline canvas',\n        e,\n      );\n    }\n    /** */\n  }\n};\n\nexport const getOptions = () => ReactScanInternals.options;\n\n// we only need to run this check once and will read the value in hot path\nlet isProduction: boolean | null = null;\nlet rdtHook: ReturnType<typeof getRDTHook>;\nexport const getIsProduction = () => {\n  if (isProduction !== null) {\n    return isProduction;\n  }\n  rdtHook ??= getRDTHook();\n  for (const renderer of rdtHook.renderers.values()) {\n    const buildType = detectReactBuildType(renderer);\n    if (buildType === 'production') {\n      isProduction = true;\n    }\n  }\n  return isProduction;\n};\n\nexport const start = () => {\n  try {\n    if (!IS_CLIENT) {\n      return;\n    }\n\n    if (\n      !ReactScanInternals.runInAllEnvironments &&\n      getIsProduction() &&\n      !ReactScanInternals.options.value.dangerouslyForceRunInProduction\n    ) {\n      return;\n    }\n\n    const localStorageOptions =\n      readLocalStorage<LocalStorageOptions>('react-scan-options');\n\n    if (localStorageOptions) {\n      const validLocalOptions = validateOptions(localStorageOptions);\n\n      if (Object.keys(validLocalOptions).length > 0) {\n        ReactScanInternals.options.value = {\n          ...ReactScanInternals.options.value,\n          ...validLocalOptions,\n        };\n      }\n    }\n\n    const options = getOptions();\n\n    initReactScanInstrumentation(() => {\n      initToolbar(!!options.value.showToolbar);\n    });\n\n    if (IS_CLIENT) {\n      setTimeout(() => {\n        if (isInstrumentationActive()) return;\n        // oxlint-disable-next-line no-console\n        console.error(\n          '[React Scan] Failed to load. Must import React Scan before React runs.',\n        );\n      }, 5000);\n    }\n  } catch (e) {\n    if (ReactScanInternals.options.value._debug === 'verbose') {\n      // oxlint-disable-next-line no-console\n      console.error(\n        '[React Scan Internal Error]',\n        'Failed to create notifications outline canvas',\n        e,\n      );\n    }\n  }\n};\n\nconst initToolbar = (showToolbar: boolean) => {\n  window.reactScanCleanupListeners?.();\n\n  const cleanupTimingTracking = startTimingTracking();\n  const cleanupOutlineCanvas = createNotificationsOutlineCanvas();\n\n  window.reactScanCleanupListeners = () => {\n    cleanupTimingTracking();\n    cleanupOutlineCanvas?.();\n  };\n\n  const windowToolbarContainer = window.__REACT_SCAN_TOOLBAR_CONTAINER__;\n\n  if (!showToolbar) {\n    windowToolbarContainer?.remove();\n    return;\n  }\n\n  windowToolbarContainer?.remove();\n  const { shadowRoot } = initRootContainer();\n  createToolbar(shadowRoot);\n};\n\nconst createNotificationsOutlineCanvas = () => {\n  try {\n    const highlightRoot = document.documentElement;\n    return createHighlightCanvas(highlightRoot);\n  } catch (e) {\n    if (ReactScanInternals.options.value._debug === 'verbose') {\n      // oxlint-disable-next-line no-console\n      console.error(\n        '[React Scan Internal Error]',\n        'Failed to create notifications outline canvas',\n        e,\n      );\n    }\n  }\n};\n\nexport const scan = (options: Options = {}) => {\n  setOptions(options);\n  const isInIframe = Store.isInIframe.value;\n\n  if (\n    isInIframe &&\n    !ReactScanInternals.options.value.allowInIframe &&\n    !ReactScanInternals.runInAllEnvironments\n  ) {\n    return;\n  }\n\n  if (options.enabled === false && options.showToolbar !== true) {\n    return;\n  }\n\n  start();\n};\n\nexport const useScan = (options: Options = {}) => {\n  setOptions(options);\n  start();\n};\n\nexport const onRender = (\n  type: unknown,\n  _onRender: (fiber: Fiber, renders: Array<Render>) => void,\n) => {\n  const prevOnRender = ReactScanInternals.onRender;\n  ReactScanInternals.onRender = (fiber, renders) => {\n    prevOnRender?.(fiber, renders);\n    if (getType(fiber.type) === type) {\n      _onRender(fiber, renders);\n    }\n  };\n};\n\nexport const ignoredProps = new WeakSet<\n  Exclude<ReactNode, undefined | null | string | number | boolean | bigint>\n>();\n\nexport const ignoreScan = (node: ReactNode) => {\n  if (node && typeof node === 'object') {\n    ignoredProps.add(node);\n  }\n};\n"
  },
  {
    "path": "packages/scan/src/core/instrumentation.ts",
    "content": "import { type Signal, signal } from '@preact/signals';\nimport {\n  ClassComponentTag,\n  type Fiber,\n  type FiberRoot,\n  ForwardRefTag,\n  FunctionComponentTag,\n  MemoComponentTag,\n  type MemoizedState,\n  SimpleMemoComponentTag,\n  didFiberCommit,\n  getDisplayName,\n  getFiberId,\n  getMutatedHostFibers,\n  getTimings,\n  getType,\n  hasMemoCache,\n  instrument,\n  traverseContexts,\n  traverseProps,\n  traverseRenderedFibers,\n} from 'bippy';\nimport { isValidElement } from 'preact';\nimport { isEqual } from '~core/utils';\nimport {\n  collectContextChanges,\n  collectPropsChanges,\n  collectStateChanges,\n} from '~web/views/inspector/timeline/utils';\nimport {\n  type Change,\n  type ContextChange,\n  ReactScanInternals,\n  type StateChange,\n} from './index';\n\nexport enum RenderPhase {\n  Mount = 0b001,\n  Update = 0b010,\n  Unmount = 0b100,\n}\n\nexport const RENDER_PHASE_STRING_TO_ENUM = {\n  mount: RenderPhase.Mount,\n  update: RenderPhase.Update,\n  unmount: RenderPhase.Unmount,\n} as const;\n\nexport interface AggregatedChange {\n  type: number;\n  unstable: boolean;\n}\n\nexport interface AggregatedRender {\n  name: string;\n  frame: number | null;\n  phase: number;\n  time: number | null;\n  aggregatedCount: number;\n  forget: boolean;\n  changes: AggregatedChange;\n  unnecessary: boolean | null;\n  didCommit: boolean;\n  fps: number;\n  computedKey: import('./index').OutlineKey | null;\n  computedCurrent: DOMRect | null;\n}\n\nlet fps = 0;\nlet lastTime = performance.now();\nlet frameCount = 0;\nlet initedFps = false;\n\nconst updateFPS = () => {\n  frameCount++;\n  const now = performance.now();\n  if (now - lastTime >= 1000) {\n    fps = frameCount;\n    frameCount = 0;\n    lastTime = now;\n  }\n  requestAnimationFrame(updateFPS);\n};\n\nexport const getFPS = () => {\n  if (!initedFps) {\n    initedFps = true;\n    updateFPS();\n    fps = 60;\n  }\n\n  return fps;\n};\n\nexport const isElementVisible = (el: Element) => {\n  const style = window.getComputedStyle(el);\n  return (\n    style.display !== 'none' &&\n    style.visibility !== 'hidden' &&\n    style.contentVisibility !== 'hidden' &&\n    style.opacity !== '0'\n  );\n};\n\nexport const isValueUnstable = (prevValue: unknown, nextValue: unknown) => {\n  const prevValueString = fastSerialize(prevValue);\n  const nextValueString = fastSerialize(nextValue);\n  return (\n    prevValueString === nextValueString &&\n    unstableTypes.includes(typeof prevValue) &&\n    unstableTypes.includes(typeof nextValue)\n  );\n};\n\nexport const isElementInViewport = (\n  el: Element,\n  rect = el.getBoundingClientRect(),\n) => {\n  const isVisible =\n    rect.bottom > 0 &&\n    rect.right > 0 &&\n    rect.top < window.innerHeight &&\n    rect.left < window.innerWidth;\n\n  return isVisible && rect.width && rect.height;\n};\n\nexport const enum ChangeReason {\n  Props = 0b001,\n  FunctionalState = 0b010,\n  ClassState = 0b011,\n  Context = 0b100,\n}\n\nexport interface AggregatedChange {\n  type: number; // union of AggregatedChangeReason\n  unstable: boolean;\n}\n\nexport interface Render {\n  phase: RenderPhase;\n  componentName: string | null;\n  time: number | null;\n  count: number;\n  forget: boolean;\n  changes: Array<Change>;\n  unnecessary: boolean | null;\n  didCommit: boolean;\n  fps: number;\n}\n\nconst unstableTypes = ['function', 'object'];\n\nconst cache = new WeakMap<object, string>();\n\nexport function fastSerialize(value: unknown, depth = 0): string {\n  if (depth < 0) return '…';\n\n  switch (typeof value) {\n    case 'function':\n      return value.toString();\n    case 'string':\n      return value;\n    case 'number':\n    case 'boolean':\n    case 'undefined':\n      return String(value);\n    case 'object':\n      break;\n    default:\n      return String(value);\n  }\n\n  if (value === null) return 'null';\n\n  if (cache.has(value)) {\n    const cached = cache.get(value);\n    if (cached !== undefined) {\n      return cached;\n    }\n  }\n\n  if (Array.isArray(value)) {\n    const str = value.length ? `[${value.length}]` : '[]';\n    cache.set(value, str);\n    return str;\n  }\n\n  if (isValidElement(value)) {\n    const type = getDisplayName(value.type) ?? '';\n    const propCount = value.props ? Object.keys(value.props).length : 0;\n    const str = `<${type} ${propCount}>`;\n    cache.set(value, str);\n    return str;\n  }\n\n  if (Object.getPrototypeOf(value) === Object.prototype) {\n    const keys = Object.keys(value);\n    const str = keys.length ? `{${keys.length}}` : '{}';\n    cache.set(value, str);\n    return str;\n  }\n\n  const ctor =\n    value && typeof value === 'object' ? value.constructor : undefined;\n  if (ctor && typeof ctor === 'function' && ctor.name) {\n    const str = `${ctor.name}{…}`;\n    cache.set(value, str);\n    return str;\n  }\n\n  const tagString = Object.prototype.toString.call(value).slice(8, -1);\n  const str = `${tagString}{…}`;\n  cache.set(value, str);\n  return str;\n}\n\nexport const getStateChanges = (fiber: Fiber): StateChange[] => {\n  if (!fiber) return [];\n  const changes: StateChange[] = [];\n\n  if (\n    fiber.tag === FunctionComponentTag ||\n    fiber.tag === ForwardRefTag ||\n    fiber.tag === SimpleMemoComponentTag ||\n    fiber.tag === MemoComponentTag\n  ) {\n    let memoizedState: MemoizedState | null = fiber.memoizedState;\n    let prevState: MemoizedState | null | undefined =\n      fiber.alternate?.memoizedState;\n    let index = 0;\n\n    while (memoizedState) {\n      if (memoizedState.queue && memoizedState.memoizedState !== undefined) {\n        const change: StateChange = {\n          type: ChangeReason.FunctionalState,\n          name: index.toString(),\n          value: memoizedState.memoizedState,\n          prevValue: prevState?.memoizedState,\n        };\n        if (!isEqual(change.prevValue, change.value)) {\n          changes.push(change);\n        }\n      }\n      memoizedState = memoizedState.next;\n      prevState = prevState?.next;\n      index++;\n    }\n\n    return changes;\n  }\n\n  if (fiber.tag === ClassComponentTag) {\n    // when we have class component fiber, memoizedState is the component state\n    const change: StateChange = {\n      type: ChangeReason.ClassState,\n      name: 'state',\n      value: fiber.memoizedState,\n      prevValue: fiber.alternate?.memoizedState,\n    };\n    if (!isEqual(change.prevValue, change.value)) {\n      changes.push(change);\n    }\n    return changes;\n  }\n\n  return changes;\n};\ninterface ContextFiber {\n  context: unknown; // refers to Context<T>;\n  memoizedValue: unknown;\n}\n\nlet lastContextId = 0;\nconst contextIdMap = new WeakMap<ContextFiber, number>();\nconst getContextId = (contextFiber: ContextFiber) => {\n  const existing = contextIdMap.get(contextFiber);\n  if (existing) {\n    return existing;\n  }\n  lastContextId++;\n  contextIdMap.set(contextFiber, lastContextId);\n  return lastContextId;\n};\n\nfunction getContextChangesTraversal(\n  this: Array<Change>,\n  nextValue: ContextFiber | null | undefined,\n  prevValue: ContextFiber | null | undefined,\n): void {\n  if (!nextValue || !prevValue) return;\n  // const prevMemoizedValue = prevValue.memoizedValue;\n  const nextMemoizedValue = nextValue.memoizedValue;\n\n  const change: ContextChange = {\n    type: ChangeReason.Context,\n    name:\n      (nextValue.context as { displayName: string | undefined }).displayName ??\n      'Context.Provider',\n    value: nextMemoizedValue,\n    contextType: getContextId(nextValue.context as ContextFiber),\n\n    // unstable: false,\n  };\n  this.push(change);\n\n  // const prevValueString = fastSerialize(prevMemoizedValue);\n  // const nextValueString = fastSerialize(nextMemoizedValue);\n\n  // if (\n  //   unstableTypes.includes(typeof prevMemoizedValue) &&\n  //   unstableTypes.includes(typeof nextMemoizedValue) &&\n  //   prevValueString === nextValueString\n  // ) {\n  //   change.unstable = true;\n  // }\n}\n\nexport const getContextChanges = (fiber: Fiber) => {\n  const changes: Array<ContextChange> = [];\n\n  // Alexis: we use bind functions so that the compiler doesn't produce\n  // any closures\n  traverseContexts(fiber, getContextChangesTraversal.bind(changes));\n\n  return changes;\n};\n\ntype OnRenderHandler = (fiber: Fiber, renders: Array<Render>) => void;\ntype OnCommitStartHandler = () => void;\ntype OnCommitFinishHandler = () => void;\ntype OnErrorHandler = (error: unknown) => void;\ntype IsValidFiberHandler = (fiber: Fiber) => boolean;\ntype OnActiveHandler = () => void;\n\ninterface InstrumentationConfig {\n  onCommitStart: OnCommitStartHandler;\n  isValidFiber: IsValidFiberHandler;\n  onRender: OnRenderHandler;\n  onCommitFinish: OnCommitFinishHandler;\n  onError: OnErrorHandler;\n  onActive?: OnActiveHandler;\n  onPostCommitFiberRoot: () => void;\n  // monitoring does not need to track changes, and it adds overhead to leave it on\n  trackChanges: boolean;\n  // allows monitoring to continue tracking renders even if react scan dev mode is disabled\n  forceAlwaysTrackRenders?: boolean;\n}\n\ninterface InstrumentationInstance {\n  key: string;\n  config: InstrumentationConfig;\n  instrumentation: Instrumentation;\n}\n\ninterface Instrumentation {\n  isPaused: Signal<boolean>;\n  fiberRoots: WeakSet<FiberRoot>;\n}\n\nconst instrumentationInstances = new Map<string, InstrumentationInstance>();\nlet inited = false;\n\nconst getAllInstances = () => Array.from(instrumentationInstances.values());\n\ninterface IsRenderUnnecessaryState {\n  isRequiredChange: boolean;\n}\n\nfunction isRenderUnnecessaryTraversal(\n  this: IsRenderUnnecessaryState,\n  _propsName: string,\n  prevValue: unknown,\n  nextValue: unknown,\n): void {\n  if (\n    !isEqual(prevValue, nextValue) &&\n    !isValueUnstable(prevValue, nextValue)\n  ) {\n    this.isRequiredChange = true;\n  }\n}\n\n// FIXME: calculation is slow\nexport const isRenderUnnecessary = (fiber: Fiber) => {\n  if (!didFiberCommit(fiber)) return true;\n\n  const mutatedHostFibers = getMutatedHostFibers(fiber);\n  for (const mutatedHostFiber of mutatedHostFibers) {\n    const state: IsRenderUnnecessaryState = {\n      isRequiredChange: false,\n    };\n    traverseProps(mutatedHostFiber, isRenderUnnecessaryTraversal.bind(state));\n    if (state.isRequiredChange) return false;\n  }\n  return true;\n};\n\n// // re-implement this in new-outlines\n// const shouldRunUnnecessaryRenderCheck = () => {\n//   // yes, this can be condensed into one conditional, but ifs are easier to reason/build on than long boolean expressions\n//   if (!ReactScanInternals.options.value.trackUnnecessaryRenders) {\n//     return false;\n//   }\n\n//   // only run unnecessaryRenderCheck when monitoring is active in production if the user set dangerouslyForceRunInProduction\n//   if (\n//     getIsProduction() &&\n//     Store.monitor.value &&\n//     ReactScanInternals.options.value.dangerouslyForceRunInProduction &&\n//     ReactScanInternals.options.value.trackUnnecessaryRenders\n//   ) {\n//     return true;\n//   }\n\n//   if (getIsProduction() && Store.monitor.value) {\n//     return false;\n//   }\n\n//   return ReactScanInternals.options.value.trackUnnecessaryRenders;\n// };\n\nconst TRACK_UNNECESSARY_RENDERS = false;\n\nexport interface RenderData {\n  selfTime: number;\n  totalTime: number;\n  renderCount: number;\n  lastRenderTimestamp: number;\n}\n\nexport interface OldRenderData {\n  count: number;\n  time: number;\n  renders: Array<Render>;\n  displayName: string | null;\n  // oxlint-disable-next-line typescript/no-explicit-any\n  type: any;\n  // oxlint-disable-next-line typescript/no-explicit-any\n  changes?: any;\n}\n\nconst RENDER_DEBOUNCE_MS = 16;\n\nexport const renderDataMap = new WeakMap<object, Map<string, RenderData>>();\n\nfunction getFiberIdentifier(fiber: Fiber) {\n  return String(getFiberId(fiber));\n}\n\nexport function getRenderData(fiber: Fiber) {\n  const id = getFiberIdentifier(fiber);\n  const keyMap = renderDataMap.get(getType(fiber) as object);\n\n  if (keyMap) {\n    return keyMap.get(id);\n  }\n\n  return undefined;\n}\n\nexport function setRenderData(fiber: Fiber, value: RenderData) {\n  const type = getType(fiber.type);\n  const id = getFiberIdentifier(fiber);\n  let keyMap = renderDataMap.get(type as object);\n\n  if (!keyMap) {\n    keyMap = new Map();\n    renderDataMap.set(type as object, keyMap);\n  }\n\n  keyMap.set(id, value);\n}\n\nconst trackRender = (\n  fiber: Fiber,\n  fiberSelfTime: number,\n  fiberTotalTime: number,\n  hasChanges: boolean,\n  hasDomMutations: boolean,\n) => {\n  const currentTimestamp = Date.now();\n  const existingData = getRenderData(fiber);\n\n  if (\n    (hasChanges || hasDomMutations) &&\n    (!existingData ||\n      currentTimestamp - (existingData.lastRenderTimestamp || 0) >\n        RENDER_DEBOUNCE_MS)\n  ) {\n    const renderData: RenderData = existingData || {\n      selfTime: 0,\n      totalTime: 0,\n      renderCount: 0,\n      lastRenderTimestamp: currentTimestamp,\n    };\n\n    renderData.renderCount = (renderData.renderCount || 0) + 1;\n    renderData.selfTime = fiberSelfTime || 0;\n    renderData.totalTime = fiberTotalTime || 0;\n    renderData.lastRenderTimestamp = currentTimestamp;\n\n    setRenderData(fiber, { ...renderData });\n  }\n};\n\nexport const createInstrumentation = (\n  instanceKey: string,\n  config: InstrumentationConfig,\n) => {\n  const instrumentation: Instrumentation = {\n    // this will typically be false, but in cases where a user provides showToolbar: true, this will be true\n    isPaused: signal(!ReactScanInternals.options.value.enabled),\n    fiberRoots: new WeakSet<FiberRoot>(),\n  };\n  instrumentationInstances.set(instanceKey, {\n    key: instanceKey,\n    config,\n    instrumentation,\n  });\n  if (!inited) {\n    inited = true;\n\n    instrument({\n      name: 'react-scan',\n      onActive: config.onActive,\n      onCommitFiberRoot(_rendererID, root) {\n        instrumentation.fiberRoots.add(root);\n        // for now we always track everything for notifications, it may be worth it to make this configurable\n        // if (\n        //   ReactScanInternals.instrumentation?.isPaused.value &&\n        //   (Store.inspectState.value.kind === \"inspect-off\" ||\n        //     Store.inspectState.value.kind === \"uninitialized\") &&\n        //   !config.forceAlwaysTrackRenders\n        // ) {\n        //   return;\n        // }\n        const allInstances = getAllInstances();\n        for (const instance of allInstances) {\n          instance.config.onCommitStart();\n        }\n\n        traverseRenderedFibers(\n          root.current,\n          (fiber: Fiber, phase: 'mount' | 'update' | 'unmount') => {\n            const type = getType(fiber.type);\n            if (!type) return null;\n\n            const allInstances = getAllInstances();\n            const validInstancesIndicies: Array<number> = [];\n            for (let i = 0, len = allInstances.length; i < len; i++) {\n              const instance = allInstances[i];\n              if (!instance.config.isValidFiber(fiber)) continue;\n              validInstancesIndicies.push(i);\n            }\n            if (!validInstancesIndicies.length) return null;\n\n            const changes: Array<Change> = [];\n\n            if (allInstances.some((instance) => instance.config.trackChanges)) {\n              const changesProps = collectPropsChanges(fiber).changes;\n              const changesState = collectStateChanges(fiber).changes;\n              const changesContext = collectContextChanges(fiber).changes;\n\n              changes.push.apply(\n                null,\n                changesProps.map(\n                  (change) =>\n                    ({\n                      type: ChangeReason.Props,\n                      name: change.name,\n                      value: change.value,\n                    }) as Change,\n                ),\n              );\n\n              for (const change of changesState) {\n                if (fiber.tag === ClassComponentTag) {\n                  changes.push({\n                    type: ChangeReason.ClassState,\n                    name: change.name.toString(),\n                    value: change.value,\n                  } as Change);\n                } else {\n                  changes.push({\n                    type: ChangeReason.FunctionalState,\n                    name: change.name.toString(),\n                    value: change.value,\n                  } as Change);\n                }\n              }\n\n              changes.push.apply(\n                null,\n                changesContext.map(\n                  (change) =>\n                    ({\n                      type: ChangeReason.Context,\n                      name: change.name,\n                      value: change.value,\n                      contextType: Number(change.contextType),\n                    }) as Change,\n                ),\n              );\n            }\n\n            const { selfTime: fiberSelfTime, totalTime: fiberTotalTime } =\n              getTimings(fiber);\n\n            const fps = getFPS();\n            const render: Render = {\n              phase: RENDER_PHASE_STRING_TO_ENUM[phase],\n              componentName: getDisplayName(type),\n              count: 1,\n              changes,\n              time: fiberSelfTime,\n              forget: hasMemoCache(fiber),\n              // todo: allow this to be toggle-able through toolbar\n              // todo: performance optimization: if the last fiber measure was very off screen, do not run isRenderUnnecessary\n              unnecessary: TRACK_UNNECESSARY_RENDERS\n                ? isRenderUnnecessary(fiber)\n                : null,\n              didCommit: didFiberCommit(fiber),\n              fps,\n            };\n\n            // First, determine if this is a real render we should track\n            const hasChanges = changes.length > 0;\n            const hasDomMutations = getMutatedHostFibers(fiber).length > 0;\n\n            if (phase === 'update') {\n              trackRender(\n                fiber,\n                fiberSelfTime,\n                fiberTotalTime,\n                hasChanges,\n                hasDomMutations,\n              );\n            }\n\n            for (let i = 0, len = validInstancesIndicies.length; i < len; i++) {\n              const index = validInstancesIndicies[i];\n              const instance = allInstances[index];\n              instance.config.onRender(fiber, [render]);\n            }\n          },\n        );\n\n        for (const instance of allInstances) {\n          instance.config.onCommitFinish();\n        }\n      },\n      onPostCommitFiberRoot() {\n        const allInstances = getAllInstances();\n        for (const instance of allInstances) {\n          instance.config.onPostCommitFiberRoot();\n        }\n      },\n    });\n  }\n  return instrumentation;\n};\n"
  },
  {
    "path": "packages/scan/src/core/notifications/event-tracking.ts",
    "content": "import { useSyncExternalStore } from 'preact/compat';\nimport { not_globally_unique_generateId } from '~core/utils';\nimport { MAX_INTERACTION_BATCH, interactionStore } from './interaction-store';\nimport {\n  FiberRenders,\n  PerformanceEntryChannelEvent,\n  TimeoutStage,\n  listenForPerformanceEntryInteractions,\n  listenForRenders,\n  setupDetailedPointerTimingListener,\n  setupPerformancePublisher,\n} from './performance';\nimport {\n  MAX_CHANNEL_SIZE,\n  performanceEntryChannels,\n} from './performance-store';\nimport { BoundedArray } from './performance-utils';\nimport { createStore } from '~web/utils/create-store';\n\nlet profileListeners: Array<(interaction: FinalInteraction) => void> = [];\n\ntype FinalInteraction = {\n  detailedTiming: TimeoutStage;\n  latency: number;\n  completedAt: number;\n};\n\nexport const listenForProfile = (\n  listener: (interaction: FinalInteraction) => void,\n) => {\n  profileListeners.push(listener);\n\n  return () => {\n    profileListeners = profileListeners.filter(\n      (existingListener) => existingListener !== listener,\n    );\n  };\n};\n\nexport let interactionStatus:\n  | { kind: 'started'; startedAt: number }\n  | { kind: 'completed'; startedAt: number; endedAt: number }\n  | { kind: 'no-interaction' } = {\n  kind: 'no-interaction',\n};\n\ntype NewInteractionStoreState = {\n  /**\n   * problem definition: we need to store bounds but how do we handle uninitialized bounds\n   *\n   * i guess what we said before, we just have one active bounds and that's all that matters chat\n   */\n\n  startAt: number;\n  endAt: number;\n};\n\nexport const interactionStatusStore: {\n  state: NewInteractionStoreState | null;\n  listeners: Array<(state: NewInteractionStoreState) => void>;\n  addListener: (cb: (state: NewInteractionStoreState) => void) => () => void;\n} = {\n  state: null,\n  addListener: (cb) => {\n    interactionStatusStore.listeners.push(cb);\n    return () => {\n      interactionStatusStore.listeners =\n        interactionStatusStore.listeners.filter((l) => l !== cb);\n    };\n  },\n  listeners: [],\n};\n\nlet accumulatedFiberRendersOverTask: null | FiberRenders = null;\ntype InteractionEvent = {\n  kind: 'interaction';\n  data: {\n    startAt: number;\n    endAt: number;\n    meta: {\n      detailedTiming: TimeoutStage;\n      latency: number;\n      kind: PerformanceEntryChannelEvent['kind'];\n    };\n  };\n};\n\ntype LongRenderPipeline = {\n  kind: 'long-render';\n  data: {\n    startAt: number;\n    endAt: number;\n    meta: {\n      latency: number;\n      fiberRenders: FiberRenders;\n      fps: number;\n    };\n  };\n};\n\nexport type SlowdownEvent = (InteractionEvent | LongRenderPipeline) & {\n  id: string;\n};\n\ntype ToolbarEventStoreState = {\n  state: {\n    events: BoundedArray<SlowdownEvent>;\n  };\n  actions: {\n    addEvent: (event: SlowdownEvent) => void;\n    addListener: (listener: (event: SlowdownEvent) => void) => () => void;\n    clear: () => void;\n  };\n};\n\ntype DebugEvent = {\n  kind: string;\n  at: number;\n  meta?: unknown;\n};\nexport const debugEventStore = createStore<{\n  state: {\n    events: Array<DebugEvent>;\n  };\n  actions: {\n    // oxlint-disable-next-line typescript/no-explicit-any\n    addEvent: (event: any) => void;\n    clear: () => void;\n  };\n}>()((set) => ({\n  state: {\n    events: [],\n  },\n  actions: {\n    addEvent: (event: DebugEvent) => {\n      set((store) => ({\n        state: {\n          events: [...store.state.events, event],\n        },\n      }));\n    },\n    clear: () => {\n      set({\n        state: {\n          events: [],\n        },\n      });\n    },\n  },\n}));\n\nconst EVENT_STORE_CAPACITY = 200;\n\nexport const toolbarEventStore = createStore<ToolbarEventStoreState>()(\n  (set, get) => {\n    const listeners = new Set<(event: SlowdownEvent) => void>();\n\n    return {\n      state: {\n        events: new BoundedArray(EVENT_STORE_CAPACITY),\n      },\n\n      actions: {\n        addEvent: (event: SlowdownEvent) => {\n          listeners.forEach((listener) => listener(event));\n\n          const events = [...get().state.events, event];\n          const applyOverlapCheckToLongRenderEvent = (\n            longRenderEvent: LongRenderPipeline & { id: string },\n            onOverlap: (\n              overlapsWith: InteractionEvent & { id: string },\n            ) => void,\n          ) => {\n            const overlapsWith = events.find((event) => {\n              if (event.kind === 'long-render') {\n                return;\n              }\n\n              if (event.id === longRenderEvent.id) {\n                return;\n              }\n\n              /**\n               * |---x-----------x------ (interaction)\n               * |x-----------x          (long-render)\n               */\n\n              if (\n                longRenderEvent.data.startAt <= event.data.startAt &&\n                longRenderEvent.data.endAt <= event.data.endAt &&\n                longRenderEvent.data.endAt >= event.data.startAt\n              ) {\n                return true;\n              }\n\n              /**\n             * |x-----------x---- (interaction)\n             * |--x------------x  (long-render)\n             *\n\n             */\n\n              if (\n                event.data.startAt <= longRenderEvent.data.startAt &&\n                event.data.endAt >= longRenderEvent.data.startAt\n              ) {\n                return true;\n              }\n\n              /**\n               *\n               * |--x-------------x    (interaction)\n               * |x------------------x (long-render)\n               *\n               */\n\n              if (\n                longRenderEvent.data.startAt <= event.data.startAt &&\n                longRenderEvent.data.endAt >= event.data.endAt\n              ) {\n                return true;\n              }\n            }) as undefined | (InteractionEvent & { id: string }); // invariant: because we early check the typechecker does not know it must be the case that when it finds something, it will be an interaction it overlaps with\n\n            if (overlapsWith) {\n              onOverlap(overlapsWith);\n            }\n          };\n\n          const toRemove = new Set<string>();\n\n          events.forEach((event) => {\n            if (event.kind === 'interaction') return;\n            applyOverlapCheckToLongRenderEvent(event, () => {\n              toRemove.add(event.id);\n            });\n          });\n\n          const withRemovedEvents = events.filter(\n            (event) => !toRemove.has(event.id),\n          );\n\n          set(() => ({\n            state: {\n              events: BoundedArray.fromArray(\n                withRemovedEvents,\n                EVENT_STORE_CAPACITY,\n              ),\n            },\n          }));\n        },\n\n        addListener: (listener: (event: SlowdownEvent) => void) => {\n          listeners.add(listener);\n          return () => {\n            listeners.delete(listener);\n          };\n        },\n\n        clear: () => {\n          set({\n            state: {\n              events: new BoundedArray(EVENT_STORE_CAPACITY),\n            },\n          });\n        },\n      },\n    };\n  },\n);\n\nexport const useToolbarEventLog = () => {\n  return useSyncExternalStore(\n    toolbarEventStore.subscribe,\n    toolbarEventStore.getState,\n  );\n};\n\nlet taskDirtyAt: null | number = null;\nlet taskDirtyOrigin: null | number = null;\n\nlet previousTrackCurrentMouseOverElementCallback:\n  | ((e: MouseEvent) => void)\n  | null = null;\n\nlet overToolbar: boolean | null;\n\nconst trackCurrentMouseOverToolbar = () => {\n  const callback = (e: MouseEvent) => {\n    overToolbar = e\n      .composedPath()\n      .map((path) => (path as Element).id)\n      .filter(Boolean)\n      .includes('react-scan-toolbar');\n  };\n\n  document.addEventListener('mouseover', callback);\n  previousTrackCurrentMouseOverElementCallback = callback;\n\n  return () => {\n    if (previousTrackCurrentMouseOverElementCallback) {\n      document.removeEventListener(\n        'mouseover',\n        previousTrackCurrentMouseOverElementCallback,\n      );\n    }\n  };\n};\n\n// stops long tasks b/c backgrounded from being reported\nexport const startDirtyTaskTracking = () => {\n  const onVisibilityChange = () => {\n    taskDirtyAt = performance.now();\n    taskDirtyOrigin = performance.timeOrigin;\n  };\n\n  document.addEventListener('visibilitychange', onVisibilityChange);\n\n  return () => {\n    document.removeEventListener('visibilitychange', onVisibilityChange);\n  };\n};\n\nexport const HIGH_SEVERITY_FPS_DROP_TIME = 150;\n\nlet framesDrawnInTheLastSecond: Array<number> = [];\n\nexport function startLongPipelineTracking() {\n  let rafHandle: number;\n  let timeoutHandle: ReturnType<typeof setTimeout>;\n\n  function measure() {\n    let unSub: (() => void) | null = null;\n    accumulatedFiberRendersOverTask = null;\n    accumulatedFiberRendersOverTask = {};\n    unSub = listenForRenders(accumulatedFiberRendersOverTask);\n    const startOrigin = performance.timeOrigin;\n    const startTime = performance.now();\n    rafHandle = requestAnimationFrame(() => {\n      // very low overhead, on the order of dozens of microseconds to run\n      timeoutHandle = setTimeout(() => {\n        const endNow = performance.now();\n        const duration = endNow - startTime;\n        const endOrigin = performance.timeOrigin;\n        framesDrawnInTheLastSecond.push(endNow + endOrigin);\n\n        const framesInTheLastSecond = framesDrawnInTheLastSecond.filter(\n          (frameAt) => endNow + endOrigin - frameAt <= 1000,\n        );\n\n        const fps = framesInTheLastSecond.length;\n        framesDrawnInTheLastSecond = framesInTheLastSecond;\n\n        const taskConsideredDirty =\n          taskDirtyAt !== null && taskDirtyOrigin !== null\n            ? endNow + endOrigin - (taskDirtyOrigin + taskDirtyAt) < 100\n            : null;\n        // not useful to report slowdowns caused by things like outlines (can get expensive not fully optimized)\n        const wasTaskInfluencedByToolbar = overToolbar !== null && overToolbar;\n\n        if (\n          duration > HIGH_SEVERITY_FPS_DROP_TIME &&\n          !taskConsideredDirty &&\n          document.visibilityState === 'visible' &&\n          !wasTaskInfluencedByToolbar\n        ) {\n          const endAt = endOrigin + endNow;\n          const startAt = startTime + startOrigin;\n\n          toolbarEventStore.getState().actions.addEvent({\n            kind: 'long-render',\n            id: not_globally_unique_generateId(),\n            data: {\n              endAt: endAt,\n              startAt: startAt,\n              meta: {\n                // oxlint-disable-next-line typescript/no-non-null-assertion\n                fiberRenders: accumulatedFiberRendersOverTask!,\n                latency: duration,\n                fps,\n              },\n            },\n          });\n        }\n\n        taskDirtyAt = null;\n        taskDirtyOrigin = null;\n\n        unSub?.();\n        measure();\n      }, 0);\n    });\n    return unSub;\n  }\n\n  const measureUnSub = measure();\n\n  return () => {\n    measureUnSub();\n    cancelAnimationFrame(rafHandle);\n    clearTimeout(timeoutHandle);\n  };\n}\nexport const startTimingTracking = () => {\n  const unSubPerformance = setupPerformancePublisher();\n  const unSubMouseOver = trackCurrentMouseOverToolbar();\n  const unSubDirtyTaskTracking = startDirtyTaskTracking();\n  const unSubLongPipelineTracking = startLongPipelineTracking();\n\n  const onComplete = async (\n    _: string,\n    finalInteraction: FinalInteraction,\n    event: PerformanceEntryChannelEvent,\n  ) => {\n    toolbarEventStore.getState().actions.addEvent({\n      kind: 'interaction',\n      id: not_globally_unique_generateId(),\n      data: {\n        startAt: finalInteraction.detailedTiming.blockingTimeStart,\n        endAt: performance.now() + performance.timeOrigin,\n        meta: { ...finalInteraction, kind: event.kind }, // TODO, will need interaction specific metadata here\n      },\n    });\n\n    const existingCompletedInteractions =\n      performanceEntryChannels.getChannelState('recording');\n\n    finalInteraction.detailedTiming.stopListeningForRenders();\n\n    if (existingCompletedInteractions.length) {\n      // then performance entry and our detailed timing handlers are out of sync, we disregard that entry\n      // it may be possible the performance entry returned before detailed timing. If that's the case we should update\n      // assumptions and deal with mapping the entry back to the detailed timing here\n      performanceEntryChannels.updateChannelState(\n        'recording',\n        () => new BoundedArray(MAX_CHANNEL_SIZE),\n      );\n    }\n  };\n  const unSubDetailedPointerTiming = setupDetailedPointerTimingListener(\n    'pointer',\n    {\n      onComplete,\n    },\n  );\n  const unSubDetailedKeyboardTiming = setupDetailedPointerTimingListener(\n    'keyboard',\n    {\n      onComplete,\n    },\n  );\n\n  const unSubInteractions = listenForPerformanceEntryInteractions(\n    (completedInteraction) => {\n      interactionStore.setState(\n        BoundedArray.fromArray(\n          interactionStore.getCurrentState().concat(completedInteraction),\n          MAX_INTERACTION_BATCH,\n        ),\n      );\n    },\n  );\n\n  return () => {\n    unSubMouseOver();\n    unSubDirtyTaskTracking();\n    unSubLongPipelineTracking();\n    unSubPerformance();\n    unSubDetailedPointerTiming();\n    unSubInteractions();\n    unSubDetailedKeyboardTiming();\n  };\n};\n"
  },
  {
    "path": "packages/scan/src/core/notifications/interaction-store.ts",
    "content": "import { BoundedArray } from \"~core/notifications/performance-utils\";\nimport { CompletedInteraction } from \"./performance\";\n\ntype Subscriber<T> = (data: T) => void;\n\nexport class Store<T> {\n  private subscribers: Set<Subscriber<T>> = new Set();\n  private currentValue: T;\n\n  constructor(initialValue: T) {\n    this.currentValue = initialValue;\n  }\n\n  subscribe(subscriber: Subscriber<T>): () => void {\n    this.subscribers.add(subscriber);\n\n    subscriber(this.currentValue);\n\n    return () => {\n      this.subscribers.delete(subscriber);\n    };\n  }\n\n  setState(data: T) {\n    this.currentValue = data;\n    this.subscribers.forEach((subscriber) => subscriber(data));\n  }\n\n  getCurrentState(): T {\n    return this.currentValue;\n  }\n}\nexport const MAX_INTERACTION_BATCH = 150;\nexport const interactionStore = new Store<BoundedArray<CompletedInteraction>>(\n  new BoundedArray(MAX_INTERACTION_BATCH)\n);\n"
  },
  {
    "path": "packages/scan/src/core/notifications/outline-overlay.ts",
    "content": "import { signal } from '@preact/signals';\nimport { iife } from './performance-utils';\n\nexport let highlightCanvas: HTMLCanvasElement | null = null;\nexport let highlightCtx: CanvasRenderingContext2D | null = null;\n\nlet animationFrame: number | null = null;\n\ntype TransitionHighlightState = {\n  kind: 'transition';\n  transitionTo: {\n    name: string;\n    rects: Array<DOMRect>;\n    alpha: number;\n  };\n  current: {\n    name: string;\n    rects: Array<DOMRect>;\n    alpha: number;\n  } | null;\n};\ntype HighlightState =\n  | TransitionHighlightState\n  | {\n      kind: 'move-out';\n      current: {\n        name: string;\n        rects: Array<DOMRect>;\n        alpha: number;\n      };\n    }\n  | {\n      kind: 'idle';\n      current: {\n        name: string;\n        rects: Array<DOMRect>;\n      } | null;\n    };\n\nexport const HighlightStore = signal<HighlightState>({\n  kind: 'idle',\n  current: null,\n});\n\nlet currFrame: ReturnType<typeof requestAnimationFrame> | null = null;\nlet lastFrameTime = 0;\nconst FADE_SPEED = 1.8;\nconst MAX_DELTA = 0.05;\nconst DEFAULT_DELTA = 1 / 60;\n\nexport const drawHighlights = () => {\n  if (currFrame) {\n    cancelAnimationFrame(currFrame);\n  }\n  currFrame = requestAnimationFrame((timestamp) => {\n    if (!highlightCanvas || !highlightCtx) {\n      return;\n    }\n\n    const dt = lastFrameTime\n      ? Math.min((timestamp - lastFrameTime) / 1000, MAX_DELTA)\n      : DEFAULT_DELTA;\n    lastFrameTime = timestamp;\n    const step = FADE_SPEED * dt;\n\n    highlightCtx.clearRect(0, 0, highlightCanvas.width, highlightCanvas.height);\n\n    const color = 'hsl(271, 76%, 53%)';\n    const state = HighlightStore.value;\n    const { alpha, current } = iife(() => {\n      switch (state.kind) {\n        case 'transition': {\n          const current =\n            state.current?.alpha && state.current.alpha > 0\n              ? state.current\n              : state.transitionTo;\n          return {\n            alpha: current ? current.alpha : 0,\n            current,\n          };\n        }\n        case 'move-out': {\n          return { alpha: state.current?.alpha ?? 0, current: state.current };\n        }\n        case 'idle': {\n          return { alpha: 1, current: state.current };\n        }\n      }\n      // exhaustive check\n      state satisfies never;\n    });\n\n    current?.rects.forEach((rect) => {\n      if (!highlightCtx) {\n        // typescript cant tell this closure is synchronous/non-escaping\n        return;\n      }\n      highlightCtx.shadowColor = color;\n      highlightCtx.shadowBlur = 6;\n      highlightCtx.strokeStyle = color;\n      highlightCtx.lineWidth = 2;\n\n      highlightCtx.globalAlpha = alpha;\n\n      highlightCtx.beginPath();\n      highlightCtx.rect(rect.left, rect.top, rect.width, rect.height);\n      highlightCtx.stroke();\n\n      highlightCtx.shadowBlur = 0;\n      highlightCtx.beginPath();\n      highlightCtx.rect(rect.left, rect.top, rect.width, rect.height);\n      highlightCtx.stroke();\n    });\n\n    switch (state.kind) {\n      case 'move-out': {\n        if (state.current.alpha === 0) {\n          HighlightStore.value = {\n            kind: 'idle',\n            current: null,\n          };\n          lastFrameTime = 0;\n          return;\n        }\n        if (state.current.alpha <= 0.01) {\n          state.current.alpha = 0;\n        }\n        state.current.alpha = Math.max(0, state.current.alpha - step);\n        drawHighlights();\n        return;\n      }\n      case 'transition': {\n        if (state.current && state.current.alpha > 0) {\n          state.current.alpha = Math.max(0, state.current.alpha - step);\n          drawHighlights();\n          return;\n        }\n\n        // invariant, state.current.alpha === 0\n        if (state.transitionTo.alpha === 1) {\n          HighlightStore.value = {\n            kind: 'idle',\n            current: state.transitionTo,\n          };\n          lastFrameTime = 0;\n          return;\n        }\n\n        state.transitionTo.alpha = Math.min(state.transitionTo.alpha + step, 1);\n\n        drawHighlights();\n      }\n      case 'idle': {\n        // no-op\n        lastFrameTime = 0;\n        return;\n      }\n    }\n  });\n};\n\nlet handleResizeListener: (() => void) | null = null;\nexport const createHighlightCanvas = (root: HTMLElement) => {\n  highlightCanvas = document.createElement('canvas');\n  highlightCtx = highlightCanvas.getContext('2d', { alpha: true });\n  if (!highlightCtx) return null;\n\n  const dpr = window.devicePixelRatio || 1;\n  const { innerWidth, innerHeight } = window;\n\n  highlightCanvas.style.width = `${innerWidth}px`;\n  highlightCanvas.style.height = `${innerHeight}px`;\n  highlightCanvas.width = innerWidth * dpr;\n  highlightCanvas.height = innerHeight * dpr;\n  highlightCanvas.style.position = 'fixed';\n  highlightCanvas.style.left = '0';\n  highlightCanvas.style.top = '0';\n  highlightCanvas.style.pointerEvents = 'none';\n  highlightCanvas.style.zIndex = '2147483600';\n\n  highlightCtx.scale(dpr, dpr);\n\n  root.appendChild(highlightCanvas);\n\n  if (handleResizeListener) {\n    window.removeEventListener('resize', handleResizeListener);\n  }\n\n  const handleResize = () => {\n    if (!highlightCanvas || !highlightCtx) return;\n    const dpr = window.devicePixelRatio || 1;\n    const { innerWidth, innerHeight } = window;\n\n    highlightCanvas.style.width = `${innerWidth}px`;\n    highlightCanvas.style.height = `${innerHeight}px`;\n    highlightCanvas.width = innerWidth * dpr;\n    highlightCanvas.height = innerHeight * dpr;\n    highlightCtx.scale(dpr, dpr);\n\n    drawHighlights();\n  };\n  handleResizeListener = handleResize;\n\n  window.addEventListener('resize', handleResize);\n\n  HighlightStore.subscribe(() => {\n    requestAnimationFrame(() => {\n      drawHighlights();\n    });\n  });\n\n  return cleanup;\n};\n\nexport function cleanup() {\n  if (animationFrame) {\n    cancelAnimationFrame(animationFrame);\n    animationFrame = null;\n  }\n  if (highlightCanvas?.parentNode) {\n    highlightCanvas.parentNode.removeChild(highlightCanvas);\n  }\n  highlightCanvas = null;\n  highlightCtx = null;\n}\n"
  },
  {
    "path": "packages/scan/src/core/notifications/performance-store.ts",
    "content": "import { BoundedArray } from \"./performance-utils\";\nimport { PerformanceEntryChannelEvent } from \"./performance\";\n\ntype UnSubscribe = () => void;\ntype Callback<T> = (item: T) => void;\ntype Updater<T> = (state: BoundedArray<T>) => BoundedArray<T>;\ntype ChanelName = string;\n\ntype PerformanceEntryChannelsType<T> = {\n  subscribe: (to: ChanelName, cb: Callback<T>) => UnSubscribe;\n  publish: (\n    item: T,\n    to: ChanelName,\n    dropFirst: boolean,\n    createIfNoChannel: boolean\n  ) => void;\n  channels: Record<\n    ChanelName,\n    { callbacks: BoundedArray<Callback<T>>; state: BoundedArray<T> }\n  >;\n  getAvailableChannels: () => BoundedArray<string>;\n  updateChannelState: (\n    channel: ChanelName,\n    updater: Updater<T>,\n    createIfNoChannel: boolean\n  ) => void;\n};\n\nexport const MAX_CHANNEL_SIZE = 50;\n// a set of entities communicate to each other through channels\n// the state in the channel is persisted until the receiving end consumes it\n// multiple subscribes to the same channel will likely lead to unintended behavior if the subscribers are separate entities\nclass PerformanceEntryChannels<T> implements PerformanceEntryChannelsType<T> {\n  channels: PerformanceEntryChannelsType<T>[\"channels\"] = {};\n  publish(item: T, to: ChanelName, createIfNoChannel = true) {\n    const existingChannel = this.channels[to];\n    if (!existingChannel) {\n      if (!createIfNoChannel) {\n        return;\n      }\n      this.channels[to] = {\n        callbacks: new BoundedArray<Callback<T>>(MAX_CHANNEL_SIZE),\n        state: new BoundedArray<T>(MAX_CHANNEL_SIZE),\n      };\n      this.channels[to].state.push(item);\n      return;\n    }\n\n    existingChannel.state.push(item);\n    existingChannel.callbacks.forEach((cb) => cb(item));\n  }\n\n  getAvailableChannels() {\n    return BoundedArray.fromArray(Object.keys(this.channels), MAX_CHANNEL_SIZE);\n  }\n  subscribe(to: ChanelName, cb: Callback<T>, dropFirst: boolean = false) {\n    const defer = () => {\n      if (!dropFirst) {\n        this.channels[to].state.forEach((item) => {\n          cb(item);\n        });\n      }\n      return () => {\n        const filtered = this.channels[to].callbacks.filter(\n          (subscribed) => subscribed !== cb\n        );\n        this.channels[to].callbacks = BoundedArray.fromArray(\n          filtered,\n          MAX_CHANNEL_SIZE\n        );\n      };\n    };\n    const existing = this.channels[to];\n    if (!existing) {\n      this.channels[to] = {\n        callbacks: new BoundedArray<Callback<T>>(MAX_CHANNEL_SIZE),\n        state: new BoundedArray<T>(MAX_CHANNEL_SIZE),\n      };\n      this.channels[to].callbacks.push(cb);\n      return defer();\n    }\n\n    existing.callbacks.push(cb);\n    return defer();\n  }\n  updateChannelState(\n    channel: ChanelName,\n    updater: Updater<T>,\n    createIfNoChannel = true\n  ) {\n    const existingChannel = this.channels[channel];\n    if (!existingChannel) {\n      if (!createIfNoChannel) {\n        return;\n      }\n\n      const state = new BoundedArray<T>(MAX_CHANNEL_SIZE);\n      const newChannel = {\n        callbacks: new BoundedArray<Callback<T>>(MAX_CHANNEL_SIZE),\n        state,\n      };\n\n      this.channels[channel] = newChannel;\n      newChannel.state = updater(state);\n      return;\n    }\n\n    existingChannel.state = updater(existingChannel.state);\n  }\n\n  getChannelState(channel: ChanelName) {\n    return (\n      this.channels[channel].state ?? new BoundedArray<T>(MAX_CHANNEL_SIZE)\n    );\n  }\n}\n// todo: discriminated union the events when we start using multiple channels\n// we used to use multiple channels, but now we only use 1. This is still a useful abstraction incase we ever need more channels again\nexport const performanceEntryChannels =\n  new PerformanceEntryChannels<PerformanceEntryChannelEvent>();\n"
  },
  {
    "path": "packages/scan/src/core/notifications/performance-utils.ts",
    "content": "import { Fiber } from 'bippy';\nexport const getChildrenFromFiberLL = (fiber: Fiber) => {\n  const children: Array<Fiber> = [];\n\n  let curr: typeof fiber.child = fiber.child;\n\n  while (curr) {\n    children.push(curr);\n\n    curr = curr.sibling;\n  }\n\n  return children;\n};\n\ntype Node = Map<\n  Fiber,\n  {\n    children: Array<Fiber>;\n    parent: Fiber | null;\n    isRoot: boolean;\n    isSVG: boolean;\n  }\n>;\n\nexport const createChildrenAdjacencyList = (root: Fiber, limit: number) => {\n  const tree: Node = new Map([]);\n\n  const queue: Array<[node: Fiber, parent: Fiber | null]> = [];\n  const visited = new Set<Fiber>();\n\n  queue.push([root, root.return]);\n  let traversed = 1;\n\n  while (queue.length) {\n    if (traversed >= limit) {\n      return tree;\n    }\n    // oxlint-disable-next-line typescript/no-non-null-assertion\n    const [node, parent] = queue.pop()!;\n    const children = getChildrenFromFiberLL(node);\n\n    tree.set(node, {\n      children: [],\n      parent,\n      isRoot: node === root,\n      isSVG: node.type === 'svg',\n    });\n\n    for (const child of children) {\n      traversed += 1;\n      // this isn't needed since the fiber tree is a TREE, not a graph, but it makes me feel safer\n      if (visited.has(child)) {\n        continue;\n      }\n      visited.add(child);\n      tree.get(node)?.children.push(child);\n      queue.push([child, node]);\n    }\n  }\n  return tree;\n};\n\nconst THROW_INVARIANTS = false;\n\nexport const invariantError = (message: string | undefined) => {\n  if (THROW_INVARIANTS) {\n    throw new Error(message);\n  }\n};\n\nexport const iife = <T>(fn: () => T): T => fn();\n\nexport class BoundedArray<T> extends Array<T> {\n  constructor(private capacity: number = 25) {\n    super();\n  }\n\n  push(...items: T[]): number {\n    const result = super.push(...items);\n    while (this.length > this.capacity) {\n      this.shift();\n    }\n    return result;\n  }\n  // do not couple capacity with a default param, it must be explicit\n  static fromArray<T>(array: Array<T>, capacity: number) {\n    const arr = new BoundedArray<T>(capacity);\n    arr.push(...array);\n    return arr;\n  }\n}\n"
  },
  {
    "path": "packages/scan/src/core/notifications/performance.ts",
    "content": "import {\n  Fiber,\n  getDisplayName,\n  getTimings,\n  hasMemoCache,\n  isHostFiber,\n  traverseFiber,\n} from 'bippy';\nimport { Store } from '../..';\n\nimport {\n  BoundedArray,\n  invariantError,\n} from '~core/notifications/performance-utils';\nimport {\n  SectionData,\n  collectInspectorDataWithoutCounts,\n} from '~web/views/inspector/timeline/utils';\nimport {\n  getFiberFromElement,\n  getParentCompositeFiber,\n} from '~web/views/inspector/utils';\nimport { performanceEntryChannels } from './performance-store';\nimport type {\n  PerformanceInteraction,\n  PerformanceInteractionEntry,\n} from './types';\nimport { not_globally_unique_generateId } from '~core/utils';\n\ninterface PathFilters {\n  skipProviders: boolean;\n  skipHocs: boolean;\n  skipContainers: boolean;\n  skipMinified: boolean;\n  skipUtilities: boolean;\n  skipBoundaries: boolean;\n}\n\nconst DEFAULT_PATH_FILTERS: PathFilters = {\n  skipProviders: true,\n  skipHocs: true,\n  skipContainers: true,\n  skipMinified: true,\n  skipUtilities: true,\n  skipBoundaries: true,\n};\n\nconst PATH_FILTER_PATTERNS = {\n  providers: [/Provider$/, /^Provider$/, /^Context$/],\n  hocs: [/^with[A-Z]/, /^forward(?:Ref)?$/i, /^Forward(?:Ref)?\\(/],\n  containers: [/^(?:App)?Container$/, /^Root$/, /^ReactDev/],\n  utilities: [\n    /^Fragment$/,\n    /^Suspense$/,\n    /^ErrorBoundary$/,\n    /^Portal$/,\n    /^Consumer$/,\n    /^Layout$/,\n    /^Router/,\n    /^Hydration/,\n  ],\n  boundaries: [/^Boundary$/, /Boundary$/, /^Provider$/, /Provider$/],\n};\n\nconst shouldIncludeInPath = (\n  name: string,\n  filters: PathFilters = DEFAULT_PATH_FILTERS,\n): boolean => {\n  const patternsToCheck: Array<RegExp> = [];\n  if (filters.skipProviders) patternsToCheck.push(...PATH_FILTER_PATTERNS.providers);\n  if (filters.skipHocs) patternsToCheck.push(...PATH_FILTER_PATTERNS.hocs);\n  if (filters.skipContainers) patternsToCheck.push(...PATH_FILTER_PATTERNS.containers);\n  if (filters.skipUtilities) patternsToCheck.push(...PATH_FILTER_PATTERNS.utilities);\n  if (filters.skipBoundaries) patternsToCheck.push(...PATH_FILTER_PATTERNS.boundaries);\n  return !patternsToCheck.some((pattern) => pattern.test(name));\n};\n\nconst minifiedPatterns = [\n  /^[a-z]$/,\n  /^[a-z][0-9]$/,\n  /^_+$/,\n  /^[A-Za-z][_$]$/,\n  /^[a-z]{1,2}$/,\n];\n\nconst isMinified = (name: string): boolean => {\n  for (let i = 0; i < minifiedPatterns.length; i++) {\n    if (minifiedPatterns[i].test(name)) return true;\n  }\n  const hasNoVowels = !/[aeiou]/i.test(name);\n  const hasMostlyNumbers = (name.match(/\\d/g)?.length ?? 0) > name.length / 2;\n  const isSingleWordLowerCase = /^[a-z]+$/.test(name);\n  const hasRandomLookingChars = /[$_]{2,}/.test(name);\n  return (\n    Number(hasNoVowels) +\n      Number(hasMostlyNumbers) +\n      Number(isSingleWordLowerCase) +\n      Number(hasRandomLookingChars) >=\n    2\n  );\n};\n\ninterface FiberType {\n  displayName?: string;\n  name?: string;\n  [key: string]: unknown;\n}\n\nconst getCleanComponentName = (component: FiberType): string => {\n  const name = getDisplayName(component);\n  if (!name) return '';\n  return name.replace(\n    /^(?:Memo|Forward(?:Ref)?|With.*?)\\((?<inner>.*?)\\)$/,\n    '$<inner>',\n  );\n};\n\nconst getInteractionPath = (\n  initialFiber: Fiber | null,\n  filters: PathFilters = DEFAULT_PATH_FILTERS,\n): Array<string> => {\n  if (!initialFiber) return [];\n\n  const currentName = getDisplayName(initialFiber.type);\n  if (!currentName) return [];\n\n  const stack = new Array<string>();\n  let fiber = initialFiber;\n  while (fiber.return) {\n    const name = getCleanComponentName(fiber.type);\n    if (name && !isMinified(name) && shouldIncludeInPath(name, filters) && name.toLowerCase() !== name) {\n      stack.push(name);\n    }\n    fiber = fiber.return;\n  }\n  const fullPath = new Array<string>(stack.length);\n  for (let i = 0; i < stack.length; i++) {\n    fullPath[i] = stack[stack.length - i - 1];\n  }\n  return fullPath;\n};\n\nconst getFirstNameFromAncestor = (\n  fiber: Fiber,\n  accept: (name: string) => boolean = () => true,\n) => {\n  let curr: Fiber | null = fiber;\n\n  while (curr) {\n    const currName = getDisplayName(curr.type);\n    if (currName && accept(currName)) {\n      return currName;\n    }\n\n    curr = curr.return;\n  }\n  return null;\n};\n\nlet unsubscribeTrackVisibilityChange: (() => void) | undefined;\n// fixme: compress me if this stays here for bad interaction time checks\nlet lastVisibilityHiddenAt: number | 'never-hidden' = 'never-hidden';\n\nconst trackVisibilityChange = () => {\n  unsubscribeTrackVisibilityChange?.();\n  const onVisibilityChange = () => {\n    if (document.hidden) {\n      lastVisibilityHiddenAt = Date.now();\n    }\n  };\n  document.addEventListener('visibilitychange', onVisibilityChange);\n\n  unsubscribeTrackVisibilityChange = () => {\n    document.removeEventListener('visibilitychange', onVisibilityChange);\n  };\n};\nexport type FiberRenders = Record<\n  string,\n  {\n    renderCount: number;\n    parents: Set<string>;\n    selfTime: number;\n    totalTime: number;\n    hasMemoCache: boolean;\n    wasFiberRenderMount: boolean;\n    nodeInfo: Array<{\n      selfTime: number;\n      element: Element;\n      name: string;\n    }>;\n    changes: ReturnType<typeof collectInspectorDataWithoutCounts>;\n  }\n>;\n\n/**\n * we need to fix:\n * - if there's a tab switch during a task being tracked, then u disregard that task (i hope this doesn't make tab switches hard to debug that cause slowdowns, ug i suppose it probably would, right? Depends how the browser queues it but i suppose u can think of a scenario. It would be most optimal to subtract the timing but not sure how reliable that would be)\n * - we need to see why the tracking is just off\n * - we need to correctly implement the precise activation this time\n */\n\ntype InteractionStartStage = {\n  kind: 'interaction-start';\n  interactionType: 'pointer' | 'keyboard';\n  interactionUUID: string;\n  interactionStartDetail: number;\n  blockingTimeStart: number;\n  componentPath: Array<string>;\n  componentName: string;\n  childrenTree: Record<\n    string,\n    { children: Array<string>; firstNamedAncestor: string; isRoot: boolean }\n  >;\n  fiberRenders: FiberRenders;\n  stopListeningForRenders: () => void;\n};\n\ntype JSEndStage = Omit<InteractionStartStage, 'kind'> & {\n  kind: 'js-end-stage';\n  jsEndDetail: number;\n};\n\ntype RAFStage = Omit<JSEndStage, 'kind'> & {\n  kind: 'raf-stage';\n  rafStart: number;\n};\n\nexport type TimeoutStage = Omit<RAFStage, 'kind'> & {\n  kind: 'timeout-stage';\n  commitEnd: number;\n  blockingTimeEnd: number;\n};\n\nexport type PerformanceEntryChannelEvent =\n  | {\n      kind: 'entry-received';\n      entry: PerformanceInteraction;\n    }\n  | {\n      kind: 'auto-complete-race';\n      interactionUUID: string;\n      detailedTiming: TimeoutStage;\n    };\n\nexport type CompletedInteraction = {\n  detailedTiming: TimeoutStage;\n  latency: number;\n  completedAt: number;\n  flushNeeded: boolean;\n};\n\ntype UnInitializedStage = {\n  kind: 'uninitialized-stage';\n  // todo: no longer a uuid\n  interactionUUID: string;\n  interactionType: 'pointer' | 'keyboard';\n};\n\ntype CurrentInteraction = {\n  kind: 'pointer' | 'keyboard';\n  interactionUUID: string;\n  pointerUpStart: number;\n  // needed for when inputs that can be clicked and trigger on change (like checkboxes)\n  clickChangeStart: number | null;\n  clickHandlerMicroTaskEnd: number | null;\n  rafStart: number | null;\n  commmitEnd: number | null;\n  timeorigin: number;\n\n  // for now i don't trust performance now timing for UTC time...\n  blockingTimeStart: number;\n  blockingTimeEnd: number | null;\n  fiberRenders: Map<\n    string,\n    {\n      renderCount: number;\n      parents: Set<string>;\n      selfTime: number;\n    }\n  >;\n  componentPath: Array<string>;\n  componentName: string;\n  childrenTree: Record<\n    string,\n    { children: Array<string>; firstNamedAncestor: string; isRoot: boolean }\n  >;\n};\n\nexport let currentInteractions: Array<CurrentInteraction> = [];\nconst getInteractionType = (\n  eventName: string,\n): 'pointer' | 'keyboard' | null => {\n  // todo: track pointer down, but tends to not house expensive logic so not very high priority\n  if (['pointerup', 'click'].includes(eventName)) {\n    return 'pointer';\n  }\n  if (eventName.includes('key')) {\n  }\n  if (['keydown', 'keyup'].includes(eventName)) {\n    return 'keyboard';\n  }\n  return null;\n};\nlet onEntryAnimationId: number | null = null;\nconst setupPerformanceListener = (\n  onEntry: (interaction: PerformanceInteraction) => void,\n) => {\n  trackVisibilityChange();\n  const interactionMap = new Map<string, PerformanceInteraction>();\n  const interactionTargetMap = new Map<string, Element>();\n\n  const processInteractionEntry = (entry: PerformanceInteractionEntry) => {\n    if (!entry.interactionId) return;\n\n    if (\n      entry.interactionId &&\n      entry.target &&\n      !interactionTargetMap.has(entry.interactionId)\n    ) {\n      interactionTargetMap.set(entry.interactionId, entry.target);\n    }\n    if (entry.target) {\n      let current: Element | null = entry.target;\n      while (current) {\n        if (\n          current.id === 'react-scan-toolbar-root' ||\n          current.id === 'react-scan-root'\n        ) {\n          return;\n        }\n        current = current.parentElement;\n      }\n    }\n\n    const existingInteraction = interactionMap.get(entry.interactionId);\n\n    if (existingInteraction) {\n      if (entry.duration > existingInteraction.latency) {\n        existingInteraction.entries = [entry];\n        existingInteraction.latency = entry.duration;\n      } else if (\n        entry.duration === existingInteraction.latency &&\n        entry.startTime === existingInteraction.entries[0].startTime\n      ) {\n        existingInteraction.entries.push(entry);\n      }\n    } else {\n      const interactionType = getInteractionType(entry.name);\n      if (!interactionType) {\n        return;\n      }\n\n      const interaction: PerformanceInteraction = {\n        id: entry.interactionId,\n        latency: entry.duration,\n        entries: [entry],\n        target: entry.target,\n        type: interactionType,\n        startTime: entry.startTime,\n        endTime: Date.now(),\n        processingStart: entry.processingStart,\n        processingEnd: entry.processingEnd,\n        duration: entry.duration,\n        inputDelay: entry.processingStart - entry.startTime,\n        processingDuration: entry.processingEnd - entry.processingStart,\n        presentationDelay:\n          entry.duration - (entry.processingEnd - entry.startTime),\n        // componentPath:\n        timestamp: Date.now(),\n        timeSinceTabInactive:\n          lastVisibilityHiddenAt === 'never-hidden'\n            ? 'never-hidden'\n            : Date.now() - lastVisibilityHiddenAt,\n        visibilityState: document.visibilityState,\n        timeOrigin: performance.timeOrigin,\n        referrer: document.referrer,\n      };\n      //\n      interactionMap.set(interaction.id, interaction);\n\n      /**\n       * This seems odd, but it gives us determinism that we will receive an entry AFTER our detailed timing collection\n       * runs because browser semantics (raf(() => setTimeout) will always run before a doubleRaf)\n       *\n       * this also handles the case where multiple entries are dispatched for semantically the same interaction,\n       * they will get merged into a single interaction, where the largest latency is recorded, which is what\n       * we are interested in this application\n       */\n\n      if (!onEntryAnimationId) {\n        onEntryAnimationId = requestAnimationFrame(() => {\n          requestAnimationFrame(() => {\n            // oxlint-disable-next-line typescript/no-non-null-assertion\n            onEntry(interactionMap.get(interaction.id)!);\n            onEntryAnimationId = null;\n          });\n        });\n      }\n    }\n  };\n\n  const po = new PerformanceObserver((list) => {\n    const entries = list.getEntries();\n    for (let i = 0, len = entries.length; i < len; i++) {\n      const entry = entries[i];\n      processInteractionEntry(entry as PerformanceInteractionEntry);\n    }\n  });\n\n  try {\n    po.observe({\n      type: 'event',\n      buffered: true,\n      durationThreshold: 16,\n    } as PerformanceObserverInit);\n    po.observe({\n      type: 'first-input',\n      buffered: true,\n    });\n  } catch {\n    /* Should collect error logs*/\n  }\n\n  return () => po.disconnect();\n};\n\nexport const setupPerformancePublisher = () => {\n  return setupPerformanceListener((entry) => {\n    performanceEntryChannels.publish(\n      {\n        kind: 'entry-received',\n        entry,\n      },\n      'recording',\n    );\n  });\n};\n\n// we should actually only feed it the information it needs to complete so we can support safari\ntype Task = {\n  completeInteraction: (\n    entry: PerformanceEntryChannelEvent,\n  ) => CompletedInteraction;\n  startDateTime: number;\n  endDateTime: number;\n  type: 'keyboard' | 'pointer';\n  interactionUUID: string;\n};\nexport const MAX_INTERACTION_TASKS = 25;\n\nlet tasks = new BoundedArray<Task>(MAX_INTERACTION_TASKS);\n\nconst getAssociatedDetailedTimingInteraction = (\n  entry: PerformanceInteraction,\n  activeTasks: Array<Task>,\n) => {\n  let closestTask: Task | null = null;\n  for (const task of activeTasks) {\n    if (task.type !== entry.type) {\n      continue;\n    }\n\n    if (closestTask === null) {\n      closestTask = task;\n      continue;\n    }\n\n    const getAbsoluteDiff = (task: Task, entry: PerformanceInteraction) =>\n      Math.abs(task.startDateTime) - (entry.startTime + entry.timeOrigin);\n\n    if (getAbsoluteDiff(task, entry) < getAbsoluteDiff(closestTask, entry)) {\n      closestTask = task;\n    }\n  }\n\n  return closestTask;\n};\n\n// this would be cool if it listened for merge, so it had to be after\nexport const listenForPerformanceEntryInteractions = (\n  onComplete: (completedInteraction: CompletedInteraction) => void,\n) => {\n  // we make the assumption that the detailed timing will be ready before the performance timing\n  const unsubscribe = performanceEntryChannels.subscribe(\n    'recording',\n    (event) => {\n      const associatedDetailedInteraction =\n        event.kind === 'auto-complete-race'\n          ? tasks.find((task) => task.interactionUUID === event.interactionUUID)\n          : getAssociatedDetailedTimingInteraction(event.entry, tasks);\n\n      // REMINDME: this likely means we clicked a non interactable thing but our handler still ran\n      // so we shouldn't treat this as an invariant, but instead use it to verify if we clicked\n      // something interactable\n      if (!associatedDetailedInteraction) {\n        return;\n      }\n\n      const completedInteraction =\n        associatedDetailedInteraction.completeInteraction(event);\n      onComplete(completedInteraction);\n    },\n  );\n\n  return unsubscribe;\n};\n\ntype ShouldContinue = boolean;\nconst trackDetailedTiming = ({\n  onMicroTask,\n  onRAF,\n  onTimeout,\n  abort,\n}: {\n  onMicroTask: () => ShouldContinue;\n  onRAF: () => ShouldContinue;\n  onTimeout: () => void;\n  abort?: () => boolean;\n}) => {\n  queueMicrotask(() => {\n    if (abort?.() === true) {\n      return;\n    }\n\n    if (!onMicroTask()) {\n      return;\n    }\n    requestAnimationFrame(() => {\n      if (abort?.() === true) {\n        return;\n      }\n      if (!onRAF()) {\n        return;\n      }\n      setTimeout(() => {\n        if (abort?.() === true) {\n          return;\n        }\n        onTimeout();\n      }, 0);\n    });\n  });\n};\n\nconst getTargetInteractionDetails = (target: Element) => {\n  const associatedFiber = getFiberFromElement(target);\n  if (!associatedFiber) {\n    return;\n  }\n\n  // TODO: if element is minified, squash upwards till first non minified ancestor, and set name as ChildOf(<parent-name>)\n  let componentName = associatedFiber\n    ? getDisplayName(associatedFiber?.type)\n    : 'N/A';\n\n  if (!componentName) {\n    componentName =\n      getFirstNameFromAncestor(associatedFiber, (name) => name.length > 2) ??\n      'N/A';\n  }\n\n  if (!componentName) {\n    return;\n  }\n\n  const componentPath = getInteractionPath(associatedFiber);\n\n  return {\n    componentPath,\n    childrenTree: {},\n    componentName,\n    elementFiber: associatedFiber,\n  };\n};\n\ntype LastInteractionRef = {\n  current: (\n    | InteractionStartStage\n    | JSEndStage\n    | RAFStage\n    | TimeoutStage\n    | UnInitializedStage\n  ) & { stageStart: number };\n};\n\n/**\n *\n * handles tracking event timings for arbitrarily overlapping handlers with cancel logic\n */\nexport const setupDetailedPointerTimingListener = (\n  kind: 'pointer' | 'keyboard',\n  options: {\n    onStart?: (interactionUUID: string) => void;\n    onComplete?: (\n      interactionUUID: string,\n      finalInteraction: {\n        detailedTiming: TimeoutStage;\n        latency: number;\n        completedAt: number;\n        flushNeeded: boolean;\n      },\n      entry: PerformanceEntryChannelEvent,\n    ) => void;\n    onError?: (interactionUUID: string) => void;\n  },\n) => {\n  let instrumentationIdInControl: string | null = null;\n\n  const getEvent = (\n    info: { phase: 'start' } | { phase: 'end'; target: Element },\n  ) => {\n    switch (kind) {\n      case 'pointer': {\n        if (info.phase === 'start') {\n          return 'pointerup';\n        }\n        if (\n          info.target instanceof HTMLInputElement ||\n          info.target instanceof HTMLSelectElement\n        ) {\n          return 'change';\n        }\n        return 'click';\n      }\n      case 'keyboard': {\n        if (info.phase === 'start') {\n          return 'keydown';\n        }\n\n        return 'change';\n      }\n    }\n  };\n\n  const lastInteractionRef: LastInteractionRef = {\n    current: {\n      kind: 'uninitialized-stage',\n      interactionUUID: not_globally_unique_generateId(), // the first interaction uses this\n      stageStart: Date.now(),\n      interactionType: kind,\n    },\n  };\n\n  const onInteractionStart = (e: Event) => {\n    const path = e.composedPath();\n    if (\n      path.some(\n        (el) => el instanceof Element && el.id === 'react-scan-toolbar-root',\n      )\n    ) {\n      return;\n    }\n    if (Date.now() - lastInteractionRef.current.stageStart > 2000) {\n      lastInteractionRef.current = {\n        kind: 'uninitialized-stage',\n        interactionUUID: not_globally_unique_generateId(),\n        stageStart: Date.now(),\n        interactionType: kind,\n      };\n    }\n\n    if (lastInteractionRef.current.kind !== 'uninitialized-stage') {\n      return;\n    }\n\n    const pointerUpStart = performance.now();\n\n    options?.onStart?.(lastInteractionRef.current.interactionUUID);\n    const details = getTargetInteractionDetails(e.target as HTMLElement);\n\n    if (!details) {\n      options?.onError?.(lastInteractionRef.current.interactionUUID);\n      return;\n    }\n\n    const fiberRenders: InteractionStartStage['fiberRenders'] = {};\n    const stopListeningForRenders = listenForRenders(fiberRenders);\n    lastInteractionRef.current = {\n      ...lastInteractionRef.current,\n      interactionType: kind,\n      blockingTimeStart: Date.now(),\n      childrenTree: details.childrenTree,\n      componentName: details.componentName,\n      componentPath: details.componentPath,\n      fiberRenders,\n      kind: 'interaction-start',\n      interactionStartDetail: pointerUpStart,\n      stopListeningForRenders,\n    };\n\n    const event = getEvent({ phase: 'end', target: e.target as Element });\n    // oxlint-disable-next-line typescript/no-explicit-any\n    document.addEventListener(event, onLastJS as any, {\n      once: true,\n    });\n\n    // this is an edge case where a click event is not fired after a pointerdown\n    // im not sure why this happens, but it seems to only happen on non intractable elements\n    // it causes the event handler to stay alive until a future interaction, which can break timing (looks super long)\n    // or invariants (the start metadata was removed, so now its an end metadata with no start)\n    requestAnimationFrame(() => {\n      // oxlint-disable-next-line typescript/no-explicit-any\n      document.removeEventListener(event as any, onLastJS as any);\n    });\n  };\n\n  document.addEventListener(\n    getEvent({ phase: 'start' }),\n    // oxlint-disable-next-line typescript/no-explicit-any\n    onInteractionStart as any,\n    {\n      capture: true,\n    },\n  );\n\n  /**\n   *\n   * TODO: IF WE DETECT RENDERS DURING THIS PERIOD WE CAN INCLUDE THAT IN THE RESULT AND THEN BACK THAT OUT OF COMPUTED STYLE TIME AND ADD IT BACK INTO JS TIME\n   */\n  const onLastJS = (\n    e: { target: Element },\n    instrumentationId: string,\n    abort: () => boolean,\n  ) => {\n    if (\n      lastInteractionRef.current.kind !== 'interaction-start' &&\n      instrumentationId === instrumentationIdInControl\n    ) {\n      if (kind === 'pointer' && e.target instanceof HTMLSelectElement) {\n        lastInteractionRef.current = {\n          kind: 'uninitialized-stage',\n          interactionUUID: not_globally_unique_generateId(),\n          stageStart: Date.now(),\n          interactionType: kind,\n        };\n        return;\n      }\n\n      options?.onError?.(lastInteractionRef.current.interactionUUID);\n      lastInteractionRef.current = {\n        kind: 'uninitialized-stage',\n        interactionUUID: not_globally_unique_generateId(),\n        stageStart: Date.now(),\n        interactionType: kind,\n      };\n      invariantError('pointer -> click');\n      return;\n    }\n\n    instrumentationIdInControl = instrumentationId;\n\n    trackDetailedTiming({\n      abort,\n      onMicroTask: () => {\n        if (lastInteractionRef.current.kind === 'uninitialized-stage') {\n          return false;\n        }\n\n        lastInteractionRef.current = {\n          ...lastInteractionRef.current,\n          kind: 'js-end-stage',\n          jsEndDetail: performance.now(),\n        };\n        return true;\n      },\n      onRAF: () => {\n        if (\n          lastInteractionRef.current.kind !== 'js-end-stage' &&\n          lastInteractionRef.current.kind !== 'raf-stage'\n        ) {\n          options?.onError?.(lastInteractionRef.current.interactionUUID);\n          invariantError('bad transition to raf');\n          lastInteractionRef.current = {\n            kind: 'uninitialized-stage',\n            interactionUUID: not_globally_unique_generateId(),\n            stageStart: Date.now(),\n            interactionType: kind,\n          };\n          return false;\n        }\n\n        lastInteractionRef.current = {\n          ...lastInteractionRef.current,\n          kind: 'raf-stage',\n          rafStart: performance.now(),\n        };\n\n        return true;\n      },\n      onTimeout: () => {\n        if (lastInteractionRef.current.kind !== 'raf-stage') {\n          options?.onError?.(lastInteractionRef.current.interactionUUID);\n          lastInteractionRef.current = {\n            kind: 'uninitialized-stage',\n            interactionUUID: not_globally_unique_generateId(),\n            stageStart: Date.now(),\n            interactionType: kind,\n          };\n          invariantError('raf->timeout');\n          return;\n        }\n        const now = Date.now();\n        const timeoutStage: TimeoutStage = Object.freeze({\n          ...lastInteractionRef.current,\n          kind: 'timeout-stage',\n          blockingTimeEnd: now,\n          commitEnd: performance.now(),\n        });\n\n        lastInteractionRef.current = {\n          kind: 'uninitialized-stage',\n          interactionUUID: not_globally_unique_generateId(),\n          stageStart: now,\n          interactionType: kind,\n        };\n        let completed = false;\n        const completeInteraction = (event: PerformanceEntryChannelEvent) => {\n          completed = true;\n\n          const latency =\n            event.kind === 'auto-complete-race'\n              ? event.detailedTiming.commitEnd -\n                event.detailedTiming.interactionStartDetail\n              : event.entry.latency;\n          const finalInteraction = {\n            detailedTiming: timeoutStage,\n            latency,\n            completedAt: Date.now(),\n            flushNeeded: true,\n          };\n\n          options?.onComplete?.(\n            timeoutStage.interactionUUID,\n            finalInteraction,\n            event,\n          );\n          const newTasks = tasks.filter(\n            (task) => task.interactionUUID !== timeoutStage.interactionUUID,\n          );\n          tasks = BoundedArray.fromArray(newTasks, MAX_INTERACTION_TASKS);\n\n          return finalInteraction;\n        };\n\n        const task = {\n          completeInteraction,\n          endDateTime: Date.now(),\n          startDateTime: timeoutStage.blockingTimeStart,\n          type: kind,\n          interactionUUID: timeoutStage.interactionUUID,\n        };\n        tasks.push(task);\n\n        if (!isPerformanceEventAvailable()) {\n          const newTasks = tasks.filter(\n            (task) => task.interactionUUID !== timeoutStage.interactionUUID,\n          );\n          tasks = BoundedArray.fromArray(newTasks, MAX_INTERACTION_TASKS);\n          completeInteraction({\n            kind: 'auto-complete-race',\n            // redundant\n            detailedTiming: timeoutStage,\n            interactionUUID: timeoutStage.interactionUUID,\n          });\n        } else {\n          setTimeout(() => {\n            if (completed) {\n              return;\n            }\n            completeInteraction({\n              kind: 'auto-complete-race',\n              // redundant\n              detailedTiming: timeoutStage,\n              interactionUUID: timeoutStage.interactionUUID,\n            });\n            const newTasks = tasks.filter(\n              (task) => task.interactionUUID !== timeoutStage.interactionUUID,\n            );\n            tasks = BoundedArray.fromArray(newTasks, MAX_INTERACTION_TASKS);\n            // this means the max frame presentation delta we can observe is 300ms, but this should catch >99% of cases, the trade off is to not accidentally miss slowdowns if the user quickly clicked something else while this race was happening\n          }, 1000);\n        }\n      },\n    });\n  };\n\n  const onKeyPress = (e: { target: Element }) => {\n    const id = not_globally_unique_generateId();\n    onLastJS(e, id, () => id !== instrumentationIdInControl);\n  };\n\n  if (kind === 'keyboard') {\n    // oxlint-disable-next-line typescript/no-explicit-any\n    document.addEventListener('keypress', onKeyPress as any);\n  }\n\n  return () => {\n    document.removeEventListener(\n      getEvent({ phase: 'start' }),\n      // oxlint-disable-next-line typescript/no-explicit-any\n      onInteractionStart as any,\n      {\n        capture: true,\n      },\n    );\n    // oxlint-disable-next-line typescript/no-explicit-any\n    document.removeEventListener('keypress', onKeyPress as any);\n  };\n};\n\nconst getHostFromFiber = (fiber: Fiber) => {\n  return traverseFiber(fiber, (node) => {\n    // shouldn't be too slow\n    if (isHostFiber(node)) {\n      return true;\n    }\n  })?.stateNode;\n};\n\nconst isPerformanceEventAvailable = () => {\n  return 'PerformanceEventTiming' in globalThis;\n};\n\nexport const listenForRenders = (\n  fiberRenders: InteractionStartStage['fiberRenders'],\n) => {\n  const listener = (fiber: Fiber) => {\n    const displayName = getDisplayName(fiber.type);\n    if (!displayName) {\n      return;\n    }\n    const existing = fiberRenders[displayName];\n    if (!existing) {\n      const parents = new Set<string>();\n      const res = fiber.return && getParentCompositeFiber(fiber.return);\n      const parentCompositeName = res && getDisplayName(res[0]);\n      if (parentCompositeName) {\n        parents.add(parentCompositeName);\n      }\n      const { selfTime, totalTime } = getTimings(fiber);\n\n      const newChanges = collectInspectorDataWithoutCounts(fiber);\n      const emptySection: SectionData = {\n        current: [],\n        changes: new Set<string | number>(),\n        changesCounts: new Map<string | number, number>(),\n      };\n      const changes = {\n        fiberProps: newChanges.fiberProps || emptySection,\n        fiberState: newChanges.fiberState || emptySection,\n        fiberContext: newChanges.fiberContext || emptySection,\n      };\n      fiberRenders[displayName] = {\n        renderCount: 1,\n        hasMemoCache: hasMemoCache(fiber),\n        wasFiberRenderMount: wasFiberRenderMount(fiber),\n        parents: parents,\n        selfTime,\n        totalTime,\n        nodeInfo: [\n          {\n            element: getHostFromFiber(fiber),\n            name: getDisplayName(fiber.type) ?? 'Unknown',\n            selfTime: getTimings(fiber).selfTime,\n          },\n        ],\n        changes,\n      };\n\n      return;\n    }\n    const parentType = getParentCompositeFiber(fiber)?.[0]?.type;\n    if (parentType) {\n      const res = fiber.return && getParentCompositeFiber(fiber.return);\n      const parentCompositeName = res && getDisplayName(res[0]);\n      if (parentCompositeName) {\n        existing.parents.add(parentCompositeName);\n      }\n    }\n    const { selfTime, totalTime } = getTimings(fiber);\n\n    const newChanges = collectInspectorDataWithoutCounts(fiber);\n\n    if (!newChanges) return;\n\n    const emptySection: SectionData = {\n      current: [],\n      changes: new Set<string | number>(),\n      changesCounts: new Map<string | number, number>(),\n    };\n\n    existing.wasFiberRenderMount =\n      existing.wasFiberRenderMount || wasFiberRenderMount(fiber);\n    existing.hasMemoCache = existing.hasMemoCache || hasMemoCache(fiber);\n    existing.changes = {\n      fiberProps: mergeSectionData(\n        existing.changes?.fiberProps || emptySection,\n        newChanges.fiberProps || emptySection,\n      ),\n      fiberState: mergeSectionData(\n        existing.changes?.fiberState || emptySection,\n        newChanges.fiberState || emptySection,\n      ),\n      fiberContext: mergeSectionData(\n        existing.changes?.fiberContext || emptySection,\n        newChanges.fiberContext || emptySection,\n      ),\n    };\n\n    existing.renderCount += 1;\n    existing.selfTime += selfTime;\n    existing.totalTime += totalTime;\n    existing.nodeInfo.push({\n      element: getHostFromFiber(fiber),\n      name: getDisplayName(fiber.type) ?? 'Unknown',\n      selfTime: getTimings(fiber).selfTime,\n    });\n  };\n  Store.interactionListeningForRenders = listener;\n\n  return () => {\n    if (Store.interactionListeningForRenders === listener) {\n      Store.interactionListeningForRenders = null;\n    }\n  };\n};\n\nconst mergeSectionData = (\n  existing: SectionData,\n  newData: SectionData,\n): SectionData => {\n  const mergedSection: SectionData = {\n    current: [...existing.current],\n    changes: new Set<string | number>(),\n    changesCounts: new Map<string | number, number>(),\n  };\n\n  for (const value of newData.current) {\n    if (!mergedSection.current.some((item) => item.name === value.name)) {\n      mergedSection.current.push(value);\n    }\n  }\n\n  for (const change of newData.changes) {\n    if (typeof change === 'string' || typeof change === 'number') {\n      mergedSection.changes.add(change);\n      const existingCount = existing.changesCounts.get(change) || 0;\n      const newCount = newData.changesCounts.get(change) || 0;\n      mergedSection.changesCounts.set(change, existingCount + newCount);\n    }\n  }\n\n  return mergedSection;\n};\n\nconst wasFiberRenderMount = (fiber: Fiber) => {\n  if (!fiber.alternate) {\n    return true;\n  }\n\n  const prevFiber = fiber.alternate;\n\n  const wasMounted =\n    prevFiber &&\n    prevFiber.memoizedState != null &&\n    prevFiber.memoizedState.element != null &&\n    prevFiber.memoizedState.isDehydrated !== true;\n\n  const isMounted =\n    fiber.memoizedState != null &&\n    fiber.memoizedState.element != null &&\n    fiber.memoizedState.isDehydrated !== true;\n\n  return !wasMounted && isMounted;\n};\n"
  },
  {
    "path": "packages/scan/src/core/notifications/types.ts",
    "content": "export interface PerformanceInteractionEntry extends PerformanceEntry {\n  interactionId: string;\n  target: Element;\n  name: string;\n  duration: number;\n  startTime: number;\n  processingStart: number;\n  processingEnd: number;\n  entryType: string;\n}\nexport interface PerformanceInteraction {\n  id: string;\n  latency: number;\n  entries: Array<PerformanceInteractionEntry>;\n  target: Element | null;\n  type: \"pointer\" | \"keyboard\";\n  startTime: number;\n  endTime: number;\n  processingStart: number;\n  processingEnd: number;\n  duration: number;\n  inputDelay: number;\n  processingDuration: number;\n  presentationDelay: number;\n  timestamp: number;\n  timeSinceTabInactive: number | \"never-hidden\";\n  visibilityState: DocumentVisibilityState;\n  timeOrigin: number;\n  referrer: string;\n  detailedTiming?: {\n    jsHandlersTime: number; // pointerup -> click\n    prePaintTime: number; // click -> RAF\n    paintTime: number; // RAF -> setTimeout\n    compositorTime: number; // remaining duration\n  };\n}\n"
  },
  {
    "path": "packages/scan/src/core/utils.ts",
    "content": "// @ts-nocheck\nimport { type Fiber, getType } from 'bippy';\nimport { ReactScanInternals } from '~core/index';\nimport type { AggregatedChange, AggregatedRender, Render } from './instrumentation';\nimport { IS_CLIENT } from '~web/utils/constants';\n\nexport const aggregateChanges = (\n  changes: Array<Change>,\n  prevAggregatedChange?: AggregatedChange,\n) => {\n  const newChange = {\n    type: prevAggregatedChange?.type ?? 0,\n    unstable: prevAggregatedChange?.unstable ?? false,\n  };\n  for (const change of changes) {\n    newChange.type |= change.type;\n    newChange.unstable = newChange.unstable || (change.unstable ?? false);\n  }\n\n  return newChange;\n};\n\nexport const aggregateRender = (\n  newRender: Render,\n  prevAggregated: AggregatedRender,\n) => {\n  prevAggregated.changes = aggregateChanges(\n    newRender.changes,\n    prevAggregated.changes,\n  );\n  prevAggregated.aggregatedCount += 1;\n  prevAggregated.didCommit = prevAggregated.didCommit || newRender.didCommit;\n  prevAggregated.forget = prevAggregated.forget || newRender.forget;\n  prevAggregated.fps = prevAggregated.fps + newRender.fps;\n  prevAggregated.phase |= newRender.phase;\n  prevAggregated.time = (prevAggregated.time ?? 0) + (newRender.time ?? 0);\n\n  prevAggregated.unnecessary =\n    prevAggregated.unnecessary || newRender.unnecessary;\n};\n\nfunction descending(a: number, b: number): number {\n  return b - a;\n}\n\ninterface ComponentData {\n  name: string;\n  forget: boolean;\n  time: number;\n}\n\nfunction getComponentGroupNames(group: ComponentData[]): string {\n  let result = group[0].name;\n\n  const len = group.length;\n  const max = Math.min(4, len);\n\n  for (let i = 1; i < max; i++) {\n    result += `, ${group[i].name}`;\n  }\n\n  return result;\n}\n\nfunction getComponentGroupTotalTime(group: ComponentData[]): number {\n  let result = group[0].time;\n\n  for (let i = 1, len = group.length; i < len; i++) {\n    result += group[i].time;\n  }\n\n  return result;\n}\n\nfunction componentGroupHasForget(group: ComponentData[]): boolean {\n  for (let i = 0, len = group.length; i < len; i++) {\n    if (group[i].forget) {\n      return true;\n    }\n  }\n  return false;\n}\n\nexport const getLabelText = (\n  groupedAggregatedRenders: Array<AggregatedRender>,\n) => {\n  let labelText = '';\n\n  const componentsByCount = new Map<\n    number,\n    Array<{ name: string; forget: boolean; time: number }>\n  >();\n\n  for (const aggregatedRender of groupedAggregatedRenders) {\n    const { forget, time, aggregatedCount, name } = aggregatedRender;\n    if (!componentsByCount.has(aggregatedCount)) {\n      componentsByCount.set(aggregatedCount, []);\n    }\n    const components = componentsByCount.get(aggregatedCount);\n    if (components) {\n      components.push({ name, forget, time: time ?? 0 });\n    }\n  }\n\n  const sortedCounts = Array.from(componentsByCount.keys()).sort(descending);\n\n  const parts: Array<string> = [];\n  let cumulativeTime = 0;\n  for (const count of sortedCounts) {\n    const componentGroup = componentsByCount.get(count);\n    if (!componentGroup) continue;\n\n    let text = getComponentGroupNames(componentGroup);\n    const totalTime = getComponentGroupTotalTime(componentGroup);\n    const hasForget = componentGroupHasForget(componentGroup);\n\n    cumulativeTime += totalTime;\n\n    if (componentGroup.length > 4) {\n      text += '…';\n    }\n\n    if (count > 1) {\n      text += ` × ${count}`;\n    }\n\n    if (hasForget) {\n      text = `✨${text}`;\n    }\n\n    parts.push(text);\n  }\n\n  labelText = parts.join(', ');\n\n  if (!labelText.length) return null;\n\n  if (labelText.length > 40) {\n    labelText = `${labelText.slice(0, 40)}…`;\n  }\n\n  if (cumulativeTime >= 0.01) {\n    labelText += ` (${Number(cumulativeTime.toFixed(2))}ms)`;\n  }\n\n  return labelText;\n};\n\nexport const updateFiberRenderData = (fiber: Fiber, renders: Array<Render>) => {\n  ReactScanInternals.options.value.onRender?.(fiber, renders);\n  const type = getType(fiber.type) || fiber.type;\n  if (type && (typeof type === 'function' || typeof type === 'object')) {\n    const renderData = (type.renderData || {\n      count: 0,\n      time: 0,\n      renders: [],\n    }) as RenderData;\n    const firstRender = renders[0];\n    renderData.count += firstRender.count;\n    renderData.time += firstRender.time ?? 0;\n    renderData.renders.push(firstRender);\n    type.renderData = renderData;\n  }\n};\n\nexport interface RenderData {\n  count: number;\n  time: number;\n  renders: Array<Render>;\n  displayName: string | null;\n  type: unknown;\n  changes?: Array<RenderChange>;\n}\n\nexport function isEqual(a: unknown, b: unknown): boolean {\n  return a === b || (a !== a && b !== b);\n}\n\nexport const not_globally_unique_generateId = () => {\n  if (!IS_CLIENT) {\n    return '0';\n  }\n\n  // @ts-expect-error\n  if (window.reactScanIdCounter === undefined) {\n    // @ts-expect-error\n    window.reactScanIdCounter = 0;\n  }\n  // @ts-expect-error\n  return `${++window.reactScanIdCounter}`;\n};\n\nexport const playNotificationSound = (audioContext: AudioContext) => {\n  const oscillator = audioContext.createOscillator();\n  const gainNode = audioContext.createGain();\n\n  oscillator.connect(gainNode);\n  gainNode.connect(audioContext.destination);\n\n  const options = {\n    type: 'sine' as OscillatorType,\n    freq: [\n      392,\n      //  523.25,\n      600,\n      //  659.25\n    ],\n    duration: 0.3,\n    gain: 0.12,\n  };\n\n  const frequencies = options.freq;\n  const timePerNote = options.duration / frequencies.length;\n\n  frequencies.forEach((freq, i) => {\n    oscillator.frequency.setValueAtTime(\n      freq,\n      audioContext.currentTime + i * timePerNote,\n    );\n  });\n\n  oscillator.type = options.type;\n  gainNode.gain.setValueAtTime(options.gain, audioContext.currentTime);\n\n  gainNode.gain.setTargetAtTime(\n    0,\n    audioContext.currentTime + options.duration * 0.7,\n    0.05,\n  );\n\n  oscillator.start();\n  oscillator.stop(audioContext.currentTime + options.duration);\n};\n"
  },
  {
    "path": "packages/scan/src/index.ts",
    "content": "import './polyfills';\n// Bippy has a side-effect that installs the hook.\nimport 'bippy';\n\nexport * from './core/index';\n"
  },
  {
    "path": "packages/scan/src/install-hook.ts",
    "content": "export { getRDTHook as init } from 'bippy';\n"
  },
  {
    "path": "packages/scan/src/monitoring/next.ts",
    "content": ""
  },
  {
    "path": "packages/scan/src/new-outlines/canvas.ts",
    "content": "import type { ActiveOutline, OutlineData } from './types';\n\nexport const OUTLINE_ARRAY_SIZE = 7;\nconst MONO_FONT =\n  'Menlo,Consolas,Monaco,Liberation Mono,Lucida Console,monospace';\n\nconst INTERPOLATION_SPEED = 0.2;\nconst SNAP_THRESHOLD = 0.5;\nconst lerp = (start: number, end: number) => {\n  const delta = end - start;\n  if (Math.abs(delta) < SNAP_THRESHOLD) return end;\n  return start + delta * INTERPOLATION_SPEED;\n};\n\nconst MAX_PARTS_LENGTH = 4;\nconst MAX_LABEL_LENGTH = 40;\nconst TOTAL_FRAMES = 45;\n\nconst PRIMARY_COLOR = '115,97,230';\n\nfunction sortEntry(prev: [number, string[]], next: [number, string[]]): number {\n  return next[0] - prev[0];\n}\n\nfunction getSortedEntries(\n  countByNames: Map<number, string[]>,\n): [number, string[]][] {\n  const entries = [...countByNames.entries()];\n  return entries.sort(sortEntry);\n}\n\nfunction getLabelTextPart([count, names]: [number, string[]]): string {\n  let part = `${names.slice(0, MAX_PARTS_LENGTH).join(', ')} ×${count}`;\n  if (part.length > MAX_LABEL_LENGTH) {\n    part = `${part.slice(0, MAX_LABEL_LENGTH)}…`;\n  }\n  return part;\n}\n\nexport const getLabelText = (outlines: ActiveOutline[]): string => {\n  const nameByCount = new Map<string, number>();\n  for (const { name, count } of outlines) {\n    nameByCount.set(name, (nameByCount.get(name) || 0) + count);\n  }\n\n  const countByNames = new Map<number, string[]>();\n  for (const [name, count] of nameByCount) {\n    const names = countByNames.get(count);\n    if (names) {\n      names.push(name);\n    } else {\n      countByNames.set(count, [name]);\n    }\n  }\n\n  // TODO(Alexis): Optimize\n  const partsEntries = getSortedEntries(countByNames);\n  let labelText = getLabelTextPart(partsEntries[0]);\n  for (let i = 1, len = partsEntries.length; i < len; i++) {\n    labelText += ', ' + getLabelTextPart(partsEntries[i]);\n  }\n\n  if (labelText.length > MAX_LABEL_LENGTH) {\n    return `${labelText.slice(0, MAX_LABEL_LENGTH)}…`;\n  }\n\n  return labelText;\n};\n\nexport const getAreaFromOutlines = (outlines: ActiveOutline[]) => {\n  let area = 0;\n  for (const outline of outlines) {\n    area += outline.width * outline.height;\n  }\n  return area;\n};\n\nexport const updateOutlines = (\n  activeOutlines: Map<string, ActiveOutline>,\n  outlines: OutlineData[],\n) => {\n  for (const { id, name, count, x, y, width, height, didCommit } of outlines) {\n    const outline: ActiveOutline = {\n      id,\n      name,\n      count,\n      x,\n      y,\n      width,\n      height,\n      frame: 0,\n      targetX: x,\n      targetY: y,\n      targetWidth: width,\n      targetHeight: height,\n      didCommit,\n    };\n    const key = String(outline.id);\n\n    const existingOutline = activeOutlines.get(key);\n    if (existingOutline) {\n      existingOutline.count++;\n      existingOutline.frame = 0;\n      existingOutline.targetX = x;\n      existingOutline.targetY = y;\n      existingOutline.targetWidth = width;\n      existingOutline.targetHeight = height;\n      existingOutline.didCommit = didCommit;\n    } else {\n      activeOutlines.set(key, outline);\n    }\n  }\n};\n\nexport const updateScroll = (\n  activeOutlines: Map<string, ActiveOutline>,\n  deltaX: number,\n  deltaY: number,\n) => {\n  for (const outline of activeOutlines.values()) {\n    const newX = outline.x - deltaX;\n    const newY = outline.y - deltaY;\n    outline.targetX = newX;\n    outline.targetY = newY;\n  }\n};\n\nexport const initCanvas = (\n  canvas: HTMLCanvasElement | OffscreenCanvas,\n  dpr: number,\n) => {\n  const ctx = canvas.getContext('2d', { alpha: true }) as\n    | CanvasRenderingContext2D\n    | OffscreenCanvasRenderingContext2D;\n  if (ctx) {\n    ctx.scale(dpr, dpr);\n  }\n  return ctx;\n};\n\nexport const drawCanvas = (\n  ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n  canvas: HTMLCanvasElement | OffscreenCanvas,\n  dpr: number,\n  activeOutlines: Map<string, ActiveOutline>,\n) => {\n  ctx.clearRect(0, 0, canvas.width / dpr, canvas.height / dpr);\n\n  const groupedOutlinesMap = new Map<string, ActiveOutline[]>();\n  const rectMap = new Map<\n    string,\n    {\n      x: number;\n      y: number;\n      width: number;\n      height: number;\n      alpha: number;\n    }\n  >();\n\n  for (const outline of activeOutlines.values()) {\n    const {\n      x,\n      y,\n      width,\n      height,\n      targetX,\n      targetY,\n      targetWidth,\n      targetHeight,\n      frame,\n    } = outline;\n    if (targetX !== x) {\n      outline.x = lerp(x, targetX);\n    }\n    if (targetY !== y) {\n      outline.y = lerp(y, targetY);\n    }\n\n    if (targetWidth !== width) {\n      outline.width = lerp(width, targetWidth);\n    }\n    if (targetHeight !== height) {\n      outline.height = lerp(height, targetHeight);\n    }\n\n    const labelKey = `${targetX ?? x},${targetY ?? y}`;\n    const rectKey = `${labelKey},${targetWidth ?? width},${targetHeight ?? height}`;\n\n    const outlines = groupedOutlinesMap.get(labelKey);\n    if (outlines) {\n      outlines.push(outline);\n    } else {\n      groupedOutlinesMap.set(labelKey, [outline]);\n    }\n\n    const alpha = 1 - frame / TOTAL_FRAMES;\n    outline.frame++;\n\n    const rect = rectMap.get(rectKey) || {\n      x,\n      y,\n      width,\n      height,\n      alpha,\n    };\n    if (alpha > rect.alpha) {\n      rect.alpha = alpha;\n    }\n    rectMap.set(rectKey, rect);\n  }\n\n  for (const { x, y, width, height, alpha } of rectMap.values()) {\n    ctx.strokeStyle = `rgba(${PRIMARY_COLOR},${alpha})`;\n    ctx.lineWidth = 1;\n\n    // Offset by 0.5px for crisp 1px strokes on pixel boundaries\n    const rx = Math.round(x) + 0.5;\n    const ry = Math.round(y) + 0.5;\n    const rw = Math.round(width);\n    const rh = Math.round(height);\n\n    ctx.beginPath();\n    ctx.rect(rx, ry, rw, rh);\n    ctx.stroke();\n    ctx.fillStyle = `rgba(${PRIMARY_COLOR},${alpha * 0.1})`;\n    ctx.fill();\n  }\n\n  ctx.font = `11px ${MONO_FONT}`;\n\n  const labelMap = new Map<\n    string,\n    {\n      text: string;\n      width: number;\n      height: number;\n      alpha: number;\n      x: number;\n      y: number;\n      outlines: ActiveOutline[];\n    }\n  >();\n\n  ctx.textRendering = 'optimizeSpeed';\n\n  // TODO(Alexis): optimizable?\n  for (const outlines of groupedOutlinesMap.values()) {\n    const first = outlines[0];\n    const { x, y, frame } = first;\n    const alpha = 1 - frame / TOTAL_FRAMES;\n    const text = getLabelText(outlines);\n    const { width } = ctx.measureText(text);\n    const height = 11;\n    labelMap.set(`${x},${y},${width},${text}`, {\n      text,\n      width,\n      height,\n      alpha,\n      x,\n      y,\n      outlines,\n    });\n\n    let labelY: number = y - height - 4;\n\n    if (labelY < 0) {\n      labelY = 0;\n    }\n\n    if (frame > TOTAL_FRAMES) {\n      for (const outline of outlines) {\n        activeOutlines.delete(String(outline.id));\n      }\n    }\n  }\n\n  // TODO(Alexis): optimize\n  const sortedLabels = Array.from(labelMap.entries()).sort(\n    ([_, a], [__, b]) => {\n      return getAreaFromOutlines(b.outlines) - getAreaFromOutlines(a.outlines);\n    },\n  );\n\n  for (const [labelKey, label] of sortedLabels) {\n    if (!labelMap.has(labelKey)) continue;\n\n    for (const [otherKey, otherLabel] of labelMap.entries()) {\n      if (labelKey === otherKey) continue;\n\n      const { x, y, width, height } = label;\n      const {\n        x: otherX,\n        y: otherY,\n        width: otherWidth,\n        height: otherHeight,\n      } = otherLabel;\n\n      if (\n        x + width > otherX &&\n        otherX + otherWidth > x &&\n        y + height > otherY &&\n        otherY + otherHeight > y\n      ) {\n        label.text = getLabelText(label.outlines.concat(otherLabel.outlines));\n        label.width = ctx.measureText(label.text).width;\n        labelMap.delete(otherKey);\n      }\n    }\n  }\n\n  for (const label of labelMap.values()) {\n    const { x, y, alpha, width, height, text } = label;\n\n    let labelY = y - height - 4;\n\n    if (labelY < 0) {\n      labelY = 0;\n    }\n\n    ctx.fillStyle = `rgba(${PRIMARY_COLOR},${alpha})`;\n    ctx.fillRect(x, labelY, width + 4, height + 4);\n\n    ctx.fillStyle = `rgba(255,255,255,${alpha})`;\n    ctx.fillText(text, x + 2, labelY + height);\n  }\n\n  return activeOutlines.size > 0;\n};\n"
  },
  {
    "path": "packages/scan/src/new-outlines/index.ts",
    "content": "import {\n  type Fiber,\n  didFiberCommit,\n  getDisplayName,\n  getFiberId,\n  getNearestHostFibers,\n  getTimings,\n  getType,\n  isCompositeFiber,\n} from 'bippy';\nimport {\n  Change,\n  ContextChange,\n  PropsChange,\n  ReactScanInternals,\n  Store,\n  ignoredProps,\n} from '~core/index';\nimport {\n  ChangeReason,\n  createInstrumentation,\n  getContextChanges,\n  getStateChanges,\n  OldRenderData,\n} from '~core/instrumentation';\nimport { log, logIntro } from '~web/utils/log';\nimport { inspectorUpdateSignal } from '~web/views/inspector/states';\nimport {\n  OUTLINE_ARRAY_SIZE,\n  drawCanvas,\n  initCanvas,\n  updateOutlines,\n  updateScroll,\n} from './canvas';\nimport type { ActiveOutline, BlueprintOutline, OutlineData } from './types';\nimport { getChangedPropsDetailed } from '~web/views/inspector/utils';\n\n// The worker code will be replaced at build time\nconst workerCode = '__WORKER_CODE__';\n\nlet worker: Worker | null = null;\nlet canvas: HTMLCanvasElement | null = null;\nlet ctx: CanvasRenderingContext2D | null = null;\nlet dpr = 1;\nlet animationFrameId: number | null = null;\nconst activeOutlines = new Map<string, ActiveOutline>();\n\nconst blueprintMap = new Map<Fiber, BlueprintOutline>();\nconst blueprintMapKeys = new Set<Fiber>();\n\nexport const outlineFiber = (fiber: Fiber) => {\n  if (!isCompositeFiber(fiber)) return;\n  const name =\n    typeof fiber.type === 'string' ? fiber.type : getDisplayName(fiber);\n  if (!name) return;\n  const blueprint = blueprintMap.get(fiber);\n  const nearestFibers = getNearestHostFibers(fiber);\n  const didCommit = didFiberCommit(fiber);\n\n  if (!blueprint) {\n    blueprintMap.set(fiber, {\n      name,\n      count: 1,\n      elements: nearestFibers.map((fiber) => fiber.stateNode),\n      didCommit: didCommit ? 1 : 0,\n    });\n    blueprintMapKeys.add(fiber);\n  } else {\n    blueprint.count++;\n  }\n};\n\nconst mergeRects = (rects: DOMRect[]) => {\n  const firstRect = rects[0];\n  if (rects.length === 1) return firstRect;\n\n  let minX: number | undefined;\n  let minY: number | undefined;\n  let maxX: number | undefined;\n  let maxY: number | undefined;\n\n  for (let i = 0, len = rects.length; i < len; i++) {\n    const rect = rects[i];\n    minX = minX == null ? rect.x : Math.min(minX, rect.x);\n    minY = minY == null ? rect.y : Math.min(minY, rect.y);\n    maxX =\n      maxX == null ? rect.x + rect.width : Math.max(maxX, rect.x + rect.width);\n    maxY =\n      maxY == null\n        ? rect.y + rect.height\n        : Math.max(maxY, rect.y + rect.height);\n  }\n\n  if (minX == null || minY == null || maxX == null || maxY == null) {\n    return rects[0];\n  }\n\n  return new DOMRect(minX, minY, maxX - minX, maxY - minY);\n};\n\ninterface IntersectionState {\n  resolveNext: ((value: IntersectionObserverEntry[]) => void) | null;\n  seenElements: Set<Element>;\n  uniqueElements: Set<Element>;\n  done: boolean;\n}\n\nfunction onIntersect(\n  this: IntersectionState,\n  entries: IntersectionObserverEntry[],\n  observer: IntersectionObserver,\n) {\n  const newEntries: IntersectionObserverEntry[] = [];\n\n  for (const entry of entries) {\n    const element = entry.target;\n    if (!this.seenElements.has(element)) {\n      this.seenElements.add(element);\n      newEntries.push(entry);\n    }\n  }\n\n  if (newEntries.length > 0 && this.resolveNext) {\n    this.resolveNext(newEntries);\n    this.resolveNext = null;\n  }\n\n  if (this.seenElements.size === this.uniqueElements.size) {\n    observer.disconnect();\n    this.done = true;\n    if (this.resolveNext) {\n      this.resolveNext([]);\n    }\n  }\n}\n\nexport const getBatchedRectMap = async function* (\n  elements: Element[],\n): AsyncGenerator<IntersectionObserverEntry[], void, unknown> {\n  const state: IntersectionState = {\n    uniqueElements: new Set(elements),\n    seenElements: new Set(),\n    resolveNext: null,\n    done: false,\n  };\n  const observer = new IntersectionObserver(onIntersect.bind(state));\n\n  for (const element of state.uniqueElements) {\n    observer.observe(element);\n  }\n\n  while (!state.done) {\n    const entries = await new Promise<IntersectionObserverEntry[]>(\n      (resolve) => {\n        state.resolveNext = resolve;\n      },\n    );\n    if (entries.length > 0) {\n      yield entries;\n    }\n  }\n};\n\nconst SupportedArrayBuffer =\n  typeof SharedArrayBuffer !== 'undefined' ? SharedArrayBuffer : ArrayBuffer;\n\nexport const flushOutlines = async () => {\n  const elements: Element[] = [];\n\n  for (const fiber of blueprintMapKeys) {\n    const blueprint = blueprintMap.get(fiber);\n    if (!blueprint) continue;\n    for (let i = 0; i < blueprint.elements.length; i++) {\n      if (!(blueprint.elements[i] instanceof Element)) {\n        // TODO: filter this at the root\n        continue;\n      }\n      elements.push(blueprint.elements[i]);\n    }\n  }\n\n  const rectsMap = new Map<Element, DOMRect>();\n\n  // TODO(Alexis): too complex, needs breakdown\n  for await (const entries of getBatchedRectMap(elements)) {\n    for (const entry of entries) {\n      const element = entry.target;\n      const rect = entry.intersectionRect;\n      if (entry.isIntersecting && rect.width && rect.height) {\n        rectsMap.set(element, rect);\n      }\n    }\n\n    const blueprints: BlueprintOutline[] = [];\n    const blueprintRects: DOMRect[] = [];\n    const blueprintIds: number[] = [];\n\n    for (const fiber of blueprintMapKeys) {\n      const blueprint = blueprintMap.get(fiber);\n      if (!blueprint) continue;\n\n      const rects: DOMRect[] = [];\n      for (let i = 0; i < blueprint.elements.length; i++) {\n        const element = blueprint.elements[i];\n        const rect = rectsMap.get(element);\n        if (!rect) continue;\n        rects.push(rect);\n      }\n\n      if (!rects.length) continue;\n\n      blueprints.push(blueprint);\n      blueprintRects.push(mergeRects(rects));\n      blueprintIds.push(getFiberId(fiber));\n    }\n\n    if (blueprints.length > 0) {\n      const arrayBuffer = new SupportedArrayBuffer(\n        blueprints.length * OUTLINE_ARRAY_SIZE * 4,\n      );\n      const sharedView = new Float32Array(arrayBuffer);\n      const blueprintNames = new Array(blueprints.length);\n      let outlineData: OutlineData[] | undefined;\n\n      for (let i = 0, len = blueprints.length; i < len; i++) {\n        const blueprint = blueprints[i];\n        const id = blueprintIds[i];\n        const { x, y, width, height } = blueprintRects[i];\n        const { count, name, didCommit } = blueprint;\n\n        if (worker) {\n          const scaledIndex = i * OUTLINE_ARRAY_SIZE;\n          sharedView[scaledIndex] = id;\n          sharedView[scaledIndex + 1] = count;\n          sharedView[scaledIndex + 2] = x;\n          sharedView[scaledIndex + 3] = y;\n          sharedView[scaledIndex + 4] = width;\n          sharedView[scaledIndex + 5] = height;\n          sharedView[scaledIndex + 6] = didCommit;\n          blueprintNames[i] = name;\n        } else {\n          outlineData ||= new Array(blueprints.length);\n          outlineData[i] = {\n            id,\n            name,\n            count,\n            x,\n            y,\n            width,\n            height,\n            didCommit: didCommit as 0 | 1,\n          };\n        }\n      }\n\n      if (worker) {\n        worker.postMessage({\n          type: 'draw-outlines',\n          data: arrayBuffer,\n          names: blueprintNames,\n        });\n      } else if (canvas && ctx && outlineData) {\n        updateOutlines(activeOutlines, outlineData);\n        if (!animationFrameId) {\n          animationFrameId = requestAnimationFrame(draw);\n        }\n      }\n    }\n  }\n\n  for (const fiber of blueprintMapKeys) {\n    blueprintMap.delete(fiber);\n    blueprintMapKeys.delete(fiber);\n  }\n};\n\nconst draw = () => {\n  if (!ctx || !canvas) return;\n\n  const shouldContinue = drawCanvas(ctx, canvas, dpr, activeOutlines);\n\n  if (shouldContinue) {\n    animationFrameId = requestAnimationFrame(draw);\n  } else {\n    animationFrameId = null;\n  }\n};\n\nconst IS_OFFSCREEN_CANVAS_WORKER_SUPPORTED =\n  typeof OffscreenCanvas !== 'undefined' && typeof Worker !== 'undefined';\n\nconst getDpr = () => {\n  return Math.min(window.devicePixelRatio || 1, 2);\n};\n\nexport const getCanvasEl = () => {\n  cleanup();\n  const host = document.createElement('div');\n  host.setAttribute('data-react-scan', 'true');\n  const shadowRoot = host.attachShadow({ mode: 'open' });\n\n  const canvasEl = document.createElement('canvas');\n  canvasEl.style.position = 'fixed';\n  canvasEl.style.top = '0';\n  canvasEl.style.left = '0';\n  canvasEl.style.pointerEvents = 'none';\n  canvasEl.style.zIndex = '2147483646';\n  canvasEl.setAttribute('aria-hidden', 'true');\n  shadowRoot.appendChild(canvasEl);\n\n  if (!canvasEl) return null;\n\n  dpr = getDpr();\n  canvas = canvasEl;\n\n  const { innerWidth, innerHeight } = window;\n  canvasEl.style.width = `${innerWidth}px`;\n  canvasEl.style.height = `${innerHeight}px`;\n  const width = innerWidth * dpr;\n  const height = innerHeight * dpr;\n  canvasEl.width = width;\n  canvasEl.height = height;\n\n  if (\n    IS_OFFSCREEN_CANVAS_WORKER_SUPPORTED &&\n    !window.__REACT_SCAN_EXTENSION__\n  ) {\n    try {\n      worker = new Worker(\n        URL.createObjectURL(\n          new Blob([workerCode], { type: 'application/javascript' }),\n        ),\n      );\n\n      const offscreenCanvas = canvasEl.transferControlToOffscreen();\n      worker?.postMessage(\n        {\n          type: 'init',\n          canvas: offscreenCanvas,\n          width: canvasEl.width,\n          height: canvasEl.height,\n          dpr,\n        },\n        [offscreenCanvas],\n      );\n    } catch (e) {\n      // oxlint-disable-next-line no-console\n      console.warn('Failed to initialize OffscreenCanvas worker:', e);\n    }\n  }\n\n  if (!worker) {\n    ctx = initCanvas(canvasEl, dpr) as CanvasRenderingContext2D;\n  }\n\n  let isResizeScheduled = false;\n  window.addEventListener('resize', () => {\n    if (!isResizeScheduled) {\n      isResizeScheduled = true;\n      // TODO(Alexis): bindable\n      setTimeout(() => {\n        const width = window.innerWidth;\n        const height = window.innerHeight;\n        dpr = getDpr();\n        canvasEl.style.width = `${width}px`;\n        canvasEl.style.height = `${height}px`;\n        if (worker) {\n          worker.postMessage({\n            type: 'resize',\n            width,\n            height,\n            dpr,\n          });\n        } else {\n          canvasEl.width = width * dpr;\n          canvasEl.height = height * dpr;\n          if (ctx) {\n            ctx.resetTransform();\n            ctx.scale(dpr, dpr);\n          }\n          draw();\n        }\n        isResizeScheduled = false;\n      });\n    }\n  });\n\n  let prevScrollX = window.scrollX;\n  let prevScrollY = window.scrollY;\n  let isScrollScheduled = false;\n\n  window.addEventListener('scroll', () => {\n    if (!isScrollScheduled) {\n      isScrollScheduled = true;\n      // TODO(Alexis): bindable\n      setTimeout(() => {\n        const { scrollX, scrollY } = window;\n        const deltaX = scrollX - prevScrollX;\n        const deltaY = scrollY - prevScrollY;\n        prevScrollX = scrollX;\n        prevScrollY = scrollY;\n        if (worker) {\n          worker.postMessage({\n            type: 'scroll',\n            deltaX,\n            deltaY,\n          });\n        } else {\n          requestAnimationFrame(\n            updateScroll.bind(null, activeOutlines, deltaX, deltaY),\n          );\n        }\n        isScrollScheduled = false;\n      }, 16 * 2);\n    }\n  });\n\n  setInterval(() => {\n    if (blueprintMapKeys.size) {\n      requestAnimationFrame(flushOutlines);\n    }\n  }, 16 * 2);\n\n  shadowRoot.appendChild(canvasEl);\n  return host;\n};\n\nexport const hasStopped = () => {\n  return globalThis.__REACT_SCAN_STOP__;\n};\n\nexport const stop = () => {\n  globalThis.__REACT_SCAN_STOP__ = true;\n  cleanup();\n};\n\nexport const cleanup = () => {\n  const host = document.querySelector('[data-react-scan]');\n  if (host) {\n    host.remove();\n  }\n};\n\nconst reportRenderToListeners = (fiber: Fiber) => {\n  if (isCompositeFiber(fiber)) {\n    // report render has a non trivial cost because it calls Date.now(), so we want to avoid the computation if possible\n    if (\n      ReactScanInternals.options.value.showToolbar !== false &&\n      Store.inspectState.value.kind === 'focused'\n    ) {\n      const reportFiber = fiber;\n      const { selfTime } = getTimings(fiber);\n      const displayName = getDisplayName(fiber.type);\n      const fiberId = getFiberId(reportFiber);\n\n      const currentData = Store.reportData.get(fiberId);\n      const existingCount = currentData?.count ?? 0;\n      const existingTime = currentData?.time ?? 0;\n\n      const changes: Array<Change> = [];\n\n      // optimization, for now only track changes on inspected prop, cleanup later when changes is used in outline drawing\n      const listeners = Store.changesListeners.get(getFiberId(fiber));\n\n      if (listeners?.length) {\n        const propsChanges: Array<PropsChange> = getChangedPropsDetailed(\n          fiber,\n        ).map((change) => ({\n          type: ChangeReason.Props,\n          name: change.name,\n          value: change.value,\n          prevValue: change.prevValue,\n          unstable: false,\n        }));\n\n        const stateChanges = getStateChanges(fiber);\n\n        // context changes are incorrect, bippy needs to tell us the context dependencies that changed and provide those values every render\n        // currently, we say every context change, regardless of the render it happened, is a change. Which requires us to hack change tracking\n        // in the whats-changed toolbar component\n        const fiberContext = getContextChanges(fiber);\n        const contextChanges: Array<ContextChange> = fiberContext.map(\n          (info) => ({\n            name: info.name,\n            type: ChangeReason.Context,\n            value: info.value,\n            contextType: info.contextType,\n          }),\n        );\n\n        listeners.forEach((listener) => {\n          listener({\n            propsChanges,\n            stateChanges,\n            contextChanges,\n          });\n        });\n      }\n      const fiberData: OldRenderData = {\n        count: existingCount + 1,\n        time: existingTime + selfTime || 0,\n        renders: [],\n        displayName,\n        type: getType(fiber.type) || null,\n        changes,\n      };\n\n      Store.reportData.set(fiberId, fiberData);\n      needsReport = true;\n    }\n  }\n};\n\nlet needsReport = false;\nlet reportInterval: ReturnType<typeof setInterval>;\nexport const startReportInterval = () => {\n  clearInterval(reportInterval);\n  reportInterval = setInterval(() => {\n    if (needsReport) {\n      Store.lastReportTime.value = Date.now();\n      needsReport = false;\n    }\n  }, 50);\n};\n\nexport const isValidFiber = (fiber: Fiber) => {\n  if (ignoredProps.has(fiber.memoizedProps)) {\n    return false;\n  }\n\n  return true;\n};\nlet isInstrumentationInitialized = false;\n\nexport const initReactScanInstrumentation = (setupToolbar: () => void) => {\n  if (hasStopped()) return;\n  if (isInstrumentationInitialized) return;\n  isInstrumentationInitialized = true;\n  // todo: don't hardcode string getting weird ref error in iife when using process.env\n  let schedule: ReturnType<typeof requestAnimationFrame>;\n  let mounted = false;\n\n  const scheduleSetup = () => {\n    if (mounted) {\n      return;\n    }\n    if (schedule) {\n      cancelAnimationFrame(schedule);\n    }\n    schedule = requestAnimationFrame(() => {\n      mounted = true;\n      const host = getCanvasEl();\n      if (host) {\n        document.documentElement.appendChild(host);\n      }\n      setupToolbar();\n    }); // TODO(Alexis): perhaps a better timing\n  };\n\n  const instrumentation = createInstrumentation('react-scan-devtools-0.1.0', {\n    onCommitStart: () => {\n      ReactScanInternals.options.value.onCommitStart?.();\n    },\n    onActive: (() => {\n      let didActivate = false;\n      return () => {\n        if (hasStopped()) return;\n        if (didActivate) return;\n        didActivate = true;\n\n        scheduleSetup();\n        if (!window.__REACT_SCAN_EXTENSION__) {\n          globalThis.__REACT_SCAN__ = {\n            ReactScanInternals,\n          };\n        }\n        startReportInterval();\n        logIntro();\n      };\n    })(),\n    onError: () => {\n      // todo: ingest errors without accidentally collecting data about user\n    },\n    isValidFiber,\n    onRender: (fiber, renders) => {\n      if (isCompositeFiber(fiber)) {\n        Store.interactionListeningForRenders?.(fiber, renders);\n      }\n      const isOverlayPaused =\n        ReactScanInternals.instrumentation?.isPaused.value;\n      const isInspectorInactive =\n        Store.inspectState.value.kind === 'inspect-off' ||\n        Store.inspectState.value.kind === 'uninitialized';\n      const shouldFullyAbort = isOverlayPaused && isInspectorInactive;\n\n      if (shouldFullyAbort) {\n        return;\n      }\n      if (!isOverlayPaused) {\n        outlineFiber(fiber);\n      }\n      if (ReactScanInternals.options.value.log) {\n        // this can be expensive given enough re-renders\n        log(renders);\n      }\n\n      if (Store.inspectState.value.kind === 'focused') {\n        inspectorUpdateSignal.value = Date.now();\n      }\n      if (!isInspectorInactive) {\n        reportRenderToListeners(fiber);\n      }\n\n      ReactScanInternals.options.value.onRender?.(fiber, renders);\n    },\n    onCommitFinish: () => {\n      scheduleSetup();\n      ReactScanInternals.options.value.onCommitFinish?.();\n    },\n    onPostCommitFiberRoot() {\n      scheduleSetup();\n    },\n    trackChanges: false,\n  });\n  ReactScanInternals.instrumentation = instrumentation;\n};\n"
  },
  {
    "path": "packages/scan/src/new-outlines/offscreen-canvas.worker.ts",
    "content": "import { OUTLINE_ARRAY_SIZE, drawCanvas, initCanvas } from './canvas';\nimport type { ActiveOutline } from './types';\n\nlet canvas: OffscreenCanvas | null = null;\nlet ctx: OffscreenCanvasRenderingContext2D | null = null;\nlet dpr = 1;\n\nconst activeOutlines: Map<string, ActiveOutline> = new Map();\nlet animationFrameId: number | null = null;\n\nconst draw = () => {\n  if (!ctx || !canvas) return;\n\n  const shouldContinue = drawCanvas(ctx, canvas, dpr, activeOutlines);\n\n  if (shouldContinue) {\n    animationFrameId = requestAnimationFrame(draw);\n  } else {\n    animationFrameId = null;\n  }\n};\n\nself.onmessage = (event) => {\n  const { type } = event.data;\n\n  if (type === 'init') {\n    canvas = event.data.canvas;\n    dpr = event.data.dpr;\n\n    if (canvas) {\n      canvas.width = event.data.width;\n      canvas.height = event.data.height;\n      ctx = initCanvas(canvas, dpr) as OffscreenCanvasRenderingContext2D;\n    }\n  }\n\n  if (!canvas || !ctx) return;\n\n  if (type === 'resize') {\n    dpr = event.data.dpr;\n    canvas.width = event.data.width * dpr;\n    canvas.height = event.data.height * dpr;\n    ctx.resetTransform();\n    ctx.scale(dpr, dpr);\n    draw();\n\n    return;\n  }\n\n  if (type === 'draw-outlines') {\n    const { data, names } = event.data;\n\n    const sharedView = new Float32Array(data);\n    for (let i = 0; i < sharedView.length; i += OUTLINE_ARRAY_SIZE) {\n      const x = sharedView[i + 2];\n      const y = sharedView[i + 3];\n      const width = sharedView[i + 4];\n      const height = sharedView[i + 5];\n\n      const didCommit = sharedView[i + 6] as 0 | 1;\n      const outline = {\n        id: sharedView[i],\n        name: names[i / OUTLINE_ARRAY_SIZE],\n        count: sharedView[i + 1],\n        x,\n        y,\n        width,\n        height,\n        frame: 0,\n        targetX: x,\n        targetY: y,\n        targetWidth: width,\n        targetHeight: height,\n        didCommit,\n      };\n      const key = String(outline.id);\n\n      const existingOutline = activeOutlines.get(key);\n      if (existingOutline) {\n        existingOutline.count++;\n        existingOutline.frame = 0;\n        existingOutline.targetX = x;\n        existingOutline.targetY = y;\n        existingOutline.targetWidth = width;\n        existingOutline.targetHeight = height;\n        existingOutline.didCommit = didCommit;\n      } else {\n        activeOutlines.set(key, outline);\n      }\n    }\n\n    if (!animationFrameId) {\n      animationFrameId = requestAnimationFrame(draw);\n    }\n\n    return;\n  }\n\n  if (type === 'scroll') {\n    const { deltaX, deltaY } = event.data;\n    for (const outline of activeOutlines.values()) {\n      const newX = outline.x - deltaX;\n      const newY = outline.y - deltaY;\n      outline.targetX = newX;\n      outline.targetY = newY;\n    }\n  }\n};\n"
  },
  {
    "path": "packages/scan/src/new-outlines/types.ts",
    "content": "export interface OutlineData {\n  id: number;\n  name: string;\n  count: number;\n  x: number;\n  y: number;\n  width: number;\n  height: number;\n  didCommit: 0 | 1;\n}\n\nexport type InlineOutlineData = [\n  id: number,\n  count: number,\n  x: number,\n  y: number,\n  width: number,\n  height: number,\n  didCommit: 0 | 1,\n];\n\nexport interface ActiveOutline {\n  id: number;\n  name: string;\n  count: number;\n  x: number;\n  y: number;\n  width: number;\n  height: number;\n  targetX: number;\n  targetY: number;\n  targetWidth: number;\n  targetHeight: number;\n  frame: number;\n  didCommit: 1 | 0;\n}\n\nexport interface BlueprintOutline {\n  name: string;\n  count: number;\n  elements: Element[];\n  didCommit: 1 | 0;\n}\n\ndeclare global {\n  var __REACT_SCAN_STOP__: boolean;\n  var ReactScan: {\n    hasStopped: () => boolean;\n    stop: () => void;\n    cleanup: () => void;\n    init: () => void;\n    flushOutlines: () => void;\n  };\n}\n"
  },
  {
    "path": "packages/scan/src/polyfills.ts",
    "content": "if (!Array.prototype.toSorted) {\n  Object.defineProperty(Array.prototype, 'toSorted', {\n    value: function <T>(this: Array<T>, compareFn?: (a: T, b: T) => number): Array<T> {\n      return [...this].sort(compareFn);\n    },\n    writable: true,\n    configurable: true,\n  });\n}"
  },
  {
    "path": "packages/scan/src/react-component-name/__tests__/arrow-function.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\nimport { transform } from './utils';\n\ndescribe('arrow function components', () => {\n  it('handles inline JSX return', async () => {\n    const input = `\n      export const Button = () => <button>Click</button>\n    `;\n    const result = await transform(input);\n\n    expect(result).toContain(\"Button.displayName = 'Button'\");\n  });\n\n  it('handles block with JSX return', async () => {\n    const input = `\n      const Modal = () => {\n        return <div>Modal content</div>\n      }\n    `;\n    const result = await transform(input);\n    expect(result).toContain(\"Modal.displayName = 'Modal'\");\n  });\n\n  it('handles conditional returns', async () => {\n    const input = `\n      const ConditionalComponent = ({ show }) => {\n        if (show) {\n          return <div>Shown</div>\n        }\n        return <div>Hidden</div>\n      }\n    `;\n    const result = await transform(input);\n    expect(result).toContain(\n      \"ConditionalComponent.displayName = 'ConditionalComponent'\",\n    );\n  });\n\n  it('handles early returns', async () => {\n    const input = `\n      const EarlyReturn = ({ loading, error, data }) => {\n        if (loading) return <div>Loading...</div>\n        if (error) return <div>Error: {error}</div>\n        return <div>{data}</div>\n      }\n    `;\n    const result = await transform(input);\n    expect(result).toContain(\"EarlyReturn.displayName = 'EarlyReturn'\");\n  });\n});\n"
  },
  {
    "path": "packages/scan/src/react-component-name/__tests__/complex-patterns.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\nimport { transform } from './utils';\n\ndescribe('complex component patterns', () => {\n  it('handles components with hooks', async () => {\n    const input = `\n      const TodoList = () => {\n        const [todos, setTodos] = useState([])\n        useEffect(() => {\n          fetchTodos().then(setTodos)\n        }, [])\n        return <ul>{todos.map(todo => <li key={todo.id}>{todo.text}</li>)}</ul>\n      }\n    `;\n    const result = await transform(input);\n    expect(result).toContain(\"TodoList.displayName = 'TodoList'\");\n  });\n\n  it('handles components with multiple state updates', async () => {\n    const input = `\n      const Counter = () => {\n        const [count, setCount] = useState(0)\n        const increment = () => setCount(c => c + 1)\n        const decrement = () => setCount(c => c - 1)\n        return (\n          <div>\n            <button onClick={decrement}>-</button>\n            <span>{count}</span>\n            <button onClick={increment}>+</button>\n          </div>\n        )\n      }\n    `;\n    const result = await transform(input);\n    expect(result).toContain(\"Counter.displayName = 'Counter'\");\n  });\n\n  it('handles components with render props', async () => {\n    const input = `\n      const DataFetcher = ({ children, url }) => {\n        const [data, setData] = useState(null)\n        useEffect(() => {\n          fetch(url).then(setData)\n        }, [url])\n        return <>{children(data)}</>\n      }\n    `;\n    const result = await transform(input);\n    expect(result).toContain(\"DataFetcher.displayName = 'DataFetcher'\");\n  });\n\n  it('handles higher-order components', async () => {\n    const input = `\n      const withData = (WrappedComponent) => {\n        const WithData = (props) => {\n          const [data, setData] = useState(null)\n          return <WrappedComponent data={data} {...props} />\n        }\n        return WithData\n      }\n    `;\n    const result = await transform(input);\n    expect(result).toContain(\"WithData.displayName = 'WithData'\");\n  });\n});\n"
  },
  {
    "path": "packages/scan/src/react-component-name/__tests__/function-declarations.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\nimport { transform } from './utils';\n\ndescribe('function declarations', () => {\n  it('handles named function declarations', async () => {\n    const input = `\n      function Welcome(props) {\n        return <h1>Hello, {props.name}</h1>\n      }\n    `;\n    const result = await transform(input);\n    expect(result).toContain(\"Welcome.displayName = 'Welcome'\");\n  });\n\n  it('handles async components', async () => {\n    const input = `\n      async function AsyncComponent({ id }) {\n        const data = await fetchData(id)\n        return <div>{data}</div>\n      }\n    `;\n    const result = await transform(input);\n    expect(result).toContain(\"AsyncComponent.displayName = 'AsyncComponent'\");\n  });\n});\n"
  },
  {
    "path": "packages/scan/src/react-component-name/__tests__/general-cases.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\nimport { transform } from './utils';\n\ndescribe('edge cases', () => {\n  it('handles nested component declarations', async () => {\n    const input = `\n      const Parent = () => {\n        const NestedChild = () => <div>Child</div>\n        return (\n          <div>\n            <NestedChild />\n          </div>\n        )\n      }\n    `;\n    const result = await transform(input);\n    expect(result).toContain(\"Parent.displayName = 'Parent'\");\n    expect(result).toContain(\"NestedChild.displayName = 'NestedChild'\");\n  });\n\n  it('handles components with complex expressions', async () => {\n    const input = `\n      const DynamicComponent = () => {\n        const content = useMemo(() => (\n          <div>\n            {data.map(item => (\n              <Fragment key={item.id}>\n                {item.visible && <span>{item.text}</span>}\n              </Fragment>\n            ))}\n          </div>\n        ), [data])\n\n        return (\n          <>\n            {isLoading ? <Spinner /> : content}\n          </>\n        )\n      }\n    `;\n    const result = await transform(input);\n    expect(result).toContain(\n      \"DynamicComponent.displayName = 'DynamicComponent'\",\n    );\n  });\n\n  it('handles components with multiple returns in switch/case', async () => {\n    const input = `\n      const StatusComponent = ({ status }) => {\n        switch (status) {\n          case 'loading':\n            return <Spinner />\n          case 'error':\n            return <Error />\n          case 'empty':\n            return <Empty />\n          default:\n            return <Content />\n        }\n      }\n    `;\n    const result = await transform(input);\n    expect(result).toContain(\"StatusComponent.displayName = 'StatusComponent'\");\n  });\n\n  it('handles components with try/catch blocks', async () => {\n    const input = `\n      const SafeComponent = () => {\n        try {\n          const data = riskyOperation()\n          return <div>{data}</div>\n        } catch (error) {\n          return <div>Error: {error.message}</div>\n        }\n      }\n    `;\n    const result = await transform(input);\n    expect(result).toContain(\"SafeComponent.displayName = 'SafeComponent'\");\n  });\n\n  it('handles components returning primitive values', async () => {\n    const input = `\n      // Null component\n      const EmptyComponent = () => null;\n\n      // String component\n      const TextComponent = () => \"Hello World\";\n\n      // Number component\n      const NumberComponent = () => 42;\n\n      // Boolean component (though not very useful)\n      const BooleanComponent = () => true;\n\n      // Array of elements\n      const ListComponent = () => [\n        <div key=\"1\">One</div>,\n        <div key=\"2\">Two</div>\n      ];\n\n      // Conditional primitive returns\n      const ConditionalComponent = ({ value }) => {\n        if (!value) return null;\n        if (typeof value === 'string') return value;\n        if (typeof value === 'number') return value.toString();\n        return <div>{value}</div>;\n      };\n\n      // Dynamic children\n      const DynamicComponent = ({ count }) => {\n        return Array(count).fill(null).map((_, i) => <div key={i} />);\n      };\n\n      // Async component with suspense\n      const AsyncComponent = () => {\n        const resource = fetchData();\n        if (!resource.isReady) {\n          throw resource.promise;\n        }\n        return <div>{resource.read()}</div>;\n      };\n\n      // Portal component\n      const PortalComponent = () => {\n        return createPortal(<div>Portal content</div>, document.body);\n      };\n\n      // Fragment shorthand\n      const FragmentComponent = () => <>Fragment content</>;\n\n      // Nested arrays and fragments\n      const NestedComponent = () => [\n        <div key=\"1\">First</div>,\n        <>\n          <div>Nested 1</div>\n          <div>Nested 2</div>\n        </>,\n        [<div key=\"3\">Deep nested</div>]\n      ];\n    `;\n    const result = await transform(input);\n    // expect(result).toContain(\"EmptyComponent.displayName = 'EmptyComponent'\");\n    // expect(result).toContain(\"TextComponent.displayName = 'TextComponent'\");\n    // expect(result).toContain(\"NumberComponent.displayName = 'NumberComponent'\");\n    // expect(result).toContain(\n    //   \"BooleanComponent.displayName = 'BooleanComponent'\",\n    // );\n    expect(result).toContain(\"ListComponent.displayName = 'ListComponent'\");\n    expect(result).toContain(\n      \"ConditionalComponent.displayName = 'ConditionalComponent'\",\n    );\n    expect(result).toContain(\n      \"DynamicComponent.displayName = 'DynamicComponent'\",\n    );\n    expect(result).toContain(\"AsyncComponent.displayName = 'AsyncComponent'\");\n    expect(result).toContain(\"PortalComponent.displayName = 'PortalComponent'\");\n    expect(result).toContain(\n      \"FragmentComponent.displayName = 'FragmentComponent'\",\n    );\n    expect(result).toContain(\"NestedComponent.displayName = 'NestedComponent'\");\n  });\n\n  it('handles components with complex conditional returns', async () => {\n    const input = `\n      const ComplexComponent = ({ type, data }) => {\n        switch (type) {\n          case 'text': return data;\n          case 'number': return data.toString();\n          case 'array': return data.map(item => <div key={item.id}>{item.text}</div>);\n          case 'element': return <div>{data}</div>;\n          default: return null;\n        }\n      };\n\n      const TernaryComponent = ({ condition, value }) =>\n        condition\n          ? value\n          : value\n            ? <span>{value}</span>\n            : null;\n\n      const ShortCircuitComponent = ({ items }) =>\n        items?.length && items.map(item => <div key={item}>{item}</div>);\n\n      const NullishComponent = ({ text }) =>\n        text ?? <>Default text</>;\n\n      const ChainedComponent = ({ a, b, c }) =>\n        a?.b?.c ?? <div>Fallback</div>;\n\n      // More Suspense examples\n      const DataComponent = () => {\n        const data = resource.read();\n        return <div>{data}</div>;\n      };\n\n      const SuspenseImage = ({ src }) => {\n        const resource = preloadImage(src);\n        if (!resource.complete) {\n          throw resource.promise;\n        }\n        return <img src={src} alt=\"\" />;\n      };\n\n      const ProfileComponent = () => {\n        const user = userResource.read();\n        const posts = postsResource.read();\n        if (!user || !posts) {\n          throw Promise.all([user?.promise, posts?.promise]);\n        }\n        return (\n          <div>\n            <h1>{user.name}</h1>\n            {posts.map(post => <div key={post.id}>{post.title}</div>)}\n          </div>\n        );\n      };\n    `;\n    const result = await transform(input);\n    expect(result).toContain(\n      \"ComplexComponent.displayName = 'ComplexComponent'\",\n    );\n    expect(result).toContain(\n      \"TernaryComponent.displayName = 'TernaryComponent'\",\n    );\n    expect(result).toContain(\n      \"ShortCircuitComponent.displayName = 'ShortCircuitComponent'\",\n    );\n    expect(result).toContain(\n      \"NullishComponent.displayName = 'NullishComponent'\",\n    );\n    expect(result).toContain(\n      \"ChainedComponent.displayName = 'ChainedComponent'\",\n    );\n    expect(result).toContain(\"DataComponent.displayName = 'DataComponent'\");\n    expect(result).toContain(\"SuspenseImage.displayName = 'SuspenseImage'\");\n    expect(result).toContain(\n      \"ProfileComponent.displayName = 'ProfileComponent'\",\n    );\n  });\n\n  it('handles components with complex state and hooks', async () => {\n    const input = `\n      export const ValueUpdate = ({\n        valueUpdate,\n        className,\n      }) => {\n        const actions = useTraceStoreActions();\n        const referredToHeapObject = useTraceStore(getReferredToHeapObjectSelector(valueUpdate));\n        const constructorStackFrame = useTraceStore(\n          referredToHeapObject ? constructorStackFrameSelector(referredToHeapObject.constructorStackFrameId) : () => null,\n        );\n\n        const referredToHeapObjectColor = constructorStackFrame ? getFrameColor(constructorStackFrame) : undefined;\n\n        return (\n          <AnimatedTrace\n            animation={\"grow\"}\n            style={{\n              borderColor: referredToHeapObjectColor,\n            }}\n            key={valueUpdate.valueUpdateId}\n            className={cn([\n              referredToHeapObject && \"border-4\",\n              referredToHeapObject && \"m-[2px]\",\n            ])}\n          >\n            <div className=\"w-fit flex items-center\">\n              <Editable\n                styles={{\n                  notEditing: {\n                    maxWidth: \"20em\",\n                    minHeight: \"0px\",\n                    height: \"1.2rem\",\n                  },\n                }}\n                classNames={{\n                  notEditing: {\n                    input: className,\n                  },\n                }}\n                focusedId={valueUpdate.valueUpdateId}\n                key={valueUpdate.valueUpdateId}\n                state={{\n                  value: valueUpdate.value,\n                  onTrash: () => {\n                    actions.shared.deleteValueUpdate(valueUpdate.valueUpdateId);\n                  },\n                  onValueChange: (newValue) => {\n                    actions.shared.changeVariableUpdateValue({\n                      value: newValue,\n                      valueUpdateId: valueUpdate.valueUpdateId,\n                    });\n                  },\n                }}\n              />\n            </div>\n          </AnimatedTrace>\n        );\n      };\n\n      // Another example with complex state management\n      const DataGrid = ({ data, onSort }) => {\n        const [sortField, setSortField] = useState(null);\n        const [sortDirection, setSortDirection] = useState('asc');\n        const [filters, setFilters] = useState({});\n\n        const sortedData = useMemo(() => {\n          if (!sortField) return data;\n          return [...data].sort((a, b) => {\n            const aVal = a[sortField];\n            const bVal = b[sortField];\n            return sortDirection === 'asc' ? aVal - bVal : bVal - aVal;\n          });\n        }, [data, sortField, sortDirection]);\n\n        const filteredData = useMemo(() => {\n          return sortedData.filter(item => {\n            return Object.entries(filters).every(([key, value]) => {\n              return item[key].toString().toLowerCase().includes(value.toLowerCase());\n            });\n          });\n        }, [sortedData, filters]);\n\n        const handleHeaderClick = (field) => {\n          if (sortField === field) {\n            setSortDirection(prev => prev === 'asc' ? 'desc' : 'asc');\n          } else {\n            setSortField(field);\n            setSortDirection('asc');\n          }\n          onSort?.({ field, direction: sortDirection });\n        };\n\n        return (\n          <div className=\"data-grid\">\n            <div className=\"header\">\n              {Object.keys(data[0] || {}).map(field => (\n                <div\n                  key={field}\n                  onClick={() => handleHeaderClick(field)}\n                  className={cn([\n                    'header-cell',\n                    sortField === field && 'sorted',\n                    sortField === field && sortDirection === 'desc' && 'desc'\n                  ])}\n                >\n                  {field}\n                </div>\n              ))}\n            </div>\n            <div className=\"body\">\n              {filteredData.map((row, i) => (\n                <div key={i} className=\"row\">\n                  {Object.values(row).map((cell, j) => (\n                    <div key={j} className=\"cell\">{cell}</div>\n                  ))}\n                </div>\n              ))}\n            </div>\n          </div>\n        );\n      };\n    `;\n    const result = await transform(input);\n    expect(result).toContain(\"ValueUpdate.displayName = 'ValueUpdate'\");\n    expect(result).toContain(\"DataGrid.displayName = 'DataGrid'\");\n  });\n\n  it('handles all forwardRef patterns', async () => {\n    const input = `\n      import React from 'react';\n\n      // Basic forwardRef\n      const Button = React.forwardRef((props, ref) => (\n        <button ref={ref} {...props} />\n      ));\n\n      // Named function in forwardRef\n      const Input = React.forwardRef(function Input(props, ref) {\n        return <input ref={ref} {...props} />;\n      });\n\n      // forwardRef with type annotations\n      const Select = React.forwardRef<HTMLSelectElement, SelectProps>((props, ref) => (\n        <select ref={ref} {...props} />\n      ));\n\n      // forwardRef with displayName already set (should preserve it)\n      const TextArea = React.forwardRef((props, ref) => {\n        return <textarea ref={ref} {...props} />;\n      });\n      TextArea.displayName = 'CustomTextArea';\n\n      // Complex forwardRef with hooks and logic\n      const Field = React.forwardRef((props, ref) => {\n        const [value, setValue] = useState('');\n        const internalRef = useRef(null);\n\n        useImperativeHandle(ref, () => ({\n          focus: () => internalRef.current?.focus(),\n          reset: () => setValue('')\n        }));\n\n        return (\n          <div>\n            <input\n              ref={internalRef}\n              value={value}\n              onChange={e => setValue(e.target.value)}\n            />\n          </div>\n        );\n      });\n\n      // forwardRef with memo\n      const MemoizedInput = React.memo(React.forwardRef((props, ref) => (\n        <input ref={ref} {...props} />\n      )));\n\n      // forwardRef wrapped in HOC\n      const EnhancedInput = withStyles(React.forwardRef((props, ref) => (\n        <input ref={ref} {...props} />\n      )));\n    `;\n    const result = await transform(input);\n    expect(result).toContain(\"Button.displayName = 'Button'\");\n    expect(result).toContain(\"Input.displayName = 'Input'\");\n    expect(result).toContain(\"Select.displayName = 'Select'\");\n    expect(result).toContain(\"TextArea.displayName = 'CustomTextArea'\"); // Should preserve existing, todo check for one\n\n    expect(result).toContain(\"Field.displayName = 'Field'\");\n    expect(result).toContain(\"MemoizedInput.displayName = 'MemoizedInput'\");\n    expect(result).toContain(\"EnhancedInput.displayName = 'EnhancedInput'\");\n  });\n\n  it('handles all memo patterns', async () => {\n    const input = `\n      import React from 'react';\n      // Basic memo\n      const Item = React.memo(props => (\n        <div>{props.text}</div>\n      ));\n\n      // Named function in memo\n      const Header = React.memo(function Header({ title }) {\n        return <h1>{title}</h1>;\n      });\n\n      // memo with comparison function\n      const ExpensiveList = React.memo(({ items }) => (\n        <ul>\n          {items.map(item => <li key={item.id}>{item.text}</li>)}\n        </ul>\n      ), (prevProps, nextProps) => prevProps.items === nextProps.items);\n\n\n      // memo with type annotations\n      const TypedButton = React.memo<ButtonProps>(props => (\n        <button {...props} />\n      ));\n\n      // memo with displayName already set (should preserve it)\n      const Footer = React.memo(props => (\n        <footer>{props.children}</footer>\n      ));\n      Footer.displayName = 'CustomFooter';\n\n      // Complex memo with hooks and logic\n      const SearchBar = React.memo(({ onSearch }) => {\n        const [query, setQuery] = useState('');\n        const debouncedQuery = useDebounce(query, 300);\n\n        useEffect(() => {\n          onSearch(debouncedQuery);\n        }, [debouncedQuery, onSearch]);\n\n        return (\n          <input\n            type=\"search\"\n            value={query}\n            onChange={e => setQuery(e.target.value)}\n          />\n        );\n      });\n\n      // Nested memo\n      const NestedMemo = React.memo(React.memo(props => (\n        <div>{props.text}</div>\n      )));\n\n      // memo wrapped in HOC\n      const EnhancedList = withStyles(React.memo(props => (\n        <ul>{props.items.map(item => <li key={item.id}>{item.text}</li>)}</ul>\n      )));\n\n      // memo with forwardRef\n      const MemoInput = React.memo(React.forwardRef((props, ref) => (\n        <input ref={ref} {...props} />\n      )));\n    `;\n\n    const result = await transform(input);\n    expect(result).toContain(\"Item.displayName = 'Item'\");\n    expect(result).toContain(\"Header.displayName = 'Header'\");\n    expect(result).toContain(\"ExpensiveList.displayName = 'ExpensiveList'\");\n    expect(result).toContain(\"TypedButton.displayName = 'TypedButton'\");\n    expect(result).toContain(\"Footer.displayName = 'CustomFooter'\"); // Should preserve existing\n    expect(result).toContain(\"SearchBar.displayName = 'SearchBar'\");\n    expect(result).toContain(\"NestedMemo.displayName = 'NestedMemo'\");\n    expect(result).toContain(\"EnhancedList.displayName = 'EnhancedList'\");\n    expect(result).toContain(\"MemoInput.displayName = 'MemoInput'\");\n  });\n\n  it('handles components with various function calls returning JSX', async () => {\n    const input = `\n      const ArrayMethodsComponent = ({ items }) => {\n        // Filter then map\n        const filtered = items\n          .filter(item => item.visible)\n          .map(item => <div key={item.id}>{item.text}</div>);\n\n        // Reduce to JSX\n        const reduced = items.reduce((acc, item) => [\n          ...acc,\n          <div key={item.id}>{item.text}</div>\n        ], []);\n\n        // Custom function returning JSX\n        const renderItem = (item) => <div>{item.text}</div>;\n\n        // Method chaining with JSX returns\n        const processed = items\n          .slice(0, 5)\n          .filter(item => item.score > 10)\n          .map(renderItem);\n\n        return (\n          <>\n            <div>{filtered}</div>\n            <div>{reduced}</div>\n            <div>{processed}</div>\n            <div>{renderItem(items[0])}</div>\n          </>\n        );\n      };\n\n      // Custom utility functions returning JSX\n      const renderList = (items) => items.map(item => <li key={item.id}>{item.text}</li>);\n      const createWrapper = (content) => <div className=\"wrapper\">{content}</div>;\n      const withLayout = (Component) => (props) => (\n        <div className=\"layout\">\n          <Component {...props} />\n        </div>\n      );\n\n      const CustomFunctionsComponent = ({ items }) => {\n        // Direct function calls returning JSX\n        const list = renderList(items);\n        const wrapped = createWrapper(<span>Content</span>);\n\n        // Function composition\n        const content = createWrapper(renderList(items));\n\n        // HOC usage\n        const WrappedComponent = withLayout(({ text }) => <div>{text}</div>);\n\n        return (\n          <>\n            {list}\n            {wrapped}\n            {content}\n            <WrappedComponent text=\"Hello\" />\n          </>\n        );\n      };\n\n      // Promise/async function returns\n      const AsyncComponent = ({ id }) => {\n        const [data, setData] = useState(null);\n\n        useEffect(() => {\n          const fetchData = async () => {\n            const result = await api.get(id);\n            return <div>{result.data}</div>;\n          };\n\n          fetchData().then(setData);\n        }, [id]);\n\n        return data || <div>Loading...</div>;\n      };\n\n      // Complex method chaining\n      const ChainedComponent = ({ data }) => {\n        const result = Object.entries(data)\n          .filter(([_, value]) => value.isValid)\n          .map(([key, value]) => ({ key, ...value }))\n          .reduce((acc, item) => ({\n            ...acc,\n            [item.key]: <div key={item.key}>{item.content}</div>\n          }), {});\n\n        return (\n          <div>\n            {Object.values(result)}\n          </div>\n        );\n      };\n\n      // Functional composition\n      const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);\n\n      const withData = Component => props => {\n        const data = useData();\n        return <Component {...props} data={data} />;\n      };\n\n      const withTheme = Component => props => {\n        const theme = useTheme();\n        return <Component {...props} theme={theme} />;\n      };\n\n      const BaseComponent = ({ data, theme, label }) => (\n        <div className={theme}>{data[label]}</div>\n      );\n\n      const EnhancedComponent = compose(\n        withData,\n        withTheme\n      )(BaseComponent);\n    `;\n\n    const result = await transform(input);\n    expect(result).toContain(\n      \"ArrayMethodsComponent.displayName = 'ArrayMethodsComponent'\",\n    );\n    expect(result).toContain(\n      \"CustomFunctionsComponent.displayName = 'CustomFunctionsComponent'\",\n    );\n    expect(result).toContain(\"AsyncComponent.displayName = 'AsyncComponent'\");\n    expect(result).toContain(\n      \"ChainedComponent.displayName = 'ChainedComponent'\",\n    );\n    expect(result).toContain(\"BaseComponent.displayName = 'BaseComponent'\");\n    // expect(result).toContain(\n    //   \"EnhancedComponent.displayName = 'EnhancedComponent'\",\n    // );\n  });\n\n  it('handles shadcn-style component patterns', async () => {\n    const input = `\n      import React from 'react';\n      // Basic shadcn component pattern\n      const Button = React.forwardRef<\n        HTMLButtonElement,\n        React.ButtonHTMLAttributes<HTMLButtonElement>\n      >(({ className, ...props }, ref) => (\n        <button\n          className={cn(\"rounded-lg px-4\", className)}\n          ref={ref}\n          {...props}\n        />\n      ));\n\n      // With variants using cva\n      const button = cva(\n        \"rounded-lg px-4\",\n        {\n          variants: {\n            variant: {\n              default: \"bg-primary\",\n              secondary: \"bg-secondary\",\n            },\n            size: {\n              default: \"h-10\",\n              sm: \"h-8\",\n              lg: \"h-12\",\n            },\n          },\n          defaultVariants: {\n            variant: \"default\",\n            size: \"default\",\n          },\n        }\n      );\n\n      interface ButtonProps\n        extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n          VariantProps<typeof button> {}\n\n      const ButtonWithVariants = React.forwardRef<HTMLButtonElement, ButtonProps>(\n        ({ className, variant, size, ...props }, ref) => {\n          return (\n            <button\n              className={cn(button({ variant, size, className }))}\n              ref={ref}\n              {...props}\n            />\n          )\n        }\n      );\n\n      // With slot compositions\n      const Card = React.forwardRef<\n        HTMLDivElement,\n        React.HTMLAttributes<HTMLDivElement>\n      >(({ className, ...props }, ref) => (\n        <div\n          ref={ref}\n          className={cn(\"rounded-lg border\", className)}\n          {...props}\n        />\n      ));\n\n      const CardHeader = React.forwardRef<\n        HTMLDivElement,\n        React.HTMLAttributes<HTMLDivElement>\n      >(({ className, ...props }, ref) => (\n        <div\n          ref={ref}\n          className={cn(\"flex flex-col space-y-1.5 p-6\", className)}\n          {...props}\n        />\n      ));\n\n      // Component composition\n      const Dialog = ({ children, ...props }) => (\n        <DialogPrimitive.Root {...props}>\n          {children}\n        </DialogPrimitive.Root>\n      );\n\n      const DialogTrigger = React.forwardRef<\n        React.ElementRef<typeof DialogPrimitive.Trigger>,\n        React.ComponentPropsWithoutRef<typeof DialogPrimitive.Trigger>\n      >(({ className, ...props }, ref) => (\n        <DialogPrimitive.Trigger\n          ref={ref}\n          className={cn(\"\", className)}\n          {...props}\n        />\n      ));\n    `;\n\n    const result = await transform(input);\n    expect(result).toContain(\"Button.displayName = 'Button'\");\n    expect(result).toContain(\n      \"ButtonWithVariants.displayName = 'ButtonWithVariants'\",\n    );\n    expect(result).toContain(\"Card.displayName = 'Card'\");\n    expect(result).toContain(\"CardHeader.displayName = 'CardHeader'\");\n    expect(result).toContain(\"Dialog.displayName = 'Dialog'\");\n    expect(result).toContain(\"DialogTrigger.displayName = 'DialogTrigger'\");\n  });\n\n  it('handles legacy and unconventional component patterns', async () => {\n    const input = `\n\n\n      // createReactClass (after createClass was removed from React)\n      const CreateClassComponent = createReactClass({\n        render() {\n          return <div>Still Legacy</div>\n        }\n      });\n\n      // Mixins (old pattern, but still exists)\n      const mixins = {\n        componentDidMount() {\n          console.log('mounted');\n        }\n      };\n\n      const WithMixins = createReactClass({\n        mixins: [mixins],\n        render() {\n          return <div>With Mixins</div>\n        }\n      });\n\n      // Factory pattern (common in older Material-UI and other libs)\n      const createComponent = (config) => {\n        class GeneratedComponent extends React.Component {\n          render() {\n            return <div>{config.text}</div>\n          }\n        }\n        return GeneratedComponent;\n      };\n      const FactoryComponent = createComponent({ text: 'Factory' });\n\n      // Decorator pattern (still common in MobX codebases)\n      @observer\n      class DecoratedComponent extends React.Component {\n        render() {\n          return <div>{this.props.data}</div>\n        }\n      }\n\n      // Render props with multiple children functions\n      const RenderPropComponent = ({ children, render, component: Component }) => (\n        <div>\n          {children(data)}\n          {render(data)}\n          <Component data={data} />\n        </div>\n      );\n\n      // Old context pattern\n      class OldContextComponent extends React.Component {\n        static contextTypes = {\n          theme: PropTypes.object\n        };\n\n        render() {\n          return <div>{this.context.theme}</div>\n        }\n      }\n\n      // Partial application component creation\n      const createPartialComponent = (defaultProps) =>\n        function PartialComponent(props) {\n          return <div {...defaultProps} {...props} />;\n        };\n      const PartialButton = createPartialComponent({ type: 'button' });\n\n      // jQuery-style plugins (seen in older React codebases)\n      React.Component.prototype.plugin = function() {\n        return <div>Plugin</div>;\n      };\n      class PluginComponent extends React.Component {\n        render() {\n          return <>{this.plugin()}</>;\n        }\n      }\n\n\n\n      // Multiple inheritance simulation\n      const withInheritance = Base => class extends Base {\n        render() {\n          return <div>Extended {super.render()}</div>;\n        }\n      };\n      class BaseComponent extends React.Component {\n        render() {\n          return <div>Base</div>;\n        }\n      }\n      const InheritedComponent = withInheritance(BaseComponent);\n    `;\n\n    const result = await transform(input);\n    expect(result).toContain(\n      \"CreateClassComponent.displayName = 'CreateClassComponent'\",\n    );\n    expect(result).toContain(\"WithMixins.displayName = 'WithMixins'\");\n    // expect(result).toContain(\n    //   \"FactoryComponent.displayName = 'FactoryComponent'\",\n    // );\n    expect(result).toContain(\n      \"DecoratedComponent.displayName = 'DecoratedComponent'\",\n    );\n    expect(result).toContain(\n      \"RenderPropComponent.displayName = 'RenderPropComponent'\",\n    );\n    expect(result).toContain(\n      \"OldContextComponent.displayName = 'OldContextComponent'\",\n    );\n    // expect(result).toContain(\"PartialButton.displayName = 'PartialButton'\");\n    expect(result).toContain(\"PluginComponent.displayName = 'PluginComponent'\");\n    // expect(result).toContain(\n    //   \"InheritedComponent.displayName = 'InheritedComponent'\",\n    // );\n  });\n});\n"
  },
  {
    "path": "packages/scan/src/react-component-name/__tests__/react-patterns.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\nimport { transform } from './utils';\n\ndescribe('modern React patterns', () => {\n  it('handles components with hooks and context', async () => {\n    const input = `\n      const UserProfile = () => {\n        const { user } = useContext(UserContext)\n        const { theme } = useContext(ThemeContext)\n        return (\n          <div className={theme}>\n            <h1>{user.name}</h1>\n            <p>{user.email}</p>\n          </div>\n        )\n      }\n    `;\n    const result = await transform(input);\n    expect(result).toContain(\"UserProfile.displayName = 'UserProfile'\");\n  });\n\n  it('handles components with custom hooks', async () => {\n    const input = `\n      const SearchResults = () => {\n        const { data, loading, error } = useQuery(SEARCH_QUERY)\n        const { formatResult } = useSearchFormatter()\n\n        if (loading) return <div>Loading...</div>\n        if (error) return <div>Error!</div>\n\n        return (\n          <ul>\n            {data.map(item => (\n              <li key={item.id}>{formatResult(item)}</li>\n            ))}\n          </ul>\n        )\n      }\n    `;\n    const result = await transform(input);\n    expect(result).toContain(\"SearchResults.displayName = 'SearchResults'\");\n  });\n\n  it('handles components with suspense boundaries', async () => {\n    const input = `\n      const AsyncContent = () => {\n        const data = useSuspenseQuery(QUERY)\n        return (\n          <Suspense fallback={<div>Loading...</div>}>\n            <div>{data.content}</div>\n          </Suspense>\n        )\n      }\n    `;\n    const result = await transform(input);\n    expect(result).toContain(\"AsyncContent.displayName = 'AsyncContent'\");\n  });\n\n  it('handles components with error boundaries', async () => {\n    const input = `\n      import React from 'react';\n\n      class ErrorBoundary extends React.Component {\n        state = { hasError: false }\n\n        static getDerivedStateFromError(error) {\n          return { hasError: true }\n        }\n\n        render() {\n          if (this.state.hasError) {\n            return <div>Something went wrong</div>\n          }\n          return this.props.children\n        }\n      }\n    `;\n    const result = await transform(input);\n    expect(result).toContain(\"ErrorBoundary.displayName = 'ErrorBoundary'\");\n  });\n});\n"
  },
  {
    "path": "packages/scan/src/react-component-name/__tests__/ts-patterns.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\nimport { transform } from './utils';\n\ndescribe('typescript patterns', () => {\n  it('handles components with type parameters', async () => {\n    const input = `\n      interface Props<T> {\n        items: T[]\n        renderItem: (item: T) => React.ReactNode\n      }\n\n      const List = <T extends unknown>({ items, renderItem }: Props<T>) => {\n        return <div>{items.map(renderItem)}</div>\n      }\n    `;\n    const result = await transform(input);\n    expect(result).toContain(\"List.displayName = 'List'\");\n  });\n\n  it('handles components with complex types', async () => {\n    const input = `\n      type Props = {\n        id: string\n        onClick: (e: React.MouseEvent) => void\n        children: React.ReactNode\n      } & Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'onClick'>\n\n      export const Button: React.FC<Props> = ({ id, onClick, children, ...rest }) => {\n        return <button onClick={onClick} {...rest}>{children}</button>\n      }\n    `;\n    const result = await transform(input);\n    expect(result).toContain(\"Button.displayName = 'Button'\");\n  });\n});\n"
  },
  {
    "path": "packages/scan/src/react-component-name/__tests__/utils.ts",
    "content": "import { type Options, reactComponentNamePlugin } from '..';\n\ntype TransformFn = (\n  code: string,\n  id: string,\n) => Promise<{ code: string } | string | null>;\n\nexport const transform = async (code: string, options?: Options) => {\n  const plugin = reactComponentNamePlugin.vite(options || {}) as {\n    transform: TransformFn;\n  };\n  const transformFn = plugin.transform;\n  if (!transformFn) return code;\n\n  const result = await transformFn.call(\n    {\n      getCombinedSourcemap: () => null,\n      error: console.error,\n    },\n    code,\n    'test.tsx',\n  );\n\n  if (!result) return code;\n  if (typeof result === 'string') return result;\n  return result.code;\n};\n"
  },
  {
    "path": "packages/scan/src/react-component-name/astro.ts",
    "content": "import type { Options } from '.';\nimport vite from './vite';\n\nexport default (options: Options = {}) => ({\n  name: 'react-component-name',\n  hooks: {\n    // oxlint-disable-next-line typescript/no-explicit-any\n    'astro:config:setup': (astro: any) => {\n      astro.config.vite.plugins ||= [];\n      astro.config.vite.plugins.push(vite(options));\n    },\n  },\n});\n"
  },
  {
    "path": "packages/scan/src/react-component-name/babel/get-descriptive-name.ts",
    "content": "import type * as babel from '@babel/core';\n\nexport function getDescriptiveName(\n  path: babel.NodePath,\n  defaultName: string,\n): string {\n  let current: babel.NodePath | null = path;\n  while (current) {\n    switch (current.node.type) {\n      case 'FunctionDeclaration':\n      case 'FunctionExpression': {\n        if (current.node.id) {\n          return current.node.id.name;\n        }\n        break;\n      }\n      case 'VariableDeclarator': {\n        if (current.node.id.type === 'Identifier') {\n          return current.node.id.name;\n        }\n        break;\n      }\n      case 'ClassPrivateMethod':\n      case 'ClassMethod':\n      case 'ObjectMethod': {\n        switch (current.node.key.type) {\n          case 'Identifier':\n            return current.node.key.name;\n          case 'PrivateName':\n            return current.node.key.id.name;\n          default:\n            break;\n        }\n        break;\n      }\n      default:\n        break;\n    }\n    current = current.parentPath;\n  }\n  return defaultName;\n}\n"
  },
  {
    "path": "packages/scan/src/react-component-name/babel/get-root-statement-path.ts",
    "content": "import type * as babel from '@babel/core';\nimport * as t from '@babel/types';\n\nexport function getRootStatementPath(path: babel.NodePath): babel.NodePath {\n  let current = path.parentPath;\n  while (current) {\n    const next = current.parentPath;\n    if (next && t.isProgram(next.node)) {\n      return current;\n    }\n    current = next;\n  }\n  return path;\n}\n"
  },
  {
    "path": "packages/scan/src/react-component-name/babel/index.ts",
    "content": "import type { NodePath, PluginObj } from '@babel/core';\nimport * as t from '@babel/types';\nimport type { Options } from '../core/options';\nimport { isComponentishName } from './is-componentish-name';\nimport { pathReferencesImport } from './path-references-import';\nimport { unwrapNode, unwrapPath } from './unwrap';\n\nfunction getAssignedDisplayNames(path: NodePath<t.Program>): Set<string> {\n  const names = new Set<string>();\n  path.traverse({\n    AssignmentExpression(path) {\n      const { node } = path;\n\n      const memberExpr = unwrapNode(node.left, t.isMemberExpression);\n      if (!memberExpr) {\n        return;\n      }\n      const object = unwrapNode(memberExpr.object, t.isIdentifier);\n      if (!object) {\n        return;\n      }\n      if (\n        t.isIdentifier(memberExpr.property) &&\n        memberExpr.property.name === 'displayName'\n      ) {\n        names.add(object.name);\n      }\n    },\n  });\n  return names;\n}\n\nfunction isValidFunction(\n  node: t.Node,\n): node is t.ArrowFunctionExpression | t.FunctionExpression {\n  return t.isArrowFunctionExpression(node) || t.isFunctionExpression(node);\n}\n\nfunction assignDisplayName(\n  statement: NodePath<t.Statement>,\n  name: string,\n  dontAddTryCatch = false,\n): void {\n  if (dontAddTryCatch) {\n    statement.insertAfter([\n      t.expressionStatement(\n        t.assignmentExpression(\n          '=',\n          t.memberExpression(t.identifier(name), t.identifier('displayName')),\n          t.stringLiteral(name),\n        ),\n      ),\n    ]);\n  } else {\n    statement.insertAfter([\n      t.tryStatement(\n        t.blockStatement([\n          t.expressionStatement(\n            t.assignmentExpression(\n              '=',\n              t.memberExpression(\n                t.identifier(name),\n                t.identifier('displayName'),\n              ),\n              t.stringLiteral(name),\n            ),\n          ),\n        ]),\n        t.catchClause(t.identifier('error'), t.blockStatement([])),\n      ),\n    ]);\n  }\n}\n\nconst REACT_CLASS = ['Component', 'PureComponent'];\n\nfunction isNamespaceExport(\n  namespace: string,\n  moduleExports: string[],\n  path: NodePath<t.Expression>,\n): boolean {\n  const identifier = unwrapPath(path, t.isIdentifier);\n  if (identifier) {\n    return moduleExports.includes(identifier.node.name);\n  }\n  const memberExpr = unwrapPath(path, t.isMemberExpression);\n  if (memberExpr) {\n    const object = unwrapPath(memberExpr.get('object'), t.isIdentifier);\n    if (object && object.node.name === namespace) {\n      const property = memberExpr.get('property');\n      return (\n        property.isIdentifier() && moduleExports.includes(property.node.name)\n      );\n    }\n  }\n  return false;\n}\n\nfunction isReactClassComponent(path: NodePath<t.Class>): boolean {\n  const superClass = path.get('superClass');\n\n  if (!superClass.isExpression()) {\n    return false;\n  }\n  if (isNamespaceExport('React', REACT_CLASS, superClass)) {\n    return true;\n  }\n  // The usual\n  if (pathReferencesImport(superClass, 'react', REACT_CLASS, false, true)) {\n    return true;\n  }\n  return false;\n}\n\nfunction isStyledComponent(\n  moduleName: string,\n  importName: string[],\n  path: NodePath<t.Expression>,\n): boolean {\n  function isStyledImport(path: NodePath<t.Node>): boolean {\n    return (\n      (path.isIdentifier() && path.node.name === 'styled') ||\n      pathReferencesImport(path, moduleName, importName, false, false)\n    );\n  }\n  const callExpr = unwrapPath(path, t.isCallExpression);\n  if (callExpr) {\n    const callee = callExpr.get('callee');\n    // styled('h1', () => {...});\n    if (isStyledImport(callee)) {\n      return true;\n    }\n    // styled.h1(() => {...})\n    const memberExpr = unwrapPath(callee, t.isMemberExpression);\n    if (memberExpr) {\n      const object = unwrapPath(memberExpr.get('object'), t.isIdentifier);\n      if (object && isStyledImport(object)) {\n        return true;\n      }\n    }\n\n    return false;\n  }\n\n  const taggedExpr = unwrapPath(path, t.isTaggedTemplateExpression);\n  if (taggedExpr) {\n    const tag = taggedExpr.get('tag');\n\n    const memberExpr = unwrapPath(tag, t.isMemberExpression);\n    if (memberExpr) {\n      const object = unwrapPath(memberExpr.get('object'), t.isIdentifier);\n      // styled.h1`...`;\n      if (object && isStyledImport(object)) {\n        return true;\n      }\n\n      return false;\n    }\n\n    // styled(Link)`...`\n    const callExpr = unwrapPath(tag, t.isCallExpression);\n    if (callExpr) {\n      const callee = callExpr.get('callee');\n      if (isStyledImport(callee)) {\n        return true;\n      }\n\n      return false;\n    }\n  }\n  return false;\n}\n\nconst REACT_FACTORY = [\n  'forwardRef',\n  'memo',\n  'createClass',\n  // 'lazy',\n];\n\nfunction isReactComponent(\n  expr: NodePath<t.Expression>,\n  flags: Options['flags'],\n): boolean {\n  // Check for class components\n  const classExpr = unwrapPath(expr, t.isClassExpression);\n  if (classExpr && isReactClassComponent(classExpr)) {\n    return true;\n  }\n  // Check for function components\n  const funcExpr = unwrapPath(expr, isValidFunction);\n  if (funcExpr && !funcExpr.node.generator && funcExpr.node.params.length < 3) {\n    return true;\n  }\n  // Time for call exprs\n  const callExpr = unwrapPath(expr, t.isCallExpression);\n  if (callExpr) {\n    const callee = callExpr.get('callee');\n    // React\n    const factory = [...REACT_FACTORY];\n    if (!flags?.noCreateContext) {\n      factory.push('createContext');\n    }\n    if (\n      (callee.isExpression() &&\n        isNamespaceExport('React', REACT_FACTORY, callee)) ||\n      pathReferencesImport(callee, 'react', REACT_FACTORY, false, true)\n    ) {\n      return true;\n    }\n    const identifier = unwrapPath(callee, t.isIdentifier);\n    if (identifier) {\n      if (identifier.node.name === 'createReactClass') {\n        return true;\n      }\n      // Assume HOCs\n      if (/^with[A-Z]/.test(identifier.node.name)) {\n        return true;\n      }\n    }\n  }\n\n  if (flags?.noStyledComponents) return false;\n  if (isStyledComponent('@emotion/styled', ['default'], expr)) {\n    return true;\n  }\n  if (isStyledComponent('styled-components', ['default'], expr)) {\n    return true;\n  }\n  return false;\n}\n\nexport const reactScanComponentNamePlugin = (options?: Options): PluginObj => ({\n  name: 'react-scan/component-name',\n  visitor: {\n    Program(path) {\n      const assignedNames = getAssignedDisplayNames(path);\n      path.traverse({\n        ClassDeclaration(path) {\n          if (isReactClassComponent(path)) {\n            if (!path.node.id) {\n              return;\n            }\n            const name = path.node.id.name;\n            if (assignedNames.has(name)) {\n              return;\n            }\n            assignDisplayName(path, name, options?.flags?.noTryCatchDisplayNames);\n          }\n        },\n        FunctionDeclaration(path) {\n          const decl = path.node;\n\n          if (\n            // Check if the declaration has an identifier, and then check\n            decl.id &&\n            // if the name is component-ish\n            isComponentishName(decl.id.name, options?.flags) &&\n            !decl.generator &&\n            // Might be component-like, but the only valid components\n            // have zero, one or two (forwardRef) parameters\n            decl.params.length < 3\n          ) {\n            if (!path.node.id) {\n              return;\n            }\n            const name = path.node.id.name;\n            if (assignedNames.has(name)) {\n              return;\n            }\n            assignDisplayName(path, name, options?.flags?.noTryCatchDisplayNames);\n          }\n        },\n        VariableDeclarator(path) {\n          if (!path.parentPath.isVariableDeclaration()) {\n            return;\n          }\n          const identifier = path.node.id;\n          const init = path.get('init');\n          if (!(init.isExpression() && t.isIdentifier(identifier))) {\n            return;\n          }\n          if (!isComponentishName(identifier.name, options?.flags)) {\n            return;\n          }\n          if (isReactComponent(init, options?.flags)) {\n            const name = identifier.name;\n\n            if (!assignedNames.has(name)) {\n              assignDisplayName(\n                path.parentPath,\n                name,\n                options?.flags?.noTryCatchDisplayNames,\n              );\n            }\n          }\n        },\n      });\n    },\n  },\n});\n"
  },
  {
    "path": "packages/scan/src/react-component-name/babel/is-componentish-name.ts",
    "content": "// This is just a Pascal heuristic\n// we only assume a function is a component\n\nimport type { Options } from '../core/options';\n\n// if the first character is in uppercase\nexport function isComponentishName(name: string, flags: Options['flags']) {\n  return (\n    name[0] >= 'A' &&\n    name[0] <= 'Z' &&\n    !flags?.ignoreComponentSubstrings?.some((substring) =>\n      name.includes(substring),\n    )\n  );\n}\n"
  },
  {
    "path": "packages/scan/src/react-component-name/babel/is-nested-expression.ts",
    "content": "import type * as t from '@babel/types';\ntype NestedExpression =\n  | t.ParenthesizedExpression\n  | t.TypeCastExpression\n  | t.TSAsExpression\n  | t.TSSatisfiesExpression\n  | t.TSNonNullExpression\n  | t.TSInstantiationExpression\n  | t.TSTypeAssertion;\n\nexport const isNestedExpression = (node: t.Node): node is NestedExpression => {\n  switch (node.type) {\n    case 'ParenthesizedExpression':\n    case 'TypeCastExpression':\n    case 'TSAsExpression':\n    case 'TSSatisfiesExpression':\n    case 'TSNonNullExpression':\n    case 'TSTypeAssertion':\n    case 'TSInstantiationExpression':\n      return true;\n    default:\n      return false;\n  }\n};\n"
  },
  {
    "path": "packages/scan/src/react-component-name/babel/is-path-valid.ts",
    "content": "import type { NodePath } from '@babel/core';\nimport type * as t from '@babel/types';\n\ntype TypeFilter<V extends t.Node> = (node: t.Node) => node is V;\n\nexport const isPathValid = <V extends t.Node>(\n  path: unknown,\n  key: TypeFilter<V>,\n): path is NodePath<V> => {\n  return key((path as NodePath).node);\n};\n"
  },
  {
    "path": "packages/scan/src/react-component-name/babel/is-statement-top-level.ts",
    "content": "import type * as babel from '@babel/core';\nimport type * as t from '@babel/types';\n\nexport function isStatementTopLevel(\n  path: babel.NodePath<t.Statement>,\n): boolean {\n  let blockParent = path.scope.getBlockParent();\n  const programParent = path.scope.getProgramParent();\n  // a FunctionDeclaration binding refers to itself as the block parent\n  if (blockParent.path === path) {\n    blockParent = blockParent.parent;\n  }\n\n  return programParent === blockParent;\n}\n"
  },
  {
    "path": "packages/scan/src/react-component-name/babel/path-references-import.ts",
    "content": "import type { NodePath } from '@babel/core';\nimport * as t from '@babel/types';\nimport { isPathValid } from './is-path-valid';\nimport { unwrapPath } from './unwrap';\n\nexport const pathReferencesImport = (\n  path: NodePath,\n  moduleSource: string,\n  importName: string[],\n  asType: boolean,\n  defaultNamespace = false,\n): boolean => {\n  const identifier = unwrapPath(path, t.isIdentifier);\n  if (identifier) {\n    const binding = path.scope.getBinding(identifier.node.name);\n    if (binding && binding.kind === 'module') {\n      const importPath = binding.path;\n      const importParent = importPath.parentPath;\n      if (\n        isPathValid(importParent, t.isImportDeclaration) &&\n        importParent.node.source.value === moduleSource\n      ) {\n        if (isPathValid(importPath, t.isImportSpecifier)) {\n          const key = t.isIdentifier(importPath.node.imported)\n            ? importPath.node.imported.name\n            : importPath.node.imported.value;\n          return importName.includes(key);\n        }\n        if (isPathValid(importPath, t.isImportDefaultSpecifier)) {\n          return importName.includes('default');\n        }\n        if (isPathValid(importPath, t.isImportNamespaceSpecifier)) {\n          return importName.includes('*');\n        }\n      }\n    }\n    return false;\n  }\n  const memberExpr =\n    unwrapPath(path, t.isMemberExpression) ||\n    unwrapPath(path, t.isOptionalMemberExpression);\n  if (memberExpr) {\n    const object = unwrapPath(memberExpr.get('object'), t.isIdentifier);\n    if (!object) {\n      return false;\n    }\n    const property = memberExpr.get('property');\n    if (isPathValid(property, t.isIdentifier)) {\n      return (\n        importName.includes(property.node.name) &&\n        (pathReferencesImport(object, moduleSource, ['*'], asType) ||\n          (defaultNamespace &&\n            pathReferencesImport(object, moduleSource, ['default'], asType)))\n      );\n    }\n    if (isPathValid(property, t.isStringLiteral)) {\n      return (\n        importName.includes(property.node.value) &&\n        (pathReferencesImport(object, moduleSource, ['*'], asType) ||\n          (defaultNamespace &&\n            pathReferencesImport(object, moduleSource, ['default'], asType)))\n      );\n    }\n  }\n  return false;\n};\n"
  },
  {
    "path": "packages/scan/src/react-component-name/babel/unwrap.ts",
    "content": "import type { NodePath } from '@babel/core';\nimport type * as t from '@babel/types';\nimport { isNestedExpression } from './is-nested-expression';\nimport { isPathValid } from './is-path-valid';\n\ntype TrueTypeFilter<U extends t.Node> = (node: t.Node) => node is U;\ntype TypeCheck<K> = K extends TrueTypeFilter<infer U> ? U : never;\n\ntype NodeTypeFilter = (node: t.Node) => boolean;\n\nexport const unwrapNode = <K extends NodeTypeFilter>(\n  node: t.Node | null | undefined,\n  key: K,\n): TypeCheck<K> | undefined => {\n  if (!node) {\n    return undefined;\n  }\n  if (key(node)) {\n    return node as TypeCheck<K>;\n  }\n  if (isNestedExpression(node)) {\n    return unwrapNode(node.expression, key);\n  }\n  return undefined;\n};\n\ntype PathTypeFilter<V extends t.Node> = (node: t.Node) => node is V;\n\nexport const unwrapPath = <V extends t.Node>(\n  path: unknown,\n  key: PathTypeFilter<V>,\n): NodePath<V> | undefined => {\n  if (isPathValid(path, key)) {\n    return path;\n  }\n  if (isPathValid(path, isNestedExpression)) {\n    return unwrapPath(path.get('expression'), key);\n  }\n  return undefined;\n};\n"
  },
  {
    "path": "packages/scan/src/react-component-name/core/options.ts",
    "content": "import type { FilterPattern } from '@rollup/pluginutils';\n\nexport interface Options {\n  include?: FilterPattern;\n  exclude?: FilterPattern;\n  enforce?: 'pre' | 'post' | undefined;\n  flags?: {\n    noTryCatchDisplayNames?: boolean;\n    noStyledComponents?: boolean;\n    noCreateContext?: boolean;\n    ignoreComponentSubstrings?: Array<string>;\n  };\n}\n\ntype Overwrite<T, U> = Pick<T, Exclude<keyof T, keyof U>> & U;\n\nexport type OptionsResolved = Overwrite<\n  Required<Options>,\n  Pick<Options, 'enforce'>\n>;\n\nexport function resolveOptions(options: Options): OptionsResolved {\n  return {\n    include: options.include ?? [/\\.[cm]?[jt]sx?$/],\n    exclude: options.exclude ?? [/node_modules/],\n    enforce: 'enforce' in options ? options.enforce : 'pre',\n    flags: options.flags ?? {},\n  };\n}\n"
  },
  {
    "path": "packages/scan/src/react-component-name/esbuild.ts",
    "content": "import { reactComponentNamePlugin } from '.'\n\nexport default reactComponentNamePlugin.esbuild\n"
  },
  {
    "path": "packages/scan/src/react-component-name/index.ts",
    "content": "import { transformAsync } from '@babel/core';\nimport { createFilter } from '@rollup/pluginutils';\nimport { createUnplugin } from 'unplugin';\nimport { reactScanComponentNamePlugin } from './babel';\nimport type { Options } from './core/options';\n\nexport const transform = async (\n  code: string,\n  id: string,\n  filter: (id: string) => boolean,\n  options?: Options,\n) => {\n  if (!filter(id)) return null;\n\n  try {\n    const result = await transformAsync(code, {\n      plugins: [reactScanComponentNamePlugin(options)],\n      ignore: [/\\/(?<c>build|node_modules)\\//],\n      parserOpts: {\n        plugins: ['jsx', 'typescript', 'decorators'],\n      },\n      cloneInputAst: false,\n      filename: id,\n      ast: false,\n      highlightCode: false,\n      sourceMaps: true,\n      configFile: false,\n      babelrc: false,\n      generatorOpts: {\n        jsescOption: {\n          quotes: 'single',\n          minimal: true,\n        },\n      },\n    });\n\n    if (result?.code) {\n      return { code: result.code ?? '', map: result.map };\n    }\n\n    return null;\n  } catch (error) {\n    // oxlint-disable-next-line no-console\n    console.error('Error processing file:', id, error);\n    return null;\n  }\n};\n\nexport const DEFAULT_INCLUDE = '**/*.{mtsx,mjsx,tsx,jsx}';\nexport const DEFAULT_EXCLUDE = '**/node_modules/**';\nexport const reactComponentNamePlugin = createUnplugin<Options>(\n  (options?: Options) => {\n    // mirror to loader.ts when changing this\n    const filter = createFilter(\n      options?.include || DEFAULT_INCLUDE,\n      options?.exclude || [\n        DEFAULT_EXCLUDE,\n        // Next.js pages dir specific\n        '**/_app.{jsx,tsx,js,ts}',\n        '**/_document.{jsx,tsx,js,ts}',\n        '**/api/**/*',\n        // Million.js specific\n        '**/.million/**/*',\n      ],\n    );\n\n    return {\n      name: 'react-component-name',\n      enforce: 'post',\n      async transform(code, id) {\n        return transform(code, id, filter, options);\n      },\n    };\n  },\n);\n\nexport default reactComponentNamePlugin;\nexport type { Options };\n"
  },
  {
    "path": "packages/scan/src/react-component-name/loader.ts",
    "content": "import { type FilterPattern, createFilter } from '@rollup/pluginutils';\nimport { DEFAULT_EXCLUDE, DEFAULT_INCLUDE, transform } from '.';\n\ninterface LoaderContext {\n  getOptions(): { include?: FilterPattern; exclude?: FilterPattern };\n  resourcePath: string;\n  async(): (\n    error: Error | null,\n    content?: string,\n    sourceMap?: string | object,\n  ) => void;\n}\n\nexport default async function ReactComponentNameLoader(\n  this: LoaderContext,\n  code: string,\n  sourceMap: string | object | undefined,\n) {\n  const parsedMap =\n    typeof sourceMap === 'string' ? JSON.parse(sourceMap) : sourceMap;\n  const callback = this.async();\n  try {\n    const options = this.getOptions();\n    const id = this.resourcePath;\n    const filter = createFilter(\n      options?.include || DEFAULT_INCLUDE,\n      options?.exclude || [\n        DEFAULT_EXCLUDE,\n        // Next.js pages dir specific\n        '**/_app.{jsx,tsx,js,ts}',\n        '**/_document.{jsx,tsx,js,ts}',\n        '**/api/**/*',\n        // Million.js specific\n        '**/.million/**/*',\n      ],\n    );\n    if (!filter(id)) return callback(null, code, parsedMap);\n\n    const result = await transform(code, id, filter);\n\n    callback(\n      null,\n      result?.code || '',\n      result?.map ? JSON.stringify(result.map) : undefined,\n    );\n  } catch (e) {\n    callback(e as Error);\n  }\n}\n"
  },
  {
    "path": "packages/scan/src/react-component-name/rolldown.ts",
    "content": "import { reactComponentNamePlugin } from \".\";\n\n\nexport default reactComponentNamePlugin.rolldown;\n"
  },
  {
    "path": "packages/scan/src/react-component-name/rollup.ts",
    "content": "import reactComponentNamePlugin from '.';\n\nexport default reactComponentNamePlugin.rollup;\n"
  },
  {
    "path": "packages/scan/src/react-component-name/rspack.ts",
    "content": "import reactComponentNamePlugin from '.';\n\nexport default reactComponentNamePlugin.rspack;\n"
  },
  {
    "path": "packages/scan/src/react-component-name/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\"],\n    \"moduleResolution\": \"node\",\n    \"esModuleInterop\": true,\n    \"strict\": true,\n    \"strictNullChecks\": true,\n    \"resolveJsonModule\": true,\n    \"skipLibCheck\": true,\n    \"skipDefaultLibCheck\": true,\n    \"types\": [\"@types/node\"]\n  },\n  \"include\": [\".\"],\n}\n"
  },
  {
    "path": "packages/scan/src/react-component-name/vite.ts",
    "content": "import { reactComponentNamePlugin } from '.';\n\nexport default reactComponentNamePlugin.vite;\n"
  },
  {
    "path": "packages/scan/src/react-component-name/webpack.ts",
    "content": "import { reactComponentNamePlugin } from '.';\n\nexport default reactComponentNamePlugin.webpack;\n"
  },
  {
    "path": "packages/scan/src/types.d.ts",
    "content": "declare module './new-outlines/offscreen-canvas.worker' {\n  const workerCode: string;\n  export default workerCode;\n} "
  },
  {
    "path": "packages/scan/src/types.ts",
    "content": "import type { Fiber, FiberRoot } from 'bippy';\n\ntype ReactScanInternals = typeof import('./core/index')['ReactScanInternals'];\ntype Scan = typeof import('./index')['scan'];\n\nexport interface ExtendedReactRenderer {\n  findFiberByHostInstance: (instance: Element) => Fiber | null;\n  version: string;\n  bundleType: number;\n  rendererPackageName: string;\n  overrideHookState?: (\n    fiber: Fiber,\n    id: string,\n    path: string[],\n    value: unknown,\n  ) => void;\n  overrideProps?: (fiber: Fiber, path: string[], value: unknown) => void;\n  overrideContext?: (\n    fiber: Fiber,\n    contextType: unknown,\n    path: string[],\n    value: unknown,\n  ) => void;\n}\n\ndeclare global {\n  var __REACT_SCAN__: {\n    ReactScanInternals: ReactScanInternals;\n  };\n  var reactScanCleanupListeners: (() => void) | undefined;\n  var reactScan: Scan;\n  var scheduler: {\n    postTask: (cb: unknown, options: { priority: string }) => void;\n  };\n\n  type TTimer = NodeJS.Timeout;\n\n  interface Window {\n    reactScan: Scan;\n    __REACT_SCAN_TOOLBAR_CONTAINER__?: HTMLDivElement;\n    __REACT_SCAN_VERSION__?: string;\n    __REACT_SCAN_EXTENSION__?: boolean;\n    __REACT_DEVTOOLS_GLOBAL_HOOK__?: {\n      checkDCE: (fn: unknown) => void;\n      supportsFiber: boolean;\n      supportsFlight: boolean;\n      renderers: Map<number, ExtendedReactRenderer>;\n      hasUnsupportedRendererAttached: boolean;\n      onCommitFiberRoot: (\n        rendererID: number,\n        root: FiberRoot,\n        priority: void | number,\n      ) => void;\n      onCommitFiberUnmount: (rendererID: number, fiber: Fiber) => void;\n      onPostCommitFiberRoot: (rendererID: number, root: FiberRoot) => void;\n      inject: (renderer: ExtendedReactRenderer) => number;\n      _instrumentationSource?: string;\n      _instrumentationIsActive?: boolean;\n    };\n  }\n}\n"
  },
  {
    "path": "packages/scan/src/web/assets/css/styles.tailwind.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n* {\n  outline: none !important;\n  text-rendering: optimizeLegibility;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n\n  /* WebKit (Chrome, Safari, Edge) specific scrollbar styles */\n  &::-webkit-scrollbar {\n    width: 6px;\n    height: 6px;\n  }\n\n  &::-webkit-scrollbar-track {\n    border-radius: 10px;\n    background: transparent;\n  }\n\n  &::-webkit-scrollbar-thumb {\n    border-radius: 10px;\n    background: rgba(255, 255, 255, 0.3);\n  }\n\n  &::-webkit-scrollbar-thumb:hover {\n    background: rgba(255, 255, 255, 0.4);\n  }\n\n  &::-webkit-scrollbar-corner {\n    background: transparent;\n  }\n}\n\n@-moz-document url-prefix() {\n  * {\n    scrollbar-width: thin;\n    scrollbar-color: rgba(255, 255, 255, 0.4) transparent;\n    scrollbar-width: 6px;\n  }\n}\n\nbutton {\n  @apply hover:bg-none;\n  @apply outline-none;\n  @apply border-none;\n  @apply transition-colors ease-out;\n  @apply cursor-pointer;\n}\n\ninput {\n  @apply outline-none;\n  @apply border-none;\n  @apply bg-none bg-transparent;\n  @apply placeholder:text-neutral-500 placeholder:italic placeholder:text-xs;\n  @apply placeholder-shown:truncate;\n}\n\nsvg {\n  @apply w-auto h-auto;\n  @apply pointer-events-none;\n}\n\n/*\n  Using CSS content with data attributes is more performant than:\n  1. React re-renders with JSX text content\n  2. Direct DOM manipulation methods:\n     - element.textContent (creates/updates text nodes, triggers repaint)\n     - element.innerText (triggers reflow by computing styles & layout)\n     - element.innerHTML (heavy parsing, triggers reflow, security risks)\n  3. Multiple data attributes with complex CSS concatenation\n\n  This approach:\n  - Avoids React reconciliation\n  - Uses browser's native CSS engine (optimized content updates)\n  - Minimizes main thread work\n  - Reduces DOM operations\n  - Avoids forced reflows (layout recalculation)\n  - Only triggers necessary repaints\n  - Keeps pseudo-element updates in render layer\n*/\n.with-data-text {\n  overflow: hidden;\n  &::before {\n    content: attr(data-text);\n    @apply block;\n    @apply truncate;\n  }\n}\n\n#react-scan-toolbar {\n  @apply fixed left-0 top-0;\n  @apply flex flex-col;\n  @apply shadow-lg;\n  @apply font-mono text-[13px] text-white;\n  @apply bg-black;\n  @apply select-none;\n  @apply cursor-move;\n  @apply opacity-0;\n  @apply z-[2147483678];\n  @apply animate-fade-in animation-duration-300 animation-delay-300;\n  @apply shadow-[0_4px_12px_rgba(0,0,0,0.2)];\n  @apply place-self-start;\n\n  will-change: transform;\n  backface-visibility: hidden;\n}\n\n.button {\n  &:hover {\n    background: rgba(255, 255, 255, 0.1);\n  }\n\n  &:active {\n    background: rgba(255, 255, 255, 0.15);\n  }\n}\n\n.resize-line-wrapper {\n  @apply absolute;\n  @apply overflow-hidden;\n}\n\n.resize-line {\n  @apply absolute inset-0;\n  @apply overflow-hidden;\n  @apply bg-black;\n  @apply transition-all;\n\n  svg {\n    @apply absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2;\n  }\n}\n\n.resize-right,\n.resize-left {\n  @apply inset-y-0;\n  @apply w-6;\n  @apply cursor-ew-resize;\n\n  .resize-line-wrapper {\n    @apply inset-y-0;\n    @apply w-1/2;\n  }\n\n  &:hover {\n    .resize-line {\n      @apply translate-x-0;\n    }\n  }\n}\n.resize-right {\n  @apply right-0;\n  @apply translate-x-1/2;\n\n  .resize-line-wrapper {\n    @apply right-0;\n  }\n  .resize-line {\n    @apply rounded-r-lg;\n    @apply -translate-x-full;\n  }\n}\n\n.resize-left {\n  @apply left-0;\n  @apply -translate-x-1/2;\n\n  .resize-line-wrapper {\n    @apply left-0;\n  }\n  .resize-line {\n    @apply rounded-l-lg;\n    @apply translate-x-full;\n  }\n}\n\n.resize-top,\n.resize-bottom {\n  @apply inset-x-0;\n  @apply h-6;\n  @apply cursor-ns-resize;\n\n  .resize-line-wrapper {\n    @apply inset-x-0;\n    @apply h-1/2;\n  }\n\n  &:hover {\n    .resize-line {\n      @apply translate-y-0;\n    }\n  }\n}\n.resize-top {\n  @apply top-0;\n  @apply -translate-y-1/2;\n\n  .resize-line-wrapper {\n    @apply top-0;\n  }\n  .resize-line {\n    @apply rounded-t-lg;\n    @apply translate-y-full;\n  }\n}\n\n.resize-bottom {\n  @apply bottom-0;\n  @apply translate-y-1/2;\n\n  .resize-line-wrapper {\n    @apply bottom-0;\n  }\n  .resize-line {\n    @apply rounded-b-lg;\n    @apply -translate-y-full;\n  }\n}\n\n.react-scan-header {\n  @apply flex items-center gap-x-2;\n  @apply pl-3 pr-2;\n  @apply min-h-9;\n  @apply border-b-1 border-[#222];\n  @apply whitespace-nowrap overflow-hidden;\n}\n\n.react-scan-replay-button,\n.react-scan-close-button {\n  @apply flex items-center;\n  @apply p-1;\n  @apply min-w-fit;\n  @apply rounded;\n  @apply transition-all duration-300;\n}\n\n.react-scan-replay-button {\n  @apply relative;\n  @apply overflow-hidden;\n  @apply !bg-purple-500/50;\n\n  &:hover {\n    @apply bg-purple-500/25;\n  }\n\n  &.disabled {\n    @apply opacity-50;\n    @apply pointer-events-none;\n  }\n\n  &:before {\n    content: \"\";\n    @apply absolute;\n    @apply inset-0;\n    @apply -translate-x-full;\n    animation: shimmer 2s infinite;\n    background: linear-gradient(\n      to right,\n      transparent,\n      rgba(142, 97, 227, 0.3),\n      transparent\n    );\n  }\n}\n\n.react-scan-close-button {\n  @apply bg-white/10;\n\n  &:hover {\n    @apply bg-white/15;\n  }\n}\n\n@keyframes shimmer {\n  100% {\n    @apply translate-x-full;\n  }\n}\n\n.react-section-header {\n  @apply sticky z-100;\n  @apply flex items-center gap-x-2;\n  @apply px-3;\n  @apply w-full h-7;\n  @apply text-[#888] truncate;\n  @apply bg-[#0a0a0a] border-b-1 border-[#222];\n}\n\n.react-scan-section {\n  @apply flex flex-col;\n  @apply px-2;\n  @apply text-[#888];\n  @apply before:content-[attr(data-section)] before:text-gray-500;\n  @apply text-xs;\n\n  > .react-scan-property {\n    @apply -ml-3.5;\n  }\n}\n\n.react-scan-property {\n  @apply relative;\n  @apply flex flex-col;\n  @apply pl-8;\n  @apply border-l-1 border-transparent;\n  @apply overflow-hidden;\n}\n\n.react-scan-property-content {\n  @apply flex-1 flex flex-col;\n  @apply min-h-7;\n  @apply max-w-full;\n  @apply overflow-hidden;\n}\n\n.react-scan-string {\n  color: #9ecbff;\n}\n\n.react-scan-number {\n  color: #79c7ff;\n}\n\n.react-scan-boolean {\n  color: #56b6c2;\n}\n\n.react-scan-key {\n  @apply w-fit max-w-60;\n  @apply text-white whitespace-nowrap;\n}\n\n.react-scan-input {\n  @apply text-white;\n  @apply bg-black;\n}\n\n@keyframes blink {\n  from {\n    @apply opacity-100;\n  }\n  to {\n    @apply opacity-0;\n  }\n}\n\n.react-scan-arrow {\n  @apply absolute top-0 left-7;\n  @apply flex items-center justify-center;\n  @apply cursor-pointer;\n  @apply w-6 h-7;\n  @apply -translate-x-full;\n  @apply z-10;\n\n  > svg {\n    @apply transition-transform;\n  }\n}\n\n.react-scan-nested {\n  @apply relative;\n  @apply overflow-hidden;\n\n  &:before {\n    content: \"\";\n    @apply absolute top-0 left-0;\n    @apply w-[1px] h-full;\n    @apply bg-gray-500/30;\n  }\n}\n\n.react-scan-settings {\n  @apply absolute inset-0;\n  @apply flex flex-col gap-4;\n  @apply py-2 px-4;\n  @apply text-[#888];\n\n  > div {\n    @apply flex items-center justify-between;\n    @apply transition-colors duration-300;\n  }\n}\n\n.react-scan-preview-line {\n  @apply relative;\n  @apply flex items-center min-h-7 gap-x-2;\n}\n\n.react-scan-flash-overlay {\n  @apply absolute inset-0;\n  @apply opacity-0;\n  @apply z-50;\n  @apply pointer-events-none;\n  @apply transition-opacity;\n  @apply mix-blend-multiply;\n  @apply bg-purple-500/90;\n}\n\n.react-scan-toggle {\n  @apply relative;\n  @apply inline-flex;\n  @apply w-10 h-6;\n\n  input {\n    @apply absolute inset-0;\n    @apply opacity-0 z-20;\n    @apply cursor-pointer;\n    @apply w-full h-full;\n  }\n\n  input:checked {\n    + div {\n      @apply bg-[#5f3f9a];\n\n      &::before {\n        @apply translate-x-full;\n        @apply left-auto;\n        @apply border-[#5f3f9a];\n      }\n    }\n  }\n\n  > div {\n    @apply absolute inset-1;\n    @apply bg-neutral-700;\n    @apply rounded-full;\n    @apply pointer-events-none;\n    @apply transition-colors duration-300;\n\n    &:before {\n      @apply content-[''];\n      @apply absolute top-1/2 left-0;\n      @apply -translate-y-1/2;\n      @apply w-4 h-4;\n      @apply bg-white;\n      @apply border-2 border-neutral-700;\n      @apply rounded-full;\n      @apply shadow-sm;\n      @apply transition-all duration-300;\n    }\n  }\n}\n\n.react-scan-flash-active {\n  @apply opacity-40;\n  @apply transition-opacity duration-300;\n}\n\n.react-scan-inspector-overlay {\n  @apply flex flex-col;\n  @apply opacity-0;\n  @apply transition-opacity duration-200 ease-out;\n  will-change: opacity;\n\n  &.fade-out {\n    @apply opacity-0;\n  }\n\n  &.fade-in {\n    @apply opacity-100;\n  }\n}\n\n.react-scan-what-changed {\n  ul {\n    @apply list-disc;\n    @apply pl-4;\n  }\n\n  li {\n    @apply whitespace-nowrap;\n    > div {\n      @apply flex items-center justify-between gap-x-2;\n    }\n  }\n}\n\n.count-badge {\n  @apply flex gap-x-2 items-center;\n  @apply px-1.5 py-0.5;\n  @apply text-[#a855f7] text-xs font-medium tabular-nums rounded-[4px];\n  @apply bg-[#a855f7]/10;\n  @apply origin-center;\n  @apply transition-all duration-300 delay-150;\n}\n\n.count-flash {\n  @apply animate-count-flash;\n}\n\n.count-flash-white {\n  @apply animate-count-flash-shake !delay-500;\n}\n\n.change-scope {\n  @apply flex items-center gap-x-1;\n  @apply text-[#666];\n  @apply text-xs;\n  @apply font-mono;\n\n  > div {\n    @apply px-1.5 py-0.5;\n    @apply text-xs font-medium tabular-nums rounded-[4px];\n    @apply origin-center;\n    @apply transition-all duration-300 delay-150;\n\n    &[data-flash=\"true\"] {\n      @apply bg-[#a855f7]/10 text-[#a855f7];\n    }\n  }\n}\n\n.react-scan-slider {\n  @apply relative;\n  @apply min-h-6;\n\n  > input {\n    @apply absolute inset-0;\n    @apply opacity-0;\n  }\n\n  &:before {\n    @apply content-[''];\n    @apply absolute inset-x-0 top-1/2 -translate-y-1/2;\n    @apply h-1.5;\n    @apply bg-[#8e61e3]/40;\n    @apply rounded-lg;\n    @apply pointer-events-none;\n  }\n\n  &:after {\n    @apply content-[''];\n    @apply absolute inset-x-0 -inset-y-2;\n    @apply -z-10;\n  }\n\n  span {\n    @apply absolute left-0 top-1/2 -translate-y-1/2;\n    @apply w-2.5 h-2.5;\n    @apply rounded-lg;\n    @apply bg-[#8e61e3];\n    @apply pointer-events-none;\n    @apply transition-transform duration-75;\n  }\n}\n\n.resize-v-line {\n  @apply flex items-center justify-center;\n  @apply min-w-1 max-w-1;\n  @apply w-full h-full;\n  @apply transition-colors;\n\n  &:hover,\n  &:active {\n    > span {\n      @apply bg-[#222];\n    }\n\n    svg {\n      @apply opacity-100;\n    }\n  }\n\n  &::before {\n    @apply content-[\"\"];\n    @apply absolute inset-0 left-1/2 -translate-x-1/2;\n    @apply w-[1px];\n    @apply bg-[#222];\n    @apply transition-colors;\n  }\n\n  > span {\n    @apply absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2;\n    @apply w-1.5 h-4.5;\n    @apply rounded;\n    @apply transition-colors;\n  }\n\n  svg {\n    @apply absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2;\n    @apply text-neutral-400 rotate-90;\n    @apply opacity-0;\n    @apply transition-opacity;\n    @apply z-50;\n  }\n}\n\n.tree-node-search-highlight {\n  @apply truncate;\n\n  span {\n    @apply py-[1px];\n    @apply font-medium bg-yellow-300 text-black rounded-sm;\n  }\n\n  .single {\n    @apply px-[2px] mr-[1px];\n  }\n\n  .regex {\n    @apply px-[2px];\n  }\n\n  .start {\n    @apply rounded-l-sm ml-[1px];\n  }\n\n  .end {\n    @apply rounded-r-sm mr-[1px];\n  }\n\n  .middle {\n    @apply rounded-sm mx-[1px];\n  }\n}\n\n.react-scan-toolbar-notification {\n  @apply absolute inset-x-0;\n  @apply flex items-center gap-x-2;\n  @apply p-1 pl-2 text-[10px];\n  @apply text-neutral-300;\n  @apply bg-black/90;\n  @apply transition-transform;\n\n  &:before {\n    @apply content-[''];\n    @apply absolute inset-x-0;\n    @apply bg-black;\n    @apply h-2;\n  }\n\n  &.position-top {\n    @apply top-full -translate-y-full;\n    @apply rounded-b-lg;\n\n    &::before {\n      @apply top-0 -translate-y-full;\n    }\n  }\n\n  &.position-bottom {\n    @apply bottom-full translate-y-full;\n    @apply rounded-t-lg;\n\n    &::before {\n      @apply bottom-0 translate-y-full;\n    }\n  }\n\n  &.is-open {\n    @apply translate-y-0;\n  }\n}\n\n.react-scan-header-item {\n  @apply absolute inset-0 -translate-y-[200%];\n  @apply transition-transform duration-300;\n\n  &.is-visible {\n    @apply translate-y-0;\n  }\n}\n\n.react-scan-components-tree:has(.resize-v-line:hover, .resize-v-line:active)\n  .tree {\n  overflow: hidden;\n}\n\n.react-scan-expandable {\n  display: grid;\n  grid-template-rows: 0fr;\n  @apply overflow-hidden;\n  @apply transition-all duration-75;\n  transition-timing-function: ease-out;\n\n  > * {\n    min-height: 0;\n  }\n\n  &.react-scan-expanded {\n    grid-template-rows: 1fr;\n    transition-duration: 100ms;\n  }\n}\n"
  },
  {
    "path": "packages/scan/src/web/components/copy-to-clipboard/index.tsx",
    "content": "import { memo } from 'preact/compat';\nimport { useCallback, useEffect, useState } from 'preact/hooks';\nimport { cn } from '~web/utils/helpers';\nimport { Icon } from '../icon';\n\ninterface CopyToClipboardProps {\n  text: string;\n  children?: (props: {\n    ClipboardIcon: JSX.Element;\n    onClick: (e: MouseEvent) => void;\n  }) => JSX.Element;\n  onCopy?: (success: boolean, text: string) => void;\n  className?: string;\n  iconSize?: number;\n}\n\nexport const CopyToClipboard = /* @__PURE__ */ memo(\n  ({\n    text,\n    children,\n    onCopy,\n    className,\n    iconSize = 14,\n  }: CopyToClipboardProps): JSX.Element => {\n    const [isCopied, setIsCopied] = useState(false);\n\n    useEffect(() => {\n      if (isCopied) {\n        const timeout = setTimeout(() => setIsCopied(false), 600);\n        return () => {\n          clearTimeout(timeout);\n        };\n      }\n    }, [isCopied]);\n\n    const copyToClipboard = useCallback(\n      (e: MouseEvent) => {\n        e.preventDefault();\n        e.stopPropagation();\n\n        navigator.clipboard.writeText(text).then(\n          () => {\n            setIsCopied(true);\n            onCopy?.(true, text);\n          },\n          () => {\n            onCopy?.(false, text);\n          },\n        );\n      },\n      [text, onCopy],\n    );\n\n    const ClipboardIcon = (\n      <button\n        onClick={copyToClipboard}\n        type=\"button\"\n        className={cn(\n          'z-10',\n          'flex items-center justify-center',\n          'hover:text-dev-pink-400',\n          'transition-colors duration-200 ease-in-out',\n          'cursor-pointer',\n          `size-[${iconSize}px]`,\n          className,\n        )}\n      >\n        <Icon\n          name={`icon-${isCopied ? 'check' : 'copy'}`}\n          size={[iconSize]}\n          className={cn(isCopied && 'text-green-500')}\n        />\n      </button>\n    );\n\n    if (!children) {\n      return ClipboardIcon;\n    }\n\n    return children({\n      ClipboardIcon,\n      onClick: copyToClipboard,\n    });\n  },\n);\n"
  },
  {
    "path": "packages/scan/src/web/components/icon/index.tsx",
    "content": "import type { JSX } from 'preact';\nimport { type ForwardedRef, forwardRef } from 'preact/compat';\n\nexport interface SVGIconProps {\n  size?: number | Array<number>;\n  name: string;\n  fill?: string;\n  stroke?: string;\n  className?: string;\n  externalURL?: string;\n  style?: JSX.CSSProperties;\n}\n\nexport const Icon = forwardRef(({\n  size = 15,\n  name,\n  fill = 'currentColor',\n  stroke = 'currentColor',\n  className,\n  externalURL = '',\n  style,\n}: SVGIconProps, ref: ForwardedRef<SVGSVGElement>) => {\n  const width = Array.isArray(size) ? size[0] : size;\n  const height = Array.isArray(size) ? size[1] || size[0] : size;\n\n  const path = `${externalURL}#${name}`;\n\n  return (\n    <svg\n      ref={ref}\n      width={`${width}px`}\n      height={`${height}px`}\n      fill={fill}\n      stroke={stroke}\n      className={className}\n      style={{\n        ...style,\n        minWidth: `${width}px`,\n        maxWidth: `${width}px`,\n        minHeight: `${height}px`,\n        maxHeight: `${height}px`,\n      }}\n    >\n      <title>{name}</title>\n      <use href={path} />\n    </svg>\n  );\n});\n"
  },
  {
    "path": "packages/scan/src/web/components/slider/index.tsx",
    "content": "import { useCallback, useEffect, useRef } from \"preact/hooks\";\nimport { cn } from \"~web/utils/helpers\";\n\ninterface SliderProps {\n  className?: string;\n  onChange: (e: Event) => void;\n  value: number;\n  min: number;\n  max: number;\n  totalUpdates?: number;\n}\n\nexport const Slider = ({\n  value,\n  min,\n  max,\n  onChange,\n  className,\n  totalUpdates = max + 1,\n}: SliderProps) => {\n  const refThumb = useRef<HTMLSpanElement>(null);\n  const refLastValue = useRef<number>(value);\n\n  const updateThumbPosition = useCallback((value: number) => {\n    if (!refThumb.current) return;\n\n    const range = Math.max(1, max - min);\n    const valueOffset = value - min;\n    const percentage = min === max ? 0 : Math.min(100, Math.round((valueOffset / range) * 100));\n\n    refThumb.current.style.setProperty('left', `${percentage}%`);\n  }, [min, max]);\n\n  /**\n   * oxlint-disable-next-line react-hooks/exhaustive-deps\n   * we rely on min, max and value to update the thumb position\n   */\n  useEffect(() => {\n    updateThumbPosition(value);\n  }, [min, max, value]);\n\n  const handleChange = useCallback((e: Event) => {\n    const target = e.target as HTMLInputElement;\n    const newValue = Number.parseInt(target.value, 10);\n\n    if (newValue >= totalUpdates) {\n      return;\n    }\n\n    if (refLastValue.current !== newValue) {\n      refLastValue.current = newValue;\n      updateThumbPosition(newValue);\n      onChange(e);\n    }\n  }, [onChange, updateThumbPosition, totalUpdates]);\n\n  return (\n    <div\n      onPointerDown={(e) => {\n        e.stopPropagation();\n      }}\n      className={cn(\n        'react-scan-slider relative',\n        'flex-1',\n        className\n      )}\n    >\n      <input\n        type=\"range\"\n        value={value}\n        min={min}\n        max={max}\n        onChange={handleChange}\n        className={cn(\n          'react-scan-slider',\n          'flex-1',\n          'h-1.5',\n          'bg-gray-200',\n          'rounded-lg',\n          'appearance-none',\n          'cursor-pointer',\n          className\n        )}\n      />\n      <div\n        className={cn(\n          'absolute inset-0 right-2',\n          'pointer-events-none',\n        )}\n      >\n        <span ref={refThumb} />\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/scan/src/web/components/sticky-section/index.tsx",
    "content": "import { memo } from 'preact/compat';\nimport { useCallback, useRef, useState } from 'preact/hooks';\nimport type { useMergedRefs } from '~web/hooks/use-merged-refs';\n\ninterface StickyRenderProps {\n  refSticky: ReturnType<typeof useMergedRefs<HTMLElement>>;\n  isSticky: boolean;\n  calculateStickyTop: (removeSticky?: boolean) => void;\n}\n\ninterface StickyProps {\n  children: (props: StickyRenderProps) => preact.JSX.Element;\n}\n\nexport const StickySection = /* @__PURE__ */ memo(\n  ({ children }: StickyProps) => {\n    const refScrollableElement = useRef<HTMLElement | null>(null);\n    const refScrollAtTop = useRef(false);\n    const [isSticky, setIsSticky] = useState(false);\n    const refRafId = useRef(0);\n\n    const calculateStickyTop = useCallback((removeSticky = false) => {\n      const stickyElements = Array.from(\n        refScrollableElement.current?.children || [],\n      ) as HTMLElement[];\n      if (!stickyElements.length) return;\n\n      let cumulativeHeight = 0;\n\n      for (const element of stickyElements) {\n        const sticky = element as HTMLElement;\n        if (sticky.dataset.sticky) {\n          if (removeSticky) {\n            sticky.style.removeProperty('top');\n          } else {\n            sticky.style.setProperty('top', `${cumulativeHeight}px`);\n          }\n          cumulativeHeight += sticky.offsetHeight;\n        }\n      }\n    }, []);\n\n    const refSticky = useCallback(\n      (node: HTMLElement | null) => {\n        if (!node) {\n          requestAnimationFrame(() => {\n            calculateStickyTop();\n          });\n          return;\n        }\n\n        refScrollableElement.current = node.parentElement;\n        node.dataset.sticky = 'true';\n\n        const handleClick = () => {\n          if (!node.dataset.disableScroll) {\n            refScrollableElement.current?.scrollTo({\n              top: Number(node.style.top) ?? 0,\n              behavior: 'smooth',\n            });\n          }\n        };\n\n        node.onclick = handleClick;\n        calculateStickyTop();\n\n        const handleScroll = () => {\n          cancelAnimationFrame(refRafId.current);\n          refRafId.current = requestAnimationFrame(() => {\n            if (!node || !refScrollableElement.current) return;\n\n            const refRect = node.getBoundingClientRect();\n            const containerRect =\n              refScrollableElement.current.getBoundingClientRect();\n\n            const stickyOffset = Number.parseInt(getComputedStyle(node).top);\n            refScrollAtTop.current = refScrollableElement.current.scrollTop > 0;\n\n            const stickyActive =\n              refScrollAtTop.current &&\n              refRect.top <= containerRect.top + stickyOffset;\n\n            if (stickyActive !== isSticky) {\n              setIsSticky(stickyActive);\n            }\n\n            calculateStickyTop();\n          });\n        };\n\n        refScrollableElement.current?.addEventListener('scroll', handleScroll, {\n          passive: true,\n        });\n      },\n      [isSticky, calculateStickyTop],\n    );\n\n    return children({\n      refSticky,\n      isSticky,\n      calculateStickyTop,\n    });\n  },\n);\n"
  },
  {
    "path": "packages/scan/src/web/components/svg-sprite/index.tsx",
    "content": "export const SvgSprite = () => {\n  return (\n    <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"display: none;\">\n      <title>React Scan Icons</title>\n      <symbol id=\"icon-inspect\" viewBox=\"0 0 24 24\" fill=\"none\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n        <path d=\"M12.034 12.681a.498.498 0 0 1 .647-.647l9 3.5a.5.5 0 0 1-.033.943l-3.444 1.068a1 1 0 0 0-.66.66l-1.067 3.443a.5.5 0 0 1-.943.033z\" />\n        <path d=\"M5 3a2 2 0 0 0-2 2\" />\n        <path d=\"M19 3a2 2 0 0 1 2 2\" />\n        <path d=\"M5 21a2 2 0 0 1-2-2\" />\n        <path d=\"M9 3h1\" />\n        <path d=\"M9 21h2\" />\n        <path d=\"M14 3h1\" />\n        <path d=\"M3 9v1\" />\n        <path d=\"M21 9v2\" />\n        <path d=\"M3 14v1\" />\n      </symbol>\n\n      <symbol id=\"icon-focus\" viewBox=\"0 0 24 24\" fill=\"none\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n        <path d=\"M12.034 12.681a.498.498 0 0 1 .647-.647l9 3.5a.5.5 0 0 1-.033.943l-3.444 1.068a1 1 0 0 0-.66.66l-1.067 3.443a.5.5 0 0 1-.943.033z\" />\n        <path d=\"M21 11V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h6\" />\n      </symbol>\n\n      <symbol id=\"icon-next\" viewBox=\"0 0 24 24\" fill=\"none\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n        <path d=\"M6 9h6V5l7 7-7 7v-4H6V9z\" />\n      </symbol>\n\n      <symbol id=\"icon-previous\" viewBox=\"0 0 24 24\" fill=\"none\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n        <path d=\"M18 15h-6v4l-7-7 7-7v4h6v6z\" />\n      </symbol>\n\n      <symbol id=\"icon-close\" viewBox=\"0 0 24 24\" fill=\"none\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n        <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n        <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n      </symbol>\n\n      <symbol id=\"icon-replay\" viewBox=\"0 0 24 24\" fill=\"none\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n        <path d=\"M3 7V5a2 2 0 0 1 2-2h2\" />\n        <path d=\"M17 3h2a2 2 0 0 1 2 2v2\" />\n        <path d=\"M21 17v2a2 2 0 0 1-2 2h-2\" />\n        <path d=\"M7 21H5a2 2 0 0 1-2-2v-2\" />\n        <circle cx=\"12\" cy=\"12\" r=\"1\" />\n        <path d=\"M18.944 12.33a1 1 0 0 0 0-.66 7.5 7.5 0 0 0-13.888 0 1 1 0 0 0 0 .66 7.5 7.5 0 0 0 13.888 0\" />\n      </symbol>\n\n      <symbol id=\"icon-ellipsis\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n        <circle cx=\"12\" cy=\"12\" r=\"1\" />\n        <circle cx=\"19\" cy=\"12\" r=\"1\" />\n        <circle cx=\"5\" cy=\"12\" r=\"1\" />\n      </symbol>\n\n      <symbol id=\"icon-copy\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n        <rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\" />\n        <path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\" />\n      </symbol>\n\n      <symbol id=\"icon-check\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n        <path d=\"M20 6 9 17l-5-5\" />\n      </symbol>\n\n      <symbol id=\"icon-chevron-right\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n        <path d=\"m9 18 6-6-6-6\" />\n      </symbol>\n\n      <symbol id=\"icon-settings\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n        <path d=\"M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z\" /><circle cx=\"12\" cy=\"12\" r=\"3\" />\n      </symbol>\n\n      <symbol id=\"icon-flame\" viewBox=\"0 0 24 24\">\n        <path d=\"M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z\" />\n      </symbol>\n\n      <symbol id=\"icon-function\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n        <rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\" />\n        <path d=\"M9 17c2 0 2.8-1 2.8-2.8V10c0-2 1-3.3 3.2-3\" />\n        <path d=\"M9 11.2h5.7\" />\n      </symbol>\n\n      <symbol id=\"icon-triangle-alert\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n        <path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\" />\n        <path d=\"M12 9v4\" />\n        <path d=\"M12 17h.01\" />\n      </symbol>\n\n      <symbol id=\"icon-gallery-horizontal-end\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n        <path d=\"M2 7v10\" /><path d=\"M6 5v14\" />\n        <rect width=\"12\" height=\"18\" x=\"10\" y=\"3\" rx=\"2\" />\n      </symbol>\n\n      <symbol id=\"icon-search\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n        <circle cx=\"11\" cy=\"11\" r=\"8\" />\n        <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\" />\n      </symbol>\n\n      <symbol id=\"icon-lock\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n        <rect width=\"18\" height=\"11\" x=\"3\" y=\"11\" rx=\"2\" ry=\"2\" />\n        <path d=\"M7 11V7a5 5 0 0 1 10 0v4\" />\n      </symbol>\n\n      <symbol id=\"icon-lock-open\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n        <rect width=\"18\" height=\"11\" x=\"3\" y=\"11\" rx=\"2\" ry=\"2\" />\n        <path d=\"M7 11V7a5 5 0 0 1 9.9-1\" />\n      </symbol>\n\n      <symbol id=\"icon-sanil\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n        <path d=\"M2 13a6 6 0 1 0 12 0 4 4 0 1 0-8 0 2 2 0 0 0 4 0\" />\n        <circle cx=\"10\" cy=\"13\" r=\"8\" />\n        <path d=\"M2 21h12c4.4 0 8-3.6 8-8V7a2 2 0 1 0-4 0v6\" />\n        <path d=\"M18 3 19.1 5.2\" />\n      </symbol>\n    </svg>\n  )\n};\n"
  },
  {
    "path": "packages/scan/src/web/components/toggle/index.tsx",
    "content": "import type { JSX } from 'preact';\nimport { cn } from '~web/utils/helpers';\n\ninterface ToggleProps extends JSX.HTMLAttributes<HTMLInputElement> {\n  checked: boolean;\n  onChange: ((e: Event) => void);\n  className?: string;\n};\n\nexport const Toggle = ({\n  className,\n  ...props\n}: ToggleProps) => {\n  return (\n    <div className={cn('react-scan-toggle', className)}>\n      <input\n        type=\"checkbox\"\n        {...props}\n      />\n      <div />\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/scan/src/web/constants.ts",
    "content": "export const SAFE_AREA = 24;\nexport const MIN_SIZE = {\n  width: 550,\n  height: 350,\n  initialHeight: 400,\n} as const;\n\nexport const MIN_CONTAINER_WIDTH = 240;\n\nexport const LOCALSTORAGE_KEY = \"react-scan-widget-settings-v2\";\nexport const LOCALSTORAGE_COLLAPSED_KEY = \"react-scan-widget-collapsed-v1\";\nexport const LOCALSTORAGE_LAST_VIEW_KEY = \"react-scan-widget-last-view-v1\";\n"
  },
  {
    "path": "packages/scan/src/web/hooks/use-delayed-value.ts",
    "content": "import { useEffect, useState } from 'preact/hooks';\n\n/**\n * Delays a boolean value change by a specified duration.\n * Perfect for coordinating animations with state changes.\n *\n * @param {boolean} value - The boolean value to delay\n * @param {number} onDelay - Milliseconds to wait before changing to true\n * @param {number} [offDelay] - Milliseconds to wait before changing to false (defaults to onDelay)\n * @returns {boolean} The delayed value\n *\n * @example\n * // Delay both transitions by 300ms\n * const isVisible = useDelayedValue(show, 300);\n *\n * @example\n * // Quick show (100ms), slow hide (500ms)\n * const isVisible = useDelayedValue(show, 100, 500);\n *\n * @example\n * // Use with CSS transitions\n * const isVisible = useDelayedValue(show, 300);\n * return (\n *   <div\n *     className=\"transition-all duration-300\"\n *     style={{\n *       opacity: isVisible ? 1 : 0,\n *       transform: isVisible ? 'none' : 'translateY(4px)'\n *     }}\n *   >\n *     {content}\n *   </div>\n * );\n */\nexport const useDelayedValue = (\n  value: boolean,\n  onDelay: number,\n  offDelay: number = onDelay,\n): boolean => {\n  const [delayedValue, setDelayedValue] = useState(value);\n\n  /*\n   * oxlint-disable-next-line react-hooks/exhaustive-deps\n   * delayedValue is intentionally omitted to prevent unnecessary timeouts\n   * and used only in the early return check\n   */\n  useEffect(() => {\n    if (value === delayedValue) return;\n\n    const delay = value ? onDelay : offDelay;\n    const timeout = setTimeout(() => setDelayedValue(value), delay);\n\n    return () => clearTimeout(timeout);\n  }, [value, onDelay, offDelay]);\n\n  return delayedValue;\n};\n"
  },
  {
    "path": "packages/scan/src/web/hooks/use-merged-refs.ts",
    "content": "import type { Ref, RefCallback } from 'preact';\nimport { type MutableRefObject, useCallback } from 'preact/compat';\n\ntype PossibleRef<T> = Ref<T> | undefined;\n\nconst assignRef = <T>(ref: PossibleRef<T>, value: T) => {\n  if (typeof ref === 'function') {\n    ref(value);\n  } else if (ref !== null) {\n    (ref as MutableRefObject<T>).current = value;\n  }\n};\n\nconst mergeRefs = <T>(...refs: PossibleRef<T>[]) => {\n  return (node: T) => {\n    for (const ref of refs) {\n      if (ref) {\n        assignRef(ref, node);\n      }\n    }\n  };\n};\n\nexport const useMergedRefs = <T>(...refs: PossibleRef<T>[]) => {\n  return useCallback(mergeRefs(...refs), [...refs]) as RefCallback<T>;\n};\n"
  },
  {
    "path": "packages/scan/src/web/hooks/use-virtual-list.ts",
    "content": "import {\n  useCallback,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'preact/hooks';\n\nexport interface VirtualItem {\n  key: number;\n  index: number;\n  start: number;\n}\n\nexport const useVirtualList = (options: {\n  count: number;\n  getScrollElement: () => HTMLElement | null;\n  estimateSize: () => number;\n  overscan?: number;\n}) => {\n  const { count, getScrollElement, estimateSize, overscan = 5 } = options;\n  const [scrollTop, setScrollTop] = useState(0);\n  const [containerHeight, setContainerHeight] = useState(0);\n  const refResizeObserver = useRef<ResizeObserver>();\n  const refScrollElement = useRef<HTMLElement | null>(null);\n  const refRafId = useRef<number | null>(null);\n  const itemHeight = estimateSize();\n\n  const updateContainer = useCallback((entries?: ResizeObserverEntry[]) => {\n    if (!refScrollElement.current) return;\n\n    const height =\n      entries?.[0]?.contentRect.height ??\n      refScrollElement.current.getBoundingClientRect().height;\n    setContainerHeight(height);\n  }, []);\n\n  const debouncedUpdateContainer = useCallback(() => {\n    if (refRafId.current !== null) {\n      cancelAnimationFrame(refRafId.current);\n    }\n    refRafId.current = requestAnimationFrame(() => {\n      updateContainer();\n      refRafId.current = null;\n    });\n  }, [updateContainer]);\n\n  useEffect(() => {\n    const element = getScrollElement();\n    if (!element) return;\n\n    refScrollElement.current = element;\n\n    const handleScroll = () => {\n      if (!refScrollElement.current) return;\n      setScrollTop(refScrollElement.current.scrollTop);\n    };\n\n    updateContainer();\n\n    if (!refResizeObserver.current) {\n      refResizeObserver.current = new ResizeObserver(() => {\n        debouncedUpdateContainer();\n      });\n    }\n    refResizeObserver.current.observe(element);\n\n    element.addEventListener('scroll', handleScroll, { passive: true });\n\n    const mutationObserver = new MutationObserver(debouncedUpdateContainer);\n    mutationObserver.observe(element, {\n      attributes: true,\n      childList: true,\n      subtree: true,\n    });\n\n    return () => {\n      element.removeEventListener('scroll', handleScroll);\n      if (refResizeObserver.current) {\n        refResizeObserver.current.disconnect();\n      }\n      mutationObserver.disconnect();\n      if (refRafId.current !== null) {\n        cancelAnimationFrame(refRafId.current);\n      }\n    };\n  }, [getScrollElement, updateContainer, debouncedUpdateContainer]);\n\n  const visibleRange = useMemo(() => {\n    const start = Math.floor(scrollTop / itemHeight);\n    const visibleCount = Math.ceil(containerHeight / itemHeight);\n\n    return {\n      start: Math.max(0, start - overscan),\n      end: Math.min(count, start + visibleCount + overscan),\n    };\n  }, [scrollTop, itemHeight, containerHeight, count, overscan]);\n\n  const items = useMemo(() => {\n    const virtualItems: VirtualItem[] = [];\n    for (let index = visibleRange.start; index < visibleRange.end; index++) {\n      virtualItems.push({\n        key: index,\n        index,\n        start: index * itemHeight,\n      });\n    }\n    return virtualItems;\n  }, [visibleRange, itemHeight]);\n\n  return {\n    virtualItems: items,\n    totalSize: count * itemHeight,\n    scrollTop,\n    containerHeight,\n  };\n};\n"
  },
  {
    "path": "packages/scan/src/web/state.ts",
    "content": "import { signal } from \"@preact/signals\";\nimport {\n  LOCALSTORAGE_KEY,\n  MIN_CONTAINER_WIDTH,\n  MIN_SIZE,\n  SAFE_AREA,\n  LOCALSTORAGE_COLLAPSED_KEY,\n} from \"./constants\";\nimport { IS_CLIENT } from \"./utils/constants\";\nimport { readLocalStorage, saveLocalStorage } from \"./utils/helpers\";\nimport type { Corner, WidgetConfig, WidgetSettings } from \"./widget/types\";\nimport type { CollapsedPosition } from \"./widget/types\";\n\nexport const signalIsSettingsOpen = /* @__PURE__ */ signal(false);\nexport const signalRefWidget = /* @__PURE__ */ signal<HTMLDivElement | null>(\n  null\n);\n\nexport const defaultWidgetConfig = {\n  corner: \"bottom-right\" as Corner,\n  dimensions: {\n    isFullWidth: false,\n    isFullHeight: false,\n    width: MIN_SIZE.width,\n    height: MIN_SIZE.height,\n    position: { x: SAFE_AREA, y: SAFE_AREA },\n  },\n  lastDimensions: {\n    isFullWidth: false,\n    isFullHeight: false,\n    width: MIN_SIZE.width,\n    height: MIN_SIZE.height,\n    position: { x: SAFE_AREA, y: SAFE_AREA },\n  },\n  componentsTree: {\n    width: MIN_CONTAINER_WIDTH,\n  },\n} as WidgetConfig;\n\nexport const getInitialWidgetConfig = (): WidgetConfig => {\n  const stored = readLocalStorage<WidgetSettings>(LOCALSTORAGE_KEY);\n  if (!stored) {\n    saveLocalStorage(LOCALSTORAGE_KEY, {\n      corner: defaultWidgetConfig.corner,\n      dimensions: defaultWidgetConfig.dimensions,\n      lastDimensions: defaultWidgetConfig.lastDimensions,\n      componentsTree: defaultWidgetConfig.componentsTree,\n    });\n\n    return defaultWidgetConfig;\n  }\n\n  return {\n    corner: stored.corner ?? defaultWidgetConfig.corner,\n    dimensions: stored.dimensions ?? defaultWidgetConfig.dimensions,\n\n    lastDimensions:\n      stored.lastDimensions ??\n      stored.dimensions ??\n      defaultWidgetConfig.lastDimensions,\n    componentsTree: stored.componentsTree ?? defaultWidgetConfig.componentsTree,\n  };\n};\n\nexport const signalWidget = signal<WidgetConfig>(getInitialWidgetConfig());\n\nexport const updateDimensions = (): void => {\n  if (!IS_CLIENT) return;\n\n  const { dimensions } = signalWidget.value;\n  const { width, height, position } = dimensions;\n\n  signalWidget.value = {\n    ...signalWidget.value,\n    dimensions: {\n      isFullWidth: width >= window.innerWidth - SAFE_AREA * 2,\n      isFullHeight: height >= window.innerHeight - SAFE_AREA * 2,\n      width,\n      height,\n      position,\n    },\n  };\n};\n\nexport interface SlowDowns {\n  slowDowns: number;\n  hideNotification: boolean;\n}\n\nexport type WidgetStates =\n  | {\n      view: \"none\";\n    }\n  | {\n      view: \"inspector\";\n      // extra params\n    }\n  // | {\n  //     view: 'settings';\n  //     // extra params\n  //   }\n  | {\n      view: \"notifications\";\n      // extra params\n    };\n// | {\n//     view: 'summary';\n//     // extra params\n//   };\nexport const signalWidgetViews = signal<WidgetStates>({\n  view: \"none\",\n});\n\nconst storedCollapsed = readLocalStorage<CollapsedPosition | null>(\n  LOCALSTORAGE_COLLAPSED_KEY\n);\nexport const signalWidgetCollapsed =\n  /* @__PURE__ */ signal<CollapsedPosition | null>(storedCollapsed ?? null);\n"
  },
  {
    "path": "packages/scan/src/web/toolbar.tsx",
    "content": "import { Component, render } from 'preact';\nimport { Icon } from './components/icon';\nimport { Widget } from './widget';\nimport { SvgSprite } from './components/svg-sprite';\n\n\nclass ToolbarErrorBoundary extends Component {\n  state: { hasError: boolean; error: Error | null } = { hasError: false, error: null };\n\n  static getDerivedStateFromError(error: Error) {\n    return { hasError: true, error };\n  }\n\n  handleReset = () => {\n    this.setState({ hasError: false, error: null });\n  };\n\n  render() {\n    if (this.state.hasError) {\n      return (\n        <div className=\"fixed bottom-4 right-4 z-[124124124124]\">\n          <div className=\"p-3 bg-black rounded-lg shadow-lg w-80\">\n            <div className=\"flex items-center gap-2 mb-2 text-red-400 text-sm font-medium\">\n              <Icon name=\"icon-flame\" className=\"text-red-500\" size={14} />\n              React Scan ran into a problem\n            </div>\n            <div className=\"p-2 bg-black rounded font-mono text-xs text-red-300 mb-3 break-words\">\n              {this.state.error?.message || JSON.stringify(this.state.error)}\n            </div>\n            <button\n              type=\"button\"\n              onClick={this.handleReset}\n              className=\"px-3 py-1.5 bg-red-500 hover:bg-red-600 text-white rounded text-xs font-medium transition-colors flex items-center justify-center gap-1.5\"\n            >\n              Restart\n            </button>\n          </div>\n        </div>\n      );\n    }\n\n    return this.props.children;\n  }\n}\n\nexport const createToolbar = (root: ShadowRoot): HTMLElement => {\n  const container = document.createElement('div');\n  container.id = 'react-scan-toolbar-root';\n  window.__REACT_SCAN_TOOLBAR_CONTAINER__ = container;\n  root.appendChild(container);\n\n  render(\n    <ToolbarErrorBoundary>\n      <>\n        <SvgSprite />\n        <Widget />\n      </>\n    </ToolbarErrorBoundary>,\n    container,\n  );\n\n  const originalRemove = container.remove.bind(container);\n\n  container.remove = () => {\n    window.__REACT_SCAN_TOOLBAR_CONTAINER__ = undefined;\n\n    if (container.hasChildNodes()) {\n      // Double render(null) is needed to fully unmount Preact components.\n      // The first call initiates unmounting, while the second ensures\n      // cleanup of internal VNode references and event listeners.\n      render(null, container);\n      render(null, container);\n    }\n\n    originalRemove();\n  };\n\n  return container;\n};\n"
  },
  {
    "path": "packages/scan/src/web/utils/constants.ts",
    "content": "export const IS_CLIENT = typeof window !== 'undefined';\n"
  },
  {
    "path": "packages/scan/src/web/utils/create-store.ts",
    "content": "/**\n * Adapted from zustand v5.0.3\n *\n * https://github.com/pmndrs/zustand\n *\n * Do not modify unless you know what you are doing\n */\ntype SetStateInternal<T> = {\n  _(\n    partial: T | Partial<T> | { _(state: T): T | Partial<T> }['_'],\n    replace?: false,\n  ): void;\n  _(state: T | { _(state: T): T }['_'], replace: true): void;\n}['_'];\n\nexport interface StoreApi<T> {\n  setState: SetStateInternal<T>;\n  getState: () => T;\n  getInitialState: () => T;\n  subscribe: {\n    (listener: (state: T, prevState: T) => void): () => void;\n    <U>(\n      selector: (state: T) => U,\n      listener: (selectedState: U, prevSelectedState: U) => void,\n    ): () => void;\n  };\n}\n\nexport type ExtractState<S> = S extends { getState: () => infer T } ? T : never;\n\ntype Get<T, K, F> = K extends keyof T ? T[K] : F;\n\nexport type Mutate<S, Ms> = number extends Ms['length' & keyof Ms]\n  ? S\n  : Ms extends []\n    ? S\n    : Ms extends [[infer Mi, infer Ma], ...infer Mrs]\n      ? Mutate<StoreMutators<S, Ma>[Mi & StoreMutatorIdentifier], Mrs>\n      : never;\n\nexport type StateCreator<\n  T,\n  Mis extends [StoreMutatorIdentifier, unknown][] = [],\n  Mos extends [StoreMutatorIdentifier, unknown][] = [],\n  U = T,\n> = ((\n  setState: Get<Mutate<StoreApi<T>, Mis>, 'setState', never>,\n  getState: Get<Mutate<StoreApi<T>, Mis>, 'getState', never>,\n  store: Mutate<StoreApi<T>, Mis>,\n) => U) & { $$storeMutators?: Mos };\n\n// oxlint-disable-next-line no-unused-vars\nexport interface StoreMutators<S, A> {}\nexport type StoreMutatorIdentifier = keyof StoreMutators<unknown, unknown>;\n\ntype CreateStore = {\n  <T, Mos extends [StoreMutatorIdentifier, unknown][] = []>(\n    initializer: StateCreator<T, [], Mos>,\n  ): Mutate<StoreApi<T>, Mos>;\n\n  <T>(): <Mos extends [StoreMutatorIdentifier, unknown][] = []>(\n    initializer: StateCreator<T, [], Mos>,\n  ) => Mutate<StoreApi<T>, Mos>;\n};\n\ntype CreateStoreImpl = <\n  T,\n  Mos extends [StoreMutatorIdentifier, unknown][] = [],\n>(\n  initializer: StateCreator<T, [], Mos>,\n) => Mutate<StoreApi<T>, Mos>;\n\nconst createStoreImpl: CreateStoreImpl = (createState) => {\n  type TState = ReturnType<typeof createState>;\n  type Listener = (state: TState, prevState: TState) => void;\n  let state: TState;\n  const listeners: Set<Listener> = new Set();\n\n  const setState: StoreApi<TState>['setState'] = (partial, replace) => {\n    const nextState =\n      typeof partial === 'function'\n        ? (partial as (state: TState) => TState)(state)\n        : partial;\n    if (!Object.is(nextState, state)) {\n      const previousState = state;\n      state =\n        (replace ?? (typeof nextState !== 'object' || nextState === null))\n          ? (nextState as TState)\n          : Object.assign({}, state, nextState);\n      listeners.forEach((listener) => listener(state, previousState));\n    }\n  };\n\n  const getState: StoreApi<TState>['getState'] = () => state;\n\n  const getInitialState: StoreApi<TState>['getInitialState'] = () =>\n    initialState;\n\n  const subscribe: StoreApi<TState>['subscribe'] = (\n    selectorOrListener:\n      | ((state: TState, prevState: TState) => void)\n      // oxlint-disable-next-line typescript/no-explicit-any\n      | ((state: TState) => any),\n    // oxlint-disable-next-line typescript/no-explicit-any\n    listener?: (selectedState: any, prevSelectedState: any) => void,\n  ) => {\n    // oxlint-disable-next-line typescript/no-explicit-any\n    let selector: ((state: TState) => any) | undefined;\n    // oxlint-disable-next-line typescript/no-explicit-any\n    let actualListener: (state: any, prevState: any) => void;\n\n    if (listener) {\n      // Selector subscription case\n      // oxlint-disable-next-line typescript/no-explicit-any\n      selector = selectorOrListener as (state: TState) => any;\n      actualListener = listener;\n    } else {\n      // Regular subscription case\n      actualListener = selectorOrListener as (\n        state: TState,\n        prevState: TState,\n      ) => void;\n    }\n\n    let currentSlice = selector ? selector(state) : undefined;\n\n    const wrappedListener = (newState: TState, previousState: TState) => {\n      if (selector) {\n        const nextSlice = selector(newState);\n        const prevSlice = selector(previousState);\n        if (!Object.is(currentSlice, nextSlice)) {\n          currentSlice = nextSlice;\n          actualListener(nextSlice, prevSlice);\n        }\n      } else {\n        actualListener(newState, previousState);\n      }\n    };\n\n    listeners.add(wrappedListener);\n    // Unsubscribe\n    return () => listeners.delete(wrappedListener);\n  };\n\n  const api = { setState, getState, getInitialState, subscribe };\n  const initialState = (state = createState(setState, getState, api));\n  // oxlint-disable-next-line typescript/no-explicit-any\n  return api as any;\n};\n\nexport const createStore = ((createState) =>\n  createState ? createStoreImpl(createState) : createStoreImpl) as CreateStore;\n"
  },
  {
    "path": "packages/scan/src/web/utils/geiger.ts",
    "content": "// MIT License\n// Copyright (c) 2025 Kristian Dupont\n\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n// Taken from: https://github.com/kristiandupont/react-geiger/blob/main/src/Geiger.tsx\n"
  },
  {
    "path": "packages/scan/src/web/utils/helpers.ts",
    "content": "import {\n  type Fiber,\n  MemoComponentTag,\n  SimpleMemoComponentTag,\n  SuspenseComponentTag,\n  getDisplayName,\n  hasMemoCache,\n} from 'bippy';\nimport { type ClassValue, clsx } from 'clsx';\nimport { IS_CLIENT } from './constants';\nimport { twMerge } from 'tailwind-merge';\n\nexport const cn = (...inputs: Array<ClassValue>): string => {\n  return twMerge(clsx(inputs));\n};\n\nexport const isFirefox =\n  /* @__PURE__ */ typeof navigator !== 'undefined' &&\n  navigator.userAgent.includes('Firefox');\n\nexport const onIdle = (callback: () => void) => {\n  if ('scheduler' in globalThis) {\n    return globalThis.scheduler.postTask(callback, {\n      priority: 'background',\n    });\n  }\n  if ('requestIdleCallback' in window) {\n    return requestIdleCallback(callback);\n  }\n  return setTimeout(callback, 0);\n};\n\nexport const throttle = <E>(\n  callback: (e?: E) => void,\n  delay: number,\n): ((e?: E) => void) => {\n  let lastCall = 0;\n  return (e?: E) => {\n    const now = Date.now();\n    if (now - lastCall >= delay) {\n      lastCall = now;\n      return callback(e);\n    }\n    return undefined;\n  };\n};\n\nexport const tryOrElse = <T>(fn: () => T, defaultValue: T): T => {\n  try {\n    return fn();\n  } catch {\n    return defaultValue;\n  }\n};\n\nexport const readLocalStorage = <T>(storageKey: string): T | null => {\n  if (!IS_CLIENT) return null;\n\n  try {\n    const stored = localStorage.getItem(storageKey);\n    return stored ? JSON.parse(stored) : null;\n  } catch {\n    return null;\n  }\n};\n\nexport const saveLocalStorage = <T>(storageKey: string, state: T): void => {\n  if (!IS_CLIENT) return;\n\n  try {\n    window.localStorage.setItem(storageKey, JSON.stringify(state));\n  } catch {}\n};\nexport const removeLocalStorage = (storageKey: string): void => {\n  if (!IS_CLIENT) return;\n\n  try {\n    window.localStorage.removeItem(storageKey);\n  } catch {}\n};\n\ninterface WrapperBadge {\n  type: 'memo' | 'forwardRef' | 'lazy' | 'suspense' | 'profiler' | 'strict';\n  title: string;\n  compiler?: boolean;\n}\n\nexport interface ExtendedDisplayName {\n  name: string | null;\n  wrappers: Array<string>;\n  wrapperTypes: Array<WrapperBadge>;\n}\n\n// React internal tags not exported by bippy\nconst LazyComponentTag = 24;\nconst ProfilerTag = 12;\n\nexport const getExtendedDisplayName = (fiber: Fiber): ExtendedDisplayName => {\n  if (!fiber) {\n    return {\n      name: 'Unknown',\n      wrappers: [],\n      wrapperTypes: [],\n    };\n  }\n\n  const { tag, type, elementType } = fiber;\n  let name = getDisplayName(type);\n  const wrappers: Array<string> = [];\n  const wrapperTypes: Array<WrapperBadge> = [];\n\n  if (\n    hasMemoCache(fiber) ||\n    tag === SimpleMemoComponentTag ||\n    tag === MemoComponentTag ||\n    (type as { $$typeof?: symbol })?.$$typeof === Symbol.for('react.memo') ||\n    (elementType as { $$typeof?: symbol })?.$$typeof ===\n      Symbol.for('react.memo')\n  ) {\n    const compiler = hasMemoCache(fiber);\n    wrapperTypes.push({\n      type: 'memo',\n      title: compiler\n        ? 'This component has been auto-memoized by the React Compiler.'\n        : 'Memoized component that skips re-renders if props are the same',\n      compiler,\n    });\n  }\n\n  if (tag === LazyComponentTag) {\n    wrapperTypes.push({\n      type: 'lazy',\n      title: 'Lazily loaded component that supports code splitting',\n    });\n  }\n\n  if (tag === SuspenseComponentTag) {\n    wrapperTypes.push({\n      type: 'suspense',\n      title: 'Component that can suspend while content is loading',\n    });\n  }\n\n  if (tag === ProfilerTag) {\n    wrapperTypes.push({\n      type: 'profiler',\n      title: 'Component that measures rendering performance',\n    });\n  }\n\n  if (typeof name === 'string') {\n    const wrapperRegex = /^(\\w+)\\((.*)\\)$/;\n    let currentName = name;\n    while (wrapperRegex.test(currentName)) {\n      const match = currentName.match(wrapperRegex);\n      if (match?.[1] && match?.[2]) {\n        wrappers.unshift(match[1]);\n        currentName = match[2];\n      } else {\n        break;\n      }\n    }\n    name = currentName;\n  }\n\n  return {\n    name: name || 'Unknown',\n    wrappers,\n    wrapperTypes,\n  };\n};\n"
  },
  {
    "path": "packages/scan/src/web/utils/log.ts",
    "content": "// @ts-nocheck\nimport { ChangeReason, type Render } from '~core/instrumentation';\nimport { getLabelText } from '~core/utils';\n\nexport const log = (renders: Array<Render>) => {\n  const logMap = new Map<\n    string,\n    Array<{ prev: unknown; next: unknown; type: string; unstable?: boolean }>\n  >();\n  for (let i = 0, len = renders.length; i < len; i++) {\n    const render = renders[i];\n\n    if (!render.componentName) continue;\n\n    const changeLog = logMap.get(render.componentName) ?? [];\n    renders;\n    const labelText = getLabelText([\n      {\n        aggregatedCount: 1,\n\n        computedKey: null,\n        name: render.componentName,\n        frame: null,\n        ...render,\n        changes: {\n          // TODO(Alexis): use a faster reduction method\n          type: render.changes.reduce((set, change) => set | change.type, 0),\n          unstable: render.changes.some((change) => change.unstable),\n        },\n        phase: render.phase,\n        computedCurrent: null,\n      },\n    ]);\n    if (!labelText) continue;\n\n    let prevChangedProps: Record<string, unknown> | null = null;\n    let nextChangedProps: Record<string, unknown> | null = null;\n\n    if (render.changes) {\n      for (let i = 0, len = render.changes.length; i < len; i++) {\n        const { name, prevValue, nextValue, unstable, type } =\n          render.changes[i];\n        if (type === ChangeReason.Props) {\n          prevChangedProps ??= {};\n          nextChangedProps ??= {};\n          prevChangedProps[`${unstable ? '⚠️' : ''}${name} (prev)`] = prevValue;\n          nextChangedProps[`${unstable ? '⚠️' : ''}${name} (next)`] = nextValue;\n        } else {\n          changeLog.push({\n            prev: prevValue,\n            next: nextValue,\n            type: type === ChangeReason.Context ? 'context' : 'state',\n            unstable: unstable ?? false,\n          });\n        }\n      }\n    }\n\n    if (prevChangedProps && nextChangedProps) {\n      changeLog.push({\n        prev: prevChangedProps,\n        next: nextChangedProps,\n        type: 'props',\n        unstable: false,\n      });\n    }\n\n    logMap.set(labelText, changeLog);\n  }\n  for (const [name, changeLog] of Array.from(logMap.entries())) {\n    // oxlint-disable-next-line no-console\n    console.group(\n      `%c${name}`,\n      'background: hsla(0,0%,70%,.3); border-radius:3px; padding: 0 2px;',\n    );\n    for (const { type, prev, next, unstable } of changeLog) {\n      // oxlint-disable-next-line no-console\n      console.log(`${type}:`, unstable ? '⚠️' : '', prev, '!==', next);\n    }\n    // oxlint-disable-next-line no-console\n    console.groupEnd();\n  }\n};\n\nexport const logIntro = () => {\n  if (window.hideIntro) {\n    window.hideIntro = undefined;\n    return;\n  }\n  // oxlint-disable-next-line no-console\n  console.log(\n    '%c[·] %cReact Scan',\n    'font-weight:bold;color:#7a68e8;font-size:20px;',\n    'font-weight:bold;font-size:14px;',\n  );\n};\n"
  },
  {
    "path": "packages/scan/src/web/utils/pin.ts",
    "content": "import type { Fiber } from 'bippy';\n\nexport const getFiberPath = (fiber: Fiber): string => {\n  const pathSegments: string[] = [];\n  let currentFiber: Fiber | null = fiber;\n\n  while (currentFiber) {\n    const elementType = currentFiber.elementType;\n    const name =\n      typeof elementType === 'function'\n        ? elementType.displayName || elementType.name\n        : typeof elementType === 'string'\n          ? elementType\n          : 'Unknown';\n\n    const index =\n      currentFiber.index !== undefined ? `[${currentFiber.index}]` : '';\n    pathSegments.unshift(`${name}${index}`);\n\n    currentFiber = currentFiber.return ?? null;\n  }\n\n  return pathSegments.join('::');\n};\n"
  },
  {
    "path": "packages/scan/src/web/utils/preact/constant.ts",
    "content": "import {\n  type Attributes,\n  type Component,\n  type FunctionComponent,\n  createElement,\n} from 'preact';\n\nfunction CONSTANT_UPDATE() {\n  return false;\n}\n\nexport function constant<P extends Attributes>(\n  Component: FunctionComponent<P>,\n) {\n  function Memoed(this: Component<P>, props: P) {\n    this.shouldComponentUpdate = CONSTANT_UPDATE;\n    return createElement<P>(Component, props);\n  }\n  Memoed.displayName = `Memo(${Component.displayName || Component.name})`;\n  Memoed.prototype.isReactComponent = true;\n  Memoed._forwarded = true;\n  return Memoed;\n}\n"
  },
  {
    "path": "packages/scan/src/web/views/index.tsx",
    "content": "import { type ReadonlySignal, computed } from '@preact/signals';\nimport type { ReactNode } from 'preact/compat';\nimport { Store } from '~core/index';\nimport { signalWidgetViews } from '~web/state';\nimport { cn } from '~web/utils/helpers';\nimport { Header } from '~web/widget/header';\nimport { ViewInspector } from './inspector';\nimport { Toolbar } from './toolbar';\nimport { NotificationWrapper } from './notifications/notifications';\n\nconst isInspecting = computed(\n  () => Store.inspectState.value.kind === 'inspecting',\n);\n\nconst headerClassName = computed(() =>\n  cn(\n    'relative',\n    'flex-1',\n    'flex flex-col',\n    'rounded-t-lg',\n    'overflow-hidden',\n    'opacity-100',\n    'transition-[opacity]',\n    isInspecting.value && 'opacity-0 duration-0 delay-0',\n  ),\n);\n\nconst isInspectorViewOpen = computed(\n  () => signalWidgetViews.value.view === 'inspector',\n);\nconst isNotificationsViewOpen = computed(\n  () => signalWidgetViews.value.view === 'notifications',\n);\n\nexport const Content = () => {\n  return (\n    <div\n      className={cn(\n        'flex flex-1 flex-col',\n        'overflow-hidden z-10',\n        'rounded-lg',\n        'bg-black',\n        'opacity-100',\n        'transition-[border-radius]',\n        'peer-hover/left:rounded-l-none',\n        'peer-hover/right:rounded-r-none',\n        'peer-hover/top:rounded-t-none',\n        'peer-hover/bottom:rounded-b-none',\n      )}\n    >\n      <div className={headerClassName}>\n        <Header />\n        <div\n          className={cn(\n            'relative',\n            'flex-1 flex',\n            'text-white',\n            'bg-[#0A0A0A]',\n            'transition-opacity delay-150',\n            'overflow-hidden',\n            'border-b border-[#222]',\n          )}\n        >\n          <ContentView isOpen={isInspectorViewOpen}>\n            <ViewInspector />\n          </ContentView>\n\n          <ContentView isOpen={isNotificationsViewOpen}>\n            <NotificationWrapper />\n          </ContentView>\n        </div>\n      </div>\n      <Toolbar />\n    </div>\n  );\n};\n\ninterface ContentViewProps {\n  isOpen: ReadonlySignal<boolean>;\n  children: ReactNode;\n}\n\nconst ContentView = ({ isOpen, children }: ContentViewProps) => {\n  return (\n    <div\n      className={cn(\n        'flex-1',\n        'opacity-0',\n        'overflow-y-auto overflow-x-hidden',\n        'transition-opacity delay-0',\n        'pointer-events-none',\n        isOpen.value && 'opacity-100 delay-150 pointer-events-auto',\n      )}\n    >\n      <div className=\"absolute inset-0 flex\">{children}</div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/scan/src/web/views/inspector/components-tree/index.tsx",
    "content": "import {\n  useCallback,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'preact/hooks';\nimport { Store } from '~core/index';\nimport { getRenderData } from '~core/instrumentation';\nimport { Icon } from '~web/components/icon';\nimport {\n  LOCALSTORAGE_KEY,\n  MIN_CONTAINER_WIDTH,\n} from '~web/constants';\nimport { useVirtualList } from '~web/hooks/use-virtual-list';\nimport { signalWidget } from '~web/state';\nimport {\n  cn,\n  getExtendedDisplayName,\n  saveLocalStorage,\n} from '~web/utils/helpers';\nimport { getFiberPath } from '~web/utils/pin';\nimport { inspectorUpdateSignal } from '../states';\nimport {\n  type InspectableElement,\n  getCompositeComponentFromElement,\n  getInspectableElements,\n} from '../utils';\nimport {\n  type FlattenedNode,\n  type TreeNode,\n  searchState,\n  signalSkipTreeUpdate,\n} from './state';\n\nconst flattenTree = (\n  nodes: TreeNode[],\n  depth = 0,\n  parentPath: string | null = null,\n): FlattenedNode[] => {\n  return nodes.reduce<FlattenedNode[]>((acc, node, index) => {\n    const nodePath = node.element\n      ? getFiberPath(node.fiber)\n      : `${parentPath}-${index}`;\n\n    const renderData = node.fiber?.type\n      ? getRenderData(node.fiber)\n      : undefined;\n\n    const flatNode: FlattenedNode = {\n      ...node,\n      depth,\n      nodeId: nodePath,\n      parentId: parentPath,\n      fiber: node.fiber,\n      renderData,\n    };\n    acc.push(flatNode);\n\n    if (node.children?.length) {\n      acc.push(...flattenTree(node.children, depth + 1, nodePath));\n    }\n\n    return acc;\n  }, []);\n};\n\nconst getMaxDepth = (nodes: FlattenedNode[]): number => {\n  return nodes.reduce((max, node) => Math.max(max, node.depth), 0);\n};\n\nconst calculateIndentSize = (containerWidth: number, maxDepth: number) => {\n  const MIN_INDENT = 0;\n  const MAX_INDENT = 24;\n  const MIN_TOTAL_INDENT = 24;\n\n  if (maxDepth <= 0) return MAX_INDENT;\n\n  const availableSpace = Math.max(0, containerWidth - MIN_CONTAINER_WIDTH);\n\n  if (availableSpace < MIN_TOTAL_INDENT) return MIN_INDENT;\n\n  const targetTotalIndent = Math.min(\n    availableSpace * 0.3,\n    maxDepth * MAX_INDENT,\n  );\n  const baseIndent = targetTotalIndent / maxDepth;\n\n  return Math.max(MIN_INDENT, Math.min(MAX_INDENT, baseIndent));\n};\n\ninterface TreeNodeItemProps {\n  node: FlattenedNode;\n  nodeIndex: number;\n  hasChildren: boolean;\n  isCollapsed: boolean;\n  handleTreeNodeClick: (e: Event) => void;\n  handleTreeNodeToggle: (e: Event) => void;\n  searchValue: typeof searchState.value;\n}\n\nconst VALID_TYPES = ['memo', 'forwardRef', 'lazy', 'suspense'];\n\nconst parseTypeSearch = (query: string) => {\n  const typeMatch = query.match(/\\[(.*?)\\]/);\n  if (!typeMatch) return null;\n\n  const typeSearches: string[] = [];\n  const parts = typeMatch[1].split(',');\n  for (const part of parts) {\n    const trimmed = part.trim().toLowerCase();\n    if (trimmed) typeSearches.push(trimmed);\n  }\n\n  return typeSearches;\n};\n\nconst isValidTypeSearch = (typeSearches: string[]) => {\n  if (typeSearches.length === 0) return false;\n\n  for (const search of typeSearches) {\n    let isValid = false;\n    for (const validType of VALID_TYPES) {\n      if (validType.toLowerCase().includes(search)) {\n        isValid = true;\n        break;\n      }\n    }\n    if (!isValid) return false;\n  }\n  return true;\n};\n\nconst matchesTypeSearch = (\n  typeSearches: string[],\n  wrapperTypes: Array<{ type: string }>,\n) => {\n  if (typeSearches.length === 0) return true;\n  if (!wrapperTypes.length) return false;\n\n  for (const search of typeSearches) {\n    let foundMatch = false;\n    for (const wrapper of wrapperTypes) {\n      if (wrapper.type.toLowerCase().includes(search)) {\n        foundMatch = true;\n        break;\n      }\n    }\n    if (!foundMatch) return false;\n  }\n  return true;\n};\n\nconst useNodeHighlighting = (\n  node: FlattenedNode,\n  searchValue: typeof searchState.value,\n) => {\n  return useMemo(() => {\n    const { query, matches } = searchValue;\n    const isMatch = matches.some((match) => match.nodeId === node.nodeId);\n    const typeSearches = parseTypeSearch(query) || [];\n    const searchQuery = query ? query.replace(/\\[.*?\\]/, '').trim() : '';\n\n    if (!query || !isMatch) {\n      return {\n        highlightedText: <span className=\"truncate\">{node.label}</span>,\n        typeHighlight: false,\n      };\n    }\n\n    let matchesType = true;\n    if (typeSearches.length > 0) {\n      if (!node.fiber) {\n        matchesType = false;\n      } else {\n        const { wrapperTypes } = getExtendedDisplayName(node.fiber);\n        matchesType = matchesTypeSearch(typeSearches, wrapperTypes);\n      }\n    }\n\n    let textContent = <span className=\"truncate\">{node.label}</span>;\n    if (searchQuery) {\n      try {\n        if (searchQuery.startsWith('/') && searchQuery.endsWith('/')) {\n          const pattern = searchQuery.slice(1, -1);\n          const regex = new RegExp(`(${pattern})`, 'i');\n          const parts = node.label.split(regex);\n\n          textContent = (\n            <span className=\"tree-node-search-highlight\">\n              {parts.map((part, index) =>\n                regex.test(part) ? (\n                  <span\n                    key={`${node.nodeId}-${part}`}\n                    className={cn('regex', {\n                      start: regex.test(part) && index === 0,\n                      middle: regex.test(part) && index % 2 === 1,\n                      end: regex.test(part) && index === parts.length - 1,\n                      '!ml-0': index === 1,\n                    })}\n                  >\n                    {part}\n                  </span>\n                ) : (\n                  part\n                ),\n              )}\n            </span>\n          );\n        } else {\n          const lowerLabel = node.label.toLowerCase();\n          const lowerQuery = searchQuery.toLowerCase();\n          const index = lowerLabel.indexOf(lowerQuery);\n\n          if (index >= 0) {\n            textContent = (\n              <span className=\"tree-node-search-highlight\">\n                {node.label.slice(0, index)}\n                <span className=\"single\">\n                  {node.label.slice(index, index + searchQuery.length)}\n                </span>\n                {node.label.slice(index + searchQuery.length)}\n              </span>\n            );\n          }\n        }\n      } catch {}\n    }\n\n    return {\n      highlightedText: textContent,\n      typeHighlight: matchesType && typeSearches.length > 0,\n    };\n  }, [node.label, node.nodeId, node.fiber, searchValue]);\n};\n\nconst formatTime = (time: number) => {\n  if (time > 0) {\n    if (time < 0.1 - Number.EPSILON) {\n      return '< 0.1';\n    }\n    if (time < 1000) {\n      return Number(time.toFixed(1)).toString();\n    }\n    return `${(time / 1000).toFixed(1)}k`;\n  }\n  return '0';\n};\n\nconst TreeNodeItem = ({\n  node,\n  nodeIndex,\n  hasChildren,\n  isCollapsed,\n  handleTreeNodeClick,\n  handleTreeNodeToggle,\n  searchValue,\n}: TreeNodeItemProps) => {\n  const refRenderCount = useRef<HTMLSpanElement>(null);\n  const refPrevRenderCount = useRef(node.renderData?.renderCount ?? 0);\n\n  const { highlightedText, typeHighlight } = useNodeHighlighting(\n    node,\n    searchValue,\n  );\n\n  useEffect(() => {\n    const currentRenderCount = node.renderData?.renderCount;\n    const element = refRenderCount.current;\n    if (\n      !element ||\n      !refPrevRenderCount.current ||\n      !currentRenderCount ||\n      refPrevRenderCount.current === currentRenderCount\n    ) {\n      return;\n    }\n\n    element.classList.remove('count-flash');\n    void element.offsetWidth;\n    element.classList.add('count-flash');\n\n    refPrevRenderCount.current = currentRenderCount;\n  }, [node.renderData?.renderCount]);\n\n  const renderTimeInfo = useMemo(() => {\n    if (!node.renderData) return null;\n    const { selfTime, totalTime, renderCount } = node.renderData;\n\n    if (!renderCount) {\n      return null;\n    }\n\n    return (\n      <span\n        className={cn(\n          'flex items-center gap-x-0.5 ml-1.5',\n          'text-[10px] text-neutral-400',\n        )}\n      >\n        <span\n          ref={refRenderCount}\n          title={`Self time: ${formatTime(selfTime)}ms\\nTotal time: ${formatTime(totalTime)}ms`}\n          className=\"count-badge\"\n        >\n          ×{renderCount}\n        </span>\n      </span>\n    );\n  }, [node.renderData]);\n\n  const componentTypes = useMemo(() => {\n    if (!node.fiber) return null;\n    const { wrapperTypes } = getExtendedDisplayName(node.fiber);\n    const firstWrapperType = wrapperTypes[0];\n\n    return (\n      <span\n        className={cn(\n          'flex items-center gap-x-1',\n          'text-[10px] text-neutral-400 tracking-wide',\n          'overflow-hidden',\n        )}\n      >\n        {firstWrapperType && (\n          <>\n            <span\n              key={firstWrapperType.type}\n              title={firstWrapperType?.title}\n              className={cn(\n                'rounded py-[1px] px-1',\n                'bg-neutral-700 text-neutral-300',\n                'truncate',\n                firstWrapperType.type === 'memo' && 'bg-[#8e61e3] text-white',\n                typeHighlight && 'bg-yellow-300 text-black',\n              )}\n            >\n              {firstWrapperType.type}\n            </span>\n            {firstWrapperType.compiler && (\n              <span className=\"text-yellow-300 ml-1\">✨</span>\n            )}\n          </>\n        )}\n        {wrapperTypes.length > 1 && `×${wrapperTypes.length}`}\n        {renderTimeInfo}\n      </span>\n    );\n  }, [node.fiber, typeHighlight, renderTimeInfo]);\n\n  return (\n    <button\n      type=\"button\"\n      title={node.title}\n      data-index={nodeIndex}\n      className={cn(\n        'flex items-center gap-x-1',\n        'pl-1 pr-2',\n        'w-full h-7',\n        'text-left',\n        'rounded',\n        'cursor-pointer select-none',\n      )}\n      onClick={handleTreeNodeClick}\n    >\n      <button\n        type=\"button\"\n        data-index={nodeIndex}\n        onClick={handleTreeNodeToggle}\n        className={cn('w-6 h-6 flex items-center justify-center', 'text-left')}\n      >\n        {hasChildren && (\n          <Icon\n            name=\"icon-chevron-right\"\n            size={12}\n            className={cn('transition-transform', !isCollapsed && 'rotate-90')}\n          />\n        )}\n      </button>\n      {highlightedText}\n      {componentTypes}\n    </button>\n  );\n};\n\nexport const ComponentsTree = () => {\n  const refContainer = useRef<HTMLDivElement>(null);\n  const refMainContainer = useRef<HTMLDivElement>(null);\n  const refSearchInputContainer = useRef<HTMLDivElement>(null);\n  const refSearchInput = useRef<HTMLInputElement>(null);\n  const refSelectedElement = useRef<HTMLElement | null>(null);\n  const refMaxTreeDepth = useRef(0);\n  const refIsHovering = useRef(false);\n  const refIsResizing = useRef(false);\n  const refResizeHandle = useRef<HTMLDivElement>(null);\n\n  const [flattenedNodes, setFlattenedNodes] = useState<FlattenedNode[]>([]);\n  const [collapsedNodes, setCollapsedNodes] = useState<Set<string>>(new Set());\n  const [selectedIndex, setSelectedIndex] = useState<number | undefined>(\n    undefined,\n  );\n  const [searchValue, setSearchValue] = useState(searchState.value);\n\n  const visibleNodes = useMemo(() => {\n    const visible: FlattenedNode[] = [];\n    const nodes = flattenedNodes;\n    const nodeMap = new Map(nodes.map((node) => [node.nodeId, node]));\n\n    for (const node of nodes) {\n      let isVisible = true;\n\n      let currentNode = node;\n      while (currentNode.parentId) {\n        const parent = nodeMap.get(currentNode.parentId);\n        if (!parent) break;\n\n        if (collapsedNodes.has(parent.nodeId)) {\n          isVisible = false;\n          break;\n        }\n        currentNode = parent;\n      }\n\n      if (isVisible) {\n        visible.push(node);\n      }\n    }\n\n    return visible;\n  }, [collapsedNodes, flattenedNodes]);\n\n  const ITEM_HEIGHT = 28;\n\n  const { virtualItems, totalSize } = useVirtualList({\n    count: visibleNodes.length,\n    getScrollElement: () => refContainer.current,\n    estimateSize: () => ITEM_HEIGHT,\n    overscan: 5,\n  });\n\n  const handleElementClick = useCallback(\n    (element: HTMLElement) => {\n      refIsHovering.current = true;\n      refSearchInput.current?.blur();\n      signalSkipTreeUpdate.value = true;\n\n      const { parentCompositeFiber } =\n        getCompositeComponentFromElement(element);\n      if (!parentCompositeFiber) return;\n\n      Store.inspectState.value = {\n        kind: 'focused',\n        focusedDomElement: element,\n        fiber: parentCompositeFiber,\n      };\n\n      const nodeIndex = visibleNodes.findIndex(\n        (node) => node.element === element,\n      );\n      if (nodeIndex !== -1) {\n        setSelectedIndex(nodeIndex);\n        const itemTop = nodeIndex * ITEM_HEIGHT;\n        const container = refContainer.current;\n        if (container) {\n          const containerHeight = container.clientHeight;\n          const scrollTop = container.scrollTop;\n\n          if (\n            itemTop < scrollTop ||\n            itemTop + ITEM_HEIGHT > scrollTop + containerHeight\n          ) {\n            container.scrollTo({\n              top: Math.max(0, itemTop - containerHeight / 2),\n              behavior: 'instant',\n            });\n          }\n        }\n      }\n    },\n    [visibleNodes],\n  );\n\n  const handleTreeNodeClick = useCallback(\n    (e: Event) => {\n      const target = e.currentTarget as HTMLElement;\n      const index = Number(target.dataset.index);\n      if (Number.isNaN(index)) return;\n      const element = visibleNodes[index].element;\n      if (!element) return;\n      handleElementClick(element);\n    },\n    [visibleNodes, handleElementClick],\n  );\n\n  const handleToggle = useCallback((nodeId: string) => {\n    setCollapsedNodes((prev) => {\n      const next = new Set(prev);\n      if (next.has(nodeId)) {\n        next.delete(nodeId);\n      } else {\n        next.add(nodeId);\n      }\n      return next;\n    });\n  }, []);\n\n  const handleTreeNodeToggle = useCallback(\n    (e: Event) => {\n      e.stopPropagation();\n      const target = e.target as HTMLElement;\n      const index = Number(target.dataset.index);\n      if (Number.isNaN(index)) return;\n      const nodeId = visibleNodes[index].nodeId;\n      handleToggle(nodeId);\n    },\n    [visibleNodes, handleToggle],\n  );\n\n  const handleOnChangeSearch = useCallback(\n    (query: string) => {\n      refSearchInputContainer.current?.classList.remove('!border-red-500');\n      const matches: FlattenedNode[] = [];\n\n      if (!query) {\n        searchState.value = { query, matches, currentMatchIndex: -1 };\n        return;\n      }\n\n      if (query.includes('[') && !query.includes(']')) {\n        if (query.length > query.indexOf('[') + 1) {\n          refSearchInputContainer.current?.classList.add('!border-red-500');\n          return;\n        }\n      }\n\n      const typeSearches = parseTypeSearch(query) || [];\n      if (query.includes('[')) {\n        if (!isValidTypeSearch(typeSearches)) {\n          refSearchInputContainer.current?.classList.add('!border-red-500');\n          return;\n        }\n      }\n\n      const searchQuery = query.replace(/\\[.*?\\]/, '').trim();\n      const isRegex = /^\\/.*\\/$/.test(searchQuery);\n      let matchesLabel = (_label: string) => false;\n\n      if (searchQuery.startsWith('/') && !isRegex) {\n        if (searchQuery.length > 1) {\n          refSearchInputContainer.current?.classList.add('!border-red-500');\n          return;\n        }\n      }\n\n      if (isRegex) {\n        try {\n          const pattern = searchQuery.slice(1, -1);\n          const regex = new RegExp(pattern, 'i');\n          matchesLabel = (label: string) => regex.test(label);\n        } catch {\n          refSearchInputContainer.current?.classList.add('!border-red-500');\n          return;\n        }\n      } else if (searchQuery) {\n        const lowerQuery = searchQuery.toLowerCase();\n        matchesLabel = (label: string) =>\n          label.toLowerCase().includes(lowerQuery);\n      }\n\n      for (const node of flattenedNodes) {\n        let matchesSearch = true;\n\n        if (searchQuery) {\n          matchesSearch = matchesLabel(node.label);\n        }\n\n        if (matchesSearch && typeSearches.length > 0) {\n          if (!node.fiber) {\n            matchesSearch = false;\n          } else {\n            const { wrapperTypes } = getExtendedDisplayName(node.fiber);\n            matchesSearch = matchesTypeSearch(typeSearches, wrapperTypes);\n          }\n        }\n\n        if (matchesSearch) {\n          matches.push(node);\n        }\n      }\n\n      searchState.value = {\n        query,\n        matches,\n        currentMatchIndex: matches.length > 0 ? 0 : -1,\n      };\n\n      if (matches.length > 0) {\n        const firstMatch = matches[0];\n        const nodeIndex = visibleNodes.findIndex(\n          (node) => node.nodeId === firstMatch.nodeId,\n        );\n        if (nodeIndex !== -1) {\n          const itemTop = nodeIndex * ITEM_HEIGHT;\n          const container = refContainer.current;\n          if (container) {\n            const containerHeight = container.clientHeight;\n            container.scrollTo({\n              top: Math.max(0, itemTop - containerHeight / 2),\n              behavior: 'instant',\n            });\n          }\n        }\n      }\n    },\n    [flattenedNodes, visibleNodes],\n  );\n\n  const handleInputChange = useCallback(\n    (e: Event) => {\n      const target = e.currentTarget as HTMLInputElement;\n      if (!target) return;\n      handleOnChangeSearch(target.value);\n    },\n    [handleOnChangeSearch],\n  );\n\n  const navigateSearch = useCallback(\n    (direction: 'next' | 'prev') => {\n      const { matches, currentMatchIndex } = searchState.value;\n      if (matches.length === 0) return;\n\n      const newIndex =\n        direction === 'next'\n          ? (currentMatchIndex + 1) % matches.length\n          : (currentMatchIndex - 1 + matches.length) % matches.length;\n\n      searchState.value = {\n        ...searchState.value,\n        currentMatchIndex: newIndex,\n      };\n\n      const currentMatch = matches[newIndex];\n      const nodeIndex = visibleNodes.findIndex(\n        (node) => node.nodeId === currentMatch.nodeId,\n      );\n      if (nodeIndex !== -1) {\n        setSelectedIndex(nodeIndex);\n        const itemTop = nodeIndex * ITEM_HEIGHT;\n        const container = refContainer.current;\n        if (container) {\n          const containerHeight = container.clientHeight;\n          container.scrollTo({\n            top: Math.max(0, itemTop - containerHeight / 2),\n            behavior: 'instant',\n          });\n        }\n      }\n    },\n    [visibleNodes],\n  );\n\n  const updateContainerWidths = useCallback((width: number) => {\n    if (refMainContainer.current) {\n      refMainContainer.current.style.width = `${width}px`;\n    }\n    if (refContainer.current) {\n      refContainer.current.style.width = `${width}px`;\n      const indentSize = calculateIndentSize(width, refMaxTreeDepth.current);\n      refContainer.current.style.setProperty(\n        '--indentation-size',\n        `${indentSize}px`,\n      );\n    }\n  }, []);\n\n  const updateResizeDirection = useCallback((width: number) => {\n    if (!refResizeHandle.current) return;\n\n    const parentWidth = signalWidget.value.dimensions.width;\n    const maxWidth = Math.floor(parentWidth - (MIN_CONTAINER_WIDTH / 2));\n\n    refResizeHandle.current.classList.remove(\n      'cursor-ew-resize',\n      'cursor-w-resize',\n      'cursor-e-resize',\n    );\n\n    if (width <= MIN_CONTAINER_WIDTH) {\n      refResizeHandle.current.classList.add('cursor-w-resize');\n    } else if (width >= maxWidth) {\n      refResizeHandle.current.classList.add('cursor-e-resize');\n    } else {\n      refResizeHandle.current.classList.add('cursor-ew-resize');\n    }\n  }, []);\n\n  const handleResize = useCallback(\n    (e: MouseEvent) => {\n      e.preventDefault();\n      e.stopPropagation();\n\n      if (!refContainer.current) return;\n      refContainer.current.style.setProperty('pointer-events', 'none');\n\n      refIsResizing.current = true;\n\n      const startX = e.clientX;\n      const startWidth = refContainer.current.offsetWidth;\n      const parentWidth = signalWidget.value.dimensions.width;\n      const maxWidth = Math.floor(parentWidth - (MIN_CONTAINER_WIDTH / 2));\n\n      updateResizeDirection(startWidth);\n\n      const handlePointerMove = (e: PointerEvent) => {\n        const delta = startX - e.clientX;\n        const newWidth = startWidth + delta;\n        updateResizeDirection(newWidth);\n\n        const clampedWidth = Math.min(\n          maxWidth,\n          Math.max(MIN_CONTAINER_WIDTH, newWidth),\n        );\n        updateContainerWidths(clampedWidth);\n      };\n\n      const handlePointerUp = () => {\n        if (!refContainer.current) return;\n        refContainer.current.style.removeProperty('pointer-events');\n        document.removeEventListener('pointermove', handlePointerMove);\n        document.removeEventListener('pointerup', handlePointerUp);\n\n        signalWidget.value = {\n          ...signalWidget.value,\n          componentsTree: {\n            ...signalWidget.value.componentsTree,\n            width: refContainer.current.offsetWidth,\n          },\n        };\n\n        saveLocalStorage(LOCALSTORAGE_KEY, signalWidget.value);\n        refIsResizing.current = false;\n      };\n\n      document.addEventListener('pointermove', handlePointerMove);\n      document.addEventListener('pointerup', handlePointerUp);\n    },\n    [updateContainerWidths, updateResizeDirection],\n  );\n\n  useEffect(() => {\n    if (!refContainer.current) return;\n    const currentWidth = refContainer.current.offsetWidth;\n    updateResizeDirection(currentWidth);\n\n    return signalWidget.subscribe(() => {\n      if (!refContainer.current) return;\n      updateResizeDirection(refContainer.current.offsetWidth);\n    });\n  }, [updateResizeDirection]);\n\n  const onPointerLeave = useCallback(() => {\n    refIsHovering.current = false;\n  }, []);\n\n  // oxlint-disable-next-line react-hooks/exhaustive-deps\n  useEffect(() => {\n    let isInitialTreeBuild = true;\n    const buildTreeFromElements = (elements: Array<InspectableElement>) => {\n      const nodeMap = new Map<HTMLElement, TreeNode>();\n      const rootNodes: TreeNode[] = [];\n\n      for (const { element, name, fiber } of elements) {\n        if (!element) continue;\n\n        let title = name;\n        const { name: componentName, wrappers } = getExtendedDisplayName(fiber);\n        if (componentName) {\n          if (wrappers.length > 0) {\n            title = `${wrappers.join('(')}(${componentName})${')'.repeat(wrappers.length)}`;\n          } else {\n            title = componentName;\n          }\n        }\n\n        nodeMap.set(element, {\n          label: componentName || name,\n          title,\n          children: [],\n          element,\n          fiber,\n        });\n      }\n\n      for (const { element, depth } of elements) {\n        if (!element) continue;\n        const node = nodeMap.get(element);\n        if (!node) continue;\n\n        if (depth === 0) {\n          rootNodes.push(node);\n        } else {\n          let parent = element.parentElement;\n          while (parent) {\n            const parentNode = nodeMap.get(parent);\n            if (parentNode) {\n              parentNode.children = parentNode.children || [];\n              parentNode.children.push(node);\n              break;\n            }\n            parent = parent.parentElement;\n          }\n        }\n      }\n\n      return rootNodes;\n    };\n\n    const updateTree = () => {\n      const element = refSelectedElement.current;\n      if (!element) return;\n\n      const inspectableElements = getInspectableElements();\n      const tree = buildTreeFromElements(inspectableElements);\n\n      if (tree.length > 0) {\n        const flattened = flattenTree(tree);\n        const newMaxDepth = getMaxDepth(flattened);\n        refMaxTreeDepth.current = newMaxDepth;\n\n        updateContainerWidths(signalWidget.value.componentsTree.width);\n        setFlattenedNodes(flattened);\n\n        if (isInitialTreeBuild) {\n          isInitialTreeBuild = false;\n          const focusedIndex = flattened.findIndex(\n            (node) => node.element === element,\n          );\n          if (focusedIndex !== -1) {\n            const itemTop = focusedIndex * ITEM_HEIGHT;\n            const container = refContainer.current;\n            if (container) {\n              setTimeout(() => {\n                container.scrollTo({\n                  top: itemTop,\n                  behavior: 'instant',\n                });\n              }, 96);\n            }\n          }\n        }\n      }\n    };\n\n    const unsubscribeStore = Store.inspectState.subscribe((state) => {\n      if (state.kind === 'focused') {\n        if (signalSkipTreeUpdate.value) {\n          return;\n        }\n\n        handleOnChangeSearch('');\n        refSelectedElement.current = state.focusedDomElement as HTMLElement;\n        updateTree();\n      }\n    });\n\n    let rafId = 0;\n    const unsubscribeUpdates = inspectorUpdateSignal.subscribe(() => {\n      if (Store.inspectState.value.kind === 'focused') {\n        cancelAnimationFrame(rafId);\n        if (refIsResizing.current) return;\n\n        rafId = requestAnimationFrame(() => {\n          signalSkipTreeUpdate.value = false;\n          updateTree();\n        });\n      }\n    });\n\n    return () => {\n      unsubscribeStore();\n      unsubscribeUpdates();\n\n      searchState.value = {\n        query: '',\n        matches: [],\n        currentMatchIndex: -1,\n      };\n    };\n  }, []);\n\n  useEffect(() => {\n    const handleKeyDown = (e: KeyboardEvent) => {\n      if (!refIsHovering.current) return;\n\n      if (!selectedIndex) return;\n\n      switch (e.key) {\n        case 'ArrowUp': {\n          e.preventDefault();\n          e.stopPropagation();\n\n          if (selectedIndex > 0) {\n            const currentNode = visibleNodes[selectedIndex - 1];\n            if (currentNode?.element) {\n              handleElementClick(currentNode.element);\n            }\n          }\n          return;\n        }\n        case 'ArrowDown': {\n          e.preventDefault();\n          e.stopPropagation();\n\n          if (selectedIndex < visibleNodes.length - 1) {\n            const currentNode = visibleNodes[selectedIndex + 1];\n            if (currentNode?.element) {\n              handleElementClick(currentNode.element);\n            }\n          }\n          return;\n        }\n        case 'ArrowLeft': {\n          e.preventDefault();\n          e.stopPropagation();\n\n          const currentNode = visibleNodes[selectedIndex];\n          if (currentNode?.nodeId) {\n            handleToggle(currentNode.nodeId);\n          }\n          return;\n        }\n        case 'ArrowRight': {\n          e.preventDefault();\n          e.stopPropagation();\n\n          const currentNode = visibleNodes[selectedIndex];\n          if (currentNode?.nodeId) {\n            handleToggle(currentNode.nodeId);\n          }\n          return;\n        }\n      }\n    };\n\n    document.addEventListener('keydown', handleKeyDown);\n    return () => {\n      document.removeEventListener('keydown', handleKeyDown);\n    };\n  }, [selectedIndex, visibleNodes, handleElementClick, handleToggle]);\n\n  useEffect(() => {\n    return searchState.subscribe(setSearchValue);\n  }, []);\n\n  // oxlint-disable-next-line react-hooks/exhaustive-deps\n  useEffect(() => {\n    const unsubscribe = signalWidget.subscribe((state) => {\n      refMainContainer.current?.style.setProperty('transition', 'width 0.1s');\n      updateContainerWidths(state.componentsTree.width);\n\n      setTimeout(() => {\n        refMainContainer.current?.style.removeProperty('transition');\n      }, 500);\n    });\n    return unsubscribe;\n  }, []);\n\n  return (\n    <div className=\"react-scan-components-tree flex\">\n      <div\n        ref={refResizeHandle}\n        onPointerDown={handleResize}\n        className=\"relative resize-v-line\"\n      >\n        <span>\n          <Icon name=\"icon-ellipsis\" size={18} />\n        </span>\n      </div>\n      <div ref={refMainContainer} className=\"flex flex-col h-full\">\n        <div className=\"p-2 border-b border-[#1e1e1e]\">\n          <div\n            ref={refSearchInputContainer}\n            title={`Search components by:\n\n• Name (e.g., \"Button\") — Case insensitive, matches any part\n\n• Regular Expression (e.g., \"/^Button/\") — Use forward slashes\n\n• Wrapper Type (e.g., \"[memo,forwardRef]\"):\n   - Available types: memo, forwardRef, lazy, suspense\n   - Matches any part of type name (e.g., \"mo\" matches \"memo\")\n   - Use commas for multiple types\n\n• Combined Search:\n   - Mix name/regex with type: \"button [for]\"\n   - Will match components satisfying both conditions\n\n• Navigation:\n   - Enter → Next match\n   - Shift + Enter → Previous match\n   - Cmd/Ctrl + Enter → Select and focus match\n`}\n            className={cn(\n              'relative',\n              'flex items-center gap-x-1 px-2',\n              'rounded',\n              'border border-transparent',\n              'focus-within:border-[#454545]',\n              'bg-[#1e1e1e] text-neutral-300',\n              'transition-colors',\n              'whitespace-nowrap',\n              'overflow-hidden',\n            )}\n          >\n            <Icon name=\"icon-search\" size={12} className=\" text-neutral-500\" />\n            <div className=\"relative flex-1 h-7 overflow-hidden\">\n              <input\n                ref={refSearchInput}\n                type=\"text\"\n                value={searchState.value.query}\n                onClick={(e) => {\n                  e.stopPropagation();\n                  e.currentTarget.focus();\n                }}\n                onPointerDown={(e) => {\n                  e.stopPropagation();\n                }}\n                onKeyDown={(e) => {\n                  if (e.key === 'Escape') {\n                    e.currentTarget.blur();\n                  }\n                  if (searchState.value.matches.length) {\n                    if (e.key === 'Enter' && e.shiftKey) {\n                      navigateSearch('prev');\n                    } else if (e.key === 'Enter') {\n                      if (e.metaKey || e.ctrlKey) {\n                        e.preventDefault();\n                        e.stopPropagation();\n                        handleElementClick(\n                          searchState.value.matches[\n                            searchState.value.currentMatchIndex\n                          ].element as HTMLElement,\n                        );\n\n                        e.currentTarget.focus();\n                      } else {\n                        navigateSearch('next');\n                      }\n                    }\n                  }\n                }}\n                onChange={handleInputChange}\n                className=\"absolute inset-y-0 inset-x-1\"\n                placeholder=\"Component name, /regex/, or [type]\"\n              />\n            </div>\n            {searchState.value.query ? (\n              <>\n                <span className=\"flex items-center gap-x-0.5 text-xs text-neutral-500\">\n                  {searchState.value.currentMatchIndex + 1}\n                  {'|'}\n                  {searchState.value.matches.length}\n                </span>\n                {!!searchState.value.matches.length && (\n                  <>\n                    <button\n                      type=\"button\"\n                      onClick={(e) => {\n                        e.stopPropagation();\n                        navigateSearch('prev');\n                      }}\n                      className=\"button rounded w-4 h-4 flex items-center justify-center text-neutral-400 hover:text-neutral-300\"\n                    >\n                      <Icon\n                        name=\"icon-chevron-right\"\n                        className=\"-rotate-90\"\n                        size={12}\n                      />\n                    </button>\n                    <button\n                      type=\"button\"\n                      onClick={(e) => {\n                        e.stopPropagation();\n                        navigateSearch('next');\n                      }}\n                      className=\"button rounded w-4 h-4 flex items-center justify-center text-neutral-400 hover:text-neutral-300\"\n                    >\n                      <Icon\n                        name=\"icon-chevron-right\"\n                        className=\"rotate-90\"\n                        size={12}\n                      />\n                    </button>\n                  </>\n                )}\n                <button\n                  type=\"button\"\n                  onClick={(e) => {\n                    e.stopPropagation();\n                    handleOnChangeSearch('');\n                  }}\n                  className=\"button rounded w-4 h-4 flex items-center justify-center text-neutral-400 hover:text-neutral-300\"\n                >\n                  <Icon name=\"icon-close\" size={12} />\n                </button>\n              </>\n            ) : (\n              !!flattenedNodes.length && (\n                <span className=\"text-xs text-neutral-500\">\n                  {flattenedNodes.length}\n                </span>\n              )\n            )}\n          </div>\n        </div>\n        <div className=\"flex-1 overflow-hidden\">\n          <div\n            ref={refContainer}\n            onPointerLeave={onPointerLeave}\n            className=\"tree h-full overflow-auto will-change-transform\"\n          >\n            <div\n              className=\"relative w-full\"\n              style={{\n                height: totalSize,\n              }}\n            >\n              {virtualItems.map((virtualItem) => {\n                const node = visibleNodes[virtualItem.index];\n                if (!node) return null;\n\n                const isSelected =\n                  Store.inspectState.value.kind === 'focused' &&\n                  node.element === Store.inspectState.value.focusedDomElement;\n                const isKeyboardSelected = virtualItem.index === selectedIndex;\n\n                return (\n                  <div\n                    key={node.nodeId}\n                    className={cn(\n                      'absolute left-0 w-full overflow-hidden',\n                      'text-neutral-400 hover:text-neutral-300',\n                      'bg-transparent hover:bg-[#5f3f9a]/20',\n                      (isSelected || isKeyboardSelected) &&\n                        'text-neutral-300 bg-[#5f3f9a]/40 hover:bg-[#5f3f9a]/40',\n                    )}\n                    style={{\n                      top: virtualItem.start,\n                      height: ITEM_HEIGHT,\n                    }}\n                  >\n                    <div\n                      className=\"w-full h-full\"\n                      style={{\n                        paddingLeft: `calc(${node.depth} * var(--indentation-size))`,\n                      }}\n                    >\n                      <TreeNodeItem\n                        node={node}\n                        nodeIndex={virtualItem.index}\n                        hasChildren={!!node.children?.length}\n                        isCollapsed={collapsedNodes.has(node.nodeId)}\n                        handleTreeNodeClick={handleTreeNodeClick}\n                        handleTreeNodeToggle={handleTreeNodeToggle}\n                        searchValue={searchValue}\n                      />\n                    </div>\n                  </div>\n                );\n              })}\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/scan/src/web/views/inspector/components-tree/state.ts",
    "content": "import { signal } from '@preact/signals';\nimport type { Fiber } from 'bippy';\nimport type { RenderData } from '~core/instrumentation';\n\nexport interface TreeNode {\n  label: string;\n  title?: string;\n  fiber: Fiber;\n  element?: HTMLElement;\n  children?: TreeNode[];\n  renderData?: RenderData;\n}\n\nexport interface FlattenedNode extends TreeNode {\n  depth: number;\n  nodeId: string;\n  parentId: string | null;\n  fiber: Fiber;\n}\n\nexport const searchState = signal<{\n  query: string;\n  matches: FlattenedNode[];\n  currentMatchIndex: number;\n}>({\n  query: '',\n  matches: [],\n  currentMatchIndex: -1,\n});\n\nexport interface TreeItem {\n  name: string;\n  depth: number;\n  element: HTMLElement;\n  fiber: Fiber;\n}\n\nexport const signalSkipTreeUpdate = /* @__PURE__ */ signal(false);\n"
  },
  {
    "path": "packages/scan/src/web/views/inspector/diff-value.tsx",
    "content": "import { useState } from 'preact/hooks';\nimport { CopyToClipboard } from '~web/components/copy-to-clipboard';\nimport { Icon } from '~web/components/icon';\nimport { cn } from '~web/utils/helpers';\nimport { formatForClipboard, formatValuePreview, safeGetValue } from './utils';\n\nconst ArrayHeader = ({\n  length,\n  expanded,\n  onToggle,\n  isNegative,\n}: {\n  length: number;\n  expanded: boolean;\n  onToggle: () => void;\n  isNegative: boolean;\n}) => (\n  <div className=\"flex items-center gap-1\">\n    <button\n      type=\"button\"\n      onClick={onToggle}\n      className=\"flex items-center p-0 opacity-50\"\n    >\n      <Icon\n        name=\"icon-chevron-right\"\n        size={12}\n        className={cn(\n          'transition-[color,transform]',\n          isNegative ? 'text-[#f87171]' : 'text-[#4ade80]',\n          expanded && 'rotate-90',\n        )}\n      />\n    </button>\n    <span>Array({length})</span>\n  </div>\n);\n\nconst TreeNode = ({\n  value,\n  path,\n  isNegative,\n}: {\n  value: unknown;\n  path: string;\n  isNegative: boolean;\n}) => {\n  const [isExpanded, setIsExpanded] = useState(false);\n  const canExpand = value !== null &&\n    typeof value === 'object' &&\n    !(value instanceof Date);\n\n  if (!canExpand) {\n    return (\n      <div className=\"flex items-center gap-1\">\n        <span className=\"text-gray-500\">{path}:</span>\n        <span className=\"truncate\">{formatValuePreview(value)}</span>\n      </div>\n    );\n  }\n\n  const entries = Object.entries(value as object);\n\n  return (\n    <div className=\"flex flex-col\">\n      <div className=\"flex items-center gap-1\">\n        <button\n          type=\"button\"\n          onClick={() => setIsExpanded(!isExpanded)}\n          className=\"flex items-center p-0 opacity-50\"\n        >\n          <Icon\n            name=\"icon-chevron-right\"\n            size={12}\n            className={cn(\n              'transition-[color,transform]',\n              isNegative ? 'text-[#f87171]' : 'text-[#4ade80]',\n              isExpanded && 'rotate-90',\n            )}\n          />\n        </button>\n        <span className=\"text-gray-500\">{path}:</span>\n        {!isExpanded && (\n          <span className=\"truncate\">\n            {value instanceof Date ? formatValuePreview(value) : `{${Object.keys(value).join(', ')}}`}\n          </span>\n        )}\n      </div>\n      {isExpanded && (\n        <div className=\"pl-5 border-l border-[#333] mt-0.5 ml-1 flex flex-col gap-0.5\">\n          {entries.map(([key, val]) => (\n            <TreeNode\n              key={key}\n              value={val}\n              path={key}\n              isNegative={isNegative}\n            />\n          ))}\n        </div>\n      )}\n    </div>\n  );\n};\n\nexport const DiffValueView = ({\n  value,\n  expanded,\n  onToggle,\n  isNegative,\n}: {\n  value: unknown;\n  expanded: boolean;\n  onToggle: () => void;\n  isNegative: boolean;\n}) => {\n  const { value: safeValue, error } = safeGetValue(value);\n\n  if (error) {\n    return <span className=\"text-gray-500 font-italic\">{error}</span>;\n  }\n\n  const isExpandable =\n    safeValue !== null &&\n    typeof safeValue === 'object' &&\n    !(safeValue instanceof Promise);\n\n  if (!isExpandable) {\n    return <span>{formatValuePreview(safeValue)}</span>;\n  }\n\n  if (Array.isArray(safeValue)) {\n    return (\n      <div className=\"flex flex-col gap-1 relative\">\n        <ArrayHeader\n          length={safeValue.length}\n          expanded={expanded}\n          onToggle={onToggle}\n          isNegative={isNegative}\n        />\n        {expanded && (\n          <div className=\"pl-2 border-l border-[#333] mt-0.5 ml-1 flex flex-col gap-0.5\">\n            {safeValue.map((item, index) => (\n              <TreeNode\n                key={index.toString()}\n                value={item}\n                path={index.toString()}\n                isNegative={isNegative}\n              />\n            ))}\n          </div>\n        )}\n        <CopyToClipboard\n          text={formatForClipboard(safeValue)}\n          className=\"absolute top-0.5 right-0.5 opacity-0 transition-opacity group-hover:opacity-100 self-end\"\n        >\n          {({ ClipboardIcon }) => <>{ClipboardIcon}</>}\n        </CopyToClipboard>\n      </div>\n    );\n  }\n\n  // Handle objects\n  return (\n    <div className=\"flex items-start gap-1 relative\">\n      <button\n        type=\"button\"\n        onClick={onToggle}\n        className={cn('flex items-center', 'p-0 mt-0.5 mr-1', 'opacity-50')}\n      >\n        <Icon\n          name=\"icon-chevron-right\"\n          size={12}\n          className={cn(\n            'transition-[color,transform]',\n            isNegative ? 'text-[#f87171]' : 'text-[#4ade80]',\n            expanded && 'rotate-90',\n          )}\n        />\n      </button>\n      <div className=\"flex-1\">\n        {!expanded ? (\n          <span>{formatValuePreview(safeValue)}</span>\n        ) : (\n            <div className=\"pl-2 border-l border-[#333] mt-0.5 ml-1 flex flex-col gap-0.5\">\n              {Object.entries(safeValue as object).map(([key, val]) => (\n                <TreeNode\n                  key={key}\n                  value={val}\n                  path={key}\n                  isNegative={isNegative}\n                />\n              ))}\n            </div>\n        )}\n      </div>\n      <CopyToClipboard\n        text={formatForClipboard(safeValue)}\n        className=\"absolute top-0.5 right-0.5 opacity-0 transition-opacity group-hover:opacity-100 self-end\"\n      >\n        {({ ClipboardIcon }) => <>{ClipboardIcon}</>}\n      </CopyToClipboard>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/scan/src/web/views/inspector/flash-overlay.ts",
    "content": "interface FlashEntry {\n  element: HTMLElement;\n  overlay: HTMLElement;\n  scrollCleanup?: () => void;\n}\n\nconst fadeOutTimers = new WeakMap<HTMLElement, ReturnType<typeof setTimeout>>();\n\nconst trackElementPosition = (\n  element: Element,\n  callback: (element: Element) => void,\n): (() => void) => {\n  const handleScroll = callback.bind(null, element);\n\n  document.addEventListener('scroll', handleScroll, {\n    passive: true,\n    capture: true,\n  });\n\n  return () => {\n    document.removeEventListener('scroll', handleScroll, { capture: true });\n  };\n};\n\nexport const flashManager = {\n  activeFlashes: new Map<HTMLElement, FlashEntry>(),\n\n  create(container: HTMLElement) {\n    const existingOverlay = container.querySelector(\n      '.react-scan-flash-overlay',\n    );\n\n    const overlay =\n      existingOverlay instanceof HTMLElement\n        ? existingOverlay\n        : (() => {\n            const newOverlay = document.createElement('div');\n            newOverlay.className = 'react-scan-flash-overlay';\n            container.appendChild(newOverlay);\n\n            const scrollCleanup = trackElementPosition(container, () => {\n              if (container.querySelector('.react-scan-flash-overlay')) {\n                this.create(container);\n              }\n            });\n\n            this.activeFlashes.set(container, {\n              element: container,\n              overlay: newOverlay,\n              scrollCleanup,\n            });\n\n            return newOverlay;\n          })();\n\n    const existingTimer = fadeOutTimers.get(overlay);\n    if (existingTimer) {\n      clearTimeout(existingTimer);\n      fadeOutTimers.delete(overlay);\n    }\n\n    requestAnimationFrame(() => {\n      overlay.style.transition = 'none';\n      overlay.style.opacity = '0.9';\n\n      const timerId = setTimeout(() => {\n        overlay.style.transition = 'opacity 150ms ease-out';\n        overlay.style.opacity = '0';\n\n        const cleanupTimer = setTimeout(() => {\n          if (overlay.parentNode) {\n            overlay.parentNode.removeChild(overlay);\n          }\n          const entry = this.activeFlashes.get(container);\n          if (entry?.scrollCleanup) {\n            entry.scrollCleanup();\n          }\n          this.activeFlashes.delete(container);\n          fadeOutTimers.delete(overlay);\n        }, 150);\n\n        fadeOutTimers.set(overlay, cleanupTimer);\n      }, 300);\n\n      fadeOutTimers.set(overlay, timerId);\n    });\n  },\n\n  cleanup(container: HTMLElement) {\n    const entry = this.activeFlashes.get(container);\n    if (entry) {\n      const existingTimer = fadeOutTimers.get(entry.overlay);\n      if (existingTimer) {\n        clearTimeout(existingTimer);\n        fadeOutTimers.delete(entry.overlay);\n      }\n      if (entry.overlay.parentNode) {\n        entry.overlay.parentNode.removeChild(entry.overlay);\n      }\n      if (entry.scrollCleanup) {\n        entry.scrollCleanup();\n      }\n      this.activeFlashes.delete(container);\n    }\n  },\n\n  cleanupAll() {\n    for (const [, entry] of this.activeFlashes) {\n      this.cleanup(entry.element);\n    }\n  },\n};\n"
  },
  {
    "path": "packages/scan/src/web/views/inspector/header.tsx",
    "content": "import { computed, untracked, useSignalEffect } from '@preact/signals';\nimport type { Fiber } from 'bippy';\nimport { useMemo, useRef, useState } from 'preact/hooks';\nimport { Store } from '~core/index';\nimport { signalIsSettingsOpen } from '~web/state';\nimport { cn, getExtendedDisplayName } from '~web/utils/helpers';\nimport { timelineState } from './states';\n\nconst headerInspectClassName = computed(() =>\n  cn(\n    'absolute inset-0 flex items-center gap-x-2',\n    'translate-y-0',\n    'transition-transform duration-300',\n    signalIsSettingsOpen.value && '-translate-y-[200%]',\n  ),\n);\n\nexport const HeaderInspect = () => {\n  const refReRenders = useRef<HTMLSpanElement>(null);\n  const refTiming = useRef<HTMLSpanElement>(null);\n  const [currentFiber, setCurrentFiber] = useState<Fiber | null>(null);\n\n  useSignalEffect(() => {\n    const state = Store.inspectState.value;\n\n    if (state.kind === 'focused') {\n      setCurrentFiber(state.fiber);\n    }\n  });\n\n  useSignalEffect(() => {\n    const state = timelineState.value;\n    untracked(() => {\n      if (Store.inspectState.value.kind !== 'focused') return;\n      if (!refReRenders.current || !refTiming.current) return;\n\n      const { totalUpdates, currentIndex, updates, isVisible, windowOffset } =\n        state;\n\n      const reRenders = Math.max(0, totalUpdates - 1);\n      const headerText = isVisible\n        ? `#${windowOffset + currentIndex} Re-render`\n        : reRenders > 0\n          ? `×${reRenders}`\n          : '';\n\n      let formattedTime: string | undefined;\n      if (reRenders > 0 && currentIndex >= 0 && currentIndex < updates.length) {\n        const time = updates[currentIndex]?.fiberInfo?.selfTime;\n        formattedTime =\n          time > 0\n            ? time < 0.1 - Number.EPSILON\n              ? '< 0.1ms'\n              : `${Number(time.toFixed(1))}ms`\n            : undefined;\n      }\n\n      // TODO(Alexis): can be computed signal\n      refReRenders.current.dataset.text = headerText ? ` • ${headerText}` : '';\n      refTiming.current.dataset.text = formattedTime\n        ? ` • ${formattedTime}`\n        : '';\n    });\n  });\n\n  const componentName = useMemo(() => {\n    if (!currentFiber) return null;\n    const { name, wrappers, wrapperTypes } =\n      getExtendedDisplayName(currentFiber);\n\n    const title = wrappers.length\n      ? `${wrappers.join('(')}(${name})${')'.repeat(wrappers.length)}`\n      : (name ?? '');\n\n    const firstWrapperType = wrapperTypes[0];\n    return (\n      <span title={title} className=\"flex items-center gap-x-1\">\n        {name ?? 'Unknown'}\n        <span\n          title={firstWrapperType?.title}\n          className=\"flex items-center gap-x-1 text-[10px] text-purple-400\"\n        >\n          {!!firstWrapperType && (\n            <>\n              <span\n                key={firstWrapperType.type}\n                className={cn(\n                  'rounded py-[1px] px-1',\n                  'truncate',\n                  firstWrapperType.compiler && 'bg-purple-800 text-neutral-400',\n                  !firstWrapperType.compiler &&\n                    'bg-neutral-700 text-neutral-300',\n                  firstWrapperType.type === 'memo' && 'bg-[#5f3f9a] text-white',\n                )}\n              >\n                {firstWrapperType.type}\n              </span>\n              {firstWrapperType.compiler && (\n                <span className=\"text-yellow-300\">✨</span>\n              )}\n            </>\n          )}\n        </span>\n        {wrapperTypes.length > 1 && (\n          <span className=\"text-[10px] text-neutral-400\">\n            ×{wrapperTypes.length - 1}\n          </span>\n        )}\n      </span>\n    );\n  }, [currentFiber]);\n\n  return (\n    <div className={headerInspectClassName}>\n      {componentName}\n      {/* useless info */}\n      <div className=\"flex items-center gap-x-2 mr-auto text-xs text-[#888]\">\n        <span\n          ref={refReRenders}\n          className=\"with-data-text cursor-pointer !overflow-visible\"\n          title=\"Click to toggle between rerenders and total renders\"\n        />\n        <span ref={refTiming} className=\"with-data-text !overflow-visible\" />\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/scan/src/web/views/inspector/index.tsx",
    "content": "import { computed, untracked, useSignalEffect } from '@preact/signals';\nimport type { Fiber } from 'bippy';\nimport { Component } from 'preact';\nimport { useEffect, useRef } from 'preact/hooks';\nimport { Store } from '~core/index';\nimport { Icon } from '~web/components/icon';\nimport { signalIsSettingsOpen, signalWidgetViews } from '~web/state';\nimport { cn } from '~web/utils/helpers';\nimport { constant } from '~web/utils/preact/constant';\nimport { ComponentsTree } from './components-tree';\nimport { flashManager } from './flash-overlay';\nimport {\n  type TimelineUpdate,\n  inspectorUpdateSignal,\n  timelineActions,\n} from './states';\nimport {\n  collectInspectorData,\n  getStateNames,\n  resetTracking,\n} from './timeline/utils';\nimport { extractMinimalFiberInfo, getCompositeFiberFromElement } from './utils';\nimport { WhatChanged } from './what-changed';\n\nexport const globalInspectorState = {\n  lastRendered: new Map<string, unknown>(),\n  expandedPaths: new Set<string>(),\n  cleanup: () => {\n    globalInspectorState.lastRendered.clear();\n    globalInspectorState.expandedPaths.clear();\n    flashManager.cleanupAll();\n    resetTracking();\n    timelineActions.reset();\n  },\n};\n\n// todo: add reset button and error message\nclass InspectorErrorBoundary extends Component {\n  state: { error: Error | null; hasError: boolean } = {\n    hasError: false,\n    error: null,\n  };\n\n  static getDerivedStateFromError(e: Error) {\n    return { hasError: true, error: e };\n  }\n\n  handleReset = () => {\n    this.setState({ hasError: false, error: null });\n    globalInspectorState.cleanup();\n  };\n\n  render() {\n    if (this.state.hasError) {\n      return (\n        <div className=\"p-4 bg-red-950/50 h-screen backdrop-blur-sm\">\n          <div className=\"flex items-center gap-2 mb-3 text-red-400 font-medium\">\n            <Icon name=\"icon-flame\" className=\"text-red-500\" size={16} />\n            Something went wrong in the inspector\n          </div>\n          <div className=\"p-3 bg-black/40 rounded font-mono text-xs text-red-300 mb-4 break-words\">\n            {this.state.error?.message || JSON.stringify(this.state.error)}\n          </div>\n          <button\n            type=\"button\"\n            onClick={this.handleReset}\n            className=\"px-4 py-2 bg-red-500 hover:bg-red-600 text-white rounded-md text-sm font-medium transition-colors flex items-center justify-center gap-2\"\n          >\n            Reset Inspector\n          </button>\n        </div>\n      );\n    }\n\n    return this.props.children;\n  }\n}\n\nconst inspectorContainerClassName = computed(() =>\n  cn(\n    'react-scan-inspector',\n    'flex-1',\n    'opacity-0',\n    'overflow-y-auto overflow-x-hidden',\n    'transition-opacity delay-0',\n    'pointer-events-none',\n    !signalIsSettingsOpen.value && 'opacity-100 delay-300 pointer-events-auto',\n  ),\n);\n\nconst Inspector = /* @__PURE__ */ constant(() => {\n  const refLastInspectedFiber = useRef<Fiber | null>(null);\n\n  // NOTE(Alexis): no need for useCallback\n  const processUpdate = (fiber: Fiber) => {\n    if (!fiber) return;\n\n    refLastInspectedFiber.current = fiber;\n    const { data: inspectorData, shouldUpdate } = collectInspectorData(fiber);\n\n    if (shouldUpdate) {\n      const update: TimelineUpdate = {\n        timestamp: Date.now(),\n        fiberInfo: extractMinimalFiberInfo(fiber),\n        props: inspectorData.fiberProps,\n        state: inspectorData.fiberState,\n        context: inspectorData.fiberContext,\n        stateNames: getStateNames(fiber),\n      };\n\n      timelineActions.addUpdate(update, fiber);\n    }\n  };\n\n  useSignalEffect(() => {\n    const state = Store.inspectState.value;\n    untracked(() => {\n      if (state.kind !== 'focused' || !state.focusedDomElement) {\n        refLastInspectedFiber.current = null;\n        globalInspectorState.cleanup();\n        return;\n      }\n\n      if (state.kind === 'focused') {\n        signalIsSettingsOpen.value = false;\n      }\n\n      const { parentCompositeFiber } = getCompositeFiberFromElement(\n        state.focusedDomElement,\n        state.fiber,\n      );\n\n      if (!parentCompositeFiber) {\n        Store.inspectState.value = {\n          kind: 'inspect-off',\n        };\n        signalWidgetViews.value = {\n          view: 'none',\n        };\n        return;\n      }\n\n      const isNewComponent =\n        refLastInspectedFiber.current?.type !== parentCompositeFiber.type;\n\n      if (isNewComponent) {\n        refLastInspectedFiber.current = parentCompositeFiber;\n        globalInspectorState.cleanup();\n        processUpdate(parentCompositeFiber);\n      }\n    });\n  });\n\n  useSignalEffect(() => {\n    // NOTE(Alexis): just track\n    inspectorUpdateSignal.value;\n    untracked(() => {\n      const inspectState = Store.inspectState.value;\n      if (inspectState.kind !== 'focused' || !inspectState.focusedDomElement) {\n        refLastInspectedFiber.current = null;\n        globalInspectorState.cleanup();\n        return;\n      }\n\n      const { parentCompositeFiber } = getCompositeFiberFromElement(\n        inspectState.focusedDomElement,\n        inspectState.fiber,\n      );\n\n      if (!parentCompositeFiber) {\n        Store.inspectState.value = {\n          kind: 'inspect-off',\n        };\n        signalWidgetViews.value = {\n          view: 'none',\n        };\n        return;\n      }\n\n      processUpdate(parentCompositeFiber);\n\n      if (!inspectState.focusedDomElement.isConnected) {\n        refLastInspectedFiber.current = null;\n        globalInspectorState.cleanup();\n        Store.inspectState.value = {\n          kind: 'inspecting',\n          hoveredDomElement: null,\n        };\n      }\n    });\n  });\n\n  useEffect(() => {\n    return () => {\n      globalInspectorState.cleanup();\n    };\n  }, []);\n\n  return (\n    <InspectorErrorBoundary>\n      <div className={inspectorContainerClassName}>\n        <div className=\"w-full h-full\">\n          <WhatChanged />\n        </div>\n      </div>\n    </InspectorErrorBoundary>\n  );\n});\n\nexport const ViewInspector = /* @__PURE__ */ constant(() => {\n  if (Store.inspectState.value.kind !== 'focused') return null;\n  return (\n    <InspectorErrorBoundary>\n      <Inspector />\n      <ComponentsTree />\n    </InspectorErrorBoundary>\n  );\n});\n"
  },
  {
    "path": "packages/scan/src/web/views/inspector/logging.ts",
    "content": "/**\n * Safely stringifies any value, handling circular references and special types\n */\nexport function safeStringify(value: unknown): string {\n  const seen = new WeakSet();\n\n  return JSON.stringify(\n    value,\n    (_key, value) => {\n      if (typeof value === 'function') {\n        return '[Function]';\n      }\n      if (value instanceof Error) {\n        return `[Error: ${value.message}]`;\n      }\n      if (value instanceof RegExp) {\n        return value.toString();\n      }\n      if (value instanceof Map) {\n        return `Map(${value.size})`;\n      }\n      if (value instanceof Set) {\n        return `Set(${value.size})`;\n      }\n      if (typeof value === 'object' && value !== null) {\n        if (seen.has(value)) {\n          return '[Circular]';\n        }\n        seen.add(value);\n      }\n      return value;\n    },\n    2,\n  );\n}\n"
  },
  {
    "path": "packages/scan/src/web/views/inspector/overlay/index.tsx",
    "content": "import { type Fiber, getDisplayName } from 'bippy';\nimport { useEffect, useRef } from 'preact/hooks';\nimport { ReactScanInternals, Store } from '~core/index';\n\nimport { signalIsSettingsOpen, signalWidgetViews } from '~web/state';\nimport { IS_CLIENT } from '~web/utils/constants';\nimport { cn, throttle } from '~web/utils/helpers';\nconst lerp = (start: number, end: number, t: number) => start + (end - start) * t;\nimport {\n  type States,\n  findComponentDOMNode,\n  getAssociatedFiberRect,\n  getCompositeComponentFromElement,\n  nonVisualTags,\n} from '../utils';\n\ntype DrawKind = 'locked' | 'inspecting';\n\ninterface Rect {\n  left: number;\n  top: number;\n  width: number;\n  height: number;\n}\n\ninterface LockIconRect {\n  x: number;\n  y: number;\n  width: number;\n  height: number;\n}\n\nconst ANIMATION_CONFIG = {\n  frameInterval: 1000 / 60,\n  speeds: {\n    fast: 0.51,\n    slow: 0.1,\n    off: 0,\n  },\n} as const;\n\nexport const OVERLAY_DPR = IS_CLIENT\n  ? /* @__PURE__ */ window.devicePixelRatio || 1\n  : 1;\n\nexport const currentLockIconRect: LockIconRect | null = null;\n\nexport const ScanOverlay = () => {\n  const refCanvas = useRef<HTMLCanvasElement>(null);\n  const refEventCatcher = useRef<HTMLDivElement>(null);\n  const refCurrentRect = useRef<Rect | null>(null);\n  const refCurrentLockIconRect = useRef<LockIconRect | null>(null);\n  const refLastHoveredElement = useRef<Element | null>(null);\n  const refRafId = useRef<number>(0);\n  const refTimeout = useRef<TTimer>();\n  const refCleanupMap = useRef(\n    new Map<States['kind'] | 'fade-out', () => void>(),\n  );\n  const refIsFadingOut = useRef(false);\n  const refLastFrameTime = useRef<number>(0);\n\n  const drawLockIcon = (\n    ctx: CanvasRenderingContext2D,\n    x: number,\n    y: number,\n    size: number,\n  ) => {\n    ctx.save();\n    ctx.strokeStyle = 'white';\n    ctx.fillStyle = 'white';\n    ctx.lineWidth = 1.5;\n\n    const shackleWidth = size * 0.6;\n    const shackleHeight = size * 0.5;\n    const shackleX = x + (size - shackleWidth) / 2;\n    const shackleY = y;\n\n    ctx.beginPath();\n    ctx.arc(\n      shackleX + shackleWidth / 2,\n      shackleY + shackleHeight / 2,\n      shackleWidth / 2,\n      Math.PI,\n      0,\n      false,\n    );\n    ctx.stroke();\n\n    const bodyWidth = size * 0.8;\n    const bodyHeight = size * 0.5;\n    const bodyX = x + (size - bodyWidth) / 2;\n    const bodyY = y + shackleHeight / 2;\n\n    ctx.fillRect(bodyX, bodyY, bodyWidth, bodyHeight);\n    ctx.restore();\n  };\n\n  const drawStatsPill = (\n    ctx: CanvasRenderingContext2D,\n    rect: Rect,\n    kind: 'locked' | 'inspecting',\n    fiber: Fiber | null,\n  ) => {\n    if (!fiber) return;\n\n    const pillHeight = 24;\n    const pillPadding = 8;\n    const componentName =\n      (fiber?.type && getDisplayName(fiber.type)) ?? 'Unknown';\n    const text = componentName;\n\n    ctx.save();\n    ctx.font = '12px system-ui, -apple-system, sans-serif';\n    const textMetrics = ctx.measureText(text);\n    const textWidth = textMetrics.width;\n    const lockIconSize = kind === 'locked' ? 14 : 0;\n    const lockIconPadding = kind === 'locked' ? 6 : 0;\n    const pillWidth =\n      textWidth + pillPadding * 2 + lockIconSize + lockIconPadding;\n\n    const pillX = rect.left;\n    const pillY = rect.top - pillHeight - 4;\n\n    ctx.fillStyle = 'rgb(37, 37, 38, .75)';\n    ctx.beginPath();\n    ctx.roundRect(pillX, pillY, pillWidth, pillHeight, 3);\n    ctx.fill();\n\n    if (kind === 'locked') {\n      const lockX = pillX + pillPadding;\n      const lockY = pillY + (pillHeight - lockIconSize) / 2 + 2;\n      drawLockIcon(ctx, lockX, lockY, lockIconSize);\n      refCurrentLockIconRect.current = {\n        x: lockX,\n        y: lockY,\n        width: lockIconSize,\n        height: lockIconSize,\n      };\n    } else {\n      refCurrentLockIconRect.current = null;\n    }\n\n    ctx.fillStyle = 'white';\n    ctx.textBaseline = 'middle';\n    const textX =\n      pillX +\n      pillPadding +\n      (kind === 'locked' ? lockIconSize + lockIconPadding : 0);\n    ctx.fillText(text, textX, pillY + pillHeight / 2);\n    ctx.restore();\n  };\n\n  const drawRect = (\n    canvas: HTMLCanvasElement,\n    ctx: CanvasRenderingContext2D,\n    kind: DrawKind,\n    fiber: Fiber | null,\n  ) => {\n    if (!refCurrentRect.current) return;\n    const rect = refCurrentRect.current;\n    ctx.clearRect(0, 0, canvas.width, canvas.height);\n\n    ctx.strokeStyle = 'rgba(142, 97, 227, 0.5)';\n    ctx.fillStyle = 'rgba(173, 97, 230, 0.10)';\n\n    if (kind === 'locked') {\n      ctx.setLineDash([]);\n    } else {\n      ctx.setLineDash([4]);\n    }\n\n    ctx.lineWidth = 1;\n    ctx.fillRect(rect.left, rect.top, rect.width, rect.height);\n    ctx.strokeRect(rect.left, rect.top, rect.width, rect.height);\n\n    drawStatsPill(ctx, rect, kind, fiber);\n  };\n\n  const animate = (\n    canvas: HTMLCanvasElement,\n    ctx: CanvasRenderingContext2D,\n    targetRect: Rect,\n    kind: DrawKind,\n    parentCompositeFiber: Fiber,\n    onComplete?: () => void,\n  ) => {\n    const speed = ReactScanInternals.options.value\n      .animationSpeed as keyof typeof ANIMATION_CONFIG.speeds;\n    const t = ANIMATION_CONFIG.speeds[speed] ?? ANIMATION_CONFIG.speeds.off;\n\n    const animationFrame = (timestamp: number) => {\n      if (\n        timestamp - refLastFrameTime.current <\n        ANIMATION_CONFIG.frameInterval\n      ) {\n        refRafId.current = requestAnimationFrame(animationFrame);\n        return;\n      }\n      refLastFrameTime.current = timestamp;\n\n      if (!refCurrentRect.current) {\n        cancelAnimationFrame(refRafId.current);\n        return;\n      }\n\n      refCurrentRect.current = {\n        left: lerp(refCurrentRect.current.left, targetRect.left, t),\n        top: lerp(refCurrentRect.current.top, targetRect.top, t),\n        width: lerp(refCurrentRect.current.width, targetRect.width, t),\n        height: lerp(refCurrentRect.current.height, targetRect.height, t),\n      };\n\n      drawRect(canvas, ctx, kind, parentCompositeFiber);\n\n      const stillMoving =\n        Math.abs(refCurrentRect.current.left - targetRect.left) > 0.1 ||\n        Math.abs(refCurrentRect.current.top - targetRect.top) > 0.1 ||\n        Math.abs(refCurrentRect.current.width - targetRect.width) > 0.1 ||\n        Math.abs(refCurrentRect.current.height - targetRect.height) > 0.1;\n\n      if (stillMoving) {\n        refRafId.current = requestAnimationFrame(animationFrame);\n      } else {\n        refCurrentRect.current = targetRect;\n        drawRect(canvas, ctx, kind, parentCompositeFiber);\n        cancelAnimationFrame(refRafId.current);\n        ctx.restore();\n        onComplete?.();\n      }\n    };\n\n    cancelAnimationFrame(refRafId.current);\n    clearTimeout(refTimeout.current);\n\n    refRafId.current = requestAnimationFrame(animationFrame);\n\n    refTimeout.current = setTimeout(() => {\n      cancelAnimationFrame(refRafId.current);\n      refCurrentRect.current = targetRect;\n      drawRect(canvas, ctx, kind, parentCompositeFiber);\n      ctx.restore();\n      onComplete?.();\n    }, 1000);\n  };\n\n  const setupOverlayAnimation = (\n    canvas: HTMLCanvasElement,\n    ctx: CanvasRenderingContext2D,\n    targetRect: Rect,\n    kind: DrawKind,\n    parentCompositeFiber: Fiber,\n  ) => {\n    ctx.save();\n\n    if (!refCurrentRect.current) {\n      refCurrentRect.current = targetRect;\n      drawRect(canvas, ctx, kind, parentCompositeFiber);\n      ctx.restore();\n      return;\n    }\n\n    animate(canvas, ctx, targetRect, kind, parentCompositeFiber);\n  };\n\n  const drawHoverOverlay = async (\n    overlayElement: Element | null,\n    canvas: HTMLCanvasElement | null,\n    ctx: CanvasRenderingContext2D | null,\n    kind: DrawKind,\n  ) => {\n    if (!overlayElement || !canvas || !ctx) return;\n\n    const { parentCompositeFiber } =\n      getCompositeComponentFromElement(overlayElement);\n    const targetRect = await getAssociatedFiberRect(overlayElement);\n\n    if (!parentCompositeFiber || !targetRect) return;\n\n    setupOverlayAnimation(canvas, ctx, targetRect, kind, parentCompositeFiber);\n  };\n\n  const unsubscribeAll = () => {\n    for (const cleanup of refCleanupMap.current.values()) {\n      cleanup?.();\n    }\n  };\n\n  const cleanupCanvas = (canvas: HTMLCanvasElement) => {\n    const ctx = canvas.getContext('2d');\n    if (ctx) {\n      ctx.clearRect(0, 0, canvas.width, canvas.height);\n    }\n    refCurrentRect.current = null;\n    refCurrentLockIconRect.current = null;\n    refLastHoveredElement.current = null;\n    canvas.classList.remove('fade-in');\n    refIsFadingOut.current = false;\n  };\n\n  const startFadeOut = (onComplete?: () => void) => {\n    if (!refCanvas.current || refIsFadingOut.current) return;\n\n    const handleTransitionEnd = (e: TransitionEvent) => {\n      if (\n        !refCanvas.current ||\n        e.propertyName !== 'opacity' ||\n        !refIsFadingOut.current\n      ) {\n        return;\n      }\n      refCanvas.current.removeEventListener(\n        'transitionend',\n        handleTransitionEnd,\n      );\n      cleanupCanvas(refCanvas.current);\n      onComplete?.();\n    };\n    const existingListener = refCleanupMap.current.get('fade-out');\n    if (existingListener) {\n      existingListener();\n      refCleanupMap.current.delete('fade-out');\n    }\n\n    refCanvas.current.addEventListener('transitionend', handleTransitionEnd);\n    refCleanupMap.current.set('fade-out', () => {\n      refCanvas.current?.removeEventListener(\n        'transitionend',\n        handleTransitionEnd,\n      );\n    });\n\n    refIsFadingOut.current = true;\n    refCanvas.current.classList.remove('fade-in');\n    requestAnimationFrame(() => {\n      refCanvas.current?.classList.add('fade-out');\n    });\n  };\n\n  const startFadeIn = () => {\n    if (!refCanvas.current) return;\n    refIsFadingOut.current = false;\n    refCanvas.current.classList.remove('fade-out');\n    requestAnimationFrame(() => {\n      refCanvas.current?.classList.add('fade-in');\n    });\n  };\n\n  const handleHoverableElement = (componentElement: Element) => {\n    if (componentElement === refLastHoveredElement.current) return;\n\n    refLastHoveredElement.current = componentElement;\n\n    if (nonVisualTags.has(componentElement.tagName)) {\n      startFadeOut();\n    } else {\n      startFadeIn();\n    }\n\n    Store.inspectState.value = {\n      kind: 'inspecting',\n      hoveredDomElement: componentElement,\n    };\n  };\n\n  const handleNonHoverableArea = () => {\n    if (\n      !refCurrentRect.current ||\n      !refCanvas.current ||\n      refIsFadingOut.current\n    ) {\n      return;\n    }\n\n    startFadeOut();\n  };\n\n  const handlePointerMove = throttle((e?: PointerEvent) => {\n    const state = Store.inspectState.peek();\n    if (state.kind !== 'inspecting' || !refEventCatcher.current) return;\n\n    refEventCatcher.current.style.pointerEvents = 'none';\n    const element = document.elementFromPoint(e?.clientX ?? 0, e?.clientY ?? 0);\n\n    refEventCatcher.current.style.removeProperty('pointer-events');\n\n    clearTimeout(refTimeout.current);\n\n    if (element && element !== refCanvas.current) {\n      const { parentCompositeFiber } = getCompositeComponentFromElement(\n        element as Element,\n      );\n      if (parentCompositeFiber) {\n        const componentElement = findComponentDOMNode(parentCompositeFiber);\n        if (componentElement) {\n          handleHoverableElement(componentElement);\n          return;\n        }\n      }\n    }\n\n    handleNonHoverableArea();\n  }, 32);\n\n  const isClickInLockIcon = (e: MouseEvent, canvas: HTMLCanvasElement) => {\n    const currentRect = refCurrentLockIconRect.current;\n    if (!currentRect) return false;\n\n    const rect = canvas.getBoundingClientRect();\n    const scaleX = canvas.width / rect.width;\n    const scaleY = canvas.height / rect.height;\n    const x = (e.clientX - rect.left) * scaleX;\n    const y = (e.clientY - rect.top) * scaleY;\n    const adjustedX = x / OVERLAY_DPR;\n    const adjustedY = y / OVERLAY_DPR;\n\n    return (\n      adjustedX >= currentRect.x &&\n      adjustedX <= currentRect.x + currentRect.width &&\n      adjustedY >= currentRect.y &&\n      adjustedY <= currentRect.y + currentRect.height\n    );\n  };\n\n  const handleLockIconClick = (state: States) => {\n    if (state.kind === 'focused') {\n      Store.inspectState.value = {\n        kind: 'inspecting',\n        hoveredDomElement: state.focusedDomElement,\n      };\n    }\n  };\n\n  const handleElementClick = (e: MouseEvent) => {\n    const clickableElements = [\n      'react-scan-inspect-element',\n      'react-scan-power',\n    ];\n    // avoid capturing the synthetic event sent back to the toolbar, we don't want to block click events on it ever\n    if (\n      e.target instanceof HTMLElement &&\n      clickableElements.includes(e.target.id)\n    ) {\n      return;\n    }\n\n    const tagName = refLastHoveredElement.current?.tagName;\n    if (tagName && nonVisualTags.has(tagName)) {\n      return;\n    }\n\n    e.preventDefault();\n    e.stopPropagation();\n\n    const element =\n      refLastHoveredElement.current ??\n      document.elementFromPoint(e.clientX, e.clientY);\n    if (!element) return;\n\n    const clickedEl = e.composedPath().at(0);\n\n    if (\n      clickedEl instanceof HTMLElement &&\n      clickableElements.includes(clickedEl.id)\n    ) {\n      const syntheticEvent = new MouseEvent(e.type, e);\n      // @ts-ignore - this allows to know to not re-process this event when this event handler captures it\n      syntheticEvent.__reactScanSyntheticEvent = true;\n      clickedEl.dispatchEvent(syntheticEvent);\n      return;\n    }\n    const { parentCompositeFiber } = getCompositeComponentFromElement(\n      element as Element,\n    );\n    if (!parentCompositeFiber) return;\n\n    const componentElement = findComponentDOMNode(parentCompositeFiber);\n\n    if (!componentElement) {\n      refLastHoveredElement.current = null;\n      Store.inspectState.value = {\n        kind: 'inspect-off',\n      };\n      return;\n    }\n\n    Store.inspectState.value = {\n      kind: 'focused',\n      focusedDomElement: componentElement,\n      fiber: parentCompositeFiber,\n    };\n  };\n\n  const handleClick = (e: MouseEvent) => {\n    // @ts-ignore - metadata added to toolbar button events we create and dispatch\n    if (e.__reactScanSyntheticEvent) {\n      return;\n    }\n\n    const state = Store.inspectState.peek();\n    const canvas = refCanvas.current;\n    if (!canvas || !refEventCatcher.current) return;\n\n    if (isClickInLockIcon(e, canvas)) {\n      e.preventDefault();\n      e.stopPropagation();\n      handleLockIconClick(state);\n      return;\n    }\n\n    if (state.kind === 'inspecting') {\n      handleElementClick(e);\n    }\n  };\n\n  const handleKeyDown = (e: KeyboardEvent) => {\n    if (e.key !== 'Escape') return;\n\n    const state = Store.inspectState.peek();\n    const canvas = refCanvas.current;\n    if (!canvas) return;\n\n    if (document.activeElement?.id === 'react-scan-root') {\n      return;\n    }\n\n    signalWidgetViews.value = {\n      view: 'none',\n    };\n\n    if (state.kind === 'focused' || state.kind === 'inspecting') {\n      e.preventDefault();\n      e.stopPropagation();\n\n      switch (state.kind) {\n        case 'focused': {\n          startFadeIn();\n          refCurrentRect.current = null;\n          refLastHoveredElement.current = state.focusedDomElement;\n          Store.inspectState.value = {\n            kind: 'inspecting',\n            hoveredDomElement: state.focusedDomElement,\n          };\n          break;\n        }\n        case 'inspecting': {\n          startFadeOut(() => {\n            signalIsSettingsOpen.value = false;\n            Store.inspectState.value = {\n              kind: 'inspect-off',\n            };\n          });\n          break;\n        }\n      }\n    }\n  };\n\n  const handleStateChange = (\n    state: States,\n    canvas: HTMLCanvasElement,\n    ctx: CanvasRenderingContext2D,\n  ) => {\n    refCleanupMap.current.get(state.kind)?.();\n\n    if (refEventCatcher.current) {\n      if (state.kind !== 'inspecting') {\n        refEventCatcher.current.style.pointerEvents = 'none';\n      }\n    }\n\n    if (refRafId.current) {\n      cancelAnimationFrame(refRafId.current);\n    }\n\n    let unsubReport: (() => void) | undefined;\n\n    switch (state.kind) {\n      case 'inspect-off':\n        startFadeOut();\n        return;\n\n      case 'inspecting':\n        drawHoverOverlay(state.hoveredDomElement, canvas, ctx, 'inspecting');\n        break;\n\n      case 'focused':\n        if (!state.focusedDomElement) return;\n\n        if (refLastHoveredElement.current !== state.focusedDomElement) {\n          refLastHoveredElement.current = state.focusedDomElement;\n        }\n\n        signalWidgetViews.value = {\n          view: 'inspector',\n        };\n\n        drawHoverOverlay(state.focusedDomElement, canvas, ctx, 'locked');\n\n        unsubReport = Store.lastReportTime.subscribe(() => {\n          if (refRafId.current && refCurrentRect.current) {\n            const { parentCompositeFiber } = getCompositeComponentFromElement(\n              state.focusedDomElement,\n            );\n            if (parentCompositeFiber) {\n              drawHoverOverlay(state.focusedDomElement, canvas, ctx, 'locked');\n            }\n          }\n        });\n\n        if (unsubReport) {\n          refCleanupMap.current.set(state.kind, unsubReport);\n        }\n        break;\n    }\n  };\n\n  const updateCanvasSize = (\n    canvas: HTMLCanvasElement,\n    ctx: CanvasRenderingContext2D,\n  ) => {\n    const rect = canvas.getBoundingClientRect();\n    canvas.width = rect.width * OVERLAY_DPR;\n    canvas.height = rect.height * OVERLAY_DPR;\n    ctx.scale(OVERLAY_DPR, OVERLAY_DPR);\n    ctx.save();\n  };\n\n  const handleResizeOrScroll = () => {\n    const state = Store.inspectState.peek();\n    const canvas = refCanvas.current;\n    if (!canvas) return;\n    const ctx = canvas?.getContext('2d');\n    if (!ctx) return;\n\n    cancelAnimationFrame(refRafId.current);\n    clearTimeout(refTimeout.current);\n\n    updateCanvasSize(canvas, ctx);\n    refCurrentRect.current = null;\n\n    if (state.kind === 'focused' && state.focusedDomElement) {\n      drawHoverOverlay(state.focusedDomElement, canvas, ctx, 'locked');\n    } else if (state.kind === 'inspecting' && state.hoveredDomElement) {\n      drawHoverOverlay(state.hoveredDomElement, canvas, ctx, 'inspecting');\n    }\n  };\n\n  const handlePointerDown = (e: PointerEvent) => {\n    const state = Store.inspectState.peek();\n    const canvas = refCanvas.current;\n    if (!canvas) return;\n\n    if (\n      state.kind === 'inspecting' ||\n      isClickInLockIcon(e as unknown as MouseEvent, canvas)\n    ) {\n      e.preventDefault();\n      e.stopPropagation();\n      e.stopImmediatePropagation();\n    }\n  };\n\n  // oxlint-disable-next-line react-hooks/exhaustive-deps\n  useEffect(() => {\n    const canvas = refCanvas.current;\n    if (!canvas) return;\n    const ctx = canvas?.getContext('2d');\n    if (!ctx) return;\n\n    updateCanvasSize(canvas, ctx);\n\n    const unSubState = Store.inspectState.subscribe((state) => {\n      handleStateChange(state, canvas, ctx);\n    });\n\n    window.addEventListener('scroll', handleResizeOrScroll, { passive: true });\n    window.addEventListener('resize', handleResizeOrScroll, { passive: true });\n    document.addEventListener('pointermove', handlePointerMove, {\n      passive: true,\n      capture: true,\n    });\n    document.addEventListener('pointerdown', handlePointerDown, {\n      capture: true,\n    });\n    document.addEventListener('click', handleClick, { capture: true });\n    document.addEventListener('keydown', handleKeyDown, { capture: true });\n\n    return () => {\n      unsubscribeAll();\n      unSubState();\n      window.removeEventListener('scroll', handleResizeOrScroll);\n      window.removeEventListener('resize', handleResizeOrScroll);\n      document.removeEventListener('pointermove', handlePointerMove, {\n        capture: true,\n      });\n      document.removeEventListener('click', handleClick, { capture: true });\n      document.removeEventListener('pointerdown', handlePointerDown, {\n        capture: true,\n      });\n      document.removeEventListener('keydown', handleKeyDown, { capture: true });\n\n      if (refRafId.current) {\n        cancelAnimationFrame(refRafId.current);\n      }\n      clearTimeout(refTimeout.current);\n    };\n  }, []);\n\n  return (\n    <>\n      <div\n        ref={refEventCatcher}\n        className={cn('fixed top-0 left-0 w-screen h-screen', 'z-[214748365]')}\n        // DO NOT DO NOT DO NOT REMOVE THE STYLE IT WILL CAUSE MASSIVE PERFORMANCE ISSUES https://x.com/RobKnight__/status/1897524145157439558\n        style={{\n          pointerEvents: 'none',\n        }}\n      />\n      <canvas\n        ref={refCanvas}\n        dir=\"ltr\"\n        className={cn(\n          'react-scan-inspector-overlay',\n          'fixed top-0 left-0 w-screen h-screen',\n          'pointer-events-none',\n          'z-[214748367]',\n        )}\n      />\n    </>\n  );\n};\n"
  },
  {
    "path": "packages/scan/src/web/views/inspector/properties.tsx",
    "content": "import { getDisplayName } from 'bippy';\nimport {\n  useCallback,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'preact/hooks';\n\nimport { isEqual } from '~core/utils';\nimport { CopyToClipboard } from '~web/components/copy-to-clipboard';\nimport { Icon } from '~web/components/icon';\nimport { useMergedRefs } from '~web/hooks/use-merged-refs';\nimport { cn, tryOrElse } from '~web/utils/helpers';\nimport { globalInspectorState } from '.';\nimport { flashManager } from './flash-overlay';\nimport { timelineState } from './states';\nimport {\n  detectValueType,\n  formatForClipboard,\n  formatInitialValue,\n  formatValue,\n  getOverrideMethods,\n  getPath,\n  isEditableValue,\n  isExpandable,\n  isPromise,\n  sanitizeString,\n  updateNestedValue,\n} from './utils';\n\ninterface ValueMetadata {\n  type: string;\n  displayValue: string;\n  value?: unknown;\n  size?: number;\n  length?: number;\n  byteLength?: number;\n  entries?: Record<string, ValueMetadata>;\n  items?: Array<ValueMetadata>;\n}\ninterface PropertyElementProps {\n  name: string;\n  value: unknown | ValueMetadata;\n  section: string;\n  level: number;\n  parentPath?: string;\n  objectPathMap?: WeakMap<object, Set<string>>;\n  changedKeys?: Set<string | number>;\n  allowEditing?: boolean;\n}\n\ninterface PropertySectionProps {\n  refSticky?:\n    | ReturnType<typeof useMergedRefs<HTMLElement>>\n    | ((node: HTMLElement | null) => void);\n  isSticky?: boolean;\n  section: 'props' | 'state' | 'context';\n}\n\ninterface EditableValueProps {\n  value: unknown;\n  onSave: (newValue: unknown) => void;\n  onCancel: () => void;\n}\n\nexport const EditableValue = ({\n  value,\n  onSave,\n  onCancel,\n}: EditableValueProps) => {\n  const refInput = useRef<HTMLInputElement>(null);\n  const [editValue, setEditValue] = useState('');\n\n  useEffect(() => {\n    let initialValue = '';\n    try {\n      if (value instanceof Date) {\n        initialValue = value.toISOString().slice(0, 16);\n      } else if (\n        value instanceof Map ||\n        value instanceof Set ||\n        value instanceof RegExp ||\n        value instanceof Error ||\n        value instanceof ArrayBuffer ||\n        ArrayBuffer.isView(value) ||\n        (typeof value === 'object' && value !== null)\n      ) {\n        initialValue = formatValue(value);\n      } else {\n        initialValue = formatInitialValue(value);\n      }\n    } catch {\n      initialValue = String(value);\n    }\n    const sanitizedValue = sanitizeString(initialValue);\n    setEditValue(sanitizedValue);\n\n    requestAnimationFrame(() => {\n      if (!refInput.current) return;\n      refInput.current.focus();\n      if (typeof value === 'string') {\n        refInput.current.setSelectionRange(1, sanitizedValue.length - 1);\n      } else {\n        refInput.current.select();\n      }\n    });\n  }, [value]);\n\n  const handleChange = useCallback((e: Event) => {\n    const target = e.target as HTMLInputElement;\n    if (target) {\n      setEditValue(target.value);\n    }\n  }, []);\n\n  const handleKeyDown = (e: KeyboardEvent) => {\n    if (e.key === 'Enter') {\n      e.preventDefault();\n      try {\n        let newValue: unknown;\n        if (value instanceof Date) {\n          const date = new Date(editValue);\n          if (Number.isNaN(date.getTime())) {\n            throw new Error('Invalid date');\n          }\n          newValue = date;\n        } else {\n          const detected = detectValueType(editValue);\n          newValue = detected.value;\n        }\n        onSave(newValue);\n      } catch {\n        onCancel();\n      }\n    } else if (e.key === 'Escape') {\n      e.preventDefault();\n      e.stopPropagation();\n      e.stopImmediatePropagation();\n      onCancel();\n    }\n  };\n\n  return (\n    <input\n      ref={refInput}\n      type={value instanceof Date ? 'datetime-local' : 'text'}\n      className=\"react-scan-input flex-1\"\n      value={editValue}\n      onChange={handleChange}\n      onKeyDown={handleKeyDown}\n      onBlur={onCancel}\n      step={value instanceof Date ? 1 : undefined}\n    />\n  );\n};\n\nexport const PropertyElement = ({\n  name,\n  value,\n  section,\n  level,\n  parentPath,\n  objectPathMap = new WeakMap(),\n  changedKeys = new Set(),\n  allowEditing = true,\n}: PropertyElementProps) => {\n  const { updates, currentIndex } = timelineState.value;\n  const currentUpdate = updates[currentIndex];\n  const fiberInfo = currentUpdate?.fiberInfo;\n  const refElement = useRef<HTMLDivElement>(null);\n\n  const currentPath = getPath(\n    fiberInfo.displayName,\n    section,\n    parentPath ?? '',\n    name,\n  );\n  const [isExpanded, setIsExpanded] = useState(\n    globalInspectorState.expandedPaths.has(currentPath),\n  );\n  const [isEditing, setIsEditing] = useState(false);\n\n  const prevValue = globalInspectorState.lastRendered.get(currentPath);\n  const isChanged = !isEqual(prevValue, value);\n\n  useEffect(() => {\n    if (name === 'children') {\n      return;\n    }\n    if (section === 'context') {\n      // we avoid flashing context purple to avoid confusion to user that this causes a render\n      // it may be the case context changes but a fiber does not a depend on it, and the fiber is memoized\n      return;\n    }\n\n    const isFirstRender = !globalInspectorState.lastRendered.has(currentPath);\n    const shouldFlash = isChanged && refElement.current && !isFirstRender;\n\n    globalInspectorState.lastRendered.set(currentPath, value);\n\n    if (shouldFlash && refElement.current && level === 0) {\n      flashManager.create(refElement.current);\n    }\n  }, [value, isChanged, currentPath, level, name, section]);\n\n  const handleToggleExpand = useCallback(() => {\n    setIsExpanded((prevState: boolean) => {\n      const newIsExpanded = !prevState;\n      if (newIsExpanded) {\n        globalInspectorState.expandedPaths.add(currentPath);\n      } else {\n        globalInspectorState.expandedPaths.delete(currentPath);\n      }\n      return newIsExpanded;\n    });\n  }, [currentPath]);\n\n  const valuePreview = useMemo(() => {\n    if (typeof value === 'object' && value !== null) {\n      if ('displayValue' in value) {\n        return String(value.displayValue);\n      }\n    }\n    return formatValue(value);\n  }, [value]);\n\n  const clipboardText = useMemo(() => {\n    if (typeof value === 'object' && value !== null) {\n      if ('value' in value) {\n        return String(formatForClipboard(value.value));\n      }\n      if ('displayValue' in value) {\n        return String(value.displayValue);\n      }\n    }\n    return String(formatForClipboard(value));\n  }, [value]);\n\n  const isExpandableValue = useMemo(() => {\n    if (!value || typeof value !== 'object') return false;\n\n    if ('type' in value) {\n      const metadata = value as ValueMetadata;\n      switch (metadata.type) {\n        case 'array':\n        case 'Map':\n        case 'Set':\n          return (metadata.size ?? metadata.length ?? 0) > 0;\n        case 'object':\n          return (metadata.size ?? 0) > 0;\n        case 'ArrayBuffer':\n        case 'DataView':\n          return (metadata.byteLength ?? 0) > 0;\n        case 'circular':\n        case 'promise':\n        case 'function':\n        case 'error':\n          return false;\n        default:\n          if ('entries' in metadata || 'items' in metadata) {\n            return true;\n          }\n          return false;\n      }\n    }\n\n    return isExpandable(value);\n  }, [value]);\n\n  const { overrideProps, overrideHookState } = getOverrideMethods();\n  const canEdit = useMemo(() => {\n    if (!allowEditing) return false;\n    if (section === 'props') return !!overrideProps && name !== 'children';\n    if (section === 'state') return !!overrideHookState;\n    return false;\n  }, [section, overrideProps, overrideHookState, allowEditing, name]);\n\n  const handleEdit = useCallback(() => {\n    if (canEdit) {\n      setIsEditing(true);\n    }\n  }, [canEdit]);\n\n  const handleSave = (section: string, name: string, value: unknown) => {\n    const { updates, currentIndex, latestFiber } = timelineState.value;\n    const currentUpdate = updates[currentIndex];\n    if (!latestFiber) return;\n\n    const { overrideProps, overrideHookState } = getOverrideMethods();\n    if (!overrideProps || !overrideHookState) return;\n\n    if (section === 'props') {\n      tryOrElse(() => {\n        const currentProps = latestFiber.memoizedProps || {};\n        let currentValue: unknown;\n        let path: string[];\n\n        if (parentPath) {\n          const parts = parentPath.split('.');\n          path = parts.filter(\n            (part) =>\n              part !== 'props' && part !== getDisplayName(latestFiber.type),\n          );\n          path.push(name);\n          currentValue = path.reduce(\n            (obj: Record<string, unknown>, key) =>\n              obj && typeof obj === 'object'\n                ? (obj[key] as Record<string, unknown>)\n                : {},\n            currentProps as Record<string, unknown>,\n          );\n        } else {\n          path = [name];\n          currentValue = currentProps[name];\n        }\n\n        if (!isEqual(currentValue, value)) {\n          overrideProps(latestFiber, path, value);\n\n          // @pivanov: on first render, the alternate is null and we can't update it\n          if (latestFiber.alternate) {\n            overrideProps(latestFiber.alternate, path, value);\n          }\n        }\n      }, null);\n    } else if (section === 'state') {\n      tryOrElse(() => {\n        if (!parentPath) {\n          const stateNames = currentUpdate.stateNames;\n          const namedStateIndex = stateNames.indexOf(name);\n          const hookId =\n            namedStateIndex !== -1 ? namedStateIndex.toString() : name;\n          overrideHookState(latestFiber, hookId, [], value);\n        } else {\n          const fullPathParts = parentPath.split('.');\n          const stateIndex = fullPathParts.indexOf('state');\n          if (stateIndex === -1) return;\n\n          const statePath = fullPathParts.slice(stateIndex + 1);\n          const baseStateKey = statePath[0];\n          const stateNames = currentUpdate.stateNames;\n          const namedStateIndex = stateNames.indexOf(baseStateKey);\n          const hookId =\n            namedStateIndex !== -1 ? namedStateIndex.toString() : '0';\n\n          const currentState = currentUpdate.state.current;\n          if (\n            !currentState ||\n            !currentState.find((item) => item.name === Number(baseStateKey))\n          ) {\n            return;\n          }\n\n          const updatedState = updateNestedValue(\n            currentState.find((item) => item.name === Number(baseStateKey))\n              ?.value,\n            statePath.slice(1).concat(name),\n            value,\n          );\n          overrideHookState(latestFiber, hookId, [], updatedState);\n        }\n      }, null);\n    }\n\n    setIsEditing(false);\n  };\n\n  const checkCircularInValue = useMemo((): boolean => {\n    if (!value || typeof value !== 'object' || isPromise(value)) return false;\n\n    return 'type' in value && value.type === 'circular';\n  }, [value]);\n\n  const renderNestedProperties = useCallback(\n    (obj: unknown): preact.ComponentChildren => {\n      if (!obj || typeof obj !== 'object') return null;\n\n      if ('type' in obj) {\n        const metadata = obj as ValueMetadata;\n        if ('entries' in metadata && metadata.entries) {\n          const entries = Object.entries(metadata.entries);\n          if (entries.length === 0) return null;\n\n          return (\n            <div className=\"react-scan-nested\">\n              {entries.map(([key, val]) => (\n                <PropertyElement\n                  key={`${currentPath}-entry-${key}`}\n                  name={key}\n                  value={val}\n                  section={section}\n                  level={level + 1}\n                  parentPath={currentPath}\n                  objectPathMap={objectPathMap}\n                  changedKeys={changedKeys}\n                  allowEditing={allowEditing}\n                />\n              ))}\n            </div>\n          );\n        }\n\n        if ('items' in metadata && Array.isArray(metadata.items)) {\n          if (metadata.items.length === 0) return null;\n          return (\n            <div className=\"react-scan-nested\">\n              {metadata.items.map((item, i) => {\n                const itemKey = `${currentPath}-item-${item.type}-${i}`;\n                return (\n                  <PropertyElement\n                    key={itemKey}\n                    name={`${i}`}\n                    value={item}\n                    section={section}\n                    level={level + 1}\n                    parentPath={currentPath}\n                    objectPathMap={objectPathMap}\n                    changedKeys={changedKeys}\n                    allowEditing={allowEditing}\n                  />\n                );\n              })}\n            </div>\n          );\n        }\n        return null;\n      }\n\n      let entries: Array<[key: string | number, value: unknown]>;\n\n      if (obj instanceof ArrayBuffer) {\n        const view = new Uint8Array(obj);\n        entries = Array.from(view).map((v, i) => [i, v]);\n      } else if (obj instanceof DataView) {\n        const view = new Uint8Array(obj.buffer, obj.byteOffset, obj.byteLength);\n        entries = Array.from(view).map((v, i) => [i, v]);\n      } else if (ArrayBuffer.isView(obj)) {\n        if (obj instanceof BigInt64Array || obj instanceof BigUint64Array) {\n          entries = Array.from({ length: obj.length }, (_, i) => [i, obj[i]]);\n        } else {\n          const typedArray = obj as unknown as ArrayLike<number>;\n          entries = Array.from(typedArray).map((v, i) => [i, v]);\n        }\n      } else if (obj instanceof Map) {\n        entries = Array.from(obj.entries()).map(([k, v]) => [String(k), v]);\n      } else if (obj instanceof Set) {\n        entries = Array.from(obj).map((v, i) => [i, v]);\n      } else if (Array.isArray(obj)) {\n        entries = obj.map((value, index) => [`${index}`, value]);\n      } else {\n        entries = Object.entries(obj);\n      }\n\n      if (entries.length === 0) return null;\n\n      const canEditChildren = !(\n        obj instanceof DataView ||\n        obj instanceof ArrayBuffer ||\n        ArrayBuffer.isView(obj)\n      );\n\n      return (\n        <div className=\"react-scan-nested\">\n          {entries.map(([key, val]) => {\n            const itemKey = `${currentPath}-${typeof key === 'number' ? `item-${key}` : key}`;\n            return (\n              <PropertyElement\n                key={itemKey}\n                name={String(key)}\n                value={val}\n                section={section}\n                level={level + 1}\n                parentPath={currentPath}\n                objectPathMap={objectPathMap}\n                changedKeys={changedKeys}\n                allowEditing={canEditChildren}\n              />\n            );\n          })}\n        </div>\n      );\n    },\n    [section, level, currentPath, objectPathMap, changedKeys, allowEditing],\n  );\n\n  if (checkCircularInValue) {\n    return (\n      <div className=\"react-scan-property\">\n        <div className=\"react-scan-property-content\">\n          <div className=\"react-scan-preview-line\">\n            <div className=\"react-scan-key\">{name}:</div>\n            <span className=\"text-yellow-500\">[Circular Reference]</span>\n          </div>\n        </div>\n      </div>\n    );\n  }\n\n  return (\n    <div ref={refElement} className=\"react-scan-property\">\n      <div className=\"react-scan-property-content\">\n        {isExpandableValue && (\n          <button\n            type=\"button\"\n            onClick={handleToggleExpand}\n            className=\"react-scan-arrow\"\n          >\n            <Icon\n              name=\"icon-chevron-right\"\n              size={12}\n              className={cn(isExpanded && 'rotate-90')}\n            />\n          </button>\n        )}\n\n        <div\n          className={cn(\n            'group',\n            'react-scan-preview-line',\n            isChanged && 'react-scan-highlight',\n          )}\n        >\n          <div className=\"react-scan-key\">{name}:</div>\n          {isEditing && isEditableValue(value, parentPath) ? (\n            <EditableValue\n              value={value}\n              onSave={(newValue) => handleSave(section, name, newValue)}\n              onCancel={() => setIsEditing(false)}\n            />\n          ) : (\n            <button type=\"button\" className=\"truncate\" onClick={handleEdit}>\n              {valuePreview}\n            </button>\n          )}\n          <CopyToClipboard\n            text={clipboardText}\n            className=\"opacity-0 transition-opacity group-hover:opacity-100\"\n          >\n            {({ ClipboardIcon }) => <>{ClipboardIcon}</>}\n          </CopyToClipboard>\n        </div>\n        <div\n          className={cn(\n            'react-scan-expandable',\n            isExpanded && 'react-scan-expanded',\n          )}\n        >\n          {isExpandableValue && isExpanded && (\n            <div className=\"react-scan-nested\">\n              {renderNestedProperties(value)}\n            </div>\n          )}\n        </div>\n      </div>\n    </div>\n  );\n};\n\nexport const PropertySection = ({\n  refSticky,\n  isSticky,\n  section,\n}: PropertySectionProps) => {\n  const refStickyElement = useRef<HTMLElement | null>(null);\n  const { updates, currentIndex } = timelineState.value;\n  const [isExpanded, setIsExpanded] = useState(true);\n\n  const refs = useMergedRefs(refStickyElement, refSticky);\n\n  const pathMap = useMemo(() => new WeakMap<object, Set<string>>(), []);\n  const { currentData, changedKeys } = useMemo(() => {\n    const data = updates[currentIndex] ?? {\n      props: { current: {}, changes: new Set() },\n      state: { current: {}, changes: new Set() },\n      context: { current: {}, changes: new Set() },\n    };\n\n    switch (section) {\n      case 'props':\n        return {\n          currentData: data.props.current,\n          changedKeys: data.props.changes,\n        };\n      case 'state':\n        return {\n          currentData: data.state.current,\n          changedKeys: data.state.changes,\n        };\n      case 'context':\n        return {\n          currentData: data.context.current,\n          changedKeys: data.context.changes,\n        };\n      default:\n        return {\n          currentData: {},\n          changedKeys: new Set<string>(),\n        };\n    }\n  }, [section, currentIndex, updates]);\n\n  const toggleExpanded = useCallback(() => {\n    setIsExpanded((state) => {\n      if (isSticky && isExpanded) {\n        return state;\n      }\n      return !state;\n    });\n  }, [isExpanded, isSticky]);\n\n  if (\n    !currentData ||\n    (Array.isArray(currentData)\n      ? currentData.length === 0\n      : Object.keys(currentData).length === 0)\n  ) {\n    return null;\n  }\n\n  const propertyCount = Array.isArray(currentData)\n    ? currentData.length\n    : Object.keys(currentData).length;\n\n  return (\n    <>\n      <button\n        ref={refs}\n        type=\"button\"\n        onClick={toggleExpanded}\n        data-sticky\n        className=\"react-section-header\"\n      >\n        <div className=\"w-4 h-4 flex items-center justify-center\">\n          <Icon\n            name=\"icon-chevron-right\"\n            size={12}\n            className={cn(\n              isExpanded && 'rotate-90',\n              isSticky && isExpanded && 'rotate-0',\n            )}\n          />\n        </div>\n        <span className=\"capitalize\">\n          {section} {!isExpanded && propertyCount > 0 && `(${propertyCount})`}\n        </span>\n      </button>\n      <div className=\"react-scan-section\">\n        <div\n          className={cn(\n            'react-scan-expandable',\n            isExpanded && 'react-scan-expanded',\n          )}\n        >\n          <div className=\"overflow-hidden\">\n            {Array.isArray(currentData)\n              ? currentData.map(({ name, value }) => (\n                  <PropertyElement\n                    key={name}\n                    name={name}\n                    value={value}\n                    section={section}\n                    level={0}\n                    objectPathMap={pathMap}\n                    changedKeys={changedKeys}\n                  />\n                ))\n              : Object.entries(currentData).map(([key, value]) => (\n                  <PropertyElement\n                    key={key}\n                    name={key}\n                    value={value}\n                    section={section}\n                    level={0}\n                    objectPathMap={pathMap}\n                    changedKeys={changedKeys}\n                  />\n                ))}\n          </div>\n        </div>\n      </div>\n    </>\n  );\n};\n"
  },
  {
    "path": "packages/scan/src/web/views/inspector/states.ts",
    "content": "import { signal } from '@preact/signals';\nimport type { Fiber } from 'bippy';\nimport type { ComponentType } from 'preact';\nimport { flashManager } from './flash-overlay';\nimport { type SectionData, resetTracking } from './timeline/utils';\n\nexport interface MinimalFiberInfo {\n  id?: string | number;\n  key: string | null;\n  type: ComponentType<unknown> | string;\n  displayName: string;\n  selfTime: number;\n  totalTime: number;\n}\n\nexport interface TimelineUpdate {\n  timestamp: number;\n  fiberInfo: MinimalFiberInfo;\n  props: SectionData;\n  state: SectionData;\n  context: SectionData;\n  stateNames: string[];\n}\n\nexport interface TimelineState {\n  updates: Array<TimelineUpdate>;\n  currentFiber: Fiber | null;\n  totalUpdates: number;\n  windowOffset: number;\n  currentIndex: number;\n  isViewingHistory: boolean;\n  latestFiber: Fiber | null;\n  isVisible: boolean;\n  playbackSpeed: 1 | 2 | 4;\n}\n\nexport const TIMELINE_MAX_UPDATES = 1000;\n\nexport const timelineStateDefault: TimelineState = {\n  updates: [],\n  currentFiber: null,\n  totalUpdates: 0,\n  windowOffset: 0,\n  currentIndex: 0,\n  isViewingHistory: false,\n  latestFiber: null,\n  isVisible: false,\n  playbackSpeed: 1,\n};\n\nexport const timelineState = signal<TimelineState>(timelineStateDefault);\n\nexport const inspectorUpdateSignal = signal<number>(0);\n\nlet pendingUpdates: Array<{ update: TimelineUpdate; fiber: Fiber | null }> = [];\nlet batchTimeout: ReturnType<typeof setTimeout> | null = null;\n\nconst batchUpdates = () => {\n  if (pendingUpdates.length === 0) return;\n\n  const batchedUpdates = [...pendingUpdates];\n\n  const { updates, totalUpdates, currentIndex, isViewingHistory } =\n    timelineState.value;\n  const newUpdates = [...updates];\n  let newTotalUpdates = totalUpdates;\n\n  for (const { update } of batchedUpdates) {\n    if (newUpdates.length >= TIMELINE_MAX_UPDATES) {\n      newUpdates.shift();\n    }\n    newUpdates.push(update);\n    newTotalUpdates++;\n  }\n\n  const newWindowOffset = Math.max(0, newTotalUpdates - TIMELINE_MAX_UPDATES);\n\n  let newCurrentIndex: number;\n  if (isViewingHistory) {\n    if (currentIndex === totalUpdates - 1) {\n      newCurrentIndex = newUpdates.length - 1;\n    } else if (currentIndex === 0) {\n      newCurrentIndex = 0;\n    } else {\n      if (newWindowOffset === 0) {\n        newCurrentIndex = currentIndex;\n      } else {\n        newCurrentIndex = currentIndex - 1;\n      }\n    }\n  } else {\n    newCurrentIndex = newUpdates.length - 1;\n  }\n\n  const lastUpdate = batchedUpdates[batchedUpdates.length - 1];\n\n  timelineState.value = {\n    ...timelineState.value,\n    latestFiber: lastUpdate.fiber,\n    updates: newUpdates,\n    totalUpdates: newTotalUpdates,\n    windowOffset: newWindowOffset,\n    currentIndex: newCurrentIndex,\n    isViewingHistory,\n  };\n\n  // Only after signal is updated, remove the processed updates\n  pendingUpdates = pendingUpdates.slice(batchedUpdates.length);\n};\n\nexport const timelineActions = {\n  showTimeline: () => {\n    timelineState.value = {\n      ...timelineState.value,\n      isVisible: true,\n    };\n  },\n\n  hideTimeline: () => {\n    timelineState.value = {\n      ...timelineState.value,\n      isVisible: false,\n      currentIndex: timelineState.value.updates.length - 1,\n    };\n  },\n\n  updateFrame: (index: number, isViewingHistory: boolean) => {\n    timelineState.value = {\n      ...timelineState.value,\n      currentIndex: index,\n      isViewingHistory,\n    };\n  },\n\n  updatePlaybackSpeed: (speed: TimelineState['playbackSpeed']) => {\n    timelineState.value = {\n      ...timelineState.value,\n      playbackSpeed: speed,\n    };\n  },\n\n  addUpdate: (update: TimelineUpdate, latestFiber: Fiber | null) => {\n    pendingUpdates.push({ update, fiber: latestFiber });\n\n    if (!batchTimeout) {\n      const processBatch = () => {\n        batchUpdates();\n\n        batchTimeout = null;\n\n        if (pendingUpdates.length > 0) {\n          batchTimeout = setTimeout(processBatch, 96);\n        }\n      };\n\n      batchTimeout = setTimeout(processBatch, 96);\n    }\n  },\n\n  reset: () => {\n    if (batchTimeout) {\n      clearTimeout(batchTimeout);\n      batchTimeout = null;\n    }\n    pendingUpdates = [];\n    timelineState.value = timelineStateDefault;\n  },\n};\n\nexport const globalInspectorState = {\n  lastRendered: new Map<string, unknown>(),\n  expandedPaths: new Set<string>(),\n  cleanup: () => {\n    globalInspectorState.lastRendered.clear();\n    globalInspectorState.expandedPaths.clear();\n    flashManager.cleanupAll();\n    resetTracking();\n    timelineState.value = timelineStateDefault;\n  },\n};\n"
  },
  {
    "path": "packages/scan/src/web/views/inspector/timeline/index.tsx",
    "content": "import { isInstrumentationActive } from 'bippy';\nimport { memo } from 'preact/compat';\nimport { useCallback, useEffect, useMemo, useRef } from 'preact/hooks';\nimport { Icon } from '~web/components/icon';\nimport { Slider } from '~web/components/slider';\nimport type { useMergedRefs } from '~web/hooks/use-merged-refs';\nimport { timelineActions, timelineState } from '../states';\nimport { calculateSliderValues } from '../utils';\n\ninterface TimelineProps {\n  refSticky?:\n    | ReturnType<typeof useMergedRefs<HTMLElement>>\n    | ((node: HTMLElement | null) => void);\n}\n\nexport const Timeline = /* @__PURE__ */ memo(({ refSticky }: TimelineProps) => {\n  const refPlayInterval = useRef<number | null>(null);\n\n  const { currentIndex, isVisible, totalUpdates, updates } =\n    timelineState.value;\n\n  const sliderValues = useMemo(() => {\n    return calculateSliderValues(totalUpdates, currentIndex);\n  }, [totalUpdates, currentIndex]);\n\n  const handleSliderChange = async (e: Event) => {\n    const target = e.target as HTMLInputElement;\n    const value = Number.parseInt(target.value, 10);\n\n    const newIndex = Math.min(updates.length - 1, Math.max(0, value));\n\n    let isViewingHistory = false;\n    if (newIndex > 0 && newIndex < updates.length - 1) {\n      isViewingHistory = true;\n    }\n    timelineActions.updateFrame(newIndex, isViewingHistory);\n  };\n\n  useEffect(() => {\n    return () => {\n      if (refPlayInterval.current) {\n        clearInterval(refPlayInterval.current);\n      }\n    };\n  }, []);\n\n  const handleShowTimeline = useCallback(() => {\n    if (!isVisible) {\n      timelineActions.showTimeline();\n    }\n  }, [isVisible]);\n\n  const handleHideTimeline = useCallback((e: Event) => {\n    e.preventDefault();\n    e.stopPropagation();\n    if (refPlayInterval.current) {\n      clearInterval(refPlayInterval.current);\n      refPlayInterval.current = null;\n    }\n    timelineActions.hideTimeline();\n  }, []);\n\n  if (!isInstrumentationActive()) {\n    return null;\n  }\n\n  if (totalUpdates <= 1) {\n    return null;\n  }\n\n  return (\n    <button\n      ref={refSticky}\n      type=\"button\"\n      onClick={handleShowTimeline}\n      className=\"react-section-header\"\n      data-disable-scroll=\"true\"\n    >\n      <button\n        type=\"button\"\n        onClick={isVisible ? handleHideTimeline : undefined}\n        title={\n          isVisible ? 'Hide Re-renders History' : 'View Re-renders History'\n        }\n        className=\"w-4 h-4 flex items-center justify-center\"\n      >\n        <Icon name=\"icon-gallery-horizontal-end\" size={12} />\n      </button>\n      {isVisible ? (\n        <>\n          <div className=\"text-xs text-gray-500\">{sliderValues.leftValue}</div>\n          <Slider\n            min={sliderValues.min}\n            max={sliderValues.max}\n            value={sliderValues.value}\n            onChange={handleSliderChange}\n            className=\"flex-1\"\n            totalUpdates={sliderValues.rightValue + 1}\n          />\n          <div className=\"text-xs text-gray-500\">{sliderValues.rightValue}</div>\n        </>\n      ) : (\n        'View Re-renders History'\n      )}\n    </button>\n  );\n});\n"
  },
  {
    "path": "packages/scan/src/web/views/inspector/timeline/utils.ts",
    "content": "import {\n  ClassComponentTag,\n  type ContextDependency,\n  type Fiber,\n  ForwardRefTag,\n  FunctionComponentTag,\n  MemoComponentTag,\n  type MemoizedState,\n  SimpleMemoComponentTag,\n} from 'bippy';\nimport { isEqual } from '~core/utils';\nimport { getChangedPropsDetailed, isPromise } from '../utils';\n\ninterface ChangeTrackingInfo {\n  count: number;\n  currentValue: unknown;\n  previousValue: unknown;\n  lastUpdated: number;\n}\n\ntype ChangeKey = string | number;\n\nconst propsTracker = new Map<string, ChangeTrackingInfo>();\nconst stateTracker = new Map<ChangeKey, ChangeTrackingInfo>();\nconst contextTracker = new Map<string, ChangeTrackingInfo>();\nlet lastComponentType: unknown = null;\n\nconst STATE_NAME_REGEX = /\\[(?<name>\\w+),\\s*set\\w+\\]/g;\nconst PROPS_ORDER_REGEX = /\\(\\s*{\\s*(?<props>[^}]+)\\s*}\\s*\\)/;\n\nexport const getStateNames = (fiber: Fiber): Array<string> => {\n  const componentSource = fiber.type?.toString?.() || '';\n  return componentSource\n    ? Array.from(\n        componentSource.matchAll(STATE_NAME_REGEX),\n        (m: RegExpMatchArray) => m.groups?.name ?? '',\n      )\n    : [];\n};\n\nexport const resetTracking = () => {\n  propsTracker.clear();\n  stateTracker.clear();\n  contextTracker.clear();\n  lastComponentType = null;\n};\n\nexport const isInitialComponentUpdate = (fiber: Fiber): boolean => {\n  const isNewComponent = fiber.type !== lastComponentType;\n  lastComponentType = fiber.type;\n  return isNewComponent;\n};\n\nexport const trackChange = (\n  tracker: Map<ChangeKey, ChangeTrackingInfo>,\n  key: ChangeKey,\n  currentValue: unknown,\n  previousValue: unknown,\n): { hasChanged: boolean; count: number } => {\n  const existing = tracker.get(key);\n  const isInitialValue = tracker === propsTracker || tracker === contextTracker;\n  const hasChanged = !isEqual(currentValue, previousValue);\n\n  if (!existing) {\n    // For props and context, start with count 1 if there's a change\n    tracker.set(key, {\n      count: hasChanged && isInitialValue ? 1 : 0,\n      currentValue,\n      previousValue,\n      lastUpdated: Date.now(),\n    });\n\n    return {\n      hasChanged,\n      count: hasChanged && isInitialValue ? 1 : isInitialValue ? 0 : 1,\n    };\n  }\n\n  if (!isEqual(existing.currentValue, currentValue)) {\n    const newCount = existing.count + 1;\n    tracker.set(key, {\n      count: newCount,\n      currentValue,\n      previousValue: existing.currentValue,\n      lastUpdated: Date.now(),\n    });\n    return { hasChanged: true, count: newCount };\n  }\n\n  return { hasChanged: false, count: existing.count };\n};\n\nexport { propsTracker, stateTracker, contextTracker };\n\nexport interface SectionData {\n  current: Array<{ name: string | number; value: unknown }>;\n  changes: Set<string | number>;\n  changesCounts: Map<string | number, number>;\n}\n\nexport interface InspectorData {\n  fiberProps: SectionData;\n  fiberState: SectionData;\n  fiberContext: SectionData;\n}\n\nexport const getStateFromFiber = (\n  fiber: Fiber,\n): Record<string | number, unknown> => {\n  if (!fiber) return {};\n\n  if (\n    fiber.tag === FunctionComponentTag ||\n    fiber.tag === ForwardRefTag ||\n    fiber.tag === SimpleMemoComponentTag ||\n    fiber.tag === MemoComponentTag\n  ) {\n    let memoizedState: MemoizedState | null = fiber.memoizedState;\n    const state: Record<number, unknown> = {};\n    let index = 0;\n\n    while (memoizedState) {\n      if (memoizedState.queue && memoizedState.memoizedState !== undefined) {\n        state[index] = memoizedState.memoizedState;\n      }\n      memoizedState = memoizedState.next;\n      index++;\n    }\n\n    return state;\n  }\n\n  if (fiber.tag === ClassComponentTag) {\n    return fiber.memoizedState || {};\n  }\n\n  return {};\n};\n\n/**\n * Used to preserve the order of the fiber's props as represented in source code\n */\nexport const getPropsOrder = (fiber: Fiber): Array<string> => {\n  const componentSource = fiber.type?.toString?.() || '';\n  const match = componentSource.match(PROPS_ORDER_REGEX);\n  if (!match?.groups?.props) return [];\n\n  return match.groups.props\n    .split(',')\n    .map((prop: string) => prop.trim().split(':')[0].split('=')[0].trim())\n    .filter(Boolean);\n};\n\nexport interface InspectorDataResult {\n  data: InspectorData;\n  shouldUpdate: boolean;\n}\n\ninterface BaseChange {\n  name: string | number;\n  value: unknown;\n  prevValue: unknown;\n}\n\ninterface PropChange extends BaseChange {\n  name: string;\n}\n\ninterface StateChange extends BaseChange {\n  name: string | number;\n}\n\ninterface ContextChange extends BaseChange {\n  name: string;\n  contextType: unknown;\n}\n\ninterface CollectorResult<T extends BaseChange = BaseChange> {\n  current: Record<string | number, unknown>;\n  prev: Record<string | number, unknown>;\n  changes: Array<T>;\n}\n\nexport const collectPropsChanges = (\n  fiber: Fiber,\n): CollectorResult<PropChange> => {\n  const currentProps = fiber.memoizedProps || {};\n  const prevProps = fiber.alternate?.memoizedProps || {};\n\n  const current: Record<string, unknown> = {};\n  const prev: Record<string, unknown> = {};\n\n  const allProps = Object.keys(currentProps);\n  for (const key of allProps) {\n    if (key in currentProps) {\n      current[key] = currentProps[key];\n      prev[key] = prevProps[key];\n    }\n  }\n\n  const changes = getChangedPropsDetailed(fiber).map((change) => ({\n    name: change.name,\n    value: change.value,\n    prevValue: change.prevValue,\n  }));\n\n  return { current, prev, changes };\n};\n\nexport const collectStateChanges = (\n  fiber: Fiber,\n): CollectorResult<StateChange> => {\n  const current = getStateFromFiber(fiber);\n  const prev = fiber.alternate ? getStateFromFiber(fiber.alternate) : {};\n  const changes: Array<StateChange> = [];\n\n  for (const [index, value] of Object.entries(current)) {\n    const stateKey = fiber.tag === ClassComponentTag ? index : Number(index);\n    if (fiber.alternate && !isEqual(prev[index], value)) {\n      changes.push({\n        name: stateKey,\n        value,\n        prevValue: prev[index],\n      });\n    }\n  }\n\n  return { current, prev, changes };\n};\n\nexport const collectContextChanges = (\n  fiber: Fiber,\n): CollectorResult<ContextChange> => {\n  const currentContexts = getAllFiberContexts(fiber);\n  const prevContexts = fiber.alternate\n    ? getAllFiberContexts(fiber.alternate)\n    : new Map();\n\n  const current: Record<string, unknown> = {};\n  const prev: Record<string, unknown> = {};\n  const changes: Array<ContextChange> = [];\n\n  const seenContexts = new Set<unknown>();\n  for (const [contextType, ctx] of currentContexts) {\n    const name = ctx.displayName;\n    const contextKey = contextType;\n\n    if (seenContexts.has(contextKey)) continue;\n    seenContexts.add(contextKey);\n\n    current[name] = ctx.value;\n\n    const prevCtx = prevContexts.get(contextType);\n    if (prevCtx) {\n      prev[name] = prevCtx.value;\n      if (!isEqual(prevCtx.value, ctx.value)) {\n        changes.push({\n          name,\n          value: ctx.value,\n          prevValue: prevCtx.value,\n          contextType,\n        });\n      }\n    }\n  }\n\n  return { current, prev, changes };\n};\n\nexport const collectInspectorData = (fiber: Fiber): InspectorDataResult => {\n  const emptySection = (): SectionData => ({\n    current: [],\n    changes: new Set<string | number>(),\n    changesCounts: new Map<string | number, number>(),\n  });\n\n  if (!fiber) {\n    return {\n      data: {\n        fiberProps: emptySection(),\n        fiberState: emptySection(),\n        fiberContext: emptySection(),\n      },\n      shouldUpdate: false,\n    };\n  }\n\n  let hasNewChanges = false;\n  const isInitialUpdate = isInitialComponentUpdate(fiber);\n\n  const propsData = emptySection();\n  if (fiber.memoizedProps) {\n    const { current, changes } = collectPropsChanges(fiber);\n\n    for (const [key, value] of Object.entries(current)) {\n      propsData.current.push({\n        name: key,\n        value: isPromise(value)\n          ? { type: 'promise', displayValue: 'Promise' }\n          : value,\n      });\n    }\n\n    for (const change of changes) {\n      const { hasChanged, count } = trackChange(\n        propsTracker,\n        change.name,\n        change.value,\n        change.prevValue,\n      );\n\n      if (hasChanged) {\n        hasNewChanges = true;\n        propsData.changes.add(change.name);\n        propsData.changesCounts.set(change.name, count);\n      }\n    }\n  }\n\n  const stateData = emptySection();\n  const { current: stateCurrent, changes: stateChanges } =\n    collectStateChanges(fiber);\n\n  for (const [index, value] of Object.entries(stateCurrent)) {\n    const stateKey = fiber.tag === ClassComponentTag ? index : Number(index);\n    stateData.current.push({ name: stateKey, value });\n  }\n\n  for (const change of stateChanges) {\n    const { hasChanged, count } = trackChange(\n      stateTracker,\n      change.name,\n      change.value,\n      change.prevValue,\n    );\n\n    if (hasChanged) {\n      hasNewChanges = true;\n      stateData.changes.add(change.name);\n      stateData.changesCounts.set(change.name, count);\n    }\n  }\n\n  const contextData = emptySection();\n  const { current: contextCurrent, changes: contextChanges } =\n    collectContextChanges(fiber);\n\n  for (const [name, value] of Object.entries(contextCurrent)) {\n    contextData.current.push({ name, value });\n  }\n\n  if (!isInitialUpdate) {\n    for (const change of contextChanges) {\n      const { hasChanged, count } = trackChange(\n        contextTracker,\n        change.name,\n        change.value,\n        change.prevValue,\n      );\n\n      if (hasChanged) {\n        hasNewChanges = true;\n        contextData.changes.add(change.name);\n        contextData.changesCounts.set(change.name, count);\n      }\n    }\n  }\n\n  if (!hasNewChanges && !isInitialUpdate) {\n    propsData.changes.clear();\n    stateData.changes.clear();\n    contextData.changes.clear();\n  }\n\n  return {\n    data: {\n      fiberProps: propsData,\n      fiberState: stateData,\n      fiberContext: contextData,\n    },\n    shouldUpdate: hasNewChanges || isInitialUpdate,\n  };\n};\n\ninterface ContextInfo {\n  value: unknown;\n  displayName: string;\n  contextType: unknown;\n}\n// hm we potentially want to revalidate this if a fiber has new context's, i'm not sure how we can do that reactively\n// i suppose we can do one traversal on render (or during the existing traversal) that checks if any new context providers were mounted\n// and when that happens we revalidate this cache\n\n// i suppose a case this breaks is if a fiber changes ancestors through a key but doesn't remount\n// then it would have new parents... and that new parent may have new context\n// may be a fine trade off\n// the motivation is this fiber traversal on every rendering fiber is extremely expensive\nconst fiberContextsCache = new WeakMap<Fiber, Map<unknown, ContextInfo>>();\n\nexport const getAllFiberContexts = (\n  fiber: Fiber,\n): Map<unknown, ContextInfo> => {\n  if (!fiber) {\n    return new Map<unknown, ContextInfo>();\n  }\n\n  // todo validate this works\n\n  const cachedContexts = fiberContextsCache.get(fiber);\n  if (cachedContexts) {\n    return cachedContexts;\n  }\n\n  const contexts = new Map<unknown, ContextInfo>();\n  let currentFiber: Fiber | null = fiber;\n\n  while (currentFiber) {\n    const dependencies = currentFiber.dependencies;\n\n    if (dependencies?.firstContext) {\n      let contextItem: ContextDependency<unknown> | null =\n        dependencies.firstContext;\n\n      while (contextItem) {\n        const memoizedValue = contextItem.memoizedValue;\n        const displayName = contextItem.context?.displayName;\n\n        if (!contexts.has(memoizedValue)) {\n          contexts.set(contextItem.context, {\n            value: memoizedValue,\n            displayName: displayName ?? 'UnnamedContext',\n            contextType: null,\n          });\n        }\n\n        if (contextItem === contextItem.next) {\n          break;\n        }\n\n        contextItem = contextItem.next;\n      }\n    }\n\n    currentFiber = currentFiber.return;\n  }\n\n  // Cache the result for this fiber\n  fiberContextsCache.set(fiber, contexts);\n\n  return contexts;\n};\n\nexport const collectInspectorDataWithoutCounts = (fiber: Fiber) => {\n  const emptySection = (): SectionData => ({\n    current: [],\n    changes: new Set<string | number>(),\n    changesCounts: new Map<string | number, number>(),\n  });\n\n  if (!fiber) {\n    return {\n      fiberProps: emptySection(),\n      fiberState: emptySection(),\n      fiberContext: emptySection(),\n    };\n  }\n\n  // let hasNewChanges = false;\n\n  const propsData = emptySection();\n  if (fiber.memoizedProps) {\n    const { current, changes } = collectPropsChanges(fiber);\n\n    for (const [key, value] of Object.entries(current)) {\n      propsData.current.push({\n        name: key,\n        value: isPromise(value)\n          ? { type: 'promise', displayValue: 'Promise' }\n          : value,\n      });\n    }\n\n    for (const change of changes) {\n      // hasNewChanges = true;\n      propsData.changes.add(change.name);\n      propsData.changesCounts.set(change.name, 1);\n    }\n  }\n\n  const stateData = emptySection();\n  if (fiber.memoizedState) {\n    const { current, changes } = collectStateChanges(fiber);\n\n    for (const [key, value] of Object.entries(current)) {\n      stateData.current.push({\n        name: key,\n        value: isPromise(value)\n          ? { type: 'promise', displayValue: 'Promise' }\n          : value,\n      });\n    }\n\n    for (const change of changes) {\n      // hasNewChanges = true;\n      stateData.changes.add(change.name);\n      stateData.changesCounts.set(change.name, 1);\n    }\n  }\n\n  const contextData = emptySection();\n  const { current, changes } = collectContextChanges(fiber);\n\n  for (const [key, value] of Object.entries(current)) {\n    contextData.current.push({\n      name: key,\n      value: isPromise(value)\n        ? { type: 'promise', displayValue: 'Promise' }\n        : value,\n    });\n  }\n\n  for (const change of changes) {\n    // hasNewChanges = true;\n    contextData.changes.add(change.name);\n    contextData.changesCounts.set(change.name, 1);\n  }\n  // todo: is isInitialUpdate correct? Is this necessary:\n  // if (!hasNewChanges && !isInitialUpdate) {\n  //   propsData.changes.clear();\n  //   stateData.changes.clear();\n  //   contextData.changes.clear();\n  // }\n\n  return {\n    // data: {\n    fiberProps: propsData,\n    fiberState: stateData,\n    fiberContext: contextData,\n    // },\n  };\n};\n"
  },
  {
    "path": "packages/scan/src/web/views/inspector/utils.ts",
    "content": "import {\n  type Fiber,\n  FunctionComponentTag,\n  type MemoizedState,\n  getDisplayName,\n  getTimings,\n  isCompositeFiber,\n  isHostFiber,\n  traverseFiber,\n} from 'bippy';\nimport { type PropsChange, ReactScanInternals } from '~core/index';\nimport { ChangeReason } from '~core/instrumentation';\nimport { isEqual } from '~core/utils';\nimport { globalInspectorState } from '.';\nimport type { ExtendedReactRenderer } from '../../../types';\nimport { TIMELINE_MAX_UPDATES } from './states';\nimport type { MinimalFiberInfo } from './states';\nimport { getAllFiberContexts, getStateNames } from './timeline/utils';\n\ninterface StateItem {\n  name: string;\n  value: unknown;\n}\n\n// todo, change this to currently focused fiber\nexport type States =\n  | {\n      kind: 'inspecting';\n      hoveredDomElement: Element | null;\n    }\n  | {\n      kind: 'inspect-off';\n    }\n  | {\n      kind: 'focused';\n      focusedDomElement: Element;\n      fiber: Fiber;\n    }\n  | {\n      kind: 'uninitialized';\n    };\n\ninterface ReactRootContainer {\n  _reactRootContainer?: {\n    _internalRoot?: {\n      current?: {\n        child: Fiber;\n      };\n    };\n  };\n}\n\ninterface ReactInternalProps {\n  [key: string]: Fiber;\n}\n\nexport const getFiberFromElement = (element: Element): Fiber | null => {\n  if ('__REACT_DEVTOOLS_GLOBAL_HOOK__' in window) {\n    const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;\n    if (!hook?.renderers) return null;\n\n    for (const [, renderer] of Array.from(hook.renderers)) {\n      try {\n        const fiber = renderer.findFiberByHostInstance?.(element);\n        if (fiber) return fiber;\n      } catch {\n        // If React is mid-render, references to previous nodes may disappear\n      }\n    }\n  }\n\n  if ('_reactRootContainer' in element) {\n    const elementWithRoot = element as unknown as ReactRootContainer;\n    const rootContainer = elementWithRoot._reactRootContainer;\n    return rootContainer?._internalRoot?.current?.child ?? null;\n  }\n\n  for (const key in element) {\n    if (\n      key.startsWith('__reactInternalInstance$') ||\n      key.startsWith('__reactFiber')\n    ) {\n      const elementWithFiber = element as unknown as ReactInternalProps;\n      return elementWithFiber[key];\n    }\n  }\n  return null;\n};\n\nexport const getFirstStateNode = (fiber: Fiber): Element | null => {\n  let current: Fiber | null = fiber;\n  while (current) {\n    if (current.stateNode instanceof Element) {\n      return current.stateNode;\n    }\n\n    if (!current.child) {\n      break;\n    }\n    current = current.child;\n  }\n\n  while (current) {\n    if (current.stateNode instanceof Element) {\n      return current.stateNode;\n    }\n\n    if (!current.return) {\n      break;\n    }\n    current = current.return;\n  }\n  return null;\n};\n\nexport const getNearestFiberFromElement = (\n  element: Element | null,\n): Fiber | null => {\n  if (!element) return null;\n\n  try {\n    const fiber = getFiberFromElement(element);\n    if (!fiber) return null;\n\n    const res = getParentCompositeFiber(fiber);\n    return res ? res[0] : null;\n  } catch {\n    return null;\n  }\n};\n\nexport const getParentCompositeFiber = (\n  fiber: Fiber,\n): readonly [Fiber, Fiber | null] | null => {\n  let current: Fiber | null = fiber;\n  let prevHost: Fiber | null = null;\n\n  while (current) {\n    if (isCompositeFiber(current)) return [current, prevHost] as const;\n    if (isHostFiber(current) && !prevHost) prevHost = current;\n    current = current.return;\n  }\n\n  return null;\n};\n\n\nconst isFiberInTree = (fiber: Fiber, root: Fiber): boolean => {\n  {\n    // const root= fiberRootCache.get(fiber) || (fiber.alternate && fiberRootCache.get(fiber.alternate) )\n    // if (root){\n    //   return root\n    // }\n    const res = !!traverseFiber(root, (searchFiber) => searchFiber === fiber);\n\n    return res;\n  }\n};\n\nexport const isCurrentTree = (fiber: Fiber) => {\n  let curr: Fiber | null = fiber;\n  let rootFiber: Fiber | null = null;\n\n  while (curr) {\n    // todo: make sure removing null check doesn't break\n    // todo: document that fiber stores root in stateNode\n    if (!curr.stateNode) {\n      curr = curr.return;\n      continue;\n    }\n    // if the app never rendered then fiber roots will always return false, but thats fine since we don't care which\n    // fiber we read from when there never has been a re-render\n    // todo: document that better\n    if (ReactScanInternals.instrumentation?.fiberRoots.has(curr.stateNode)) {\n      rootFiber = curr;\n\n      break;\n    }\n\n    curr = curr.return;\n  }\n\n  if (!rootFiber) {\n    return false;\n  }\n\n  const fiberRoot = rootFiber.stateNode;\n  const currentRootFiber = fiberRoot.current;\n\n  return isFiberInTree(fiber, currentRootFiber);\n};\n\nexport const getAssociatedFiberRect = async (element: Element) => {\n  const associatedFiber = getNearestFiberFromElement(element);\n\n  if (!associatedFiber) return null;\n  const stateNode = getFirstStateNode(associatedFiber);\n  if (!stateNode) return null;\n\n  const rect = await new Promise<DOMRect | null>((resolve) => {\n    const observer = new IntersectionObserver((entries) => {\n      observer.disconnect();\n      resolve(entries[0]?.boundingClientRect ?? null);\n    });\n    observer.observe(stateNode);\n  });\n  return rect;\n};\n\n// todo-before-stable(rob): refactor these\nexport const getCompositeComponentFromElement = (element: Element) => {\n  const associatedFiber = getNearestFiberFromElement(element);\n\n  if (!associatedFiber) return {};\n\n  const stateNode = getFirstStateNode(associatedFiber);\n  if (!stateNode) return {};\n  const parentCompositeFiberInfo = getParentCompositeFiber(associatedFiber);\n  if (!parentCompositeFiberInfo) {\n    return {};\n  }\n  const [parentCompositeFiber] = parentCompositeFiberInfo;\n\n  return {\n    parentCompositeFiber,\n  };\n};\n\nexport const getCompositeFiberFromElement = (\n  element: Element,\n  knownFiber?: Fiber,\n) => {\n  if (!element.isConnected) return {};\n\n  let fiber = knownFiber ?? getNearestFiberFromElement(element);\n  if (!fiber) return {};\n\n  // Find root once and cache it\n  let curr: Fiber | null = fiber;\n  let rootFiber: Fiber | null = null;\n  let currentRootFiber: Fiber | null = null;\n\n  while (curr) {\n    if (!curr.stateNode) {\n      curr = curr.return;\n      continue;\n    }\n    if (ReactScanInternals.instrumentation?.fiberRoots.has(curr.stateNode)) {\n      rootFiber = curr;\n      currentRootFiber = curr.stateNode.current;\n      break;\n    }\n    curr = curr.return;\n  }\n\n  if (!rootFiber || !currentRootFiber) return {};\n\n  // Get the current associated fiber using cached root\n  fiber = isFiberInTree(fiber, currentRootFiber)\n    ? fiber\n    : (fiber.alternate ?? fiber);\n  if (!fiber) return {};\n\n  if (!getFirstStateNode(fiber)) return {};\n\n  // Get parent composite fiber\n  const parentCompositeFiber = getParentCompositeFiber(fiber)?.[0];\n  if (!parentCompositeFiber) return {};\n\n  // Use cached root to check parent fiber\n  return {\n    parentCompositeFiber: isFiberInTree(parentCompositeFiber, currentRootFiber)\n      ? parentCompositeFiber\n      : (parentCompositeFiber.alternate ?? parentCompositeFiber),\n  };\n};\n\nexport const getChangedPropsDetailed = (fiber: Fiber): Array<PropsChange> => {\n  const currentProps = fiber.memoizedProps ?? {};\n  const previousProps = fiber.alternate?.memoizedProps ?? {};\n  const changes: Array<PropsChange> = [];\n\n  for (const key in currentProps) {\n    if (key === 'children') continue;\n\n    const currentValue = currentProps[key];\n    const prevValue = previousProps[key];\n\n    if (!isEqual(currentValue, prevValue)) {\n      changes.push({\n        name: key,\n        value: currentValue,\n        prevValue,\n        type: ChangeReason.Props,\n      });\n    }\n  }\n\n  return changes;\n};\n\nexport interface OverrideMethods {\n  overrideProps:\n    | ((fiber: Fiber, path: string[], value: unknown) => void)\n    | null;\n  overrideHookState:\n    | ((fiber: Fiber, id: string, path: string[], value: unknown) => void)\n    | null;\n  overrideContext:\n    | ((fiber: Fiber, contextType: unknown, value: unknown) => void)\n    | null;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> => {\n  return value !== null && typeof value === 'object';\n};\n\nexport const getOverrideMethods = (): OverrideMethods => {\n  let overrideProps: OverrideMethods['overrideProps'] = null;\n  let overrideHookState: OverrideMethods['overrideHookState'] = null;\n  let overrideContext: OverrideMethods['overrideContext'] = null;\n\n  if ('__REACT_DEVTOOLS_GLOBAL_HOOK__' in window) {\n    const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;\n    if (!hook?.renderers) {\n      return {\n        overrideProps: null,\n        overrideHookState: null,\n        overrideContext: null,\n      };\n    }\n\n    for (const [, renderer] of Array.from(hook.renderers)) {\n      try {\n        const devToolsRenderer = renderer as ExtendedReactRenderer;\n\n        if (overrideHookState) {\n          const prevOverrideHookState = overrideHookState;\n          overrideHookState = (\n            fiber: Fiber,\n            id: string,\n            path: string[],\n            value: unknown,\n          ) => {\n            // Find the hook\n            let current = fiber.memoizedState;\n            for (let i = 0; i < Number(id); i++) {\n              if (!current?.next) break;\n              current = current.next;\n            }\n\n            if (current?.queue) {\n              // Update through React's queue mechanism\n              const queue = current.queue;\n              if (isRecord(queue) && 'dispatch' in queue) {\n                const dispatch = queue.dispatch as (value: unknown) => void;\n                dispatch(value);\n                return;\n              }\n            }\n\n            // Chain updates through all renderers to ensure consistency across different React renderers\n            // (e.g., React DOM + React Native Web in the same app)\n            prevOverrideHookState(fiber, id, path, value);\n            devToolsRenderer.overrideHookState?.(fiber, id, path, value);\n          };\n        } else if (devToolsRenderer.overrideHookState) {\n          overrideHookState = devToolsRenderer.overrideHookState;\n        }\n\n        if (overrideProps) {\n          const prevOverrideProps = overrideProps;\n          overrideProps = (\n            fiber: Fiber,\n            path: Array<string>,\n            value: unknown,\n          ) => {\n            // Chain updates through all renderers to maintain consistency\n            prevOverrideProps(fiber, path, value);\n            devToolsRenderer.overrideProps?.(fiber, path, value);\n          };\n        } else if (devToolsRenderer.overrideProps) {\n          overrideProps = devToolsRenderer.overrideProps;\n        }\n\n        // For context, we don't need the chaining pattern since we're using overrideProps internally\n        // to update the context provider's value prop, which already handles the chaining\n        overrideContext = (\n          fiber: Fiber,\n          contextType: unknown,\n          value: unknown,\n        ) => {\n          // Find the provider fiber for this context\n          let current: Fiber | null = fiber;\n          while (current) {\n            const type = current.type as { Provider?: unknown };\n            if (type === contextType || type?.Provider === contextType) {\n              // Found the provider, update both current and alternate fibers\n              if (overrideProps) {\n                overrideProps(current, ['value'], value);\n                if (current.alternate) {\n                  overrideProps(current.alternate, ['value'], value);\n                }\n              }\n              break;\n            }\n            current = current.return;\n          }\n        };\n      } catch {\n        /**/\n      }\n    }\n  }\n\n  return { overrideProps, overrideHookState, overrideContext };\n};\n\nexport const nonVisualTags = new Set([\n  'HTML',\n  'HEAD',\n  'META',\n  'TITLE',\n  'BASE',\n  'SCRIPT',\n  'SCRIPT',\n  'STYLE',\n  'LINK',\n  'NOSCRIPT',\n  'SOURCE',\n  'TRACK',\n  'EMBED',\n  'OBJECT',\n  'PARAM',\n  'TEMPLATE',\n  'PORTAL',\n  'SLOT',\n  'AREA',\n  'XML',\n  'DOCTYPE',\n  'COMMENT',\n]);\n\nexport const findComponentDOMNode = (\n  fiber: Fiber,\n  excludeNonVisualTags = true,\n): HTMLElement | null => {\n  if (fiber.stateNode && 'nodeType' in fiber.stateNode) {\n    const element = fiber.stateNode as HTMLElement;\n    if (\n      excludeNonVisualTags &&\n      element.tagName &&\n      nonVisualTags.has(element.tagName.toLowerCase())\n    ) {\n      return null;\n    }\n    return element;\n  }\n\n  let child = fiber.child;\n  while (child) {\n    const result = findComponentDOMNode(child, excludeNonVisualTags);\n    if (result) return result;\n    child = child.sibling;\n  }\n\n  return null;\n};\n\nexport interface InspectableElement {\n  element: HTMLElement;\n  depth: number;\n  name: string;\n  fiber: Fiber;\n}\n\nexport const getInspectableElements = (\n  root: HTMLElement = document.body,\n): Array<InspectableElement> => {\n  const result: Array<InspectableElement> = [];\n\n  const findInspectableFiber = (\n    element: HTMLElement | null,\n  ): HTMLElement | null => {\n    if (!element) return null;\n\n    const { parentCompositeFiber } = getCompositeComponentFromElement(element);\n    if (!parentCompositeFiber) return null;\n\n    const componentRoot = findComponentDOMNode(parentCompositeFiber);\n    return componentRoot === element ? element : null;\n  };\n\n  const traverse = (element: HTMLElement, depth = 0) => {\n    const inspectable = findInspectableFiber(element);\n    if (inspectable) {\n      const { parentCompositeFiber } =\n        getCompositeComponentFromElement(inspectable);\n\n      if (!parentCompositeFiber) return;\n\n      result.push({\n        element: inspectable,\n        depth,\n        name: getDisplayName(parentCompositeFiber.type) ?? 'Unknown',\n        fiber: parentCompositeFiber,\n      });\n    }\n\n    // Traverse children first (depth-first)\n    for (const child of Array.from(element.children)) {\n      traverse(child as HTMLElement, inspectable ? depth + 1 : depth);\n    }\n  };\n\n  traverse(root);\n  return result;\n};\n\nconst fiberMap = new WeakMap<HTMLElement, Fiber>();\n\nexport const getInspectableAncestors = (\n  element: HTMLElement,\n): Array<InspectableElement> => {\n  const result: Array<InspectableElement> = [];\n\n  const findInspectableFiber = (\n    element: HTMLElement | null,\n  ): HTMLElement | null => {\n    if (!element) return null;\n    const { parentCompositeFiber } = getCompositeComponentFromElement(element);\n    if (!parentCompositeFiber) return null;\n\n    const componentRoot = findComponentDOMNode(parentCompositeFiber);\n    if (componentRoot === element) {\n      // Store the fiber reference in WeakMap\n      fiberMap.set(element, parentCompositeFiber);\n      return element;\n    }\n    return null;\n  };\n\n  let current: HTMLElement | null = element;\n  while (current && current !== document.body) {\n    const inspectable = findInspectableFiber(current);\n    if (inspectable) {\n      // Get fiber from WeakMap\n      const fiber = fiberMap.get(inspectable);\n      if (fiber) {\n        result.unshift({\n          element: inspectable,\n          depth: 0,\n          name: getDisplayName(fiber.type) ?? 'Unknown',\n          fiber,\n        });\n      }\n    }\n    current = current.parentElement;\n  }\n\n  return result;\n};\n\ntype DiffResult = {\n  type: 'primitive' | 'reference' | 'object';\n  changes: Array<{\n    path: string[];\n    prevValue: unknown;\n    currentValue: unknown;\n    sameFunction?: boolean;\n  }>;\n  hasDeepChanges: boolean;\n};\n\ntype DiffChange = {\n  path: string[];\n  prevValue: unknown;\n  currentValue: unknown;\n  sameFunction?: boolean;\n};\n\ntype InspectableValue =\n  | Record<string, unknown>\n  | Array<unknown>\n  | Map<unknown, unknown>\n  | Set<unknown>\n  | ArrayBuffer\n  | DataView\n  | Int8Array\n  | Uint8Array\n  | Uint8ClampedArray\n  | Int16Array\n  | Uint16Array\n  | Int32Array\n  | Uint32Array\n  | Float32Array\n  | Float64Array\n  | BigInt64Array\n  | BigUint64Array;\n\nexport type AggregatedChanges = {\n  count: number;\n  // unstable: boolean;\n  currentValue: unknown;\n  previousValue: unknown;\n  // displayName?:string\n  name: string;\n};\n\nexport const isExpandable = (value: unknown): value is InspectableValue => {\n  if (value === null || typeof value !== 'object' || isPromise(value)) {\n    return false;\n  }\n\n  if (value instanceof ArrayBuffer) {\n    return true;\n  }\n\n  if (value instanceof DataView) {\n    return true;\n  }\n\n  if (ArrayBuffer.isView(value)) {\n    return true;\n  }\n\n  if (value instanceof Map || value instanceof Set) {\n    return value.size > 0;\n  }\n\n  if (Array.isArray(value)) {\n    return value.length > 0;\n  }\n\n  return Object.keys(value).length > 0;\n};\n\nexport const isEditableValue = (\n  value: unknown,\n  parentPath?: string,\n): boolean => {\n  if (value == null) return true;\n\n  if (isPromise(value)) return false;\n\n  if (typeof value === 'function') {\n    return false;\n  }\n\n  if (parentPath) {\n    const parts = parentPath.split('.');\n    let currentPath = '';\n    for (const part of parts) {\n      currentPath = currentPath ? `${currentPath}.${part}` : part;\n      const obj = globalInspectorState.lastRendered.get(currentPath);\n      if (\n        obj instanceof DataView ||\n        obj instanceof ArrayBuffer ||\n        ArrayBuffer.isView(obj)\n      ) {\n        return false;\n      }\n    }\n  }\n\n  switch (value.constructor) {\n    case Date:\n    case RegExp:\n    case Error:\n      return true;\n    default:\n      switch (typeof value) {\n        case 'string':\n        case 'number':\n        case 'boolean':\n        case 'bigint':\n          return true;\n        default:\n          return false;\n      }\n  }\n};\n\nexport const getPath = (\n  componentName: string,\n  section: string,\n  parentPath: string,\n  key: string,\n): string => {\n  if (parentPath) {\n    return `${componentName}.${parentPath}.${key}`;\n  }\n\n  if (section === 'context' && !key.startsWith('context.')) {\n    return `${componentName}.${section}.context.${key}`;\n  }\n\n  return `${componentName}.${section}.${key}`;\n};\n\nexport const sanitizeString = (value: string): string => {\n  return value\n    .replace(/[<>]/g, '')\n    .replace(/javascript:/gi, '')\n    .replace(/data:/gi, '')\n    .replace(/on\\w+=/gi, '')\n    .slice(0, 50000);\n};\n\nexport const sanitizeErrorMessage = (error: string): string => {\n  return error\n    .replace(/[<>]/g, '')\n    .replace(/&/g, '&amp;')\n    .replace(/\"/g, '&quot;')\n    .replace(/'/g, '&#x27;')\n    .replace(/\\//g, '&#x2F;');\n};\n\nexport const formatValue = (value: unknown): string => {\n  const metadata = ensureRecord(value);\n  return metadata.displayValue as string;\n};\n\nexport const formatForClipboard = (value: unknown): string => {\n  try {\n    if (value === null) return 'null';\n    if (value === undefined) return 'undefined';\n    if (isPromise(value)) return 'Promise';\n\n    if (typeof value === 'function') {\n      const fnStr = value.toString();\n      try {\n        const formatted = fnStr\n          .replace(/\\s+/g, ' ') // Normalize whitespace\n          .replace(/{\\s+/g, '{\\n  ') // Add newline after {\n          .replace(/;\\s+/g, ';\\n  ') // Add newline after ;\n          .replace(/}\\s*$/g, '\\n}') // Add newline before final }\n          .replace(/\\(\\s+/g, '(') // Remove space after (\n          .replace(/\\s+\\)/g, ')') // Remove space before )\n          .replace(/,\\s+/g, ', '); // Normalize comma spacing\n\n        return formatted;\n      } catch {\n        return fnStr;\n      }\n    }\n\n    switch (true) {\n      case value instanceof Date:\n        return value.toISOString();\n      case value instanceof RegExp:\n        return value.toString();\n      case value instanceof Error:\n        return `${value.name}: ${value.message}`;\n      case value instanceof Map:\n        return JSON.stringify(Array.from(value.entries()), null, 2);\n      case value instanceof Set:\n        return JSON.stringify(Array.from(value), null, 2);\n      case value instanceof DataView:\n        return JSON.stringify(\n          Array.from(new Uint8Array(value.buffer)),\n          null,\n          2,\n        );\n      case value instanceof ArrayBuffer:\n        return JSON.stringify(Array.from(new Uint8Array(value)), null, 2);\n      case ArrayBuffer.isView(value) && 'length' in value:\n        return JSON.stringify(\n          Array.from(value as unknown as ArrayLike<number>),\n          null,\n          2,\n        );\n      case Array.isArray(value):\n        return JSON.stringify(value, null, 2);\n      case typeof value === 'object':\n        return JSON.stringify(value, null, 2);\n      default:\n        return String(value);\n    }\n  } catch {\n    return String(value);\n  }\n};\n\nexport const parseArrayValue = (value: string): Array<unknown> => {\n  if (value.trim() === '[]') return [];\n\n  const result: Array<unknown> = [];\n  let current = '';\n  let depth = 0;\n  let inString = false;\n  let escapeNext = false;\n\n  for (let i = 0; i < value.length; i++) {\n    const char = value[i];\n\n    if (escapeNext) {\n      current += char;\n      escapeNext = false;\n      continue;\n    }\n\n    if (char === '\\\\') {\n      escapeNext = true;\n    }\n\n    if (char === '\"') {\n      inString = !inString;\n      current += char;\n      continue;\n    }\n\n    if (inString) {\n      current += char;\n      continue;\n    }\n\n    if (char === '[' || char === '{') {\n      depth++;\n      current += char;\n      continue;\n    }\n\n    if (char === ']' || char === '}') {\n      depth--;\n      current += char;\n      continue;\n    }\n\n    if (char === ',' && depth === 0) {\n      if (current.trim()) {\n        result.push(parseValue(current.trim(), ''));\n      }\n      current = '';\n      continue;\n    }\n\n    current += char;\n  }\n\n  if (current.trim()) {\n    result.push(parseValue(current.trim(), ''));\n  }\n\n  return result;\n};\n\nexport const parseValue = (value: string, currentType: unknown): unknown => {\n  try {\n    switch (typeof currentType) {\n      case 'number':\n        return Number(value);\n      case 'string':\n        return value;\n      case 'boolean':\n        return value === 'true';\n      case 'bigint':\n        return BigInt(value);\n      case 'undefined':\n        return undefined;\n      case 'object': {\n        if (!currentType) {\n          return null;\n        }\n\n        if (Array.isArray(currentType)) {\n          return parseArrayValue(value.slice(1, -1));\n        }\n\n        if (currentType instanceof RegExp) {\n          try {\n            const match = /^\\/(?<pattern>.*)\\/(?<flags>[gimuy]*)$/.exec(value);\n            if (match?.groups) {\n              return new RegExp(match.groups.pattern, match.groups.flags);\n            }\n            return new RegExp(value);\n          } catch {\n            return currentType;\n          }\n        }\n\n        if (currentType instanceof Map) {\n          const entries = value\n            .slice(1, -1)\n            .split(', ')\n            .map((entry) => {\n              const [key, val] = entry.split(' => ');\n              return [parseValue(key, ''), parseValue(val, '')] as [\n                unknown,\n                unknown,\n              ];\n            });\n          return new Map(entries);\n        }\n\n        if (currentType instanceof Set) {\n          const values = value\n            .slice(1, -1)\n            .split(', ')\n            .map((v) => parseValue(v, ''));\n          return new Set(values);\n        }\n        const entries = value\n          .slice(1, -1)\n          .split(', ')\n          .map((entry) => {\n            const [key, val] = entry.split(': ');\n            return [key, parseValue(val, '')];\n          });\n        return Object.fromEntries(entries);\n      }\n    }\n\n    return value;\n  } catch {\n    return currentType;\n  }\n};\n\nexport const detectValueType = (\n  value: string,\n): {\n  type: 'string' | 'number' | 'undefined' | 'null' | 'boolean';\n  value: unknown;\n} => {\n  const trimmed = value.trim();\n\n  switch (trimmed) {\n    case 'undefined':\n      return { type: 'undefined', value: undefined };\n    case 'null':\n      return { type: 'null', value: null };\n    case 'true':\n      return { type: 'boolean', value: true };\n    case 'false':\n      return { type: 'boolean', value: false };\n  }\n\n  if (/^\".*\"$/.test(trimmed)) {\n    return { type: 'string', value: trimmed.slice(1, -1) };\n  }\n\n  if (/^-?\\d+(?:\\.\\d+)?$/.test(trimmed)) {\n    return { type: 'number', value: Number(trimmed) };\n  }\n\n  return { type: 'string', value: `\"${trimmed}\"` };\n};\n\nexport const formatInitialValue = (value: unknown): string => {\n  if (value === undefined) return 'undefined';\n  if (value === null) return 'null';\n  if (typeof value === 'string') return `\"${value}\"`;\n  return String(value);\n};\n\nexport const updateNestedValue = (\n  obj: unknown,\n  path: Array<string>,\n  value: unknown,\n): unknown => {\n  try {\n    if (path.length === 0) return value;\n\n    const [key, ...rest] = path;\n\n    // Handle our special array of {name, value} pairs\n    if (\n      Array.isArray(obj) &&\n      obj.every((item): item is StateItem => 'name' in item && 'value' in item)\n    ) {\n      const index = obj.findIndex((item) => item.name === key);\n      if (index === -1) return obj;\n\n      const newArray = [...obj];\n      if (rest.length === 0) {\n        newArray[index] = { ...newArray[index], value };\n      } else {\n        newArray[index] = {\n          ...newArray[index],\n          value: updateNestedValue(newArray[index].value, rest, value),\n        };\n      }\n      return newArray;\n    }\n\n    if (obj instanceof Map) {\n      const newMap = new Map(obj);\n      if (rest.length === 0) {\n        newMap.set(key, value);\n      } else {\n        const currentValue = newMap.get(key);\n        newMap.set(key, updateNestedValue(currentValue, rest, value));\n      }\n      return newMap;\n    }\n\n    if (Array.isArray(obj)) {\n      const index = Number.parseInt(key, 10);\n      const newArray = [...obj];\n      if (rest.length === 0) {\n        newArray[index] = value;\n      } else {\n        newArray[index] = updateNestedValue(obj[index], rest, value);\n      }\n      return newArray;\n    }\n\n    if (obj && typeof obj === 'object') {\n      if (rest.length === 0) {\n        return { ...obj, [key]: value };\n      }\n      return {\n        ...obj,\n        [key]: updateNestedValue(\n          (obj as Record<string, unknown>)[key],\n          rest,\n          value,\n        ),\n      };\n    }\n\n    return value;\n  } catch {\n    return obj;\n  }\n};\n\nexport const areFunctionsEqual = (prev: unknown, current: unknown): boolean => {\n  try {\n    // Check if both values are actually functions\n    if (typeof prev !== 'function' || typeof current !== 'function') {\n      return false;\n    }\n\n    // Now we know both are functions, we can safely call toString()\n    return prev.toString() === current.toString();\n  } catch {\n    return false;\n  }\n};\n\nexport const getObjectDiff = (\n  prev: unknown,\n  current: unknown,\n  path: string[] = [],\n  seen = new WeakSet(),\n): DiffResult => {\n  if (prev === current) {\n    return { type: 'primitive', changes: [], hasDeepChanges: false };\n  }\n\n  if (typeof prev === 'function' && typeof current === 'function') {\n    const isSameFunction = areFunctionsEqual(prev, current);\n    return {\n      type: 'primitive',\n      changes: [\n        {\n          path,\n          prevValue: prev,\n          currentValue: current,\n          sameFunction: isSameFunction,\n        },\n      ],\n      hasDeepChanges: !isSameFunction,\n    };\n  }\n\n  if (\n    prev === null ||\n    current === null ||\n    prev === undefined ||\n    current === undefined ||\n    typeof prev !== 'object' ||\n    typeof current !== 'object'\n  ) {\n    return {\n      type: 'primitive',\n      changes: [{ path, prevValue: prev, currentValue: current }],\n      hasDeepChanges: true,\n    };\n  }\n\n  if (seen.has(prev) || seen.has(current)) {\n    return {\n      type: 'object',\n      changes: [{ path, prevValue: '[Circular]', currentValue: '[Circular]' }],\n      hasDeepChanges: false,\n    };\n  }\n\n  seen.add(prev);\n  seen.add(current);\n\n  const prevObj = prev as Record<string, unknown>;\n  const currentObj = current as Record<string, unknown>;\n  const allKeys = new Set([\n    ...Object.keys(prevObj),\n    ...Object.keys(currentObj),\n  ]);\n  const changes: Array<DiffChange> = [];\n  let hasDeepChanges = false;\n\n  for (const key of allKeys) {\n    const prevValue = prevObj[key];\n    const currentValue = currentObj[key];\n\n    if (prevValue !== currentValue) {\n      if (\n        typeof prevValue === 'object' &&\n        typeof currentValue === 'object' &&\n        prevValue !== null &&\n        currentValue !== null\n      ) {\n        const nestedDiff = getObjectDiff(\n          prevValue,\n          currentValue,\n          [...path, key],\n          seen,\n        );\n        changes.push(...nestedDiff.changes);\n        if (nestedDiff.hasDeepChanges) {\n          hasDeepChanges = true;\n        }\n      } else {\n        changes.push({\n          path: [...path, key],\n          prevValue,\n          currentValue,\n        });\n        hasDeepChanges = true;\n      }\n    }\n  }\n\n  return {\n    type: 'object',\n    changes,\n    hasDeepChanges,\n  };\n};\n\nexport const formatPath = (path: string[]): string => {\n  if (path.length === 0) return '';\n\n  return path.reduce((acc, segment, i) => {\n    // Check if segment is a number (array index)\n    if (/^\\d+$/.test(segment)) {\n      return `${acc}[${segment}]`;\n    }\n    // Add dot separator only if not first segment and previous segment wasn't an array index\n    return i === 0 ? segment : `${acc}.${segment}`;\n  }, '');\n};\n\nexport const formatFunctionBody = (body: string): string => {\n  // Remove newlines and extra spaces\n  let formatted = body.replace(/\\s+/g, ' ').trim();\n\n  // Add newlines after {, ; and before }\n  formatted = formatted\n    .replace(/{/g, '{\\n  ')\n    .replace(/;/g, ';\\n  ')\n    .replace(/}/g, '\\n}')\n    .replace(/{\\s+}/g, '{ }'); // Clean up empty blocks\n\n  // Clean up arrow functions\n  formatted = formatted.replace(/=> {\\n/g, '=> {').replace(/\\n\\s*}\\s*$/g, ' }');\n\n  return formatted;\n};\n\nexport function hackyJsFormatter(code: string) {\n  //\n  // 1) Collapse runs of whitespace to single spaces\n  //\n  const normalizedCode = code.replace(/\\s+/g, ' ').trim();\n\n  //\n  // 2) Tokenize\n  //    We'll separate out:\n  //    - parentheses: ( )\n  //    - braces: { }\n  //    - brackets: [ ]\n  //    - angle brackets: < >\n  //    - semicolon: ;\n  //    - comma: ,\n  //    - arrow =>\n  //    - colon :\n  //    - question mark ?\n  //    - exclamation mark ! (for TS non-null etc.)\n  //\n  //    We'll also try to combine () or [] or {} or <> if they appear empty.\n  //\n  const rawTokens = [];\n  let current = '';\n  for (let i = 0; i < normalizedCode.length; i++) {\n    const c = normalizedCode[i];\n\n    // Detect arrow =>\n    if (c === '=' && normalizedCode[i + 1] === '>') {\n      if (current.trim()) rawTokens.push(current.trim());\n      rawTokens.push('=>');\n      current = '';\n      i++;\n      continue;\n    }\n\n    // Single/double char punctuation\n    if (/[(){}[\\];,<>:\\?!]/.test(c)) {\n      // If we had something in current, push it\n      if (current.trim()) {\n        rawTokens.push(current.trim());\n      }\n      rawTokens.push(c);\n      current = '';\n    } else if (/\\s/.test(c)) {\n      // whitespace ends the current token\n      if (current.trim()) {\n        rawTokens.push(current.trim());\n      }\n      current = '';\n    } else {\n      current += c;\n    }\n  }\n  if (current.trim()) {\n    rawTokens.push(current.trim());\n  }\n\n  //\n  // 3) Combine immediate pairs of empty brackets, e.g. '(' + ')' => '()'\n  //    This helps keep arrow param empty parens on one line, etc.\n  //\n  const merged: Array<string> = [];\n  for (let i = 0; i < rawTokens.length; i++) {\n    const t = rawTokens[i];\n    const n = rawTokens[i + 1];\n    if (\n      (t === '(' && n === ')') ||\n      (t === '[' && n === ']') ||\n      (t === '{' && n === '}') ||\n      (t === '<' && n === '>')\n    ) {\n      merged.push(t + n); // '()', '[]', '{}', '<>'\n      i++;\n    } else {\n      merged.push(t);\n    }\n  }\n\n  //\n  // 4) We want to detect arrow param lists:\n  //    i.e. \"(\" ... \")\" immediately followed by \"=>\"\n  //    so we can keep them on one line.\n  //\n  //    Also, detect generic param lists:\n  //    i.e. identifier \"<\" ... \">\" (then maybe \"(\" ) for function calls or type declarations\n  //\n  //    We'll store indexes in sets: arrowParamSet, genericSet\n  //\n  const arrowParamSet = new Set(); // indexes inside arrow param lists\n  const genericSet = new Set(); // indexes inside generics <...>\n\n  function findMatchingPair(\n    openTok: string,\n    closeTok: string,\n    startIndex: number,\n  ) {\n    // e.g. openTok = '(', closeTok = ')'\n    let depth = 0;\n    for (let j = startIndex; j < merged.length; j++) {\n      const token = merged[j];\n      if (token === openTok) depth++;\n      else if (token === closeTok) {\n        depth--;\n        if (depth === 0) return j;\n      }\n    }\n    return -1;\n  }\n\n  // Detect arrow param sets\n  for (let i = 0; i < merged.length; i++) {\n    const t = merged[i];\n    if (t === '(') {\n      const closeIndex = findMatchingPair('(', ')', i);\n      if (closeIndex !== -1 && merged[closeIndex + 1] === '=>') {\n        // Mark all tokens from i..closeIndex as arrow param\n        for (let k = i; k <= closeIndex; k++) {\n          arrowParamSet.add(k);\n        }\n      }\n    }\n  }\n\n  // Detect generics, e.g. foo<...> or MyType<...>\n  // We do a naive approach: if we see something that looks like an identifier\n  // followed immediately by '<', we assume it's a generic.\n  for (let i = 1; i < merged.length; i++) {\n    const prev = merged[i - 1];\n    const t = merged[i];\n    // If prev is an identifier and t is '<', find matching '>'\n    if (/^[a-zA-Z0-9_$]+$/.test(prev) && t === '<') {\n      const closeIndex = findMatchingPair('<', '>', i);\n      if (closeIndex !== -1) {\n        // Mark i..closeIndex as generic\n        for (let k = i; k <= closeIndex; k++) {\n          genericSet.add(k);\n        }\n      }\n    }\n  }\n\n  //\n  // 5) Build lines with indentation. We maintain a stack for open brackets.\n  //\n  let indentLevel = 0;\n  const indentStr = '  '; // 2 spaces\n  const lines: Array<string> = [];\n  let line = '';\n\n  function pushLine() {\n    if (line.trim()) {\n      lines.push(line.replace(/\\s+$/, ''));\n    }\n    line = '';\n  }\n  function newLine() {\n    pushLine();\n    line = indentStr.repeat(indentLevel);\n  }\n\n  const stack: Array<string> = [];\n  function stackTop() {\n    return stack.length ? stack[stack.length - 1] : null;\n  }\n\n  function placeToken(tok: string, noSpaceBefore = false) {\n    if (!line.trim()) {\n      // line is empty aside from indentation\n      line += tok;\n    } else {\n      if (noSpaceBefore || /^[),;:\\].}>]$/.test(tok)) {\n        line += tok;\n      } else {\n        line += ` ${tok}`;\n      }\n    }\n  }\n\n  for (let i = 0; i < merged.length; i++) {\n    const tok = merged[i];\n    const next = merged[i + 1] || '';\n\n    // Open brackets\n    if (['(', '{', '[', '<'].includes(tok)) {\n      placeToken(tok);\n      stack.push(tok);\n\n      // If '{', definitely newline + indent\n      if (tok === '{') {\n        indentLevel++;\n        newLine();\n      } else if (tok === '(' || tok === '[' || tok === '<') {\n        // If we are in arrowParamSet or genericSet, keep it on one line\n        if (\n          (arrowParamSet.has(i) && tok === '(') ||\n          (genericSet.has(i) && tok === '<')\n        ) {\n          // Don't break lines after commas etc.\n          // We won't do multiline logic for these.\n        } else {\n          // If next is not a direct close, go multiline\n          const directClose = {\n            '(': ')',\n            '[': ']',\n            '<': '>',\n          }[tok];\n          if (\n            next !== directClose &&\n            next !== '()' &&\n            next !== '[]' &&\n            next !== '<>'\n          ) {\n            indentLevel++;\n            newLine();\n          }\n        }\n      }\n    }\n\n    // Close brackets\n    else if ([')', '}', ']', '>'].includes(tok)) {\n      // pop stack\n      const opening = stackTop();\n      if (\n        (tok === ')' && opening === '(') ||\n        (tok === ']' && opening === '[') ||\n        (tok === '>' && opening === '<')\n      ) {\n        // if not arrowParamSet or genericSet, multiline\n        if (\n          !(arrowParamSet.has(i) && tok === ')') &&\n          !(genericSet.has(i) && tok === '>')\n        ) {\n          indentLevel = Math.max(indentLevel - 1, 0);\n          newLine();\n        }\n      } else if (tok === '}' && opening === '{') {\n        indentLevel = Math.max(indentLevel - 1, 0);\n        newLine();\n      }\n      stack.pop();\n      placeToken(tok);\n      if (tok === '}') {\n        // break line after }\n        newLine();\n      }\n    }\n\n    // Combined empty pairs like '()', '[]', '{}', '<>'\n    else if (/^\\(\\)|\\[\\]|\\{\\}|\\<\\>$/.test(tok)) {\n      placeToken(tok);\n\n      // Arrow =>\n    } else if (tok === '=>') {\n      placeToken(tok);\n      // We'll let the next token (maybe '{') handle line breaks.\n\n      // Semicolon\n    } else if (tok === ';') {\n      placeToken(tok, true);\n      newLine();\n\n      // Comma\n    } else if (tok === ',') {\n      placeToken(tok, true);\n      // If inside an arrow param set or generic set, don't break\n      // Otherwise, if top is {, (, [ or <, break line\n      const top = stackTop();\n      if (\n        !(arrowParamSet.has(i) && top === '(') &&\n        !(genericSet.has(i) && top === '<')\n      ) {\n        if (top && ['{', '[', '(', '<'].includes(top)) {\n          newLine();\n        }\n      }\n\n      // Everything else (identifiers, operators, colons, question marks, etc.)\n    } else {\n      placeToken(tok);\n    }\n  }\n\n  pushLine();\n\n  // Remove extra blank lines\n  return lines\n    .join('\\n')\n    .replace(/\\n\\s*\\n+/g, '\\n')\n    .trim();\n}\n\n// Update the formatFunctionPreview to use the new formatter\nexport const formatFunctionPreview = (\n  fn: { toString(): string },\n  expanded = false,\n): string => {\n  try {\n    const fnStr = fn.toString();\n    const match = fnStr.match(\n      /(?:function\\s*)?(?:\\(([^)]*)\\)|([^=>\\s]+))\\s*=>?/,\n    );\n    if (!match) return 'ƒ';\n\n    const params = match[1] || match[2] || '';\n    const cleanParams = params.replace(/\\s+/g, '');\n\n    if (!expanded) {\n      return `ƒ (${cleanParams}) => ...`;\n    }\n\n    // For expanded view, use the new formatter\n    return hackyJsFormatter(fnStr);\n  } catch {\n    return 'ƒ';\n  }\n};\n\nexport const formatValuePreview = (value: unknown): string => {\n  if (value === null) return 'null';\n  if (value === undefined) return 'undefined';\n  if (typeof value === 'string')\n    return `\"${value.length > 150 ? `${value.slice(0, 20)}...` : value}\"`;\n  if (typeof value === 'number' || typeof value === 'boolean')\n    return String(value);\n  if (typeof value === 'function') return formatFunctionPreview(value);\n  if (Array.isArray(value)) return `Array(${value.length})`;\n  if (value instanceof Map) return `Map(${value.size})`;\n  if (value instanceof Set) return `Set(${value.size})`;\n  if (value instanceof Date) return value.toISOString();\n  if (value instanceof RegExp) return value.toString();\n  if (value instanceof Error) return `${value.name}: ${value.message}`;\n  if (typeof value === 'object') {\n    const keys = Object.keys(value as object);\n    return `{${keys.length > 2 ? `${keys.slice(0, 2).join(', ')}, ...` : keys.join(', ')}}`;\n  }\n  return String(value);\n};\n\nexport const safeGetValue = (\n  value: unknown,\n): { value: unknown; error?: string } => {\n  if (value === null || value === undefined) return { value };\n  if (typeof value === 'function') return { value };\n  if (typeof value !== 'object') return { value };\n\n  if (isPromise(value)) {\n    return { value: 'Promise' };\n  }\n\n  try {\n    const proto = Object.getPrototypeOf(value);\n    if (proto === Promise.prototype || proto?.constructor?.name === 'Promise') {\n      return { value: 'Promise' };\n    }\n\n    return { value };\n  } catch {\n    return { value: null, error: 'Error accessing value' };\n  }\n};\n\nexport interface TimelineSliderValues {\n  leftValue: number;\n  min: number;\n  max: number;\n  value: number;\n  rightValue: number;\n}\n\nexport const calculateSliderValues = (\n  totalUpdates: number,\n  currentIndex: number,\n): TimelineSliderValues => {\n  if (totalUpdates <= TIMELINE_MAX_UPDATES) {\n    return {\n      leftValue: 0,\n      min: 0,\n      max: totalUpdates - 1,\n      value: currentIndex,\n      rightValue: totalUpdates - 1,\n    };\n  }\n\n  return {\n    leftValue: totalUpdates - TIMELINE_MAX_UPDATES,\n    min: 0,\n    max: TIMELINE_MAX_UPDATES - 1,\n    value: currentIndex,\n    rightValue: totalUpdates - 1,\n  };\n};\n\n// be careful, this is an implementation detail is not stable or reliable across all react versions https://github.com/facebook/react/pull/15124\n// type UpdateQueue<S, A> = {\n//   last: Update<S, A> | null,\n//   dispatch: (A => mixed) | null,\n//   eagerReducer: ((S, A) => S) | null,\n//   eagerState: S | null,\n// };\n\ninterface ExtendedMemoizedState extends MemoizedState {\n  queue?: {\n    lastRenderedState: unknown;\n  } | null;\n  element?: unknown;\n}\n\nexport const isDirectComponent = (fiber: Fiber): boolean => {\n  if (!fiber || !fiber.type) return false;\n\n  const isFunctionalComponent = typeof fiber.type === 'function';\n  const isClassComponent = fiber.type?.prototype?.isReactComponent ?? false;\n\n  if (!(isFunctionalComponent || isClassComponent)) return false;\n\n  if (isClassComponent) {\n    return true;\n  }\n\n  let memoizedState = fiber.memoizedState;\n  while (memoizedState) {\n    if (memoizedState.queue) {\n      return true;\n    }\n    const nextState: ExtendedMemoizedState | null = memoizedState.next;\n    if (!nextState) break;\n    memoizedState = nextState;\n  }\n\n  return false;\n};\n\nexport const isPromise = (value: unknown): value is Promise<unknown> => {\n  return (\n    !!value &&\n    (value instanceof Promise || (typeof value === 'object' && 'then' in value))\n  );\n};\n\nexport const ensureRecord = (\n  value: unknown,\n  maxDepth = 2,\n  seen = new WeakSet<object>(),\n): Record<string, unknown> => {\n  if (isPromise(value)) {\n    return { type: 'promise', displayValue: 'Promise' };\n  }\n\n  if (value === null) {\n    return { type: 'null', displayValue: 'null' };\n  }\n\n  if (value === undefined) {\n    return { type: 'undefined', displayValue: 'undefined' };\n  }\n\n  switch (typeof value) {\n    case 'object': {\n      if (seen.has(value)) {\n        return { type: 'circular', displayValue: '[Circular Reference]' };\n      }\n\n      if (!value) return { type: 'null', displayValue: 'null' };\n\n      seen.add(value);\n\n      try {\n        const result: Record<string, unknown> = {};\n\n        if (value instanceof Element) {\n          result.type = 'Element';\n          result.tagName = value.tagName.toLowerCase();\n          result.displayValue = value.tagName.toLowerCase();\n          return result;\n        }\n\n        if (value instanceof Map) {\n          result.type = 'Map';\n          result.size = value.size;\n          result.displayValue = `Map(${value.size})`;\n\n          if (maxDepth > 0) {\n            const entries: Record<string, unknown> = {};\n            let index = 0;\n            for (const [key, val] of value.entries()) {\n              if (index >= 50) break;\n              try {\n                entries[String(key)] = ensureRecord(val, maxDepth - 1, seen);\n              } catch {\n                entries[String(index)] = {\n                  type: 'error',\n                  displayValue: 'Error accessing Map entry',\n                };\n              }\n              index++;\n            }\n            result.entries = entries;\n          }\n          return result;\n        }\n\n        if (value instanceof Set) {\n          result.type = 'Set';\n          result.size = value.size;\n          result.displayValue = `Set(${value.size})`;\n\n          if (maxDepth > 0) {\n            const items = [];\n            let count = 0;\n            for (const item of value) {\n              if (count >= 50) break;\n              items.push(ensureRecord(item, maxDepth - 1, seen));\n              count++;\n            }\n            result.items = items;\n          }\n          return result;\n        }\n\n        if (value instanceof Date) {\n          result.type = 'Date';\n          result.value = value.toISOString();\n          result.displayValue = value.toLocaleString();\n          return result;\n        }\n\n        if (value instanceof RegExp) {\n          result.type = 'RegExp';\n          result.value = value.toString();\n          result.displayValue = value.toString();\n          return result;\n        }\n\n        if (value instanceof Error) {\n          result.type = 'Error';\n          result.name = value.name;\n          result.message = value.message;\n          result.displayValue = `${value.name}: ${value.message}`;\n          return result;\n        }\n\n        if (value instanceof ArrayBuffer) {\n          result.type = 'ArrayBuffer';\n          result.byteLength = value.byteLength;\n          result.displayValue = `ArrayBuffer(${value.byteLength})`;\n          return result;\n        }\n\n        if (value instanceof DataView) {\n          result.type = 'DataView';\n          result.byteLength = value.byteLength;\n          result.displayValue = `DataView(${value.byteLength})`;\n          return result;\n        }\n\n        if (ArrayBuffer.isView(value)) {\n          const typedArray = value as unknown as {\n            length: number;\n            constructor: { name: string };\n            buffer: ArrayBuffer;\n          };\n          result.type = typedArray.constructor.name;\n          result.length = typedArray.length;\n          result.byteLength = typedArray.buffer.byteLength;\n          result.displayValue = `${typedArray.constructor.name}(${typedArray.length})`;\n          return result;\n        }\n\n        if (Array.isArray(value)) {\n          result.type = 'array';\n          result.length = value.length;\n          result.displayValue = `Array(${value.length})`;\n\n          if (maxDepth > 0) {\n            result.items = value\n              .slice(0, 50)\n              .map((item) => ensureRecord(item, maxDepth - 1, seen));\n          }\n          return result;\n        }\n\n        const keys = Object.keys(value);\n        result.type = 'object';\n        result.size = keys.length;\n        result.displayValue =\n          keys.length <= 5\n            ? `{${keys.join(', ')}}`\n            : `{${keys.slice(0, 5).join(', ')}, ...${keys.length - 5}}`;\n\n        if (maxDepth > 0) {\n          const entries: Record<string, unknown> = {};\n          for (const key of keys.slice(0, 50)) {\n            try {\n              entries[key] = ensureRecord(\n                (value as Record<string, unknown>)[key],\n                maxDepth - 1,\n                seen,\n              );\n            } catch {\n              entries[key] = {\n                type: 'error',\n                displayValue: 'Error accessing property',\n              };\n            }\n          }\n          result.entries = entries;\n        }\n        return result;\n      } finally {\n        seen.delete(value);\n      }\n    }\n    case 'string':\n      return {\n        type: 'string',\n        value,\n        displayValue: `\"${value}\"`,\n      };\n    case 'function':\n      return {\n        type: 'function',\n        displayValue: 'ƒ()',\n        name: value.name || 'anonymous',\n      };\n    default:\n      return {\n        type: typeof value,\n        value,\n        displayValue: String(value),\n      };\n  }\n};\n\nexport const getCurrentFiberState = (\n  fiber: Fiber,\n): Record<string, unknown> | null => {\n  if (fiber.tag !== FunctionComponentTag || !isDirectComponent(fiber)) {\n    return null;\n  }\n\n  const currentIsNewer = fiber.alternate\n    ? (fiber.actualStartTime ?? 0) > (fiber.alternate.actualStartTime ?? 0)\n    : true;\n\n  const memoizedState: ExtendedMemoizedState | null = currentIsNewer\n    ? fiber.memoizedState\n    : (fiber.alternate?.memoizedState ?? fiber.memoizedState);\n\n  if (!memoizedState) return null;\n\n  return memoizedState;\n};\n\nexport const replayComponent = async (fiber: Fiber): Promise<void> => {\n  const { overrideProps, overrideHookState, overrideContext } =\n    getOverrideMethods();\n  if (!overrideProps || !overrideHookState || !fiber) return;\n\n  try {\n    // Handle props updates\n    const currentProps = fiber.memoizedProps || {};\n    const propKeys = Object.keys(currentProps).filter((key) => {\n      const value = currentProps[key];\n      if (Array.isArray(value) || typeof value === 'string') {\n        return !Number.isInteger(Number(key)) && key !== 'length';\n      }\n      return true;\n    });\n\n    for (const key of propKeys) {\n      try {\n        const value = currentProps[key];\n        // For arrays and objects, we need to clone to trigger updates\n        const propValue = Array.isArray(value)\n          ? [...value]\n          : typeof value === 'object' && value !== null\n            ? { ...value }\n            : value;\n        overrideProps(fiber, [key], propValue);\n      } catch {}\n    }\n\n    // Handle state updates\n    const currentState = getCurrentFiberState(fiber);\n    if (currentState) {\n      const stateNames = getStateNames(fiber);\n\n      // First, handle named state hooks\n      for (const [key, value] of Object.entries(currentState)) {\n        try {\n          const namedStateIndex = stateNames.indexOf(key);\n          if (namedStateIndex !== -1) {\n            const hookId = namedStateIndex.toString();\n            // For arrays and objects, we need to clone to trigger updates\n            const stateValue = Array.isArray(value)\n              ? [...value]\n              : typeof value === 'object' && value !== null\n                ? { ...value }\n                : value;\n            overrideHookState(fiber, hookId, [], stateValue);\n          }\n        } catch {}\n      }\n\n      // Then handle unnamed state hooks\n      let hookIndex = 0;\n      let currentHook = fiber.memoizedState;\n      while (currentHook !== null) {\n        try {\n          const hookId = hookIndex.toString();\n          const value = currentHook.memoizedState;\n\n          // Only update if this hook isn't already handled by named states\n          if (!stateNames.includes(hookId)) {\n            // For arrays and objects, we need to clone to trigger updates\n            const stateValue = Array.isArray(value)\n              ? [...value]\n              : typeof value === 'object' && value !== null\n                ? { ...value }\n                : value;\n            overrideHookState(fiber, hookId, [], stateValue);\n          }\n        } catch {}\n\n        currentHook = currentHook.next as typeof currentHook;\n        hookIndex++;\n      }\n    }\n\n    // Handle context updates\n    if (overrideContext) {\n      const contexts = getAllFiberContexts(fiber);\n      if (contexts) {\n        for (const [contextType, ctx] of contexts) {\n          try {\n            // Find the provider fiber for this context\n            let current: Fiber | null = fiber;\n            while (current) {\n              const type = current.type as { Provider?: unknown };\n              if (type === contextType || type?.Provider === contextType) {\n                // Get the value we want to update to\n                const newValue = ctx.value;\n                if (newValue === undefined || newValue === null) break;\n\n                // Only update if the value has actually changed\n                const currentValue = current.memoizedProps?.value;\n                if (isEqual(currentValue, newValue)) break;\n\n                // Update the provider's value prop\n                overrideProps(current, ['value'], newValue);\n                if (current.alternate) {\n                  overrideProps(current.alternate, ['value'], newValue);\n                }\n                break;\n              }\n              current = current.return;\n            }\n          } catch {}\n        }\n      }\n    }\n\n    // Recursively handle children\n    let child = fiber.child;\n    while (child) {\n      await replayComponent(child);\n      child = child.sibling;\n    }\n  } catch {}\n};\n\nexport const extractMinimalFiberInfo = (fiber: Fiber): MinimalFiberInfo => {\n  const timings = getTimings(fiber);\n  return {\n    displayName: getDisplayName(fiber) || 'Unknown',\n    type: fiber.type,\n    key: fiber.key,\n    id: fiber.index,\n    selfTime: timings?.selfTime ?? null,\n    totalTime: timings?.totalTime ?? null,\n  };\n};\n"
  },
  {
    "path": "packages/scan/src/web/views/inspector/what-changed.tsx",
    "content": "import { type ReactNode, memo } from 'preact/compat';\nimport {\n  type Dispatch,\n  type StateUpdater,\n  useEffect,\n  useRef,\n  useState,\n} from 'preact/hooks';\nimport { CopyToClipboard } from '~web/components/copy-to-clipboard';\nimport { Icon } from '~web/components/icon';\nimport { cn, throttle } from '~web/utils/helpers';\nimport { DiffValueView } from './diff-value';\nimport { timelineState } from './states';\nimport {\n  AggregatedChanges,\n  formatFunctionPreview,\n  formatPath,\n  getObjectDiff,\n  safeGetValue,\n} from './utils';\nimport {\n  calculateTotalChanges,\n  useInspectedFiberChangeStore,\n} from './whats-changed/use-change-store';\nimport { getDisplayName, getType } from 'bippy';\nimport { Store } from '~core/index';\n\nexport type Setter<T> = Dispatch<StateUpdater<T>>;\n\nexport const WhatChanged = /* @__PURE__ */ memo(() => {\n  const [isExpanded, setIsExpanded] = useState(true);\n  const aggregatedChanges = useInspectedFiberChangeStore();\n\n  const [hasInitialized, setHasInitialized] = useState(false);\n  const hasAnyChanges = calculateTotalChanges(aggregatedChanges) > 0;\n  useEffect(() => {\n    if (!hasInitialized && hasAnyChanges) {\n      const timer = setTimeout(() => {\n        setHasInitialized(true);\n        requestAnimationFrame(() => {\n          setIsExpanded(true);\n        });\n      }, 0);\n      return () => clearTimeout(timer);\n    }\n  }, [hasInitialized, hasAnyChanges]);\n\n  const initializedContextChanges = new Map(\n    Array.from(aggregatedChanges.contextChanges.entries())\n      .filter(([, value]) => value.kind === 'initialized')\n      .map(([key, value]) => [\n        key,\n        // oxlint-disable-next-line typescript/no-non-null-assertion\n        value.kind === 'partially-initialized' ? null! : value.changes,\n      ]),\n  );\n\n  const fiber =\n    Store.inspectState.value.kind === 'focused'\n      ? Store.inspectState.value.fiber\n      : null;\n\n  if (!fiber) {\n    // invariant\n    return;\n  }\n  return (\n    <>\n      <WhatsChangedHeader />\n\n      <div className=\"overflow-hidden h-full flex flex-col gap-y-2\">\n        <div className=\"flex flex-col gap-2 px-3 pt-2\">\n          <span className=\"text-sm font-medium text-[#888]\">\n            Why did{' '}\n            <span className=\"text-[#A855F7]\">{getDisplayName(fiber)}</span>{' '}\n            render?\n          </span>\n          {!hasAnyChanges && (\n            <div className=\"text-sm text-[#737373] bg-[#1E1E1E] rounded-md p-4 flex flex-col gap-4\">\n              <div>No changes detected since selecting</div>\n              <div>\n                The props, state, and context changes within your component will\n                be reported here\n              </div>\n            </div>\n          )}\n        </div>\n        <div\n          className={cn(\n            'flex flex-col gap-y-2 pl-3 relative overflow-y-auto h-full',\n          )}\n        >\n          <Section\n            changes={aggregatedChanges.propsChanges}\n            title=\"Changed Props\"\n            isExpanded={isExpanded}\n          />\n          <Section\n            renderName={(name) =>\n              renderStateName(\n                name,\n                getDisplayName(getType(fiber)) ?? 'Unknown Component',\n              )\n            }\n            changes={aggregatedChanges.stateChanges}\n            title=\"Changed State\"\n            isExpanded={isExpanded}\n          />\n          <Section\n            changes={initializedContextChanges}\n            title=\"Changed Context\"\n            isExpanded={isExpanded}\n          />\n        </div>\n      </div>\n    </>\n  );\n});\n\nconst renderStateName = (key: string, componentName: string) => {\n  if (Number.isNaN(Number(key))) {\n    return key;\n  }\n\n  const n = Number.parseInt(key);\n  const getOrdinalSuffix = (num: number) => {\n    const lastDigit = num % 10;\n    const lastTwoDigits = num % 100;\n    if (lastTwoDigits >= 11 && lastTwoDigits <= 13) {\n      return 'th';\n    }\n    switch (lastDigit) {\n      case 1:\n        return 'st';\n      case 2:\n        return 'nd';\n      case 3:\n        return 'rd';\n      default:\n        return 'th';\n    }\n  };\n\n  return (\n    <span className=\"truncate\">\n      <span className=\"text-white\">\n        {n}\n        {getOrdinalSuffix(n)} hook{' '}\n      </span>\n      <span style={{ color: '#666' }}>\n        called in <i className=\"text-[#A855F7] truncate\">{componentName}</i>\n      </span>\n    </span>\n  );\n};\n\nconst WhatsChangedHeader = memo(() => {\n  const refProps = useRef<HTMLDivElement>(null);\n  const refState = useRef<HTMLDivElement>(null);\n  const refContext = useRef<HTMLDivElement>(null);\n\n  const refStats = useRef<{\n    isPropsChanged: boolean;\n    isStateChanged: boolean;\n    isContextChanged: boolean;\n  }>({\n    isPropsChanged: false,\n    isStateChanged: false,\n    isContextChanged: false,\n  });\n\n  useEffect(() => {\n    const flash = throttle(() => {\n      const flashElements = [];\n      if (refProps.current?.dataset.flash === 'true') {\n        flashElements.push(refProps.current);\n      }\n      if (refState.current?.dataset.flash === 'true') {\n        flashElements.push(refState.current);\n      }\n      if (refContext.current?.dataset.flash === 'true') {\n        flashElements.push(refContext.current);\n      }\n\n      for (const element of flashElements) {\n        element.classList.remove('count-flash-white');\n        void element.offsetWidth;\n        element.classList.add('count-flash-white');\n      }\n    }, 400);\n\n    const unsubscribe = timelineState.subscribe((state) => {\n      if (!refProps.current || !refState.current || !refContext.current) {\n        return;\n      }\n\n      const { currentIndex, updates } = state;\n      const currentUpdate = updates[currentIndex];\n\n      if (!currentUpdate || currentIndex === 0) {\n        return;\n      }\n\n      flash();\n\n      refStats.current = {\n        isPropsChanged: (currentUpdate.props?.changes?.size ?? 0) > 0,\n        isStateChanged: (currentUpdate.state?.changes?.size ?? 0) > 0,\n        isContextChanged: (currentUpdate.context?.changes?.size ?? 0) > 0,\n      };\n\n      if (refProps.current.dataset.flash !== 'true') {\n        refProps.current.dataset.flash =\n          refStats.current.isPropsChanged.toString();\n      }\n      if (refState.current.dataset.flash !== 'true') {\n        refState.current.dataset.flash =\n          refStats.current.isStateChanged.toString();\n      }\n      if (refContext.current.dataset.flash !== 'true') {\n        refContext.current.dataset.flash =\n          refStats.current.isContextChanged.toString();\n      }\n    });\n\n    return unsubscribe;\n  }, []);\n\n  return (\n    <button\n      type=\"button\"\n      className={cn(\n        'react-section-header',\n        'overflow-hidden',\n        'max-h-0',\n        'transition-[max-height]',\n      )}\n    >\n      <div className={cn('flex-1 react-scan-expandable')}>\n        <div className=\"overflow-hidden\">\n          <div className=\"flex items-center whitespace-nowrap\">\n            <div className=\"flex items-center gap-x-2\">What changed?</div>\n\n            <div\n              className={cn(\n                'ml-auto',\n                'change-scope',\n                'transition-opacity duration-300 delay-150',\n              )}\n            >\n              <div ref={refProps}>props</div>\n              <div ref={refState}>state</div>\n              <div ref={refContext}>context</div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </button>\n  );\n});\n\ninterface SectionProps {\n  title: string;\n  isExpanded: boolean;\n  // oxlint-disable-next-line typescript/no-explicit-any\n  changes: Map<any, AggregatedChanges>;\n  renderName?: (name: string) => ReactNode;\n}\nconst identity = <T,>(x: T) => x;\nconst Section = /* @__PURE__ */ memo(\n  ({ title, changes, renderName = identity }: SectionProps) => {\n    const [expandedFns, setExpandedFns] = useState(new Set<string>());\n    const [expandedEntries, setExpandedEntries] = useState(new Set<string>());\n\n    const entries = Array.from(changes.entries());\n\n    if (changes.size === 0) {\n      return null;\n    }\n    return (\n      <div>\n        <div className=\"text-xs text-[#888] mb-1.5\">{title}</div>\n        <div className=\"flex flex-col gap-2\">\n          {entries.map(([entryKey, change]) => {\n            const isEntryExpanded = expandedEntries.has(String(entryKey));\n            const { value: prevValue, error: prevError } = safeGetValue(\n              change.previousValue,\n            );\n            const { value: currValue, error: currError } = safeGetValue(\n              change.currentValue,\n            );\n\n            const diff = getObjectDiff(prevValue, currValue);\n\n            return (\n              <div key={entryKey}>\n                <button\n                  onClick={() => {\n                    setExpandedEntries((prev) => {\n                      const next = new Set(prev);\n                      if (next.has(String(entryKey))) {\n                        next.delete(String(entryKey));\n                      } else {\n                        next.add(String(entryKey));\n                      }\n                      return next;\n                    });\n                  }}\n                  className=\"flex items-center gap-2 w-full bg-transparent border-none p-0 cursor-pointer text-white text-xs\"\n                >\n                  <div className=\"flex items-center gap-1.5 flex-1\">\n                    <Icon\n                      name=\"icon-chevron-right\"\n                      size={12}\n                      className={cn(\n                        'text-[#666] transition-transform duration-200 ease-[cubic-bezier(0.25,0.1,0.25,1)]',\n                        {\n                          'rotate-90': isEntryExpanded,\n                        },\n                      )}\n                    />\n                    <div className=\"whitespace-pre-wrap break-words text-left font-medium flex items-center gap-x-1.5\">\n                      {renderName(change.name)}\n\n                      <CountBadge\n                        count={change.count}\n                        isFunction={typeof change.currentValue === 'function'}\n                        showWarning={diff.changes.length === 0}\n                        forceFlash\n                        // showFlame={diff.changes.length === 0}\n                        // showFn={typeof change.currentValue === 'function'}\n                      />\n                    </div>\n                  </div>\n                </button>\n                <div\n                  className={cn('react-scan-expandable', {\n                    'react-scan-expanded': isEntryExpanded,\n                  })}\n                >\n                  <div className=\"pl-3 text-xs font-mono border-l-1 border-[#333]\">\n                    <div className=\"flex flex-col gap-0.5\">\n                      {prevError || currError ? (\n                        <AccessError\n                          currError={currError}\n                          prevError={prevError}\n                        />\n                      ) : diff.changes.length > 0 ? (\n                        <DiffChange\n                          change={change}\n                          diff={diff}\n                          expandedFns={expandedFns}\n                          renderName={renderName}\n                          setExpandedFns={setExpandedFns}\n                          title={title}\n                        />\n                      ) : (\n                        <ReferenceOnlyChange\n                          currValue={currValue}\n                          entryKey={entryKey}\n                          expandedFns={expandedFns}\n                          prevValue={prevValue}\n                          setExpandedFns={setExpandedFns}\n                        />\n                      )}\n                    </div>\n                  </div>\n                </div>\n              </div>\n            );\n          })}\n        </div>\n      </div>\n    );\n  },\n);\n\nconst AccessError = ({\n  prevError,\n  currError,\n}: {\n  prevError?: string;\n  currError?: string;\n}) => {\n  return (\n    <>\n      {prevError && (\n        <div className=\"text-[#f87171] bg-[#2a1515] pr-1.5 py-[3px] rounded italic\">\n          {prevError}\n        </div>\n      )}\n      {currError && (\n        <div className=\"text-[#4ade80] bg-[#1a2a1a] pr-1.5 py-[3px] rounded italic mt-0.5\">\n          {currError}\n        </div>\n      )}\n    </>\n  );\n};\n\nconst DiffChange = ({\n  diff,\n  title,\n  renderName,\n  change,\n  expandedFns,\n  setExpandedFns,\n}: {\n  diff: {\n    changes: {\n      path: string[];\n      prevValue: unknown;\n      currentValue: unknown;\n    }[];\n  };\n  title: string;\n  renderName: (name: string) => ReactNode;\n  change: { name: string };\n  expandedFns: Set<string>;\n  setExpandedFns: (updater: (prev: Set<string>) => Set<string>) => void;\n}) => {\n  return diff.changes.map((diffChange, i) => {\n    const { value: prevDiffValue, error: prevDiffError } = safeGetValue(\n      diffChange.prevValue,\n    );\n    const { value: currDiffValue, error: currDiffError } = safeGetValue(\n      diffChange.currentValue,\n    );\n\n    const isFunction =\n      typeof prevDiffValue === 'function' ||\n      typeof currDiffValue === 'function';\n\n    let path: string | undefined;\n\n    if (title === 'Props') {\n      path =\n        diffChange.path.length > 0\n          ? `${renderName(String(change.name))}.${formatPath(diffChange.path)}`\n          : undefined;\n    }\n    if (title === 'State' && diffChange.path.length > 0) {\n      path = `state.${formatPath(diffChange.path)}`;\n    }\n\n    if (!path) {\n      path = formatPath(diffChange.path);\n    }\n\n    return (\n      <div\n        key={`${path}-${change.name}-${i}`}\n        className={cn(\n          'flex flex-col gap-y-1',\n          i < diff.changes.length - 1 && 'mb-4',\n        )}\n      >\n        {path && <div className=\"text-[#666] text-[10px]\">{path}</div>}\n        <button\n          type=\"button\"\n          className={cn(\n            'group',\n            'flex items-start',\n            'py-[3px] px-1.5',\n            'text-left text-[#f87171] bg-[#2a1515]',\n            'rounded',\n            'overflow-hidden break-all',\n            isFunction && 'cursor-pointer',\n          )}\n          onClick={\n            isFunction\n              ? () => {\n                  const fnKey = `${formatPath(diffChange.path)}-prev`;\n                  setExpandedFns((prev) => {\n                    const next = new Set(prev);\n                    if (next.has(fnKey)) {\n                      next.delete(fnKey);\n                    } else {\n                      next.add(fnKey);\n                    }\n                    return next;\n                  });\n                }\n              : undefined\n          }\n        >\n          <span className=\"w-3 flex items-center justify-center opacity-50\">\n            -\n          </span>\n          <span className=\"flex-1 whitespace-nowrap font-mono\">\n            {prevDiffError ? (\n              <span className=\"italic text-[#f87171]\">{prevDiffError}</span>\n            ) : isFunction ? (\n              <div className=\"flex gap-1 items-start flex-col\">\n                <div className=\"flex gap-1 items-start w-full\">\n                  <span className=\"flex-1 max-h-40\">\n                    {formatFunctionPreview(\n                      prevDiffValue as (...args: unknown[]) => unknown,\n                      expandedFns.has(`${formatPath(diffChange.path)}-prev`),\n                    )}\n                  </span>\n                  {typeof prevDiffValue === 'function' && (\n                    <CopyToClipboard\n                      text={prevDiffValue.toString()}\n                      className=\"opacity-0 transition-opacity group-hover:opacity-100\"\n                    >\n                      {({ ClipboardIcon }) => <>{ClipboardIcon}</>}\n                    </CopyToClipboard>\n                  )}\n                </div>\n                {prevDiffValue?.toString() === currDiffValue?.toString() && (\n                  <div className=\"text-[10px] text-[#666] italic\">\n                    Function reference changed\n                  </div>\n                )}\n              </div>\n            ) : (\n              <DiffValueView\n                value={prevDiffValue}\n                expanded={expandedFns.has(\n                  `${formatPath(diffChange.path)}-prev`,\n                )}\n                onToggle={() => {\n                  const key = `${formatPath(diffChange.path)}-prev`;\n                  setExpandedFns((prev) => {\n                    const next = new Set(prev);\n                    if (next.has(key)) {\n                      next.delete(key);\n                    } else {\n                      next.add(key);\n                    }\n                    return next;\n                  });\n                }}\n                isNegative={true}\n              />\n            )}\n          </span>\n        </button>\n        <button\n          type=\"button\"\n          className={cn(\n            'group',\n            'flex items-start',\n            'py-[3px] px-1.5',\n            'text-left text-[#4ade80] bg-[#1a2a1a]',\n            'rounded',\n            'overflow-hidden break-all',\n            isFunction && 'cursor-pointer',\n          )}\n          onClick={\n            isFunction\n              ? () => {\n                  const fnKey = `${formatPath(diffChange.path)}-current`;\n                  setExpandedFns((prev) => {\n                    const next = new Set(prev);\n                    if (next.has(fnKey)) {\n                      next.delete(fnKey);\n                    } else {\n                      next.add(fnKey);\n                    }\n                    return next;\n                  });\n                }\n              : undefined\n          }\n        >\n          <span className=\"w-3 flex items-center justify-center opacity-50\">\n            +\n          </span>\n          <span className=\"flex-1 whitespace-pre-wrap font-mono\">\n            {currDiffError ? (\n              <span className=\"italic text-[#4ade80]\">{currDiffError}</span>\n            ) : isFunction ? (\n              <div className=\"flex gap-1 items-start flex-col\">\n                <div className=\"flex gap-1 items-start w-full\">\n                  <span className=\"flex-1\">\n                    {formatFunctionPreview(\n                      currDiffValue as (...args: unknown[]) => unknown,\n                      expandedFns.has(`${formatPath(diffChange.path)}-current`),\n                    )}\n                  </span>\n                  {typeof currDiffValue === 'function' && (\n                    <CopyToClipboard\n                      text={currDiffValue.toString()}\n                      className=\"opacity-0 transition-opacity group-hover:opacity-100\"\n                    >\n                      {({ ClipboardIcon }) => <>{ClipboardIcon}</>}\n                    </CopyToClipboard>\n                  )}\n                </div>\n                {prevDiffValue?.toString() === currDiffValue?.toString() && (\n                  <div className=\"text-[10px] text-[#666] italic\">\n                    Function reference changed\n                  </div>\n                )}\n              </div>\n            ) : (\n              <DiffValueView\n                value={currDiffValue}\n                expanded={expandedFns.has(\n                  `${formatPath(diffChange.path)}-current`,\n                )}\n                onToggle={() => {\n                  const key = `${formatPath(diffChange.path)}-current`;\n                  setExpandedFns((prev) => {\n                    const next = new Set(prev);\n                    if (next.has(key)) {\n                      next.delete(key);\n                    } else {\n                      next.add(key);\n                    }\n                    return next;\n                  });\n                }}\n                isNegative={false}\n              />\n            )}\n          </span>\n        </button>\n      </div>\n    );\n  });\n};\n\nconst ReferenceOnlyChange = ({\n  prevValue,\n  currValue,\n  entryKey,\n  expandedFns,\n  setExpandedFns,\n}: {\n  prevValue: unknown;\n  currValue: unknown;\n  entryKey: string | number;\n  expandedFns: Set<string>;\n  setExpandedFns: (updater: (prev: Set<string>) => Set<string>) => void;\n}) => {\n  return (\n    <>\n      <div className=\"group flex gap-0.5 items-start text-[#f87171] bg-[#2a1515] py-[3px] px-1.5 rounded\">\n        <span className=\"w-3 flex items-center justify-center opacity-50\">\n          -\n        </span>\n        <span className=\"flex-1 overflow-hidden whitespace-pre-wrap font-mono\">\n          <DiffValueView\n            value={prevValue}\n            expanded={expandedFns.has(`${String(entryKey)}-prev`)}\n            onToggle={() => {\n              const key = `${String(entryKey)}-prev`;\n              setExpandedFns((prev) => {\n                const next = new Set(prev);\n                if (next.has(key)) {\n                  next.delete(key);\n                } else {\n                  next.add(key);\n                }\n                return next;\n              });\n            }}\n            isNegative={true}\n          />\n        </span>\n      </div>\n      <div className=\"group flex gap-0.5 items-start text-[#4ade80] bg-[#1a2a1a] py-[3px] px-1.5 rounded mt-0.5\">\n        <span className=\"w-3 flex items-center justify-center opacity-50\">\n          +\n        </span>\n        <span className=\"flex-1 overflow-hidden whitespace-pre-wrap font-mono\">\n          <DiffValueView\n            value={currValue}\n            expanded={expandedFns.has(`${String(entryKey)}-current`)}\n            onToggle={() => {\n              const key = `${String(entryKey)}-current`;\n              setExpandedFns((prev) => {\n                const next = new Set(prev);\n                if (next.has(key)) {\n                  next.delete(key);\n                } else {\n                  next.add(key);\n                }\n                return next;\n              });\n            }}\n            isNegative={false}\n          />\n        </span>\n      </div>\n      {typeof currValue === 'object' && currValue !== null && (\n        <div className=\"text-[#666] text-[10px] italic mt-1 flex items-center gap-x-1\">\n          <Icon\n            name=\"icon-triangle-alert\"\n            className=\"text-yellow-500 mb-px\"\n            size={14}\n          />\n          <span>Reference changed but objects are structurally the same</span>\n        </div>\n      )}\n    </>\n  );\n};\n\nconst CountBadge = ({\n  count,\n  forceFlash,\n  isFunction,\n  showWarning,\n}: {\n  count: number;\n  forceFlash: boolean;\n  isFunction: boolean;\n  showWarning: boolean;\n}) => {\n  const refIsFirstRender = useRef(true);\n  const refBadge = useRef<HTMLDivElement>(null);\n  const refPrevCount = useRef(count);\n\n  useEffect(() => {\n    const element = refBadge.current;\n    if (!element || refPrevCount.current === count) {\n      return;\n    }\n\n    element.classList.remove('count-flash');\n    void element.offsetWidth;\n    element.classList.add('count-flash');\n\n    refPrevCount.current = count;\n  }, [count]);\n\n  useEffect(() => {\n    if (refIsFirstRender.current) {\n      refIsFirstRender.current = false;\n      return;\n    }\n\n    if (forceFlash) {\n      let timer = setTimeout(() => {\n        refBadge.current?.classList.add('count-flash-white');\n        timer = setTimeout(() => {\n          refBadge.current?.classList.remove('count-flash-white');\n        }, 300);\n      }, 500);\n      return () => {\n        clearTimeout(timer);\n      };\n    }\n  }, [forceFlash]);\n\n  return (\n    <div ref={refBadge} className=\"count-badge\">\n      {showWarning && (\n        <Icon\n          name=\"icon-triangle-alert\"\n          className=\"text-yellow-500 mb-px\"\n          size={14}\n        />\n      )}\n      {isFunction && (\n        <Icon name=\"icon-function\" className=\"text-[#A855F7] mb-px\" size={14} />\n      )}\n      x{count}\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/scan/src/web/views/inspector/whats-changed/use-change-store.ts",
    "content": "import { useEffect, useRef, useState } from 'preact/hooks';\nimport {\n  ChangesListener,\n  ChangesPayload,\n  ContextChange,\n  Store,\n} from '~core/index';\nimport { Fiber, getFiberId } from 'bippy';\nimport { isEqual } from '~core/utils';\nimport { signal } from '@preact/signals';\n\nconst CHANGES_QUEUE_INTERVAL = 50;\n\ninterface SectionData {\n  current: Array<{ name: string; value: unknown }>;\n  changes: Set<string>;\n}\n\nexport interface InspectorData {\n  fiberProps: SectionData;\n  fiberState: SectionData;\n  fiberContext: SectionData;\n}\ninterface InspectorState extends InspectorData {\n  fiber: Fiber | null;\n}\n\nexport const inspectorState = signal<InspectorState>({\n  fiber: null,\n  fiberProps: { current: [], changes: new Set() },\n  fiberState: { current: [], changes: new Set() },\n  fiberContext: { current: [], changes: new Set() },\n});\n\nexport type AggregatedChanges = {\n  count: number;\n  currentValue: unknown;\n  previousValue: unknown;\n  name: string;\n  lastUpdated: number;\n  id: string;\n};\n\nexport type AllAggregatedChanges = {\n  // oxlint-disable-next-line typescript/no-explicit-any\n  propsChanges: Map<any, AggregatedChanges>;\n  // oxlint-disable-next-line typescript/no-explicit-any\n  stateChanges: Map<any, AggregatedChanges>;\n  contextChanges: Map<\n    // oxlint-disable-next-line typescript/no-explicit-any\n    any,\n    | { changes: AggregatedChanges; kind: 'initialized' }\n    | {\n        // this looks weird, because it is\n        // its a work around to allow context changes to be sent impotently\n        // (react-scan internals do not yet handle sending context changes the render they change)\n        kind: 'partially-initialized';\n        value: unknown;\n        name: string;\n        lastUpdated: number;\n        id: string;\n      }\n  >;\n};\n\nconst getContextChangesValue = (\n  discriminated:\n    | { kind: 'partially-initialized'; value: unknown }\n    | { kind: 'initialized'; changes: AggregatedChanges },\n) => {\n  switch (discriminated.kind) {\n    case 'initialized': {\n      return discriminated.changes.currentValue;\n    }\n    case 'partially-initialized': {\n      return discriminated.value;\n    }\n  }\n};\nconst processChanges = (\n  changes: Array<{ name: string; value: unknown; prevValue?: unknown }>,\n  // oxlint-disable-next-line typescript/no-explicit-any\n  targetMap: Map<any, AggregatedChanges>,\n) => {\n  for (const change of changes) {\n    const existing = targetMap.get(change.name);\n\n    if (existing) {\n      targetMap.set(existing.name, {\n        count: existing.count + 1,\n        currentValue: change.value,\n        id: existing.name,\n        lastUpdated: Date.now(),\n        name: existing.name,\n        previousValue: change.prevValue,\n      });\n      continue;\n    }\n\n    targetMap.set(change.name, {\n      count: 1,\n      currentValue: change.value,\n      id: change.name,\n      lastUpdated: Date.now(),\n      name: change.name,\n      previousValue: change.prevValue,\n    });\n  }\n};\n\nconst processContextChanges = (\n  contextChanges: Array<ContextChange>,\n  aggregatedChanges: AllAggregatedChanges,\n) => {\n  for (const change of contextChanges) {\n    const existing = aggregatedChanges.contextChanges.get(change.contextType);\n\n    if (existing) {\n      if (isEqual(getContextChangesValue(existing), change.value)) {\n        continue;\n      }\n      if (existing.kind === 'partially-initialized') {\n        aggregatedChanges.contextChanges.set(change.contextType, {\n          kind: 'initialized',\n          changes: {\n            count: 1,\n            currentValue: change.value,\n            id: change.contextType.toString(), // come back to this why was this ever expected to be a number?\n            lastUpdated: Date.now(),\n            name: change.name,\n            previousValue: existing.value,\n          },\n        });\n        continue;\n      }\n\n      aggregatedChanges.contextChanges.set(change.contextType, {\n        kind: 'initialized',\n        changes: {\n          count: existing.changes.count + 1,\n          currentValue: change.value,\n          id: change.contextType.toString(),\n          lastUpdated: Date.now(),\n          name: change.name,\n          previousValue: existing.changes.currentValue,\n        },\n      });\n\n      continue;\n    }\n\n    aggregatedChanges.contextChanges.set(change.contextType, {\n      kind: 'partially-initialized',\n      id: change.contextType.toString(),\n      lastUpdated: Date.now(),\n      name: change.name,\n      value: change.value,\n    });\n  }\n};\n\nconst collapseQueue = (queue: Array<ChangesPayload>) => {\n  const localAggregatedChanges: AllAggregatedChanges = {\n    contextChanges: new Map(),\n    propsChanges: new Map(),\n    stateChanges: new Map(),\n  };\n\n  queue.forEach((changes) => {\n    // context is a special case since we don't send precise diffs and need to be idempotent\n    processContextChanges(changes.contextChanges, localAggregatedChanges);\n\n    processChanges(changes.stateChanges, localAggregatedChanges.stateChanges);\n\n    processChanges(changes.propsChanges, localAggregatedChanges.propsChanges);\n  });\n\n  return localAggregatedChanges;\n};\nconst mergeSimpleChanges = <\n  T extends\n    | AllAggregatedChanges['propsChanges']\n    | AllAggregatedChanges['stateChanges'],\n>(\n  existingChanges: T,\n  incomingChanges: T,\n): T => {\n  const mergedChanges = new Map();\n\n  existingChanges.forEach((value, key) => {\n    mergedChanges.set(key, value);\n  });\n\n  incomingChanges.forEach((incomingChange, key) => {\n    const existing = mergedChanges.get(key);\n\n    if (!existing) {\n      mergedChanges.set(key, incomingChange);\n      return;\n    }\n\n    mergedChanges.set(key, {\n      count: existing.count + incomingChange.count,\n      currentValue: incomingChange.currentValue,\n      id: incomingChange.id,\n      lastUpdated: incomingChange.lastUpdated,\n      name: incomingChange.name,\n      previousValue: incomingChange.previousValue,\n    });\n  });\n\n  return mergedChanges as T;\n};\n\nconst mergeContextChanges = (\n  existing: AllAggregatedChanges,\n  incoming: AllAggregatedChanges,\n) => {\n  const contextChanges: AllAggregatedChanges['contextChanges'] = new Map();\n\n  existing.contextChanges.forEach((value, key) => {\n    contextChanges.set(key, value);\n  });\n\n  incoming.contextChanges.forEach((incomingChange, key) => {\n    const existingChange = contextChanges.get(key);\n\n    if (!existingChange) {\n      contextChanges.set(key, incomingChange);\n      return;\n    }\n    if (\n      getContextChangesValue(incomingChange) ===\n      getContextChangesValue(existingChange)\n    ) {\n      // we do this for a second time just in context merge to handle the partial initialization case (the collapsed queue will not have the information to remove the partially initialized set of changes)\n      return;\n    }\n\n    switch (existingChange.kind) {\n      case 'initialized': {\n        switch (incomingChange.kind) {\n          case 'initialized': {\n            const preInitEntryOffset = 1;\n            contextChanges.set(key, {\n              kind: 'initialized',\n              changes: {\n                ...incomingChange.changes,\n                // if existing was initialized, the pre-initialization done by the collapsed queue was not necessary, so we need to increment count to account for the preInit entry\n                count:\n                  incomingChange.changes.count +\n                  existingChange.changes.count +\n                  preInitEntryOffset,\n                currentValue: incomingChange.changes.currentValue,\n\n                previousValue: incomingChange.changes.previousValue, // we always want to show this value, since this will be the true state transition (if you make the previousValue the last seen currentValue, u will have weird behavior with primitive state updates)\n              },\n            });\n            return;\n          }\n          case 'partially-initialized': {\n            contextChanges.set(key, {\n              kind: 'initialized',\n              changes: {\n                count: existingChange.changes.count + 1,\n                currentValue: incomingChange.value,\n                id: incomingChange.id,\n                lastUpdated: incomingChange.lastUpdated,\n                name: incomingChange.name,\n                previousValue: existingChange.changes.currentValue,\n              },\n            });\n            return;\n          }\n        }\n      }\n      case 'partially-initialized': {\n        switch (incomingChange.kind) {\n          case 'initialized': {\n            contextChanges.set(key, {\n              kind: 'initialized',\n              changes: {\n                count: incomingChange.changes.count + 1,\n                currentValue: incomingChange.changes.currentValue,\n                id: incomingChange.changes.id,\n                lastUpdated: incomingChange.changes.lastUpdated,\n                name: incomingChange.changes.name,\n                previousValue: existingChange.value,\n              },\n            });\n            return;\n          }\n          case 'partially-initialized': {\n            contextChanges.set(key, {\n              kind: 'initialized',\n              changes: {\n                count: 1,\n                currentValue: incomingChange.value,\n                id: incomingChange.id,\n                lastUpdated: incomingChange.lastUpdated,\n                name: incomingChange.name,\n                previousValue: existingChange.value,\n              },\n            });\n            return;\n          }\n        }\n      }\n    }\n  });\n\n  return contextChanges;\n};\n\nconst mergeChanges = (\n  existing: AllAggregatedChanges,\n  incoming: AllAggregatedChanges,\n): AllAggregatedChanges => {\n  const contextChanges = mergeContextChanges(existing, incoming);\n\n  const propChanges = mergeSimpleChanges(\n    existing.propsChanges,\n    incoming.propsChanges,\n  );\n  const stateChanges = mergeSimpleChanges(\n    existing.stateChanges,\n    incoming.stateChanges,\n  );\n\n  return {\n    contextChanges,\n    propsChanges: propChanges,\n    stateChanges,\n  };\n};\n\n/**\n * Calculate total count of changes across props, state and context\n */\nexport const calculateTotalChanges = (changes: AllAggregatedChanges) => {\n  return (\n    Array.from(changes.propsChanges.values()).reduce(\n      (acc, change) => acc + change.count,\n      0,\n    ) +\n    Array.from(changes.stateChanges.values()).reduce(\n      (acc, change) => acc + change.count,\n      0,\n    ) +\n    Array.from(changes.contextChanges.values())\n      .filter(\n        (change): change is Extract<typeof change, { kind: 'initialized' }> =>\n          change.kind === 'initialized',\n      )\n      .reduce((acc, change) => acc + change.changes.count, 0)\n  );\n};\n\nexport const useInspectedFiberChangeStore = (opts?: {\n  onChangeUpdate?: (countUpdated: number) => void;\n}) => {\n  const pendingChanges = useRef<{ queue: ChangesPayload[] }>({ queue: [] });\n  // flushed state read from queue stream\n  const [aggregatedChanges, setAggregatedChanges] =\n    useState<AllAggregatedChanges>({\n      propsChanges: new Map(),\n      stateChanges: new Map(),\n      contextChanges: new Map(),\n    });\n\n  const fiber =\n    Store.inspectState.value.kind === 'focused'\n      ? Store.inspectState.value.fiber\n      : null;\n  const fiberId = fiber ? getFiberId(fiber) : null;\n\n  // oxlint-disable-next-line react-hooks/exhaustive-deps\n  useEffect(() => {\n    const interval = setInterval(() => {\n      // optimization to avoid unconditional renders\n      if (pendingChanges.current.queue.length === 0) return;\n\n      setAggregatedChanges((prevAggregatedChanges) => {\n        const queueChanges = collapseQueue(pendingChanges.current.queue);\n        const merged = mergeChanges(prevAggregatedChanges, queueChanges);\n        const prevTotal = calculateTotalChanges(prevAggregatedChanges);\n        const newTotal = calculateTotalChanges(merged);\n        const changeCount = newTotal - prevTotal;\n        opts?.onChangeUpdate?.(changeCount);\n\n        return merged;\n      });\n\n      pendingChanges.current.queue = [];\n    }, CHANGES_QUEUE_INTERVAL);\n\n    return () => {\n      clearInterval(interval);\n    };\n  }, [fiber]);\n\n  // un-throttled subscription\n  useEffect(() => {\n    if (!fiberId) {\n      return;\n    }\n    const listener: ChangesListener = (change) => {\n      pendingChanges.current?.queue.push(change);\n    };\n\n    let listeners = Store.changesListeners.get(fiberId);\n\n    if (!listeners) {\n      listeners = [];\n      Store.changesListeners.set(fiberId, listeners);\n    }\n\n    listeners.push(listener);\n\n    return () => {\n      setAggregatedChanges({\n        propsChanges: new Map(),\n        stateChanges: new Map(),\n        contextChanges: new Map(),\n      });\n      pendingChanges.current.queue = [];\n      Store.changesListeners.set(\n        fiberId,\n        Store.changesListeners.get(fiberId)?.filter((l) => l !== listener) ??\n          [],\n      );\n    };\n  }, [fiberId]);\n\n  // cleanup\n  // oxlint-disable-next-line react-hooks/exhaustive-deps\n  useEffect(() => {\n    return () => {\n      setAggregatedChanges({\n        propsChanges: new Map(),\n        stateChanges: new Map(),\n        contextChanges: new Map(),\n      });\n      pendingChanges.current.queue = [];\n    };\n  }, [fiberId]);\n\n  return aggregatedChanges;\n};\n"
  },
  {
    "path": "packages/scan/src/web/views/notifications/collapsed-event.tsx",
    "content": "import { useEffect, useRef, useState } from 'preact/hooks';\nimport {\n  DroppedFramesEvent,\n  getComponentName,\n  getEventSeverity,\n  InteractionEvent,\n} from './data';\nimport { SlowdownHistoryItem } from './slowdown-history';\nimport { ChevronRight } from './icons';\nimport { cn } from '~web/utils/helpers';\n\nexport type CollapsedDroppedFrame = {\n  kind: 'collapsed-frame-drops';\n  events: Array<DroppedFramesEvent>;\n  timestamp: number;\n};\n\ntype CollapsedKeyboardInput = {\n  kind: 'collapsed-keyboard';\n  events: Array<InteractionEvent>;\n  timestamp: number;\n};\nconst useNestedFlash = ({\n  flashingItemsCount,\n  totalEvents,\n}: {\n  totalEvents: number; // this breaks if you have constant 1 item flashing, but the actual item is different over time (it's fine for now)\n  flashingItemsCount: number;\n}) => {\n  const [newFlash, setNewFlash] = useState(false);\n  const flashedFor = useRef(0);\n  const lastFlashTime = useRef<number>(0);\n\n  // oxlint-disable-next-line react-hooks/exhaustive-deps\n  useEffect(() => {\n    if (flashedFor.current >= totalEvents) {\n      return;\n    }\n\n    const now = Date.now();\n    const debounceTime = 250;\n    const timeSinceLastFlash = now - lastFlashTime.current;\n\n    if (timeSinceLastFlash >= debounceTime) {\n      setNewFlash(false);\n      const timeout = setTimeout(() => {\n        flashedFor.current = totalEvents;\n        lastFlashTime.current = Date.now();\n        setNewFlash(true);\n        // horrible, don't look at this, move along\n        setTimeout(() => {\n          setNewFlash(false);\n        }, 2000);\n      }, 50);\n      return () => clearTimeout(timeout);\n    } else {\n      const delayNeeded = debounceTime - timeSinceLastFlash;\n      const timeout = setTimeout(() => {\n        setNewFlash(false);\n        setTimeout(() => {\n          flashedFor.current = totalEvents;\n          lastFlashTime.current = Date.now();\n          setNewFlash(true);\n          // horrible, don't look at this, move along\n          setTimeout(() => {\n            setNewFlash(false);\n          }, 2000);\n        }, 50);\n      }, delayNeeded);\n      return () => clearTimeout(timeout);\n    }\n  }, [flashingItemsCount]);\n\n  return newFlash;\n};\n\nexport const CollapsedItem = ({\n  item,\n  shouldFlash,\n}: {\n  item: CollapsedDroppedFrame | CollapsedKeyboardInput;\n  shouldFlash: (id: string) => boolean;\n}) => {\n  const [expanded, setExpanded] = useState(false);\n\n  const severity = item.events.map(getEventSeverity).reduce((prev, curr) => {\n    switch (curr) {\n      case 'high': {\n        return 'high';\n      }\n      case 'needs-improvement': {\n        return prev === 'high' ? 'high' : 'needs-improvement';\n      }\n      case 'low': {\n        return prev;\n      }\n    }\n  }, 'low');\n  const flashingItemsCount = item.events.reduce(\n    (prev, curr) => (shouldFlash(curr.id) ? prev + 1 : prev),\n    0,\n  );\n\n  const shouldFlashAgain = useNestedFlash({\n    flashingItemsCount,\n    totalEvents: item.events.length,\n  });\n\n  return (\n    <div className={cn(['flex flex-col gap-y-0.5'])}>\n      <button\n        onClick={() => setExpanded((expanded) => !expanded)}\n        className={cn([\n          'pl-2 py-1.5  text-sm flex items-center rounded-sm hover:bg-[#18181B] relative overflow-hidden',\n          shouldFlashAgain &&\n            !expanded &&\n            'after:absolute after:inset-0 after:bg-purple-500/30 after:animate-[fadeOut_1s_ease-out_forwards]',\n        ])}\n      >\n        <div\n          className={cn([\n            'w-4/5 flex items-center justify-start h-full text-xs truncate gap-x-1.5',\n          ])}\n        >\n          <span className={cn(['min-w-fit'])}>\n            <ChevronRight\n              key={`chevron-${item.timestamp}`}\n              className={cn([\n                'text-[#A1A1AA] transition-transform',\n                expanded ? 'rotate-90' : '',\n              ])}\n              size={14}\n            />\n          </span>\n\n          <span className={cn(['text-xs'])}>\n            {item.kind === 'collapsed-frame-drops'\n              ? 'FPS Drops'\n              : getComponentName(item.events.at(0)?.componentPath ?? [])}\n          </span>\n        </div>\n        <div\n          className={cn(['ml-auto min-w-fit flex justify-end items-center'])}\n        >\n          <div\n            style={{\n              lineHeight: '10px',\n            }}\n            className={cn([\n              'w-fit flex items-center text-[10px] justify-center h-full text-white px-1 py-1 rounded-sm font-semibold',\n              severity === 'low' && 'bg-green-500/60',\n              severity === 'needs-improvement' && 'bg-[#b77116] text-[10px]',\n              severity === 'high' && 'bg-[#b94040]',\n            ])}\n          >\n            x{item.events.length}\n          </div>\n        </div>\n      </button>\n      {expanded && (\n        <IndentedContent>\n          {item.events\n            .toSorted((a, b) => b.timestamp - a.timestamp)\n            .map((event) => (\n              <SlowdownHistoryItem\n                event={event}\n                shouldFlash={shouldFlash(event.id)}\n              />\n            ))}\n        </IndentedContent>\n      )}\n    </div>\n  );\n};\n\nconst IndentedContent = ({\n  children,\n}: { children: JSX.Element | JSX.Element[] }) => (\n  <div className=\"relative pl-6 flex flex-col gap-y-1\">\n    <div className=\"absolute left-3 top-0 bottom-0 w-px bg-[#27272A]\" />\n    {children}\n  </div>\n);\n"
  },
  {
    "path": "packages/scan/src/web/views/notifications/data.ts",
    "content": "import { createContext } from 'preact';\nimport { SetStateAction } from 'preact/compat';\nimport { Dispatch, useContext } from 'preact/hooks';\nimport { HIGH_SEVERITY_FPS_DROP_TIME } from '~core/notifications/event-tracking';\nimport { getFiberFromElement } from '../inspector/utils';\nimport { hasMemoCache } from 'bippy';\n\nexport type GroupedFiberRender = {\n  id: string;\n  name: string;\n  count: number;\n  changes: {\n    props: Array<{ name: string; count: number }>;\n    state: Array<{ index: number; count: number }>;\n    context: Array<{ name: string; count: number }>;\n  };\n  // fixme: incorrect assumption, make this nullable\n  /** Not available when running in production, but we will not render notifications in production */\n  totalTime: number;\n  elements: Array<Element>; // can't do a weak set because need to iterate over them......\n  deletedAll: boolean;\n  parents: Set<string>;\n  hasMemoCache: boolean;\n  wasFiberRenderMount: boolean;\n};\nexport const getComponentName = (path: Array<string>) => {\n  const filteredPath = path.filter((item) => item.length > 2);\n  // in production, all names can be minified\n  if (filteredPath.length === 0) {\n    return path.at(-1) ?? 'Unknown';\n  }\n  // oxlint-disable-next-line typescript/no-non-null-assertion\n  return filteredPath.at(-1)!;\n};\n\nexport const getTotalTime = (\n  timing: InteractionTiming | DroppedFramesTiming,\n) => {\n  switch (timing.kind) {\n    case 'interaction': {\n      const {\n        renderTime,\n        otherJSTime,\n        framePreparation,\n        frameConstruction,\n        frameDraw,\n      } = timing;\n      return (\n        renderTime +\n        otherJSTime +\n        framePreparation +\n        frameConstruction +\n        (frameDraw ?? 0)\n      );\n    }\n    case 'dropped-frames': {\n      return timing.otherTime + timing.renderTime;\n    }\n  }\n};\n\nexport type DroppedFramesTiming = {\n  kind: 'dropped-frames';\n  renderTime: number;\n  otherTime: number;\n};\nexport type InteractionTiming = {\n  kind: 'interaction';\n  renderTime: number;\n  otherJSTime: number;\n  /** After JS, before paint. Things like layerize, css style recalcs */\n  framePreparation: number;\n  /** paint/commit. This is where the browser constructs the data structure that represents what will be drawn to screen */\n  frameConstruction: number;\n  /** GPU/compositing/rasterization. This is where, off the main thread, the data structure built is used to draw the next frame. This value is not available on safari due to lack of PerformanceEntry API */\n  frameDraw: number | null;\n};\n\nexport const isRenderMemoizable = (\n  groupedFiberRender: GroupedFiberRender,\n): boolean => {\n  if (groupedFiberRender.wasFiberRenderMount) {\n    // no amount of memoization can prevent a mount render\n    return false;\n  }\n  // this shouldn't be needed, it implies we either are tracking renders wrong, are tracking changes wrong, or are not tracking some other \"state\" that can cause re-renders, but its a better fallback than failing\n  if (groupedFiberRender.hasMemoCache) {\n    return false;\n  }\n\n  return (\n    groupedFiberRender.changes.context.length === 0 &&\n    groupedFiberRender.changes.props.length === 0 &&\n    groupedFiberRender.changes.state.length === 0\n  );\n};\n\nexport const getTimeSplit = (\n  timing: DroppedFramesTiming | InteractionTiming,\n) => {\n  switch (timing.kind) {\n    case 'dropped-frames': {\n      return {\n        render: timing.renderTime,\n        other: timing.otherTime,\n      };\n    }\n    case 'interaction': {\n      return {\n        render: timing.renderTime,\n        other: getTotalTime(timing) + timing.renderTime,\n      };\n    }\n  }\n};\n\nexport type InteractionEvent = {\n  kind: 'interaction';\n  type: 'click' | 'keyboard';\n  id: string;\n  componentPath: Array<string>;\n  groupedFiberRenders: Array<GroupedFiberRender>;\n  timing: InteractionTiming;\n  /** Not available in safari, and API used to get value is not stable on chrome */\n  memory: number | null;\n  timestamp: number;\n};\nexport type DroppedFramesEvent = {\n  kind: 'dropped-frames';\n  id: string;\n  groupedFiberRenders: Array<GroupedFiberRender>;\n  timing: DroppedFramesTiming;\n  /** Not available in safari, and API used to get value is not stable on chrome */\n  memory: number | null;\n  timestamp: number;\n  fps: number;\n};\nexport type NotificationEvent = InteractionEvent | DroppedFramesEvent;\n\nexport type NotificationsState = {\n  events: Array<NotificationEvent>;\n  // todo: discriminated union this all, i don't want to do it yet till i stabilize the data i need/ implement it all\n  selectedEvent: NotificationEvent | null;\n  filterBy: 'severity' | 'latest';\n  selectedFiber: NotificationEvent['groupedFiberRenders'][number] | null;\n  detailsExpanded: boolean;\n  moreInfoExpanded: boolean;\n  route:\n    | 'render-visualization'\n    | 'other-visualization'\n    // | \"render-guide\"\n    | 'render-explanation'\n    // | \"other-guide\"\n    | 'optimize';\n  /**\n   * Conceptually a synthetic query parameter\n   */\n  routeMessage: null | {\n    kind: 'auto-open-overview-accordion';\n    name:\n      | 'other-not-javascript'\n      | 'other-javascript'\n      | 'render'\n      | 'other-frame-drop';\n  };\n  audioNotificationsOptions:\n    | {\n        audioContext: null;\n        enabled: false;\n      }\n    | {\n        enabled: true;\n        audioContext: AudioContext;\n      };\n};\n\nexport const getEventSeverity = (event: NotificationEvent) => {\n  const totalTime = getTotalTime(event.timing);\n  switch (event.kind) {\n    case 'interaction': {\n      if (totalTime < 200) return 'low';\n      if (totalTime < 500) return 'needs-improvement';\n      return 'high';\n    }\n    case 'dropped-frames': {\n      if (totalTime < 50) return 'low';\n      if (totalTime < HIGH_SEVERITY_FPS_DROP_TIME) return 'needs-improvement';\n      return 'high';\n    }\n  }\n};\n\nexport const getReadableSeverity = (\n  severity: 'low' | 'needs-improvement' | 'high',\n) => {\n  switch (severity) {\n    case 'high': {\n      return 'Poor';\n    }\n    case 'needs-improvement': {\n      return 'Laggy';\n    }\n    case 'low': {\n      return 'Good';\n    }\n  }\n};\nexport const NOTIFICATIONS_BORDER = '#27272A';\nexport const useNotificationsContext = () =>\n  useContext(NotificationStateContext);\n\nexport const NotificationStateContext = createContext<{\n  notificationState: NotificationsState;\n  setNotificationState: Dispatch<SetStateAction<NotificationsState>>;\n  setRoute: ({\n    route,\n    routeMessage,\n  }: {\n    route: NotificationsState['route'];\n    routeMessage: NotificationsState['routeMessage'] | null;\n  }) => void;\n  // oxlint-disable-next-line typescript/no-non-null-assertion\n}>(null!);\n"
  },
  {
    "path": "packages/scan/src/web/views/notifications/details-routes.tsx",
    "content": "import { ReactNode, useEffect, useRef, useState } from 'preact/compat';\nimport { playNotificationSound } from '~core/utils';\nimport { cn } from '~web/utils/helpers';\nimport { useNotificationsContext } from './data';\nimport { CloseIcon } from './icons';\nimport { NotificationTabs } from './notification-tabs';\nimport { Optimize } from './optimize';\nimport { OtherVisualization } from './other-visualization';\nimport { RenderBarChart } from './render-bar-chart';\nimport { RenderExplanation } from './render-explanation';\nimport { signalWidgetViews } from '~web/state';\n\nexport const DetailsRoutes = () => {\n  const { notificationState, setNotificationState } = useNotificationsContext();\n  const [dots, setDots] = useState('...');\n  const containerRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    const interval = setInterval(() => {\n      setDots((prev) => {\n        if (prev === '...') return '';\n        return prev + '.';\n      });\n    }, 500);\n\n    return () => clearInterval(interval);\n  }, []);\n\n  if (!notificationState.selectedEvent) {\n    return (\n      <div\n        ref={containerRef}\n        className={cn([\n          'h-full w-full flex flex-col items-center justify-center relative py-2 px-4',\n        ])}\n      >\n        <div\n          className={cn([\n            'p-2 flex justify-center items-center border-[#27272A] absolute top-0 right-0',\n          ])}\n        >\n          <button\n            onClick={() => {\n              signalWidgetViews.value = {\n                view: 'none',\n              };\n            }}\n          >\n            <CloseIcon size={18} className=\"text-[#6F6F78]\" />\n          </button>\n        </div>\n        <div\n          className={cn([\n            'flex flex-col items-start pt-5 bg-[#0A0A0A] p-5 rounded-sm max-w-md',\n            ' shadow-lg',\n          ])}\n        >\n          <div className={cn(['flex flex-col items-start gap-y-4'])}>\n            <div className={cn(['flex items-center'])}>\n              <span className={cn(['text-zinc-400 font-medium text-[17px]'])}>\n                Scanning for slowdowns\n                {dots}\n              </span>\n            </div>\n            {notificationState.events.length !== 0 && (\n              <p className={cn(['text-xs'])}>\n                Click on an item in the{' '}\n                <span className={cn(['text-purple-400'])}>History</span> list to\n                get started\n              </p>\n            )}\n            <p className={cn(['text-zinc-600 text-xs'])}>\n              You don't need to keep this panel open for React Scan to record\n              slowdowns\n            </p>\n            <p className={cn(['text-zinc-600 text-xs'])}>\n              Enable audio alerts to hear a delightful ding every time a large\n              slowdown is recorded\n            </p>\n            <button\n              onClick={() => {\n                if (notificationState.audioNotificationsOptions.enabled) {\n                  setNotificationState((prev) => {\n                    if (\n                      prev.audioNotificationsOptions.audioContext?.state !==\n                      'closed'\n                    ) {\n                      prev.audioNotificationsOptions.audioContext?.close();\n                    }\n                    localStorage.setItem('react-scan-notifications-audio', 'false');\n                    return {\n                      ...prev,\n                      audioNotificationsOptions: {\n                        audioContext: null,\n                        enabled: false,\n                      },\n                    };\n                  });\n                  return;\n                }\n                localStorage.setItem('react-scan-notifications-audio', 'true');\n                const audioContext = new AudioContext();\n                playNotificationSound(audioContext);\n                setNotificationState((prev) => ({\n                  ...prev,\n                  audioNotificationsOptions: {\n                    enabled: true,\n                    audioContext,\n                  },\n                }));\n              }}\n              className={cn([\n                'px-4 py-2 bg-zinc-800 hover:bg-zinc-700 rounded-sm w-full',\n                ' text-sm flex items-center gap-x-2 justify-center',\n              ])}\n            >\n              {notificationState.audioNotificationsOptions.enabled ? (\n                <>\n                  <span className=\"flex items-center gap-x-1\">\n                    Disable audio alerts\n                  </span>\n                </>\n              ) : (\n                <>\n                  <span className=\"flex items-center gap-x-1\">\n                    Enable audio alerts\n                  </span>\n                </>\n              )}\n            </button>\n          </div>\n        </div>\n      </div>\n    );\n  }\n\n  switch (notificationState.route) {\n    case 'render-visualization': {\n      return (\n        <TabLayout>\n          <RenderBarChart selectedEvent={notificationState.selectedEvent} />\n        </TabLayout>\n      );\n    }\n    case 'render-explanation': {\n      if (!notificationState.selectedFiber) {\n        // todo: dev only\n        throw new Error(\n          'Invariant: must have selected fiber when viewing render explanation',\n        );\n      }\n      return (\n        <TabLayout>\n          <RenderExplanation\n            selectedFiber={notificationState.selectedFiber}\n            selectedEvent={notificationState.selectedEvent}\n          />\n        </TabLayout>\n      );\n    }\n\n    case 'other-visualization': {\n      return (\n        <TabLayout>\n          <div\n            className={cn(['flex w-full h-full flex-col overflow-y-auto'])}\n            id=\"overview-scroll-container\"\n          >\n            <OtherVisualization\n              selectedEvent={notificationState.selectedEvent}\n            />\n          </div>\n        </TabLayout>\n      );\n    }\n    case 'optimize': {\n      return (\n        <TabLayout>\n          <Optimize selectedEvent={notificationState.selectedEvent} />\n        </TabLayout>\n      );\n    }\n  }\n  // exhaustive verification\n  notificationState.route satisfies never;\n};\n\nconst TabLayout = ({ children }: { children: ReactNode }) => {\n  const { notificationState } = useNotificationsContext();\n  if (!notificationState.selectedEvent) {\n    // todo: dev only\n    throw new Error(\n      'Invariant: d must have selected event when viewing render explanation',\n    );\n  }\n  return (\n    <div className={cn([`w-full h-full flex flex-col gap-y-2`])}>\n      <div className={cn(['h-[50px] w-full'])}>\n        <NotificationTabs selectedEvent={notificationState.selectedEvent} />\n      </div>\n      <div\n        className={cn(['h-calc(100%-50px) flex flex-col overflow-y-auto px-3'])}\n      >\n        {children}\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/scan/src/web/views/notifications/icons.tsx",
    "content": "import { ReactScanInternals } from '~core/index';\nimport { cn } from '~web/utils/helpers';\n\nexport const ChevronRight = ({\n  size = 24,\n  className,\n}: {\n  size?: number;\n  className?: string;\n}) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width={size}\n    height={size}\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    stroke-width=\"2\"\n    stroke-linecap=\"round\"\n    stroke-linejoin=\"round\"\n    className={cn(['lucide lucide-chevron-right', className])}\n  >\n    <path d=\"m9 18 6-6-6-6\" />\n  </svg>\n);\nexport const CopyX = ({\n  size = 24,\n  className,\n}: {\n  size?: number;\n  className?: string;\n}) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width={size}\n    height={size}\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    strokeWidth=\"2\"\n    strokeLinecap=\"round\"\n    strokeLinejoin=\"round\"\n    className={cn(['lucide lucide-copy-x', className])}\n  >\n    <line x1=\"12\" x2=\"18\" y1=\"12\" y2=\"18\" />\n    <line x1=\"12\" x2=\"18\" y1=\"18\" y2=\"12\" />\n    <rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\" />\n    <path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\" />\n  </svg>\n);\n\nexport const Notification = ({\n  className = '',\n  size = 24,\n  events = [],\n}: {\n  className?: string;\n  size?: number;\n  events: boolean[];\n}) => {\n  const hasHighSeverity = events.includes(true);\n  const totalSevere = events.filter((e) => e).length;\n  const displayCount = totalSevere > 99 ? '>99' : totalSevere;\n  const badgeSize = hasHighSeverity\n    ? Math.max(size * 0.6, 14)\n    : Math.max(size * 0.4, 6);\n\n\n  return (\n    <div className=\"relative\">\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        width={size}\n        height={size}\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        stroke-width=\"2\"\n        stroke-linecap=\"round\"\n        stroke-linejoin=\"round\"\n        className={`lucide lucide-bell ${className}`}\n      >\n        <path d=\"M10.268 21a2 2 0 0 0 3.464 0\" />\n        <path d=\"M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326\" />\n      </svg>\n      {events.length > 0 &&\n        totalSevere > 0 &&\n        ReactScanInternals.options.value.showNotificationCount && (\n          <div\n            className={cn([\n              'absolute',\n              hasHighSeverity ? '-top-2.5 -right-2.5' : '-top-1 -right-1',\n              'rounded-full',\n              'flex items-center justify-center',\n              'text-[8px] font-medium text-white',\n              'aspect-square',\n              hasHighSeverity ? 'bg-red-500/90' : 'bg-purple-500/90',\n            ])}\n            style={{\n              width: `${badgeSize}px`,\n              height: `${badgeSize}px`,\n              padding: hasHighSeverity ? '0.5px' : '0',\n            }}\n          >\n            {hasHighSeverity && displayCount}\n          </div>\n        )}\n    </div>\n  );\n};\n\nexport const CloseIcon = ({\n  className = '',\n  size = 24,\n}: { className?: string; size?: number }) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width={size}\n    height={size}\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    stroke-width=\"2\"\n    stroke-linecap=\"round\"\n    stroke-linejoin=\"round\"\n    className={className}\n  >\n    <path d=\"M18 6 6 18\" />\n    <path d=\"m6 6 12 12\" />\n  </svg>\n);\nexport const VolumeOnIcon = ({\n  className = '',\n  size = 24,\n}: { className?: string; size?: number }) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width={size}\n    height={size}\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    stroke-width=\"2\"\n    stroke-linecap=\"round\"\n    stroke-linejoin=\"round\"\n    className={className}\n  >\n    <path d=\"M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z\" />\n    <path d=\"M16 9a5 5 0 0 1 0 6\" />\n    <path d=\"M19.364 18.364a9 9 0 0 0 0-12.728\" />\n  </svg>\n);\n\nexport const VolumeOffIcon = ({\n  className = '',\n  size = 24,\n}: { className?: string; size?: number }) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width={size}\n    height={size}\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    stroke-width=\"2\"\n    stroke-linecap=\"round\"\n    stroke-linejoin=\"round\"\n    className={className}\n  >\n    <path d=\"M16 9a5 5 0 0 1 .95 2.293\" />\n    <path d=\"M19.364 5.636a9 9 0 0 1 1.889 9.96\" />\n    <path d=\"m2 2 20 20\" />\n    <path d=\"m7 7-.587.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298V11\" />\n    <path d=\"M9.828 4.172A.686.686 0 0 1 11 4.657v.686\" />\n  </svg>\n);\n\nexport const ArrowLeft = ({\n  size = 24,\n  className,\n}: {\n  size?: number;\n  className?: string;\n}) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width={size}\n    height={size}\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    stroke-width=\"2\"\n    stroke-linecap=\"round\"\n    stroke-linejoin=\"round\"\n    className={cn(['lucide lucide-arrow-left', className])}\n  >\n    <path d=\"m12 19-7-7 7-7\" />\n    <path d=\"M19 12H5\" />\n  </svg>\n);\n\nexport const PointerIcon = ({\n  className = '',\n  size = 24,\n}: { className?: string; size?: number }) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width={size}\n    height={size}\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    stroke-width=\"2\"\n    stroke-linecap=\"round\"\n    stroke-linejoin=\"round\"\n    className={className}\n  >\n    <path d=\"M14 4.1 12 6\" />\n    <path d=\"m5.1 8-2.9-.8\" />\n    <path d=\"m6 12-1.9 2\" />\n    <path d=\"M7.2 2.2 8 5.1\" />\n    <path d=\"M9.037 9.69a.498.498 0 0 1 .653-.653l11 4.5a.5.5 0 0 1-.074.949l-4.349 1.041a1 1 0 0 0-.74.739l-1.04 4.35a.5.5 0 0 1-.95.074z\" />\n  </svg>\n);\n\nexport const KeyboardIcon = ({\n  className = '',\n  size = 24,\n}: { className?: string; size?: number }) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width={size}\n    height={size}\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    stroke-width=\"2\"\n    stroke-linecap=\"round\"\n    stroke-linejoin=\"round\"\n    className={className}\n  >\n    <path d=\"M10 8h.01\" />\n    <path d=\"M12 12h.01\" />\n    <path d=\"M14 8h.01\" />\n    <path d=\"M16 12h.01\" />\n    <path d=\"M18 8h.01\" />\n    <path d=\"M6 8h.01\" />\n    <path d=\"M7 16h10\" />\n    <path d=\"M8 12h.01\" />\n    <rect width=\"20\" height=\"16\" x=\"2\" y=\"4\" rx=\"2\" />\n  </svg>\n);\nexport const ClearIcon = ({\n  className = '',\n  size = 24,\n}: { className?: string; size?: number }) => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width={size}\n      height={size}\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      stroke-width=\"2\"\n      stroke-linecap=\"round\"\n      stroke-linejoin=\"round\"\n      className={className}\n      style={{ transform: 'rotate(180deg)' }}\n    >\n      <circle cx=\"12\" cy=\"12\" r=\"10\" />\n      <path d=\"m4.9 4.9 14.2 14.2\" />\n    </svg>\n  );\n};\nexport const TrendingDownIcon = ({\n  className = '',\n  size = 24,\n}: { className?: string; size?: number }) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width={size}\n    height={size}\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    strokeWidth=\"2\"\n    strokeLinecap=\"round\"\n    strokeLinejoin=\"round\"\n    className={className}\n  >\n    <polyline points=\"22 17 13.5 8.5 8.5 13.5 2 7\" />\n    <polyline points=\"16 17 22 17 22 11\" />\n  </svg>\n);\n"
  },
  {
    "path": "packages/scan/src/web/views/notifications/notification-header.tsx",
    "content": "import { cn } from '~web/utils/helpers';\nimport {\n  NotificationEvent,\n  getComponentName,\n  getEventSeverity,\n  getTotalTime,\n} from './data';\nimport { CloseIcon } from './icons';\nimport { signalWidgetViews } from '~web/state';\n\nexport const NotificationHeader = ({\n  selectedEvent,\n}: {\n  selectedEvent: NotificationEvent;\n}) => {\n  const severity = getEventSeverity(selectedEvent);\n  switch (selectedEvent.kind) {\n    case 'interaction': {\n      return (\n        // h-[48px] is a hack to adjust for header size\n        <div\n          className={cn([`w-full flex border-b border-[#27272A] min-h-[48px]`])}\n        >\n          {/* todo: make css variables for colors */}\n          <div\n            className={cn([\n              'min-w-fit w-full justify-start flex items-center border-r border-[#27272A] pl-5 pr-2 text-sm gap-x-4',\n            ])}\n          >\n            <div className={cn(['flex items-center gap-x-2 '])}>\n              <span className={cn(['text-[#5a5a5a] mr-0.5'])}>\n                {selectedEvent.type === 'click' ? 'Clicked ' : 'Typed in '}\n              </span>\n              <span>{getComponentName(selectedEvent.componentPath)}</span>\n              <div\n                className={cn([\n                  'w-fit flex items-center justify-center h-fit text-white px-1 rounded-sm font-semibold text-[10px] whitespace-nowrap',\n                  severity === 'low' && 'bg-green-500/50',\n                  severity === 'needs-improvement' && 'bg-[#b77116]',\n                  severity === 'high' && 'bg-[#b94040]',\n                ])}\n              >\n                {getTotalTime(selectedEvent.timing).toFixed(0)}ms processing\n                time\n              </div>\n            </div>\n            <div\n              className={cn(['flex items-center gap-x-2  justify-end ml-auto'])}\n            >\n              <div\n                className={cn([\n                  'p-2 flex justify-center items-center border-[#27272A]',\n                ])}\n              >\n                <button\n                  onClick={() => {\n                    signalWidgetViews.value = {\n                      view: 'none',\n                    };\n                  }}\n                  title=\"Close\"\n                >\n                  <CloseIcon size={18} className=\"text-[#6F6F78]\" />\n                </button>\n              </div>\n            </div>\n          </div>\n        </div>\n      );\n    }\n    case 'dropped-frames': {\n      return (\n        <div\n          className={cn([`w-full flex border-b border-[#27272A] min-h-[48px]`])}\n        >\n          <div\n            className={cn([\n              'min-w-fit w-full justify-start flex items-center border-r border-[#27272A] pl-5 pr-2 text-sm gap-x-4',\n            ])}\n          >\n            <div className={cn(['flex items-center gap-x-2 '])}>\n              FPS Drop\n              <div\n                className={cn([\n                  'w-fit flex items-center justify-center h-fit text-white px-1 rounded-sm font-semibold text-[10px] whitespace-nowrap',\n                  severity === 'low' && 'bg-green-500/50',\n                  severity === 'needs-improvement' && 'bg-[#b77116]',\n                  severity === 'high' && 'bg-[#b94040]',\n                ])}\n              >\n                dropped to {selectedEvent.fps} FPS\n              </div>\n            </div>\n\n            <div\n              className={cn([\n                'flex items-center gap-x-2 w-2/4 justify-end ml-auto',\n              ])}\n            >\n              <div\n                className={cn([\n                  'p-2 flex justify-center items-center border-[#27272A]',\n                ])}\n              >\n                <button\n                  onClick={() => {\n                    signalWidgetViews.value = {\n                      view: 'none',\n                    };\n                  }}\n                >\n                  <CloseIcon size={18} className=\"text-[#6F6F78]\" />\n                </button>\n              </div>\n            </div>\n          </div>\n        </div>\n      );\n    }\n  }\n};\n"
  },
  {
    "path": "packages/scan/src/web/views/notifications/notification-tabs.tsx",
    "content": "import { cn } from '~web/utils/helpers';\nimport { NotificationEvent, useNotificationsContext } from './data';\nimport { Popover } from './popover';\nimport { VolumeOffIcon, VolumeOnIcon } from './icons';\nimport { playNotificationSound } from '~core/utils';\n\nexport const NotificationTabs = ({\n  selectedEvent: _,\n}: {\n  selectedEvent: NotificationEvent;\n}) => {\n  const { notificationState, setNotificationState, setRoute } =\n    useNotificationsContext();\n  return (\n    <div\n      className={cn([\n        'flex w-full justify-between items-center px-3 py-2 text-xs',\n      ])}\n    >\n      <div\n        className={cn([\n          'bg-[#18181B] flex items-center gap-x-1 p-1 rounded-sm',\n        ])}\n      >\n        <button\n          onClick={() => {\n            setRoute({\n              route: 'render-visualization',\n              routeMessage: null,\n            });\n          }}\n          className={cn([\n            'w-1/2 flex items-center justify-center whitespace-nowrap py-[5px] px-1 gap-x-1',\n            notificationState.route === 'render-visualization' ||\n            notificationState.route === 'render-explanation'\n              ? 'text-white bg-[#7521c8] rounded-sm'\n              : 'text-[#6E6E77] bg-[#18181B] rounded-sm',\n          ])}\n        >\n          Ranked\n        </button>\n        <button\n          onClick={() => {\n            setRoute({\n              route: 'other-visualization',\n              routeMessage: null,\n            });\n          }}\n          className={cn([\n            'w-1/2 flex items-center justify-center whitespace-nowrap py-[5px] px-1 gap-x-1',\n            notificationState.route === 'other-visualization'\n              ? 'text-white bg-[#7521c8] rounded-sm'\n              : 'text-[#6E6E77] bg-[#18181B] rounded-sm',\n          ])}\n        >\n          Overview\n        </button>\n        <button\n          onClick={() => {\n            setRoute({\n              route: 'optimize',\n              routeMessage: null,\n            });\n          }}\n          className={cn([\n            'w-1/2 flex items-center justify-center whitespace-nowrap py-[5px] px-1 gap-x-1',\n            notificationState.route === 'optimize'\n              ? 'text-white bg-[#7521c8] rounded-sm'\n              : 'text-[#6E6E77] bg-[#18181B] rounded-sm',\n          ])}\n        >\n          <span>Prompts</span>\n        </button>\n      </div>\n      <Popover\n        triggerContent={\n          <button\n            onClick={() => {\n              setNotificationState((prev) => {\n                if (\n                  prev.audioNotificationsOptions.enabled &&\n                  prev.audioNotificationsOptions.audioContext.state !== 'closed'\n                ) {\n                  prev.audioNotificationsOptions.audioContext.close();\n                }\n                const prevEnabledState = prev.audioNotificationsOptions.enabled;\n                localStorage.setItem(\n                  'react-scan-notifications-audio',\n                  String(!prevEnabledState),\n                );\n\n                const audioContext = new AudioContext();\n                if (!prev.audioNotificationsOptions.enabled) {\n                  playNotificationSound(audioContext);\n                }\n                if (prevEnabledState) {\n                  audioContext.close();\n                }\n                return {\n                  ...prev,\n                  audioNotificationsOptions: prevEnabledState\n                    ? {\n                        audioContext: null,\n                        enabled: false,\n                      }\n                    : {\n                        audioContext,\n                        enabled: true,\n                      },\n                };\n              });\n            }}\n            className=\"ml-auto\"\n          >\n            <div\n              className={cn([\n                'flex gap-x-2 justify-center items-center text-[#6E6E77]',\n              ])}\n            >\n              <span>Alerts</span>\n              {notificationState.audioNotificationsOptions.enabled ? (\n                <VolumeOnIcon size={16} className=\"text-[#6E6E77]\" />\n              ) : (\n                <VolumeOffIcon size={16} className=\"text-[#6E6E77]\" />\n              )}\n            </div>\n          </button>\n        }\n      >\n        <>Play a chime when a slowdown is recorded</>\n      </Popover>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/scan/src/web/views/notifications/notifications.tsx",
    "content": "import { forwardRef } from 'preact/compat';\nimport { useEffect, useRef, useState } from 'preact/hooks';\nimport { not_globally_unique_generateId } from '~core/utils';\nimport { useToolbarEventLog } from '~core/notifications/event-tracking';\nimport { FiberRenders } from '~core/notifications/performance';\nimport { iife, invariantError } from '~core/notifications/performance-utils';\nimport { playNotificationSound } from '~core/utils';\nimport { cn } from '~web/utils/helpers';\nimport {\n  NotificationStateContext,\n  NotificationsState,\n  getEventSeverity,\n  getTotalTime,\n  useNotificationsContext,\n} from './data';\nimport { DetailsRoutes } from './details-routes';\nimport { NotificationHeader } from './notification-header';\nimport { fadeOutHighlights } from './render-bar-chart';\nimport { SlowdownHistory, useLaggedEvents } from './slowdown-history';\n\nconst getGroupedFiberRenders = (fiberRenders: FiberRenders) => {\n  const res = Object.values(fiberRenders).map((render) => ({\n    id: not_globally_unique_generateId(),\n    totalTime: render.nodeInfo.reduce((prev, curr) => prev + curr.selfTime, 0),\n    count: render.nodeInfo.length,\n    name: render.nodeInfo[0].name, // invariant, at least one exists,\n    deletedAll: false,\n    parents: render.parents,\n    hasMemoCache: render.hasMemoCache,\n    wasFiberRenderMount: render.wasFiberRenderMount,\n    // it would be nice if we calculated the % of components memoizable, but this would have to be calculated downstream before it got aggregated\n    elements: render.nodeInfo.map((node) => node.element),\n    changes: {\n      context: render.changes.fiberContext.current\n        .filter((change) =>\n          render.changes.fiberContext.changesCounts.get(change.name),\n        )\n        .map((change) => ({\n          name: String(change.name),\n          count:\n            render.changes.fiberContext.changesCounts.get(change.name) ?? 0,\n        })),\n      props: render.changes.fiberProps.current\n        .filter((change) =>\n          render.changes.fiberProps.changesCounts.get(change.name),\n        )\n        .map((change) => ({\n          name: String(change.name),\n          count: render.changes.fiberProps.changesCounts.get(change.name) ?? 0,\n        })),\n      state: render.changes.fiberState.current\n        .filter((change) =>\n          render.changes.fiberState.changesCounts.get(Number(change.name)),\n        )\n        .map((change) => ({\n          index: change.name as number,\n          count:\n            render.changes.fiberState.changesCounts.get(Number(change.name)) ??\n            0,\n        })),\n    },\n  }));\n\n  return res;\n};\n\nconst useGarbageCollectElements = (\n  notificationEvents: NotificationsState['events'],\n) => {\n  useEffect(() => {\n    const checkElementsExistence = () => {\n      notificationEvents.forEach((event) => {\n        if (!event.groupedFiberRenders) return;\n\n        event.groupedFiberRenders.forEach((render) => {\n          if (render.deletedAll) return;\n\n          if (!render.elements || render.elements.length === 0) {\n            render.deletedAll = true;\n            return;\n          }\n\n          const initialLength = render.elements.length;\n          render.elements = render.elements.filter((element) => {\n            return element && element.isConnected;\n          });\n\n          if (render.elements.length === 0 && initialLength > 0) {\n            render.deletedAll = true;\n          }\n        });\n      });\n    };\n\n    const intervalId = setInterval(checkElementsExistence, 5000);\n\n    return () => {\n      clearInterval(intervalId);\n    };\n  }, [notificationEvents]);\n};\n\nexport const useAppNotifications = () => {\n  const log = useToolbarEventLog();\n\n  const notificationEvents: NotificationsState['events'] = [];\n\n  useGarbageCollectElements(notificationEvents);\n\n  log.state.events.forEach((event) => {\n    const fiberRenders =\n      event.kind === 'interaction'\n        ? event.data.meta.detailedTiming.fiberRenders\n        : event.data.meta.fiberRenders;\n    const groupedFiberRenders = getGroupedFiberRenders(fiberRenders);\n    const renderTime = groupedFiberRenders.reduce(\n      (prev, curr) => prev + curr.totalTime,\n      0,\n    );\n    switch (event.kind) {\n      case 'interaction': {\n        const { commitEnd, jsEndDetail, interactionStartDetail, rafStart } =\n          event.data.meta.detailedTiming;\n\n        // this is a known bug, js time doesn't backfill render time from async renders (or async js in general)\n        // the current impl is a close enough approximation so will leave as is until there is a dedicated effort to fix it\n        if (jsEndDetail - interactionStartDetail - renderTime < 0) {\n          invariantError('js time must be longer than render time');\n        }\n        const otherJSTime = Math.max(\n          0,\n          jsEndDetail - interactionStartDetail - renderTime,\n        );\n\n        const frameDraw = Math.max(\n          event.data.meta.latency - (commitEnd - interactionStartDetail),\n          0,\n        );\n        notificationEvents.push({\n          componentPath: event.data.meta.detailedTiming.componentPath,\n          groupedFiberRenders,\n          id: event.id,\n          kind: 'interaction',\n          memory: null,\n          timestamp: event.data.startAt,\n          type:\n            event.data.meta.detailedTiming.interactionType === 'keyboard'\n              ? 'keyboard'\n              : 'click',\n          timing: {\n            renderTime: renderTime,\n            kind: 'interaction',\n            otherJSTime,\n            framePreparation: rafStart - jsEndDetail,\n            frameConstruction: commitEnd - rafStart,\n            frameDraw,\n          },\n        });\n        return;\n      }\n      case 'long-render': {\n        notificationEvents.push({\n          kind: 'dropped-frames',\n          id: event.id,\n          memory: null,\n          timing: {\n            kind: 'dropped-frames',\n            renderTime: renderTime,\n            otherTime: event.data.meta.latency,\n          },\n          groupedFiberRenders,\n          timestamp: event.data.startAt,\n          fps: event.data.meta.fps,\n        });\n        return;\n      }\n    }\n  });\n  return notificationEvents;\n};\nconst timeout = 1000;\nexport const NotificationAudio = () => {\n  const { notificationState, setNotificationState } = useNotificationsContext();\n  const playedFor = useRef<number | null>(null);\n  const debounceTimeout = useRef<NodeJS.Timeout | null>(null);\n  const lastPlayedTime = useRef<number>(0);\n\n  const [laggedEvents] = useLaggedEvents();\n\n  const alertEventsCount = laggedEvents.filter(\n    // todo: make this configurable\n    (event) => getEventSeverity(event) === 'high',\n  ).length;\n\n  // oxlint-disable-next-line react-hooks/exhaustive-deps\n  useEffect(() => {\n    // todo: sync with options\n    const audioEnabledString = localStorage.getItem(\n      'react-scan-notifications-audio',\n    );\n\n    if (audioEnabledString !== 'false' && audioEnabledString !== 'true') {\n      localStorage.setItem('react-scan-notifications-audio', 'false');\n      return;\n    }\n\n    const audioEnabled = audioEnabledString === 'false' ? false : true;\n\n    if (audioEnabled) {\n      setNotificationState((prev) => {\n        if (prev.audioNotificationsOptions.enabled) {\n          return prev;\n        }\n        return {\n          ...prev,\n          audioNotificationsOptions: {\n            enabled: true,\n            audioContext: new AudioContext(),\n          },\n        };\n      });\n      return;\n    }\n  }, []);\n\n  // oxlint-disable-next-line react-hooks/exhaustive-deps\n  useEffect(() => {\n    const { audioNotificationsOptions } = notificationState;\n    if (!audioNotificationsOptions.enabled) {\n      return;\n    }\n    if (alertEventsCount === 0) {\n      return;\n    }\n    if (playedFor.current && playedFor.current >= alertEventsCount) {\n      return;\n    }\n\n    if (debounceTimeout.current) {\n      clearTimeout(debounceTimeout.current);\n    }\n\n    const now = Date.now();\n    const timeSinceLastPlay = now - lastPlayedTime.current;\n    const remainingDebounceTime = Math.max(0, timeout - timeSinceLastPlay);\n\n    debounceTimeout.current = setTimeout(() => {\n      playNotificationSound(audioNotificationsOptions.audioContext);\n      playedFor.current = alertEventsCount;\n      lastPlayedTime.current = Date.now();\n      debounceTimeout.current = null;\n    }, remainingDebounceTime);\n  }, [alertEventsCount]);\n\n  useEffect(() => {\n    if (alertEventsCount !== 0) {\n      return;\n    }\n    playedFor.current = null;\n  }, [alertEventsCount]);\n\n  useEffect(() => {\n    return () => {\n      if (debounceTimeout.current) {\n        clearTimeout(debounceTimeout.current);\n      }\n    };\n  }, []);\n\n  return null;\n};\n\nexport const NotificationWrapper = forwardRef<HTMLDivElement>((_, ref) => {\n  const events = useAppNotifications();\n  const [notificationState, setNotificationState] =\n    useState<NotificationsState>({\n      detailsExpanded: false,\n      events,\n      filterBy: 'latest',\n      moreInfoExpanded: false,\n      route: 'render-visualization',\n      selectedEvent:\n        events.toSorted((a, b) => a.timestamp - b.timestamp).at(-1) ?? null,\n      selectedFiber: null,\n      routeMessage: null,\n      audioNotificationsOptions: {\n        enabled: false,\n        audioContext: null,\n      },\n    });\n\n  notificationState.events = events;\n  return (\n    <NotificationStateContext.Provider\n      value={{\n        notificationState,\n        setNotificationState,\n        setRoute: ({ route, routeMessage }) => {\n          setNotificationState((prev) => {\n            const newState = { ...prev, route, routeMessage };\n            switch (route) {\n              case 'render-visualization': {\n                fadeOutHighlights();\n                return {\n                  ...newState,\n                  selectedFiber: null,\n                };\n              }\n              case 'optimize': {\n                fadeOutHighlights();\n                return {\n                  ...newState,\n                  selectedFiber: null,\n                };\n              }\n              case 'other-visualization': {\n                fadeOutHighlights();\n                return {\n                  ...newState,\n                  selectedFiber: null,\n                };\n              }\n              case 'render-explanation': {\n                // it would be ideal not to fade this out, but need to spend the time to sync the outline positions as they change in a performant (this was solved in react scan just need to follow same semantics)\n                fadeOutHighlights();\n\n                return newState;\n              }\n            }\n            route satisfies never;\n          });\n        },\n      }}\n    >\n      <NotificationAudio />\n      <Notifications ref={ref} />\n    </NotificationStateContext.Provider>\n  );\n});\nexport const Notifications = forwardRef<HTMLDivElement>((_, ref) => {\n  const { notificationState } = useNotificationsContext();\n\n  return (\n    <div ref={ref} className={cn(['h-full w-full flex flex-col'])}>\n      {notificationState.selectedEvent && (\n        <div\n          className={cn([\n            'w-full h-[48px] flex flex-col',\n            notificationState.moreInfoExpanded && 'h-[235px]',\n            notificationState.moreInfoExpanded &&\n              notificationState.selectedEvent.kind === 'dropped-frames' &&\n              'h-[150px]',\n          ])}\n        >\n          <NotificationHeader selectedEvent={notificationState.selectedEvent} />\n          {notificationState.moreInfoExpanded && <MoreInfo />}\n        </div>\n      )}\n      <div\n        className={cn([\n          'flex ',\n          notificationState.selectedEvent ? 'h-[calc(100%-48px)]' : 'h-full',\n          notificationState.moreInfoExpanded && 'h-[calc(100%-200px)]',\n          notificationState.moreInfoExpanded &&\n            notificationState.selectedEvent?.kind === 'dropped-frames' &&\n            'h-[calc(100%-150px)]',\n        ])}\n      >\n        <div className={cn(['h-full min-w-[200px]'])}>\n          <SlowdownHistory />\n        </div>\n        <div className={cn(['w-[calc(100%-200px)] h-full overflow-y-auto'])}>\n          <DetailsRoutes />\n        </div>\n      </div>\n    </div>\n  );\n});\n\nconst MoreInfo = () => {\n  const { notificationState } = useNotificationsContext();\n\n  if (!notificationState.selectedEvent) {\n    throw new Error('Invariant must have selected event for more info');\n  }\n\n  const event = notificationState.selectedEvent;\n\n  return (\n    <div\n      className={cn([\n        'px-4 py-2 border-b border-[#27272A] bg-[#18181B]/50 h-[calc(100%-40px)]',\n        event.kind === 'dropped-frames' && `h-[calc(100%-25px)]`,\n      ])}\n    >\n      <div className={cn(['flex flex-col gap-y-4 h-full'])}>\n        {iife(() => {\n          switch (event.kind) {\n            case 'interaction': {\n              return (\n                <>\n                  <div className={cn(['flex items-center gap-x-3'])}>\n                    <span className=\"text-[#6F6F78] text-xs font-medium\">\n                      {event.type === 'click'\n                        ? 'Clicked component location'\n                        : 'Typed in component location'}\n                    </span>\n                    <div className=\"font-mono text-[#E4E4E7] flex items-center bg-[#27272A] pl-2 py-1 rounded-sm overflow-x-auto\">\n                      {event.componentPath.toReversed().map((part, i) => (\n                        <>\n                          <span\n                            style={{\n                              lineHeight: '14px',\n                            }}\n                            key={part}\n                            className=\"text-[10px] whitespace-nowrap\"\n                          >\n                            {part}\n                          </span>\n                          {i < event.componentPath.length - 1 && (\n                            <span className=\"text-[#6F6F78] mx-0.5\">‹</span>\n                          )}\n                        </>\n                      ))}\n                    </div>\n                  </div>\n\n                  <div className={cn(['flex items-center gap-x-3'])}>\n                    <span className=\"text-[#6F6F78] text-xs font-medium\">\n                      Total Time\n                    </span>\n                    <span className=\"text-[#E4E4E7] bg-[#27272A] px-1.5 py-1 rounded-sm text-xs\">\n                      {getTotalTime(event.timing).toFixed(0)}ms\n                    </span>\n                  </div>\n                  <div className={cn(['flex items-center gap-x-3'])}>\n                    <span className=\"text-[#6F6F78] text-xs font-medium\">\n                      Occurred\n                    </span>\n                    <span className=\"text-[#E4E4E7] bg-[#27272A] px-1.5 py-1 rounded-sm text-xs\">\n                      {`${((Date.now() - event.timestamp) / 1000).toFixed(0)}s ago`}\n                    </span>\n                  </div>\n                </>\n              );\n            }\n            case 'dropped-frames': {\n              return (\n                <>\n                  <div className={cn(['flex items-center gap-x-3'])}>\n                    <span className=\"text-[#6F6F78] text-xs font-medium\">\n                      Total Time\n                    </span>\n                    <span className=\"text-[#E4E4E7] bg-[#27272A] px-1.5 py-1 rounded-sm text-xs\">\n                      {getTotalTime(event.timing).toFixed(0)}ms\n                    </span>\n                  </div>\n\n                  <div className={cn(['flex items-center gap-x-3'])}>\n                    <span className=\"text-[#6F6F78] text-xs font-medium\">\n                      Occurred\n                    </span>\n                    <span className=\"text-[#E4E4E7] bg-[#27272A] px-1.5 py-1 rounded-sm text-xs\">\n                      {`${((Date.now() - event.timestamp) / 1000).toFixed(0)}s ago`}\n                    </span>\n                  </div>\n                </>\n              );\n            }\n          }\n        })}\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/scan/src/web/views/notifications/optimize.tsx",
    "content": "import { useState } from 'preact/hooks';\nimport { cn } from '~web/utils/helpers';\nimport {\n  GroupedFiberRender,\n  NotificationEvent,\n  getComponentName,\n  getTotalTime,\n} from './data';\nimport { iife } from '~core/notifications/performance-utils';\n\nconst formatReactData = (groupedFiberRenders: Array<GroupedFiberRender>) => {\n  let text = '';\n\n  const filteredFibers = groupedFiberRenders\n    .toSorted((a, b) => b.totalTime - a.totalTime)\n    .slice(0, 30)\n    .filter((fiber) => fiber.totalTime > 5);\n\n  filteredFibers.forEach((fiberRender) => {\n    let localText = '';\n\n    localText += 'Component Name:';\n    localText += fiberRender.name;\n    localText += '\\n';\n\n    localText += `Rendered: ${fiberRender.count} times\\n`;\n    localText += `Sum of self times for ${fiberRender.name} is ${fiberRender.totalTime.toFixed(0)}ms\\n`;\n    if (fiberRender.changes.props.length > 0) {\n      localText += `Changed props for all ${fiberRender.name} instances (\"name:count\" pairs)\\n`;\n      fiberRender.changes.props.forEach((change) => {\n        localText += `${change.name}:${change.count}x\\n`;\n      });\n    }\n\n    if (fiberRender.changes.state.length > 0) {\n      localText += `Changed state for all ${fiberRender.name} instances (\"hook index:count\" pairs)\\n`;\n      fiberRender.changes.state.forEach((change) => {\n        localText += `${change.index}:${change.count}x\\n`;\n      });\n    }\n\n    if (fiberRender.changes.context.length > 0) {\n      localText += `Changed context for all ${fiberRender.name} instances (\"context display name (if exists):count\" pairs)\\n`;\n      fiberRender.changes.context.forEach((change) => {\n        localText += `${change.name}:${change.count}x\\n`;\n      });\n    }\n\n    text += localText;\n    text += '\\n';\n  });\n\n  return text;\n};\n\nexport const generateInteractionDataPrompt = ({\n  renderTime,\n  eHandlerTimeExcludingRenders,\n  toRafTime,\n  commitTime,\n  framePresentTime,\n  formattedReactData,\n}: {\n  renderTime: number;\n  eHandlerTimeExcludingRenders: number;\n  toRafTime: number;\n  commitTime: number;\n  framePresentTime: number | null;\n  formattedReactData: string;\n}) => {\n  return `I will provide you with a set of high level, and low level performance data about an interaction in a React App:\n### High level\n- react component render time: ${renderTime.toFixed(0)}ms\n- how long it took to run javascript event handlers (EXCLUDING REACT RENDERS): ${eHandlerTimeExcludingRenders.toFixed(0)}ms\n- how long it took from the last event handler time, to the last request animation frame: ${toRafTime.toFixed(0)}ms\n\t- things like prepaint, style recalculations, layerization, async web API's like observers may occur during this time\n- how long it took from the last request animation frame to when the dom was committed: ${commitTime.toFixed(0)}ms\n\t- during this period you will see paint, commit, potential style recalcs, and other misc browser activity. Frequently high times here imply css that makes the browser do a lot of work, or mutating expensive dom properties during the event handler stage. This can be many things, but it narrows the problem scope significantly when this is high\n${framePresentTime === null ? '' : `- how long it took from dom commit for the frame to be presented: ${framePresentTime.toFixed(0)}ms. This is when information about how to paint the next frame is sent to the compositor threads, and when the GPU does work. If this is high, look for issues that may be a bottleneck for operations occurring during this time`}\n\n### Low level\nWe also have lower level information about react components, such as their render time, and which props/state/context changed when they re-rendered.\n${formattedReactData}`;\n};\n\nconst generateInteractionOptimizationPrompt = ({\n  interactionType,\n  name,\n  componentPath,\n  time,\n  renderTime,\n  eHandlerTimeExcludingRenders,\n  toRafTime,\n  commitTime,\n  framePresentTime,\n  formattedReactData,\n}: {\n  interactionType: string;\n  name: string;\n  componentPath: string;\n\n  time: number;\n  renderTime: number;\n  eHandlerTimeExcludingRenders: number;\n  toRafTime: number;\n  commitTime: number;\n  framePresentTime: number | null;\n  formattedReactData: string;\n}) => `You will attempt to implement a performance improvement to a user interaction in a React app. You will be provided with data about the interaction, and the slow down.\n\nYour should split your goals into 2 parts:\n- identifying the problem\n- fixing the problem\n\t- it is okay to implement a fix even if you aren't 100% sure the fix solves the performance problem. When you aren't sure, you should tell the user to try repeating the interaction, and feeding the \"Formatted Data\" in the React Scan notifications optimize tab. This allows you to start a debugging flow with the user, where you attempt a fix, and observe the result. The user may make a mistake when they pass you the formatted data, so must make sure, given the data passed to you, that the associated data ties to the same interaction you were trying to debug.\n\n\nMake sure to check if the user has the react compiler enabled (project dependent, configured through build tool), so you don't unnecessarily memoize components. If it is, you do not need to worry about memoizing user components\n\nOne challenge you may face is the performance problem lies in a node_module, not in user code. If you are confident the problem originates because of a node_module, there are multiple strategies, which are context dependent:\n- you can try to work around the problem, knowing which module is slow\n- you can determine if its possible to resolve the problem in the node_module by modifying non node_module code\n- you can monkey patch the node_module to experiment and see if it's really the problem (you can modify a functions properties to hijack the call for example)\n- you can determine if it's feasible to replace whatever node_module is causing the problem with a performant option (this is an extreme)\n\nThe interaction was a ${interactionType} on the component named ${name}. This component has the following ancestors ${componentPath}. This is the path from the component, to the root. This should be enough information to figure out where this component is in the user's code base\n\nThis path is the component that was clicked, so it should tell you roughly where component had an event handler that triggered a state change.\n\nPlease note that the leaf node of this path might not be user code (if they use a UI library), and they may contain many wrapper components that just pass through children that aren't relevant to the actual click. So make you sure analyze the path and understand what the user code is doing\n\nWe have a set of high level, and low level data about the performance issue.\n\nThe click took ${time.toFixed(0)}ms from interaction start, to when a new frame was presented to a user.\n\nWe also provide you with a breakdown of what the browser spent time on during the period of interaction start to frame presentation.\n\n- react component render time: ${renderTime.toFixed(0)}ms\n- how long it took to run javascript event handlers (EXCLUDING REACT RENDERS): ${eHandlerTimeExcludingRenders.toFixed(0)}ms\n- how long it took from the last event handler time, to the last request animation frame: ${toRafTime.toFixed(0)}ms\n\t- things like prepaint, style recalculations, layerization, async web API's like observers may occur during this time\n- how long it took from the last request animation frame to when the dom was committed: ${commitTime.toFixed(0)}ms\n\t- during this period you will see paint, commit, potential style recalcs, and other misc browser activity. Frequently high times here imply css that makes the browser do a lot of work, or mutating expensive dom properties during the event handler stage. This can be many things, but it narrows the problem scope significantly when this is high\n${framePresentTime === null ? '' : `- how long it took from dom commit for the frame to be presented: ${framePresentTime.toFixed(0)}ms. This is when information about how to paint the next frame is sent to the compositor threads, and when the GPU does work. If this is high, look for issues that may be a bottleneck for operations occurring during this time`}\n\n\nWe also have lower level information about react components, such as their render time, and which props/state/context changed when they re-rendered.\n\n${formattedReactData}\n\nYou may notice components have many renders, but much fewer props/state/context changes. This normally implies most of the components could of been memoized to avoid computation\n\nIt's also important to remember if a component had no props/state/context change, and it was memoized, it would not render. So the flow should be:\n- find the most expensive components\n- see what's causing them to render\n- determine how you can make those state/props/context not change for a large set of the renders\n- once there are no more changes left, you can memoize the component so it no longer unnecessarily re-renders. \n\nAn important thing to note is that if you see a lot of react renders (some components with very high render counts), but javascript excluding renders is much higher than render time, it is possible that the components with lots of renders run hooks like useEffect/useLayoutEffect, which run during the JS event handler period.\n\nIt's also good to note that react profiles hook times in development, and if many hooks are called (lets say 5,000 components all called a useEffect), it will have to profile every single one. And it may also be the case the comparison of the hooks dependency can be expensive, and that would not be tracked in render time.\n\nIf a node_module is the component with high renders, you can experiment to see if that component is the root issue (because of hooks). You should use the same instructions for node_module debugging mentioned previously.\n\n`;\nconst generateFrameDropOptimizationPrompt = ({\n  renderTime,\n  otherTime,\n  formattedReactData,\n}: {\n  renderTime: number;\n\n  otherTime: number;\n  formattedReactData: string;\n}) => `You will attempt to implement a performance improvement to a large slowdown in a react app\n\nYour should split your goals into 2 parts:\n- identifying the problem\n- fixing the problem\n\t- it is okay to implement a fix even if you aren't 100% sure the fix solves the performance problem. When you aren't sure, you should tell the user to try repeating the interaction, and feeding the \"Formatted Data\" in the React Scan notifications optimize tab. This allows you to start a debugging flow with the user, where you attempt a fix, and observe the result. The user may make a mistake when they pass you the formatted data, so must make sure, given the data passed to you, that the associated data ties to the same interaction you were trying to debug.\n\nMake sure to check if the user has the react compiler enabled (project dependent, configured through build tool), so you don't unnecessarily memoize components. If it is, you do not need to worry about memoizing user components\n\nOne challenge you may face is the performance problem lies in a node_module, not in user code. If you are confident the problem originates because of a node_module, there are multiple strategies, which are context dependent:\n- you can try to work around the problem, knowing which module is slow\n- you can determine if its possible to resolve the problem in the node_module by modifying non node_module code\n- you can monkey patch the node_module to experiment and see if it's really the problem (you can modify a functions properties to hijack the call for example)\n- you can determine if it's feasible to replace whatever node_module is causing the problem with a performant option (this is an extreme)\n\n\nWe have the high level time of how much react spent rendering, and what else the browser spent time on during this slowdown\n\n- react component render time: ${renderTime.toFixed(0)}ms\n- other time: ${otherTime}ms\n\n\nWe also have lower level information about react components, such as their render time, and which props/state/context changed when they re-rendered.\n\n${formattedReactData}\n\nYou may notice components have many renders, but much fewer props/state/context changes. This normally implies most of the components could of been memoized to avoid computation\n\nIt's also important to remember if a component had no props/state/context change, and it was memoized, it would not render. So the flow should be:\n- find the most expensive components\n- see what's causing them to render\n- determine how you can make those state/props/context not change for a large set of the renders\n- once there are no more changes left, you can memoize the component so it no longer unnecessarily re-renders. \n\nAn important thing to note is that if you see a lot of react renders (some components with very high render counts), but other time is much higher than render time, it is possible that the components with lots of renders run hooks like useEffect/useLayoutEffect, which run outside of what we profile (just react render time).\n\nIt's also good to note that react profiles hook times in development, and if many hooks are called (lets say 5,000 components all called a useEffect), it will have to profile every single one. And it may also be the case the comparison of the hooks dependency can be expensive, and that would not be tracked in render time.\n\nIf a node_module is the component with high renders, you can experiment to see if that component is the root issue (because of hooks). You should use the same instructions for node_module debugging mentioned previously.\n\nIf renders don't seem to be the problem, see if there are any expensive CSS properties being added/mutated, or any expensive DOM Element mutations/new elements being created that could cause this slowdown. \n`;\n\nexport const generateFrameDropExplanationPrompt = ({\n  renderTime,\n  otherTime,\n  formattedReactData,\n}: {\n  renderTime: number;\n\n  otherTime: number;\n  formattedReactData: string;\n}) => `Your goal will be to help me find the source of a performance problem in a React App. I collected a large dataset about this specific performance problem.\n\nWe have the high level time of how much react spent rendering, and what else the browser spent time on during this slowdown\n\n- react component render time: ${renderTime.toFixed(0)}ms\n- other time (other JavaScript, hooks like useEffect, style recalculations, layerization, paint & commit and everything else the browser might do to draw a new frame after javascript mutates the DOM): ${otherTime}ms\n\n\nWe also have lower level information about react components, such as their render time, and which props/state/context changed when they re-rendered.\n\n${formattedReactData}\n\nYou may notice components have many renders, but much fewer props/state/context changes. This normally implies most of the components could of been memoized to avoid computation\n\nIt's also important to remember if a component had no props/state/context change, and it was memoized, it would not render. So a flow we can go through is:\n- find the most expensive components\n- see what's causing them to render\n- determine how you can make those state/props/context not change for a large set of the renders\n- once there are no more changes left, you can memoize the component so it no longer unnecessarily re-renders. \n\n\nAn important thing to note is that if you see a lot of react renders (some components with very high render counts), but other time is much higher than render time, it is possible that the components with lots of renders run hooks like useEffect/useLayoutEffect, which run outside of what we profile (just react render time).\n\nIt's also good to note that react profiles hook times in development, and if many hooks are called (lets say 5,000 components all called a useEffect), it will have to profile every single one, and this can add significant overhead when thousands of effects ran.\n\nIf it's not possible to explain the root problem from this data, please ask me for more data explicitly, and what we would need to know to find the source of the performance problem.\n`;\n\nconst generateFrameDropDataPrompt = ({\n  renderTime,\n  otherTime,\n  formattedReactData,\n}: {\n  renderTime: number;\n\n  otherTime: number;\n  formattedReactData: string;\n}) => `I will provide you with a set of high level, and low level performance data about a large frame drop in a React App:\n### High level\n- react component render time: ${renderTime.toFixed(0)}ms\n- how long it took to run everything else (other JavaScript, hooks like useEffect, style recalculations, layerization, paint & commit and everything else the browser might do to draw a new frame after javascript mutates the DOM): ${otherTime}ms\n\n### Low level\nWe also have lower level information about react components, such as their render time, and which props/state/context changed when they re-rendered.\n${formattedReactData}`;\n\nexport const generateInteractionExplanationPrompt = ({\n  interactionType,\n  name,\n  time,\n  renderTime,\n  eHandlerTimeExcludingRenders,\n  toRafTime,\n  commitTime,\n  framePresentTime,\n  formattedReactData,\n}: {\n  interactionType: string;\n  name: string;\n  time: number;\n  renderTime: number;\n  eHandlerTimeExcludingRenders: number;\n  toRafTime: number;\n  commitTime: number;\n  framePresentTime: number | null;\n  formattedReactData: string;\n}) => `Your goal will be to help me find the source of a performance problem. I collected a large dataset about this specific performance problem.\n\nThere was a ${interactionType} on a component named ${name}. This means, roughly, the component that handled the ${interactionType} event was named ${name}.\n\nWe have a set of high level, and low level data about the performance issue.\n\nThe click took ${time.toFixed(0)}ms from interaction start, to when a new frame was presented to a user.\n\nWe also provide you with a breakdown of what the browser spent time on during the period of interaction start to frame presentation.\n\n- react component render time: ${renderTime.toFixed(0)}ms\n- how long it took to run javascript event handlers (EXCLUDING REACT RENDERS): ${eHandlerTimeExcludingRenders.toFixed(0)}ms\n- how long it took from the last event handler time, to the last request animation frame: ${toRafTime.toFixed(0)}ms\n\t- things like prepaint, style recalculations, layerization, async web API's like observers may occur during this time\n- how long it took from the last request animation frame to when the dom was committed: ${commitTime.toFixed(0)}ms\n\t- during this period you will see paint, commit, potential style recalcs, and other misc browser activity. Frequently high times here imply css that makes the browser do a lot of work, or mutating expensive dom properties during the event handler stage. This can be many things, but it narrows the problem scope significantly when this is high\n${framePresentTime === null ? '' : `- how long it took from dom commit for the frame to be presented: ${framePresentTime.toFixed(0)}ms. This is when information about how to paint the next frame is sent to the compositor threads, and when the GPU does work. If this is high, look for issues that may be a bottleneck for operations occurring during this time`}\n\nWe also have lower level information about react components, such as their render time, and which props/state/context changed when they re-rendered.\n\n${formattedReactData}\n\n\nYou may notice components have many renders, but much fewer props/state/context changes. This normally implies most of the components could of been memoized to avoid computation\n\nIt's also important to remember if a component had no props/state/context change, and it was memoized, it would not render. So a flow we can go through is:\n- find the most expensive components\n- see what's causing them to render\n- determine how you can make those state/props/context not change for a large set of the renders\n- once there are no more changes left, you can memoize the component so it no longer unnecessarily re-renders. \n\n\nAn important thing to note is that if you see a lot of react renders (some components with very high render counts), but javascript excluding renders is much higher than render time, it is possible that the components with lots of renders run hooks like useEffect/useLayoutEffect, which run during the JS event handler period.\n\nIt's also good to note that react profiles hook times in development, and if many hooks are called (lets say 5,000 components all called a useEffect), it will have to profile every single one. And it may also be the case the comparison of the hooks dependency can be expensive, and that would not be tracked in render time.\n\nIf it's not possible to explain the root problem from this data, please ask me for more data explicitly, and what we would need to know to find the source of the performance problem.\n`;\nexport const getLLMPrompt = (\n  activeTab: 'fix' | 'data' | 'explanation',\n  selectedEvent: NotificationEvent,\n) =>\n  iife(() => {\n    switch (activeTab) {\n      case 'data': {\n        switch (selectedEvent.kind) {\n          case 'dropped-frames': {\n            return generateFrameDropDataPrompt({\n              formattedReactData: formatReactData(\n                selectedEvent.groupedFiberRenders,\n              ),\n              renderTime: selectedEvent.groupedFiberRenders.reduce(\n                (prev, curr) => prev + curr.totalTime,\n                0,\n              ),\n              otherTime: selectedEvent.timing.otherTime,\n            });\n          }\n          case 'interaction': {\n            return generateInteractionDataPrompt({\n              commitTime: selectedEvent.timing.frameConstruction,\n              eHandlerTimeExcludingRenders: selectedEvent.timing.otherJSTime,\n              formattedReactData: formatReactData(\n                selectedEvent.groupedFiberRenders,\n              ),\n              framePresentTime: selectedEvent.timing.frameDraw,\n              renderTime: selectedEvent.groupedFiberRenders.reduce(\n                (prev, curr) => prev + curr.totalTime,\n                0,\n              ),\n              toRafTime: selectedEvent.timing.framePreparation,\n            });\n          }\n        }\n      }\n      case 'explanation': {\n        switch (selectedEvent.kind) {\n          case 'dropped-frames': {\n            return generateFrameDropExplanationPrompt({\n              formattedReactData: formatReactData(\n                selectedEvent.groupedFiberRenders,\n              ),\n              renderTime: selectedEvent.groupedFiberRenders.reduce(\n                (prev, curr) => prev + curr.totalTime,\n                0,\n              ),\n              otherTime: selectedEvent.timing.otherTime,\n            });\n          }\n          case 'interaction': {\n            return generateInteractionExplanationPrompt({\n              commitTime: selectedEvent.timing.frameConstruction,\n              eHandlerTimeExcludingRenders: selectedEvent.timing.otherJSTime,\n              formattedReactData: formatReactData(\n                selectedEvent.groupedFiberRenders,\n              ),\n              framePresentTime: selectedEvent.timing.frameDraw,\n              interactionType: selectedEvent.type,\n              name: getComponentName(selectedEvent.componentPath),\n              renderTime: selectedEvent.groupedFiberRenders.reduce(\n                (prev, curr) => prev + curr.totalTime,\n                0,\n              ),\n              time: getTotalTime(selectedEvent.timing),\n              toRafTime: selectedEvent.timing.framePreparation,\n            });\n          }\n        }\n      }\n      case 'fix': {\n        switch (selectedEvent.kind) {\n          case 'dropped-frames': {\n            return generateFrameDropOptimizationPrompt({\n              formattedReactData: formatReactData(\n                selectedEvent.groupedFiberRenders,\n              ),\n\n              renderTime: selectedEvent.groupedFiberRenders.reduce(\n                (prev, curr) => prev + curr.totalTime,\n                0,\n              ),\n              otherTime: selectedEvent.timing.otherTime,\n            });\n          }\n          case 'interaction': {\n            return generateInteractionOptimizationPrompt({\n              commitTime: selectedEvent.timing.frameConstruction,\n              componentPath: selectedEvent.componentPath.join('>'),\n              eHandlerTimeExcludingRenders: selectedEvent.timing.otherJSTime,\n              formattedReactData: formatReactData(\n                selectedEvent.groupedFiberRenders,\n              ),\n              framePresentTime: selectedEvent.timing.frameDraw,\n              interactionType: selectedEvent.type,\n              name: getComponentName(selectedEvent.componentPath),\n              renderTime: selectedEvent.groupedFiberRenders.reduce(\n                (prev, curr) => prev + curr.totalTime,\n                0,\n              ),\n              time: getTotalTime(selectedEvent.timing),\n              toRafTime: selectedEvent.timing.framePreparation,\n            });\n          }\n        }\n      }\n    }\n  });\n\nexport const Optimize = ({\n  selectedEvent,\n}: { selectedEvent: NotificationEvent }) => {\n  const [activeTab, setActiveTab] = useState<'fix' | 'explanation' | 'data'>(\n    'fix',\n  );\n  const [copying, setCopying] = useState(false);\n\n  return (\n    <div className={cn(['w-full h-full'])}>\n      <div\n        className={cn([\n          'border border-[#27272A] rounded-sm h-4/5 text-xs overflow-hidden',\n        ])}\n      >\n        <div className={cn(['bg-[#18181B] p-1 rounded-t-sm'])}>\n          <div className={cn(['flex items-center gap-x-1'])}>\n            <button\n              onClick={() => setActiveTab('fix')}\n              className={cn([\n                'flex items-center justify-center whitespace-nowrap py-1.5 px-3 rounded-sm',\n                activeTab === 'fix'\n                  ? 'text-white bg-[#7521c8]'\n                  : 'text-[#6E6E77] hover:text-white',\n              ])}\n            >\n              Fix\n            </button>\n\n            <button\n              onClick={() => setActiveTab('explanation')}\n              className={cn([\n                'flex items-center justify-center whitespace-nowrap py-1.5 px-3 rounded-sm',\n                activeTab === 'explanation'\n                  ? 'text-white bg-[#7521c8]'\n                  : 'text-[#6E6E77] hover:text-white',\n              ])}\n            >\n              Explanation\n            </button>\n            <button\n              onClick={() => setActiveTab('data')}\n              className={cn([\n                'flex items-center justify-center whitespace-nowrap py-1.5 px-3 rounded-sm',\n                activeTab === 'data'\n                  ? 'text-white bg-[#7521c8]'\n                  : 'text-[#6E6E77] hover:text-white',\n              ])}\n            >\n              Data\n            </button>\n          </div>\n        </div>\n        <div className={cn(['overflow-y-auto h-full'])}>\n          <pre\n            className={cn([\n              'p-2 h-full',\n              'whitespace-pre-wrap break-words',\n              'text-gray-300 font-mono ',\n            ])}\n          >\n            {getLLMPrompt(activeTab, selectedEvent)}\n          </pre>\n        </div>\n      </div>\n      <button\n        onClick={async () => {\n          const text = getLLMPrompt(activeTab, selectedEvent);\n\n          await navigator.clipboard.writeText(text);\n          setCopying(true);\n          setTimeout(() => setCopying(false), 1000);\n        }}\n        className={cn([\n          'mt-4 px-4 py-2 bg-[#18181B] text-[#6E6E77] rounded-sm',\n          'hover:text-white transition-colors duration-200',\n          'flex items-center justify-center gap-x-2 text-xs',\n        ])}\n      >\n        <span>{copying ? 'Copied!' : 'Copy Prompt'}</span>\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          width=\"16\"\n          height=\"16\"\n          viewBox=\"0 0 24 24\"\n          fill=\"none\"\n          stroke=\"currentColor\"\n          strokeWidth=\"2\"\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n          className={cn([\n            'transition-transform duration-200',\n            copying && 'scale-110',\n          ])}\n        >\n          {copying ? (\n            <path d=\"M20 6L9 17l-5-5\" />\n          ) : (\n            <>\n              <rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\" />\n              <path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\" />\n            </>\n          )}\n        </svg>\n      </button>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/scan/src/web/views/notifications/other-visualization.tsx",
    "content": "import { ReactNode } from 'preact/compat';\nimport { useContext, useEffect, useState } from 'preact/hooks';\nimport { getIsProduction } from '~core/index';\nimport { iife } from '~core/notifications/performance-utils';\nimport { cn } from '~web/utils/helpers';\nimport {\n  InteractionEvent,\n  NotificationEvent,\n  getTotalTime,\n  useNotificationsContext,\n} from './data';\nimport { getLLMPrompt } from './optimize';\nimport { ToolbarElementContext } from '~web/widget';\ntype BaseTimeDataItem = {\n  name: string;\n  time: number;\n  color: string;\n  kind:\n    | 'other-not-javascript'\n    | 'other-javascript'\n    | 'render'\n    | 'other-frame-drop'\n    | 'total-processing-time';\n};\n\ntype TimeData = Array<BaseTimeDataItem>;\n\nconst getTimeData = (\n  selectedEvent: NotificationEvent,\n  isProduction: boolean,\n) => {\n  switch (selectedEvent.kind) {\n    // todo: push instead of conditional spread\n    case 'dropped-frames': {\n      const timeData: TimeData = [\n        ...(isProduction\n          ? [\n              {\n                name: 'Total Processing Time',\n                time: getTotalTime(selectedEvent.timing),\n                color: 'bg-red-500',\n                kind: 'total-processing-time' as const,\n              },\n            ]\n          : [\n              {\n                name: 'Renders',\n                time: selectedEvent.timing.renderTime,\n                color: 'bg-purple-500',\n                kind: 'render' as const,\n              },\n              {\n                name: 'JavaScript, DOM updates, Draw Frame',\n                time: selectedEvent.timing.otherTime,\n                color: 'bg-[#4b4b4b]',\n                kind: 'other-frame-drop' as const,\n              },\n            ]),\n      ];\n      return timeData;\n    }\n    case 'interaction': {\n      const timeData: TimeData = [\n        ...(!isProduction\n          ? [\n              {\n                name: 'Renders',\n                time: selectedEvent.timing.renderTime,\n                color: 'bg-purple-500',\n                kind: 'render' as const,\n              },\n            ]\n          : []),\n        {\n          name: isProduction\n            ? 'React Renders, Hooks, Other JavaScript'\n            : 'JavaScript/React Hooks ',\n          time: selectedEvent.timing.otherJSTime,\n          color: 'bg-[#EFD81A]',\n\n          kind: 'other-javascript',\n        },\n\n        {\n          name: 'Update DOM and Draw New Frame',\n          time:\n            getTotalTime(selectedEvent.timing) -\n            selectedEvent.timing.renderTime -\n            selectedEvent.timing.otherJSTime,\n          color: 'bg-[#1D3A66]',\n          kind: 'other-not-javascript',\n        },\n      ];\n\n      return timeData;\n    }\n  }\n};\n\nexport const OtherVisualization = ({\n  selectedEvent,\n}: {\n  selectedEvent: NotificationEvent;\n}) => {\n  const [isProduction] = useState(getIsProduction() ?? false);\n  const { notificationState } = useNotificationsContext();\n  const [expandedItems, setExpandedItems] = useState<string[]>(\n    notificationState.routeMessage?.name\n      ? [notificationState.routeMessage.name]\n      : [],\n  );\n  const timeData = getTimeData(selectedEvent, isProduction);\n  const root = useContext(ToolbarElementContext);\n\n  // for when a user clicks a bar of a non render, and gets sent to the other visualization and passes a route message on the way\n  // oxlint-disable-next-line react-hooks/exhaustive-deps\n  useEffect(() => {\n    if (notificationState.routeMessage?.name) {\n      const container = root?.querySelector('#overview-scroll-container');\n      const element = root?.querySelector(\n        `#react-scan-overview-bar-${notificationState.routeMessage.name}`,\n      ) as HTMLElement;\n\n      if (container && element) {\n        const elementTop = element.getBoundingClientRect().top;\n        const containerTop = container.getBoundingClientRect().top;\n        const scrollOffset = elementTop - containerTop;\n        container.scrollTop = container.scrollTop + scrollOffset;\n      }\n    }\n  }, [notificationState.route]);\n\n  // oxlint-disable-next-line react-hooks/exhaustive-deps\n  useEffect(() => {\n    if (notificationState.route === 'other-visualization') {\n      setExpandedItems((prev) =>\n        notificationState.routeMessage?.name\n          ? [notificationState.routeMessage.name]\n          : prev,\n      );\n    }\n  }, [notificationState.route]);\n\n  const totalTime = timeData.reduce((acc, item) => acc + item.time, 0);\n\n  return (\n    <div className=\"rounded-sm border border-zinc-800 text-xs\">\n      <div className=\"p-2 border-b border-zinc-800 bg-zinc-900/50\">\n        <div className=\"flex items-center justify-between\">\n          <h3 className=\"text-xs font-medium\">What was time spent on?</h3>\n          <span className=\"text-xs text-zinc-400\">\n            Total: {totalTime.toFixed(0)}ms\n          </span>\n        </div>\n      </div>\n      <div className=\"divide-y divide-zinc-800\">\n        {timeData.map((entry) => {\n          const isExpanded = expandedItems.includes(entry.kind);\n          return (\n            <div key={entry.kind} id={`react-scan-overview-bar-${entry.kind}`}>\n              <button\n                onClick={() =>\n                  setExpandedItems((prev) =>\n                    prev.includes(entry.kind)\n                      ? prev.filter((item) => item !== entry.kind)\n                      : [...prev, entry.kind],\n                  )\n                }\n                className=\"w-full px-3 py-2 flex items-center gap-4 hover:bg-zinc-800/50 transition-colors\"\n              >\n                <div className=\"flex-1\">\n                  <div className=\"flex items-center justify-between mb-2\">\n                    <div className=\"flex items-center gap-0.5\">\n                      <svg\n                        className={`h-4 w-4 text-zinc-400 transition-transform ${isExpanded ? 'rotate-90' : ''}`}\n                        fill=\"none\"\n                        stroke=\"currentColor\"\n                        viewBox=\"0 0 24 24\"\n                      >\n                        <path\n                          strokeLinecap=\"round\"\n                          strokeLinejoin=\"round\"\n                          strokeWidth={2}\n                          d=\"M9 5l7 7-7 7\"\n                        />\n                      </svg>\n                      <span className=\"font-medium flex items-center text-left\">\n                        {entry.name}\n                      </span>\n                    </div>\n                    <span className=\" text-zinc-400\">\n                      {entry.time.toFixed(0)}ms\n                    </span>\n                  </div>\n                  <div className=\"h-1 bg-zinc-800 rounded-full overflow-hidden\">\n                    <div\n                      className={`h-full ${entry.color} transition-all`}\n                      style={{\n                        width: `${(entry.time / totalTime) * 100}%`,\n                      }}\n                    />\n                  </div>\n                </div>\n              </button>\n              {isExpanded && (\n                <div className=\"bg-zinc-900/30 border-t border-zinc-800 px-2.5 py-3\">\n                  <p className=\" text-zinc-400 mb-4 text-xs\">\n                    {iife(() => {\n                      switch (selectedEvent.kind) {\n                        case 'interaction': {\n                          switch (entry.kind) {\n                            case 'render': {\n                              return (\n                                <Explanation\n                                  input={getRenderInput(selectedEvent)}\n                                />\n                              );\n                            }\n\n                            case 'other-javascript': {\n                              return (\n                                <Explanation\n                                  input={getJSInput(selectedEvent)}\n                                />\n                              );\n                            }\n\n                            case 'other-not-javascript': {\n                              return (\n                                <Explanation\n                                  input={getDrawInput(selectedEvent)}\n                                />\n                              );\n                            }\n                          }\n                        }\n                        case 'dropped-frames': {\n                          switch (entry.kind) {\n                            case 'total-processing-time': {\n                              return (\n                                <Explanation\n                                  input={{\n                                    kind: 'total-processing',\n                                    data: {\n                                      time: getTotalTime(selectedEvent.timing),\n                                    },\n                                  }}\n                                />\n                              );\n                            }\n                            case 'render': {\n                              return (\n                                <>\n                                  <Explanation\n                                    input={{\n                                      kind: 'render',\n                                      data: {\n                                        topByTime:\n                                          selectedEvent.groupedFiberRenders\n                                            .toSorted(\n                                              (a, b) =>\n                                                b.totalTime - a.totalTime,\n                                            )\n                                            .slice(0, 3)\n                                            .map((render) => ({\n                                              name: render.name,\n                                              percentage:\n                                                render.totalTime /\n                                                getTotalTime(\n                                                  selectedEvent.timing,\n                                                ),\n                                            })),\n                                      },\n                                    }}\n                                  />\n                                </>\n                              );\n                            }\n                            case 'other-frame-drop': {\n                              return (\n                                <Explanation\n                                  input={{\n                                    kind: 'other',\n                                  }}\n                                />\n                              );\n                            }\n                          }\n                        }\n                      }\n                    })}\n                  </p>\n                </div>\n              )}\n            </div>\n          );\n        })}\n      </div>\n    </div>\n  );\n};\n\ntype OverviewInput =\n  | {\n      kind: 'js-explanation-base';\n    }\n  | {\n      kind: 'total-processing';\n      data: {\n        time: number;\n      };\n    }\n  | {\n      kind: 'high-render-count-high-js';\n      data: {\n        renderCount: number;\n        topByCount: Array<{ name: string; count: number }>;\n      };\n    }\n  | {\n      kind: 'low-render-count-high-js';\n      data: {\n        renderCount: number;\n      };\n    }\n  | {\n      kind: 'high-render-count-update-dom-draw-frame';\n      data: {\n        count: number;\n        percentageOfTotal: number;\n        copyButton: ReactNode;\n      };\n    }\n  | {\n      kind: 'update-dom-draw-frame';\n      data: {\n        copyButton: ReactNode;\n      };\n    }\n  | {\n      kind: 'render';\n      data: { topByTime: Array<{ name: string; percentage: number }> };\n    }\n  | {\n      kind: 'other';\n    };\n\nexport const getTotalProcessingTimeInput = (event: NotificationEvent) => {\n  return {\n    kind: 'total-processing',\n    data: {\n      time: getTotalTime(event.timing),\n    },\n  } satisfies OverviewInput;\n};\n\nconst getDrawInput = (event: InteractionEvent): OverviewInput => {\n  const renderCount = event.groupedFiberRenders.reduce(\n    (prev, curr) => prev + curr.count,\n    0,\n  );\n\n  const renderTime = event.timing.renderTime;\n  const totalTime = getTotalTime(event.timing);\n  const renderPercentage = (renderTime / totalTime) * 100;\n\n  if (renderCount > 100) {\n    return {\n      kind: 'high-render-count-update-dom-draw-frame',\n      data: {\n        count: renderCount,\n        percentageOfTotal: renderPercentage,\n        copyButton: <CopyPromptButton />,\n      },\n    };\n  }\n\n  return {\n    kind: 'update-dom-draw-frame',\n    data: {\n      copyButton: <CopyPromptButton />,\n    },\n  };\n};\n\nconst CopyPromptButton = () => {\n  const [copying, setCopying] = useState(false);\n  const { notificationState } = useNotificationsContext();\n\n  return (\n    <button\n      onClick={async () => {\n        if (!notificationState.selectedEvent) {\n          return;\n        }\n\n        await navigator.clipboard.writeText(\n          getLLMPrompt('explanation', notificationState.selectedEvent),\n        );\n        setCopying(true);\n        setTimeout(() => setCopying(false), 1000);\n      }}\n      className=\"bg-zinc-800 flex hover:bg-zinc-700 text-zinc-200 px-2 py-1 rounded gap-x-3\"\n    >\n      <span>{copying ? 'Copied!' : 'Copy Prompt'}</span>\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        width=\"16\"\n        height=\"16\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        className={cn([\n          'transition-transform duration-200',\n          copying && 'scale-110',\n        ])}\n      >\n        {copying ? (\n          <path d=\"M20 6L9 17l-5-5\" />\n        ) : (\n          <>\n            <rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\" />\n            <path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\" />\n          </>\n        )}\n      </svg>\n    </button>\n  );\n};\n\nconst getRenderInput = (event: InteractionEvent): OverviewInput => {\n  if (event.timing.renderTime / getTotalTime(event.timing) > 0.3) {\n    return {\n      kind: 'render',\n      data: {\n        topByTime: event.groupedFiberRenders\n          .toSorted((a, b) => b.totalTime - a.totalTime)\n          .slice(0, 3)\n          .map((e) => ({\n            percentage: e.totalTime / getTotalTime(event.timing),\n            name: e.name,\n          })),\n      },\n    };\n  }\n\n  return {\n    kind: 'other',\n  };\n};\n\nconst getJSInput = (event: InteractionEvent): OverviewInput => {\n  const renderCount = event.groupedFiberRenders.reduce(\n    (prev, curr) => prev + curr.count,\n    0,\n  );\n  if (event.timing.otherJSTime / getTotalTime(event.timing) < 0.2) {\n    return {\n      kind: 'js-explanation-base',\n    };\n  }\n  if (\n    event.groupedFiberRenders.find((render) => render.count > 200) ||\n    event.groupedFiberRenders.reduce((prev, curr) => prev + curr.count, 0) > 500\n  ) {\n    // not sure a great heuristic for picking the render count\n    return {\n      kind: 'high-render-count-high-js',\n      data: {\n        renderCount,\n        topByCount: event.groupedFiberRenders\n          .filter((groupedRender) => groupedRender.count > 100)\n          .toSorted((a, b) => b.count - a.count)\n          .slice(0, 3),\n      },\n    };\n  }\n  if (event.timing.otherJSTime / getTotalTime(event.timing) > 0.3) {\n    if (event.timing.renderTime > 0.2) {\n      return {\n        kind: 'js-explanation-base',\n      };\n    }\n\n    return {\n      kind: 'low-render-count-high-js',\n      data: {\n        renderCount,\n      },\n    };\n  }\n\n  return {\n    kind: 'js-explanation-base',\n  };\n};\n\nconst Explanation = ({ input }: { input: OverviewInput }) => {\n  switch (input.kind) {\n    case 'total-processing': {\n      return (\n        <div\n          className={cn([\n            'text-[#E4E4E7] text-[10px] leading-6 flex flex-col gap-y-2',\n          ])}\n        >\n          <p>\n            This is the time it took to draw the entire frame that was presented\n            to the user. To be at 60FPS, this number needs to be {'<=16ms'}\n          </p>\n\n          <p>\n            To debug the issue, check the \"Ranked\" tab to see if there are\n            significant component renders\n          </p>\n          <p>\n            On a production React build, React Scan can't access the time it\n            took for component to render. To get that information, run React\n            Scan on a development build\n          </p>\n\n          <p>\n            To understand precisely what caused the slowdown while in\n            production, use the <strong>Chrome profiler</strong> and analyze the\n            function call times.\n          </p>\n\n          <p></p>\n        </div>\n      );\n    }\n    case 'render': {\n      return (\n        <div\n          className={cn([\n            'text-[#E4E4E7] text-[10px] leading-6 flex flex-col gap-y-2',\n          ])}\n        >\n          <p>\n            This is the time it took React to run components, and internal logic\n            to handle the output of your component.\n          </p>\n\n          <div className={cn(['flex flex-col'])}>\n            <p>The slowest components for this time period were:</p>\n            {input.data.topByTime.map((item) => (\n              <div key={item.name}>\n                <strong>{item.name}</strong>:{' '}\n                {(item.percentage * 100).toFixed(0)}% of total\n              </div>\n            ))}\n          </div>\n          <p>\n            To view the render times of all your components, and what caused\n            them to render, go to the \"Ranked\" tab\n          </p>\n          <p>The \"Ranked\" tab shows the render times of every component.</p>\n          <p>\n            The render times of the same components are grouped together into\n            one bar.\n          </p>\n          <p>\n            Clicking the component will show you what props, state, or context\n            caused the component to re-render.\n          </p>\n        </div>\n      );\n    }\n    case 'js-explanation-base': {\n      return (\n        <div\n          className={cn([\n            'text-[#E4E4E7] text-[10px] leading-6 flex flex-col gap-y-2',\n          ])}\n        >\n          <p>\n            This is the period when JavaScript hooks and other JavaScript\n            outside of React Renders run.\n          </p>\n          <p>\n            The most common culprit for high JS time is expensive hooks, like\n            expensive callbacks inside of <code>useEffect</code>'s or a large\n            number of useEffect's called, but this can also be JavaScript event\n            handlers (<code>'onclick'</code>, <code>'onchange'</code>) that\n            performed expensive computation.\n          </p>\n          <p>\n            If you have lots of components rendering that call hooks, like\n            useEffect, it can add significant overhead even if the callbacks are\n            not expensive. If this is the case, you can try optimizing the\n            renders of those components to avoid the hook from having to run.\n          </p>\n          <p>\n            You should profile your app using the{' '}\n            <strong>Chrome DevTools profiler</strong> to learn exactly which\n            functions took the longest to execute.\n          </p>\n        </div>\n      );\n    }\n    case 'high-render-count-high-js': {\n      return (\n        <div\n          className={cn([\n            'text-[#E4E4E7] text-[10px] leading-6 flex flex-col gap-y-2',\n          ])}\n        >\n          <p>\n            This is the period when JavaScript hooks and other JavaScript\n            outside of React Renders run.\n          </p>\n          {input.data.renderCount === 0 ? (\n            <>\n              <p>\n                There were no renders, which means nothing related to React\n                caused this slowdown. The most likely cause of the slowdown is a\n                slow JavaScript event handler, or code related to a Web API\n              </p>\n              <p>\n                You should try to reproduce the slowdown while profiling your\n                website with the\n                <strong>Chrome DevTools profiler</strong> to see exactly what\n                functions took the longest to execute.\n              </p>\n            </>\n          ) : (\n            <>\n              {' '}\n              <p>\n                There were <strong>{input.data.renderCount}</strong> renders,\n                which could have contributed to the high JavaScript/Hook time if\n                they ran lots of hooks, like <code>useEffects</code>.\n              </p>\n              <div className={cn(['flex flex-col'])}>\n                <p>You should try optimizing the renders of:</p>\n                {input.data.topByCount.map((item) => (\n                  <div key={item.name}>\n                    - <strong>{item.name}</strong> (rendered {item.count}x)\n                  </div>\n                ))}\n              </div>\n              and then checking if the problem still exists.\n              <p>\n                You can also try profiling your app using the{' '}\n                <strong>Chrome DevTools profiler</strong> to see exactly what\n                functions took the longest to execute.\n              </p>\n            </>\n          )}\n        </div>\n      );\n    }\n    case 'low-render-count-high-js': {\n      return (\n        <div\n          className={cn([\n            'text-[#E4E4E7] text-[10px] leading-6 flex flex-col gap-y-2',\n          ])}\n        >\n          <p>\n            This is the period when JavaScript hooks and other JavaScript\n            outside of React Renders run.\n          </p>\n          <p>\n            There were only <strong>{input.data.renderCount}</strong> renders\n            detected, which means either you had very expensive hooks like{' '}\n            <code>useEffect</code>/<code>useLayoutEffect</code>, or there is\n            other JavaScript running during this interaction that took up the\n            majority of the time.\n          </p>\n          <p>\n            To understand precisely what caused the slowdown, use the{' '}\n            <strong>Chrome profiler</strong> and analyze the function call\n            times.\n          </p>\n        </div>\n      );\n    }\n    case 'high-render-count-update-dom-draw-frame': {\n      return (\n        <div\n          className={cn([\n            'text-[#E4E4E7] text-[10px] leading-6 flex flex-col gap-y-2',\n          ])}\n        >\n          <p>\n            These are the calculations the browser is forced to do in response\n            to the JavaScript that ran during the interaction.\n          </p>\n          <p>\n            This can be caused by CSS updates/CSS recalculations, or new DOM\n            elements/DOM mutations.\n          </p>\n          <p>\n            During this interaction, there were{' '}\n            <strong>{input.data.count}</strong> renders, which was{' '}\n            <strong>{input.data.percentageOfTotal.toFixed(0)}%</strong> of the\n            time spent processing\n          </p>\n          <p>\n            The work performed as a result of the renders may have forced the\n            browser to spend a lot of time to draw the next frame.\n          </p>\n          <p>\n            You can try optimizing the renders to see if the performance problem\n            still exists using the \"Ranked\" tab.\n          </p>\n          <p>\n            If you use an AI-based code editor, you can export the performance\n            data collected as a prompt.\n          </p>\n\n          <p>{input.data.copyButton}</p>\n          <p>\n            Provide this formatted data to the model and ask it to find, or fix,\n            what could be causing this performance problem.\n          </p>\n          <p>For a larger selection of prompts, try the \"Prompts\" tab</p>\n        </div>\n      );\n    }\n    case 'update-dom-draw-frame': {\n      return (\n        <div\n          className={cn([\n            'text-[#E4E4E7] text-[10px] leading-6 flex flex-col gap-y-2',\n          ])}\n        >\n          <p>\n            These are the calculations the browser is forced to do in response\n            to the JavaScript that ran during the interaction.\n          </p>\n          <p>\n            This can be caused by CSS updates/CSS recalculations, or new DOM\n            elements/DOM mutations.\n          </p>\n          <p>\n            If you use an AI-based code editor, you can export the performance\n            data collected as a prompt.\n          </p>\n\n          <p>{input.data.copyButton}</p>\n          <p>\n            Provide this formatted data to the model and ask it to find, or fix,\n            what could be causing this performance problem.\n          </p>\n          <p>For a larger selection of prompts, try the \"Prompts\" tab</p>\n        </div>\n      );\n    }\n    case 'other': {\n      return (\n        <div\n          className={cn([\n            'text-[#E4E4E7] text-[10px] leading-6 flex flex-col gap-y-2',\n          ])}\n        >\n          <p>\n            This is the time it took to run everything other than React renders.\n            This can be hooks like <code>useEffect</code>, other JavaScript not\n            part of React, or work the browser has to do to update the DOM and\n            draw the next frame.\n          </p>\n          <p>\n            To get a better picture of what happened, profile your app using the{' '}\n            <strong>Chrome profiler</strong> when the performance problem\n            arises.\n          </p>\n        </div>\n      );\n    }\n  }\n};\n"
  },
  {
    "path": "packages/scan/src/web/views/notifications/popover.tsx",
    "content": "import {\n  ComponentProps,\n  ReactNode,\n  createPortal,\n  useContext,\n  useEffect,\n  useRef,\n  useState,\n} from 'preact/compat';\nimport { cn } from '~web/utils/helpers';\nimport { ToolbarElementContext } from '~web/widget';\n\ntype PopoverState = 'closed' | 'opening' | 'open' | 'closing';\n\n/**\n *\n * fixme: very hacky and suboptimal popover (api and implementation)\n */\nexport const Popover = ({\n  children,\n  triggerContent,\n  wrapperProps,\n}: {\n  children: ReactNode;\n  triggerContent: ReactNode;\n  wrapperProps?: ComponentProps<'div'>;\n}) => {\n  const [popoverState, setPopoverState] = useState<PopoverState>('closed');\n  const [elBoundingRect, setElBoundingRect] = useState<DOMRect | null>(null);\n  const [viewportSize, setViewportSize] = useState({\n    width: window.innerWidth,\n    height: window.innerHeight,\n  });\n  const triggerRef = useRef<HTMLDivElement | null>(null);\n  const popoverRef = useRef<HTMLDivElement | null>(null);\n  const portalEl = useContext(ToolbarElementContext);\n  const isHoveredRef = useRef(false);\n\n  useEffect(() => {\n    const handleResize = () => {\n      setViewportSize({\n        width: window.innerWidth,\n        height: window.innerHeight,\n      });\n      updateRect();\n    };\n\n    window.addEventListener('resize', handleResize);\n    return () => window.removeEventListener('resize', handleResize);\n  }, []);\n\n  const updateRect = () => {\n    if (triggerRef.current && portalEl) {\n      const triggerRect = triggerRef.current.getBoundingClientRect();\n      const portalRect = portalEl.getBoundingClientRect();\n\n      const centerX = triggerRect.left + triggerRect.width / 2;\n      const centerY = triggerRect.top;\n\n      const rect = new DOMRect(\n        centerX - portalRect.left,\n        centerY - portalRect.top,\n        triggerRect.width,\n        triggerRect.height,\n      );\n      setElBoundingRect(rect);\n    }\n  };\n\n  // oxlint-disable-next-line react-hooks/exhaustive-deps\n  useEffect(() => {\n    updateRect();\n  }, [triggerRef.current]);\n\n  useEffect(() => {\n    if (popoverState === 'opening') {\n      const timer = setTimeout(() => setPopoverState('open'), 120);\n      return () => clearTimeout(timer);\n    } else if (popoverState === 'closing') {\n      const timer = setTimeout(() => setPopoverState('closed'), 120);\n      return () => clearTimeout(timer);\n    }\n  }, [popoverState]);\n\n  // just incase we didn't capture the mouse leave event because the underlying container moved\n  useEffect(() => {\n    const interval = setInterval(() => {\n      if (!isHoveredRef.current && popoverState !== 'closed') {\n        setPopoverState('closing');\n      }\n    }, 1000);\n\n    return () => clearInterval(interval);\n  }, [popoverState]);\n\n  const handleMouseEnter = () => {\n    isHoveredRef.current = true;\n    updateRect();\n    setPopoverState('opening');\n  };\n\n  const handleMouseLeave = () => {\n    isHoveredRef.current = false;\n    updateRect();\n    setPopoverState('closing');\n  };\n\n  const getPopoverPosition = () => {\n    if (!elBoundingRect || !portalEl) return { top: 0, left: 0 };\n\n    const portalRect = portalEl.getBoundingClientRect();\n    const popoverWidth = 175;\n    const popoverHeight = popoverRef.current?.offsetHeight || 40;\n    const safeArea = 5;\n\n    const viewportX = elBoundingRect.x + portalRect.left;\n    const viewportY = elBoundingRect.y + portalRect.top;\n\n    let left = viewportX;\n    let top = viewportY - 4;\n\n    if (left - popoverWidth / 2 < safeArea) {\n      left = safeArea + popoverWidth / 2;\n    } else if (left + popoverWidth / 2 > viewportSize.width - safeArea) {\n      left = viewportSize.width - safeArea - popoverWidth / 2;\n    }\n\n    if (top - popoverHeight < safeArea) {\n      top = viewportY + elBoundingRect.height + 4;\n    }\n\n    return {\n      top: top - portalRect.top,\n      left: left - portalRect.left,\n    };\n  };\n\n  const popoverPosition = getPopoverPosition();\n\n  return (\n    <>\n      {portalEl &&\n        elBoundingRect &&\n        popoverState !== 'closed' &&\n        createPortal(\n          <div\n            ref={popoverRef}\n            className={cn([\n              'absolute z-100 bg-white text-black rounded-lg px-3 py-2 shadow-lg',\n              'transition-[opacity] duration-120 ease-out',\n              'after:content-[\"\"] after:absolute after:top-[100%]',\n              'after:left-1/2 after:-translate-x-1/2',\n              'after:w-[10px] after:h-[6px]',\n              'after:border-l-[5px] after:border-l-transparent',\n              'after:border-r-[5px] after:border-r-transparent',\n              'after:border-t-[6px] after:border-t-white',\n              'pointer-events-none',\n              popoverState === 'opening' || popoverState === 'closing'\n                ? 'opacity-0'\n                : 'opacity-100',\n            ])}\n            style={{\n              top: popoverPosition.top + 'px',\n              left: popoverPosition.left + 'px',\n              transform: `translate(-50%, calc(-100% - 4px)) scale(${popoverState === 'open' ? 1 : 0.97})`,\n              minWidth: '175px',\n              willChange: 'opacity, transform',\n            }}\n          >\n            {children}\n          </div>,\n          portalEl,\n        )}\n\n      <div\n        ref={triggerRef}\n        onMouseEnter={handleMouseEnter}\n        onMouseLeave={handleMouseLeave}\n        {...wrapperProps}\n      >\n        {triggerContent}\n      </div>\n    </>\n  );\n};\n"
  },
  {
    "path": "packages/scan/src/web/views/notifications/render-bar-chart.tsx",
    "content": "import { useRef, useState } from 'preact/hooks';\nimport { getBatchedRectMap } from 'src/new-outlines';\nimport { getIsProduction } from '~core/index';\nimport { iife } from '~core/notifications/performance-utils';\nimport { cn } from '~web/utils/helpers';\nimport {\n  GroupedFiberRender,\n  NotificationEvent,\n  getTotalTime,\n  isRenderMemoizable,\n  useNotificationsContext,\n} from './data';\nimport {\n  HighlightStore,\n  drawHighlights,\n} from '~core/notifications/outline-overlay';\nimport { ChevronRight } from './icons';\n\n// todo: cleanup, convoluted ternaries\nexport const fadeOutHighlights = () => {\n  const curr = HighlightStore.value.current\n    ? HighlightStore.value.current\n    : HighlightStore.value.kind === 'transition'\n      ? HighlightStore.value.transitionTo\n      : null;\n  if (!curr) {\n    return;\n  }\n\n  if (HighlightStore.value.kind === 'transition') {\n    HighlightStore.value = {\n      kind: 'move-out',\n      // because we want to dynamically fade this value\n      current:\n        HighlightStore.value.current?.alpha === 0\n          ? // we want to only start fading from transition if current is done animating out\n            HighlightStore.value.transitionTo\n          : // if current doesn't exist then transition must exist\n            (HighlightStore.value.current ?? HighlightStore.value.transitionTo),\n    };\n    return;\n  }\n\n  HighlightStore.value = {\n    kind: 'move-out',\n    current: {\n      alpha: 0,\n      ...curr,\n    },\n  };\n};\n\ntype Bars = Array<\n  | { kind: 'other-frame-drop'; totalTime: number }\n  | { kind: 'other-not-javascript'; totalTime: number }\n  | { kind: 'other-javascript'; totalTime: number }\n  | { kind: 'render'; event: GroupedFiberRender; totalTime: number }\n>;\n\nexport const NO_PURGE = ['hover:bg-[#0f0f0f]'];\n\nexport const RenderBarChart = ({\n  selectedEvent,\n}: { selectedEvent: NotificationEvent }) => {\n  const totalInteractionTime = getTotalTime(selectedEvent.timing);\n  const nonRender = totalInteractionTime - selectedEvent.timing.renderTime;\n  const [isProduction] = useState(getIsProduction());\n  const events = selectedEvent.groupedFiberRenders;\n  const bars: Bars = events.map((event) => ({\n    event,\n    kind: 'render',\n    totalTime: isProduction ? event.count : event.totalTime,\n  }));\n\n  const isShowingExtraInfo = iife(() => {\n    switch (selectedEvent.kind) {\n      case 'dropped-frames': {\n        return selectedEvent.timing.renderTime / totalInteractionTime < 0.1;\n      }\n      case 'interaction': {\n        return (\n          (selectedEvent.timing.otherJSTime + selectedEvent.timing.renderTime) /\n            totalInteractionTime <\n          0.2\n        );\n      }\n    }\n  });\n  /**\n   * We don't add the extra bars in production because we can't compare them to the renders, so the bar is useless, user can use overview tab to see times\n   */\n  if (selectedEvent.kind === 'interaction' && !isProduction) {\n    bars.push({\n      kind: 'other-javascript',\n      totalTime: selectedEvent.timing.otherJSTime,\n    });\n  }\n\n  if (isShowingExtraInfo && !isProduction) {\n    if (selectedEvent.kind === 'interaction') {\n      bars.push({\n        kind: 'other-not-javascript',\n        totalTime:\n          getTotalTime(selectedEvent.timing) -\n          selectedEvent.timing.renderTime -\n          selectedEvent.timing.otherJSTime,\n      });\n    } else {\n      bars.push({\n        kind: 'other-frame-drop',\n        totalTime: nonRender,\n      });\n    }\n  }\n\n  const debouncedMouseEnter = useRef<{\n    timer: ReturnType<typeof setTimeout> | null;\n    lastCallAt: number | null;\n  }>({\n    lastCallAt: null,\n    timer: null,\n  });\n\n  const totalBarTime = bars.reduce((prev, curr) => prev + curr.totalTime, 0);\n\n  return (\n    <div className={cn(['flex flex-col h-full w-full gap-y-1'])}>\n      {iife(() => {\n        if (isProduction && bars.length === 0) {\n          return (\n            <div className=\"flex flex-col items-center justify-center h-full text-zinc-400\">\n              <p className=\"text-sm w-full text-left text-white mb-1.5\">\n                No data available\n              </p>\n              <p className=\"text-x w-full text-lefts\">\n                No data was collected during this period\n              </p>\n            </div>\n          );\n        }\n        if (bars.length === 0) {\n          return (\n            <div className=\"flex flex-col items-center justify-center h-full text-zinc-400\">\n              <p className=\"text-sm w-full text-left text-white mb-1.5\">\n                No renders collected\n              </p>\n              <p className=\"text-x w-full text-lefts\">\n                There were no renders during this period\n              </p>\n            </div>\n          );\n        }\n      })}\n\n      {bars\n        .toSorted((a, b) => b.totalTime - a.totalTime)\n        .map((bar) => (\n          <RenderBar\n            key={bar.kind === 'render' ? bar.event.id : bar.kind}\n            bars={bars}\n            bar={bar}\n            debouncedMouseEnter={debouncedMouseEnter}\n            totalBarTime={totalBarTime}\n            isProduction={isProduction}\n          />\n        ))}\n    </div>\n  );\n};\n\nconst getTransitionState = (state: {\n  current: { alpha: number } | null;\n  transitionTo: { alpha: number };\n}) => {\n  if (!state.current) {\n    return 'fading-in';\n  }\n  if (state.current.alpha > 0) {\n    return 'fading-out' as const;\n  }\n  return 'fading-in' as const;\n};\n\nconst RenderBar = ({\n  bar,\n  debouncedMouseEnter,\n  totalBarTime,\n  isProduction,\n  bars,\n  depth = 0,\n}: {\n  depth?: number;\n  bars: Bars;\n  bar: Bars[number];\n  debouncedMouseEnter: {\n    current: {\n      timer: ReturnType<typeof setTimeout> | null;\n      lastCallAt: number | null;\n    };\n  };\n  totalBarTime: number;\n  isProduction: boolean | null;\n}) => {\n  const { setNotificationState, setRoute } = useNotificationsContext();\n  const [isExpanded, setIsExpanded] = useState(false);\n\n  const isLeaf = bar.kind === 'render' ? bar.event.parents.size === 0 : true;\n\n  const parentBars = bars.filter((otherBar) =>\n    otherBar.kind === 'render' && bar.kind === 'render'\n      ? bar.event.parents.has(otherBar.event.name) &&\n        otherBar.event.name !== bar.event.name\n      : false,\n  );\n\n  const missingParentNames =\n    bar.kind === 'render'\n      ? Array.from(bar.event.parents).filter(\n          (parentName) =>\n            !bars.some(\n              (b) => b.kind === 'render' && b.event.name === parentName,\n            ),\n        )\n      : [];\n\n  const handleBarClick = () => {\n    if (bar.kind === 'render') {\n      setNotificationState((prev) => ({\n        ...prev,\n        selectedFiber: bar.event,\n      }));\n\n      setRoute({\n        route: 'render-explanation',\n        routeMessage: null,\n      });\n    } else {\n      setRoute({\n        route: 'other-visualization',\n        routeMessage: {\n          kind: 'auto-open-overview-accordion',\n          name: bar.kind,\n        },\n      });\n    }\n  };\n\n  return (\n    <div className=\"w-full\">\n      <div\n        className={cn(['w-full flex items-center relative text-xs min-w-0'])}\n      >\n        <button\n          onMouseLeave={() => {\n            debouncedMouseEnter.current.timer &&\n              clearTimeout(debouncedMouseEnter.current.timer);\n            fadeOutHighlights();\n          }}\n          onMouseEnter={async () => {\n            const highlightBars = async () => {\n              debouncedMouseEnter.current.lastCallAt = Date.now();\n              if (bar.kind !== 'render') {\n                const curr = HighlightStore.value.current\n                  ? HighlightStore.value.current\n                  : HighlightStore.value.kind === 'transition'\n                    ? HighlightStore.value.transitionTo\n                    : null;\n\n                if (!curr) {\n                  HighlightStore.value = {\n                    kind: 'idle',\n                    current: null,\n                  };\n                  return;\n                }\n                HighlightStore.value = {\n                  kind: 'move-out',\n                  current: {\n                    alpha: 0,\n                    ...curr,\n                  },\n                };\n                return;\n              }\n              const state = HighlightStore.value;\n              const currentState = iife(() => {\n                switch (state.kind) {\n                  case 'transition': {\n                    return state.transitionTo;\n                  }\n                  case 'idle':\n                  case 'move-out': {\n                    return state.current;\n                  }\n                }\n              });\n              const stateRects: Array<DOMRect> = [];\n\n              if (state.kind === 'transition') {\n                const transitionState = getTransitionState(state);\n                iife(() => {\n                  switch (transitionState) {\n                    case 'fading-in': {\n                      HighlightStore.value = {\n                        kind: 'transition',\n                        current: state.transitionTo,\n                        transitionTo: {\n                          rects: stateRects,\n                          alpha: 0,\n                          name: bar.event.name,\n                        },\n                      };\n                      return;\n                    }\n                    case 'fading-out': {\n                      HighlightStore.value = {\n                        kind: 'transition',\n                        current: HighlightStore.value.current\n                          ? {\n                              alpha: 0,\n                              ...HighlightStore.value.current,\n                            }\n                          : null,\n                        transitionTo: {\n                          rects: stateRects,\n                          alpha: 0,\n                          name: bar.event.name,\n                        },\n                      };\n                      return;\n                    }\n                  }\n                });\n              } else {\n                HighlightStore.value = {\n                  kind: 'transition',\n                  transitionTo: {\n                    rects: stateRects,\n                    alpha: 0,\n                    name: bar.event.name,\n                  },\n                  current: currentState\n                    ? {\n                        alpha: 0,\n                        ...currentState,\n                      }\n                    : null,\n                };\n              }\n\n              const trueElements = bar.event.elements.filter(\n                (element) => element instanceof Element,\n              );\n\n              for await (const entries of getBatchedRectMap(trueElements)) {\n                entries.forEach(({ boundingClientRect }) => {\n                  stateRects.push(boundingClientRect);\n                });\n                drawHighlights();\n              }\n            };\n\n            if (\n              debouncedMouseEnter.current.lastCallAt &&\n              Date.now() - debouncedMouseEnter.current.lastCallAt < 200\n            ) {\n              debouncedMouseEnter.current.timer &&\n                clearTimeout(debouncedMouseEnter.current.timer);\n              debouncedMouseEnter.current.timer = setTimeout(() => {\n                highlightBars();\n              }, 200);\n              return;\n            }\n\n            highlightBars();\n          }}\n          onClick={handleBarClick}\n          className={cn([\n            'h-full w-[90%] flex items-center hover:bg-[#0f0f0f] rounded-l-md min-w-0 relative',\n          ])}\n        >\n          <div\n            style={{\n              minWidth: 'fit-content',\n              width: `${(bar.totalTime / totalBarTime) * 100}%`,\n            }}\n            className={cn([\n              'flex items-center rounded-sm text-white text-xs h-[28px] shrink-0',\n              bar.kind === 'render' && 'bg-[#412162] group-hover:bg-[#5b2d89]',\n              bar.kind === 'other-frame-drop' &&\n                'bg-[#44444a] group-hover:bg-[#6a6a6a]',\n              bar.kind === 'other-javascript' &&\n                'bg-[#efd81a6b] group-hover:bg-[#efda1a2f]',\n              bar.kind === 'other-not-javascript' &&\n                'bg-[#214379d4] group-hover:bg-[#21437982]',\n            ])}\n          />\n          <div\n            className={cn([\n              'absolute inset-0 flex items-center px-2',\n              'min-w-0',\n            ])}\n          >\n            <div className=\"flex items-center gap-x-2 min-w-0 w-full\">\n              <span className={cn(['truncate'])}>\n                {iife(() => {\n                  switch (bar.kind) {\n                    case 'other-frame-drop': {\n                      return 'JavaScript, DOM updates, Draw Frame';\n                    }\n                    case 'other-javascript': {\n                      return 'JavaScript/React Hooks';\n                    }\n                    case 'other-not-javascript': {\n                      return 'Update DOM and Draw New Frame';\n                    }\n                    case 'render': {\n                      return bar.event.name;\n                    }\n                  }\n                })}\n              </span>\n              {bar.kind === 'render' && isRenderMemoizable(bar.event) && (\n                <div\n                  style={{\n                    lineHeight: '10px',\n                  }}\n                  className={cn([\n                    'px-1 py-0.5 bg-[#6a369e] flex items-center rounded-sm font-semibold text-[8px] shrink-0',\n                  ])}\n                >\n                  Memoizable\n                </div>\n              )}\n            </div>\n          </div>\n        </button>\n\n        <button\n          onClick={() =>\n            bar.kind === 'render' && !isLeaf && setIsExpanded(!isExpanded)\n          }\n          className={cn([\n            'flex items-center min-w-fit shrink-0 rounded-r-md h-[28px]',\n            !isLeaf && 'hover:bg-[#0f0f0f]',\n            bar.kind === 'render' && !isLeaf\n              ? 'cursor-pointer'\n              : 'cursor-default',\n          ])}\n        >\n          <div className=\"w-[20px] flex items-center justify-center\">\n            {bar.kind === 'render' && !isLeaf && (\n              <ChevronRight\n                className={cn(\n                  'transition-transform',\n                  isExpanded && 'rotate-90',\n                )}\n                size={16}\n              />\n            )}\n          </div>\n\n          <div\n            style={{\n              minWidth: isLeaf ? 'fit-content' : isProduction ? '30px' : '60px',\n            }}\n            className=\"flex items-center justify-end gap-x-1\"\n          >\n            {bar.kind === 'render' && (\n              <span className={cn(['text-[10px]'])}>x{bar.event.count}</span>\n            )}\n\n            {(bar.kind !== 'render' || !isProduction) && (\n              <span className=\"text-[10px] text-[#7346a0] pr-1\">\n                {bar.totalTime < 1 ? '<1' : bar.totalTime.toFixed(0)}\n                ms\n              </span>\n            )}\n          </div>\n        </button>\n\n        {depth === 0 && (\n          <div\n            className={cn([\n              'absolute right-0 top-1/2 transition-none -translate-y-1/2 bg-white text-black px-2 py-1 rounded text-xs opacity-0 group-hover:opacity-100 transition-opacity mr-16',\n              'pointer-events-none',\n            ])}\n          >\n            Click to learn more\n          </div>\n        )}\n      </div>\n\n      {isExpanded &&\n        (parentBars.length > 0 || missingParentNames.length > 0) && (\n          <div className=\"pl-3 flex flex-col gap-y-1 mt-1\">\n            {parentBars\n              .toSorted((a, b) => b.totalTime - a.totalTime)\n              .map((parentBar, i) => (\n                <RenderBar\n                  depth={depth + 1}\n                  key={i}\n                  bar={parentBar}\n                  debouncedMouseEnter={debouncedMouseEnter}\n                  totalBarTime={totalBarTime}\n                  isProduction={isProduction}\n                  bars={bars}\n                />\n              ))}\n            {missingParentNames.map((parentName) => (\n              <div key={parentName} className=\"w-full\">\n                <div className=\"w-full flex items-center relative text-xs\">\n                  <div className=\"h-full w-full flex items-center relative\">\n                    <div className=\"flex items-center rounded-sm text-white text-xs h-[28px] w-full\" />\n                    <div className=\"absolute inset-0 flex items-center px-2\">\n                      <span className=\"truncate whitespace-nowrap text-white/70 w-full\">\n                        {parentName}\n                      </span>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            ))}\n          </div>\n        )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/scan/src/web/views/notifications/render-explanation.tsx",
    "content": "import { cn } from '~web/utils/helpers';\nimport { NotificationEvent, useNotificationsContext } from './data';\nimport { useLayoutEffect, useState } from 'preact/hooks';\nimport { ArrowLeft, CloseIcon } from './icons';\nimport { getIsProduction } from '~core/index';\n\nexport const RenderExplanation = ({\n  selectedEvent: _,\n  selectedFiber,\n}: {\n  selectedFiber: NotificationEvent['groupedFiberRenders'][number];\n  selectedEvent: NotificationEvent;\n}) => {\n  const { setRoute } = useNotificationsContext();\n  const [tipisShown, setTipIsShown] = useState(true);\n  const [isProduction] = useState(getIsProduction());\n\n  useLayoutEffect(() => {\n    const res = localStorage.getItem('react-scan-tip-shown');\n    const asBool = res === 'true' ? true : res === 'false' ? false : null;\n    if (asBool === null) {\n      setTipIsShown(true);\n      localStorage.setItem('react-scan-tip-is-shown', 'true');\n      return;\n    }\n    if (!asBool) {\n      setTipIsShown(false);\n    }\n  }, []);\n  const isMemoizable =\n    selectedFiber.changes.context.length === 0 &&\n    selectedFiber.changes.props.length === 0 &&\n    selectedFiber.changes.state.length === 0;\n  return (\n    <div\n      className={cn([\n        'w-full min-h-fit h-full flex flex-col py-4 pt-0 rounded-sm',\n      ])}\n    >\n      <div className={cn(['flex items-start gap-x-4 '])}>\n        <button\n          onClick={() => {\n            setRoute({\n              route: 'render-visualization',\n              routeMessage: null,\n            });\n          }}\n          className={cn([\n            'text-white hover:bg-[#34343b] flex gap-x-1 justify-center items-center mb-4 w-fit px-2.5 py-1.5 text-xs rounded-sm bg-[#18181B]',\n          ])}\n        >\n          <ArrowLeft size={14} /> <span>Overview</span>\n        </button>\n        <div className={cn(['flex flex-col gap-y-1'])}>\n          <div\n            className={cn(['text-sm font-bold text-white overflow-x-hidden'])}\n          >\n            <div className=\"flex items-center gap-x-2 truncate\">\n              {selectedFiber.name}\n            </div>\n          </div>\n          <div className={cn(['flex gap-x-2'])}>\n            {!isProduction && (\n              <>\n                <div className={cn(['text-xs text-gray-400'])}>\n                  • Render time: {selectedFiber.totalTime.toFixed(0)}ms\n                </div>\n              </>\n            )}\n            <div className={cn(['text-xs text-gray-400 mb-4'])}>\n              • Renders: {selectedFiber.count}x\n            </div>\n          </div>\n        </div>\n      </div>\n      {tipisShown && !isMemoizable && (\n        <div\n          className={cn([\n            'w-full mb-4 bg-[#0A0A0A] border border-[#27272A] rounded-sm overflow-hidden flex relative',\n          ])}\n        >\n          <button\n            onClick={() => {\n              setTipIsShown(false);\n\n              localStorage.setItem('react-scan-tip-shown', 'false');\n            }}\n            className={cn([\n              'absolute right-2 top-2 rounded-sm p-1 hover:bg-[#18181B]',\n            ])}\n          >\n            <CloseIcon size={12} />\n          </button>\n          <div className={cn(['w-1 bg-[#d36cff]'])} />\n          <div className={cn(['flex-1'])}>\n            <div\n              className={cn(['px-3 py-2 text-gray-100 text-xs font-semibold'])}\n            >\n              How to stop renders\n            </div>\n            <div className={cn(['px-3 pb-2 text-gray-400 text-[10px]'])}>\n              Stop the following props, state and context from changing between\n              renders, and wrap the component in React.memo if not already\n            </div>\n          </div>\n        </div>\n      )}\n\n      {isMemoizable && (\n        <div\n          className={cn([\n            'w-full mb-4 bg-[#0A0A0A] border border-[#27272A] rounded-sm overflow-hidden flex',\n          ])}\n        >\n          <div className={cn(['w-1 bg-[#d36cff]'])} />\n          <div className={cn(['flex-1'])}>\n            <div\n              className={cn(['px-3 py-2 text-gray-100 text-sm font-semibold'])}\n            >\n              No changes detected\n            </div>\n            <div className={cn(['px-3 pb-2 text-gray-400 text-xs'])}>\n              This component would not have rendered if it was memoized\n            </div>\n          </div>\n        </div>\n      )}\n      <div className={cn(['flex w-full'])}>\n        <div\n          className={cn([\n            'flex flex-col border border-[#27272A] rounded-l-sm overflow-hidden w-1/3',\n          ])}\n        >\n          <div\n            className={cn([\n              'text-[14px] font-semibold px-2 py-2 bg-[#18181B] text-white flex justify-center',\n            ])}\n          >\n            Changed Props\n          </div>\n          {selectedFiber.changes.props.length > 0 ? (\n            selectedFiber.changes.props\n              .toSorted((a, b) => b.count - a.count)\n              .map((change) => (\n                <div\n                  key={change.name}\n                  className={cn([\n                    'flex flex-col justify-between items-center border-t overflow-x-auto border-[#27272A] px-1 py-1 text-wrap bg-[#0A0A0A] text-[10px]',\n                  ])}\n                >\n                  <span className={cn(['text-white '])}>{change.name}</span>\n                  <div\n                    className={cn([' text-[8px]  text-[#d36cff] pl-1 py-1 '])}\n                  >\n                    {change.count}/{selectedFiber.count}x\n                  </div>\n                </div>\n              ))\n          ) : (\n            <div\n              className={cn([\n                'flex items-center justify-center h-full bg-[#0A0A0A] text-[#A1A1AA] border-t border-[#27272A]',\n              ])}\n            >\n              No changes\n            </div>\n          )}\n        </div>\n        <div\n          className={cn([\n            'flex flex-col border border-[#27272A] border-l-0 overflow-hidden w-1/3',\n          ])}\n        >\n          <div\n            className={cn([\n              ' text-[14px] font-semibold px-2 py-2 bg-[#18181B] text-white flex justify-center',\n            ])}\n          >\n            Changed State\n          </div>\n          {selectedFiber.changes.state.length > 0 ? (\n            selectedFiber.changes.state\n              .toSorted((a, b) => b.count - a.count)\n              .map((change) => (\n                <div\n                  key={change.index}\n                  className={cn([\n                    'flex flex-col justify-between items-center border-t overflow-x-auto border-[#27272A] px-1 py-1 text-wrap bg-[#0A0A0A] text-[10px]',\n                  ])}\n                >\n                  <span className={cn(['text-white '])}>\n                    index {change.index}\n                  </span>\n                  <div\n                    className={cn([\n                      'rounded-full  text-[#d36cff] pl-1 py-1 text-[8px]',\n                    ])}\n                  >\n                    {change.count}/{selectedFiber.count}x\n                  </div>\n                </div>\n              ))\n          ) : (\n            <div\n              className={cn([\n                'flex items-center justify-center h-full bg-[#0A0A0A] text-[#A1A1AA] border-t border-[#27272A]',\n              ])}\n            >\n              No changes\n            </div>\n          )}\n        </div>\n        <div\n          className={cn([\n            'flex flex-col border border-[#27272A] border-l-0 rounded-r-sm overflow-hidden w-1/3',\n          ])}\n        >\n          <div\n            className={cn([\n              ' text-[14px] font-semibold px-2 py-2 bg-[#18181B] text-white flex justify-center',\n            ])}\n          >\n            Changed Context\n          </div>\n          {selectedFiber.changes.context.length > 0 ? (\n            selectedFiber.changes.context\n\n              .toSorted((a, b) => b.count - a.count)\n              .map((change) => (\n                <div\n                  key={change.name}\n                  className={cn([\n                    'flex flex-col justify-between items-center border-t  border-[#27272A] px-1 py-1 bg-[#0A0A0A] text-[10px] overflow-x-auto',\n                  ])}\n                >\n                  <span className={cn(['text-white '])}>{change.name}</span>\n                  <div\n                    className={cn([\n                      'rounded-full text-[#d36cff] pl-1 py-1 text-[8px] text-wrap',\n                    ])}\n                  >\n                    {change.count}/{selectedFiber.count}x\n                  </div>\n                </div>\n              ))\n          ) : (\n            <div\n              className={cn([\n                'flex items-center justify-center h-full bg-[#0A0A0A] text-[#A1A1AA] border-t border-[#27272A] py-2',\n              ])}\n            >\n              No changes\n            </div>\n          )}\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/scan/src/web/views/notifications/slowdown-history.tsx",
    "content": "import { useEffect, useRef, useState } from 'preact/compat';\nimport { cn } from '~web/utils/helpers';\nimport {\n  InteractionEvent,\n  NotificationEvent,\n  getComponentName,\n  getEventSeverity,\n  getTotalTime,\n  useNotificationsContext,\n} from './data';\nimport {\n  ClearIcon,\n  KeyboardIcon,\n  PointerIcon,\n  TrendingDownIcon,\n} from './icons';\nimport { Popover } from './popover';\nimport { iife } from '~core/notifications/performance-utils';\nimport { toolbarEventStore } from '~core/notifications/event-tracking';\nimport { CollapsedDroppedFrame, CollapsedItem } from './collapsed-event';\n\nconst useFlashManager = (events: NotificationEvent[]) => {\n  const prevEventsRef = useRef<NotificationEvent[]>([]);\n  const [newEventIds, setNewEventIds] = useState<Set<string>>(new Set());\n  const isInitialMount = useRef(true);\n\n  useEffect(() => {\n    if (isInitialMount.current) {\n      isInitialMount.current = false;\n      prevEventsRef.current = events;\n      return;\n    }\n\n    const currentIds = new Set(events.map((e) => e.id));\n    const prevIds = new Set(prevEventsRef.current.map((e) => e.id));\n\n    const newIds = new Set<string>();\n    currentIds.forEach((id) => {\n      if (!prevIds.has(id)) {\n        newIds.add(id);\n      }\n    });\n\n    if (newIds.size > 0) {\n      setNewEventIds(newIds);\n      setTimeout(() => {\n        setNewEventIds(new Set());\n      }, 2000);\n    }\n\n    prevEventsRef.current = events;\n  }, [events]);\n\n  return (id: string) => newEventIds.has(id);\n};\n\nconst useFlash = ({ shouldFlash }: { shouldFlash: boolean }) => {\n  const [isFlashing, setIsFlashing] = useState(shouldFlash);\n  useEffect(() => {\n    if (shouldFlash) {\n      setIsFlashing(true);\n      const timer = setTimeout(() => {\n        setIsFlashing(false);\n      }, 1000);\n      return () => clearTimeout(timer);\n    }\n  }, [shouldFlash]);\n\n  return isFlashing;\n};\n\nexport const SlowdownHistoryItem = ({\n  event,\n  shouldFlash,\n}: {\n  event: NotificationEvent;\n  shouldFlash: boolean;\n}) => {\n  const { notificationState, setNotificationState } = useNotificationsContext();\n\n  const severity = getEventSeverity(event);\n\n  const isFlashing = useFlash({ shouldFlash });\n\n  switch (event.kind) {\n    case 'interaction': {\n      return (\n        <button\n          onClick={() => {\n            setNotificationState((prev) => ({\n              ...prev,\n              selectedEvent: event,\n              route: 'render-visualization',\n              selectedFiber: null,\n            }));\n          }}\n          className={cn([\n            'pl-2 py-1.5  text-sm flex w-full items-center rounded-sm hover:bg-[#18181B] relative overflow-hidden',\n            event.id === notificationState.selectedEvent?.id && 'bg-[#18181B]',\n            isFlashing &&\n              'after:absolute after:inset-0 after:bg-purple-500/30 after:animate-[fadeOut_1s_ease-out_forwards]',\n          ])}\n        >\n          <div\n            className={cn([\n              'w-4/5 flex items-center justify-start h-full gap-x-1.5',\n            ])}\n          >\n            <span className={cn(['min-w-fit text-xs'])}>\n              {iife(() => {\n                switch (event.type) {\n                  case 'click': {\n                    return <PointerIcon size={14} />;\n                  }\n                  case 'keyboard': {\n                    return <KeyboardIcon size={14} />;\n                  }\n                }\n              })}\n            </span>\n\n            <span className={cn(['text-xs pr-1 truncate'])}>\n              {getComponentName(event.componentPath)}\n            </span>\n          </div>\n          <div\n            className={cn([' min-w-fit flex justify-end items-center ml-auto'])}\n          >\n            <div\n              style={{\n                lineHeight: '10px',\n              }}\n              className={cn([\n                'gap-x-0.5 w-fit flex items-end justify-center h-full text-white px-1 py-1 rounded-sm font-semibold text-[10px]',\n                severity === 'low' && 'bg-green-500/50',\n                severity === 'needs-improvement' && 'bg-[#b77116] text-[10px]',\n                severity === 'high' && 'bg-[#b94040]',\n              ])}\n            >\n              <div\n                style={{\n                  lineHeight: '10px',\n                }}\n                className={cn(['text-[10px] text-white flex items-end'])}\n              >\n                {getTotalTime(event.timing).toFixed(0)}ms\n              </div>\n            </div>\n          </div>\n        </button>\n      );\n    }\n    case 'dropped-frames': {\n      return (\n        <button\n          onClick={() => {\n            setNotificationState((prev) => ({\n              ...prev,\n              selectedEvent: event,\n              // explicitly force back to render-visualization since the user might get confused when they don't see the detailed view immediately when clicking the view\n              route: 'render-visualization',\n              selectedFiber: null,\n            }));\n          }}\n          className={cn([\n            'pl-2 py-1.5  w-full text-sm flex items-center rounded-sm hover:bg-[#18181B] relative overflow-hidden',\n            event.id === notificationState.selectedEvent?.id && 'bg-[#18181B]',\n            isFlashing &&\n              'after:absolute after:inset-0 after:bg-purple-500/30 after:animate-[fadeOut_1s_ease-out_forwards]',\n          ])}\n        >\n          <div\n            className={cn([\n              'w-4/5 flex items-center justify-start h-full text-xs truncate',\n            ])}\n          >\n            <TrendingDownIcon size={14} className=\"mr-1.5\" /> FPS Drop\n          </div>\n          <div\n            className={cn([' min-w-fit flex justify-end items-center ml-auto'])}\n          >\n            <div\n              style={{\n                lineHeight: '10px',\n              }}\n              className={cn([\n                'w-fit flex items-center justify-center h-full text-white px-1 py-1 rounded-sm text-[10px] font-bold',\n                severity === 'low' && 'bg-green-500/60',\n                severity === 'needs-improvement' && 'bg-[#b77116] text-[10px]',\n                severity === 'high' && 'bg-[#b94040]',\n              ])}\n            >\n              {event.fps} FPS\n            </div>\n          </div>\n        </button>\n      );\n    }\n  }\n};\n\ntype CollapsedKeyboardInput = {\n  kind: 'collapsed-keyboard';\n  events: Array<InteractionEvent>;\n  timestamp: number;\n};\n\ntype HistoryEvent =\n  | {\n      kind: 'single';\n      event: NotificationEvent;\n      timestamp: number;\n    }\n  | CollapsedKeyboardInput\n  | CollapsedDroppedFrame;\n\nconst collapseEvents = (events: Array<NotificationEvent>) => {\n  const newEvents = events.reduce<Array<HistoryEvent>>((prev, curr) => {\n    const lastEvent = prev.at(-1);\n    if (!lastEvent) {\n      return [\n        {\n          kind: 'single',\n          event: curr,\n          timestamp: curr.timestamp,\n        },\n      ];\n    }\n\n    switch (lastEvent.kind) {\n      case 'collapsed-keyboard': {\n        if (\n          curr.kind === 'interaction' &&\n          curr.type === 'keyboard' &&\n          // must be on the same semantic component, it would be ideal to compare on fiberId, but i digress\n          curr.componentPath.join('-') ===\n            lastEvent.events[0].componentPath.join('-')\n        ) {\n          const eventsWithoutLast = prev.filter((e) => e !== lastEvent);\n\n          return [\n            ...eventsWithoutLast,\n            {\n              kind: 'collapsed-keyboard',\n              events: [...lastEvent.events, curr],\n              timestamp: Math.max(\n                ...[...lastEvent.events, curr].map((e) => e.timestamp),\n              ),\n            },\n          ];\n        }\n\n        return [\n          ...prev,\n          {\n            kind: 'single',\n            event: curr,\n            timestamp: curr.timestamp,\n          },\n        ];\n      }\n      case 'single': {\n        // if its a keyboard input on the same element\n        if (\n          lastEvent.event.kind === 'interaction' &&\n          lastEvent.event.type === 'keyboard' &&\n          curr.kind === 'interaction' &&\n          curr.type === 'keyboard' &&\n          lastEvent.event.componentPath.join('-') ===\n            curr.componentPath.join('-')\n        ) {\n          const eventsWithoutLast = prev.filter((e) => e !== lastEvent);\n          return [\n            ...eventsWithoutLast,\n            {\n              kind: 'collapsed-keyboard',\n              events: [lastEvent.event, curr],\n              timestamp: Math.max(lastEvent.event.timestamp, curr.timestamp),\n            },\n          ];\n        }\n        if (\n          lastEvent.event.kind === 'dropped-frames' &&\n          curr.kind === 'dropped-frames'\n        ) {\n          const eventsWithoutLast = prev.filter((e) => e !== lastEvent);\n\n          return [\n            ...eventsWithoutLast,\n            {\n              kind: 'collapsed-frame-drops',\n              events: [lastEvent.event, curr],\n              timestamp: Math.max(lastEvent.event.timestamp, curr.timestamp),\n            },\n          ];\n        }\n        return [\n          ...prev,\n          {\n            kind: 'single',\n            event: curr,\n            timestamp: curr.timestamp,\n          },\n        ];\n      }\n      case 'collapsed-frame-drops': {\n        if (curr.kind === 'dropped-frames') {\n          const eventsWithoutLast = prev.filter((e) => e !== lastEvent);\n          return [\n            ...eventsWithoutLast,\n            {\n              kind: 'collapsed-frame-drops',\n              events: [...lastEvent.events, curr],\n              timestamp: Math.max(\n                ...[...lastEvent.events, curr].map((e) => e.timestamp),\n              ),\n            },\n          ];\n        }\n        return [\n          ...prev,\n          {\n            kind: 'single',\n            event: curr,\n            timestamp: curr.timestamp,\n          },\n        ];\n      }\n    }\n  }, []);\n  return newEvents;\n};\n\nexport const useLaggedEvents = (lagMs = 150) => {\n  const { notificationState } = useNotificationsContext();\n  const [laggedEvents, setLaggedEvents] = useState(notificationState.events);\n\n  // oxlint-disable-next-line react-hooks/exhaustive-deps\n  useEffect(() => {\n    setTimeout(() => {\n      setLaggedEvents(notificationState.events);\n    }, lagMs);\n  }, [notificationState.events]);\n  return [laggedEvents, setLaggedEvents] as const;\n};\n\nexport const SlowdownHistory = () => {\n  const { notificationState, setNotificationState } = useNotificationsContext();\n  const shouldFlash = useFlashManager(notificationState.events);\n  const [laggedEvents, setLaggedEvents] = useLaggedEvents();\n  // this is to avoid a flicker from our overlapping events deduping logic. This should be handled downstream, but this simplifies logic for now\n  const collapsedEvents = collapseEvents(laggedEvents).toSorted(\n    (a, b) => b.timestamp - a.timestamp,\n  );\n\n  return (\n    <div\n      className={cn([\n        `w-full h-full gap-y-2 flex flex-col border-r border-[#27272A] overflow-y-auto`,\n      ])}\n    >\n      <div\n        className={cn([\n          'text-sm text-[#65656D] pl-3 pr-1 w-full flex items-center justify-between',\n        ])}\n      >\n        <span>History</span>\n        <Popover\n          wrapperProps={{\n            className: 'h-full flex items-center justify-center ml-auto',\n          }}\n          triggerContent={\n            <button\n              className={cn(['hover:bg-[#18181B] rounded-full p-2'])}\n              title=\"Clear all events\"\n              onClick={() => {\n                toolbarEventStore.getState().actions.clear();\n                setNotificationState((prev) => ({\n                  ...prev,\n                  selectedEvent: null,\n                  selectedFiber: null,\n                  route:\n                    prev.route === 'other-visualization'\n                      ? 'other-visualization'\n                      : 'render-visualization',\n                }));\n                setLaggedEvents([]);\n              }}\n            >\n              <ClearIcon className={cn([''])} size={16} />\n            </button>\n          }\n        >\n          <div className={cn(['w-full flex justify-center'])}>\n            Clear all events\n          </div>\n        </Popover>\n      </div>\n      <div className={cn(['flex flex-col px-1 gap-y-1'])}>\n        {collapsedEvents.length === 0 && (\n          <div\n            className={cn([\n              'flex items-center justify-center text-zinc-500 text-sm py-4',\n            ])}\n          >\n            No Events\n          </div>\n        )}\n        {collapsedEvents.map((historyItem) =>\n          iife(() => {\n            switch (historyItem.kind) {\n              case 'collapsed-keyboard': {\n                return (\n                  <CollapsedItem shouldFlash={shouldFlash} item={historyItem} />\n                );\n              }\n              case 'single': {\n                return (\n                  <SlowdownHistoryItem\n                    key={historyItem.event.id}\n                    event={historyItem.event}\n                    shouldFlash={shouldFlash(historyItem.event.id)}\n                  />\n                );\n              }\n              case 'collapsed-frame-drops': {\n                return (\n                  <CollapsedItem shouldFlash={shouldFlash} item={historyItem} />\n                );\n              }\n            }\n          }),\n        )}\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/scan/src/web/views/settings/header.tsx",
    "content": "import { signalIsSettingsOpen } from '~web/state';\nimport { cn } from '~web/utils/helpers';\n\nexport const HeaderSettings = () => {\n  const isSettingsOpen = signalIsSettingsOpen.value;\n  return (\n    <span\n      data-text=\"Settings\"\n      className={cn(\n        'absolute inset-0 flex items-center',\n        'with-data-text',\n        'transition-transform duration-300',\n        isSettingsOpen ? 'translate-y-0' : '-translate-y-[200%]',\n      )}\n    />\n  );\n};\n"
  },
  {
    "path": "packages/scan/src/web/views/toolbar/index.tsx",
    "content": "import { useSignalEffect } from '@preact/signals';\nimport {\n  useCallback,\n  useEffect,\n  useLayoutEffect,\n  useState,\n} from 'preact/hooks';\nimport {\n  type LocalStorageOptions,\n  ReactScanInternals,\n  Store,\n} from '~core/index';\nimport { Icon } from '~web/components/icon';\nimport { Toggle } from '~web/components/toggle';\nimport { signalWidgetViews } from '~web/state';\nimport { cn, readLocalStorage, saveLocalStorage } from '~web/utils/helpers';\nimport { constant } from '~web/utils/preact/constant';\nimport { FPSMeter } from '~web/widget/fps-meter';\nimport { getEventSeverity } from '../notifications/data';\nimport { Notification } from '../notifications/icons';\nimport { useAppNotifications } from '../notifications/notifications';\n\nexport const Toolbar = constant(() => {\n  const events = useAppNotifications();\n  const [laggedEvents, setLaggedEvents] = useState(events);\n\n  useEffect(() => {\n    const timeout = setTimeout(() => {\n      setLaggedEvents(events);\n      // 500 + buffer to never see intermediary state\n      // todo: check if we still need this large of buffer\n    }, 500 + 100);\n    return () => {\n      clearTimeout(timeout);\n    };\n  }, [events]);\n\n  const inspectState = Store.inspectState;\n  const isInspectActive = inspectState.value.kind === 'inspecting';\n  const isInspectFocused = inspectState.value.kind === 'focused';\n\n  const [seenEvents, setSeenEvents] = useState<Array<string>>([]);\n\n  const onToggleInspect = useCallback(() => {\n    const currentState = Store.inspectState.value;\n\n    switch (currentState.kind) {\n      case 'inspecting': {\n        signalWidgetViews.value = {\n          view: 'none',\n        };\n        Store.inspectState.value = {\n          kind: 'inspect-off',\n        };\n        return;\n      }\n\n      case 'focused': {\n        signalWidgetViews.value = {\n          view: 'inspector',\n        };\n        Store.inspectState.value = {\n          kind: 'inspecting',\n          hoveredDomElement: null,\n        };\n        return;\n      }\n      // todo: auto select the root fibers first stateNode, and tell the user to select the element\n      case 'inspect-off': {\n        signalWidgetViews.value = {\n          view: 'none',\n        };\n        Store.inspectState.value = {\n          kind: 'inspecting',\n          hoveredDomElement: null,\n        };\n        return;\n      }\n      case 'uninitialized': {\n        return;\n      }\n    }\n  }, []);\n\n  const onToggleActive = useCallback((e: Event) => {\n    e.preventDefault();\n    e.stopPropagation();\n\n    if (!ReactScanInternals.instrumentation) {\n      return;\n    }\n    // todo: set a single source of truth\n    const isPaused = !ReactScanInternals.instrumentation.isPaused.value;\n    ReactScanInternals.instrumentation.isPaused.value = isPaused;\n    const existingLocalStorageOptions =\n      readLocalStorage<LocalStorageOptions>('react-scan-options');\n    saveLocalStorage('react-scan-options', {\n      ...existingLocalStorageOptions,\n      enabled: !isPaused,\n    });\n  }, []);\n\n  useSignalEffect(() => {\n    const state = Store.inspectState.value;\n    if (state.kind === 'uninitialized') {\n      Store.inspectState.value = {\n        kind: 'inspect-off',\n      };\n    }\n  });\n\n  let inspectIcon = null;\n  let inspectColor = '#999';\n\n  if (isInspectActive) {\n    inspectIcon = <Icon name=\"icon-inspect\" />;\n    inspectColor = '#8e61e3';\n  } else if (isInspectFocused) {\n    inspectIcon = <Icon name=\"icon-focus\" />;\n    inspectColor = '#8e61e3';\n  } else {\n    inspectIcon = <Icon name=\"icon-inspect\" />;\n    inspectColor = '#999';\n  }\n\n  // oxlint-disable-next-line react-hooks/exhaustive-deps\n  useLayoutEffect(() => {\n    if (signalWidgetViews.value.view !== 'notifications') {\n      return;\n    }\n    const ids = new Set(events.map((event) => event.id));\n    setSeenEvents([...ids.values()]);\n  }, [events.length, signalWidgetViews.value.view]);\n\n  return (\n    <div className=\"flex max-h-9 min-h-9 flex-1 items-stretch overflow-hidden\">\n      <div className=\"h-full flex items-center min-w-fit\">\n        <button\n          type=\"button\"\n          id=\"react-scan-inspect-element\"\n          title=\"Inspect element\"\n          onClick={onToggleInspect}\n          className=\"button flex items-center justify-center h-full w-full pl-3 pr-2.5\"\n          style={{ color: inspectColor }}\n        >\n          {inspectIcon}\n        </button>\n      </div>\n\n      <div className=\"h-full flex items-center justify-center\">\n        <button\n          type=\"button\"\n          id=\"react-scan-notifications\"\n          title=\"Notifications\"\n          onClick={() => {\n            if (Store.inspectState.value.kind !== 'inspect-off') {\n              Store.inspectState.value = {\n                kind: 'inspect-off',\n              };\n            }\n            switch (signalWidgetViews.value.view) {\n              case 'inspector': {\n                Store.inspectState.value = {\n                  kind: 'inspect-off',\n                };\n\n                const ids = new Set(events.map((event) => event.id));\n                setSeenEvents([...ids.values()]);\n                signalWidgetViews.value = {\n                  view: 'notifications',\n                };\n                return;\n              }\n              case 'notifications': {\n                signalWidgetViews.value = {\n                  view: 'none',\n                };\n                return;\n              }\n              case 'none': {\n                const ids = new Set(events.map((event) => event.id));\n                setSeenEvents([...ids.values()]);\n                signalWidgetViews.value = {\n                  view: 'notifications',\n                };\n                return;\n              }\n            }\n          }}\n          className=\"button flex items-center justify-center h-full pl-2.5 pr-2.5\"\n          style={{ color: inspectColor }}\n        >\n          <Notification\n            events={laggedEvents\n              .filter((event) => !seenEvents.includes(event.id))\n              .map((event) => getEventSeverity(event) === 'high')}\n            size={16}\n            className={cn([\n              'text-[#999]',\n              signalWidgetViews.value.view === 'notifications' &&\n                'text-[#8E61E3]',\n            ])}\n          />\n        </button>\n      </div>\n\n      <Toggle\n        checked={!ReactScanInternals.instrumentation?.isPaused.value}\n        onChange={onToggleActive}\n        className=\"place-self-center\"\n        title=\"Outline Re-renders\"\n      />\n\n      {/* todo add back showFPS*/}\n      {ReactScanInternals.options.value.showFPS && <FPSMeter />}\n    </div>\n  );\n});\n"
  },
  {
    "path": "packages/scan/src/web/widget/fps-meter.tsx",
    "content": "import { useEffect, useState } from 'preact/hooks';\nimport { getFPS } from '~core/instrumentation';\nimport { cn } from '~web/utils/helpers';\n\nexport const FpsMeterInner = ({fps}:{fps: number}) => {\n\n\n  const getColor = (fps: number) => {\n    if (fps < 30) return '#EF4444';\n    if (fps < 50) return '#F59E0B';\n    return 'rgb(214,132,245)';\n  };\n\n  return (\n    <div\n      className={cn(\n        'flex items-center gap-x-1 px-2 w-full',\n        'h-6',\n        'rounded-md',\n        'font-mono leading-none',\n        'bg-[#141414]',\n        'ring-1 ring-white/[0.08]',\n      )}\n    >\n      <div\n        style={{ color: getColor(fps) }}\n        className=\"text-sm font-semibold tracking-wide transition-colors ease-in-out w-full flex justify-center items-center\"\n      >\n        {fps}\n      </div>\n      <span className=\"text-white/30 text-[11px] font-medium tracking-wide ml-auto min-w-fit\">\n        FPS\n      </span>\n    </div>\n  );\n};\n\n\nexport const FPSMeter = () => {\n  const [fps, setFps] = useState<null | number>(null);\n\n  useEffect(() => {\n    const intervalId = setInterval(() => {\n      setFps(getFPS());\n    }, 200);\n\n    return () => clearInterval(intervalId);\n  }, []);\n\n  return (\n    <div\n      className={cn(\n        'flex items-center justify-end gap-x-2 px-1 ml-1 w-[72px]',\n        'whitespace-nowrap text-sm text-white',\n      )}\n    >\n      {/* fixme: default fps state*/}\n      {fps === null ? <>️</> : <FpsMeterInner fps={fps} />}\n    </div>\n  );\n};\n\n\n"
  },
  {
    "path": "packages/scan/src/web/widget/header.tsx",
    "content": "import { Store } from '~core/index';\nimport { Icon } from '~web/components/icon';\nimport { useDelayedValue } from '~web/hooks/use-delayed-value';\nimport { signalWidgetViews } from '~web/state';\nimport { cn } from '~web/utils/helpers';\nimport { HeaderInspect } from '~web/views/inspector/header';\n\nexport const Header = () => {\n  const isInitialView = useDelayedValue(\n    Store.inspectState.value.kind === 'focused',\n    150,\n    0,\n  );\n  const handleClose = () => {\n    signalWidgetViews.value = {\n      view: 'none',\n    };\n    Store.inspectState.value = {\n      kind: 'inspect-off',\n    };\n  };\n\n  const isHeaderIsNotifications =\n    signalWidgetViews.value.view === 'notifications';\n\n  if (isHeaderIsNotifications) {\n    return;\n  }\n\n  return (\n    <div className=\"react-scan-header\">\n      <div className=\"relative flex-1 h-full\">\n        <div\n          className={cn(\n            'react-scan-header-item is-visible',\n            !isInitialView && '!duration-0',\n          )}\n        >\n          <HeaderInspect />\n        </div>\n      </div>\n\n      <button\n        type=\"button\"\n        title=\"Close\"\n        className=\"react-scan-close-button\"\n        onClick={handleClose}\n      >\n        <Icon name=\"icon-close\" />\n      </button>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/scan/src/web/widget/helpers.ts",
    "content": "import { MIN_SIZE, SAFE_AREA } from '../constants';\nimport type { Corner, Position, ResizeHandleProps, Size } from './types';\n\nclass WindowDimensions {\n  maxWidth: number;\n  maxHeight: number;\n\n  constructor(\n    public width: number,\n    public height: number,\n  ) {\n    this.maxWidth = width - SAFE_AREA * 2;\n    this.maxHeight = height - SAFE_AREA * 2;\n  }\n\n  rightEdge(width: number): number {\n    return this.width - width - SAFE_AREA;\n  }\n\n  bottomEdge(height: number): number {\n    return this.height - height - SAFE_AREA;\n  }\n\n  isFullWidth(width: number): boolean {\n    return width >= this.maxWidth;\n  }\n\n  isFullHeight(height: number): boolean {\n    return height >= this.maxHeight;\n  }\n}\n\nlet cachedWindowDimensions: WindowDimensions | undefined;\n\nexport const getWindowDimensions = () => {\n  const currentWidth = window.innerWidth;\n  const currentHeight = window.innerHeight;\n\n  if (\n    cachedWindowDimensions &&\n    cachedWindowDimensions.width === currentWidth &&\n    cachedWindowDimensions.height === currentHeight\n  ) {\n    return cachedWindowDimensions;\n  }\n\n  cachedWindowDimensions = new WindowDimensions(currentWidth, currentHeight);\n\n  return cachedWindowDimensions;\n};\n\nexport const getOppositeCorner = (\n  position: ResizeHandleProps['position'],\n  currentCorner: Corner,\n  isFullScreen: boolean,\n  isFullWidth?: boolean,\n  isFullHeight?: boolean,\n): Corner => {\n  // For full screen mode\n  if (isFullScreen) {\n    if (position === 'top-left') return 'bottom-right';\n    if (position === 'top-right') return 'bottom-left';\n    if (position === 'bottom-left') return 'top-right';\n    if (position === 'bottom-right') return 'top-left';\n\n    const [vertical, horizontal] = currentCorner.split('-');\n    if (position === 'left') return `${vertical}-right` as Corner;\n    if (position === 'right') return `${vertical}-left` as Corner;\n    if (position === 'top') return `bottom-${horizontal}` as Corner;\n    if (position === 'bottom') return `top-${horizontal}` as Corner;\n  }\n\n  // For full width mode\n  if (isFullWidth) {\n    if (position === 'left')\n      return `${currentCorner.split('-')[0]}-right` as Corner;\n    if (position === 'right')\n      return `${currentCorner.split('-')[0]}-left` as Corner;\n  }\n\n  // For full height mode\n  if (isFullHeight) {\n    if (position === 'top')\n      return `bottom-${currentCorner.split('-')[1]}` as Corner;\n    if (position === 'bottom')\n      return `top-${currentCorner.split('-')[1]}` as Corner;\n  }\n\n  return currentCorner;\n};\n\nexport const calculatePosition = (\n  corner: Corner,\n  width: number,\n  height: number,\n): Position => {\n  const isRTL = getComputedStyle(document.body).direction === 'rtl';\n\n  const windowWidth = window.innerWidth;\n  const windowHeight = window.innerHeight;\n\n  // Check if widget is minimized\n  const isMinimized = width === MIN_SIZE.width;\n\n  // Only bound dimensions if minimized\n  const effectiveWidth = isMinimized\n    ? width\n    : Math.min(width, windowWidth - SAFE_AREA * 2);\n  const effectiveHeight = isMinimized\n    ? height\n    : Math.min(height, windowHeight - SAFE_AREA * 2);\n\n  // Calculate base positions\n  let x: number;\n  let y: number;\n\n  let leftBound = SAFE_AREA;\n  let rightBound =  windowWidth - effectiveWidth - SAFE_AREA;\n  let topBound = SAFE_AREA;\n  let bottomBound = windowHeight - effectiveHeight - SAFE_AREA;\n\n  switch (corner) {\n    case 'top-right':\n      x = isRTL ? -leftBound : rightBound;\n      y = topBound;\n      break;\n    case 'bottom-right':\n      x = isRTL ? -leftBound : rightBound;\n      y = bottomBound;\n      break;\n    case 'bottom-left':\n      x = isRTL ? -rightBound : leftBound;\n      y = bottomBound;\n      break;\n    case 'top-left':\n      x = isRTL ? -rightBound : leftBound;\n      y = topBound;\n      break;\n    default:\n      x = leftBound;\n      y = topBound;\n      break;\n  }\n\n  // Only ensure positions are within bounds if minimized\n  if (isMinimized) {\n    if (isRTL) {\n      // For RTL\n      x = Math.min(\n        -leftBound,\n        Math.max(x, -rightBound)\n      );\n    } else {\n      // For LTR\n      x = Math.max(\n        leftBound,\n        Math.min(x, rightBound),\n      );\n    }\n    y = Math.max(\n      topBound,\n      Math.min(y, bottomBound),\n    );\n  }\n\n  return { x, y };\n};\n\nconst positionMatchesCorner = (\n  position: ResizeHandleProps['position'],\n  corner: Corner,\n): boolean => {\n  const [vertical, horizontal] = corner.split('-');\n  return position !== vertical && position !== horizontal;\n};\n\nexport const getHandleVisibility = (\n  position: ResizeHandleProps['position'],\n  corner: Corner,\n  isFullWidth: boolean,\n  isFullHeight: boolean,\n): boolean => {\n  if (isFullWidth && isFullHeight) {\n    return true;\n  }\n\n  // Normal state\n  if (!isFullWidth && !isFullHeight) {\n    return positionMatchesCorner(position, corner);\n  }\n\n  // Full width state\n  if (isFullWidth) {\n    return position !== corner.split('-')[0];\n  }\n\n  // Full height state\n  if (isFullHeight) {\n    return position !== corner.split('-')[1];\n  }\n\n  return false;\n};\n\nexport const calculateBoundedSize = (\n  currentSize: number,\n  delta: number,\n  isWidth: boolean,\n): number => {\n  const min = isWidth ? MIN_SIZE.width : MIN_SIZE.initialHeight;\n  const max = isWidth\n    ? getWindowDimensions().maxWidth\n    : getWindowDimensions().maxHeight;\n\n  const newSize = currentSize + delta;\n  return Math.min(Math.max(min, newSize), max);\n};\n\nexport const calculateNewSizeAndPosition = (\n  position: ResizeHandleProps['position'],\n  initialSize: Size,\n  initialPosition: Position,\n  deltaX: number,\n  deltaY: number,\n): { newSize: Size; newPosition: Position } => {\n  const isRTL = getComputedStyle(document.body).direction === 'rtl';\n\n  const maxWidth = window.innerWidth - SAFE_AREA * 2;\n  const maxHeight = window.innerHeight - SAFE_AREA * 2;\n\n  let newWidth = initialSize.width;\n  let newHeight = initialSize.height;\n  let newX = initialPosition.x;\n  let newY = initialPosition.y;\n\n  // horizontal resize for RTL\n  if (isRTL && position.includes('right')) {\n    // Check if we have enough space on the right\n    const availableWidth = -initialPosition.x + initialSize.width - SAFE_AREA;\n    const proposedWidth = Math.min(initialSize.width + deltaX, availableWidth);\n    newWidth = Math.min(maxWidth, Math.max(MIN_SIZE.width, proposedWidth));\n    newX = initialPosition.x + (newWidth - initialSize.width);\n  }\n  if (isRTL && position.includes('left')) {\n    // Check if we have enough space on the left\n    const availableWidth = window.innerWidth - initialPosition.x - SAFE_AREA;\n    const proposedWidth = Math.min(initialSize.width - deltaX, availableWidth);\n    newWidth = Math.min(maxWidth, Math.max(MIN_SIZE.width, proposedWidth));\n  }\n  // horizontal resize for LTR\n  if (!isRTL && position.includes('right')) {\n    // Check if we have enough space on the right\n    const availableWidth = window.innerWidth - initialPosition.x - SAFE_AREA;\n    const proposedWidth = Math.min(initialSize.width + deltaX, availableWidth);\n    newWidth = Math.min(maxWidth, Math.max(MIN_SIZE.width, proposedWidth));\n  }\n  if (!isRTL && position.includes('left')) {\n    // Check if we have enough space on the left\n    const availableWidth = initialPosition.x + initialSize.width - SAFE_AREA;\n    const proposedWidth = Math.min(initialSize.width - deltaX, availableWidth);\n    newWidth = Math.min(maxWidth, Math.max(MIN_SIZE.width, proposedWidth));\n    newX = initialPosition.x - (newWidth - initialSize.width);\n  }\n\n  // vertical resize\n  if (position.includes('bottom')) {\n    // Check if we have enough space at the bottom\n    const availableHeight = window.innerHeight - initialPosition.y - SAFE_AREA;\n    const proposedHeight = Math.min(\n      initialSize.height + deltaY,\n      availableHeight,\n    );\n    newHeight = Math.min(\n      maxHeight,\n      Math.max(MIN_SIZE.initialHeight, proposedHeight),\n    );\n  }\n  if (position.includes('top')) {\n    // Check if we have enough space at the top\n    const availableHeight = initialPosition.y + initialSize.height - SAFE_AREA;\n    const proposedHeight = Math.min(\n      initialSize.height - deltaY,\n      availableHeight,\n    );\n    newHeight = Math.min(\n      maxHeight,\n      Math.max(MIN_SIZE.initialHeight, proposedHeight),\n    );\n    newY = initialPosition.y - (newHeight - initialSize.height);\n  }\n\n  let leftBound = SAFE_AREA;\n  let rightBound = window.innerWidth - SAFE_AREA - newWidth;\n  let topBound = SAFE_AREA;\n  let bottomBound = window.innerHeight - SAFE_AREA - newHeight;\n\n  // Ensure position stays within bounds\n  if (isRTL) {\n    // for RTL\n    newX = Math.min(\n      -leftBound,\n      Math.max(newX, -rightBound)\n    );\n  } else {\n    // for LTR\n    newX = Math.max(\n      leftBound,\n      Math.min(newX, rightBound),\n    );\n  }\n\n  newY = Math.max(\n    topBound,\n    Math.min(newY, bottomBound),\n  );\n\n  return {\n    newSize: { width: newWidth, height: newHeight },\n    newPosition: { x: newX, y: newY },\n  };\n};\n\nexport const getClosestCorner = (position: Position): Corner => {\n  const windowDims = getWindowDimensions();\n\n  const distances: Record<Corner, number> = {\n    'top-left': Math.hypot(position.x, position.y),\n    'top-right': Math.hypot(windowDims.maxWidth - position.x, position.y),\n    'bottom-left': Math.hypot(position.x, windowDims.maxHeight - position.y),\n    'bottom-right': Math.hypot(\n      windowDims.maxWidth - position.x,\n      windowDims.maxHeight - position.y,\n    ),\n  };\n\n  let closest: Corner = 'top-left';\n\n  for (const key in distances) {\n    if (distances[key as Corner] < distances[closest]) {\n      closest = key as Corner;\n    }\n  }\n\n  return closest;\n};\n\n// Helper to determine best corner based on cursor position, widget size, and movement\nexport const getBestCorner = (\n  mouseX: number,\n  mouseY: number,\n  initialMouseX?: number,\n  initialMouseY?: number,\n  threshold = 100,\n): Corner => {\n  const deltaX = initialMouseX !== undefined ? mouseX - initialMouseX : 0;\n  const deltaY = initialMouseY !== undefined ? mouseY - initialMouseY : 0;\n\n  const windowCenterX = window.innerWidth / 2;\n  const windowCenterY = window.innerHeight / 2;\n\n  // Determine movement direction\n  const movingRight = deltaX > threshold;\n  const movingLeft = deltaX < -threshold;\n  const movingDown = deltaY > threshold;\n  const movingUp = deltaY < -threshold;\n\n  // If horizontal movement\n  if (movingRight || movingLeft) {\n    const isBottom = mouseY > windowCenterY;\n    return movingRight\n      ? isBottom\n        ? 'bottom-right'\n        : 'top-right'\n      : isBottom\n        ? 'bottom-left'\n        : 'top-left';\n  }\n\n  // If vertical movement\n  if (movingDown || movingUp) {\n    const isRight = mouseX > windowCenterX;\n    return movingDown\n      ? isRight\n        ? 'bottom-right'\n        : 'bottom-left'\n      : isRight\n        ? 'top-right'\n        : 'top-left';\n  }\n\n  // If no significant movement, use quadrant-based position\n  return mouseX > windowCenterX\n    ? mouseY > windowCenterY\n      ? 'bottom-right'\n      : 'top-right'\n    : mouseY > windowCenterY\n      ? 'bottom-left'\n      : 'top-left';\n};\n"
  },
  {
    "path": "packages/scan/src/web/widget/index.tsx",
    "content": "import { createContext, type JSX } from \"preact\";\nimport { useCallback, useEffect, useRef, useState } from \"preact/hooks\";\nimport { Store, ReactScanInternals } from \"~core/index\";\nimport {\n  cn,\n  saveLocalStorage,\n  removeLocalStorage,\n  readLocalStorage,\n} from \"~web/utils/helpers\";\nimport { Content } from \"~web/views\";\nimport { ScanOverlay } from \"~web/views/inspector/overlay\";\nimport {\n  LOCALSTORAGE_KEY,\n  LOCALSTORAGE_COLLAPSED_KEY,\n  MIN_SIZE,\n  SAFE_AREA,\n  LOCALSTORAGE_LAST_VIEW_KEY,\n} from \"../constants\";\nimport {\n  defaultWidgetConfig,\n  signalRefWidget,\n  signalWidget,\n  signalWidgetViews,\n  updateDimensions,\n  type WidgetStates,\n} from \"../state\";\nimport {\n  calculateBoundedSize,\n  calculatePosition,\n  getBestCorner,\n} from \"./helpers\";\nimport { ResizeHandle } from \"./resize-handle\";\nimport { signalWidgetCollapsed } from \"~web/state\";\nimport { Icon } from \"~web/components/icon\";\nimport { Corner } from \"./types\";\nimport type { CollapsedPosition } from \"./types\";\n\nconst COLLAPSED_SIZE = {\n  horizontal: { width: 20, height: 48 },\n  vertical: { width: 48, height: 20 },\n} as const;\n\nexport const Widget = () => {\n  const refWidget = useRef<HTMLDivElement | null>(null);\n  const refShouldOpen = useRef<boolean>(false);\n\n  const refInitialMinimizedWidth = useRef<number>(0);\n  const refInitialMinimizedHeight = useRef<number>(0);\n  const refExpandingFromCollapsed = useRef<boolean>(false);\n\n  const updateWidgetPosition = useCallback((shouldSave = true) => {\n    if (!refWidget.current) return;\n\n    const { corner } = signalWidget.value;\n    let newWidth: number;\n    let newHeight: number;\n\n    if (signalWidgetCollapsed.value) {\n      const orientation =\n        signalWidgetCollapsed.value.orientation || \"horizontal\";\n      const size = COLLAPSED_SIZE[orientation];\n      newWidth = size.width;\n      newHeight = size.height;\n    } else if (refShouldOpen.current) {\n      const lastDims = signalWidget.value.lastDimensions;\n      newWidth = calculateBoundedSize(lastDims.width, 0, true);\n      newHeight = calculateBoundedSize(lastDims.height, 0, false);\n\n      if (refExpandingFromCollapsed.current) {\n        refExpandingFromCollapsed.current = false;\n      }\n    } else {\n      newWidth = refInitialMinimizedWidth.current;\n      newHeight = refInitialMinimizedHeight.current;\n    }\n\n    const newPosition = calculatePosition(corner, newWidth, newHeight);\n\n    // When collapsed, override position so arrow is flush against the viewport edge.\n    let finalPosition = newPosition;\n    if (signalWidgetCollapsed.value) {\n      const { corner: collapsedCorner, orientation = \"horizontal\" } =\n        signalWidgetCollapsed.value;\n      const size = COLLAPSED_SIZE[orientation];\n\n      switch (collapsedCorner) {\n        case \"top-left\":\n          finalPosition =\n            orientation === \"horizontal\"\n              ? { x: -1, y: SAFE_AREA }\n              : { x: SAFE_AREA, y: -1 };\n          break;\n        case \"bottom-left\":\n          finalPosition =\n            orientation === \"horizontal\"\n              ? { x: -1, y: window.innerHeight - size.height - SAFE_AREA }\n              : { x: SAFE_AREA, y: window.innerHeight - size.height + 1 };\n          break;\n        case \"top-right\":\n          finalPosition =\n            orientation === \"horizontal\"\n              ? { x: window.innerWidth - size.width + 1, y: SAFE_AREA }\n              : { x: window.innerWidth - size.width - SAFE_AREA, y: -1 };\n          break;\n        case \"bottom-right\":\n        default:\n          finalPosition =\n            orientation === \"horizontal\"\n              ? {\n                  x: window.innerWidth - size.width + 1,\n                  y: window.innerHeight - size.height - SAFE_AREA,\n                }\n              : {\n                  x: window.innerWidth - size.width - SAFE_AREA,\n                  y: window.innerHeight - size.height + 1,\n                };\n          break;\n      }\n    }\n\n    const isTooSmall =\n      newWidth < MIN_SIZE.width || newHeight < MIN_SIZE.initialHeight;\n    const shouldPersist = shouldSave && !isTooSmall;\n\n    const container = refWidget.current;\n    const containerStyle = container.style;\n\n    let rafId: number | null = null;\n    const onTransitionEnd = () => {\n      updateDimensions();\n      container.removeEventListener(\"transitionend\", onTransitionEnd);\n      if (rafId) {\n        cancelAnimationFrame(rafId);\n        rafId = null;\n      }\n    };\n\n    container.addEventListener(\"transitionend\", onTransitionEnd);\n    containerStyle.transition = \"all 0.25s cubic-bezier(0, 0, 0.2, 1)\";\n\n    rafId = requestAnimationFrame(() => {\n      containerStyle.width = `${newWidth}px`;\n      containerStyle.height = `${newHeight}px`;\n      containerStyle.transform = `translate3d(${finalPosition.x}px, ${finalPosition.y}px, 0)`;\n      rafId = null;\n    });\n\n    const newDimensions = {\n      isFullWidth: newWidth >= window.innerWidth - SAFE_AREA * 2,\n      isFullHeight: newHeight >= window.innerHeight - SAFE_AREA * 2,\n      width: newWidth,\n      height: newHeight,\n      position: finalPosition,\n    };\n\n    signalWidget.value = {\n      corner,\n      dimensions: newDimensions,\n      lastDimensions: refShouldOpen\n        ? signalWidget.value.lastDimensions\n        : newWidth > refInitialMinimizedWidth.current\n          ? newDimensions\n          : signalWidget.value.lastDimensions,\n      componentsTree: signalWidget.value.componentsTree,\n    };\n\n    if (shouldPersist) {\n      saveLocalStorage(LOCALSTORAGE_KEY, {\n        corner: signalWidget.value.corner,\n        dimensions: signalWidget.value.dimensions,\n        lastDimensions: signalWidget.value.lastDimensions,\n        componentsTree: signalWidget.value.componentsTree,\n      });\n    }\n\n    updateDimensions();\n  }, []);\n\n  const handleDrag = useCallback(\n    (e: JSX.TargetedPointerEvent<HTMLDivElement>) => {\n      e.preventDefault();\n\n      if (!refWidget.current || (e.target as HTMLElement).closest(\"button\"))\n        return;\n\n      const container = refWidget.current;\n      const containerStyle = container.style;\n      const { dimensions } = signalWidget.value;\n\n      const initialMouseX = e.clientX;\n      const initialMouseY = e.clientY;\n\n      const initialX = dimensions.position.x;\n      const initialY = dimensions.position.y;\n\n      let currentX = initialX;\n      let currentY = initialY;\n      let rafId: number | null = null;\n      let hasMoved = false;\n      let lastMouseX = initialMouseX;\n      let lastMouseY = initialMouseY;\n\n      const handlePointerMove = (e: globalThis.PointerEvent) => {\n        if (rafId) return;\n\n        hasMoved = true;\n        lastMouseX = e.clientX;\n        lastMouseY = e.clientY;\n\n        rafId = requestAnimationFrame(() => {\n          const deltaX = lastMouseX - initialMouseX;\n          const deltaY = lastMouseY - initialMouseY;\n\n          currentX = Number(initialX) + deltaX;\n          currentY = Number(initialY) + deltaY;\n\n          containerStyle.transition = \"none\";\n          containerStyle.transform = `translate3d(${currentX}px, ${currentY}px, 0)`;\n\n          const widgetRight = currentX + dimensions.width;\n          const widgetBottom = currentY + dimensions.height;\n\n          const outsideLeft = Math.max(0, -currentX);\n          const outsideRight = Math.max(0, widgetRight - window.innerWidth);\n          const outsideTop = Math.max(0, -currentY);\n          const outsideBottom = Math.max(0, widgetBottom - window.innerHeight);\n\n          const horizontalOutside = Math.min(\n            dimensions.width,\n            outsideLeft + outsideRight\n          );\n          const verticalOutside = Math.min(\n            dimensions.height,\n            outsideTop + outsideBottom\n          );\n          const areaOutside =\n            horizontalOutside * dimensions.height +\n            verticalOutside * dimensions.width -\n            horizontalOutside * verticalOutside;\n          const totalArea = dimensions.width * dimensions.height;\n\n          // todo: delete this doesn't do anything\n          let shouldCollapse = areaOutside > totalArea * 0.35;\n\n          if (!shouldCollapse && ReactScanInternals.options.value.showFPS) {\n            const fpsRight = currentX + dimensions.width;\n            const fpsLeft = fpsRight - 100;\n\n            const fpsFullyOutside =\n              fpsRight <= 0 ||\n              fpsLeft >= window.innerWidth ||\n              currentY + dimensions.height <= 0 ||\n              currentY >= window.innerHeight;\n\n            shouldCollapse = fpsFullyOutside;\n          }\n\n          if (shouldCollapse) {\n            const widgetCenterX = currentX + dimensions.width / 2;\n            const widgetCenterY = currentY + dimensions.height / 2;\n            const screenCenterX = window.innerWidth / 2;\n            const screenCenterY = window.innerHeight / 2;\n\n            let targetCorner: Corner;\n            if (widgetCenterX < screenCenterX) {\n              targetCorner =\n                widgetCenterY < screenCenterY ? \"top-left\" : \"bottom-left\";\n            } else {\n              targetCorner =\n                widgetCenterY < screenCenterY ? \"top-right\" : \"bottom-right\";\n            }\n\n            let orientation: \"horizontal\" | \"vertical\";\n            const horizontalOverflow = Math.max(outsideLeft, outsideRight);\n            const verticalOverflow = Math.max(outsideTop, outsideBottom);\n\n            orientation =\n              horizontalOverflow > verticalOverflow ? \"horizontal\" : \"vertical\";\n\n            signalWidget.value = {\n              ...signalWidget.value,\n              corner: targetCorner,\n              lastDimensions: {\n                ...dimensions,\n                position: calculatePosition(\n                  targetCorner,\n                  dimensions.width,\n                  dimensions.height\n                ),\n              },\n            };\n\n            const collapsedPosition: CollapsedPosition = {\n              corner: targetCorner,\n              orientation,\n            };\n\n            signalWidgetCollapsed.value = collapsedPosition;\n            saveLocalStorage(LOCALSTORAGE_COLLAPSED_KEY, collapsedPosition);\n            saveLocalStorage(LOCALSTORAGE_KEY, signalWidget.value);\n            updateWidgetPosition(false);\n\n            document.removeEventListener(\"pointermove\", handlePointerMove);\n            document.removeEventListener(\"pointerup\", handlePointerEnd);\n            if (rafId) {\n              cancelAnimationFrame(rafId);\n              rafId = null;\n            }\n          }\n\n          rafId = null;\n        });\n      };\n\n      const handlePointerEnd = () => {\n        if (!container) return;\n\n        if (rafId) {\n          cancelAnimationFrame(rafId);\n          rafId = null;\n        }\n\n        document.removeEventListener(\"pointermove\", handlePointerMove);\n        document.removeEventListener(\"pointerup\", handlePointerEnd);\n\n        // Calculate total movement distance\n        const totalDeltaX = Math.abs(lastMouseX - initialMouseX);\n        const totalDeltaY = Math.abs(lastMouseY - initialMouseY);\n        const totalMovement = Math.sqrt(\n          totalDeltaX * totalDeltaX + totalDeltaY * totalDeltaY\n        );\n\n        // Only consider it a move if we moved more than 60 pixels\n        if (!hasMoved || totalMovement < 60) return;\n\n        const newCorner = getBestCorner(\n          lastMouseX,\n          lastMouseY,\n          initialMouseX,\n          initialMouseY,\n          Store.inspectState.value.kind === \"focused\" ? 80 : 40\n        );\n\n        if (newCorner === signalWidget.value.corner) {\n          containerStyle.transition =\n            \"transform 0.25s cubic-bezier(0, 0, 0.2, 1)\";\n          const currentPosition = signalWidget.value.dimensions.position;\n          requestAnimationFrame(() => {\n            containerStyle.transform = `translate3d(${currentPosition.x}px, ${currentPosition.y}px, 0)`;\n          });\n\n          return;\n        }\n\n        const snappedPosition = calculatePosition(\n          newCorner,\n          dimensions.width,\n          dimensions.height\n        );\n\n        if (currentX === initialX && currentY === initialY) return;\n\n        const onTransitionEnd = () => {\n          containerStyle.transition = \"none\";\n          updateDimensions();\n          container.removeEventListener(\"transitionend\", onTransitionEnd);\n          if (rafId) {\n            cancelAnimationFrame(rafId);\n            rafId = null;\n          }\n        };\n\n        container.addEventListener(\"transitionend\", onTransitionEnd);\n        containerStyle.transition =\n          \"transform 0.25s cubic-bezier(0, 0, 0.2, 1)\";\n\n        requestAnimationFrame(() => {\n          containerStyle.transform = `translate3d(${snappedPosition.x}px, ${snappedPosition.y}px, 0)`;\n        });\n\n        signalWidget.value = {\n          corner: newCorner,\n          dimensions: {\n            isFullWidth: dimensions.isFullWidth,\n            isFullHeight: dimensions.isFullHeight,\n            width: dimensions.width,\n            height: dimensions.height,\n            position: snappedPosition,\n          },\n          lastDimensions: signalWidget.value.lastDimensions,\n          componentsTree: signalWidget.value.componentsTree,\n        };\n\n        saveLocalStorage(LOCALSTORAGE_KEY, {\n          corner: newCorner,\n          dimensions: signalWidget.value.dimensions,\n          lastDimensions: signalWidget.value.lastDimensions,\n          componentsTree: signalWidget.value.componentsTree,\n        });\n      };\n\n      document.addEventListener(\"pointermove\", handlePointerMove);\n      document.addEventListener(\"pointerup\", handlePointerEnd);\n    },\n    []\n  );\n\n  const handleCollapsedDrag = useCallback(\n    (e: JSX.TargetedPointerEvent<HTMLDivElement>) => {\n      e.preventDefault();\n\n      if (!refWidget.current || !signalWidgetCollapsed.value) return;\n\n      const { corner: collapsedCorner, orientation = \"horizontal\" } =\n        signalWidgetCollapsed.value;\n\n      const initialMouseX = e.clientX;\n      const initialMouseY = e.clientY;\n\n      let rafId: number | null = null;\n      let hasExpanded = false;\n\n      const DRAG_THRESHOLD = 50;\n\n      const handlePointerMove = (e: globalThis.PointerEvent) => {\n        if (hasExpanded || rafId) return;\n\n        const deltaX = e.clientX - initialMouseX;\n        const deltaY = e.clientY - initialMouseY;\n\n        let shouldExpand = false;\n\n        if (orientation === \"horizontal\") {\n          if (collapsedCorner.endsWith(\"left\") && deltaX > DRAG_THRESHOLD) {\n            shouldExpand = true;\n          } else if (\n            collapsedCorner.endsWith(\"right\") &&\n            deltaX < -DRAG_THRESHOLD\n          ) {\n            shouldExpand = true;\n          }\n        } else {\n          if (collapsedCorner.startsWith(\"top\") && deltaY > DRAG_THRESHOLD) {\n            shouldExpand = true;\n          } else if (\n            collapsedCorner.startsWith(\"bottom\") &&\n            deltaY < -DRAG_THRESHOLD\n          ) {\n            shouldExpand = true;\n          }\n        }\n\n        if (shouldExpand) {\n          hasExpanded = true;\n\n          signalWidgetCollapsed.value = null;\n          saveLocalStorage(LOCALSTORAGE_COLLAPSED_KEY, null);\n\n          if (refInitialMinimizedWidth.current === 0 && refWidget.current) {\n            requestAnimationFrame(() => {\n              if (refWidget.current) {\n                refWidget.current.style.width = \"min-content\";\n                const naturalWidth = refWidget.current.offsetWidth;\n                refInitialMinimizedWidth.current = naturalWidth || 300;\n\n                const lastDims = signalWidget.value.lastDimensions;\n                const targetWidth = calculateBoundedSize(\n                  lastDims.width,\n                  0,\n                  true\n                );\n                const targetHeight = calculateBoundedSize(\n                  lastDims.height,\n                  0,\n                  false\n                );\n\n                let newX = e.clientX - targetWidth / 2;\n                let newY = e.clientY - targetHeight / 2;\n\n                newX = Math.max(\n                  SAFE_AREA,\n                  Math.min(newX, window.innerWidth - targetWidth - SAFE_AREA)\n                );\n                newY = Math.max(\n                  SAFE_AREA,\n                  Math.min(newY, window.innerHeight - targetHeight - SAFE_AREA)\n                );\n\n                signalWidget.value = {\n                  ...signalWidget.value,\n                  dimensions: {\n                    ...signalWidget.value.dimensions,\n                    position: { x: newX, y: newY },\n                  },\n                };\n\n                updateWidgetPosition(true);\n\n                const savedView = readLocalStorage<WidgetStates>(\n                  LOCALSTORAGE_LAST_VIEW_KEY\n                );\n                signalWidgetViews.value = savedView || { view: \"none\" };\n\n                setTimeout(() => {\n                  if (refWidget.current) {\n                    const dragEvent = new PointerEvent(\"pointerdown\", {\n                      clientX: e.clientX,\n                      clientY: e.clientY,\n                      pointerId: e.pointerId,\n                      bubbles: true,\n                    });\n                    refWidget.current.dispatchEvent(dragEvent);\n                  }\n                }, 100);\n              }\n            });\n          } else {\n            updateWidgetPosition(true);\n            const savedView = readLocalStorage<WidgetStates>(\n              LOCALSTORAGE_LAST_VIEW_KEY\n            );\n            signalWidgetViews.value = savedView || { view: \"none\" };\n          }\n\n          document.removeEventListener(\"pointermove\", handlePointerMove);\n          document.removeEventListener(\"pointerup\", handlePointerEnd);\n        }\n      };\n\n      const handlePointerEnd = () => {\n        if (rafId) {\n          cancelAnimationFrame(rafId);\n          rafId = null;\n        }\n        document.removeEventListener(\"pointermove\", handlePointerMove);\n        document.removeEventListener(\"pointerup\", handlePointerEnd);\n      };\n\n      document.addEventListener(\"pointermove\", handlePointerMove);\n      document.addEventListener(\"pointerup\", handlePointerEnd);\n    },\n    []\n  );\n\n  // oxlint-disable-next-line react-hooks/exhaustive-deps\n  useEffect(() => {\n    if (!refWidget.current) return;\n\n    removeLocalStorage(LOCALSTORAGE_LAST_VIEW_KEY);\n\n    if (!signalWidgetCollapsed.value) {\n      refWidget.current.style.width = \"min-content\";\n      refInitialMinimizedHeight.current = 36; // height of the header\n      refInitialMinimizedWidth.current = refWidget.current.offsetWidth;\n    } else {\n      refInitialMinimizedHeight.current = 36;\n      refInitialMinimizedWidth.current = 0;\n    }\n\n    refWidget.current.style.maxWidth = `calc(100vw - ${SAFE_AREA * 2}px)`;\n    refWidget.current.style.maxHeight = `calc(100vh - ${SAFE_AREA * 2}px)`;\n\n    updateWidgetPosition();\n\n    if (\n      Store.inspectState.value.kind !== \"focused\" &&\n      !signalWidgetCollapsed.value &&\n      !refExpandingFromCollapsed.current\n    ) {\n      signalWidget.value = {\n        ...signalWidget.value,\n        dimensions: {\n          isFullWidth: false,\n          isFullHeight: false,\n          width: refInitialMinimizedWidth.current,\n          height: refInitialMinimizedHeight.current,\n          position: signalWidget.value.dimensions.position,\n        },\n      };\n    }\n\n    signalRefWidget.value = refWidget.current;\n\n    const unsubscribeSignalWidget = signalWidget.subscribe((widget) => {\n      if (!refWidget.current) return;\n\n      const { x, y } = widget.dimensions.position;\n      const { width, height } = widget.dimensions;\n      const container = refWidget.current;\n\n      requestAnimationFrame(() => {\n        container.style.transform = `translate3d(${x}px, ${y}px, 0)`;\n        container.style.width = `${width}px`;\n        container.style.height = `${height}px`;\n      });\n    });\n\n    const unsubscribeSignalWidgetViews = signalWidgetViews.subscribe(\n      (state) => {\n        refShouldOpen.current = state.view !== \"none\";\n        updateWidgetPosition();\n\n        if (!signalWidgetCollapsed.value) {\n          if (state.view !== \"none\") {\n            saveLocalStorage(LOCALSTORAGE_LAST_VIEW_KEY, state);\n          } else {\n            removeLocalStorage(LOCALSTORAGE_LAST_VIEW_KEY);\n          }\n        }\n      }\n    );\n\n    const unsubscribeStoreInspectState = Store.inspectState.subscribe(\n      (state) => {\n        refShouldOpen.current = state.kind === \"focused\";\n        updateWidgetPosition();\n      }\n    );\n\n    const handleWindowResize = () => {\n      updateWidgetPosition(true);\n    };\n\n    window.addEventListener(\"resize\", handleWindowResize, { passive: true });\n\n    return () => {\n      window.removeEventListener(\"resize\", handleWindowResize);\n      unsubscribeSignalWidgetViews();\n      unsubscribeStoreInspectState();\n      unsubscribeSignalWidget();\n\n      saveLocalStorage(LOCALSTORAGE_KEY, {\n        ...defaultWidgetConfig,\n        corner: signalWidget.value.corner,\n      });\n    };\n  }, []);\n\n  // i don't want to put the ref in state, so this is the solution to force context to propagate it\n  const [_, setTriggerRender] = useState(false);\n  useEffect(() => {\n    setTriggerRender(true);\n  }, []);\n\n  const isCollapsed = signalWidgetCollapsed.value;\n\n  let arrowRotationClass = \"\";\n  if (isCollapsed) {\n    const { orientation = \"horizontal\", corner } = isCollapsed;\n    if (orientation === \"horizontal\") {\n      arrowRotationClass = corner?.endsWith(\"right\") ? \"rotate-180\" : \"\";\n    } else {\n      arrowRotationClass = corner?.startsWith(\"bottom\")\n        ? \"-rotate-90\"\n        : \"rotate-90\";\n    }\n  }\n\n  return (\n    <>\n      <ScanOverlay />\n      <ToolbarElementContext.Provider value={refWidget.current}>\n        <div\n          id=\"react-scan-toolbar\"\n          dir=\"ltr\"\n          ref={refWidget}\n          onPointerDown={!isCollapsed ? handleDrag : handleCollapsedDrag}\n          className={cn(\n            \"fixed inset-0\",\n            isCollapsed\n              ? (() => {\n                  const { orientation = \"horizontal\", corner } = isCollapsed;\n                  if (orientation === \"horizontal\") {\n                    return corner?.endsWith(\"right\")\n                      ? \"rounded-tl-lg rounded-bl-lg shadow-lg\"\n                      : \"rounded-tr-lg rounded-br-lg shadow-lg\";\n                  } else {\n                    return corner?.startsWith(\"bottom\")\n                      ? \"rounded-tl-lg rounded-tr-lg shadow-lg\"\n                      : \"rounded-bl-lg rounded-br-lg shadow-lg\";\n                  }\n                })()\n              : \"rounded-lg shadow-lg\",\n            \"flex flex-col\",\n            \"font-mono text-[13px]\",\n            \"user-select-none\",\n            \"opacity-0\",\n            isCollapsed ? \"cursor-pointer\" : \"cursor-move\",\n            \"z-[124124124124]\",\n            \"animate-fade-in animation-duration-300 animation-delay-300\",\n            \"will-change-transform\",\n            \"[touch-action:none]\"\n          )}\n          style={{ WebkitAppRegion: \"no-drag\" }}\n        >\n          {/* this entire feature is vibe coded don't think too hard about the code its probably very non coherent */}\n          {isCollapsed ? (\n            <button\n              type=\"button\"\n              onClick={() => {\n                signalWidgetCollapsed.value = null;\n                saveLocalStorage(LOCALSTORAGE_COLLAPSED_KEY, null);\n\n                if (\n                  refInitialMinimizedWidth.current === 0 &&\n                  refWidget.current\n                ) {\n                  requestAnimationFrame(() => {\n                    if (refWidget.current) {\n                      refWidget.current.style.width = \"min-content\";\n                      const naturalWidth = refWidget.current.offsetWidth;\n                      refInitialMinimizedWidth.current = naturalWidth || 300;\n                      updateWidgetPosition(true);\n                    }\n                  });\n                }\n\n                const savedView = readLocalStorage<WidgetStates>(\n                  LOCALSTORAGE_LAST_VIEW_KEY\n                );\n                signalWidgetViews.value = savedView || { view: \"none\" };\n              }}\n              className=\"flex items-center justify-center w-full h-full text-white\"\n              title=\"Expand toolbar\"\n            >\n              <Icon\n                name=\"icon-chevron-right\"\n                size={16}\n                className={cn(\"transition-transform\", arrowRotationClass)}\n              />\n            </button>\n          ) : (\n            <>\n              <ResizeHandle position=\"top\" />\n              <ResizeHandle position=\"bottom\" />\n              <ResizeHandle position=\"left\" />\n              <ResizeHandle position=\"right\" />\n              <Content />\n            </>\n          )}\n        </div>\n      </ToolbarElementContext.Provider>\n    </>\n  );\n};\n\nexport const ToolbarElementContext = createContext<HTMLElement | null>(null);\n"
  },
  {
    "path": "packages/scan/src/web/widget/resize-handle.tsx",
    "content": "import type { JSX } from 'preact';\nimport { useCallback, useEffect, useRef } from 'preact/hooks';\nimport { Store } from '~core/index';\nimport { Icon } from '~web/components/icon';\nimport {\n  LOCALSTORAGE_KEY,\n  MIN_CONTAINER_WIDTH,\n  MIN_SIZE,\n} from '~web/constants';\nimport {\n  signalRefWidget,\n  signalWidget,\n  signalWidgetViews,\n} from '~web/state';\nimport { cn, saveLocalStorage } from '~web/utils/helpers';\nimport {\n  calculateNewSizeAndPosition,\n  calculatePosition,\n  getClosestCorner,\n  getHandleVisibility,\n  getOppositeCorner,\n  getWindowDimensions,\n} from './helpers';\nimport type { Corner, ResizeHandleProps } from './types';\n\nexport const ResizeHandle = ({ position }: ResizeHandleProps) => {\n  const refContainer = useRef<HTMLDivElement>(null);\n\n  const prevWidth = useRef<number | null>(null);\n  const prevHeight = useRef<number | null>(null);\n  const prevCorner = useRef<Corner | null>(null);\n\n  // oxlint-disable-next-line react-hooks/exhaustive-deps\n  useEffect(() => {\n    const container = refContainer.current;\n    if (!container) return;\n\n    const updateVisibility = () => {\n      container.classList.remove('pointer-events-none');\n\n      const isFocused = Store.inspectState.value.kind === 'focused';\n      const shouldShow = signalWidgetViews.value.view !== 'none';\n      const isVisible =\n        (isFocused || shouldShow) &&\n        getHandleVisibility(\n          position,\n          signalWidget.value.corner,\n          signalWidget.value.dimensions.isFullWidth,\n          signalWidget.value.dimensions.isFullHeight,\n        );\n\n      if (isVisible) {\n        container.classList.remove(\n          'hidden',\n          'pointer-events-none',\n          'opacity-0',\n        );\n      } else {\n        container.classList.add('hidden', 'pointer-events-none', 'opacity-0');\n      }\n    };\n\n    const unsubscribeSignalWidget = signalWidget.subscribe((state) => {\n      if (\n        prevWidth.current !== null &&\n        prevHeight.current !== null &&\n        prevCorner.current !== null &&\n        state.dimensions.width === prevWidth.current &&\n        state.dimensions.height === prevHeight.current &&\n        state.corner === prevCorner.current\n      ) {\n        return;\n      }\n\n      updateVisibility();\n\n      prevWidth.current = state.dimensions.width;\n      prevHeight.current = state.dimensions.height;\n      prevCorner.current = state.corner;\n    });\n\n    const unsubscribeInspectState = Store.inspectState.subscribe(() => {\n      updateVisibility();\n    });\n\n    return () => {\n      unsubscribeSignalWidget();\n      unsubscribeInspectState();\n      prevWidth.current = null;\n      prevHeight.current = null;\n      prevCorner.current = null;\n    };\n  }, []);\n\n  // oxlint-disable-next-line react-hooks/exhaustive-deps\n  const handleResize = useCallback(\n    (e: JSX.TargetedPointerEvent<HTMLDivElement>) => {\n      e.preventDefault();\n      e.stopPropagation();\n\n      const widget = signalRefWidget.value;\n      if (!widget) return;\n\n      const containerStyle = widget.style;\n      const { dimensions } = signalWidget.value;\n      const initialX = e.clientX;\n      const initialY = e.clientY;\n\n      const initialWidth = dimensions.width;\n      const initialHeight = dimensions.height;\n      const initialPosition = dimensions.position;\n\n      signalWidget.value = {\n        ...signalWidget.value,\n        dimensions: {\n          ...dimensions,\n          isFullWidth: false,\n          isFullHeight: false,\n          width: initialWidth,\n          height: initialHeight,\n          position: initialPosition,\n        },\n      };\n\n      let rafId: number | null = null;\n\n      const handlePointerMove = (e: PointerEvent) => {\n        if (rafId) return;\n\n        containerStyle.transition = 'none';\n\n        rafId = requestAnimationFrame(() => {\n          const { newSize, newPosition } = calculateNewSizeAndPosition(\n            position,\n            { width: initialWidth, height: initialHeight },\n            initialPosition,\n            e.clientX - initialX,\n            e.clientY - initialY,\n          );\n\n          containerStyle.transform = `translate3d(${newPosition.x}px, ${newPosition.y}px, 0)`;\n          containerStyle.width = `${newSize.width}px`;\n          containerStyle.height = `${newSize.height}px`;\n\n          // Adjust components tree width when widget is resized\n          const maxTreeWidth = Math.floor(newSize.width - (MIN_CONTAINER_WIDTH / 2));\n          const currentTreeWidth = signalWidget.value.componentsTree.width;\n          const newTreeWidth = Math.min(\n            maxTreeWidth,\n            Math.max(MIN_CONTAINER_WIDTH, currentTreeWidth),\n          );\n\n          signalWidget.value = {\n            ...signalWidget.value,\n            dimensions: {\n              isFullWidth: false,\n              isFullHeight: false,\n              width: newSize.width,\n              height: newSize.height,\n              position: newPosition,\n            },\n            componentsTree: {\n              ...signalWidget.value.componentsTree,\n              width: newTreeWidth,\n            },\n          };\n\n          rafId = null;\n        });\n      };\n\n      const handlePointerUp = () => {\n        if (rafId) {\n          cancelAnimationFrame(rafId);\n          rafId = null;\n        }\n        document.removeEventListener('pointermove', handlePointerMove);\n        document.removeEventListener('pointerup', handlePointerUp);\n\n        const { dimensions, corner } = signalWidget.value;\n        const windowDims = getWindowDimensions();\n        const isCurrentFullWidth = windowDims.isFullWidth(dimensions.width);\n        const isCurrentFullHeight = windowDims.isFullHeight(dimensions.height);\n        const isFullScreen = isCurrentFullWidth && isCurrentFullHeight;\n\n        let newCorner = corner;\n        if (isFullScreen || isCurrentFullWidth || isCurrentFullHeight) {\n          newCorner = getClosestCorner(dimensions.position);\n        }\n\n        const newPosition = calculatePosition(\n          newCorner,\n          dimensions.width,\n          dimensions.height,\n        );\n\n        const onTransitionEnd = () => {\n          widget.removeEventListener('transitionend', onTransitionEnd);\n        };\n\n        widget.addEventListener('transitionend', onTransitionEnd);\n        containerStyle.transform = `translate3d(${newPosition.x}px, ${newPosition.y}px, 0)`;\n\n        signalWidget.value = {\n          ...signalWidget.value,\n          corner: newCorner,\n          dimensions: {\n            isFullWidth: isCurrentFullWidth,\n            isFullHeight: isCurrentFullHeight,\n            width: dimensions.width,\n            height: dimensions.height,\n            position: newPosition,\n          },\n          lastDimensions: {\n            isFullWidth: isCurrentFullWidth,\n            isFullHeight: isCurrentFullHeight,\n            width: dimensions.width,\n            height: dimensions.height,\n            position: newPosition,\n          },\n        };\n\n        saveLocalStorage(LOCALSTORAGE_KEY, {\n          corner: newCorner,\n          dimensions: signalWidget.value.dimensions,\n          lastDimensions: signalWidget.value.lastDimensions,\n          componentsTree: signalWidget.value.componentsTree,\n        });\n      };\n\n      document.addEventListener('pointermove', handlePointerMove, {\n        passive: true,\n      });\n      document.addEventListener('pointerup', handlePointerUp);\n    },\n    [],\n  );\n\n  // oxlint-disable-next-line react-hooks/exhaustive-deps\n  const handleDoubleClick = useCallback(\n    (e: JSX.TargetedMouseEvent<HTMLDivElement>) => {\n      e.preventDefault();\n      e.stopPropagation();\n\n      const widget = signalRefWidget.value;\n      if (!widget) return;\n\n      const containerStyle = widget.style;\n      const { dimensions, corner } = signalWidget.value;\n      const windowDims = getWindowDimensions();\n\n      const isCurrentFullWidth = windowDims.isFullWidth(dimensions.width);\n      const isCurrentFullHeight = windowDims.isFullHeight(dimensions.height);\n      const isFullScreen = isCurrentFullWidth && isCurrentFullHeight;\n      const isPartiallyMaximized =\n        (isCurrentFullWidth || isCurrentFullHeight) && !isFullScreen;\n\n      let newWidth = dimensions.width;\n      let newHeight = dimensions.height;\n      const newCorner = getOppositeCorner(\n        position,\n        corner,\n        isFullScreen,\n        isCurrentFullWidth,\n        isCurrentFullHeight,\n      );\n\n      if (position === 'left' || position === 'right') {\n        newWidth = isCurrentFullWidth ? dimensions.width : windowDims.maxWidth;\n        if (isPartiallyMaximized) {\n          newWidth = isCurrentFullWidth ? MIN_SIZE.width : windowDims.maxWidth;\n        }\n      } else {\n        newHeight = isCurrentFullHeight\n          ? dimensions.height\n          : windowDims.maxHeight;\n        if (isPartiallyMaximized) {\n          newHeight = isCurrentFullHeight\n            ? MIN_SIZE.initialHeight\n            : windowDims.maxHeight;\n        }\n      }\n\n      if (isFullScreen) {\n        if (position === 'left' || position === 'right') {\n          newWidth = MIN_SIZE.width;\n        } else {\n          newHeight = MIN_SIZE.initialHeight;\n        }\n      }\n\n      const newPosition = calculatePosition(newCorner, newWidth, newHeight);\n      const newDimensions = {\n        isFullWidth: windowDims.isFullWidth(newWidth),\n        isFullHeight: windowDims.isFullHeight(newHeight),\n        width: newWidth,\n        height: newHeight,\n        position: newPosition,\n      };\n\n      // Adjust components tree width when widget is resized\n      const maxTreeWidth = Math.floor(newWidth - MIN_SIZE.width / 2);\n      const currentTreeWidth = signalWidget.value.componentsTree.width;\n      const defaultWidth = Math.floor(newWidth * 0.3); // Use 30% of window width as default\n\n      const newTreeWidth = isCurrentFullWidth\n        ? MIN_CONTAINER_WIDTH\n        : (position === 'left' || position === 'right') && !isCurrentFullWidth\n          ? Math.min(maxTreeWidth, Math.max(MIN_CONTAINER_WIDTH, defaultWidth))\n          : Math.min(\n              maxTreeWidth,\n              Math.max(MIN_CONTAINER_WIDTH, currentTreeWidth),\n            );\n\n      requestAnimationFrame(() => {\n        signalWidget.value = {\n          corner: newCorner,\n          dimensions: newDimensions,\n          lastDimensions: dimensions,\n          componentsTree: {\n            ...signalWidget.value.componentsTree,\n            width: newTreeWidth,\n          },\n        };\n\n        containerStyle.transition = 'all 0.25s cubic-bezier(0, 0, 0.2, 1)';\n        containerStyle.width = `${newWidth}px`;\n        containerStyle.height = `${newHeight}px`;\n        containerStyle.transform = `translate3d(${newPosition.x}px, ${newPosition.y}px, 0)`;\n      });\n\n      saveLocalStorage(LOCALSTORAGE_KEY, {\n        corner: newCorner,\n        dimensions: newDimensions,\n        lastDimensions: dimensions,\n        componentsTree: {\n          ...signalWidget.value.componentsTree,\n          width: newTreeWidth,\n        },\n      });\n    },\n    [],\n  );\n\n  return (\n    <div\n      ref={refContainer}\n      onPointerDown={handleResize}\n      onDblClick={handleDoubleClick}\n      className={cn(\n        'absolute z-50',\n        'flex items-center justify-center',\n        'group',\n        'transition-colors select-none',\n        'peer',\n        {\n          'resize-left peer/left': position === 'left',\n          'resize-right peer/right z-10': position === 'right',\n          'resize-top peer/top': position === 'top',\n          'resize-bottom peer/bottom': position === 'bottom',\n        },\n      )}\n    >\n      <span className=\"resize-line-wrapper\">\n        <span className=\"resize-line\">\n          <Icon\n            name=\"icon-ellipsis\"\n            size={18}\n            className={cn(\n              'text-neutral-400',\n              (position === 'left' || position === 'right') && 'rotate-90',\n            )}\n          />\n        </span>\n      </span>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/scan/src/web/widget/types.ts",
    "content": "export interface Position {\n  x: number;\n  y: number;\n}\n\nexport interface Size {\n  width: number;\n  height: number;\n}\n\nexport type Corner = \"top-left\" | \"top-right\" | \"bottom-left\" | \"bottom-right\";\n\nexport type CollapsedPosition = {\n  corner: Corner;\n  orientation: \"horizontal\" | \"vertical\";\n};\n\nexport interface ResizeHandleProps {\n  position: Corner | \"top\" | \"bottom\" | \"left\" | \"right\";\n}\n\nexport interface WidgetDimensions {\n  isFullWidth: boolean;\n  isFullHeight: boolean;\n  width: number;\n  height: number;\n  position: Position;\n}\n\nexport interface ComponentsTreeConfig {\n  width: number;\n}\n\nexport interface WidgetConfig {\n  corner: Corner;\n  dimensions: WidgetDimensions;\n  lastDimensions: WidgetDimensions;\n  componentsTree: ComponentsTreeConfig;\n}\n\nexport interface WidgetSettings {\n  corner: Corner;\n  dimensions: WidgetDimensions;\n  lastDimensions: WidgetDimensions;\n  componentsTree: ComponentsTreeConfig;\n}\n"
  },
  {
    "path": "packages/scan/src/worker-shim.ts",
    "content": "// This gets injected into the bundle\nexport function createInlineWorker(code: string) {\n  const blob = new Blob([code], { type: 'application/javascript' });\n  const url = URL.createObjectURL(blob);\n  return new Worker(url);\n} "
  },
  {
    "path": "packages/scan/tailwind.config.mjs",
    "content": "export default {\n  content: ['./src/**/*.{js,jsx,ts,tsx}'],\n  corePlugins: {\n    preflight: true,\n  },\n  darkMode: 'class',\n  theme: {\n    extend: {\n      fontFamily: {\n        mono: [\n          'Menlo',\n          'Consolas',\n          'Monaco',\n          'Liberation Mono',\n          'Lucida Console',\n          'monospace',\n        ],\n      },\n      spacing: {\n        3.5: '0.875rem',\n        4.5: '1.125rem',\n        5.5: '1.375rem',\n        6.5: '1.625rem',\n        7.5: '1.875rem',\n        8.5: '2.125rem',\n        9.5: '2.375rem',\n      },\n      colors: {\n        inspect: '#8e61e3',\n      },\n      fontSize: {\n        xxs: '0.5rem',\n      },\n      cursor: {\n        'nwse-resize': 'nwse-resize',\n        'nesw-resize': 'nesw-resize',\n        'ns-resize': 'ns-resize',\n        'ew-resize': 'ew-resize',\n        move: 'move',\n      },\n      keyframes: {\n        fadeIn: {\n          '0%': { opacity: '0' },\n          '100%': { opacity: '1' },\n        },\n        fadeOut: {\n          '0%': { opacity: '1' },\n          '100%': { opacity: '0' },\n        },\n        shake: {\n          '0%': { transform: 'translateX(0)' },\n          '25%': { transform: 'translateX(-5px)' },\n          '50%': { transform: 'translateX(5px)' },\n          '75%': { transform: 'translateX(-5px)' },\n          '100%': { transform: 'translateX(0)' },\n        },\n        rotate: {\n          '0%': { transform: 'rotate(0deg)' },\n          '100%': { transform: 'rotate(360deg)' },\n        },\n        countFlash: {\n          '0%': {\n            backgroundColor: 'rgba(168, 85, 247, 0.3)',\n            transform: 'scale(1.05)',\n          },\n          '100%': {\n            backgroundColor: 'rgba(168, 85, 247, 0.1)',\n            transform: 'scale(1)',\n          },\n        },\n        countFlashShake: {\n          '0%': { transform: 'translateX(0)' },\n          '25%': { transform: 'translateX(-5px)' },\n          '50%': {\n            transform: 'translateX(5px) scale(1.1)',\n          },\n          '75%': { transform: 'translateX(-5px)' },\n          '100%': { transform: 'translateX(0)' },\n        },\n      },\n      animation: {\n        'fade-in': 'fadeIn ease-in forwards',\n        'fade-out': 'fadeOut ease-out forwards',\n        rotate: 'rotate linear infinite',\n        shake: 'shake .4s ease-in-out forwards',\n        'count-flash': 'countFlash .3s ease-out forwards',\n        'count-flash-shake': 'countFlashShake .3s ease-out forwards',\n      },\n      zIndex: {\n        100: 100,\n      },\n      borderWidth: {\n        1: '1px',\n      },\n    },\n  },\n  safelist: [\n    'cursor-nwse-resize',\n    'cursor-nesw-resize',\n    'cursor-ns-resize',\n    'cursor-ew-resize',\n    'cursor-move',\n  ],\n  plugins: [\n    ({ addUtilities }) => {\n      const newUtilities = {\n        '.pointer-events-bounding-box': {\n          'pointer-events': 'bounding-box',\n        },\n      };\n      addUtilities(newUtilities);\n    },\n    ({ addUtilities }) => {\n      const newUtilities = {\n        '.animation-duration-0': {\n          'animation-duration': '0s',\n        },\n        '.animation-delay-0': {\n          'animation-delay': '0s',\n        },\n        '.animation-duration-100': {\n          'animation-duration': '.1s',\n        },\n        '.animation-delay-100': {\n          'animation-delay': '.1s',\n        },\n        '.animation-delay-150': {\n          'animation-delay': '.15s',\n        },\n        '.animation-duration-200': {\n          'animation-duration': '.2s',\n        },\n        '.animation-delay-200': {\n          'animation-delay': '.2s',\n        },\n        '.animation-duration-300': {\n          'animation-duration': '.3s',\n        },\n        '.animation-delay-300': {\n          'animation-delay': '.3s',\n        },\n        '.animation-duration-500': {\n          'animation-duration': '.5s',\n        },\n        '.animation-delay-500': {\n          'animation-delay': '.5s',\n        },\n        '.animation-duration-700': {\n          'animation-duration': '.7s',\n        },\n        '.animation-delay-700': {\n          'animation-delay': '.7s',\n        },\n        '.animation-duration-1000': {\n          'animation-duration': '1s',\n        },\n        '.animation-delay-1000': {\n          'animation-delay': '1s',\n        },\n      };\n\n      addUtilities(newUtilities);\n    },\n  ],\n};\n"
  },
  {
    "path": "packages/scan/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"rootDir\": \"src\",\n    \"outDir\": \"dist\",\n    \"baseUrl\": \".\",\n    \"types\": [\n      \"node\"\n    ],\n    \"paths\": {\n      \"~web/*\": [\n        \"src/web/*\"\n      ],\n      \"~core/*\": [\n        \"src/core/*\"\n      ],\n      \"~monitor/*\": [\n        \"src/monitor/*\"\n      ],\n      \"~instrumentation/*\": [\n        \"src/instrumentation/*\"\n      ]\n    }\n  },\n  \"include\": [\n    \"src\",\n    \"global.d.ts\"\n  ],\n  \"exclude\": [\n    \"node_modules\",\n    \"dist\"\n  ]\n}\n"
  },
  {
    "path": "packages/scan/tsup.config.ts",
    "content": "import * as fs from 'node:fs';\nimport fsPromise from 'node:fs/promises';\nimport path from 'node:path';\nimport { TsconfigPathsPlugin } from '@esbuild-plugins/tsconfig-paths';\nimport { init, parse } from 'es-module-lexer';\nimport { defineConfig } from 'tsup';\nimport { workerPlugin } from './worker-plugin';\n\nconst DIST_PATH = './dist';\n\nconst addDirectivesToChunkFiles = async (readPath: string): Promise<void> => {\n  try {\n    const files = await fsPromise.readdir(readPath);\n    for (const file of files) {\n      if (file.endsWith('.mjs') || file.endsWith('.js')) {\n        const filePath = path.join(readPath, file);\n        const data = await fsPromise.readFile(filePath, 'utf8');\n        const updatedContent = `'use client';\\n${data}`;\n        await fsPromise.writeFile(filePath, updatedContent, 'utf8');\n      }\n    }\n  } catch (err) {\n    // oxlint-disable-next-line no-console\n    console.error('Error:', err);\n  }\n};\n\nconst banner = `/**\n * Copyright 2025 Aiden Bai, Million Software, Inc.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software\n * and associated documentation files (the “Software”), to deal in the Software without restriction,\n * including without limitation the rights to use, copy, modify, merge, publish, distribute,\n * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or\n * substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */`;\n\nvoid (async () => {\n  await init;\n\n  if (fs.existsSync(DIST_PATH)) {\n    fs.rmSync(DIST_PATH, { recursive: true });\n  }\n  fs.mkdirSync(DIST_PATH, { recursive: true });\n\n  const code = fs.readFileSync('./src/core/index.ts', 'utf8');\n  const [_, allExports] = parse(code);\n  const names: Array<string> = [];\n  for (const exportItem of allExports) {\n    names.push(exportItem.n);\n  }\n\n  const createFn = (name: string) => `export let ${name}=()=>{}`;\n  const createVar = (name: string) => `export let ${name}=undefined`;\n\n  let script = '';\n  for (const name of names) {\n    if (name[0].toLowerCase() === name[0]) {\n      script += `${createFn(name)}\\n`;\n      continue;\n    }\n    script += `${createVar(name)}\\n`;\n  }\n\n  setTimeout(() => {\n    for (const ext of ['js', 'mjs', 'global.js']) {\n      fs.writeFileSync(`./dist/rsc-shim.${ext}`, script);\n    }\n  }, 500);\n})();\n\nexport default defineConfig([\n  {\n    entry: ['./src/auto.ts', './src/install-hook.ts'],\n    outDir: DIST_PATH,\n    banner: {\n      js: banner,\n    },\n    splitting: false,\n    clean: false,\n    sourcemap: false,\n    format: ['iife'],\n    target: 'esnext',\n    platform: 'browser',\n    treeshake: true,\n    dts: true,\n    minify: process.env.NODE_ENV === 'production' ? 'terser' : false,\n    env: {\n      NODE_ENV: process.env.NODE_ENV ?? 'development',\n    },\n    external: [\n      'react',\n      'react-dom',\n      'next',\n      'next/navigation',\n      'react-router',\n      'react-router-dom',\n      '@remix-run/react',\n    ],\n    esbuildPlugins: [workerPlugin],\n    loader: {\n      '.css': 'text',\n      '.worker.js': 'text',\n    },\n  },\n  {\n    entry: [\n      './src/index.ts',\n      './src/install-hook.ts',\n      './src/core/all-environments.ts',\n    ],\n    banner: {\n      js: banner,\n    },\n    outDir: DIST_PATH,\n    splitting: false,\n    clean: false,\n    sourcemap: false,\n    format: ['cjs', 'esm'],\n    target: 'esnext',\n    platform: 'browser',\n    // FIXME: tree shaking removes use client directive\n    // Info: vercel analytics does the same thing- https://github.com/vercel/analytics/blob/main/packages/web/tsup.config.js\n    treeshake: false,\n    dts: true,\n    watch: process.env.NODE_ENV === 'development',\n    async onSuccess() {\n      await addDirectivesToChunkFiles(DIST_PATH);\n    },\n    minify: false,\n    env: {\n      NODE_ENV: process.env.NODE_ENV ?? 'development',\n      NPM_PACKAGE_VERSION: JSON.parse(\n        fs.readFileSync(\n          path.join(__dirname, '../scan', 'package.json'),\n          'utf8',\n        ),\n      ).version,\n    },\n    external: [\n      'react',\n      'react-dom',\n      'next',\n      'next/navigation',\n      'react-router',\n      'react-router-dom',\n      '@remix-run/react',\n      'preact',\n      '@preact/signals',\n    ],\n    loader: {\n      '.css': 'text',\n    },\n    esbuildPlugins: [\n      workerPlugin,\n      TsconfigPathsPlugin({\n        tsconfig: path.resolve(__dirname, './tsconfig.json'),\n      }),\n    ],\n  },\n  {\n    entry: ['./src/cli.mts'],\n    outDir: DIST_PATH,\n    banner: {\n      js: banner,\n    },\n    splitting: false,\n    clean: false,\n    sourcemap: false,\n    format: ['cjs'],\n    target: 'esnext',\n    platform: 'node',\n    minify: false,\n    env: {\n      NODE_ENV: process.env.NODE_ENV ?? 'development',\n      NPM_PACKAGE_VERSION: JSON.parse(\n        fs.readFileSync(\n          path.join(__dirname, '../scan', 'package.json'),\n          'utf8',\n        ),\n      ).version,\n    },\n    watch: process.env.NODE_ENV === 'development',\n  },\n  {\n    entry: [\n      './src/react-component-name/index.ts',\n      './src/react-component-name/vite.ts',\n      './src/react-component-name/webpack.ts',\n      './src/react-component-name/esbuild.ts',\n      './src/react-component-name/rspack.ts',\n      './src/react-component-name/rolldown.ts',\n      './src/react-component-name/rollup.ts',\n      './src/react-component-name/astro.ts',\n    ],\n    outDir: `${DIST_PATH}/react-component-name`,\n    splitting: false,\n    sourcemap: false,\n    clean: false,\n    format: ['cjs', 'esm'],\n    target: 'esnext',\n    external: [\n      'unplugin',\n      'estree-walker',\n      '@rollup/pluginutils',\n      '@babel/types',\n      '@babel/parser',\n      '@babel/traverse',\n      '@babel/generator',\n      '@babel/core',\n      'rollup',\n      'webpack',\n      'esbuild',\n      'rspack',\n      'vite',\n    ],\n    dts: true,\n    minify: false,\n    treeshake: true,\n    env: {\n      NODE_ENV: process.env.NODE_ENV || 'development',\n    },\n    outExtension: ({ format }) => ({\n      js: format === 'esm' ? '.mjs' : '.js',\n    }),\n    esbuildOptions: (options, context) => {\n      options.mainFields = ['module', 'main'];\n      options.conditions = ['import', 'require', 'node', 'default'];\n      options.format = context.format === 'esm' ? 'esm' : 'cjs';\n      options.preserveSymlinks = true;\n    },\n  },\n]);\n"
  },
  {
    "path": "packages/scan/vite.config.mts",
    "content": "import tsconfigPaths from 'vite-tsconfig-paths';\nimport { defineConfig } from 'vitest/config';\n\nexport default defineConfig({\n  plugins: [tsconfigPaths()],\n});\n"
  },
  {
    "path": "packages/scan/worker-plugin.ts",
    "content": "import * as esbuild from 'esbuild';\n\n/**\n * A hacky plugin to build the worker file (resolving all imports), and inline\n * the javascript into a variable by replacing __WORKER_CODE__ string in bundle with the worker\n * build output\n */\nexport const workerPlugin = {\n  name: 'worker-plugin',\n  setup(build) {\n    const workerResult = esbuild.buildSync({\n      entryPoints: ['src/new-outlines/offscreen-canvas.worker.ts'],\n      bundle: true,\n      write: false,\n      format: 'iife',\n      platform: 'browser',\n      minify: true,\n    });\n    const workerCode = workerResult.outputFiles[0].text;\n\n    build.onEnd((result) => {\n      if (!result.outputFiles) return;\n\n      for (const file of result.outputFiles) {\n        const newText = file.text.replace(\n          'var workerCode = \"__WORKER_CODE__\"',\n          `var workerCode = ${JSON.stringify(workerCode)}`,\n        );\n        file.contents = Buffer.from(newText);\n      }\n    });\n  },\n};\n"
  },
  {
    "path": "packages/vite-plugin-react-scan/.npmignore",
    "content": "# Source\nsrc/\n\n# Config files\ntsconfig.json\n.biome*\nbiome.json\n\n# Development files\nnode_modules/\n*.log\n.DS_Store\n"
  },
  {
    "path": "packages/vite-plugin-react-scan/CHANGELOG.md",
    "content": "# @react-scan/vite-plugin-react-scan\n\n## 0.2.3\n\n### Patch Changes\n\n- fix\n- Updated dependencies\n  - react-scan@0.5.3\n\n## 0.2.2\n\n### Patch Changes\n\n- fix\n- Updated dependencies\n  - react-scan@0.5.2\n\n## 0.2.1\n\n### Patch Changes\n\n- fix: infinite mounting\n- Updated dependencies\n  - react-scan@0.5.1\n\n## 0.2.0\n\n### Minor Changes\n\n- cleanup\n\n### Patch Changes\n\n- Updated dependencies\n- Updated dependencies [9d38ffe]\n  - react-scan@0.5.0\n"
  },
  {
    "path": "packages/vite-plugin-react-scan/LICENSE",
    "content": "Copyright 2025 Aiden Bai, Million Software, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "packages/vite-plugin-react-scan/README.md",
    "content": "# @react-scan/vite-plugin-react-scan\n\nA Vite plugin that integrates React Scan into your Vite application, automatically detecting performance issues in your React components.\n\n## Installation\n\n```bash\n# npm\nnpm install -D @react-scan/vite-plugin-react-scan react-scan\n\n# pnpm\npnpm add -D @react-scan/vite-plugin-react-scan react-scan\n\n# yarn\nyarn add -D @react-scan/vite-plugin-react-scan react-scan\n```\n\n> **Note:** Make sure `react-scan` is installed as a peer dependency. The plugin will automatically locate it in your project's dependency tree.\n\n## Usage\n\nAdd the plugin to your `vite.config.ts`:\n\n```ts\nimport { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\nimport reactScan from '@react-scan/vite-plugin-react-scan';\n\nexport default defineConfig({\n  plugins: [\n    react(),\n    reactScan({\n      // options (optional)\n    }),\n  ],\n});\n```\n\n## Options\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `enable` | `boolean` | `process.env.NODE_ENV === 'development'` | Enable/disable scanning |\n| `scanOptions` | `object` | `{ ... }` | Custom React Scan options |\n| `autoDisplayNames` | `boolean` | `false` | Automatically add display names to React components |\n| `debug` | `boolean` | `false` | Enable debug logging |\n\n## Example Configuration\n\n```ts\nimport { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\nimport reactScan from '@react-scan/vite-plugin-react-scan';\n\nexport default defineConfig({\n  plugins: [\n    react(),\n    reactScan({\n      enable: true,\n      autoDisplayNames: true,\n      scanOptions: {} // React Scan specific options\n    }),\n  ],\n});\n```\n\n## Development vs Production\n\n- In development: The plugin injects React Scan directly into your application for real-time analysis\n- In production: The plugin can be disabled/enabled by default with specific options\n\n## Contributing\n\nContributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details.\n\n## License\n\nReact Scan Vite Plugin is [MIT-licensed](LICENSE) open-source software by Aiden Bai, [Million Software, Inc.](https://million.dev), and [contributors](https://github.com/aidenybai/react-scan/graphs/contributors).\n"
  },
  {
    "path": "packages/vite-plugin-react-scan/package.json",
    "content": "{\n  \"name\": \"@react-scan/vite-plugin-react-scan\",\n  \"version\": \"0.2.3\",\n  \"description\": \"A Vite plugin for React Scan - detects performance issues in your React app.\",\n  \"type\": \"module\",\n  \"main\": \"dist/index.js\",\n  \"module\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"scripts\": {\n    \"build\": \"rimraf dist && tsc\",\n    \"postbuild\": \"node ../../scripts/version-warning.mjs\",\n    \"lint\": \"oxlint src && pnpm typecheck\",\n    \"typecheck\": \"tsc --noEmit\",\n    \"prepublishOnly\": \"rimraf dist && npm run build\"\n  },\n  \"peerDependenciesMeta\": {\n    \"react-scan\": {\n      \"optional\": false\n    },\n    \"vite\": {\n      \"optional\": false\n    }\n  },\n  \"keywords\": [\n    \"react\",\n    \"react-scan\",\n    \"react scan\",\n    \"render\",\n    \"performance\",\n    \"vite\",\n    \"vite plugin\"\n  ],\n  \"author\": {\n    \"name\": \"Team React Scan\",\n    \"url\": \"https://github.com/aidenybai/react-scan\"\n  },\n  \"license\": \"MIT\",\n  \"homepage\": \"https://github.com/aidenybai/react-scan\",\n  \"bugs\": {\n    \"url\": \"https://github.com/aidenybai/react-scan/issues\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/aidenybai/react-scan.git\"\n  },\n  \"dependencies\": {\n    \"@babel/core\": \"^7.26.0\",\n    \"@babel/plugin-transform-react-jsx\": \"^7.25.9\",\n    \"@babel/preset-typescript\": \"^7.23.3\",\n    \"babel-plugin-add-react-displayname\": \"^0.0.5\",\n    \"cheerio\": \"^1.0.0\"\n  },\n  \"peerDependencies\": {\n    \"react-scan\": \">=0.5.3\",\n    \"vite\": \"^2 || ^3 || ^4 || ^5 || ^6\"\n  },\n  \"devDependencies\": {\n    \"@types/babel__core\": \"^7.20.5\",\n    \"@types/cheerio\": \"^0.22.35\",\n    \"@types/node\": \"^22.10.7\",\n    \"typescript\": \"latest\",\n    \"vite\": \"^6.3.0\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"files\": [\n    \"dist\",\n    \"README.md\",\n    \"LICENSE\"\n  ]\n}\n"
  },
  {
    "path": "packages/vite-plugin-react-scan/src/global.d.ts",
    "content": "declare module 'babel-plugin-add-react-displayname/index.js';\n"
  },
  {
    "path": "packages/vite-plugin-react-scan/src/index.ts",
    "content": "import fs from 'node:fs';\nimport path from 'node:path';\nimport { transformAsync } from '@babel/core';\nimport babelPluginReactDisplayName from 'babel-plugin-add-react-displayname/index.js';\nimport * as cheerio from 'cheerio';\nimport type { Options } from 'react-scan';\nimport type { Plugin, ResolvedConfig } from 'vite';\n\nasync function resolveModuleFileContent(moduleName: string, startDir: string = process.cwd()) {\n  const modulePath = path.join('node_modules', moduleName);\n  const resolvedPath = path.resolve(startDir, modulePath);\n\n  try {\n    return await fs.promises.readFile(resolvedPath, 'utf-8');\n  } catch {\n    const parentDir = path.dirname(startDir);\n    if (parentDir !== startDir) {\n      return resolveModuleFileContent(moduleName, parentDir);\n    }\n    throw new Error(`Module ${moduleName} not found`);\n  }\n}\n\ninterface Logger {\n  debug: (...args: unknown[]) => void;\n  info: (...args: unknown[]) => void;\n  warn: (...args: unknown[]) => void;\n  error: (...args: unknown[]) => void;\n}\n\nconst createLogger = (prefix: string, debug = false): Logger => {\n  return {\n    debug: (...args: unknown[]) =>\n      debug && process.stdout.write(`[${prefix}] ${args.join(' ')}\\n`),\n    info: (...args: unknown[]) =>\n      process.stdout.write(`[${prefix}] ${args.join(' ')}\\n`),\n    warn: (...args: unknown[]) =>\n      process.stderr.write(`[${prefix}] WARN: ${args.join(' ')}\\n`),\n    error: (...args: unknown[]) =>\n      process.stderr.write(`[${prefix}] ERROR: ${args.join(' ')}\\n`),\n  };\n};\n\ninterface ReactScanPluginOptions {\n  /**\n   * Enable/disable scanning\n   * @default process.env.NODE_ENV === 'development'\n   */\n  enable?: boolean;\n\n  /**\n   * Custom React Scan options\n   */\n  scanOptions?: Options;\n\n  /**\n   * Enable debug logging\n   * @default false\n   */\n  debug?: boolean;\n\n  /**\n   * Automatically add display names to React components\n   * @default true\n   */\n  autoDisplayNames?: boolean;\n}\n\nconst PLUGIN_NAME = 'vite-plugin-react-scan';\n\nconst DEFAULT_SCAN_OPTIONS: Partial<Options> = {};\n\nconst validateOptions = (options: ReactScanPluginOptions) => {\n  if (options.scanOptions && typeof options.scanOptions !== 'object') {\n    throw new Error('scanOptions must be an object');\n  }\n\n  if (options.enable !== undefined && typeof options.enable !== 'boolean') {\n    throw new Error('enable must be a boolean');\n  }\n\n  if (options.debug !== undefined && typeof options.debug !== 'boolean') {\n    throw new Error('debug must be a boolean');\n  }\n\n  if (\n    options.autoDisplayNames !== undefined &&\n    typeof options.autoDisplayNames !== 'boolean'\n  ) {\n    throw new Error('autoDisplayNames must be a boolean');\n  }\n};\n\nconst JSX_EXTENSIONS = ['.jsx', '.tsx'] as const;\nconst REACT_SCAN_IDENTIFIER = 'react-scan';\n\nconst isJsxFile = (id: string) =>\n  JSX_EXTENSIONS.some((ext) => id.endsWith(ext));\n\nconst reactScanPlugin = (options: ReactScanPluginOptions = {}): Plugin => {\n  validateOptions(options);\n  const {\n    enable = process.env.NODE_ENV === 'development',\n    scanOptions = DEFAULT_SCAN_OPTIONS,\n    debug = false,\n    autoDisplayNames = false,\n  } = options;\n\n  let config: ResolvedConfig;\n  let isBuild = false;\n  let scanFilePath = '';\n  let assetsDir = '';\n\n  const log = createLogger(PLUGIN_NAME, debug);\n\n  const generateScanScript = (options: Options = {}) => {\n    const hasOptions = Object.keys(options).length > 0;\n\n    if (isBuild) {\n      // Create a proper JSON string for the options and wrap it in single quotes\n      const optionsJson = hasOptions ? `${JSON.stringify(options)}` : '{}';\n\n      return `\n        <script>\n          const runScan = (options) => {\n            if (reactScan){\n              reactScan(${optionsJson});\n            }\n          };\n        </script>\n        <script src=\"${scanFilePath}\" onload=\"runScan()\"></script>\n      `;\n    }\n\n    // Development version - use the base path from config\n    const base = config.base || '/';\n    return `\n    <script type=\"module\">\n      import { scan } from '${base}@id/react-scan';\n      (async () => {\n        try {\n          scan(${hasOptions ? JSON.stringify(options) : ''});\n        } catch (error) {\n          console.error('[${PLUGIN_NAME}] Scan failed:', error);\n        }\n      })();\n    </script>`;\n  };\n\n  return {\n    name: PLUGIN_NAME,\n    enforce: 'pre',\n\n    config(config) {\n      return {\n        optimizeDeps: {\n          exclude: [...(config.optimizeDeps?.exclude || []), 'react-scan'],\n        },\n      };\n    },\n\n    transform: async (code, id) => {\n      if (!autoDisplayNames || !isJsxFile(id)) {\n        return null;\n      }\n\n      try {\n        const result = await transformAsync(code, {\n          presets: [\n            [\n              '@babel/preset-typescript',\n              {\n                isTSX: isJsxFile(id),\n                allExtensions: true,\n              },\n            ],\n          ],\n          plugins: [\n            [\n              '@babel/plugin-transform-react-jsx',\n              {\n                runtime: 'automatic',\n              },\n            ],\n            babelPluginReactDisplayName,\n          ],\n          filename: id,\n          configFile: false,\n          babelrc: false,\n        });\n\n        if (!result?.code) {\n          log.warn(`No code generated for ${id}`);\n          return null;\n        }\n\n        log.debug(`Successfully transformed ${id}`);\n        return { code: result.code };\n      } catch (error) {\n        log.error(`Failed to transform ${id}:`, error);\n        return null;\n      }\n    },\n\n    configResolved(resolvedConfig) {\n      config = resolvedConfig;\n      isBuild = config.command === 'build';\n      assetsDir = config.build?.assetsDir || 'assets';\n      const base = config.base || '/';\n\n      // Ensure base path is properly formatted\n      scanFilePath = path.posix.join(base, assetsDir, 'auto.global.js');\n\n      log.debug('Plugin initialized with config:', {\n        mode: config.mode,\n        base,\n        enable,\n        isBuild,\n        assetsDir,\n        scanOptions,\n        scanFilePath,\n      });\n    },\n\n    transformIndexHtml(html) {\n      if (!enable) {\n        log.debug('Plugin disabled');\n        return html;\n      }\n\n      try {\n        const $ = cheerio.load(html);\n        const scanScript = generateScanScript(scanOptions);\n\n        // Remove any existing React Scan script to avoid duplicates\n        let removedCount = 0;\n        $('script').each((_index: number, element: cheerio.Element) => {\n          const content = $(element).html() || '';\n          if (content.includes(REACT_SCAN_IDENTIFIER)) {\n            $(element).remove();\n            removedCount++;\n          }\n        });\n\n        if (removedCount > 0) {\n          log.debug(`Removed ${removedCount} existing scan script(s)`);\n        }\n\n        if (isBuild) {\n          // In build, insert at the beginning of head\n          $('head').prepend(scanScript);\n          log.debug(\n            'Injected scan script at the beginning of head (build)',\n          );\n        } else {\n          // In development, insert after Vite's client script\n          const viteClientScript = $('script[src=\"/@vite/client\"]');\n          if (viteClientScript.length) {\n            viteClientScript.after(scanScript);\n            log.debug('Injected scan script after Vite client (serve)');\n          } else {\n            $('head').append(scanScript);\n            log.debug('Injected scan script at end of head (serve)');\n          }\n        }\n\n        return $.html();\n      } catch (error) {\n        log.error('Failed to transform HTML:', error);\n        return html;\n      }\n    },\n\n    resolveId(id) {\n      if (!isBuild && id === `/@id/${REACT_SCAN_IDENTIFIER}`) {\n        log.debug('Resolving react-scan module');\n        return REACT_SCAN_IDENTIFIER;\n      }\n      return null;\n    },\n\n    async generateBundle() {\n      if (isBuild && enable) {\n        log.debug('Build started, processing react-scan');\n\n        try {\n          const moduleNamePath = `${REACT_SCAN_IDENTIFIER}/dist/auto.global.js`;\n          const content = await resolveModuleFileContent(moduleNamePath);\n\n          // Let Vite handle the file placement in configured assets directory\n          const assetFileName = `${assetsDir}/auto.global.js`;\n\n          // Emit the file to the build output\n          this.emitFile({\n            type: 'asset',\n            fileName: assetFileName,\n            source: content,\n          });\n\n          // Store the full path for use in the script tag\n          scanFilePath = `/${assetFileName}`;\n          log.debug('Emitted react-scan as asset:', assetFileName);\n        } catch (error) {\n          log.error('Failed to process react-scan:', error);\n          throw new Error(\n            `Unable to locate '${REACT_SCAN_IDENTIFIER}'. This module is a required peer dependency.\nPlease ensure 'react-scan' is installed in your project using your preferred package manager.`,\n          );\n        }\n      }\n    },\n\n    buildEnd() {\n      if (isBuild) {\n        log.debug('Build completed');\n      }\n    },\n  };\n};\n\nexport default reactScanPlugin;\n"
  },
  {
    "path": "packages/vite-plugin-react-scan/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES6\",\n    \"module\": \"ES6\",\n    \"lib\": [\n      \"es2016\"\n    ],\n    \"moduleResolution\": \"node\",\n    \"esModuleInterop\": true,\n    \"strict\": true,\n    \"skipLibCheck\": true,\n    \"declaration\": true,\n    \"isolatedModules\": true,\n    \"resolveJsonModule\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"outDir\": \"dist\",\n    \"rootDir\": \"src\"\n  },\n  \"include\": [\n    \"src\"\n  ],\n  \"exclude\": [\n    \"node_modules\",\n    \"dist\"\n  ]\n}\n"
  },
  {
    "path": "packages/website/.gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.*\n.yarn/*\n!.yarn/patches\n!.yarn/plugins\n!.yarn/releases\n!.yarn/versions\n\n# testing\n/coverage\n\n# next.js\n/.next/\n/out/\n\n# production\n/build\n\n# misc\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# env files (can opt-in for committing if needed)\n.env*\n\n# vercel\n.vercel\n\n# typescript\n*.tsbuildinfo\nnext-env.d.ts\n"
  },
  {
    "path": "packages/website/.oxlintrc.json",
    "content": "{\n  \"$schema\": \"../../node_modules/oxlint/configuration_schema.json\",\n  \"extends\": [\"../../.oxlintrc.json\"],\n  \"plugins\": [\"nextjs\"],\n  \"rules\": {\n    \"react/no-danger\": \"off\"\n  }\n}\n"
  },
  {
    "path": "packages/website/AGENTS.md",
    "content": "Concise rules for building accessible, fast, delightful UIs Use MUST/SHOULD/NEVER to guide decisions\n\n## Interactions\n\n- Keyboard\n  - MUST: Full keyboard support per [WAI-ARIA APG](https://www.w3.org/WAI/ARIA/apg/patterns/)\n  - MUST: Visible focus rings (`:focus-visible`; group with `:focus-within`)\n  - MUST: Manage focus (trap, move, and return) per APG patterns\n- Targets & input\n  - MUST: Hit target ≥24px (mobile ≥44px) If visual <24px, expand hit area\n  - MUST: Mobile `<input>` font-size ≥16px or set:\n    ```html\n    <meta\n      name=\"viewport\"\n      content=\"width=device-width, initial-scale=1, maximum-scale=1, viewport-fit=cover\"\n    />\n    ```\n  - NEVER: Disable browser zoom\n  - MUST: `touch-action: manipulation` to prevent double-tap zoom; set `-webkit-tap-highlight-color` to match design\n- Inputs & forms (behavior)\n  - MUST: Hydration-safe inputs (no lost focus/value)\n  - NEVER: Block paste in `<input>/<textarea>`\n  - MUST: Loading buttons show spinner and keep original label\n  - MUST: Enter submits focused text input In `<textarea>`, ⌘/Ctrl+Enter submits; Enter adds newline\n  - MUST: Keep submit enabled until request starts; then disable, show spinner, use idempotency key\n  - MUST: Don't block typing; accept free text and validate after\n  - MUST: Allow submitting incomplete forms to surface validation\n  - MUST: Errors inline next to fields; on submit, focus first error\n  - MUST: `autocomplete` + meaningful `name`; correct `type` and `inputmode`\n  - SHOULD: Disable spellcheck for emails/codes/usernames\n  - SHOULD: Placeholders end with ellipsis and show example pattern (eg, `+1 (123) 456-7890`, `sk-012345…`)\n  - MUST: Warn on unsaved changes before navigation\n  - MUST: Compatible with password managers & 2FA; allow pasting one-time codes\n  - MUST: Trim values to handle text expansion trailing spaces\n  - MUST: No dead zones on checkboxes/radios; label+control share one generous hit target\n- State & navigation\n  - MUST: URL reflects state (deep-link filters/tabs/pagination/expanded panels) Prefer libs like [nuqs](https://nuqs.dev)\n  - MUST: Back/Forward restores scroll\n  - MUST: Links are links—use `<a>/<Link>` for navigation (support Cmd/Ctrl/middle-click)\n- Feedback\n  - SHOULD: Optimistic UI; reconcile on response; on failure show error and rollback or offer Undo\n  - MUST: Confirm destructive actions or provide Undo window\n  - MUST: Use polite `aria-live` for toasts/inline validation\n  - SHOULD: Ellipsis (`…`) for options that open follow-ups (eg, \"Rename…\") and loading states (eg, \"Loading…\", \"Saving…\", \"Generating…\")\n- Touch/drag/scroll\n  - MUST: Design forgiving interactions (generous targets, clear affordances; avoid finickiness)\n  - MUST: Delay first tooltip in a group; subsequent peers no delay\n  - MUST: Intentional `overscroll-behavior: contain` in modals/drawers\n  - MUST: During drag, disable text selection and set `inert` on dragged element/containers\n  - MUST: No \"dead-looking\" interactive zones—if it looks clickable, it is\n- Autofocus\n  - SHOULD: Autofocus on desktop when there's a single primary input; rarely on mobile (to avoid layout shift)\n\n## Animation\n\n- MUST: Honor `prefers-reduced-motion` (provide reduced variant)\n- SHOULD: Prefer CSS > Web Animations API > JS libraries\n- MUST: Animate compositor-friendly props (`transform`, `opacity`); avoid layout/repaint props (`top/left/width/height`)\n- SHOULD: Animate only to clarify cause/effect or add deliberate delight\n- SHOULD: Choose easing to match the change (size/distance/trigger)\n- MUST: Animations are interruptible and input-driven (avoid autoplay)\n- MUST: Correct `transform-origin` (motion starts where it \"physically\" should)\n\n## Layout\n\n- SHOULD: Optical alignment; adjust by ±1px when perception beats geometry\n- MUST: Deliberate alignment to grid/baseline/edges/optical centers—no accidental placement\n- SHOULD: Balance icon/text lockups (stroke/weight/size/spacing/color)\n- MUST: Verify mobile, laptop, ultra-wide (simulate ultra-wide at 50% zoom)\n- MUST: Respect safe areas (use env(safe-area-inset-\\*))\n- MUST: Avoid unwanted scrollbars; fix overflows\n\n## Content & Accessibility\n\n- SHOULD: Inline help first; tooltips last resort\n- MUST: Skeletons mirror final content to avoid layout shift\n- MUST: `<title>` matches current context\n- MUST: No dead ends; always offer next step/recovery\n- MUST: Design empty/sparse/dense/error states\n- SHOULD: Curly quotes (\" \"); avoid widows/orphans\n- MUST: Tabular numbers for comparisons (`font-variant-numeric: tabular-nums` or a mono like Geist Mono)\n- MUST: Redundant status cues (not color-only); icons have text labels\n- MUST: Don't ship the schema—visuals may omit labels but accessible names still exist\n- MUST: Use the ellipsis character `…` (not ``)\n- MUST: `scroll-margin-top` on headings for anchored links; include a \"Skip to content\" link; hierarchical `<h1–h6>`\n- MUST: Resilient to user-generated content (short/avg/very long)\n- MUST: Locale-aware dates/times/numbers/currency\n- MUST: Accurate names (`aria-label`), decorative elements `aria-hidden`, verify in the Accessibility Tree\n- MUST: Icon-only buttons have descriptive `aria-label`\n- MUST: Prefer native semantics (`button`, `a`, `label`, `table`) before ARIA\n- SHOULD: Right-clicking the nav logo surfaces brand assets\n- MUST: Use non-breaking spaces to glue terms: `10&nbsp;MB`, `⌘&nbsp;+&nbsp;K`, `Vercel&nbsp;SDK`\n\n## Performance\n\n- SHOULD: Test iOS Low Power Mode and macOS Safari\n- MUST: Measure reliably (disable extensions that skew runtime)\n- MUST: Track and minimize re-renders (React DevTools/React Scan)\n- MUST: Profile with CPU/network throttling\n- MUST: Batch layout reads/writes; avoid unnecessary reflows/repaints\n- MUST: Mutations (`POST/PATCH/DELETE`) target <500 ms\n- SHOULD: Prefer uncontrolled inputs; make controlled loops cheap (keystroke cost)\n- MUST: Virtualize large lists (eg, `virtua`)\n- MUST: Preload only above-the-fold images; lazy-load the rest\n- MUST: Prevent CLS from images (explicit dimensions or reserved space)\n\n## Design\n\n- SHOULD: Layered shadows (ambient + direct)\n- SHOULD: Crisp edges via semi-transparent borders + shadows\n- SHOULD: Nested radii: child ≤ parent; concentric\n- SHOULD: Hue consistency: tint borders/shadows/text toward bg hue\n- MUST: Accessible charts (color-blind-friendly palettes)\n- MUST: Meet contrast—prefer [APCA](https://apcacontrast.com/) over WCAG 2\n- MUST: Increase contrast on `:hover/:active/:focus`\n- SHOULD: Match browser UI to bg\n- SHOULD: Avoid gradient banding (use masks when needed)\n\n## Copywriting\n\n- MUST: Use active voice.\n- MUST: Use title case for headings and buttons.\n- MUST: Be clear and concise. Use as few words as possible.\n- MUST: Prefer & over and.\n- MUST: Use action-oriented language.\n- MUST: Keep nouns consistent. Introduce as few unique terms as possible.\n- MUST: Write in second person. Avoid first person.\n- MUST: Use consistent placeholders.\n- MUST: Use numerals for counts.\n- MUST: Use consistent currency formatting.\n- MUST: Separate numbers & units with a space.\n- MUST: Use a non-breaking space e.g., 10&nbsp;MB.\n- MUST: Default to positive language. Frame messages in an encouraging, problem-solving way, even for errors.\n- MUST: Error messages guide the exit. Don't just state what went wrong—tell the user how to fix it.\n- MUST: Avoid ambiguity. Labels are clear & specific.\n- MUST: Instead of the button label \"Continue\", say \"Save API Key\".\n"
  },
  {
    "path": "packages/website/README.md",
    "content": "This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).\n\n## Getting Started\n\nFirst, run the development server:\n\n```bash\nnpm run dev\n# or\nyarn dev\n# or\npnpm dev\n# or\nbun dev\n```\n\nOpen [http://localhost:3000](http://localhost:3000) with your browser to see the result.\n\nYou can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.\n\nThis project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.\n\n## Learn More\n\nTo learn more about Next.js, take a look at the following resources:\n\n- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.\n- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.\n\nYou can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!\n\n## Deploy on Vercel\n\nThe easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.\n\nCheck out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.\n"
  },
  {
    "path": "packages/website/app/api/waitlist/route.ts",
    "content": "import { NextResponse } from 'next/server';\nimport { z } from 'zod';\n\nconst schema = z.object({\n  email: z.string().email(),\n  name: z.string().optional(),\n});\n\n/**\n * Add a user to the waitlist\n *\n * @example\n * fetch(\"/api/waitlist\", {\n *   method: \"POST\",\n *   body: JSON.stringify({ email, name }),\n * })\n * name is optional, if provided it will be split into first and last name\n */\n\nexport async function POST(request: Request) {\n  try {\n    const body = await request.json();\n    const { email, name } = schema.parse(body);\n    const options = {\n      method: 'POST',\n      headers: {\n        Authorization: `Bearer ${process.env.LOOPS_API_KEY}`,\n        'Content-Type': 'application/json',\n      },\n      body: JSON.stringify({\n        email,\n        firstName: name?.split(' ')[0],\n        lastName: name?.split(' ')[1],\n        source: 'replay waitlist',\n      }),\n    };\n\n    const response = await fetch(\n      'https://app.loops.so/api/v1/contacts/create',\n      options,\n    );\n    const data = await response.json();\n\n    if (!data.success) {\n      return NextResponse.json({ error: data.message }, { status: 500 });\n    }\n    return NextResponse.json({ ok: true });\n  } catch (error) {\n    if (error instanceof z.ZodError) {\n      return NextResponse.json({ error: error.message }, { status: 400 });\n    }\n    return NextResponse.json(\n      { error: 'Failed to add to waitlist' },\n      { status: 500 },\n    );\n  }\n}\n"
  },
  {
    "path": "packages/website/app/globals.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\nhtml,\nbody {\n  background-color: #000;\n  color: #fff;\n  min-height: 100vh;\n  overflow-x: clip;\n}\n\n::selection {\n  background: rgba(139, 92, 246, 0.3);\n  color: #c4b5fd;\n}\n\n::-moz-selection {\n  background: rgba(139, 92, 246, 0.3);\n  color: #c4b5fd;\n}\n\n/* Dark scrollbar */\n* {\n  scrollbar-width: thin;\n  scrollbar-color: #404040 #0d0d0d;\n}\n\n*::-webkit-scrollbar {\n  width: 8px;\n  height: 8px;\n}\n\n*::-webkit-scrollbar-track {\n  background: #0d0d0d;\n}\n\n*::-webkit-scrollbar-thumb {\n  background-color: #404040;\n  border-radius: 4px;\n}\n\n*::-webkit-scrollbar-thumb:hover {\n  background-color: #525252;\n}\n\n/* Shimmer text animation */\n@keyframes shimmer {\n  0% {\n    background-position: 200% 0;\n  }\n  100% {\n    background-position: -200% 0;\n  }\n}\n\n.shimmer-text {\n  background: linear-gradient(\n    90deg,\n    #818181 0%,\n    #818181 35%,\n    #ffffff 50%,\n    #818181 65%,\n    #818181 100%\n  );\n  background-size: 150% 100%;\n  background-clip: text;\n  -webkit-background-clip: text;\n  -webkit-text-fill-color: transparent;\n  animation: shimmer 2s ease-in-out infinite;\n}\n\n/* Logo shimmer */\n@keyframes logo-shimmer {\n  0% {\n    filter: drop-shadow(0 0 0 rgba(139, 92, 246, 0));\n    transform: scale(1);\n  }\n  50% {\n    filter: drop-shadow(0 0 16px rgba(139, 92, 246, 0.9));\n    transform: scale(1.04);\n  }\n  100% {\n    filter: drop-shadow(0 0 0 rgba(139, 92, 246, 0));\n    transform: scale(1);\n  }\n}\n\n.logo-shimmer-once {\n  animation: logo-shimmer 0.9s ease-out;\n  will-change: transform, filter;\n  transform-origin: center;\n  backface-visibility: hidden;\n  transform: translateZ(0);\n}\n\n@media (hover: hover) and (pointer: fine) {\n  .logo-shimmer-once:hover {\n    animation: logo-shimmer 0.9s ease-out;\n  }\n}\n\n/* Highlighted code lines */\n.highlighted-code .line-changed {\n  background-color: rgba(139, 92, 246, 0.15);\n  border-left: 2px solid rgba(139, 92, 246, 0.75);\n  padding-left: 0.5rem;\n  display: inline-block;\n  width: 100%;\n  box-sizing: border-box;\n}\n\n/* Fade animations */\n@keyframes fadeIn {\n  from {\n    opacity: 0;\n    transform: translateY(8px);\n  }\n  to {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n\n.animate-fade-in {\n  animation: fadeIn 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards;\n}\n\n@media (prefers-reduced-motion: reduce) {\n  *,\n  *::before,\n  *::after {\n    animation-duration: 0.01ms !important;\n    animation-iteration-count: 1 !important;\n    transition-duration: 0.01ms !important;\n    scroll-behavior: auto !important;\n  }\n}\n"
  },
  {
    "path": "packages/website/app/layout.tsx",
    "content": "import \"./globals.css\";\nimport { Geist, Geist_Mono } from \"next/font/google\";\nimport Script from \"next/script\";\nimport { Analytics } from \"@vercel/analytics/next\";\nimport { SpeedInsights } from \"@vercel/speed-insights/next\";\nimport Header from \"../components/header\";\n\nconst geistSans = Geist({\n  variable: \"--font-geist-sans\",\n  subsets: [\"latin\"],\n  display: \"swap\",\n  preload: true,\n});\nconst geistMono = Geist_Mono({\n  variable: \"--font-geist-mono\",\n  subsets: [\"latin\"],\n  display: \"swap\",\n  preload: true,\n});\n\nexport const metadata = {\n  title: \"React Scan\",\n  description:\n    \"React Scan automatically detects and highlights components that cause performance issues in your React app.\",\n  icons: {\n    icon: \"/logo.svg\",\n  },\n  openGraph: {\n    type: \"website\",\n    url: \"https://react-scan.com\",\n    title: \"React Scan\",\n    description:\n      \"React Scan automatically detects and highlights components that cause performance issues in your React app.\",\n    images: \"https://react-scan.com/banner.png\",\n  },\n  twitter: {\n    card: \"summary_large_image\",\n    title: \"React Scan\",\n    description:\n      \"React Scan automatically detects and highlights components that cause performance issues in your React app.\",\n    images: \"https://react-scan.com/banner.png\",\n  },\n};\n\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n  return (\n    <html lang=\"en\">\n      <head>\n        <meta name=\"theme-color\" content=\"#000000\" />\n        <link rel=\"canonical\" href=\"https://react-scan.com\" />\n        <Script\n          src=\"/auto.global.js\"\n          strategy=\"beforeInteractive\"\n        />\n        {process.env.NODE_ENV === \"development\" && (\n          <Script\n            src=\"//unpkg.com/react-grab@latest/dist/index.global.js\"\n            crossOrigin=\"anonymous\"\n            strategy=\"beforeInteractive\"\n            data-options='{\"activationKey\":\"g\"}'\n          />\n        )}\n      </head>\n      <body\n        className={`${geistSans.variable} ${geistMono.variable} antialiased`}\n        style={{ fontFamily: \"var(--font-geist-sans)\" }}\n      >\n        <main className=\"mx-auto w-full max-w-3xl px-4 py-24 sm:px-8\">\n          <Header />\n          <div className=\"pt-4 sm:pt-8\">{children}</div>\n        </main>\n        <Analytics />\n        <SpeedInsights />\n      </body>\n    </html>\n  );\n}\n"
  },
  {
    "path": "packages/website/app/page.tsx",
    "content": "\"use client\";\n\nimport Link from \"next/link\";\nimport InstallGuide from \"@/components/install-guide\";\nimport Companies from \"@/components/companies\";\nimport { IconGithub } from \"@/components/icons/icon-github\";\nimport { IconDiscord } from \"@/components/icons/icon-discord\";\n\nexport default function Home() {\n  return (\n    <div className=\"flex flex-col gap-4 text-base sm:text-lg\">\n      <div className=\"text-pretty text-white\">\n        <span className=\"font-bold\">React&nbsp;Scan</span> automatically detects performance issues\n        in your React&nbsp;app.\n      </div>\n\n      <div className=\"text-pretty text-white/70\">\n        Previously, existing tools required lots of code change, lacked simple visual cues, and\n        didn&apos;t have a simple, portable&nbsp;API.\n      </div>\n\n      <div className=\"text-pretty text-white/70\">Instead, React Scan:</div>\n      <ul className=\"flex flex-col gap-1.5 text-white/70 pl-5 list-disc marker:text-white/30\">\n        <li>Requires no code changes</li>\n        <li>Highlights exactly the components you need to optimize</li>\n        <li>Available via script tag, npm, you name&nbsp;it!</li>\n      </ul>\n\n      <InstallGuide />\n\n      <div className=\"flex gap-3 pt-2\">\n        <Link\n          href=\"https://github.com/aidenybai/react-scan#install\"\n          className=\"inline-flex items-center gap-2 rounded-md border border-white/20 bg-white px-3 py-1.5 text-sm text-black transition-all hover:bg-white/90 active:scale-[0.98] sm:text-base\"\n        >\n          <IconGithub className=\"h-[18px] w-[18px]\" />\n          Star on GitHub\n        </Link>\n        <Link\n          href=\"https://discord.gg/KV3FhDq7FA\"\n          className=\"inline-flex items-center gap-2 rounded-md border border-white/10 bg-white/5 px-3 py-1.5 text-sm text-white transition-all hover:bg-white/10 active:scale-[0.98] sm:text-base\"\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n        >\n          <IconDiscord className=\"h-[18px] w-[18px]\" />\n          Join Discord\n        </Link>\n      </div>\n\n      <Companies />\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/website/app/react-scan.ts",
    "content": "\"use client\";\n\nimport { scan } from \"react-scan\";\n\nscan();\n"
  },
  {
    "path": "packages/website/app/replay/page.tsx",
    "content": "'use client';\n\nimport { useRef, useState, useEffect } from 'react';\n\nexport default function ReplayPage() {\n  const videoRef = useRef<HTMLVideoElement>(null);\n  const [isModalOpen, setIsModalOpen] = useState(false);\n  const [isClosing, setIsClosing] = useState(false);\n  const [email, setEmail] = useState('');\n  const [isSubmitting, setIsSubmitting] = useState(false);\n  const [showSuccess, setShowSuccess] = useState(false);\n  const [showError, setShowError] = useState(false);\n\n  const closeModal = () => {\n    setIsClosing(true);\n    setTimeout(() => {\n      setIsModalOpen(false);\n      setIsClosing(false);\n      if (videoRef.current) {\n        videoRef.current.pause();\n      }\n    }, 300);\n  };\n\n  useEffect(() => {\n    const handleKeyDown = (e: KeyboardEvent) => {\n      if (e.key === 'Escape' && isModalOpen) {\n        closeModal();\n      }\n    };\n    window.addEventListener('keydown', handleKeyDown);\n\n    if (isModalOpen) {\n      document.body.style.overflow = 'hidden';\n    }\n\n    return () => {\n      window.removeEventListener('keydown', handleKeyDown);\n      document.body.style.overflow = 'auto';\n    };\n  }, [isModalOpen]);\n\n  const handleSubmit = async (e: React.FormEvent) => {\n    e.preventDefault();\n    setIsSubmitting(true);\n\n    try {\n      const response = await fetch('/api/waitlist', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n        },\n        body: JSON.stringify({ email }),\n      });\n\n      if (!response.ok) {\n        throw new Error('Failed to join waitlist');\n      }\n\n      setEmail('');\n      setShowSuccess(true);\n      setTimeout(() => setShowSuccess(false), 3000);\n    } catch (error) {\n      console.error('Error joining waitlist:', error);\n      setShowError(true);\n      setTimeout(() => setShowError(false), 3000);\n    } finally {\n      setIsSubmitting(false);\n    }\n  };\n\n  return (\n    <div className=\"mx-auto max-w-xl\">\n      <div className=\"mt-8 space-y-6\">\n        <div>\n          <span className=\"font-bold\">Replay for React Scan</span> saves clips of your website when performance drops, and provides a breakdown of what happened\n        </div>\n\n        <div className=\"relative\">\n          <form onSubmit={handleSubmit} className=\"flex flex-col sm:flex-row gap-2 sm:gap-0\">\n            <input\n              type=\"email\"\n              placeholder=\"Enter your email\"\n              value={email}\n              onChange={(e) => setEmail(e.target.value)}\n              className=\"flex-1 px-4 py-2.5 bg-white border sm:border-r-0 border-gray-200 text-gray-900 placeholder-gray-500 focus:outline-none focus:border-[#4B4DB3] focus:ring-2 focus:ring-[#4B4DB3]/20\"\n              required\n            />\n            <button\n              type=\"submit\"\n              disabled={isSubmitting}\n              className=\"relative px-6 py-2.5 bg-[#4B4DB3] text-white font-medium hover:bg-[#3F41A0] transition-colors whitespace-nowrap border border-[#4B4DB3] disabled:opacity-50 disabled:cursor-not-allowed overflow-hidden\"\n            >\n              <span className={`transition-opacity duration-200 ${isSubmitting ? 'opacity-0' : 'opacity-100'}`}>\n                Join Waitlist\n              </span>\n              {isSubmitting && (\n                <div className=\"absolute inset-0 flex items-center justify-center\">\n                  <svg className=\"animate-spin h-5 w-5 text-white\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\">\n                    <circle className=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"4\"></circle>\n                    <path className=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"></path>\n                  </svg>\n                </div>\n              )}\n            </button>\n          </form>\n\n          <div className={`absolute inset-x-0 -top-12 transition-all duration-500 ${showSuccess ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-2'}`}>\n            <div className=\"flex items-center justify-center\">\n              <div className=\"bg-white border border-[#4B4DB3]/20 shadow-lg text-[#4B4DB3] px-4 py-2 rounded-full font-medium flex items-center gap-2\">\n                <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n                  <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M5 13l4 4L19 7\" />\n                </svg>\n                <span>{\"You're on the waitlist!\"}</span>\n                <span className=\"animate-bounce inline-block\">🎉</span>\n              </div>\n            </div>\n          </div>\n\n          <div className={`absolute inset-x-0 -top-12 transition-all duration-500 ${showError ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-2'}`}>\n            <div className=\"flex items-center justify-center\">\n              <div className=\"bg-white border border-red-500/20 shadow-lg text-red-500 px-4 py-2 rounded-full font-medium flex items-center gap-2\">\n                <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n                  <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\" />\n                </svg>\n                <span>Error joining waitlist</span>\n              </div>\n            </div>\n          </div>\n        </div>\n\n        <p className=\"text-sm text-gray-500\">Watch the demo below to see how it works.</p>\n\n        <div className=\"relative w-full aspect-video\">\n          <div className=\"absolute inset-0 bg-black/[0.02] rounded-xl backdrop-blur-sm\">\n            <div className=\"relative overflow-hidden group cursor-pointer h-full\" onClick={() => setIsModalOpen(true)}>\n              <div className=\"relative pb-[56.25%]\">\n                <div className=\"absolute inset-0\">\n                <video\n                  className=\"w-full h-full object-cover\"\n                  src=\"/player-video.mp4\"\n                  playsInline\n                  muted\n                  preload=\"metadata\"\n                  poster=\"/thumbnail.png\"\n                />\n                  <div className=\"absolute inset-0 w-full h-full flex items-center justify-center\">\n                    <div className=\"h-16 w-16 rounded-full bg-white/90 backdrop-blur-sm shadow-lg flex items-center justify-center transform group-hover:scale-105 transition-all duration-300 group-hover:bg-white\">\n                      <svg className=\"w-7 h-7 text-gray-900 relative left-0.5\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n                        <path d=\"M8 5v14l11-7z\" />\n                      </svg>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n\n      {isModalOpen && (\n        <div \n          className=\"fixed inset-0 z-50 flex items-center justify-center overflow-y-auto pt-8 pb-16\"\n          onClick={closeModal}\n          style={{ \n            opacity: 0,\n            animation: isClosing \n              ? 'fadeOut 300ms ease-in-out forwards'\n              : 'fadeIn 300ms ease-in-out forwards',\n          }}\n        >\n          <div \n            className=\"w-[95vw] md:w-[80vw] max-h-[90vh] relative\" \n            onClick={e => e.stopPropagation()}\n            style={{ \n              opacity: 0,\n              transform: 'scale(0.95)',\n              animation: isClosing\n                ? 'scaleOut 300ms ease-in-out forwards'\n                : 'scaleIn 300ms ease-in-out forwards',\n            }}\n          >\n            <style jsx>{`\n              @keyframes fadeIn {\n                from { opacity: 0; backdrop-filter: blur(0); background: rgba(0, 0, 0, 0); }\n                to { opacity: 1; backdrop-filter: blur(4px); background: rgba(0, 0, 0, 0.4); }\n              }\n              @keyframes fadeOut {\n                from { opacity: 1; backdrop-filter: blur(4px); background: rgba(0, 0, 0, 0.4); }\n                to { opacity: 0; backdrop-filter: blur(0); background: rgba(0, 0, 0, 0); }\n              }\n              @keyframes scaleIn {\n                from { opacity: 0; transform: scale(0.95); }\n                to { opacity: 1; transform: scale(1); }\n              }\n              @keyframes scaleOut {\n                from { opacity: 1; transform: scale(1); }\n                to { opacity: 0; transform: scale(0.95); }\n              }\n            `}</style>\n            <div className=\"bg-black rounded-lg border border-white/10 shadow-2xl flex flex-col overflow-hidden\">\n              <div className=\"hidden sm:flex justify-end px-3 py-2 bg-white border-b border-[#4B4DB3]/10\">\n                <button\n                  onClick={closeModal}\n                  className=\"h-7 w-7 rounded-md hover:bg-[#4B4DB3]/5 flex items-center justify-center transition-colors\"\n                >\n                  <svg className=\"w-4 h-4 text-[#4B4DB3]\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n                    <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n                  </svg>\n                </button>\n              </div>\n\n              <button\n                onClick={closeModal}\n                className=\"sm:hidden absolute right-3 top-3 z-10 h-8 w-8 rounded-full bg-black/50 backdrop-blur-sm flex items-center justify-center\"\n              >\n                <svg className=\"w-5 h-5 text-white\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n                  <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n                </svg>\n              </button>\n\n              <div className=\"bg-black\">\n                <video\n                  ref={videoRef}\n                  className=\"w-full\"\n                  src=\"/player-video.mp4\"\n                  controls\n                  autoPlay\n                  playsInline\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n      )}\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/website/components/cli.tsx",
    "content": "'use client';\n\nimport React, { useState } from 'react';\n\nconst ClipboardIcon = ({ className }: { className: string }) => (\n  <svg\n    className={className}\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n  >\n    <path\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      strokeWidth={2}\n      d=\"M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3\"\n    />\n  </svg>\n);\n\nconst CheckIcon = ({ className }: { className: string }) => (\n  <svg\n    className={className}\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n  >\n    <path\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      strokeWidth={2}\n      d=\"M5 13l4 4L19 7\"\n    />\n  </svg>\n);\n\nexport default function CLI({ command }: { command: string }) {\n  const [copied, setCopied] = useState(false);\n\n  const copyToClipboard = async () => {\n    await navigator.clipboard.writeText(command);\n    setCopied(true);\n    setTimeout(() => setCopied(false), 2000);\n  };\n\n  return (\n    <div className=\"relative mt-4 flex min-h-[160px] flex-col rounded-lg bg-[#171717] p-4\">\n      <div className=\"overflow-hidden border border-[#373737] bg-[#1e1e1e] text-white\">\n        <div className=\"flex items-center justify-between border-b border-[#333] bg-[#1e1e1e] px-4 py-1\">\n          <div className=\"flex items-center gap-1\">\n            <span className=\"size-2 rounded-full bg-[#858585]/30\"></span>\n            <span className=\"size-2 rounded-full bg-[#858585]/30\"></span>\n            <span className=\"size-2 rounded-full bg-[#858585]/30\"></span>\n          </div>\n          <div className=\"flex items-center gap-2\">\n            <span className=\"text-[#858585]\">~</span>\n          </div>\n        </div>\n        <pre className=\"group relative whitespace-pre-wrap break-words bg-[#171717] p-4 font-mono text-sm\">\n          <span className=\"text-white\">$ {command}</span>\n          <button\n            onClick={() => {\n              void copyToClipboard();\n            }}\n            className=\"absolute right-4 top-4 rounded bg-[#333] p-1.5 opacity-0 transition-opacity hover:bg-[#444] group-hover:opacity-100\"\n          >\n            {copied ? (\n              <CheckIcon className=\"size-4 text-green-500\" />\n            ) : (\n                <ClipboardIcon className=\"size-4 text-white\" />\n            )}\n          </button>\n        </pre>\n      </div>\n      <div className=\"mt-auto pt-4 text-xs text-neutral-400\">\n        Install via {`<script>`} or npm instead?\n        <a\n          className=\"ml-1 text-neutral-400 underline hover:text-white\"\n          href=\"https://github.com/aidenybai/react-scan#readme\"\n        >\n          Full installation guide →\n        </a>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/website/components/code.tsx",
    "content": "export default function Code({\n  children,\n  className,\n}: {\n  children: React.ReactNode;\n  className?: string;\n}) {\n  return (\n    <code className={`rounded-md bg-neutral-100 p-1 text-sm ${className}`}>\n      {children}\n    </code>\n  );\n}\n"
  },
  {
    "path": "packages/website/components/companies.tsx",
    "content": "import Image from 'next/image';\n\nconst LOGO_FILTER = 'brightness(0) invert(1) drop-shadow(0 0 0 white) drop-shadow(0 0 0 white)';\n\ninterface CompanyLogo {\n  src: string;\n  alt: string;\n  width: number;\n}\n\nconst LOGOS: CompanyLogo[] = [\n  { src: '/perplexity-logo.png', alt: 'Perplexity', width: 120 },\n  { src: '/shopify-logo.png', alt: 'Shopify', width: 90 },\n  { src: '/faire-logo.svg', alt: 'Faire', width: 120 },\n];\n\nexport default function Companies() {\n  return (\n    <div className=\"pt-2\">\n      <div className=\"mb-3 text-sm text-white/50 sm:text-base\">\n        Trusted by engineering teams at:\n      </div>\n      <div className=\"flex items-center gap-6\">\n        {LOGOS.map((logo) => (\n          <div\n            key={logo.alt}\n            className=\"opacity-50 transition-opacity hover:opacity-80\"\n          >\n            <Image\n              src={logo.src}\n              alt={logo.alt}\n              width={logo.width}\n              height={30}\n              style={{ filter: LOGO_FILTER }}\n            />\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/website/components/counter.tsx",
    "content": "import { createContext, useContext, useState, ReactNode } from 'react';\n\ninterface CounterContextType {\n  count: number;\n  increment: () => void;\n  decrement: () => void;\n  reset: () => void;\n}\n\nconst CounterContext = createContext<CounterContextType | undefined>(undefined);\n\nexport const CounterProvider = ({ children }: { children: ReactNode }) => {\n  const [count, setCount] = useState(0);\n\n  const increment = () => setCount(prev => prev + 1);\n  const decrement = () => setCount(prev => prev - 1);\n  const reset = () => setCount(0);\n\n  const value = {\n    count,\n    increment,\n    decrement,\n    reset,\n  };\n\n  return (\n    <CounterContext.Provider value={value}>\n      {children}\n    </CounterContext.Provider>\n  );\n}\n\nconst useCounter = () => {\n  const context = useContext(CounterContext);\n  if (context === undefined) {\n    throw new Error('useCounter must be used within a CounterProvider');\n  }\n  return context;\n}\n\nconst CounterDisplay = () => {\n  const { count } = useCounter();\n  return <h2 className=\"text-2xl font-bold\">Counter: {count}</h2>;\n}\n\nconst CounterButton = ({\n  variant = 'primary',\n  className = '',\n  ...props\n}) => {\n  const baseClasses = \"px-4 py-2 text-white rounded hover:opacity-90\";\n  const variantClasses = variant === 'primary'\n    ? 'bg-blue-500 hover:bg-blue-600'\n    : 'bg-gray-500 hover:bg-gray-600';\n\n  return (\n    <button\n      className={`${baseClasses} ${variantClasses} ${className}`}\n      {...props}\n    />\n  );\n}\n\nconst IncrementButton = () => {\n  const { increment } = useCounter();\n  return <CounterButton onClick={increment}>+</CounterButton>;\n}\n\nconst DecrementButton = () => {\n  const { decrement } = useCounter();\n  return <CounterButton onClick={decrement}>-</CounterButton>;\n}\n\nconst ResetButton = () => {\n  const { reset } = useCounter();\n  return <CounterButton onClick={reset} variant=\"secondary\">Reset</CounterButton>;\n}\n\nconst CounterControls = () => {\n  return (\n    <div className=\"flex gap-2\">\n      <DecrementButton />\n      <IncrementButton />\n      <ResetButton />\n    </div>\n  );\n}\n\nexport const Counter = () => {\n  return (\n    <div className=\"flex flex-col items-center gap-4 p-4 border rounded-lg shadow-sm\">\n      <CounterDisplay />\n      <CounterControls />\n    </div>\n  );\n}\n\nexport const CounterExample = () => {\n  return (\n    <CounterProvider>\n      <Counter />\n    </CounterProvider>\n  );\n}\n"
  },
  {
    "path": "packages/website/components/footer.tsx",
    "content": "export default function Footer() {\n  return (\n    <div className=\"border-t border-white/10 px-6 py-4 text-xs text-white/40\">\n      <div className=\"mx-auto flex w-full max-w-2xl items-center justify-between gap-2\">\n        <div>&copy; {new Date().getFullYear()} Million Software, Inc.</div>\n        <a\n          className=\"hover:text-white/70 transition-colors\"\n          href=\"https://github.com/aidenybai/react-scan\"\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n        >\n          GitHub\n        </a>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/website/components/header.tsx",
    "content": "\"use client\";\n\nimport { useState, useEffect, useRef } from \"react\";\nimport Link from \"next/link\";\nimport Image from \"next/image\";\n\nexport default function Header() {\n  const [isMenuOpen, setIsMenuOpen] = useState(false);\n  const menuRef = useRef<HTMLDivElement>(null);\n  const buttonRef = useRef<HTMLButtonElement>(null);\n\n  useEffect(() => {\n    const handleClickOutside = (event: MouseEvent) => {\n      if (\n        isMenuOpen &&\n        menuRef.current &&\n        buttonRef.current &&\n        !menuRef.current.contains(event.target as Node) &&\n        !buttonRef.current.contains(event.target as Node)\n      ) {\n        setIsMenuOpen(false);\n      }\n    };\n\n    document.addEventListener(\"mousedown\", handleClickOutside);\n    return () => {\n      document.removeEventListener(\"mousedown\", handleClickOutside);\n    };\n  }, [isMenuOpen]);\n\n  return (\n    <nav className=\"relative flex items-center justify-between text-base sm:text-lg\">\n      <Link href=\"/\" className=\"flex items-center gap-3 text-inherit no-underline\">\n        <Image src=\"/logo.svg\" alt=\"React Scan\" width={30} height={30} />\n      </Link>\n\n      <button\n        ref={buttonRef}\n        onClick={() => setIsMenuOpen(!isMenuOpen)}\n        className=\"md:hidden p-2 hover:bg-white/10 rounded-md text-white/60\"\n        aria-label=\"Toggle menu\"\n      >\n        <svg className=\"w-6 h-6\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n          {isMenuOpen ? (\n            <path\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n              strokeWidth={2}\n              d=\"M6 18L18 6M6 6l12 12\"\n            />\n          ) : (\n            <path\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n              strokeWidth={2}\n              d=\"M4 6h16M4 12h16M4 18h16\"\n            />\n          )}\n        </svg>\n      </button>\n\n      <div className=\"hidden md:flex gap-4 text-sm sm:text-base\">\n        <Link\n          href=\"https://github.com/aidenybai/react-scan#readme\"\n          className=\"text-white/50 underline hover:text-white transition-colors\"\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n        >\n          docs\n        </Link>\n        <Link\n          href=\"https://github.com/aidenybai/react-scan\"\n          className=\"text-white/50 underline hover:text-white transition-colors\"\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n        >\n          github\n        </Link>\n      </div>\n\n      {isMenuOpen && (\n        <div\n          ref={menuRef}\n          className=\"absolute right-0 top-[calc(100%+0.5rem)] w-48 bg-[#1a1a1a] border border-white/10 rounded-lg shadow-xl overflow-hidden md:hidden z-[100]\"\n        >\n          <div className=\"divide-y divide-white/10\">\n            <Link\n              href=\"https://github.com/aidenybai/react-scan#readme\"\n              className=\"block px-4 py-3 text-white/60 hover:bg-white/5 hover:text-white transition-colors\"\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n              onClick={() => setIsMenuOpen(false)}\n            >\n              docs\n            </Link>\n            <Link\n              href=\"https://github.com/aidenybai/react-scan\"\n              className=\"block px-4 py-3 text-white/60 hover:bg-white/5 hover:text-white transition-colors\"\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n              onClick={() => setIsMenuOpen(false)}\n            >\n              github\n            </Link>\n          </div>\n        </div>\n      )}\n    </nav>\n  );\n}\n"
  },
  {
    "path": "packages/website/components/icons/icon-discord.tsx",
    "content": "import type { IconProps } from \"./types\";\n\nexport const IconDiscord = ({\n  width = 16,\n  height = 16,\n  className = \"\",\n}: IconProps) => (\n  <svg\n    width={width}\n    height={height}\n    className={className}\n    fill=\"currentColor\"\n    viewBox=\"0 -28.5 256 256\"\n  >\n    <path d=\"M216.856339,16.5966031 C200.285002,8.84328665 182.566144,3.2084988 164.041564,0 C161.766523,4.11318106 159.108624,9.64549908 157.276099,14.0464379 C137.583995,11.0849896 118.072967,11.0849896 98.7430163,14.0464379 C96.9108417,9.64549908 94.1925838,4.11318106 91.8971895,0 C73.3526068,3.2084988 55.6133949,8.86399117 39.0420583,16.6376612 C5.61752293,67.146514 -3.4433191,116.400813 1.08711069,164.955721 C23.2560196,181.510915 44.7403634,191.567697 65.8621325,198.148576 C71.0772151,190.971126 75.7283628,183.341335 79.7352139,175.300261 C72.104019,172.400575 64.7949724,168.822202 57.8887866,164.667963 C59.7209612,163.310589 61.5131304,161.891452 63.2445898,160.431257 C105.36741,180.133187 151.134928,180.133187 192.754523,160.431257 C194.506336,161.891452 196.298154,163.310589 198.110326,164.667963 C191.183787,168.842556 183.854737,172.420929 176.223542,175.320965 C180.230393,183.341335 184.861538,190.991831 190.096624,198.16893 C211.238746,191.588051 232.743023,181.531619 254.911949,164.955721 C260.227747,108.668201 245.831087,59.8662432 216.856339,16.5966031 Z M85.4738752,135.09489 C72.8290281,135.09489 62.4592217,123.290155 62.4592217,108.914901 C62.4592217,94.5396472 72.607595,82.7145587 85.4738752,82.7145587 C98.3405064,82.7145587 108.709962,94.5189427 108.488529,108.914901 C108.508531,123.290155 98.3405064,135.09489 85.4738752,135.09489 Z M170.525237,135.09489 C157.88039,135.09489 147.510584,123.290155 147.510584,108.914901 C147.510584,94.5396472 157.658606,82.7145587 170.525237,82.7145587 C183.391518,82.7145587 193.761324,94.5189427 193.539891,108.914901 C193.539891,123.290155 183.391518,135.09489 170.525237,135.09489 Z\" />\n  </svg>\n);\n"
  },
  {
    "path": "packages/website/components/icons/icon-github.tsx",
    "content": "import type { IconProps } from \"./types\";\n\nexport const IconGithub = ({\n  width = 16,\n  height = 16,\n  className = \"\",\n}: IconProps) => (\n  <svg\n    width={width}\n    height={height}\n    className={className}\n    fill=\"currentColor\"\n    viewBox=\"0 0 24 24\"\n  >\n    <path\n      fillRule=\"evenodd\"\n      d=\"M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z\"\n      clipRule=\"evenodd\"\n    />\n  </svg>\n);\n"
  },
  {
    "path": "packages/website/components/icons/types.ts",
    "content": "export interface IconProps {\n  width?: number;\n  height?: number;\n  className?: string;\n}\n"
  },
  {
    "path": "packages/website/components/install-guide.tsx",
    "content": "'use client';\n\nimport { useState, useEffect, useRef, useCallback } from 'react';\nimport hljs from 'highlight.js/lib/core';\nimport xml from 'highlight.js/lib/languages/xml';\nimport javascript from 'highlight.js/lib/languages/javascript';\nimport typescript from 'highlight.js/lib/languages/typescript';\nimport bash from 'highlight.js/lib/languages/bash';\nimport 'highlight.js/styles/github-dark.css';\n\nhljs.registerLanguage('xml', xml);\nhljs.registerLanguage('javascript', javascript);\nhljs.registerLanguage('typescript', typescript);\nhljs.registerLanguage('bash', bash);\n\nconst COPY_FEEDBACK_DURATION_MS = 2000;\n\nconst InlineCode = ({ children }: { children: React.ReactNode }) => (\n  <code className=\"rounded bg-white/10 px-1.5 py-0.5 font-mono text-xs text-white/70\">\n    {children}\n  </code>\n);\n\ninterface InstallTab {\n  id: string;\n  label: string;\n  description: React.ReactNode;\n  lang: string;\n  code: string;\n}\n\nconst INSTALL_TABS: InstallTab[] = [\n  {\n    id: 'cli',\n    label: 'CLI',\n    description: '',\n    lang: 'bash',\n    code: `npx -y react-scan@latest init`,\n  },\n  {\n    id: 'script',\n    label: 'Script Tag',\n    description: <>Paste this before any scripts in your <InlineCode>index.html</InlineCode></>,\n    lang: 'xml',\n    code: `<!-- paste this BEFORE any scripts -->\n<script\n  crossOrigin=\"anonymous\"\n  src=\"//unpkg.com/react-scan/dist/auto.global.js\"\n></script>`,\n  },\n  {\n    id: 'nextjs-app',\n    label: 'Next.js (App)',\n    description: <>Add this inside of your <InlineCode>app/layout.tsx</InlineCode></>,\n    lang: 'typescript',\n    code: `import Script from \"next/script\";\n\nexport default function RootLayout({ children }) {\n  return (\n    <html>\n      <head>\n        <Script\n          src=\"//unpkg.com/react-scan/dist/auto.global.js\"\n          crossOrigin=\"anonymous\"\n          strategy=\"beforeInteractive\"\n        />\n      </head>\n      <body>{children}</body>\n    </html>\n  );\n}`,\n  },\n  {\n    id: 'nextjs-pages',\n    label: 'Next.js (Pages)',\n    description: <>Add this into your <InlineCode>pages/_document.tsx</InlineCode></>,\n    lang: 'typescript',\n    code: `import { Html, Head, Main, NextScript } from \"next/document\";\nimport Script from \"next/script\";\n\nexport default function Document() {\n  return (\n    <Html lang=\"en\">\n      <Head>\n        <Script\n          src=\"//unpkg.com/react-scan/dist/auto.global.js\"\n          crossOrigin=\"anonymous\"\n          strategy=\"beforeInteractive\"\n        />\n      </Head>\n      <body>\n        <Main />\n        <NextScript />\n      </body>\n    </Html>\n  );\n}`,\n  },\n  {\n    id: 'vite',\n    label: 'Vite',\n    description: <>Example <InlineCode>index.html</InlineCode> with React Scan enabled</>,\n    lang: 'xml',\n    code: `<!doctype html>\n<html lang=\"en\">\n  <head>\n    <script\n      crossOrigin=\"anonymous\"\n      src=\"//unpkg.com/react-scan/dist/auto.global.js\"\n    ></script>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>`,\n  },\n  {\n    id: 'remix',\n    label: 'Remix',\n    description: <>Add this inside your <InlineCode>app/root.tsx</InlineCode></>,\n    lang: 'typescript',\n    code: `import { Links, Meta, Outlet, Scripts } from \"@remix-run/react\";\n\nexport default function App() {\n  return (\n    <html>\n      <head>\n        <Meta />\n        <script\n          crossOrigin=\"anonymous\"\n          src=\"//unpkg.com/react-scan/dist/auto.global.js\"\n        />\n        <Links />\n      </head>\n      <body>\n        <Outlet />\n        <Scripts />\n      </body>\n    </html>\n  );\n}`,\n  },\n];\n\nconst CopyIcon = () => (\n  <svg\n    width=\"16\"\n    height=\"16\"\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    strokeWidth=\"2\"\n    strokeLinecap=\"round\"\n    strokeLinejoin=\"round\"\n  >\n    <rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\" />\n    <path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\" />\n  </svg>\n);\n\nconst CheckIcon = () => (\n  <svg\n    width=\"16\"\n    height=\"16\"\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    strokeWidth=\"2\"\n    strokeLinecap=\"round\"\n    strokeLinejoin=\"round\"\n  >\n    <path d=\"M5 13l4 4L19 7\" />\n  </svg>\n);\n\nexport default function InstallGuide() {\n  const [activeTabId, setActiveTabId] = useState(INSTALL_TABS[0].id);\n  const [didCopy, setDidCopy] = useState(false);\n  const [height, setHeight] = useState('auto');\n  const contentRef = useRef<HTMLPreElement>(null);\n\n  const activeTab =\n    INSTALL_TABS.find((tab) => tab.id === activeTabId) ?? INSTALL_TABS[0];\n\n  const highlightedCode = hljs.highlight(activeTab.code, {\n    language: activeTab.lang,\n  }).value;\n\n  const syncHeight = useCallback(() => {\n    if (contentRef.current) {\n      setHeight(`${contentRef.current.scrollHeight}px`);\n    }\n  }, []);\n\n  useEffect(syncHeight, [activeTabId, syncHeight]);\n\n  const handleTabChange = (tabId: string) => {\n    syncHeight();\n    setActiveTabId(tabId);\n  };\n\n  const handleCopy = () => {\n    navigator.clipboard\n      .writeText(activeTab.code)\n      .then(() => {\n        setDidCopy(true);\n        setTimeout(() => setDidCopy(false), COPY_FEEDBACK_DURATION_MS);\n      })\n      .catch(() => {});\n  };\n\n  const headingText =\n    activeTabId === 'cli'\n      ? 'Run this command to get started:'\n      : 'It takes 1 script tag to get started:';\n\n  return (\n    <div>\n      <span className=\"hidden sm:inline text-white\">\n        {headingText}\n        {activeTabId === 'cli' && (\n          <button\n            type=\"button\"\n            onClick={() => handleTabChange('script')}\n            className=\"ml-3 text-xs italic text-white/40 hover:text-white/60 hover:underline transition-colors sm:text-sm\"\n          >\n            Prefer manual install?\n          </button>\n        )}\n      </span>\n      <div className=\"mt-4 overflow-hidden rounded-lg border border-white/10 bg-white/5 text-white shadow-[0_8px_30px_rgb(0,0,0,0.3)]\">\n        <div className=\"flex items-center gap-4 overflow-x-auto border-b border-white/10 px-4 pt-2\">\n          {INSTALL_TABS.map((tab) => {\n            const isActive = tab.id === activeTab.id;\n            return (\n              <button\n                key={tab.id}\n                type=\"button\"\n                className={`shrink-0 whitespace-nowrap border-b pb-2 font-sans text-sm transition-colors sm:text-base ${\n                  isActive\n                    ? 'border-white text-white'\n                    : 'border-transparent text-white/60 hover:text-white'\n                }`}\n                onClick={() => handleTabChange(tab.id)}\n              >\n                {tab.label}\n              </button>\n            );\n          })}\n        </div>\n        <div className=\"relative bg-black/60\">\n          {activeTabId === 'cli' ? (\n            <button\n              type=\"button\"\n              onClick={handleCopy}\n              className=\"group flex w-full items-center justify-between gap-4 px-4 py-6 transition-colors hover:bg-white/5\"\n            >\n              <pre className=\"overflow-x-auto font-mono text-base leading-relaxed text-white/80\">\n                <code dangerouslySetInnerHTML={{ __html: highlightedCode }} />\n              </pre>\n              <span className=\"shrink-0 text-white/50 transition-colors group-hover:text-white\">\n                {didCopy ? <CheckIcon /> : <CopyIcon />}\n              </span>\n            </button>\n          ) : (\n            <div className=\"group relative\">\n              <button\n                type=\"button\"\n                onClick={handleCopy}\n                className=\"absolute right-4 top-4 z-10 text-white/50 opacity-0 transition-opacity hover:text-white group-hover:opacity-100\"\n                aria-label=\"Copy code\"\n              >\n                {didCopy ? <CheckIcon /> : <CopyIcon />}\n              </button>\n              <div\n                className=\"overflow-hidden transition-[height] duration-200 ease-out\"\n                style={{ height }}\n              >\n                <pre\n                  ref={contentRef}\n                  className=\"overflow-x-auto p-4 font-mono text-[13px] leading-relaxed text-white/80\"\n                >\n                  <code dangerouslySetInnerHTML={{ __html: highlightedCode }} />\n                </pre>\n              </div>\n            </div>\n          )}\n        </div>\n      </div>\n      {activeTab.id !== 'cli' && activeTab.description && (\n        <span className=\"mt-4 block text-sm text-white/50 sm:text-base\">\n          {activeTab.description}\n        </span>\n      )}\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/website/components/test-data-types.tsx",
    "content": "'use client';\n\nimport { useState, useEffect } from \"react\";\n\nexport const TestDataTypes = () => {\n  // Basic types\n  const [text, setText] = useState(\"\");\n  const [num, setNum] = useState(0);\n  const [bool, setBool] = useState(false);\n  const [nullVal, setNullVal] = useState<string | null>(null);\n  const [undefinedVal, setUndefinedVal] = useState<string | undefined>(undefined);\n\n  // Object types\n  const [map, setMap] = useState<Map<string, string>>(new Map());\n  const [set, setSet] = useState<Set<string>>(new Set());\n  const [array, setArray] = useState<Array<number>>([]);\n  const [object, setObject] = useState({ a: 0, b: 0, c: { nested: false } });\n  const [date, setDate] = useState<Date>(new Date(0));\n  const [regex, setRegex] = useState(/test/g);\n  const [error, setError] = useState(new Error(''));\n\n  // Buffer types\n  const [typedArray, setTypedArray] = useState(new Uint8Array());\n  const [arrayBuffer, setArrayBuffer] = useState(new ArrayBuffer(0));\n  const [dataView, setDataView] = useState(new DataView(new ArrayBuffer(0)));\n\n  // Special types\n  const [bigInt, setBigInt] = useState(BigInt(0));\n  const [symbol, setSymbol] = useState(Symbol('initial'));\n  const [promise, setPromise] = useState<Promise<string>>(Promise.resolve(''));\n  const [weakMap, setWeakMap] = useState(new WeakMap());\n  const [weakSet, setWeakSet] = useState(new WeakSet());\n\n  // Keep track of objects we put in WeakMap/WeakSet\n  const [weakMapRef, setWeakMapRef] = useState<{ obj: { id: number }, value: string } | null>(null);\n  const [weakSetRef, setWeakSetRef] = useState<{ obj: { id: number } } | null>(null);\n\n  // Track Promise state\n  const [promiseState, setPromiseState] = useState<'pending' | 'resolved' | 'rejected'>('pending');\n\n  // Update Promise state when it changes\n  useEffect(() => {\n    promise\n      .then(() => setPromiseState('resolved'))\n      .catch(() => setPromiseState('rejected'));\n  }, [promise]);\n\n  // Initialize all states on the client side only\n  useEffect(() => {\n    setText(\"Hello\");\n    setNum(42);\n    setBool(true);\n    setMap(new Map([['key1', 'value1'], ['key2', 'value2']]));\n    setSet(new Set(['item1', 'item2']));\n    setArray([1, 2, 3, 4, 5]);\n    setObject({ a: 1, b: 2, c: { nested: true } });\n    setDate(new Date());\n    setRegex(/test/g);\n    setError(new Error('Test error'));\n    setTypedArray(new Uint8Array([1, 2, 3]));\n    setArrayBuffer(new ArrayBuffer(8));\n    setDataView(new DataView(new ArrayBuffer(8)));\n    setBigInt(BigInt(9007199254740991));\n    setSymbol(Symbol('test'));\n    setPromise(Promise.resolve('test'));\n  }, []);\n\n  const randomizeValues = () => {\n    // Basic types\n    setText(Math.random().toString(36).substring(7));\n    setNum(Math.floor(Math.random() * 1000));\n    setBool(Math.random() > 0.5);\n    setNullVal(Math.random() > 0.5 ? null : 'not null');\n    setUndefinedVal(Math.random() > 0.5 ? undefined : 'defined');\n\n    // Object types\n    setMap(new Map([\n      [`key${Math.random()}`, Math.random().toString()],\n      [`key${Math.random()}`, Math.random().toString()]\n    ]));\n    setSet(new Set([\n      Math.random().toString(),\n      Math.random().toString()\n    ]));\n    setArray(Array.from({ length: 5 }, () => Math.floor(Math.random() * 100)));\n    setObject({\n      a: Math.random(),\n      b: Math.random(),\n      c: { nested: Math.random() > 0.5 }\n    });\n    setDate(new Date(Date.now() + Math.random() * 10000000));\n    setRegex(new RegExp(`test${Math.floor(Math.random() * 100)}`, 'g'));\n    setError(new Error(`Random error ${Math.random()}`));\n\n    // Buffer types\n    setTypedArray(new Uint8Array([\n      Math.floor(Math.random() * 255),\n      Math.floor(Math.random() * 255),\n      Math.floor(Math.random() * 255)\n    ]));\n    setArrayBuffer(new ArrayBuffer(Math.floor(Math.random() * 16)));\n    setDataView(new DataView(new ArrayBuffer(8)));\n\n    // Special types\n    setBigInt(BigInt(Math.floor(Math.random() * 1000000)));\n    setSymbol(Symbol(`test${Math.random()}`));\n    // setPromise(Promise.resolve(`test${Math.random()}`));\n\n    // Reset Promise state when creating new Promise\n    setPromiseState('pending');\n\n    // Create new WeakMap with a random object\n    setWeakMap(new WeakMap());\n    setWeakMapRef({ obj: { id: Math.random() }, value: Math.random().toString() });\n\n    // Create new WeakSet with a random object\n    setWeakSet(new WeakSet());\n    setWeakSetRef({ obj: { id: Math.random() } });\n  };\n\n  // Helper functions to check WeakMap/WeakSet contents\n  const getWeakMapInfo = () => {\n    if (!weakMapRef) return '[WeakMap: empty]';\n    return weakMap.has(weakMapRef.obj)\n      ? `[WeakMap: has entry {${weakMapRef.obj.id} => \"${weakMapRef.value}\"}]`\n      : '[WeakMap: reference lost]';\n  };\n\n  const getWeakSetInfo = () => {\n    if (!weakSetRef) return '[WeakSet: empty]';\n    return weakSet.has(weakSetRef.obj)\n      ? `[WeakSet: has object {id: ${weakSetRef.obj.id}}]`\n      : '[WeakSet: reference lost]';\n  };\n\n  return (\n    <div className=\"p-4\">\n      <h1 className=\"mb-4 text-xl font-bold\">Test Data Types</h1>\n      <div\n        onClick={randomizeValues}\n        className=\"cursor-pointer rounded-lg border border-gray-200 bg-white p-4 shadow-sm\"\n      >\n        <div className=\"space-y-6\">\n          <section>\n            <h2 className=\"mb-2 text-sm font-semibold text-gray-700\">Basic Types</h2>\n            <div className=\"grid grid-cols-2 gap-2 text-sm\">\n              <div className=\"rounded bg-gray-50 p-2\">\n                <span className=\"font-medium\">text:</span> {text}\n              </div>\n              <div className=\"rounded bg-gray-50 p-2\">\n                <span className=\"font-medium\">num:</span> {num}\n              </div>\n              <div className=\"rounded bg-gray-50 p-2\">\n                <span className=\"font-medium\">bool:</span> {String(bool)}\n              </div>\n              <div className=\"rounded bg-gray-50 p-2\">\n                <span className=\"font-medium\">null:</span> {String(nullVal)}\n              </div>\n              <div className=\"rounded bg-gray-50 p-2\">\n                <span className=\"font-medium\">undefined:</span> {String(undefinedVal)}\n              </div>\n            </div>\n          </section>\n\n          <section>\n            <h2 className=\"mb-2 text-sm font-semibold text-gray-700\">Object Types</h2>\n            <div className=\"space-y-2 text-sm\">\n              <div className=\"rounded bg-gray-50 p-2\">\n                <span className=\"font-medium\">map:</span> {JSON.stringify(Array.from(map.entries()))}\n              </div>\n              <div className=\"rounded bg-gray-50 p-2\">\n                <span className=\"font-medium\">set:</span> {JSON.stringify(Array.from(set))}\n              </div>\n              <div className=\"rounded bg-gray-50 p-2\">\n                <span className=\"font-medium\">array:</span> {JSON.stringify(array)}\n              </div>\n              <div className=\"rounded bg-gray-50 p-2\">\n                <span className=\"font-medium\">object:</span> {JSON.stringify(object)}\n              </div>\n              <div className=\"rounded bg-gray-50 p-2\">\n                <span className=\"font-medium\">date:</span> {date.toISOString()}\n              </div>\n              <div className=\"rounded bg-gray-50 p-2\">\n                <span className=\"font-medium\">regex:</span> {regex.toString()}\n              </div>\n              <div className=\"rounded bg-gray-50 p-2\">\n                <span className=\"font-medium\">error:</span> {error.message}\n              </div>\n            </div>\n          </section>\n\n          <section>\n            <h2 className=\"mb-2 text-sm font-semibold text-gray-700\">Buffer Types</h2>\n            <div className=\"space-y-2 text-sm\">\n              <div className=\"rounded bg-gray-50 p-2\">\n                <span className=\"font-medium\">typedArray:</span> {Array.from(typedArray).join(',')}\n              </div>\n              <div className=\"rounded bg-gray-50 p-2\">\n                <span className=\"font-medium\">arrayBuffer:</span> (size: {arrayBuffer.byteLength})\n              </div>\n              <div className=\"rounded bg-gray-50 p-2\">\n                <span className=\"font-medium\">dataView:</span> {' '}\n                [{Array.from(new Uint8Array(dataView.buffer)).join(', ')}]\n              </div>\n            </div>\n          </section>\n\n          <section>\n            <h2 className=\"mb-2 text-sm font-semibold text-gray-700\">Special Types</h2>\n            <div className=\"space-y-2 text-sm\">\n              <div className=\"rounded bg-gray-50 p-2\">\n                <span className=\"font-medium\">bigInt:</span> {bigInt.toString()}\n              </div>\n              <div className=\"rounded bg-gray-50 p-2\">\n                <span className=\"font-medium\">symbol:</span> {symbol.toString()}\n              </div>\n              <div className=\"rounded bg-gray-50 p-2\">\n                <span className=\"font-medium\">promise:</span> [Promise {promiseState}]\n              </div>\n              <div className=\"rounded bg-gray-50 p-2\">\n                <span className=\"font-medium\">weakMap:</span> {getWeakMapInfo()}\n              </div>\n              <div className=\"rounded bg-gray-50 p-2\">\n                <span className=\"font-medium\">weakSet:</span> {getWeakSetInfo()}\n              </div>\n            </div>\n          </section>\n        </div>\n\n        <div className=\"mt-4 text-center text-xs text-gray-500\">\n          Click anywhere to randomize values\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/website/components/todo-demo.tsx",
    "content": "'use client';\n\nimport { useState, useEffect, useCallback } from 'react';\n\ninterface Todo {\n  id: number;\n  text: string;\n  timestamp: Date;\n}\n\nfunction TodoInput({\n  value: input,\n  onChange: setInput,\n  onAdd: addTodo,\n}: {\n  value: string;\n  onChange: (value: string) => void;\n  onAdd: () => void;\n  }) {\n  return (\n    <div className=\"mb-4 flex gap-2\">\n      <input\n        type=\"text\"\n        value={input}\n        onChange={(e) => {\n          setInput(e.target.value)\n        }}\n        onKeyDown={(e) => e.key === 'Enter' && addTodo()}\n        className=\"flex-1 border p-2\"\n        placeholder=\"Add task...\"\n      />\n      <AddButton onClick={addTodo} />\n    </div>\n  );\n}\n\nfunction AddButton({ onClick }: { onClick: () => void }) {\n  return (\n    <button\n      onClick={onClick}\n      className=\"bg-black px-4 py-2 text-white\"\n    >\n      Add +\n    </button>\n  );\n}\n\nfunction TodoList({ items, onDelete }: {\n  items: Array<Todo>;\n  onDelete: (id: number) => void;\n}) {\n  return (\n    <ul className=\"space-y-2\">\n      {items.map((todo) => (\n        <TodoItem\n          key={todo.id}\n          todo={todo}\n          onDelete={() => onDelete(todo.id)}\n        />\n      ))}\n    </ul>\n  );\n}\n\nfunction TodoItem({ todo, onDelete }: {\n  todo: Todo;\n  onDelete: () => void;\n}) {\n  return (\n    <li className=\"group flex items-center justify-between border p-2\">\n      <div>\n        <div>{todo.text}</div>\n        <div className=\"text-xs text-gray-500\">\n          {todo.timestamp.toLocaleTimeString()}\n        </div>\n      </div>\n      <button\n        onClick={onDelete}\n        className=\"px-2 text-red-500 opacity-0 group-hover:opacity-100\"\n      >\n        ×\n      </button>\n    </li>\n  );\n}\n\nexport default function TodoDemo({ closeAction }: { closeAction: () => void }) {\n  const [todos, setTodos] = useState<Array<Todo>>([]);\n  const [input, setInput] = useState('');\n  const [isMobile, setIsMobile] = useState(false);\n\n  useEffect(() => {\n    setIsMobile(window.innerWidth < 768);\n    const handleResize = () => setIsMobile(window.innerWidth < 768);\n    window.addEventListener('resize', handleResize);\n    return () => window.removeEventListener('resize', handleResize);\n  }, []);\n\n  const addTodo = useCallback(() => {\n    if (!input.trim()) return;\n    setTodos([...todos, {\n      id: Date.now(),\n      text: input,\n      timestamp: new Date()\n    }]);\n    setInput('');\n  }, [input, todos]);\n\n  const deleteTodo = (id: number) => {\n    setTodos(todos.filter(todo => todo.id !== id));\n  };\n\n  const mobileClasses = \"p-4 bg-white border border-gray-200 rounded-lg w-full\";\n  const desktopClasses = \"p-4 bg-white border-l border-gray-200 w-[400px] h-full fixed right-0 top-0 shadow-lg\";\n\n  return (\n    <div className={isMobile ? mobileClasses : desktopClasses}>\n      <div className=\"mb-4 flex items-center justify-between\">\n        <h2 className=\"text-xl font-bold\">Demo</h2>\n        <button\n          onClick={closeAction}\n          className=\"text-2xl text-gray-500 hover:text-gray-700\"\n        >\n          ×\n        </button>\n      </div>\n      <div className=\"mb-4 text-sm text-gray-600\">\n        {todos.length} task{todos.length !== 1 ? 's' : ''}\n      </div>\n      <TodoInput\n        value={input}\n        onChange={setInput}\n        onAdd={addTodo}\n      />\n      <TodoList items={todos} onDelete={deleteTodo} />\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/website/next.config.ts",
    "content": "import type { NextConfig } from \"next\";\nimport ReactComponentNamePlugin from \"react-scan/react-component-name/webpack\";\n\nconst nextConfig: NextConfig = {\n  /* config options here */\n  webpack: (config) => {\n    config.plugins.push(ReactComponentNamePlugin({}))\n    return config\n  }\n};\n\nexport default nextConfig;\n"
  },
  {
    "path": "packages/website/package.json",
    "content": "{\n  \"name\": \"@react-scan/website\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"presync\": \"cp ../scan/dist/auto.global.js ./public/auto.global.js\",\n    \"dev\": \"pnpm presync && next dev --turbopack\",\n    \"build\": \"pnpm --filter react-scan build && pnpm presync && next build\",\n    \"postbuild\": \"node ../../scripts/version-warning.mjs\",\n    \"start\": \"next start\",\n    \"lint\": \"oxlint .\"\n  },\n  \"dependencies\": {\n    \"@vercel/analytics\": \"^1.4.1\",\n    \"@vercel/speed-insights\": \"^1.1.0\",\n    \"highlight.js\": \"^11.11.1\",\n    \"next\": \"15.2.6\",\n    \"react\": \"19.0.0\",\n    \"react-dom\": \"19.0.0\",\n    \"react-grab\": \"^0.1.15\",\n    \"react-scan\": \"^0.5.3\",\n    \"zod\": \"^3.23.8\"\n  },\n  \"devDependencies\": {\n    \"@types/react\": \"^19.0.10\",\n    \"@types/react-dom\": \"^19.0.4\"\n  }\n}\n"
  },
  {
    "path": "packages/website/postcss.config.mjs",
    "content": "/** @type {import('postcss-load-config').Config} */\nconst config = {\n  plugins: {\n    tailwindcss: {},\n  },\n};\n\nexport default config;\n"
  },
  {
    "path": "packages/website/public/auto.global.js",
    "content": "'use client';\n(function (exports) {\n  'use strict';\n\n  /**\n   * Copyright 2025 Aiden Bai, Million Software, Inc.\n   *\n   * Permission is hereby granted, free of charge, to any person obtaining a copy of this software\n   * and associated documentation files (the “Software”), to deal in the Software without restriction,\n   * including without limitation the rights to use, copy, modify, merge, publish, distribute,\n   * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is\n   * furnished to do so, subject to the following conditions:\n   *\n   * The above copyright notice and this permission notice shall be included in all copies or\n   * substantial portions of the Software.\n   *\n   * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n   * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n   * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n   * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n   */\n\n  // src/polyfills.ts\n  if (!Array.prototype.toSorted) {\n    Object.defineProperty(Array.prototype, \"toSorted\", {\n      value: function(compareFn) {\n        return [...this].sort(compareFn);\n      },\n      writable: true,\n      configurable: true\n    });\n  }\n\n  // ../../node_modules/.pnpm/bippy@0.3.33_@types+react@18.3.23_react@19.0.0/node_modules/bippy/dist/src-8Vg0r_-J.js\n  var version = \"0.3.33\";\n  var BIPPY_INSTRUMENTATION_STRING = `bippy-${version}`;\n  var objectDefineProperty = Object.defineProperty;\n  var objectHasOwnProperty = Object.prototype.hasOwnProperty;\n  var NO_OP = () => {\n  };\n  var checkDCE = (fn2) => {\n    try {\n      const code = Function.prototype.toString.call(fn2);\n      if (code.indexOf(\"^_^\") > -1) setTimeout(() => {\n        throw new Error(\"React is running in production mode, but dead code elimination has not been applied. Read how to correctly configure React for production: https://reactjs.org/link/perf-use-production-build\");\n      });\n    } catch {\n    }\n  };\n  var isRealReactDevtools = (rdtHook2 = getRDTHook()) => {\n    return \"getFiberRoots\" in rdtHook2;\n  };\n  var isReactRefreshOverride = false;\n  var injectFnStr = void 0;\n  var isReactRefresh = (rdtHook2 = getRDTHook()) => {\n    if (isReactRefreshOverride) return true;\n    if (typeof rdtHook2.inject === \"function\") injectFnStr = rdtHook2.inject.toString();\n    return Boolean(injectFnStr?.includes(\"(injected)\"));\n  };\n  var onActiveListeners = /* @__PURE__ */ new Set();\n  var _renderers = /* @__PURE__ */ new Set();\n  var installRDTHook = (onActive) => {\n    const renderers = /* @__PURE__ */ new Map();\n    let i5 = 0;\n    let rdtHook2 = {\n      _instrumentationIsActive: false,\n      _instrumentationSource: BIPPY_INSTRUMENTATION_STRING,\n      checkDCE,\n      hasUnsupportedRendererAttached: false,\n      inject(renderer) {\n        const nextID = ++i5;\n        renderers.set(nextID, renderer);\n        _renderers.add(renderer);\n        if (!rdtHook2._instrumentationIsActive) {\n          rdtHook2._instrumentationIsActive = true;\n          onActiveListeners.forEach((listener) => listener());\n        }\n        return nextID;\n      },\n      on: NO_OP,\n      onCommitFiberRoot: NO_OP,\n      onCommitFiberUnmount: NO_OP,\n      onPostCommitFiberRoot: NO_OP,\n      renderers,\n      supportsFiber: true,\n      supportsFlight: true\n    };\n    try {\n      objectDefineProperty(globalThis, \"__REACT_DEVTOOLS_GLOBAL_HOOK__\", {\n        configurable: true,\n        enumerable: true,\n        get() {\n          return rdtHook2;\n        },\n        set(newHook) {\n          if (newHook && typeof newHook === \"object\") {\n            const ourRenderers = rdtHook2.renderers;\n            rdtHook2 = newHook;\n            if (ourRenderers.size > 0) {\n              ourRenderers.forEach((renderer, id) => {\n                _renderers.add(renderer);\n                newHook.renderers.set(id, renderer);\n              });\n              patchRDTHook(onActive);\n            }\n          }\n        }\n      });\n      const originalWindowHasOwnProperty = window.hasOwnProperty;\n      let hasRanHack = false;\n      objectDefineProperty(window, \"hasOwnProperty\", {\n        configurable: true,\n        value: function(...args) {\n          try {\n            if (!hasRanHack && args[0] === \"__REACT_DEVTOOLS_GLOBAL_HOOK__\") {\n              globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__ = void 0;\n              hasRanHack = true;\n              return -0;\n            }\n          } catch {\n          }\n          return originalWindowHasOwnProperty.apply(this, args);\n        },\n        writable: true\n      });\n    } catch {\n      patchRDTHook(onActive);\n    }\n    return rdtHook2;\n  };\n  var patchRDTHook = (onActive) => {\n    if (onActive) onActiveListeners.add(onActive);\n    try {\n      const rdtHook2 = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;\n      if (!rdtHook2) return;\n      if (!rdtHook2._instrumentationSource) {\n        const isReactDevtools = isRealReactDevtools();\n        rdtHook2.checkDCE = checkDCE;\n        rdtHook2.supportsFiber = true;\n        rdtHook2.supportsFlight = true;\n        rdtHook2.hasUnsupportedRendererAttached = false;\n        rdtHook2._instrumentationSource = BIPPY_INSTRUMENTATION_STRING;\n        rdtHook2._instrumentationIsActive = false;\n        if (!isReactDevtools) rdtHook2.on = NO_OP;\n        if (rdtHook2.renderers.size) {\n          rdtHook2._instrumentationIsActive = true;\n          onActiveListeners.forEach((listener) => listener());\n          return;\n        }\n        const prevInject = rdtHook2.inject;\n        if (isReactRefresh(rdtHook2) && !isReactDevtools) {\n          isReactRefreshOverride = true;\n          const nextID = rdtHook2.inject({ scheduleRefresh() {\n          } });\n          if (nextID) rdtHook2._instrumentationIsActive = true;\n        }\n        rdtHook2.inject = (renderer) => {\n          const id = prevInject(renderer);\n          _renderers.add(renderer);\n          rdtHook2._instrumentationIsActive = true;\n          onActiveListeners.forEach((listener) => listener());\n          return id;\n        };\n      }\n      if (rdtHook2.renderers.size || rdtHook2._instrumentationIsActive || isReactRefresh()) onActive?.();\n    } catch {\n    }\n  };\n  var hasRDTHook = () => {\n    return objectHasOwnProperty.call(globalThis, \"__REACT_DEVTOOLS_GLOBAL_HOOK__\");\n  };\n  var getRDTHook = (onActive) => {\n    if (!hasRDTHook()) return installRDTHook(onActive);\n    patchRDTHook(onActive);\n    return globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;\n  };\n  var isClientEnvironment = () => {\n    return Boolean(typeof window !== \"undefined\" && (window.document?.createElement || window.navigator?.product === \"ReactNative\"));\n  };\n  var safelyInstallRDTHook = () => {\n    try {\n      if (isClientEnvironment()) getRDTHook();\n    } catch {\n    }\n  };\n  var FunctionComponentTag = 0;\n  var ClassComponentTag = 1;\n  var HostRootTag = 3;\n  var HostComponentTag = 5;\n  var HostTextTag = 6;\n  var FragmentTag = 7;\n  var ContextConsumerTag = 9;\n  var ForwardRefTag = 11;\n  var SuspenseComponentTag = 13;\n  var MemoComponentTag = 14;\n  var SimpleMemoComponentTag = 15;\n  var DehydratedSuspenseComponentTag = 18;\n  var OffscreenComponentTag = 22;\n  var LegacyHiddenComponentTag = 23;\n  var HostHoistableTag = 26;\n  var HostSingletonTag = 27;\n  var CONCURRENT_MODE_NUMBER = 60111;\n  var CONCURRENT_MODE_SYMBOL_STRING = \"Symbol(react.concurrent_mode)\";\n  var DEPRECATED_ASYNC_MODE_SYMBOL_STRING = \"Symbol(react.async_mode)\";\n  var PerformedWork = 1;\n  var Placement = 2;\n  var Hydrating = 4096;\n  var Update = 4;\n  var Cloned = 8;\n  var ChildDeletion = 16;\n  var ContentReset = 32;\n  var Snapshot = 1024;\n  var Visibility = 8192;\n  var MutationMask = Placement | Update | ChildDeletion | ContentReset | Hydrating | Visibility | Snapshot;\n  var isHostFiber = (fiber) => {\n    switch (fiber.tag) {\n      case HostComponentTag:\n      case HostHoistableTag:\n      case HostSingletonTag:\n        return true;\n      default:\n        return typeof fiber.type === \"string\";\n    }\n  };\n  var isCompositeFiber = (fiber) => {\n    switch (fiber.tag) {\n      case ClassComponentTag:\n      case ForwardRefTag:\n      case FunctionComponentTag:\n      case MemoComponentTag:\n      case SimpleMemoComponentTag:\n        return true;\n      default:\n        return false;\n    }\n  };\n  var traverseContexts = (fiber, selector) => {\n    try {\n      const nextDependencies = fiber.dependencies;\n      const prevDependencies = fiber.alternate?.dependencies;\n      if (!nextDependencies || !prevDependencies) return false;\n      if (typeof nextDependencies !== \"object\" || !(\"firstContext\" in nextDependencies) || typeof prevDependencies !== \"object\" || !(\"firstContext\" in prevDependencies)) return false;\n      let nextContext = nextDependencies.firstContext;\n      let prevContext = prevDependencies.firstContext;\n      while (nextContext && typeof nextContext === \"object\" && \"memoizedValue\" in nextContext || prevContext && typeof prevContext === \"object\" && \"memoizedValue\" in prevContext) {\n        if (selector(nextContext, prevContext) === true) return true;\n        nextContext = nextContext?.next;\n        prevContext = prevContext?.next;\n      }\n    } catch {\n    }\n    return false;\n  };\n  var didFiberRender = (fiber) => {\n    const nextProps = fiber.memoizedProps;\n    const prevProps = fiber.alternate?.memoizedProps || {};\n    const flags = fiber.flags ?? fiber.effectTag ?? 0;\n    switch (fiber.tag) {\n      case ClassComponentTag:\n      case ContextConsumerTag:\n      case ForwardRefTag:\n      case FunctionComponentTag:\n      case MemoComponentTag:\n      case SimpleMemoComponentTag:\n        return (flags & PerformedWork) === PerformedWork;\n      default:\n        if (!fiber.alternate) return true;\n        return prevProps !== nextProps || fiber.alternate.memoizedState !== fiber.memoizedState || fiber.alternate.ref !== fiber.ref;\n    }\n  };\n  var didFiberCommit = (fiber) => {\n    return Boolean((fiber.flags & (MutationMask | Cloned)) !== 0 || (fiber.subtreeFlags & (MutationMask | Cloned)) !== 0);\n  };\n  var getMutatedHostFibers = (fiber) => {\n    const mutations = [];\n    const stack = [fiber];\n    while (stack.length) {\n      const node = stack.pop();\n      if (!node) continue;\n      if (isHostFiber(node) && didFiberCommit(node) && didFiberRender(node)) mutations.push(node);\n      if (node.child) stack.push(node.child);\n      if (node.sibling) stack.push(node.sibling);\n    }\n    return mutations;\n  };\n  var shouldFilterFiber = (fiber) => {\n    switch (fiber.tag) {\n      case DehydratedSuspenseComponentTag:\n        return true;\n      case FragmentTag:\n      case HostTextTag:\n      case LegacyHiddenComponentTag:\n      case OffscreenComponentTag:\n        return true;\n      case HostRootTag:\n        return false;\n      default: {\n        const symbolOrNumber = typeof fiber.type === \"object\" && fiber.type !== null ? fiber.type.$$typeof : fiber.type;\n        const typeSymbol = typeof symbolOrNumber === \"symbol\" ? symbolOrNumber.toString() : symbolOrNumber;\n        switch (typeSymbol) {\n          case CONCURRENT_MODE_NUMBER:\n          case CONCURRENT_MODE_SYMBOL_STRING:\n          case DEPRECATED_ASYNC_MODE_SYMBOL_STRING:\n            return true;\n          default:\n            return false;\n        }\n      }\n    }\n  };\n  var getNearestHostFibers = (fiber) => {\n    const hostFibers = [];\n    const stack = [];\n    if (isHostFiber(fiber)) hostFibers.push(fiber);\n    else if (fiber.child) stack.push(fiber.child);\n    while (stack.length) {\n      const currentNode = stack.pop();\n      if (!currentNode) break;\n      if (isHostFiber(currentNode)) hostFibers.push(currentNode);\n      else if (currentNode.child) stack.push(currentNode.child);\n      if (currentNode.sibling) stack.push(currentNode.sibling);\n    }\n    return hostFibers;\n  };\n  function traverseFiber(fiber, selector, ascending = false) {\n    const isAsync = fiber && selector(fiber) instanceof Promise;\n    if (isAsync) return traverseFiberAsync(fiber, selector, ascending);\n    return traverseFiberSync(fiber, selector, ascending);\n  }\n  var traverseFiberSync = (fiber, selector, ascending = false) => {\n    if (!fiber) return null;\n    if (selector(fiber) === true) return fiber;\n    let child = ascending ? fiber.return : fiber.child;\n    while (child) {\n      const match = traverseFiberSync(child, selector, ascending);\n      if (match) return match;\n      child = ascending ? null : child.sibling;\n    }\n    return null;\n  };\n  var traverseFiberAsync = async (fiber, selector, ascending = false) => {\n    if (!fiber) return null;\n    if (await selector(fiber) === true) return fiber;\n    let child = ascending ? fiber.return : fiber.child;\n    while (child) {\n      const match = await traverseFiberAsync(child, selector, ascending);\n      if (match) return match;\n      child = ascending ? null : child.sibling;\n    }\n    return null;\n  };\n  var getTimings = (fiber) => {\n    const totalTime = fiber?.actualDuration ?? 0;\n    let selfTime = totalTime;\n    let child = fiber?.child ?? null;\n    while (totalTime > 0 && child != null) {\n      selfTime -= child.actualDuration ?? 0;\n      child = child.sibling;\n    }\n    return {\n      selfTime,\n      totalTime\n    };\n  };\n  var hasMemoCache = (fiber) => {\n    return Boolean(fiber.updateQueue?.memoCache);\n  };\n  var getType = (type) => {\n    const currentType = type;\n    if (typeof currentType === \"function\") return currentType;\n    if (typeof currentType === \"object\" && currentType) return getType(currentType.type || currentType.render);\n    return null;\n  };\n  var getDisplayName = (type) => {\n    const currentType = type;\n    if (typeof currentType === \"string\") return currentType;\n    if (typeof currentType !== \"function\" && !(typeof currentType === \"object\" && currentType)) return null;\n    const name = currentType.displayName || currentType.name || null;\n    if (name) return name;\n    const unwrappedType = getType(currentType);\n    if (!unwrappedType) return null;\n    return unwrappedType.displayName || unwrappedType.name || null;\n  };\n  var detectReactBuildType = (renderer) => {\n    try {\n      if (typeof renderer.version === \"string\" && renderer.bundleType > 0) return \"development\";\n    } catch {\n    }\n    return \"production\";\n  };\n  var isInstrumentationActive = () => {\n    const rdtHook2 = getRDTHook();\n    return Boolean(rdtHook2._instrumentationIsActive) || isRealReactDevtools() || isReactRefresh();\n  };\n  var fiberId = 0;\n  var fiberIdMap = /* @__PURE__ */ new WeakMap();\n  var setFiberId = (fiber, id = fiberId++) => {\n    fiberIdMap.set(fiber, id);\n  };\n  var getFiberId = (fiber) => {\n    let id = fiberIdMap.get(fiber);\n    if (!id && fiber.alternate) id = fiberIdMap.get(fiber.alternate);\n    if (!id) {\n      id = fiberId++;\n      setFiberId(fiber, id);\n    }\n    return id;\n  };\n  var mountFiberRecursively = (onRender2, firstChild, traverseSiblings) => {\n    let fiber = firstChild;\n    while (fiber != null) {\n      if (!fiberIdMap.has(fiber)) getFiberId(fiber);\n      const shouldIncludeInTree = !shouldFilterFiber(fiber);\n      if (shouldIncludeInTree && didFiberRender(fiber)) onRender2(fiber, \"mount\");\n      if (fiber.tag === SuspenseComponentTag) {\n        const isTimedOut = fiber.memoizedState !== null;\n        if (isTimedOut) {\n          const primaryChildFragment = fiber.child;\n          const fallbackChildFragment = primaryChildFragment ? primaryChildFragment.sibling : null;\n          if (fallbackChildFragment) {\n            const fallbackChild = fallbackChildFragment.child;\n            if (fallbackChild !== null) mountFiberRecursively(onRender2, fallbackChild, false);\n          }\n        } else {\n          let primaryChild = null;\n          if (fiber.child !== null) primaryChild = fiber.child.child;\n          if (primaryChild !== null) mountFiberRecursively(onRender2, primaryChild, false);\n        }\n      } else if (fiber.child != null) mountFiberRecursively(onRender2, fiber.child, true);\n      fiber = traverseSiblings ? fiber.sibling : null;\n    }\n  };\n  var updateFiberRecursively = (onRender2, nextFiber, prevFiber, parentFiber) => {\n    if (!fiberIdMap.has(nextFiber)) getFiberId(nextFiber);\n    if (!prevFiber) return;\n    if (!fiberIdMap.has(prevFiber)) getFiberId(prevFiber);\n    const isSuspense = nextFiber.tag === SuspenseComponentTag;\n    const shouldIncludeInTree = !shouldFilterFiber(nextFiber);\n    if (shouldIncludeInTree && didFiberRender(nextFiber)) onRender2(nextFiber, \"update\");\n    const prevDidTimeout = isSuspense && prevFiber.memoizedState !== null;\n    const nextDidTimeOut = isSuspense && nextFiber.memoizedState !== null;\n    if (prevDidTimeout && nextDidTimeOut) {\n      const nextFallbackChildSet = nextFiber.child?.sibling ?? null;\n      const prevFallbackChildSet = prevFiber.child?.sibling ?? null;\n      if (nextFallbackChildSet !== null && prevFallbackChildSet !== null) updateFiberRecursively(onRender2, nextFallbackChildSet, prevFallbackChildSet);\n    } else if (prevDidTimeout && !nextDidTimeOut) {\n      const nextPrimaryChildSet = nextFiber.child;\n      if (nextPrimaryChildSet !== null) mountFiberRecursively(onRender2, nextPrimaryChildSet, true);\n    } else if (!prevDidTimeout && nextDidTimeOut) {\n      unmountFiberChildrenRecursively(onRender2, prevFiber);\n      const nextFallbackChildSet = nextFiber.child?.sibling ?? null;\n      if (nextFallbackChildSet !== null) mountFiberRecursively(onRender2, nextFallbackChildSet, true);\n    } else if (nextFiber.child !== prevFiber.child) {\n      let nextChild = nextFiber.child;\n      while (nextChild) {\n        if (nextChild.alternate) {\n          const prevChild = nextChild.alternate;\n          updateFiberRecursively(onRender2, nextChild, prevChild);\n        } else mountFiberRecursively(onRender2, nextChild, false);\n        nextChild = nextChild.sibling;\n      }\n    }\n  };\n  var unmountFiber = (onRender2, fiber) => {\n    const isRoot = fiber.tag === HostRootTag;\n    if (isRoot || !shouldFilterFiber(fiber)) onRender2(fiber, \"unmount\");\n  };\n  var unmountFiberChildrenRecursively = (onRender2, fiber) => {\n    const isTimedOutSuspense = fiber.tag === SuspenseComponentTag && fiber.memoizedState !== null;\n    let child = fiber.child;\n    if (isTimedOutSuspense) {\n      const primaryChildFragment = fiber.child;\n      const fallbackChildFragment = primaryChildFragment?.sibling ?? null;\n      child = fallbackChildFragment?.child ?? null;\n    }\n    while (child !== null) {\n      if (child.return !== null) {\n        unmountFiber(onRender2, child);\n        unmountFiberChildrenRecursively(onRender2, child);\n      }\n      child = child.sibling;\n    }\n  };\n  var commitId = 0;\n  var rootInstanceMap = /* @__PURE__ */ new WeakMap();\n  var traverseRenderedFibers = (root, onRender2) => {\n    const fiber = \"current\" in root ? root.current : root;\n    let rootInstance = rootInstanceMap.get(root);\n    if (!rootInstance) {\n      rootInstance = {\n        id: commitId++,\n        prevFiber: null\n      };\n      rootInstanceMap.set(root, rootInstance);\n    }\n    const { prevFiber } = rootInstance;\n    if (!fiber) unmountFiber(onRender2, fiber);\n    else if (prevFiber !== null) {\n      const wasMounted = prevFiber && prevFiber.memoizedState != null && prevFiber.memoizedState.element != null && prevFiber.memoizedState.isDehydrated !== true;\n      const isMounted = fiber.memoizedState != null && fiber.memoizedState.element != null && fiber.memoizedState.isDehydrated !== true;\n      if (!wasMounted && isMounted) mountFiberRecursively(onRender2, fiber, false);\n      else if (wasMounted && isMounted) updateFiberRecursively(onRender2, fiber, fiber.alternate);\n      else if (wasMounted && !isMounted) unmountFiber(onRender2, fiber);\n    } else mountFiberRecursively(onRender2, fiber, true);\n    rootInstance.prevFiber = fiber;\n  };\n  var instrument = (options) => {\n    return getRDTHook(() => {\n      const rdtHook2 = getRDTHook();\n      options.onActive?.();\n      rdtHook2._instrumentationSource = options.name ?? BIPPY_INSTRUMENTATION_STRING;\n      const prevOnCommitFiberRoot = rdtHook2.onCommitFiberRoot;\n      if (options.onCommitFiberRoot) rdtHook2.onCommitFiberRoot = (rendererID, root, priority) => {\n        if (prevOnCommitFiberRoot) prevOnCommitFiberRoot(rendererID, root, priority);\n        options.onCommitFiberRoot?.(rendererID, root, priority);\n      };\n      const prevOnCommitFiberUnmount = rdtHook2.onCommitFiberUnmount;\n      if (options.onCommitFiberUnmount) rdtHook2.onCommitFiberUnmount = (rendererID, root) => {\n        if (prevOnCommitFiberUnmount) prevOnCommitFiberUnmount(rendererID, root);\n        options.onCommitFiberUnmount?.(rendererID, root);\n      };\n      const prevOnPostCommitFiberRoot = rdtHook2.onPostCommitFiberRoot;\n      if (options.onPostCommitFiberRoot) rdtHook2.onPostCommitFiberRoot = (rendererID, root) => {\n        if (prevOnPostCommitFiberRoot) prevOnPostCommitFiberRoot(rendererID, root);\n        options.onPostCommitFiberRoot?.(rendererID, root);\n      };\n    });\n  };\n  safelyInstallRDTHook();\n\n  // src/web/utils/constants.ts\n  var IS_CLIENT = typeof window !== \"undefined\";\n\n  // ../../node_modules/.pnpm/preact@10.26.9/node_modules/preact/dist/preact.module.js\n  var n;\n  var l;\n  var u;\n  var t;\n  var i;\n  var r;\n  var o;\n  var e;\n  var f;\n  var c;\n  var s;\n  var a;\n  var h;\n  var p = {};\n  var v = [];\n  var y = /acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;\n  var w = Array.isArray;\n  function d(n3, l5) {\n    for (var u5 in l5) n3[u5] = l5[u5];\n    return n3;\n  }\n  function g(n3) {\n    n3 && n3.parentNode && n3.parentNode.removeChild(n3);\n  }\n  function _(l5, u5, t4) {\n    var i5, r5, o4, e4 = {};\n    for (o4 in u5) \"key\" == o4 ? i5 = u5[o4] : \"ref\" == o4 ? r5 = u5[o4] : e4[o4] = u5[o4];\n    if (arguments.length > 2 && (e4.children = arguments.length > 3 ? n.call(arguments, 2) : t4), \"function\" == typeof l5 && null != l5.defaultProps) for (o4 in l5.defaultProps) void 0 === e4[o4] && (e4[o4] = l5.defaultProps[o4]);\n    return m(l5, e4, i5, r5, null);\n  }\n  function m(n3, t4, i5, r5, o4) {\n    var e4 = { type: n3, props: t4, key: i5, ref: r5, __k: null, __: null, __b: 0, __e: null, __c: null, constructor: void 0, __v: null == o4 ? ++u : o4, __i: -1, __u: 0 };\n    return null == o4 && null != l.vnode && l.vnode(e4), e4;\n  }\n  function k(n3) {\n    return n3.children;\n  }\n  function x(n3, l5) {\n    this.props = n3, this.context = l5;\n  }\n  function S(n3, l5) {\n    if (null == l5) return n3.__ ? S(n3.__, n3.__i + 1) : null;\n    for (var u5; l5 < n3.__k.length; l5++) if (null != (u5 = n3.__k[l5]) && null != u5.__e) return u5.__e;\n    return \"function\" == typeof n3.type ? S(n3) : null;\n  }\n  function C(n3) {\n    var l5, u5;\n    if (null != (n3 = n3.__) && null != n3.__c) {\n      for (n3.__e = n3.__c.base = null, l5 = 0; l5 < n3.__k.length; l5++) if (null != (u5 = n3.__k[l5]) && null != u5.__e) {\n        n3.__e = n3.__c.base = u5.__e;\n        break;\n      }\n      return C(n3);\n    }\n  }\n  function M(n3) {\n    (!n3.__d && (n3.__d = true) && i.push(n3) && !$.__r++ || r != l.debounceRendering) && ((r = l.debounceRendering) || o)($);\n  }\n  function $() {\n    for (var n3, u5, t4, r5, o4, f5, c4, s5 = 1; i.length; ) i.length > s5 && i.sort(e), n3 = i.shift(), s5 = i.length, n3.__d && (t4 = void 0, o4 = (r5 = (u5 = n3).__v).__e, f5 = [], c4 = [], u5.__P && ((t4 = d({}, r5)).__v = r5.__v + 1, l.vnode && l.vnode(t4), O(u5.__P, t4, r5, u5.__n, u5.__P.namespaceURI, 32 & r5.__u ? [o4] : null, f5, null == o4 ? S(r5) : o4, !!(32 & r5.__u), c4), t4.__v = r5.__v, t4.__.__k[t4.__i] = t4, z(f5, t4, c4), t4.__e != o4 && C(t4)));\n    $.__r = 0;\n  }\n  function I(n3, l5, u5, t4, i5, r5, o4, e4, f5, c4, s5) {\n    var a4, h5, y4, w4, d5, g5, _4 = t4 && t4.__k || v, m3 = l5.length;\n    for (f5 = P(u5, l5, _4, f5, m3), a4 = 0; a4 < m3; a4++) null != (y4 = u5.__k[a4]) && (h5 = -1 == y4.__i ? p : _4[y4.__i] || p, y4.__i = a4, g5 = O(n3, y4, h5, i5, r5, o4, e4, f5, c4, s5), w4 = y4.__e, y4.ref && h5.ref != y4.ref && (h5.ref && q(h5.ref, null, y4), s5.push(y4.ref, y4.__c || w4, y4)), null == d5 && null != w4 && (d5 = w4), 4 & y4.__u || h5.__k === y4.__k ? f5 = A(y4, f5, n3) : \"function\" == typeof y4.type && void 0 !== g5 ? f5 = g5 : w4 && (f5 = w4.nextSibling), y4.__u &= -7);\n    return u5.__e = d5, f5;\n  }\n  function P(n3, l5, u5, t4, i5) {\n    var r5, o4, e4, f5, c4, s5 = u5.length, a4 = s5, h5 = 0;\n    for (n3.__k = new Array(i5), r5 = 0; r5 < i5; r5++) null != (o4 = l5[r5]) && \"boolean\" != typeof o4 && \"function\" != typeof o4 ? (f5 = r5 + h5, (o4 = n3.__k[r5] = \"string\" == typeof o4 || \"number\" == typeof o4 || \"bigint\" == typeof o4 || o4.constructor == String ? m(null, o4, null, null, null) : w(o4) ? m(k, { children: o4 }, null, null, null) : null == o4.constructor && o4.__b > 0 ? m(o4.type, o4.props, o4.key, o4.ref ? o4.ref : null, o4.__v) : o4).__ = n3, o4.__b = n3.__b + 1, e4 = null, -1 != (c4 = o4.__i = L(o4, u5, f5, a4)) && (a4--, (e4 = u5[c4]) && (e4.__u |= 2)), null == e4 || null == e4.__v ? (-1 == c4 && (i5 > s5 ? h5-- : i5 < s5 && h5++), \"function\" != typeof o4.type && (o4.__u |= 4)) : c4 != f5 && (c4 == f5 - 1 ? h5-- : c4 == f5 + 1 ? h5++ : (c4 > f5 ? h5-- : h5++, o4.__u |= 4))) : n3.__k[r5] = null;\n    if (a4) for (r5 = 0; r5 < s5; r5++) null != (e4 = u5[r5]) && 0 == (2 & e4.__u) && (e4.__e == t4 && (t4 = S(e4)), B(e4, e4));\n    return t4;\n  }\n  function A(n3, l5, u5) {\n    var t4, i5;\n    if (\"function\" == typeof n3.type) {\n      for (t4 = n3.__k, i5 = 0; t4 && i5 < t4.length; i5++) t4[i5] && (t4[i5].__ = n3, l5 = A(t4[i5], l5, u5));\n      return l5;\n    }\n    n3.__e != l5 && (l5 && n3.type && !u5.contains(l5) && (l5 = S(n3)), u5.insertBefore(n3.__e, l5 || null), l5 = n3.__e);\n    do {\n      l5 = l5 && l5.nextSibling;\n    } while (null != l5 && 8 == l5.nodeType);\n    return l5;\n  }\n  function H(n3, l5) {\n    return l5 = l5 || [], null == n3 || \"boolean\" == typeof n3 || (w(n3) ? n3.some(function(n4) {\n      H(n4, l5);\n    }) : l5.push(n3)), l5;\n  }\n  function L(n3, l5, u5, t4) {\n    var i5, r5, o4 = n3.key, e4 = n3.type, f5 = l5[u5];\n    if (null === f5 && null == n3.key || f5 && o4 == f5.key && e4 == f5.type && 0 == (2 & f5.__u)) return u5;\n    if (t4 > (null != f5 && 0 == (2 & f5.__u) ? 1 : 0)) for (i5 = u5 - 1, r5 = u5 + 1; i5 >= 0 || r5 < l5.length; ) {\n      if (i5 >= 0) {\n        if ((f5 = l5[i5]) && 0 == (2 & f5.__u) && o4 == f5.key && e4 == f5.type) return i5;\n        i5--;\n      }\n      if (r5 < l5.length) {\n        if ((f5 = l5[r5]) && 0 == (2 & f5.__u) && o4 == f5.key && e4 == f5.type) return r5;\n        r5++;\n      }\n    }\n    return -1;\n  }\n  function T(n3, l5, u5) {\n    \"-\" == l5[0] ? n3.setProperty(l5, null == u5 ? \"\" : u5) : n3[l5] = null == u5 ? \"\" : \"number\" != typeof u5 || y.test(l5) ? u5 : u5 + \"px\";\n  }\n  function j(n3, l5, u5, t4, i5) {\n    var r5, o4;\n    n: if (\"style\" == l5) if (\"string\" == typeof u5) n3.style.cssText = u5;\n    else {\n      if (\"string\" == typeof t4 && (n3.style.cssText = t4 = \"\"), t4) for (l5 in t4) u5 && l5 in u5 || T(n3.style, l5, \"\");\n      if (u5) for (l5 in u5) t4 && u5[l5] == t4[l5] || T(n3.style, l5, u5[l5]);\n    }\n    else if (\"o\" == l5[0] && \"n\" == l5[1]) r5 = l5 != (l5 = l5.replace(f, \"$1\")), o4 = l5.toLowerCase(), l5 = o4 in n3 || \"onFocusOut\" == l5 || \"onFocusIn\" == l5 ? o4.slice(2) : l5.slice(2), n3.l || (n3.l = {}), n3.l[l5 + r5] = u5, u5 ? t4 ? u5.u = t4.u : (u5.u = c, n3.addEventListener(l5, r5 ? a : s, r5)) : n3.removeEventListener(l5, r5 ? a : s, r5);\n    else {\n      if (\"http://www.w3.org/2000/svg\" == i5) l5 = l5.replace(/xlink(H|:h)/, \"h\").replace(/sName$/, \"s\");\n      else if (\"width\" != l5 && \"height\" != l5 && \"href\" != l5 && \"list\" != l5 && \"form\" != l5 && \"tabIndex\" != l5 && \"download\" != l5 && \"rowSpan\" != l5 && \"colSpan\" != l5 && \"role\" != l5 && \"popover\" != l5 && l5 in n3) try {\n        n3[l5] = null == u5 ? \"\" : u5;\n        break n;\n      } catch (n4) {\n      }\n      \"function\" == typeof u5 || (null == u5 || false === u5 && \"-\" != l5[4] ? n3.removeAttribute(l5) : n3.setAttribute(l5, \"popover\" == l5 && 1 == u5 ? \"\" : u5));\n    }\n  }\n  function F(n3) {\n    return function(u5) {\n      if (this.l) {\n        var t4 = this.l[u5.type + n3];\n        if (null == u5.t) u5.t = c++;\n        else if (u5.t < t4.u) return;\n        return t4(l.event ? l.event(u5) : u5);\n      }\n    };\n  }\n  function O(n3, u5, t4, i5, r5, o4, e4, f5, c4, s5) {\n    var a4, h5, p5, v5, y4, _4, m3, b3, S2, C4, M3, $3, P4, A4, H3, L2, T4, j4 = u5.type;\n    if (null != u5.constructor) return null;\n    128 & t4.__u && (c4 = !!(32 & t4.__u), o4 = [f5 = u5.__e = t4.__e]), (a4 = l.__b) && a4(u5);\n    n: if (\"function\" == typeof j4) try {\n      if (b3 = u5.props, S2 = \"prototype\" in j4 && j4.prototype.render, C4 = (a4 = j4.contextType) && i5[a4.__c], M3 = a4 ? C4 ? C4.props.value : a4.__ : i5, t4.__c ? m3 = (h5 = u5.__c = t4.__c).__ = h5.__E : (S2 ? u5.__c = h5 = new j4(b3, M3) : (u5.__c = h5 = new x(b3, M3), h5.constructor = j4, h5.render = D), C4 && C4.sub(h5), h5.props = b3, h5.state || (h5.state = {}), h5.context = M3, h5.__n = i5, p5 = h5.__d = true, h5.__h = [], h5._sb = []), S2 && null == h5.__s && (h5.__s = h5.state), S2 && null != j4.getDerivedStateFromProps && (h5.__s == h5.state && (h5.__s = d({}, h5.__s)), d(h5.__s, j4.getDerivedStateFromProps(b3, h5.__s))), v5 = h5.props, y4 = h5.state, h5.__v = u5, p5) S2 && null == j4.getDerivedStateFromProps && null != h5.componentWillMount && h5.componentWillMount(), S2 && null != h5.componentDidMount && h5.__h.push(h5.componentDidMount);\n      else {\n        if (S2 && null == j4.getDerivedStateFromProps && b3 !== v5 && null != h5.componentWillReceiveProps && h5.componentWillReceiveProps(b3, M3), !h5.__e && null != h5.shouldComponentUpdate && false === h5.shouldComponentUpdate(b3, h5.__s, M3) || u5.__v == t4.__v) {\n          for (u5.__v != t4.__v && (h5.props = b3, h5.state = h5.__s, h5.__d = false), u5.__e = t4.__e, u5.__k = t4.__k, u5.__k.some(function(n4) {\n            n4 && (n4.__ = u5);\n          }), $3 = 0; $3 < h5._sb.length; $3++) h5.__h.push(h5._sb[$3]);\n          h5._sb = [], h5.__h.length && e4.push(h5);\n          break n;\n        }\n        null != h5.componentWillUpdate && h5.componentWillUpdate(b3, h5.__s, M3), S2 && null != h5.componentDidUpdate && h5.__h.push(function() {\n          h5.componentDidUpdate(v5, y4, _4);\n        });\n      }\n      if (h5.context = M3, h5.props = b3, h5.__P = n3, h5.__e = false, P4 = l.__r, A4 = 0, S2) {\n        for (h5.state = h5.__s, h5.__d = false, P4 && P4(u5), a4 = h5.render(h5.props, h5.state, h5.context), H3 = 0; H3 < h5._sb.length; H3++) h5.__h.push(h5._sb[H3]);\n        h5._sb = [];\n      } else do {\n        h5.__d = false, P4 && P4(u5), a4 = h5.render(h5.props, h5.state, h5.context), h5.state = h5.__s;\n      } while (h5.__d && ++A4 < 25);\n      h5.state = h5.__s, null != h5.getChildContext && (i5 = d(d({}, i5), h5.getChildContext())), S2 && !p5 && null != h5.getSnapshotBeforeUpdate && (_4 = h5.getSnapshotBeforeUpdate(v5, y4)), L2 = a4, null != a4 && a4.type === k && null == a4.key && (L2 = N(a4.props.children)), f5 = I(n3, w(L2) ? L2 : [L2], u5, t4, i5, r5, o4, e4, f5, c4, s5), h5.base = u5.__e, u5.__u &= -161, h5.__h.length && e4.push(h5), m3 && (h5.__E = h5.__ = null);\n    } catch (n4) {\n      if (u5.__v = null, c4 || null != o4) if (n4.then) {\n        for (u5.__u |= c4 ? 160 : 128; f5 && 8 == f5.nodeType && f5.nextSibling; ) f5 = f5.nextSibling;\n        o4[o4.indexOf(f5)] = null, u5.__e = f5;\n      } else for (T4 = o4.length; T4--; ) g(o4[T4]);\n      else u5.__e = t4.__e, u5.__k = t4.__k;\n      l.__e(n4, u5, t4);\n    }\n    else null == o4 && u5.__v == t4.__v ? (u5.__k = t4.__k, u5.__e = t4.__e) : f5 = u5.__e = V(t4.__e, u5, t4, i5, r5, o4, e4, c4, s5);\n    return (a4 = l.diffed) && a4(u5), 128 & u5.__u ? void 0 : f5;\n  }\n  function z(n3, u5, t4) {\n    for (var i5 = 0; i5 < t4.length; i5++) q(t4[i5], t4[++i5], t4[++i5]);\n    l.__c && l.__c(u5, n3), n3.some(function(u6) {\n      try {\n        n3 = u6.__h, u6.__h = [], n3.some(function(n4) {\n          n4.call(u6);\n        });\n      } catch (n4) {\n        l.__e(n4, u6.__v);\n      }\n    });\n  }\n  function N(n3) {\n    return \"object\" != typeof n3 || null == n3 || n3.__b && n3.__b > 0 ? n3 : w(n3) ? n3.map(N) : d({}, n3);\n  }\n  function V(u5, t4, i5, r5, o4, e4, f5, c4, s5) {\n    var a4, h5, v5, y4, d5, _4, m3, b3 = i5.props, k3 = t4.props, x4 = t4.type;\n    if (\"svg\" == x4 ? o4 = \"http://www.w3.org/2000/svg\" : \"math\" == x4 ? o4 = \"http://www.w3.org/1998/Math/MathML\" : o4 || (o4 = \"http://www.w3.org/1999/xhtml\"), null != e4) {\n      for (a4 = 0; a4 < e4.length; a4++) if ((d5 = e4[a4]) && \"setAttribute\" in d5 == !!x4 && (x4 ? d5.localName == x4 : 3 == d5.nodeType)) {\n        u5 = d5, e4[a4] = null;\n        break;\n      }\n    }\n    if (null == u5) {\n      if (null == x4) return document.createTextNode(k3);\n      u5 = document.createElementNS(o4, x4, k3.is && k3), c4 && (l.__m && l.__m(t4, e4), c4 = false), e4 = null;\n    }\n    if (null == x4) b3 === k3 || c4 && u5.data == k3 || (u5.data = k3);\n    else {\n      if (e4 = e4 && n.call(u5.childNodes), b3 = i5.props || p, !c4 && null != e4) for (b3 = {}, a4 = 0; a4 < u5.attributes.length; a4++) b3[(d5 = u5.attributes[a4]).name] = d5.value;\n      for (a4 in b3) if (d5 = b3[a4], \"children\" == a4) ;\n      else if (\"dangerouslySetInnerHTML\" == a4) v5 = d5;\n      else if (!(a4 in k3)) {\n        if (\"value\" == a4 && \"defaultValue\" in k3 || \"checked\" == a4 && \"defaultChecked\" in k3) continue;\n        j(u5, a4, null, d5, o4);\n      }\n      for (a4 in k3) d5 = k3[a4], \"children\" == a4 ? y4 = d5 : \"dangerouslySetInnerHTML\" == a4 ? h5 = d5 : \"value\" == a4 ? _4 = d5 : \"checked\" == a4 ? m3 = d5 : c4 && \"function\" != typeof d5 || b3[a4] === d5 || j(u5, a4, d5, b3[a4], o4);\n      if (h5) c4 || v5 && (h5.__html == v5.__html || h5.__html == u5.innerHTML) || (u5.innerHTML = h5.__html), t4.__k = [];\n      else if (v5 && (u5.innerHTML = \"\"), I(\"template\" == t4.type ? u5.content : u5, w(y4) ? y4 : [y4], t4, i5, r5, \"foreignObject\" == x4 ? \"http://www.w3.org/1999/xhtml\" : o4, e4, f5, e4 ? e4[0] : i5.__k && S(i5, 0), c4, s5), null != e4) for (a4 = e4.length; a4--; ) g(e4[a4]);\n      c4 || (a4 = \"value\", \"progress\" == x4 && null == _4 ? u5.removeAttribute(\"value\") : null != _4 && (_4 !== u5[a4] || \"progress\" == x4 && !_4 || \"option\" == x4 && _4 != b3[a4]) && j(u5, a4, _4, b3[a4], o4), a4 = \"checked\", null != m3 && m3 != u5[a4] && j(u5, a4, m3, b3[a4], o4));\n    }\n    return u5;\n  }\n  function q(n3, u5, t4) {\n    try {\n      if (\"function\" == typeof n3) {\n        var i5 = \"function\" == typeof n3.__u;\n        i5 && n3.__u(), i5 && null == u5 || (n3.__u = n3(u5));\n      } else n3.current = u5;\n    } catch (n4) {\n      l.__e(n4, t4);\n    }\n  }\n  function B(n3, u5, t4) {\n    var i5, r5;\n    if (l.unmount && l.unmount(n3), (i5 = n3.ref) && (i5.current && i5.current != n3.__e || q(i5, null, u5)), null != (i5 = n3.__c)) {\n      if (i5.componentWillUnmount) try {\n        i5.componentWillUnmount();\n      } catch (n4) {\n        l.__e(n4, u5);\n      }\n      i5.base = i5.__P = null;\n    }\n    if (i5 = n3.__k) for (r5 = 0; r5 < i5.length; r5++) i5[r5] && B(i5[r5], u5, t4 || \"function\" != typeof n3.type);\n    t4 || g(n3.__e), n3.__c = n3.__ = n3.__e = void 0;\n  }\n  function D(n3, l5, u5) {\n    return this.constructor(n3, u5);\n  }\n  function E(u5, t4, i5) {\n    var r5, o4, e4, f5;\n    t4 == document && (t4 = document.documentElement), l.__ && l.__(u5, t4), o4 = (r5 = \"function\" == \"undefined\") ? null : t4.__k, e4 = [], f5 = [], O(t4, u5 = (t4).__k = _(k, null, [u5]), o4 || p, p, t4.namespaceURI, o4 ? null : t4.firstChild ? n.call(t4.childNodes) : null, e4, o4 ? o4.__e : t4.firstChild, r5, f5), z(e4, u5, f5);\n  }\n  function K(n3) {\n    function l5(n4) {\n      var u5, t4;\n      return this.getChildContext || (u5 = /* @__PURE__ */ new Set(), (t4 = {})[l5.__c] = this, this.getChildContext = function() {\n        return t4;\n      }, this.componentWillUnmount = function() {\n        u5 = null;\n      }, this.shouldComponentUpdate = function(n5) {\n        this.props.value != n5.value && u5.forEach(function(n6) {\n          n6.__e = true, M(n6);\n        });\n      }, this.sub = function(n5) {\n        u5.add(n5);\n        var l6 = n5.componentWillUnmount;\n        n5.componentWillUnmount = function() {\n          u5 && u5.delete(n5), l6 && l6.call(n5);\n        };\n      }), n4.children;\n    }\n    return l5.__c = \"__cC\" + h++, l5.__ = n3, l5.Provider = l5.__l = (l5.Consumer = function(n4, l6) {\n      return n4.children(l6);\n    }).contextType = l5, l5;\n  }\n  n = v.slice, l = { __e: function(n3, l5, u5, t4) {\n    for (var i5, r5, o4; l5 = l5.__; ) if ((i5 = l5.__c) && !i5.__) try {\n      if ((r5 = i5.constructor) && null != r5.getDerivedStateFromError && (i5.setState(r5.getDerivedStateFromError(n3)), o4 = i5.__d), null != i5.componentDidCatch && (i5.componentDidCatch(n3, t4 || {}), o4 = i5.__d), o4) return i5.__E = i5;\n    } catch (l6) {\n      n3 = l6;\n    }\n    throw n3;\n  } }, u = 0, t = function(n3) {\n    return null != n3 && null == n3.constructor;\n  }, x.prototype.setState = function(n3, l5) {\n    var u5;\n    u5 = null != this.__s && this.__s != this.state ? this.__s : this.__s = d({}, this.state), \"function\" == typeof n3 && (n3 = n3(d({}, u5), this.props)), n3 && d(u5, n3), null != n3 && this.__v && (l5 && this._sb.push(l5), M(this));\n  }, x.prototype.forceUpdate = function(n3) {\n    this.__v && (this.__e = true, n3 && this.__h.push(n3), M(this));\n  }, x.prototype.render = k, i = [], o = \"function\" == typeof Promise ? Promise.prototype.then.bind(Promise.resolve()) : setTimeout, e = function(n3, l5) {\n    return n3.__v.__b - l5.__v.__b;\n  }, $.__r = 0, f = /(PointerCapture)$|Capture$/i, c = 0, s = F(false), a = F(true), h = 0;\n\n  // ../../node_modules/.pnpm/preact@10.26.9/node_modules/preact/hooks/dist/hooks.module.js\n  var t2;\n  var r2;\n  var u2;\n  var i2;\n  var o2 = 0;\n  var f2 = [];\n  var c2 = l;\n  var e2 = c2.__b;\n  var a2 = c2.__r;\n  var v2 = c2.diffed;\n  var l2 = c2.__c;\n  var m2 = c2.unmount;\n  var s2 = c2.__;\n  function p2(n3, t4) {\n    c2.__h && c2.__h(r2, n3, o2 || t4), o2 = 0;\n    var u5 = r2.__H || (r2.__H = { __: [], __h: [] });\n    return n3 >= u5.__.length && u5.__.push({}), u5.__[n3];\n  }\n  function d2(n3) {\n    return o2 = 1, h2(D2, n3);\n  }\n  function h2(n3, u5, i5) {\n    var o4 = p2(t2++, 2);\n    if (o4.t = n3, !o4.__c && (o4.__ = [D2(void 0, u5), function(n4) {\n      var t4 = o4.__N ? o4.__N[0] : o4.__[0], r5 = o4.t(t4, n4);\n      t4 !== r5 && (o4.__N = [r5, o4.__[1]], o4.__c.setState({}));\n    }], o4.__c = r2, !r2.__f)) {\n      var f5 = function(n4, t4, r5) {\n        if (!o4.__c.__H) return true;\n        var u6 = o4.__c.__H.__.filter(function(n5) {\n          return !!n5.__c;\n        });\n        if (u6.every(function(n5) {\n          return !n5.__N;\n        })) return !c4 || c4.call(this, n4, t4, r5);\n        var i6 = o4.__c.props !== n4;\n        return u6.forEach(function(n5) {\n          if (n5.__N) {\n            var t5 = n5.__[0];\n            n5.__ = n5.__N, n5.__N = void 0, t5 !== n5.__[0] && (i6 = true);\n          }\n        }), c4 && c4.call(this, n4, t4, r5) || i6;\n      };\n      r2.__f = true;\n      var c4 = r2.shouldComponentUpdate, e4 = r2.componentWillUpdate;\n      r2.componentWillUpdate = function(n4, t4, r5) {\n        if (this.__e) {\n          var u6 = c4;\n          c4 = void 0, f5(n4, t4, r5), c4 = u6;\n        }\n        e4 && e4.call(this, n4, t4, r5);\n      }, r2.shouldComponentUpdate = f5;\n    }\n    return o4.__N || o4.__;\n  }\n  function y2(n3, u5) {\n    var i5 = p2(t2++, 3);\n    !c2.__s && C2(i5.__H, u5) && (i5.__ = n3, i5.u = u5, r2.__H.__h.push(i5));\n  }\n  function _2(n3, u5) {\n    var i5 = p2(t2++, 4);\n    !c2.__s && C2(i5.__H, u5) && (i5.__ = n3, i5.u = u5, r2.__h.push(i5));\n  }\n  function A2(n3) {\n    return o2 = 5, T2(function() {\n      return { current: n3 };\n    }, []);\n  }\n  function T2(n3, r5) {\n    var u5 = p2(t2++, 7);\n    return C2(u5.__H, r5) && (u5.__ = n3(), u5.__H = r5, u5.__h = n3), u5.__;\n  }\n  function q2(n3, t4) {\n    return o2 = 8, T2(function() {\n      return n3;\n    }, t4);\n  }\n  function x2(n3) {\n    var u5 = r2.context[n3.__c], i5 = p2(t2++, 9);\n    return i5.c = n3, u5 ? (null == i5.__ && (i5.__ = true, u5.sub(r2)), u5.props.value) : n3.__;\n  }\n  function j2() {\n    for (var n3; n3 = f2.shift(); ) if (n3.__P && n3.__H) try {\n      n3.__H.__h.forEach(z2), n3.__H.__h.forEach(B2), n3.__H.__h = [];\n    } catch (t4) {\n      n3.__H.__h = [], c2.__e(t4, n3.__v);\n    }\n  }\n  c2.__b = function(n3) {\n    r2 = null, e2 && e2(n3);\n  }, c2.__ = function(n3, t4) {\n    n3 && t4.__k && t4.__k.__m && (n3.__m = t4.__k.__m), s2 && s2(n3, t4);\n  }, c2.__r = function(n3) {\n    a2 && a2(n3), t2 = 0;\n    var i5 = (r2 = n3.__c).__H;\n    i5 && (u2 === r2 ? (i5.__h = [], r2.__h = [], i5.__.forEach(function(n4) {\n      n4.__N && (n4.__ = n4.__N), n4.u = n4.__N = void 0;\n    })) : (i5.__h.forEach(z2), i5.__h.forEach(B2), i5.__h = [], t2 = 0)), u2 = r2;\n  }, c2.diffed = function(n3) {\n    v2 && v2(n3);\n    var t4 = n3.__c;\n    t4 && t4.__H && (t4.__H.__h.length && (1 !== f2.push(t4) && i2 === c2.requestAnimationFrame || ((i2 = c2.requestAnimationFrame) || w2)(j2)), t4.__H.__.forEach(function(n4) {\n      n4.u && (n4.__H = n4.u), n4.u = void 0;\n    })), u2 = r2 = null;\n  }, c2.__c = function(n3, t4) {\n    t4.some(function(n4) {\n      try {\n        n4.__h.forEach(z2), n4.__h = n4.__h.filter(function(n5) {\n          return !n5.__ || B2(n5);\n        });\n      } catch (r5) {\n        t4.some(function(n5) {\n          n5.__h && (n5.__h = []);\n        }), t4 = [], c2.__e(r5, n4.__v);\n      }\n    }), l2 && l2(n3, t4);\n  }, c2.unmount = function(n3) {\n    m2 && m2(n3);\n    var t4, r5 = n3.__c;\n    r5 && r5.__H && (r5.__H.__.forEach(function(n4) {\n      try {\n        z2(n4);\n      } catch (n5) {\n        t4 = n5;\n      }\n    }), r5.__H = void 0, t4 && c2.__e(t4, r5.__v));\n  };\n  var k2 = \"function\" == typeof requestAnimationFrame;\n  function w2(n3) {\n    var t4, r5 = function() {\n      clearTimeout(u5), k2 && cancelAnimationFrame(t4), setTimeout(n3);\n    }, u5 = setTimeout(r5, 35);\n    k2 && (t4 = requestAnimationFrame(r5));\n  }\n  function z2(n3) {\n    var t4 = r2, u5 = n3.__c;\n    \"function\" == typeof u5 && (n3.__c = void 0, u5()), r2 = t4;\n  }\n  function B2(n3) {\n    var t4 = r2;\n    n3.__c = n3.__(), r2 = t4;\n  }\n  function C2(n3, t4) {\n    return !n3 || n3.length !== t4.length || t4.some(function(t5, r5) {\n      return t5 !== n3[r5];\n    });\n  }\n  function D2(n3, t4) {\n    return \"function\" == typeof t4 ? t4(n3) : t4;\n  }\n\n  // ../../node_modules/.pnpm/@preact+signals-core@1.11.0/node_modules/@preact/signals-core/dist/signals-core.module.js\n  var i3 = Symbol.for(\"preact-signals\");\n  function t3() {\n    if (!(s3 > 1)) {\n      var i5, t4 = false;\n      while (void 0 !== h3) {\n        var r5 = h3;\n        h3 = void 0;\n        f3++;\n        while (void 0 !== r5) {\n          var o4 = r5.o;\n          r5.o = void 0;\n          r5.f &= -3;\n          if (!(8 & r5.f) && c3(r5)) try {\n            r5.c();\n          } catch (r6) {\n            if (!t4) {\n              i5 = r6;\n              t4 = true;\n            }\n          }\n          r5 = o4;\n        }\n      }\n      f3 = 0;\n      s3--;\n      if (t4) throw i5;\n    } else s3--;\n  }\n  var o3 = void 0;\n  function n2(i5) {\n    var t4 = o3;\n    o3 = void 0;\n    try {\n      return i5();\n    } finally {\n      o3 = t4;\n    }\n  }\n  var h3 = void 0;\n  var s3 = 0;\n  var f3 = 0;\n  var v3 = 0;\n  function e3(i5) {\n    if (void 0 !== o3) {\n      var t4 = i5.n;\n      if (void 0 === t4 || t4.t !== o3) {\n        t4 = { i: 0, S: i5, p: o3.s, n: void 0, t: o3, e: void 0, x: void 0, r: t4 };\n        if (void 0 !== o3.s) o3.s.n = t4;\n        o3.s = t4;\n        i5.n = t4;\n        if (32 & o3.f) i5.S(t4);\n        return t4;\n      } else if (-1 === t4.i) {\n        t4.i = 0;\n        if (void 0 !== t4.n) {\n          t4.n.p = t4.p;\n          if (void 0 !== t4.p) t4.p.n = t4.n;\n          t4.p = o3.s;\n          t4.n = void 0;\n          o3.s.n = t4;\n          o3.s = t4;\n        }\n        return t4;\n      }\n    }\n  }\n  function u3(i5, t4) {\n    this.v = i5;\n    this.i = 0;\n    this.n = void 0;\n    this.t = void 0;\n    this.W = null == t4 ? void 0 : t4.watched;\n    this.Z = null == t4 ? void 0 : t4.unwatched;\n  }\n  u3.prototype.brand = i3;\n  u3.prototype.h = function() {\n    return true;\n  };\n  u3.prototype.S = function(i5) {\n    var t4 = this, r5 = this.t;\n    if (r5 !== i5 && void 0 === i5.e) {\n      i5.x = r5;\n      this.t = i5;\n      if (void 0 !== r5) r5.e = i5;\n      else n2(function() {\n        var i6;\n        null == (i6 = t4.W) || i6.call(t4);\n      });\n    }\n  };\n  u3.prototype.U = function(i5) {\n    var t4 = this;\n    if (void 0 !== this.t) {\n      var r5 = i5.e, o4 = i5.x;\n      if (void 0 !== r5) {\n        r5.x = o4;\n        i5.e = void 0;\n      }\n      if (void 0 !== o4) {\n        o4.e = r5;\n        i5.x = void 0;\n      }\n      if (i5 === this.t) {\n        this.t = o4;\n        if (void 0 === o4) n2(function() {\n          var i6;\n          null == (i6 = t4.Z) || i6.call(t4);\n        });\n      }\n    }\n  };\n  u3.prototype.subscribe = function(i5) {\n    var t4 = this;\n    return E2(function() {\n      var r5 = t4.value, n3 = o3;\n      o3 = void 0;\n      try {\n        i5(r5);\n      } finally {\n        o3 = n3;\n      }\n    });\n  };\n  u3.prototype.valueOf = function() {\n    return this.value;\n  };\n  u3.prototype.toString = function() {\n    return this.value + \"\";\n  };\n  u3.prototype.toJSON = function() {\n    return this.value;\n  };\n  u3.prototype.peek = function() {\n    var i5 = o3;\n    o3 = void 0;\n    try {\n      return this.value;\n    } finally {\n      o3 = i5;\n    }\n  };\n  Object.defineProperty(u3.prototype, \"value\", { get: function() {\n    var i5 = e3(this);\n    if (void 0 !== i5) i5.i = this.i;\n    return this.v;\n  }, set: function(i5) {\n    if (i5 !== this.v) {\n      if (f3 > 100) throw new Error(\"Cycle detected\");\n      this.v = i5;\n      this.i++;\n      v3++;\n      s3++;\n      try {\n        for (var r5 = this.t; void 0 !== r5; r5 = r5.x) r5.t.N();\n      } finally {\n        t3();\n      }\n    }\n  } });\n  function d3(i5, t4) {\n    return new u3(i5, t4);\n  }\n  function c3(i5) {\n    for (var t4 = i5.s; void 0 !== t4; t4 = t4.n) if (t4.S.i !== t4.i || !t4.S.h() || t4.S.i !== t4.i) return true;\n    return false;\n  }\n  function a3(i5) {\n    for (var t4 = i5.s; void 0 !== t4; t4 = t4.n) {\n      var r5 = t4.S.n;\n      if (void 0 !== r5) t4.r = r5;\n      t4.S.n = t4;\n      t4.i = -1;\n      if (void 0 === t4.n) {\n        i5.s = t4;\n        break;\n      }\n    }\n  }\n  function l3(i5) {\n    var t4 = i5.s, r5 = void 0;\n    while (void 0 !== t4) {\n      var o4 = t4.p;\n      if (-1 === t4.i) {\n        t4.S.U(t4);\n        if (void 0 !== o4) o4.n = t4.n;\n        if (void 0 !== t4.n) t4.n.p = o4;\n      } else r5 = t4;\n      t4.S.n = t4.r;\n      if (void 0 !== t4.r) t4.r = void 0;\n      t4 = o4;\n    }\n    i5.s = r5;\n  }\n  function y3(i5, t4) {\n    u3.call(this, void 0);\n    this.x = i5;\n    this.s = void 0;\n    this.g = v3 - 1;\n    this.f = 4;\n    this.W = null == t4 ? void 0 : t4.watched;\n    this.Z = null == t4 ? void 0 : t4.unwatched;\n  }\n  y3.prototype = new u3();\n  y3.prototype.h = function() {\n    this.f &= -3;\n    if (1 & this.f) return false;\n    if (32 == (36 & this.f)) return true;\n    this.f &= -5;\n    if (this.g === v3) return true;\n    this.g = v3;\n    this.f |= 1;\n    if (this.i > 0 && !c3(this)) {\n      this.f &= -2;\n      return true;\n    }\n    var i5 = o3;\n    try {\n      a3(this);\n      o3 = this;\n      var t4 = this.x();\n      if (16 & this.f || this.v !== t4 || 0 === this.i) {\n        this.v = t4;\n        this.f &= -17;\n        this.i++;\n      }\n    } catch (i6) {\n      this.v = i6;\n      this.f |= 16;\n      this.i++;\n    }\n    o3 = i5;\n    l3(this);\n    this.f &= -2;\n    return true;\n  };\n  y3.prototype.S = function(i5) {\n    if (void 0 === this.t) {\n      this.f |= 36;\n      for (var t4 = this.s; void 0 !== t4; t4 = t4.n) t4.S.S(t4);\n    }\n    u3.prototype.S.call(this, i5);\n  };\n  y3.prototype.U = function(i5) {\n    if (void 0 !== this.t) {\n      u3.prototype.U.call(this, i5);\n      if (void 0 === this.t) {\n        this.f &= -33;\n        for (var t4 = this.s; void 0 !== t4; t4 = t4.n) t4.S.U(t4);\n      }\n    }\n  };\n  y3.prototype.N = function() {\n    if (!(2 & this.f)) {\n      this.f |= 6;\n      for (var i5 = this.t; void 0 !== i5; i5 = i5.x) i5.t.N();\n    }\n  };\n  Object.defineProperty(y3.prototype, \"value\", { get: function() {\n    if (1 & this.f) throw new Error(\"Cycle detected\");\n    var i5 = e3(this);\n    this.h();\n    if (void 0 !== i5) i5.i = this.i;\n    if (16 & this.f) throw this.v;\n    return this.v;\n  } });\n  function w3(i5, t4) {\n    return new y3(i5, t4);\n  }\n  function _3(i5) {\n    var r5 = i5.u;\n    i5.u = void 0;\n    if (\"function\" == typeof r5) {\n      s3++;\n      var n3 = o3;\n      o3 = void 0;\n      try {\n        r5();\n      } catch (t4) {\n        i5.f &= -2;\n        i5.f |= 8;\n        b(i5);\n        throw t4;\n      } finally {\n        o3 = n3;\n        t3();\n      }\n    }\n  }\n  function b(i5) {\n    for (var t4 = i5.s; void 0 !== t4; t4 = t4.n) t4.S.U(t4);\n    i5.x = void 0;\n    i5.s = void 0;\n    _3(i5);\n  }\n  function g2(i5) {\n    if (o3 !== this) throw new Error(\"Out-of-order effect\");\n    l3(this);\n    o3 = i5;\n    this.f &= -2;\n    if (8 & this.f) b(this);\n    t3();\n  }\n  function p3(i5) {\n    this.x = i5;\n    this.u = void 0;\n    this.s = void 0;\n    this.o = void 0;\n    this.f = 32;\n  }\n  p3.prototype.c = function() {\n    var i5 = this.S();\n    try {\n      if (8 & this.f) return;\n      if (void 0 === this.x) return;\n      var t4 = this.x();\n      if (\"function\" == typeof t4) this.u = t4;\n    } finally {\n      i5();\n    }\n  };\n  p3.prototype.S = function() {\n    if (1 & this.f) throw new Error(\"Cycle detected\");\n    this.f |= 1;\n    this.f &= -9;\n    _3(this);\n    a3(this);\n    s3++;\n    var i5 = o3;\n    o3 = this;\n    return g2.bind(this, i5);\n  };\n  p3.prototype.N = function() {\n    if (!(2 & this.f)) {\n      this.f |= 2;\n      this.o = h3;\n      h3 = this;\n    }\n  };\n  p3.prototype.d = function() {\n    this.f |= 8;\n    if (!(1 & this.f)) b(this);\n  };\n  p3.prototype.dispose = function() {\n    this.d();\n  };\n  function E2(i5) {\n    var t4 = new p3(i5);\n    try {\n      t4.c();\n    } catch (i6) {\n      t4.d();\n      throw i6;\n    }\n    var r5 = t4.d.bind(t4);\n    r5[Symbol.dispose] = r5;\n    return r5;\n  }\n  var s4;\n  function l4(i5, n3) {\n    l[i5] = n3.bind(null, l[i5] || function() {\n    });\n  }\n  function d4(i5) {\n    if (s4) s4();\n    s4 = i5 && i5.S();\n  }\n  function h4(i5) {\n    var r5 = this, f5 = i5.data, o4 = useSignal(f5);\n    o4.value = f5;\n    var e4 = T2(function() {\n      var i6 = r5.__v;\n      while (i6 = i6.__) if (i6.__c) {\n        i6.__c.__$f |= 4;\n        break;\n      }\n      r5.__$u.c = function() {\n        var i7, t4 = r5.__$u.S(), f6 = e4.value;\n        t4();\n        if (t(f6) || 3 !== (null == (i7 = r5.base) ? void 0 : i7.nodeType)) {\n          r5.__$f |= 1;\n          r5.setState({});\n        } else r5.base.data = f6;\n      };\n      return w3(function() {\n        var i7 = o4.value.value;\n        return 0 === i7 ? 0 : true === i7 ? \"\" : i7 || \"\";\n      });\n    }, []);\n    return e4.value;\n  }\n  h4.displayName = \"_st\";\n  Object.defineProperties(u3.prototype, { constructor: { configurable: true, value: void 0 }, type: { configurable: true, value: h4 }, props: { configurable: true, get: function() {\n    return { data: this };\n  } }, __b: { configurable: true, value: 1 } });\n  l4(\"__b\", function(i5, r5) {\n    if (\"string\" == typeof r5.type) {\n      var n3, t4 = r5.props;\n      for (var f5 in t4) if (\"children\" !== f5) {\n        var o4 = t4[f5];\n        if (o4 instanceof u3) {\n          if (!n3) r5.__np = n3 = {};\n          n3[f5] = o4;\n          t4[f5] = o4.peek();\n        }\n      }\n    }\n    i5(r5);\n  });\n  l4(\"__r\", function(i5, r5) {\n    d4();\n    var n3, t4 = r5.__c;\n    if (t4) {\n      t4.__$f &= -2;\n      if (void 0 === (n3 = t4.__$u)) t4.__$u = n3 = function(i6) {\n        var r6;\n        E2(function() {\n          r6 = this;\n        });\n        r6.c = function() {\n          t4.__$f |= 1;\n          t4.setState({});\n        };\n        return r6;\n      }();\n    }\n    d4(n3);\n    i5(r5);\n  });\n  l4(\"__e\", function(i5, r5, n3, t4) {\n    d4();\n    i5(r5, n3, t4);\n  });\n  l4(\"diffed\", function(i5, r5) {\n    d4();\n    var n3;\n    if (\"string\" == typeof r5.type && (n3 = r5.__e)) {\n      var t4 = r5.__np, f5 = r5.props;\n      if (t4) {\n        var o4 = n3.U;\n        if (o4) for (var e4 in o4) {\n          var u5 = o4[e4];\n          if (void 0 !== u5 && !(e4 in t4)) {\n            u5.d();\n            o4[e4] = void 0;\n          }\n        }\n        else n3.U = o4 = {};\n        for (var a4 in t4) {\n          var c4 = o4[a4], s5 = t4[a4];\n          if (void 0 === c4) {\n            c4 = p4(n3, a4, s5, f5);\n            o4[a4] = c4;\n          } else c4.o(s5, f5);\n        }\n      }\n    }\n    i5(r5);\n  });\n  function p4(i5, r5, n3, t4) {\n    var f5 = r5 in i5 && void 0 === i5.ownerSVGElement, o4 = d3(n3);\n    return { o: function(i6, r6) {\n      o4.value = i6;\n      t4 = r6;\n    }, d: E2(function() {\n      var n4 = o4.value.value;\n      if (t4[r5] !== n4) {\n        t4[r5] = n4;\n        if (f5) i5[r5] = n4;\n        else if (n4) i5.setAttribute(r5, n4);\n        else i5.removeAttribute(r5);\n      }\n    }) };\n  }\n  l4(\"unmount\", function(i5, r5) {\n    if (\"string\" == typeof r5.type) {\n      var n3 = r5.__e;\n      if (n3) {\n        var t4 = n3.U;\n        if (t4) {\n          n3.U = void 0;\n          for (var f5 in t4) {\n            var o4 = t4[f5];\n            if (o4) o4.d();\n          }\n        }\n      }\n    } else {\n      var e4 = r5.__c;\n      if (e4) {\n        var u5 = e4.__$u;\n        if (u5) {\n          e4.__$u = void 0;\n          u5.d();\n        }\n      }\n    }\n    i5(r5);\n  });\n  l4(\"__h\", function(i5, r5, n3, t4) {\n    if (t4 < 3 || 9 === t4) r5.__$f |= 2;\n    i5(r5, n3, t4);\n  });\n  x.prototype.shouldComponentUpdate = function(i5, r5) {\n    var n3 = this.__$u, t4 = n3 && void 0 !== n3.s;\n    for (var f5 in r5) return true;\n    if (this.__f || \"boolean\" == typeof this.u && true === this.u) {\n      if (!(t4 || 2 & this.__$f || 4 & this.__$f)) return true;\n      if (1 & this.__$f) return true;\n    } else {\n      if (!(t4 || 4 & this.__$f)) return true;\n      if (3 & this.__$f) return true;\n    }\n    for (var o4 in i5) if (\"__source\" !== o4 && i5[o4] !== this.props[o4]) return true;\n    for (var e4 in this.props) if (!(e4 in i5)) return true;\n    return false;\n  };\n  function useSignal(i5) {\n    return T2(function() {\n      return d3(i5);\n    }, []);\n  }\n  function useSignalEffect(i5) {\n    var r5 = A2(i5);\n    r5.current = i5;\n    y2(function() {\n      return E2(function() {\n        return r5.current();\n      });\n    }, []);\n  }\n\n  // src/core/utils.ts\n  function descending(a4, b3) {\n    return b3 - a4;\n  }\n  function getComponentGroupNames(group) {\n    let result = group[0].name;\n    const len = group.length;\n    const max = Math.min(4, len);\n    for (let i5 = 1; i5 < max; i5++) {\n      result += `, ${group[i5].name}`;\n    }\n    return result;\n  }\n  function getComponentGroupTotalTime(group) {\n    let result = group[0].time;\n    for (let i5 = 1, len = group.length; i5 < len; i5++) {\n      result += group[i5].time;\n    }\n    return result;\n  }\n  function componentGroupHasForget(group) {\n    for (let i5 = 0, len = group.length; i5 < len; i5++) {\n      if (group[i5].forget) {\n        return true;\n      }\n    }\n    return false;\n  }\n  var getLabelText = (groupedAggregatedRenders) => {\n    let labelText = \"\";\n    const componentsByCount = /* @__PURE__ */ new Map();\n    for (const aggregatedRender of groupedAggregatedRenders) {\n      const { forget, time, aggregatedCount, name } = aggregatedRender;\n      if (!componentsByCount.has(aggregatedCount)) {\n        componentsByCount.set(aggregatedCount, []);\n      }\n      const components = componentsByCount.get(aggregatedCount);\n      if (components) {\n        components.push({ name, forget, time: time ?? 0 });\n      }\n    }\n    const sortedCounts = Array.from(componentsByCount.keys()).sort(descending);\n    const parts = [];\n    let cumulativeTime = 0;\n    for (const count of sortedCounts) {\n      const componentGroup = componentsByCount.get(count);\n      if (!componentGroup) continue;\n      let text = getComponentGroupNames(componentGroup);\n      const totalTime = getComponentGroupTotalTime(componentGroup);\n      const hasForget = componentGroupHasForget(componentGroup);\n      cumulativeTime += totalTime;\n      if (componentGroup.length > 4) {\n        text += \"\\u2026\";\n      }\n      if (count > 1) {\n        text += ` \\xD7 ${count}`;\n      }\n      if (hasForget) {\n        text = `\\u2728${text}`;\n      }\n      parts.push(text);\n    }\n    labelText = parts.join(\", \");\n    if (!labelText.length) return null;\n    if (labelText.length > 40) {\n      labelText = `${labelText.slice(0, 40)}\\u2026`;\n    }\n    if (cumulativeTime >= 0.01) {\n      labelText += ` (${Number(cumulativeTime.toFixed(2))}ms)`;\n    }\n    return labelText;\n  };\n  function isEqual(a4, b3) {\n    return a4 === b3 || a4 !== a4 && b3 !== b3;\n  }\n  var not_globally_unique_generateId = () => {\n    if (!IS_CLIENT) {\n      return \"0\";\n    }\n    if (window.reactScanIdCounter === void 0) {\n      window.reactScanIdCounter = 0;\n    }\n    return `${++window.reactScanIdCounter}`;\n  };\n  var playNotificationSound = (audioContext) => {\n    const oscillator = audioContext.createOscillator();\n    const gainNode = audioContext.createGain();\n    oscillator.connect(gainNode);\n    gainNode.connect(audioContext.destination);\n    const options = {\n      type: \"sine\",\n      freq: [\n        392,\n        //  523.25,\n        600\n        //  659.25\n      ],\n      duration: 0.3,\n      gain: 0.12\n    };\n    const frequencies = options.freq;\n    const timePerNote = options.duration / frequencies.length;\n    frequencies.forEach((freq, i5) => {\n      oscillator.frequency.setValueAtTime(\n        freq,\n        audioContext.currentTime + i5 * timePerNote\n      );\n    });\n    oscillator.type = options.type;\n    gainNode.gain.setValueAtTime(options.gain, audioContext.currentTime);\n    gainNode.gain.setTargetAtTime(\n      0,\n      audioContext.currentTime + options.duration * 0.7,\n      0.05\n    );\n    oscillator.start();\n    oscillator.stop(audioContext.currentTime + options.duration);\n  };\n\n  // ../../node_modules/.pnpm/preact@10.26.9/node_modules/preact/compat/dist/compat.module.js\n  function g4(n3, t4) {\n    for (var e4 in t4) n3[e4] = t4[e4];\n    return n3;\n  }\n  function E3(n3, t4) {\n    for (var e4 in n3) if (\"__source\" !== e4 && !(e4 in t4)) return true;\n    for (var r5 in t4) if (\"__source\" !== r5 && n3[r5] !== t4[r5]) return true;\n    return false;\n  }\n  function C3(n3, t4) {\n    var e4 = t4(), r5 = d2({ t: { __: e4, u: t4 } }), u5 = r5[0].t, o4 = r5[1];\n    return _2(function() {\n      u5.__ = e4, u5.u = t4, x3(u5) && o4({ t: u5 });\n    }, [n3, e4, t4]), y2(function() {\n      return x3(u5) && o4({ t: u5 }), n3(function() {\n        x3(u5) && o4({ t: u5 });\n      });\n    }, [n3]), e4;\n  }\n  function x3(n3) {\n    var t4, e4, r5 = n3.u, u5 = n3.__;\n    try {\n      var o4 = r5();\n      return !((t4 = u5) === (e4 = o4) && (0 !== t4 || 1 / t4 == 1 / e4) || t4 != t4 && e4 != e4);\n    } catch (n4) {\n      return true;\n    }\n  }\n  function N2(n3, t4) {\n    this.props = n3, this.context = t4;\n  }\n  function M2(n3, e4) {\n    function r5(n4) {\n      var t4 = this.props.ref, r6 = t4 == n4.ref;\n      return !r6 && t4 && (t4.call ? t4(null) : t4.current = null), E3(this.props, n4);\n    }\n    function u5(e5) {\n      return this.shouldComponentUpdate = r5, _(n3, e5);\n    }\n    return u5.displayName = \"Memo(\" + (n3.displayName || n3.name) + \")\", u5.prototype.isReactComponent = true, u5.__f = true, u5;\n  }\n  (N2.prototype = new x()).isPureReactComponent = true, N2.prototype.shouldComponentUpdate = function(n3, t4) {\n    return E3(this.props, n3) || E3(this.state, t4);\n  };\n  var T3 = l.__b;\n  l.__b = function(n3) {\n    n3.type && n3.type.__f && n3.ref && (n3.props.ref = n3.ref, n3.ref = null), T3 && T3(n3);\n  };\n  var A3 = \"undefined\" != typeof Symbol && Symbol.for && Symbol.for(\"react.forward_ref\") || 3911;\n  function D3(n3) {\n    function t4(t5) {\n      var e4 = g4({}, t5);\n      return delete e4.ref, n3(e4, t5.ref || null);\n    }\n    return t4.$$typeof = A3, t4.render = t4, t4.prototype.isReactComponent = t4.__f = true, t4.displayName = \"ForwardRef(\" + (n3.displayName || n3.name) + \")\", t4;\n  }\n  var F3 = l.__e;\n  l.__e = function(n3, t4, e4, r5) {\n    if (n3.then) {\n      for (var u5, o4 = t4; o4 = o4.__; ) if ((u5 = o4.__c) && u5.__c) return null == t4.__e && (t4.__e = e4.__e, t4.__k = e4.__k), u5.__c(n3, t4);\n    }\n    F3(n3, t4, e4, r5);\n  };\n  var U = l.unmount;\n  function V2(n3, t4, e4) {\n    return n3 && (n3.__c && n3.__c.__H && (n3.__c.__H.__.forEach(function(n4) {\n      \"function\" == typeof n4.__c && n4.__c();\n    }), n3.__c.__H = null), null != (n3 = g4({}, n3)).__c && (n3.__c.__P === e4 && (n3.__c.__P = t4), n3.__c.__e = true, n3.__c = null), n3.__k = n3.__k && n3.__k.map(function(n4) {\n      return V2(n4, t4, e4);\n    })), n3;\n  }\n  function W(n3, t4, e4) {\n    return n3 && e4 && (n3.__v = null, n3.__k = n3.__k && n3.__k.map(function(n4) {\n      return W(n4, t4, e4);\n    }), n3.__c && n3.__c.__P === t4 && (n3.__e && e4.appendChild(n3.__e), n3.__c.__e = true, n3.__c.__P = e4)), n3;\n  }\n  function P3() {\n    this.__u = 0, this.o = null, this.__b = null;\n  }\n  function j3(n3) {\n    var t4 = n3.__.__c;\n    return t4 && t4.__a && t4.__a(n3);\n  }\n  function B3() {\n    this.i = null, this.l = null;\n  }\n  l.unmount = function(n3) {\n    var t4 = n3.__c;\n    t4 && t4.__R && t4.__R(), t4 && 32 & n3.__u && (n3.type = null), U && U(n3);\n  }, (P3.prototype = new x()).__c = function(n3, t4) {\n    var e4 = t4.__c, r5 = this;\n    null == r5.o && (r5.o = []), r5.o.push(e4);\n    var u5 = j3(r5.__v), o4 = false, i5 = function() {\n      o4 || (o4 = true, e4.__R = null, u5 ? u5(l5) : l5());\n    };\n    e4.__R = i5;\n    var l5 = function() {\n      if (!--r5.__u) {\n        if (r5.state.__a) {\n          var n4 = r5.state.__a;\n          r5.__v.__k[0] = W(n4, n4.__c.__P, n4.__c.__O);\n        }\n        var t5;\n        for (r5.setState({ __a: r5.__b = null }); t5 = r5.o.pop(); ) t5.forceUpdate();\n      }\n    };\n    r5.__u++ || 32 & t4.__u || r5.setState({ __a: r5.__b = r5.__v.__k[0] }), n3.then(i5, i5);\n  }, P3.prototype.componentWillUnmount = function() {\n    this.o = [];\n  }, P3.prototype.render = function(n3, e4) {\n    if (this.__b) {\n      if (this.__v.__k) {\n        var r5 = document.createElement(\"div\"), o4 = this.__v.__k[0].__c;\n        this.__v.__k[0] = V2(this.__b, r5, o4.__O = o4.__P);\n      }\n      this.__b = null;\n    }\n    var i5 = e4.__a && _(k, null, n3.fallback);\n    return i5 && (i5.__u &= -33), [_(k, null, e4.__a ? null : n3.children), i5];\n  };\n  var H2 = function(n3, t4, e4) {\n    if (++e4[1] === e4[0] && n3.l.delete(t4), n3.props.revealOrder && (\"t\" !== n3.props.revealOrder[0] || !n3.l.size)) for (e4 = n3.i; e4; ) {\n      for (; e4.length > 3; ) e4.pop()();\n      if (e4[1] < e4[0]) break;\n      n3.i = e4 = e4[2];\n    }\n  };\n  function Z(n3) {\n    return this.getChildContext = function() {\n      return n3.context;\n    }, n3.children;\n  }\n  function Y(n3) {\n    var e4 = this, r5 = n3.h;\n    if (e4.componentWillUnmount = function() {\n      E(null, e4.v), e4.v = null, e4.h = null;\n    }, e4.h && e4.h !== r5 && e4.componentWillUnmount(), !e4.v) {\n      for (var u5 = e4.__v; null !== u5 && !u5.__m && null !== u5.__; ) u5 = u5.__;\n      e4.h = r5, e4.v = { nodeType: 1, parentNode: r5, childNodes: [], __k: { __m: u5.__m }, contains: function() {\n        return true;\n      }, insertBefore: function(n4, t4) {\n        this.childNodes.push(n4), e4.h.insertBefore(n4, t4);\n      }, removeChild: function(n4) {\n        this.childNodes.splice(this.childNodes.indexOf(n4) >>> 1, 1), e4.h.removeChild(n4);\n      } };\n    }\n    E(_(Z, { context: e4.context }, n3.__v), e4.v);\n  }\n  function $2(n3, e4) {\n    var r5 = _(Y, { __v: n3, h: e4 });\n    return r5.containerInfo = e4, r5;\n  }\n  (B3.prototype = new x()).__a = function(n3) {\n    var t4 = this, e4 = j3(t4.__v), r5 = t4.l.get(n3);\n    return r5[0]++, function(u5) {\n      var o4 = function() {\n        t4.props.revealOrder ? (r5.push(u5), H2(t4, n3, r5)) : u5();\n      };\n      e4 ? e4(o4) : o4();\n    };\n  }, B3.prototype.render = function(n3) {\n    this.i = null, this.l = /* @__PURE__ */ new Map();\n    var t4 = H(n3.children);\n    n3.revealOrder && \"b\" === n3.revealOrder[0] && t4.reverse();\n    for (var e4 = t4.length; e4--; ) this.l.set(t4[e4], this.i = [1, 0, this.i]);\n    return n3.children;\n  }, B3.prototype.componentDidUpdate = B3.prototype.componentDidMount = function() {\n    var n3 = this;\n    this.l.forEach(function(t4, e4) {\n      H2(n3, e4, t4);\n    });\n  };\n  var q3 = \"undefined\" != typeof Symbol && Symbol.for && Symbol.for(\"react.element\") || 60103;\n  var G2 = /^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|dominant|fill|flood|font|glyph(?!R)|horiz|image(!S)|letter|lighting|marker(?!H|W|U)|overline|paint|pointer|shape|stop|strikethrough|stroke|text(?!L)|transform|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/;\n  var J2 = /^on(Ani|Tra|Tou|BeforeInp|Compo)/;\n  var K2 = /[A-Z0-9]/g;\n  var Q = \"undefined\" != typeof document;\n  var X = function(n3) {\n    return (\"undefined\" != typeof Symbol && \"symbol\" == typeof Symbol() ? /fil|che|rad/ : /fil|che|ra/).test(n3);\n  };\n  x.prototype.isReactComponent = {}, [\"componentWillMount\", \"componentWillReceiveProps\", \"componentWillUpdate\"].forEach(function(t4) {\n    Object.defineProperty(x.prototype, t4, { configurable: true, get: function() {\n      return this[\"UNSAFE_\" + t4];\n    }, set: function(n3) {\n      Object.defineProperty(this, t4, { configurable: true, writable: true, value: n3 });\n    } });\n  });\n  var en = l.event;\n  function rn() {\n  }\n  function un() {\n    return this.cancelBubble;\n  }\n  function on() {\n    return this.defaultPrevented;\n  }\n  l.event = function(n3) {\n    return en && (n3 = en(n3)), n3.persist = rn, n3.isPropagationStopped = un, n3.isDefaultPrevented = on, n3.nativeEvent = n3;\n  };\n  var cn = { enumerable: false, configurable: true, get: function() {\n    return this.class;\n  } };\n  var fn = l.vnode;\n  l.vnode = function(n3) {\n    \"string\" == typeof n3.type && function(n4) {\n      var t4 = n4.props, e4 = n4.type, u5 = {}, o4 = -1 === e4.indexOf(\"-\");\n      for (var i5 in t4) {\n        var l5 = t4[i5];\n        if (!(\"value\" === i5 && \"defaultValue\" in t4 && null == l5 || Q && \"children\" === i5 && \"noscript\" === e4 || \"class\" === i5 || \"className\" === i5)) {\n          var c4 = i5.toLowerCase();\n          \"defaultValue\" === i5 && \"value\" in t4 && null == t4.value ? i5 = \"value\" : \"download\" === i5 && true === l5 ? l5 = \"\" : \"translate\" === c4 && \"no\" === l5 ? l5 = false : \"o\" === c4[0] && \"n\" === c4[1] ? \"ondoubleclick\" === c4 ? i5 = \"ondblclick\" : \"onchange\" !== c4 || \"input\" !== e4 && \"textarea\" !== e4 || X(t4.type) ? \"onfocus\" === c4 ? i5 = \"onfocusin\" : \"onblur\" === c4 ? i5 = \"onfocusout\" : J2.test(i5) && (i5 = c4) : c4 = i5 = \"oninput\" : o4 && G2.test(i5) ? i5 = i5.replace(K2, \"-$&\").toLowerCase() : null === l5 && (l5 = void 0), \"oninput\" === c4 && u5[i5 = c4] && (i5 = \"oninputCapture\"), u5[i5] = l5;\n        }\n      }\n      \"select\" == e4 && u5.multiple && Array.isArray(u5.value) && (u5.value = H(t4.children).forEach(function(n5) {\n        n5.props.selected = -1 != u5.value.indexOf(n5.props.value);\n      })), \"select\" == e4 && null != u5.defaultValue && (u5.value = H(t4.children).forEach(function(n5) {\n        n5.props.selected = u5.multiple ? -1 != u5.defaultValue.indexOf(n5.props.value) : u5.defaultValue == n5.props.value;\n      })), t4.class && !t4.className ? (u5.class = t4.class, Object.defineProperty(u5, \"className\", cn)) : (t4.className && !t4.class || t4.class && t4.className) && (u5.class = u5.className = t4.className), n4.props = u5;\n    }(n3), n3.$$typeof = q3, fn && fn(n3);\n  };\n  var an = l.__r;\n  l.__r = function(n3) {\n    an && an(n3), n3.__c;\n  };\n  var sn = l.diffed;\n  l.diffed = function(n3) {\n    sn && sn(n3);\n    var t4 = n3.props, e4 = n3.__e;\n    null != e4 && \"textarea\" === n3.type && \"value\" in t4 && t4.value !== e4.value && (e4.value = null == t4.value ? \"\" : t4.value);\n  };\n\n  // ../../node_modules/.pnpm/preact@10.26.9/node_modules/preact/jsx-runtime/dist/jsxRuntime.module.js\n  var f4 = 0;\n  function u4(e4, t4, n3, o4, i5, u5) {\n    t4 || (t4 = {});\n    var a4, c4, p5 = t4;\n    if (\"ref\" in p5) for (c4 in p5 = {}, t4) \"ref\" == c4 ? a4 = t4[c4] : p5[c4] = t4[c4];\n    var l5 = { type: e4, props: p5, key: n3, ref: a4, __k: null, __: null, __b: 0, __e: null, __c: null, constructor: void 0, __v: --f4, __i: -1, __u: 0, __source: i5, __self: u5 };\n    if (\"function\" == typeof e4 && (a4 = e4.defaultProps)) for (c4 in a4) void 0 === p5[c4] && (p5[c4] = a4[c4]);\n    return l.vnode && l.vnode(l5), l5;\n  }\n\n  // src/web/components/icon/index.tsx\n  var Icon = D3(({\n    size = 15,\n    name,\n    fill = \"currentColor\",\n    stroke = \"currentColor\",\n    className,\n    externalURL = \"\",\n    style\n  }, ref) => {\n    const width = Array.isArray(size) ? size[0] : size;\n    const height = Array.isArray(size) ? size[1] || size[0] : size;\n    const path = `${externalURL}#${name}`;\n    return /* @__PURE__ */ u4(\n      \"svg\",\n      {\n        ref,\n        width: `${width}px`,\n        height: `${height}px`,\n        fill,\n        stroke,\n        className,\n        style: {\n          ...style,\n          minWidth: `${width}px`,\n          maxWidth: `${width}px`,\n          minHeight: `${height}px`,\n          maxHeight: `${height}px`\n        },\n        children: [\n          /* @__PURE__ */ u4(\"title\", { children: name }),\n          /* @__PURE__ */ u4(\"use\", { href: path })\n        ]\n      }\n    );\n  });\n\n  // src/web/constants.ts\n  var SAFE_AREA = 24;\n  var MIN_SIZE = {\n    width: 550,\n    height: 350,\n    initialHeight: 400\n  };\n  var MIN_CONTAINER_WIDTH = 240;\n  var LOCALSTORAGE_KEY = \"react-scan-widget-settings-v2\";\n  var LOCALSTORAGE_COLLAPSED_KEY = \"react-scan-widget-collapsed-v1\";\n  var LOCALSTORAGE_LAST_VIEW_KEY = \"react-scan-widget-last-view-v1\";\n\n  // ../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs\n  function r4(e4) {\n    var t4, f5, n3 = \"\";\n    if (\"string\" == typeof e4 || \"number\" == typeof e4) n3 += e4;\n    else if (\"object\" == typeof e4) if (Array.isArray(e4)) {\n      var o4 = e4.length;\n      for (t4 = 0; t4 < o4; t4++) e4[t4] && (f5 = r4(e4[t4])) && (n3 && (n3 += \" \"), n3 += f5);\n    } else for (f5 in e4) e4[f5] && (n3 && (n3 += \" \"), n3 += f5);\n    return n3;\n  }\n  function clsx() {\n    for (var e4, t4, f5 = 0, n3 = \"\", o4 = arguments.length; f5 < o4; f5++) (e4 = arguments[f5]) && (t4 = r4(e4)) && (n3 && (n3 += \" \"), n3 += t4);\n    return n3;\n  }\n\n  // ../../node_modules/.pnpm/tailwind-merge@2.6.0/node_modules/tailwind-merge/dist/bundle-mjs.mjs\n  var CLASS_PART_SEPARATOR = \"-\";\n  var createClassGroupUtils = (config) => {\n    const classMap = createClassMap(config);\n    const {\n      conflictingClassGroups,\n      conflictingClassGroupModifiers\n    } = config;\n    const getClassGroupId = (className) => {\n      const classParts = className.split(CLASS_PART_SEPARATOR);\n      if (classParts[0] === \"\" && classParts.length !== 1) {\n        classParts.shift();\n      }\n      return getGroupRecursive(classParts, classMap) || getGroupIdForArbitraryProperty(className);\n    };\n    const getConflictingClassGroupIds = (classGroupId, hasPostfixModifier) => {\n      const conflicts = conflictingClassGroups[classGroupId] || [];\n      if (hasPostfixModifier && conflictingClassGroupModifiers[classGroupId]) {\n        return [...conflicts, ...conflictingClassGroupModifiers[classGroupId]];\n      }\n      return conflicts;\n    };\n    return {\n      getClassGroupId,\n      getConflictingClassGroupIds\n    };\n  };\n  var getGroupRecursive = (classParts, classPartObject) => {\n    if (classParts.length === 0) {\n      return classPartObject.classGroupId;\n    }\n    const currentClassPart = classParts[0];\n    const nextClassPartObject = classPartObject.nextPart.get(currentClassPart);\n    const classGroupFromNextClassPart = nextClassPartObject ? getGroupRecursive(classParts.slice(1), nextClassPartObject) : void 0;\n    if (classGroupFromNextClassPart) {\n      return classGroupFromNextClassPart;\n    }\n    if (classPartObject.validators.length === 0) {\n      return void 0;\n    }\n    const classRest = classParts.join(CLASS_PART_SEPARATOR);\n    return classPartObject.validators.find(({\n      validator\n    }) => validator(classRest))?.classGroupId;\n  };\n  var arbitraryPropertyRegex = /^\\[(.+)\\]$/;\n  var getGroupIdForArbitraryProperty = (className) => {\n    if (arbitraryPropertyRegex.test(className)) {\n      const arbitraryPropertyClassName = arbitraryPropertyRegex.exec(className)[1];\n      const property = arbitraryPropertyClassName?.substring(0, arbitraryPropertyClassName.indexOf(\":\"));\n      if (property) {\n        return \"arbitrary..\" + property;\n      }\n    }\n  };\n  var createClassMap = (config) => {\n    const {\n      theme,\n      prefix\n    } = config;\n    const classMap = {\n      nextPart: /* @__PURE__ */ new Map(),\n      validators: []\n    };\n    const prefixedClassGroupEntries = getPrefixedClassGroupEntries(Object.entries(config.classGroups), prefix);\n    prefixedClassGroupEntries.forEach(([classGroupId, classGroup]) => {\n      processClassesRecursively(classGroup, classMap, classGroupId, theme);\n    });\n    return classMap;\n  };\n  var processClassesRecursively = (classGroup, classPartObject, classGroupId, theme) => {\n    classGroup.forEach((classDefinition) => {\n      if (typeof classDefinition === \"string\") {\n        const classPartObjectToEdit = classDefinition === \"\" ? classPartObject : getPart(classPartObject, classDefinition);\n        classPartObjectToEdit.classGroupId = classGroupId;\n        return;\n      }\n      if (typeof classDefinition === \"function\") {\n        if (isThemeGetter(classDefinition)) {\n          processClassesRecursively(classDefinition(theme), classPartObject, classGroupId, theme);\n          return;\n        }\n        classPartObject.validators.push({\n          validator: classDefinition,\n          classGroupId\n        });\n        return;\n      }\n      Object.entries(classDefinition).forEach(([key, classGroup2]) => {\n        processClassesRecursively(classGroup2, getPart(classPartObject, key), classGroupId, theme);\n      });\n    });\n  };\n  var getPart = (classPartObject, path) => {\n    let currentClassPartObject = classPartObject;\n    path.split(CLASS_PART_SEPARATOR).forEach((pathPart) => {\n      if (!currentClassPartObject.nextPart.has(pathPart)) {\n        currentClassPartObject.nextPart.set(pathPart, {\n          nextPart: /* @__PURE__ */ new Map(),\n          validators: []\n        });\n      }\n      currentClassPartObject = currentClassPartObject.nextPart.get(pathPart);\n    });\n    return currentClassPartObject;\n  };\n  var isThemeGetter = (func) => func.isThemeGetter;\n  var getPrefixedClassGroupEntries = (classGroupEntries, prefix) => {\n    if (!prefix) {\n      return classGroupEntries;\n    }\n    return classGroupEntries.map(([classGroupId, classGroup]) => {\n      const prefixedClassGroup = classGroup.map((classDefinition) => {\n        if (typeof classDefinition === \"string\") {\n          return prefix + classDefinition;\n        }\n        if (typeof classDefinition === \"object\") {\n          return Object.fromEntries(Object.entries(classDefinition).map(([key, value]) => [prefix + key, value]));\n        }\n        return classDefinition;\n      });\n      return [classGroupId, prefixedClassGroup];\n    });\n  };\n  var createLruCache = (maxCacheSize) => {\n    if (maxCacheSize < 1) {\n      return {\n        get: () => void 0,\n        set: () => {\n        }\n      };\n    }\n    let cacheSize = 0;\n    let cache2 = /* @__PURE__ */ new Map();\n    let previousCache = /* @__PURE__ */ new Map();\n    const update = (key, value) => {\n      cache2.set(key, value);\n      cacheSize++;\n      if (cacheSize > maxCacheSize) {\n        cacheSize = 0;\n        previousCache = cache2;\n        cache2 = /* @__PURE__ */ new Map();\n      }\n    };\n    return {\n      get(key) {\n        let value = cache2.get(key);\n        if (value !== void 0) {\n          return value;\n        }\n        if ((value = previousCache.get(key)) !== void 0) {\n          update(key, value);\n          return value;\n        }\n      },\n      set(key, value) {\n        if (cache2.has(key)) {\n          cache2.set(key, value);\n        } else {\n          update(key, value);\n        }\n      }\n    };\n  };\n  var IMPORTANT_MODIFIER = \"!\";\n  var createParseClassName = (config) => {\n    const {\n      separator,\n      experimentalParseClassName\n    } = config;\n    const isSeparatorSingleCharacter = separator.length === 1;\n    const firstSeparatorCharacter = separator[0];\n    const separatorLength = separator.length;\n    const parseClassName = (className) => {\n      const modifiers = [];\n      let bracketDepth = 0;\n      let modifierStart = 0;\n      let postfixModifierPosition;\n      for (let index = 0; index < className.length; index++) {\n        let currentCharacter = className[index];\n        if (bracketDepth === 0) {\n          if (currentCharacter === firstSeparatorCharacter && (isSeparatorSingleCharacter || className.slice(index, index + separatorLength) === separator)) {\n            modifiers.push(className.slice(modifierStart, index));\n            modifierStart = index + separatorLength;\n            continue;\n          }\n          if (currentCharacter === \"/\") {\n            postfixModifierPosition = index;\n            continue;\n          }\n        }\n        if (currentCharacter === \"[\") {\n          bracketDepth++;\n        } else if (currentCharacter === \"]\") {\n          bracketDepth--;\n        }\n      }\n      const baseClassNameWithImportantModifier = modifiers.length === 0 ? className : className.substring(modifierStart);\n      const hasImportantModifier = baseClassNameWithImportantModifier.startsWith(IMPORTANT_MODIFIER);\n      const baseClassName = hasImportantModifier ? baseClassNameWithImportantModifier.substring(1) : baseClassNameWithImportantModifier;\n      const maybePostfixModifierPosition = postfixModifierPosition && postfixModifierPosition > modifierStart ? postfixModifierPosition - modifierStart : void 0;\n      return {\n        modifiers,\n        hasImportantModifier,\n        baseClassName,\n        maybePostfixModifierPosition\n      };\n    };\n    if (experimentalParseClassName) {\n      return (className) => experimentalParseClassName({\n        className,\n        parseClassName\n      });\n    }\n    return parseClassName;\n  };\n  var sortModifiers = (modifiers) => {\n    if (modifiers.length <= 1) {\n      return modifiers;\n    }\n    const sortedModifiers = [];\n    let unsortedModifiers = [];\n    modifiers.forEach((modifier) => {\n      const isArbitraryVariant = modifier[0] === \"[\";\n      if (isArbitraryVariant) {\n        sortedModifiers.push(...unsortedModifiers.sort(), modifier);\n        unsortedModifiers = [];\n      } else {\n        unsortedModifiers.push(modifier);\n      }\n    });\n    sortedModifiers.push(...unsortedModifiers.sort());\n    return sortedModifiers;\n  };\n  var createConfigUtils = (config) => ({\n    cache: createLruCache(config.cacheSize),\n    parseClassName: createParseClassName(config),\n    ...createClassGroupUtils(config)\n  });\n  var SPLIT_CLASSES_REGEX = /\\s+/;\n  var mergeClassList = (classList, configUtils) => {\n    const {\n      parseClassName,\n      getClassGroupId,\n      getConflictingClassGroupIds\n    } = configUtils;\n    const classGroupsInConflict = [];\n    const classNames = classList.trim().split(SPLIT_CLASSES_REGEX);\n    let result = \"\";\n    for (let index = classNames.length - 1; index >= 0; index -= 1) {\n      const originalClassName = classNames[index];\n      const {\n        modifiers,\n        hasImportantModifier,\n        baseClassName,\n        maybePostfixModifierPosition\n      } = parseClassName(originalClassName);\n      let hasPostfixModifier = Boolean(maybePostfixModifierPosition);\n      let classGroupId = getClassGroupId(hasPostfixModifier ? baseClassName.substring(0, maybePostfixModifierPosition) : baseClassName);\n      if (!classGroupId) {\n        if (!hasPostfixModifier) {\n          result = originalClassName + (result.length > 0 ? \" \" + result : result);\n          continue;\n        }\n        classGroupId = getClassGroupId(baseClassName);\n        if (!classGroupId) {\n          result = originalClassName + (result.length > 0 ? \" \" + result : result);\n          continue;\n        }\n        hasPostfixModifier = false;\n      }\n      const variantModifier = sortModifiers(modifiers).join(\":\");\n      const modifierId = hasImportantModifier ? variantModifier + IMPORTANT_MODIFIER : variantModifier;\n      const classId = modifierId + classGroupId;\n      if (classGroupsInConflict.includes(classId)) {\n        continue;\n      }\n      classGroupsInConflict.push(classId);\n      const conflictGroups = getConflictingClassGroupIds(classGroupId, hasPostfixModifier);\n      for (let i5 = 0; i5 < conflictGroups.length; ++i5) {\n        const group = conflictGroups[i5];\n        classGroupsInConflict.push(modifierId + group);\n      }\n      result = originalClassName + (result.length > 0 ? \" \" + result : result);\n    }\n    return result;\n  };\n  function twJoin() {\n    let index = 0;\n    let argument;\n    let resolvedValue;\n    let string = \"\";\n    while (index < arguments.length) {\n      if (argument = arguments[index++]) {\n        if (resolvedValue = toValue(argument)) {\n          string && (string += \" \");\n          string += resolvedValue;\n        }\n      }\n    }\n    return string;\n  }\n  var toValue = (mix) => {\n    if (typeof mix === \"string\") {\n      return mix;\n    }\n    let resolvedValue;\n    let string = \"\";\n    for (let k3 = 0; k3 < mix.length; k3++) {\n      if (mix[k3]) {\n        if (resolvedValue = toValue(mix[k3])) {\n          string && (string += \" \");\n          string += resolvedValue;\n        }\n      }\n    }\n    return string;\n  };\n  function createTailwindMerge(createConfigFirst, ...createConfigRest) {\n    let configUtils;\n    let cacheGet;\n    let cacheSet;\n    let functionToCall = initTailwindMerge;\n    function initTailwindMerge(classList) {\n      const config = createConfigRest.reduce((previousConfig, createConfigCurrent) => createConfigCurrent(previousConfig), createConfigFirst());\n      configUtils = createConfigUtils(config);\n      cacheGet = configUtils.cache.get;\n      cacheSet = configUtils.cache.set;\n      functionToCall = tailwindMerge;\n      return tailwindMerge(classList);\n    }\n    function tailwindMerge(classList) {\n      const cachedResult = cacheGet(classList);\n      if (cachedResult) {\n        return cachedResult;\n      }\n      const result = mergeClassList(classList, configUtils);\n      cacheSet(classList, result);\n      return result;\n    }\n    return function callTailwindMerge() {\n      return functionToCall(twJoin.apply(null, arguments));\n    };\n  }\n  var fromTheme = (key) => {\n    const themeGetter = (theme) => theme[key] || [];\n    themeGetter.isThemeGetter = true;\n    return themeGetter;\n  };\n  var arbitraryValueRegex = /^\\[(?:([a-z-]+):)?(.+)\\]$/i;\n  var fractionRegex = /^\\d+\\/\\d+$/;\n  var stringLengths = /* @__PURE__ */ new Set([\"px\", \"full\", \"screen\"]);\n  var tshirtUnitRegex = /^(\\d+(\\.\\d+)?)?(xs|sm|md|lg|xl)$/;\n  var lengthUnitRegex = /\\d+(%|px|r?em|[sdl]?v([hwib]|min|max)|pt|pc|in|cm|mm|cap|ch|ex|r?lh|cq(w|h|i|b|min|max))|\\b(calc|min|max|clamp)\\(.+\\)|^0$/;\n  var colorFunctionRegex = /^(rgba?|hsla?|hwb|(ok)?(lab|lch))\\(.+\\)$/;\n  var shadowRegex = /^(inset_)?-?((\\d+)?\\.?(\\d+)[a-z]+|0)_-?((\\d+)?\\.?(\\d+)[a-z]+|0)/;\n  var imageRegex = /^(url|image|image-set|cross-fade|element|(repeating-)?(linear|radial|conic)-gradient)\\(.+\\)$/;\n  var isLength = (value) => isNumber(value) || stringLengths.has(value) || fractionRegex.test(value);\n  var isArbitraryLength = (value) => getIsArbitraryValue(value, \"length\", isLengthOnly);\n  var isNumber = (value) => Boolean(value) && !Number.isNaN(Number(value));\n  var isArbitraryNumber = (value) => getIsArbitraryValue(value, \"number\", isNumber);\n  var isInteger = (value) => Boolean(value) && Number.isInteger(Number(value));\n  var isPercent = (value) => value.endsWith(\"%\") && isNumber(value.slice(0, -1));\n  var isArbitraryValue = (value) => arbitraryValueRegex.test(value);\n  var isTshirtSize = (value) => tshirtUnitRegex.test(value);\n  var sizeLabels = /* @__PURE__ */ new Set([\"length\", \"size\", \"percentage\"]);\n  var isArbitrarySize = (value) => getIsArbitraryValue(value, sizeLabels, isNever);\n  var isArbitraryPosition = (value) => getIsArbitraryValue(value, \"position\", isNever);\n  var imageLabels = /* @__PURE__ */ new Set([\"image\", \"url\"]);\n  var isArbitraryImage = (value) => getIsArbitraryValue(value, imageLabels, isImage);\n  var isArbitraryShadow = (value) => getIsArbitraryValue(value, \"\", isShadow);\n  var isAny = () => true;\n  var getIsArbitraryValue = (value, label, testValue) => {\n    const result = arbitraryValueRegex.exec(value);\n    if (result) {\n      if (result[1]) {\n        return typeof label === \"string\" ? result[1] === label : label.has(result[1]);\n      }\n      return testValue(result[2]);\n    }\n    return false;\n  };\n  var isLengthOnly = (value) => (\n    // `colorFunctionRegex` check is necessary because color functions can have percentages in them which which would be incorrectly classified as lengths.\n    // For example, `hsl(0 0% 0%)` would be classified as a length without this check.\n    // I could also use lookbehind assertion in `lengthUnitRegex` but that isn't supported widely enough.\n    lengthUnitRegex.test(value) && !colorFunctionRegex.test(value)\n  );\n  var isNever = () => false;\n  var isShadow = (value) => shadowRegex.test(value);\n  var isImage = (value) => imageRegex.test(value);\n  var getDefaultConfig = () => {\n    const colors = fromTheme(\"colors\");\n    const spacing = fromTheme(\"spacing\");\n    const blur = fromTheme(\"blur\");\n    const brightness = fromTheme(\"brightness\");\n    const borderColor = fromTheme(\"borderColor\");\n    const borderRadius = fromTheme(\"borderRadius\");\n    const borderSpacing = fromTheme(\"borderSpacing\");\n    const borderWidth = fromTheme(\"borderWidth\");\n    const contrast = fromTheme(\"contrast\");\n    const grayscale = fromTheme(\"grayscale\");\n    const hueRotate = fromTheme(\"hueRotate\");\n    const invert = fromTheme(\"invert\");\n    const gap = fromTheme(\"gap\");\n    const gradientColorStops = fromTheme(\"gradientColorStops\");\n    const gradientColorStopPositions = fromTheme(\"gradientColorStopPositions\");\n    const inset = fromTheme(\"inset\");\n    const margin = fromTheme(\"margin\");\n    const opacity = fromTheme(\"opacity\");\n    const padding = fromTheme(\"padding\");\n    const saturate = fromTheme(\"saturate\");\n    const scale = fromTheme(\"scale\");\n    const sepia = fromTheme(\"sepia\");\n    const skew = fromTheme(\"skew\");\n    const space = fromTheme(\"space\");\n    const translate = fromTheme(\"translate\");\n    const getOverscroll = () => [\"auto\", \"contain\", \"none\"];\n    const getOverflow = () => [\"auto\", \"hidden\", \"clip\", \"visible\", \"scroll\"];\n    const getSpacingWithAutoAndArbitrary = () => [\"auto\", isArbitraryValue, spacing];\n    const getSpacingWithArbitrary = () => [isArbitraryValue, spacing];\n    const getLengthWithEmptyAndArbitrary = () => [\"\", isLength, isArbitraryLength];\n    const getNumberWithAutoAndArbitrary = () => [\"auto\", isNumber, isArbitraryValue];\n    const getPositions = () => [\"bottom\", \"center\", \"left\", \"left-bottom\", \"left-top\", \"right\", \"right-bottom\", \"right-top\", \"top\"];\n    const getLineStyles = () => [\"solid\", \"dashed\", \"dotted\", \"double\", \"none\"];\n    const getBlendModes = () => [\"normal\", \"multiply\", \"screen\", \"overlay\", \"darken\", \"lighten\", \"color-dodge\", \"color-burn\", \"hard-light\", \"soft-light\", \"difference\", \"exclusion\", \"hue\", \"saturation\", \"color\", \"luminosity\"];\n    const getAlign = () => [\"start\", \"end\", \"center\", \"between\", \"around\", \"evenly\", \"stretch\"];\n    const getZeroAndEmpty = () => [\"\", \"0\", isArbitraryValue];\n    const getBreaks = () => [\"auto\", \"avoid\", \"all\", \"avoid-page\", \"page\", \"left\", \"right\", \"column\"];\n    const getNumberAndArbitrary = () => [isNumber, isArbitraryValue];\n    return {\n      cacheSize: 500,\n      separator: \":\",\n      theme: {\n        colors: [isAny],\n        spacing: [isLength, isArbitraryLength],\n        blur: [\"none\", \"\", isTshirtSize, isArbitraryValue],\n        brightness: getNumberAndArbitrary(),\n        borderColor: [colors],\n        borderRadius: [\"none\", \"\", \"full\", isTshirtSize, isArbitraryValue],\n        borderSpacing: getSpacingWithArbitrary(),\n        borderWidth: getLengthWithEmptyAndArbitrary(),\n        contrast: getNumberAndArbitrary(),\n        grayscale: getZeroAndEmpty(),\n        hueRotate: getNumberAndArbitrary(),\n        invert: getZeroAndEmpty(),\n        gap: getSpacingWithArbitrary(),\n        gradientColorStops: [colors],\n        gradientColorStopPositions: [isPercent, isArbitraryLength],\n        inset: getSpacingWithAutoAndArbitrary(),\n        margin: getSpacingWithAutoAndArbitrary(),\n        opacity: getNumberAndArbitrary(),\n        padding: getSpacingWithArbitrary(),\n        saturate: getNumberAndArbitrary(),\n        scale: getNumberAndArbitrary(),\n        sepia: getZeroAndEmpty(),\n        skew: getNumberAndArbitrary(),\n        space: getSpacingWithArbitrary(),\n        translate: getSpacingWithArbitrary()\n      },\n      classGroups: {\n        // Layout\n        /**\n         * Aspect Ratio\n         * @see https://tailwindcss.com/docs/aspect-ratio\n         */\n        aspect: [{\n          aspect: [\"auto\", \"square\", \"video\", isArbitraryValue]\n        }],\n        /**\n         * Container\n         * @see https://tailwindcss.com/docs/container\n         */\n        container: [\"container\"],\n        /**\n         * Columns\n         * @see https://tailwindcss.com/docs/columns\n         */\n        columns: [{\n          columns: [isTshirtSize]\n        }],\n        /**\n         * Break After\n         * @see https://tailwindcss.com/docs/break-after\n         */\n        \"break-after\": [{\n          \"break-after\": getBreaks()\n        }],\n        /**\n         * Break Before\n         * @see https://tailwindcss.com/docs/break-before\n         */\n        \"break-before\": [{\n          \"break-before\": getBreaks()\n        }],\n        /**\n         * Break Inside\n         * @see https://tailwindcss.com/docs/break-inside\n         */\n        \"break-inside\": [{\n          \"break-inside\": [\"auto\", \"avoid\", \"avoid-page\", \"avoid-column\"]\n        }],\n        /**\n         * Box Decoration Break\n         * @see https://tailwindcss.com/docs/box-decoration-break\n         */\n        \"box-decoration\": [{\n          \"box-decoration\": [\"slice\", \"clone\"]\n        }],\n        /**\n         * Box Sizing\n         * @see https://tailwindcss.com/docs/box-sizing\n         */\n        box: [{\n          box: [\"border\", \"content\"]\n        }],\n        /**\n         * Display\n         * @see https://tailwindcss.com/docs/display\n         */\n        display: [\"block\", \"inline-block\", \"inline\", \"flex\", \"inline-flex\", \"table\", \"inline-table\", \"table-caption\", \"table-cell\", \"table-column\", \"table-column-group\", \"table-footer-group\", \"table-header-group\", \"table-row-group\", \"table-row\", \"flow-root\", \"grid\", \"inline-grid\", \"contents\", \"list-item\", \"hidden\"],\n        /**\n         * Floats\n         * @see https://tailwindcss.com/docs/float\n         */\n        float: [{\n          float: [\"right\", \"left\", \"none\", \"start\", \"end\"]\n        }],\n        /**\n         * Clear\n         * @see https://tailwindcss.com/docs/clear\n         */\n        clear: [{\n          clear: [\"left\", \"right\", \"both\", \"none\", \"start\", \"end\"]\n        }],\n        /**\n         * Isolation\n         * @see https://tailwindcss.com/docs/isolation\n         */\n        isolation: [\"isolate\", \"isolation-auto\"],\n        /**\n         * Object Fit\n         * @see https://tailwindcss.com/docs/object-fit\n         */\n        \"object-fit\": [{\n          object: [\"contain\", \"cover\", \"fill\", \"none\", \"scale-down\"]\n        }],\n        /**\n         * Object Position\n         * @see https://tailwindcss.com/docs/object-position\n         */\n        \"object-position\": [{\n          object: [...getPositions(), isArbitraryValue]\n        }],\n        /**\n         * Overflow\n         * @see https://tailwindcss.com/docs/overflow\n         */\n        overflow: [{\n          overflow: getOverflow()\n        }],\n        /**\n         * Overflow X\n         * @see https://tailwindcss.com/docs/overflow\n         */\n        \"overflow-x\": [{\n          \"overflow-x\": getOverflow()\n        }],\n        /**\n         * Overflow Y\n         * @see https://tailwindcss.com/docs/overflow\n         */\n        \"overflow-y\": [{\n          \"overflow-y\": getOverflow()\n        }],\n        /**\n         * Overscroll Behavior\n         * @see https://tailwindcss.com/docs/overscroll-behavior\n         */\n        overscroll: [{\n          overscroll: getOverscroll()\n        }],\n        /**\n         * Overscroll Behavior X\n         * @see https://tailwindcss.com/docs/overscroll-behavior\n         */\n        \"overscroll-x\": [{\n          \"overscroll-x\": getOverscroll()\n        }],\n        /**\n         * Overscroll Behavior Y\n         * @see https://tailwindcss.com/docs/overscroll-behavior\n         */\n        \"overscroll-y\": [{\n          \"overscroll-y\": getOverscroll()\n        }],\n        /**\n         * Position\n         * @see https://tailwindcss.com/docs/position\n         */\n        position: [\"static\", \"fixed\", \"absolute\", \"relative\", \"sticky\"],\n        /**\n         * Top / Right / Bottom / Left\n         * @see https://tailwindcss.com/docs/top-right-bottom-left\n         */\n        inset: [{\n          inset: [inset]\n        }],\n        /**\n         * Right / Left\n         * @see https://tailwindcss.com/docs/top-right-bottom-left\n         */\n        \"inset-x\": [{\n          \"inset-x\": [inset]\n        }],\n        /**\n         * Top / Bottom\n         * @see https://tailwindcss.com/docs/top-right-bottom-left\n         */\n        \"inset-y\": [{\n          \"inset-y\": [inset]\n        }],\n        /**\n         * Start\n         * @see https://tailwindcss.com/docs/top-right-bottom-left\n         */\n        start: [{\n          start: [inset]\n        }],\n        /**\n         * End\n         * @see https://tailwindcss.com/docs/top-right-bottom-left\n         */\n        end: [{\n          end: [inset]\n        }],\n        /**\n         * Top\n         * @see https://tailwindcss.com/docs/top-right-bottom-left\n         */\n        top: [{\n          top: [inset]\n        }],\n        /**\n         * Right\n         * @see https://tailwindcss.com/docs/top-right-bottom-left\n         */\n        right: [{\n          right: [inset]\n        }],\n        /**\n         * Bottom\n         * @see https://tailwindcss.com/docs/top-right-bottom-left\n         */\n        bottom: [{\n          bottom: [inset]\n        }],\n        /**\n         * Left\n         * @see https://tailwindcss.com/docs/top-right-bottom-left\n         */\n        left: [{\n          left: [inset]\n        }],\n        /**\n         * Visibility\n         * @see https://tailwindcss.com/docs/visibility\n         */\n        visibility: [\"visible\", \"invisible\", \"collapse\"],\n        /**\n         * Z-Index\n         * @see https://tailwindcss.com/docs/z-index\n         */\n        z: [{\n          z: [\"auto\", isInteger, isArbitraryValue]\n        }],\n        // Flexbox and Grid\n        /**\n         * Flex Basis\n         * @see https://tailwindcss.com/docs/flex-basis\n         */\n        basis: [{\n          basis: getSpacingWithAutoAndArbitrary()\n        }],\n        /**\n         * Flex Direction\n         * @see https://tailwindcss.com/docs/flex-direction\n         */\n        \"flex-direction\": [{\n          flex: [\"row\", \"row-reverse\", \"col\", \"col-reverse\"]\n        }],\n        /**\n         * Flex Wrap\n         * @see https://tailwindcss.com/docs/flex-wrap\n         */\n        \"flex-wrap\": [{\n          flex: [\"wrap\", \"wrap-reverse\", \"nowrap\"]\n        }],\n        /**\n         * Flex\n         * @see https://tailwindcss.com/docs/flex\n         */\n        flex: [{\n          flex: [\"1\", \"auto\", \"initial\", \"none\", isArbitraryValue]\n        }],\n        /**\n         * Flex Grow\n         * @see https://tailwindcss.com/docs/flex-grow\n         */\n        grow: [{\n          grow: getZeroAndEmpty()\n        }],\n        /**\n         * Flex Shrink\n         * @see https://tailwindcss.com/docs/flex-shrink\n         */\n        shrink: [{\n          shrink: getZeroAndEmpty()\n        }],\n        /**\n         * Order\n         * @see https://tailwindcss.com/docs/order\n         */\n        order: [{\n          order: [\"first\", \"last\", \"none\", isInteger, isArbitraryValue]\n        }],\n        /**\n         * Grid Template Columns\n         * @see https://tailwindcss.com/docs/grid-template-columns\n         */\n        \"grid-cols\": [{\n          \"grid-cols\": [isAny]\n        }],\n        /**\n         * Grid Column Start / End\n         * @see https://tailwindcss.com/docs/grid-column\n         */\n        \"col-start-end\": [{\n          col: [\"auto\", {\n            span: [\"full\", isInteger, isArbitraryValue]\n          }, isArbitraryValue]\n        }],\n        /**\n         * Grid Column Start\n         * @see https://tailwindcss.com/docs/grid-column\n         */\n        \"col-start\": [{\n          \"col-start\": getNumberWithAutoAndArbitrary()\n        }],\n        /**\n         * Grid Column End\n         * @see https://tailwindcss.com/docs/grid-column\n         */\n        \"col-end\": [{\n          \"col-end\": getNumberWithAutoAndArbitrary()\n        }],\n        /**\n         * Grid Template Rows\n         * @see https://tailwindcss.com/docs/grid-template-rows\n         */\n        \"grid-rows\": [{\n          \"grid-rows\": [isAny]\n        }],\n        /**\n         * Grid Row Start / End\n         * @see https://tailwindcss.com/docs/grid-row\n         */\n        \"row-start-end\": [{\n          row: [\"auto\", {\n            span: [isInteger, isArbitraryValue]\n          }, isArbitraryValue]\n        }],\n        /**\n         * Grid Row Start\n         * @see https://tailwindcss.com/docs/grid-row\n         */\n        \"row-start\": [{\n          \"row-start\": getNumberWithAutoAndArbitrary()\n        }],\n        /**\n         * Grid Row End\n         * @see https://tailwindcss.com/docs/grid-row\n         */\n        \"row-end\": [{\n          \"row-end\": getNumberWithAutoAndArbitrary()\n        }],\n        /**\n         * Grid Auto Flow\n         * @see https://tailwindcss.com/docs/grid-auto-flow\n         */\n        \"grid-flow\": [{\n          \"grid-flow\": [\"row\", \"col\", \"dense\", \"row-dense\", \"col-dense\"]\n        }],\n        /**\n         * Grid Auto Columns\n         * @see https://tailwindcss.com/docs/grid-auto-columns\n         */\n        \"auto-cols\": [{\n          \"auto-cols\": [\"auto\", \"min\", \"max\", \"fr\", isArbitraryValue]\n        }],\n        /**\n         * Grid Auto Rows\n         * @see https://tailwindcss.com/docs/grid-auto-rows\n         */\n        \"auto-rows\": [{\n          \"auto-rows\": [\"auto\", \"min\", \"max\", \"fr\", isArbitraryValue]\n        }],\n        /**\n         * Gap\n         * @see https://tailwindcss.com/docs/gap\n         */\n        gap: [{\n          gap: [gap]\n        }],\n        /**\n         * Gap X\n         * @see https://tailwindcss.com/docs/gap\n         */\n        \"gap-x\": [{\n          \"gap-x\": [gap]\n        }],\n        /**\n         * Gap Y\n         * @see https://tailwindcss.com/docs/gap\n         */\n        \"gap-y\": [{\n          \"gap-y\": [gap]\n        }],\n        /**\n         * Justify Content\n         * @see https://tailwindcss.com/docs/justify-content\n         */\n        \"justify-content\": [{\n          justify: [\"normal\", ...getAlign()]\n        }],\n        /**\n         * Justify Items\n         * @see https://tailwindcss.com/docs/justify-items\n         */\n        \"justify-items\": [{\n          \"justify-items\": [\"start\", \"end\", \"center\", \"stretch\"]\n        }],\n        /**\n         * Justify Self\n         * @see https://tailwindcss.com/docs/justify-self\n         */\n        \"justify-self\": [{\n          \"justify-self\": [\"auto\", \"start\", \"end\", \"center\", \"stretch\"]\n        }],\n        /**\n         * Align Content\n         * @see https://tailwindcss.com/docs/align-content\n         */\n        \"align-content\": [{\n          content: [\"normal\", ...getAlign(), \"baseline\"]\n        }],\n        /**\n         * Align Items\n         * @see https://tailwindcss.com/docs/align-items\n         */\n        \"align-items\": [{\n          items: [\"start\", \"end\", \"center\", \"baseline\", \"stretch\"]\n        }],\n        /**\n         * Align Self\n         * @see https://tailwindcss.com/docs/align-self\n         */\n        \"align-self\": [{\n          self: [\"auto\", \"start\", \"end\", \"center\", \"stretch\", \"baseline\"]\n        }],\n        /**\n         * Place Content\n         * @see https://tailwindcss.com/docs/place-content\n         */\n        \"place-content\": [{\n          \"place-content\": [...getAlign(), \"baseline\"]\n        }],\n        /**\n         * Place Items\n         * @see https://tailwindcss.com/docs/place-items\n         */\n        \"place-items\": [{\n          \"place-items\": [\"start\", \"end\", \"center\", \"baseline\", \"stretch\"]\n        }],\n        /**\n         * Place Self\n         * @see https://tailwindcss.com/docs/place-self\n         */\n        \"place-self\": [{\n          \"place-self\": [\"auto\", \"start\", \"end\", \"center\", \"stretch\"]\n        }],\n        // Spacing\n        /**\n         * Padding\n         * @see https://tailwindcss.com/docs/padding\n         */\n        p: [{\n          p: [padding]\n        }],\n        /**\n         * Padding X\n         * @see https://tailwindcss.com/docs/padding\n         */\n        px: [{\n          px: [padding]\n        }],\n        /**\n         * Padding Y\n         * @see https://tailwindcss.com/docs/padding\n         */\n        py: [{\n          py: [padding]\n        }],\n        /**\n         * Padding Start\n         * @see https://tailwindcss.com/docs/padding\n         */\n        ps: [{\n          ps: [padding]\n        }],\n        /**\n         * Padding End\n         * @see https://tailwindcss.com/docs/padding\n         */\n        pe: [{\n          pe: [padding]\n        }],\n        /**\n         * Padding Top\n         * @see https://tailwindcss.com/docs/padding\n         */\n        pt: [{\n          pt: [padding]\n        }],\n        /**\n         * Padding Right\n         * @see https://tailwindcss.com/docs/padding\n         */\n        pr: [{\n          pr: [padding]\n        }],\n        /**\n         * Padding Bottom\n         * @see https://tailwindcss.com/docs/padding\n         */\n        pb: [{\n          pb: [padding]\n        }],\n        /**\n         * Padding Left\n         * @see https://tailwindcss.com/docs/padding\n         */\n        pl: [{\n          pl: [padding]\n        }],\n        /**\n         * Margin\n         * @see https://tailwindcss.com/docs/margin\n         */\n        m: [{\n          m: [margin]\n        }],\n        /**\n         * Margin X\n         * @see https://tailwindcss.com/docs/margin\n         */\n        mx: [{\n          mx: [margin]\n        }],\n        /**\n         * Margin Y\n         * @see https://tailwindcss.com/docs/margin\n         */\n        my: [{\n          my: [margin]\n        }],\n        /**\n         * Margin Start\n         * @see https://tailwindcss.com/docs/margin\n         */\n        ms: [{\n          ms: [margin]\n        }],\n        /**\n         * Margin End\n         * @see https://tailwindcss.com/docs/margin\n         */\n        me: [{\n          me: [margin]\n        }],\n        /**\n         * Margin Top\n         * @see https://tailwindcss.com/docs/margin\n         */\n        mt: [{\n          mt: [margin]\n        }],\n        /**\n         * Margin Right\n         * @see https://tailwindcss.com/docs/margin\n         */\n        mr: [{\n          mr: [margin]\n        }],\n        /**\n         * Margin Bottom\n         * @see https://tailwindcss.com/docs/margin\n         */\n        mb: [{\n          mb: [margin]\n        }],\n        /**\n         * Margin Left\n         * @see https://tailwindcss.com/docs/margin\n         */\n        ml: [{\n          ml: [margin]\n        }],\n        /**\n         * Space Between X\n         * @see https://tailwindcss.com/docs/space\n         */\n        \"space-x\": [{\n          \"space-x\": [space]\n        }],\n        /**\n         * Space Between X Reverse\n         * @see https://tailwindcss.com/docs/space\n         */\n        \"space-x-reverse\": [\"space-x-reverse\"],\n        /**\n         * Space Between Y\n         * @see https://tailwindcss.com/docs/space\n         */\n        \"space-y\": [{\n          \"space-y\": [space]\n        }],\n        /**\n         * Space Between Y Reverse\n         * @see https://tailwindcss.com/docs/space\n         */\n        \"space-y-reverse\": [\"space-y-reverse\"],\n        // Sizing\n        /**\n         * Width\n         * @see https://tailwindcss.com/docs/width\n         */\n        w: [{\n          w: [\"auto\", \"min\", \"max\", \"fit\", \"svw\", \"lvw\", \"dvw\", isArbitraryValue, spacing]\n        }],\n        /**\n         * Min-Width\n         * @see https://tailwindcss.com/docs/min-width\n         */\n        \"min-w\": [{\n          \"min-w\": [isArbitraryValue, spacing, \"min\", \"max\", \"fit\"]\n        }],\n        /**\n         * Max-Width\n         * @see https://tailwindcss.com/docs/max-width\n         */\n        \"max-w\": [{\n          \"max-w\": [isArbitraryValue, spacing, \"none\", \"full\", \"min\", \"max\", \"fit\", \"prose\", {\n            screen: [isTshirtSize]\n          }, isTshirtSize]\n        }],\n        /**\n         * Height\n         * @see https://tailwindcss.com/docs/height\n         */\n        h: [{\n          h: [isArbitraryValue, spacing, \"auto\", \"min\", \"max\", \"fit\", \"svh\", \"lvh\", \"dvh\"]\n        }],\n        /**\n         * Min-Height\n         * @see https://tailwindcss.com/docs/min-height\n         */\n        \"min-h\": [{\n          \"min-h\": [isArbitraryValue, spacing, \"min\", \"max\", \"fit\", \"svh\", \"lvh\", \"dvh\"]\n        }],\n        /**\n         * Max-Height\n         * @see https://tailwindcss.com/docs/max-height\n         */\n        \"max-h\": [{\n          \"max-h\": [isArbitraryValue, spacing, \"min\", \"max\", \"fit\", \"svh\", \"lvh\", \"dvh\"]\n        }],\n        /**\n         * Size\n         * @see https://tailwindcss.com/docs/size\n         */\n        size: [{\n          size: [isArbitraryValue, spacing, \"auto\", \"min\", \"max\", \"fit\"]\n        }],\n        // Typography\n        /**\n         * Font Size\n         * @see https://tailwindcss.com/docs/font-size\n         */\n        \"font-size\": [{\n          text: [\"base\", isTshirtSize, isArbitraryLength]\n        }],\n        /**\n         * Font Smoothing\n         * @see https://tailwindcss.com/docs/font-smoothing\n         */\n        \"font-smoothing\": [\"antialiased\", \"subpixel-antialiased\"],\n        /**\n         * Font Style\n         * @see https://tailwindcss.com/docs/font-style\n         */\n        \"font-style\": [\"italic\", \"not-italic\"],\n        /**\n         * Font Weight\n         * @see https://tailwindcss.com/docs/font-weight\n         */\n        \"font-weight\": [{\n          font: [\"thin\", \"extralight\", \"light\", \"normal\", \"medium\", \"semibold\", \"bold\", \"extrabold\", \"black\", isArbitraryNumber]\n        }],\n        /**\n         * Font Family\n         * @see https://tailwindcss.com/docs/font-family\n         */\n        \"font-family\": [{\n          font: [isAny]\n        }],\n        /**\n         * Font Variant Numeric\n         * @see https://tailwindcss.com/docs/font-variant-numeric\n         */\n        \"fvn-normal\": [\"normal-nums\"],\n        /**\n         * Font Variant Numeric\n         * @see https://tailwindcss.com/docs/font-variant-numeric\n         */\n        \"fvn-ordinal\": [\"ordinal\"],\n        /**\n         * Font Variant Numeric\n         * @see https://tailwindcss.com/docs/font-variant-numeric\n         */\n        \"fvn-slashed-zero\": [\"slashed-zero\"],\n        /**\n         * Font Variant Numeric\n         * @see https://tailwindcss.com/docs/font-variant-numeric\n         */\n        \"fvn-figure\": [\"lining-nums\", \"oldstyle-nums\"],\n        /**\n         * Font Variant Numeric\n         * @see https://tailwindcss.com/docs/font-variant-numeric\n         */\n        \"fvn-spacing\": [\"proportional-nums\", \"tabular-nums\"],\n        /**\n         * Font Variant Numeric\n         * @see https://tailwindcss.com/docs/font-variant-numeric\n         */\n        \"fvn-fraction\": [\"diagonal-fractions\", \"stacked-fractions\"],\n        /**\n         * Letter Spacing\n         * @see https://tailwindcss.com/docs/letter-spacing\n         */\n        tracking: [{\n          tracking: [\"tighter\", \"tight\", \"normal\", \"wide\", \"wider\", \"widest\", isArbitraryValue]\n        }],\n        /**\n         * Line Clamp\n         * @see https://tailwindcss.com/docs/line-clamp\n         */\n        \"line-clamp\": [{\n          \"line-clamp\": [\"none\", isNumber, isArbitraryNumber]\n        }],\n        /**\n         * Line Height\n         * @see https://tailwindcss.com/docs/line-height\n         */\n        leading: [{\n          leading: [\"none\", \"tight\", \"snug\", \"normal\", \"relaxed\", \"loose\", isLength, isArbitraryValue]\n        }],\n        /**\n         * List Style Image\n         * @see https://tailwindcss.com/docs/list-style-image\n         */\n        \"list-image\": [{\n          \"list-image\": [\"none\", isArbitraryValue]\n        }],\n        /**\n         * List Style Type\n         * @see https://tailwindcss.com/docs/list-style-type\n         */\n        \"list-style-type\": [{\n          list: [\"none\", \"disc\", \"decimal\", isArbitraryValue]\n        }],\n        /**\n         * List Style Position\n         * @see https://tailwindcss.com/docs/list-style-position\n         */\n        \"list-style-position\": [{\n          list: [\"inside\", \"outside\"]\n        }],\n        /**\n         * Placeholder Color\n         * @deprecated since Tailwind CSS v3.0.0\n         * @see https://tailwindcss.com/docs/placeholder-color\n         */\n        \"placeholder-color\": [{\n          placeholder: [colors]\n        }],\n        /**\n         * Placeholder Opacity\n         * @see https://tailwindcss.com/docs/placeholder-opacity\n         */\n        \"placeholder-opacity\": [{\n          \"placeholder-opacity\": [opacity]\n        }],\n        /**\n         * Text Alignment\n         * @see https://tailwindcss.com/docs/text-align\n         */\n        \"text-alignment\": [{\n          text: [\"left\", \"center\", \"right\", \"justify\", \"start\", \"end\"]\n        }],\n        /**\n         * Text Color\n         * @see https://tailwindcss.com/docs/text-color\n         */\n        \"text-color\": [{\n          text: [colors]\n        }],\n        /**\n         * Text Opacity\n         * @see https://tailwindcss.com/docs/text-opacity\n         */\n        \"text-opacity\": [{\n          \"text-opacity\": [opacity]\n        }],\n        /**\n         * Text Decoration\n         * @see https://tailwindcss.com/docs/text-decoration\n         */\n        \"text-decoration\": [\"underline\", \"overline\", \"line-through\", \"no-underline\"],\n        /**\n         * Text Decoration Style\n         * @see https://tailwindcss.com/docs/text-decoration-style\n         */\n        \"text-decoration-style\": [{\n          decoration: [...getLineStyles(), \"wavy\"]\n        }],\n        /**\n         * Text Decoration Thickness\n         * @see https://tailwindcss.com/docs/text-decoration-thickness\n         */\n        \"text-decoration-thickness\": [{\n          decoration: [\"auto\", \"from-font\", isLength, isArbitraryLength]\n        }],\n        /**\n         * Text Underline Offset\n         * @see https://tailwindcss.com/docs/text-underline-offset\n         */\n        \"underline-offset\": [{\n          \"underline-offset\": [\"auto\", isLength, isArbitraryValue]\n        }],\n        /**\n         * Text Decoration Color\n         * @see https://tailwindcss.com/docs/text-decoration-color\n         */\n        \"text-decoration-color\": [{\n          decoration: [colors]\n        }],\n        /**\n         * Text Transform\n         * @see https://tailwindcss.com/docs/text-transform\n         */\n        \"text-transform\": [\"uppercase\", \"lowercase\", \"capitalize\", \"normal-case\"],\n        /**\n         * Text Overflow\n         * @see https://tailwindcss.com/docs/text-overflow\n         */\n        \"text-overflow\": [\"truncate\", \"text-ellipsis\", \"text-clip\"],\n        /**\n         * Text Wrap\n         * @see https://tailwindcss.com/docs/text-wrap\n         */\n        \"text-wrap\": [{\n          text: [\"wrap\", \"nowrap\", \"balance\", \"pretty\"]\n        }],\n        /**\n         * Text Indent\n         * @see https://tailwindcss.com/docs/text-indent\n         */\n        indent: [{\n          indent: getSpacingWithArbitrary()\n        }],\n        /**\n         * Vertical Alignment\n         * @see https://tailwindcss.com/docs/vertical-align\n         */\n        \"vertical-align\": [{\n          align: [\"baseline\", \"top\", \"middle\", \"bottom\", \"text-top\", \"text-bottom\", \"sub\", \"super\", isArbitraryValue]\n        }],\n        /**\n         * Whitespace\n         * @see https://tailwindcss.com/docs/whitespace\n         */\n        whitespace: [{\n          whitespace: [\"normal\", \"nowrap\", \"pre\", \"pre-line\", \"pre-wrap\", \"break-spaces\"]\n        }],\n        /**\n         * Word Break\n         * @see https://tailwindcss.com/docs/word-break\n         */\n        break: [{\n          break: [\"normal\", \"words\", \"all\", \"keep\"]\n        }],\n        /**\n         * Hyphens\n         * @see https://tailwindcss.com/docs/hyphens\n         */\n        hyphens: [{\n          hyphens: [\"none\", \"manual\", \"auto\"]\n        }],\n        /**\n         * Content\n         * @see https://tailwindcss.com/docs/content\n         */\n        content: [{\n          content: [\"none\", isArbitraryValue]\n        }],\n        // Backgrounds\n        /**\n         * Background Attachment\n         * @see https://tailwindcss.com/docs/background-attachment\n         */\n        \"bg-attachment\": [{\n          bg: [\"fixed\", \"local\", \"scroll\"]\n        }],\n        /**\n         * Background Clip\n         * @see https://tailwindcss.com/docs/background-clip\n         */\n        \"bg-clip\": [{\n          \"bg-clip\": [\"border\", \"padding\", \"content\", \"text\"]\n        }],\n        /**\n         * Background Opacity\n         * @deprecated since Tailwind CSS v3.0.0\n         * @see https://tailwindcss.com/docs/background-opacity\n         */\n        \"bg-opacity\": [{\n          \"bg-opacity\": [opacity]\n        }],\n        /**\n         * Background Origin\n         * @see https://tailwindcss.com/docs/background-origin\n         */\n        \"bg-origin\": [{\n          \"bg-origin\": [\"border\", \"padding\", \"content\"]\n        }],\n        /**\n         * Background Position\n         * @see https://tailwindcss.com/docs/background-position\n         */\n        \"bg-position\": [{\n          bg: [...getPositions(), isArbitraryPosition]\n        }],\n        /**\n         * Background Repeat\n         * @see https://tailwindcss.com/docs/background-repeat\n         */\n        \"bg-repeat\": [{\n          bg: [\"no-repeat\", {\n            repeat: [\"\", \"x\", \"y\", \"round\", \"space\"]\n          }]\n        }],\n        /**\n         * Background Size\n         * @see https://tailwindcss.com/docs/background-size\n         */\n        \"bg-size\": [{\n          bg: [\"auto\", \"cover\", \"contain\", isArbitrarySize]\n        }],\n        /**\n         * Background Image\n         * @see https://tailwindcss.com/docs/background-image\n         */\n        \"bg-image\": [{\n          bg: [\"none\", {\n            \"gradient-to\": [\"t\", \"tr\", \"r\", \"br\", \"b\", \"bl\", \"l\", \"tl\"]\n          }, isArbitraryImage]\n        }],\n        /**\n         * Background Color\n         * @see https://tailwindcss.com/docs/background-color\n         */\n        \"bg-color\": [{\n          bg: [colors]\n        }],\n        /**\n         * Gradient Color Stops From Position\n         * @see https://tailwindcss.com/docs/gradient-color-stops\n         */\n        \"gradient-from-pos\": [{\n          from: [gradientColorStopPositions]\n        }],\n        /**\n         * Gradient Color Stops Via Position\n         * @see https://tailwindcss.com/docs/gradient-color-stops\n         */\n        \"gradient-via-pos\": [{\n          via: [gradientColorStopPositions]\n        }],\n        /**\n         * Gradient Color Stops To Position\n         * @see https://tailwindcss.com/docs/gradient-color-stops\n         */\n        \"gradient-to-pos\": [{\n          to: [gradientColorStopPositions]\n        }],\n        /**\n         * Gradient Color Stops From\n         * @see https://tailwindcss.com/docs/gradient-color-stops\n         */\n        \"gradient-from\": [{\n          from: [gradientColorStops]\n        }],\n        /**\n         * Gradient Color Stops Via\n         * @see https://tailwindcss.com/docs/gradient-color-stops\n         */\n        \"gradient-via\": [{\n          via: [gradientColorStops]\n        }],\n        /**\n         * Gradient Color Stops To\n         * @see https://tailwindcss.com/docs/gradient-color-stops\n         */\n        \"gradient-to\": [{\n          to: [gradientColorStops]\n        }],\n        // Borders\n        /**\n         * Border Radius\n         * @see https://tailwindcss.com/docs/border-radius\n         */\n        rounded: [{\n          rounded: [borderRadius]\n        }],\n        /**\n         * Border Radius Start\n         * @see https://tailwindcss.com/docs/border-radius\n         */\n        \"rounded-s\": [{\n          \"rounded-s\": [borderRadius]\n        }],\n        /**\n         * Border Radius End\n         * @see https://tailwindcss.com/docs/border-radius\n         */\n        \"rounded-e\": [{\n          \"rounded-e\": [borderRadius]\n        }],\n        /**\n         * Border Radius Top\n         * @see https://tailwindcss.com/docs/border-radius\n         */\n        \"rounded-t\": [{\n          \"rounded-t\": [borderRadius]\n        }],\n        /**\n         * Border Radius Right\n         * @see https://tailwindcss.com/docs/border-radius\n         */\n        \"rounded-r\": [{\n          \"rounded-r\": [borderRadius]\n        }],\n        /**\n         * Border Radius Bottom\n         * @see https://tailwindcss.com/docs/border-radius\n         */\n        \"rounded-b\": [{\n          \"rounded-b\": [borderRadius]\n        }],\n        /**\n         * Border Radius Left\n         * @see https://tailwindcss.com/docs/border-radius\n         */\n        \"rounded-l\": [{\n          \"rounded-l\": [borderRadius]\n        }],\n        /**\n         * Border Radius Start Start\n         * @see https://tailwindcss.com/docs/border-radius\n         */\n        \"rounded-ss\": [{\n          \"rounded-ss\": [borderRadius]\n        }],\n        /**\n         * Border Radius Start End\n         * @see https://tailwindcss.com/docs/border-radius\n         */\n        \"rounded-se\": [{\n          \"rounded-se\": [borderRadius]\n        }],\n        /**\n         * Border Radius End End\n         * @see https://tailwindcss.com/docs/border-radius\n         */\n        \"rounded-ee\": [{\n          \"rounded-ee\": [borderRadius]\n        }],\n        /**\n         * Border Radius End Start\n         * @see https://tailwindcss.com/docs/border-radius\n         */\n        \"rounded-es\": [{\n          \"rounded-es\": [borderRadius]\n        }],\n        /**\n         * Border Radius Top Left\n         * @see https://tailwindcss.com/docs/border-radius\n         */\n        \"rounded-tl\": [{\n          \"rounded-tl\": [borderRadius]\n        }],\n        /**\n         * Border Radius Top Right\n         * @see https://tailwindcss.com/docs/border-radius\n         */\n        \"rounded-tr\": [{\n          \"rounded-tr\": [borderRadius]\n        }],\n        /**\n         * Border Radius Bottom Right\n         * @see https://tailwindcss.com/docs/border-radius\n         */\n        \"rounded-br\": [{\n          \"rounded-br\": [borderRadius]\n        }],\n        /**\n         * Border Radius Bottom Left\n         * @see https://tailwindcss.com/docs/border-radius\n         */\n        \"rounded-bl\": [{\n          \"rounded-bl\": [borderRadius]\n        }],\n        /**\n         * Border Width\n         * @see https://tailwindcss.com/docs/border-width\n         */\n        \"border-w\": [{\n          border: [borderWidth]\n        }],\n        /**\n         * Border Width X\n         * @see https://tailwindcss.com/docs/border-width\n         */\n        \"border-w-x\": [{\n          \"border-x\": [borderWidth]\n        }],\n        /**\n         * Border Width Y\n         * @see https://tailwindcss.com/docs/border-width\n         */\n        \"border-w-y\": [{\n          \"border-y\": [borderWidth]\n        }],\n        /**\n         * Border Width Start\n         * @see https://tailwindcss.com/docs/border-width\n         */\n        \"border-w-s\": [{\n          \"border-s\": [borderWidth]\n        }],\n        /**\n         * Border Width End\n         * @see https://tailwindcss.com/docs/border-width\n         */\n        \"border-w-e\": [{\n          \"border-e\": [borderWidth]\n        }],\n        /**\n         * Border Width Top\n         * @see https://tailwindcss.com/docs/border-width\n         */\n        \"border-w-t\": [{\n          \"border-t\": [borderWidth]\n        }],\n        /**\n         * Border Width Right\n         * @see https://tailwindcss.com/docs/border-width\n         */\n        \"border-w-r\": [{\n          \"border-r\": [borderWidth]\n        }],\n        /**\n         * Border Width Bottom\n         * @see https://tailwindcss.com/docs/border-width\n         */\n        \"border-w-b\": [{\n          \"border-b\": [borderWidth]\n        }],\n        /**\n         * Border Width Left\n         * @see https://tailwindcss.com/docs/border-width\n         */\n        \"border-w-l\": [{\n          \"border-l\": [borderWidth]\n        }],\n        /**\n         * Border Opacity\n         * @see https://tailwindcss.com/docs/border-opacity\n         */\n        \"border-opacity\": [{\n          \"border-opacity\": [opacity]\n        }],\n        /**\n         * Border Style\n         * @see https://tailwindcss.com/docs/border-style\n         */\n        \"border-style\": [{\n          border: [...getLineStyles(), \"hidden\"]\n        }],\n        /**\n         * Divide Width X\n         * @see https://tailwindcss.com/docs/divide-width\n         */\n        \"divide-x\": [{\n          \"divide-x\": [borderWidth]\n        }],\n        /**\n         * Divide Width X Reverse\n         * @see https://tailwindcss.com/docs/divide-width\n         */\n        \"divide-x-reverse\": [\"divide-x-reverse\"],\n        /**\n         * Divide Width Y\n         * @see https://tailwindcss.com/docs/divide-width\n         */\n        \"divide-y\": [{\n          \"divide-y\": [borderWidth]\n        }],\n        /**\n         * Divide Width Y Reverse\n         * @see https://tailwindcss.com/docs/divide-width\n         */\n        \"divide-y-reverse\": [\"divide-y-reverse\"],\n        /**\n         * Divide Opacity\n         * @see https://tailwindcss.com/docs/divide-opacity\n         */\n        \"divide-opacity\": [{\n          \"divide-opacity\": [opacity]\n        }],\n        /**\n         * Divide Style\n         * @see https://tailwindcss.com/docs/divide-style\n         */\n        \"divide-style\": [{\n          divide: getLineStyles()\n        }],\n        /**\n         * Border Color\n         * @see https://tailwindcss.com/docs/border-color\n         */\n        \"border-color\": [{\n          border: [borderColor]\n        }],\n        /**\n         * Border Color X\n         * @see https://tailwindcss.com/docs/border-color\n         */\n        \"border-color-x\": [{\n          \"border-x\": [borderColor]\n        }],\n        /**\n         * Border Color Y\n         * @see https://tailwindcss.com/docs/border-color\n         */\n        \"border-color-y\": [{\n          \"border-y\": [borderColor]\n        }],\n        /**\n         * Border Color S\n         * @see https://tailwindcss.com/docs/border-color\n         */\n        \"border-color-s\": [{\n          \"border-s\": [borderColor]\n        }],\n        /**\n         * Border Color E\n         * @see https://tailwindcss.com/docs/border-color\n         */\n        \"border-color-e\": [{\n          \"border-e\": [borderColor]\n        }],\n        /**\n         * Border Color Top\n         * @see https://tailwindcss.com/docs/border-color\n         */\n        \"border-color-t\": [{\n          \"border-t\": [borderColor]\n        }],\n        /**\n         * Border Color Right\n         * @see https://tailwindcss.com/docs/border-color\n         */\n        \"border-color-r\": [{\n          \"border-r\": [borderColor]\n        }],\n        /**\n         * Border Color Bottom\n         * @see https://tailwindcss.com/docs/border-color\n         */\n        \"border-color-b\": [{\n          \"border-b\": [borderColor]\n        }],\n        /**\n         * Border Color Left\n         * @see https://tailwindcss.com/docs/border-color\n         */\n        \"border-color-l\": [{\n          \"border-l\": [borderColor]\n        }],\n        /**\n         * Divide Color\n         * @see https://tailwindcss.com/docs/divide-color\n         */\n        \"divide-color\": [{\n          divide: [borderColor]\n        }],\n        /**\n         * Outline Style\n         * @see https://tailwindcss.com/docs/outline-style\n         */\n        \"outline-style\": [{\n          outline: [\"\", ...getLineStyles()]\n        }],\n        /**\n         * Outline Offset\n         * @see https://tailwindcss.com/docs/outline-offset\n         */\n        \"outline-offset\": [{\n          \"outline-offset\": [isLength, isArbitraryValue]\n        }],\n        /**\n         * Outline Width\n         * @see https://tailwindcss.com/docs/outline-width\n         */\n        \"outline-w\": [{\n          outline: [isLength, isArbitraryLength]\n        }],\n        /**\n         * Outline Color\n         * @see https://tailwindcss.com/docs/outline-color\n         */\n        \"outline-color\": [{\n          outline: [colors]\n        }],\n        /**\n         * Ring Width\n         * @see https://tailwindcss.com/docs/ring-width\n         */\n        \"ring-w\": [{\n          ring: getLengthWithEmptyAndArbitrary()\n        }],\n        /**\n         * Ring Width Inset\n         * @see https://tailwindcss.com/docs/ring-width\n         */\n        \"ring-w-inset\": [\"ring-inset\"],\n        /**\n         * Ring Color\n         * @see https://tailwindcss.com/docs/ring-color\n         */\n        \"ring-color\": [{\n          ring: [colors]\n        }],\n        /**\n         * Ring Opacity\n         * @see https://tailwindcss.com/docs/ring-opacity\n         */\n        \"ring-opacity\": [{\n          \"ring-opacity\": [opacity]\n        }],\n        /**\n         * Ring Offset Width\n         * @see https://tailwindcss.com/docs/ring-offset-width\n         */\n        \"ring-offset-w\": [{\n          \"ring-offset\": [isLength, isArbitraryLength]\n        }],\n        /**\n         * Ring Offset Color\n         * @see https://tailwindcss.com/docs/ring-offset-color\n         */\n        \"ring-offset-color\": [{\n          \"ring-offset\": [colors]\n        }],\n        // Effects\n        /**\n         * Box Shadow\n         * @see https://tailwindcss.com/docs/box-shadow\n         */\n        shadow: [{\n          shadow: [\"\", \"inner\", \"none\", isTshirtSize, isArbitraryShadow]\n        }],\n        /**\n         * Box Shadow Color\n         * @see https://tailwindcss.com/docs/box-shadow-color\n         */\n        \"shadow-color\": [{\n          shadow: [isAny]\n        }],\n        /**\n         * Opacity\n         * @see https://tailwindcss.com/docs/opacity\n         */\n        opacity: [{\n          opacity: [opacity]\n        }],\n        /**\n         * Mix Blend Mode\n         * @see https://tailwindcss.com/docs/mix-blend-mode\n         */\n        \"mix-blend\": [{\n          \"mix-blend\": [...getBlendModes(), \"plus-lighter\", \"plus-darker\"]\n        }],\n        /**\n         * Background Blend Mode\n         * @see https://tailwindcss.com/docs/background-blend-mode\n         */\n        \"bg-blend\": [{\n          \"bg-blend\": getBlendModes()\n        }],\n        // Filters\n        /**\n         * Filter\n         * @deprecated since Tailwind CSS v3.0.0\n         * @see https://tailwindcss.com/docs/filter\n         */\n        filter: [{\n          filter: [\"\", \"none\"]\n        }],\n        /**\n         * Blur\n         * @see https://tailwindcss.com/docs/blur\n         */\n        blur: [{\n          blur: [blur]\n        }],\n        /**\n         * Brightness\n         * @see https://tailwindcss.com/docs/brightness\n         */\n        brightness: [{\n          brightness: [brightness]\n        }],\n        /**\n         * Contrast\n         * @see https://tailwindcss.com/docs/contrast\n         */\n        contrast: [{\n          contrast: [contrast]\n        }],\n        /**\n         * Drop Shadow\n         * @see https://tailwindcss.com/docs/drop-shadow\n         */\n        \"drop-shadow\": [{\n          \"drop-shadow\": [\"\", \"none\", isTshirtSize, isArbitraryValue]\n        }],\n        /**\n         * Grayscale\n         * @see https://tailwindcss.com/docs/grayscale\n         */\n        grayscale: [{\n          grayscale: [grayscale]\n        }],\n        /**\n         * Hue Rotate\n         * @see https://tailwindcss.com/docs/hue-rotate\n         */\n        \"hue-rotate\": [{\n          \"hue-rotate\": [hueRotate]\n        }],\n        /**\n         * Invert\n         * @see https://tailwindcss.com/docs/invert\n         */\n        invert: [{\n          invert: [invert]\n        }],\n        /**\n         * Saturate\n         * @see https://tailwindcss.com/docs/saturate\n         */\n        saturate: [{\n          saturate: [saturate]\n        }],\n        /**\n         * Sepia\n         * @see https://tailwindcss.com/docs/sepia\n         */\n        sepia: [{\n          sepia: [sepia]\n        }],\n        /**\n         * Backdrop Filter\n         * @deprecated since Tailwind CSS v3.0.0\n         * @see https://tailwindcss.com/docs/backdrop-filter\n         */\n        \"backdrop-filter\": [{\n          \"backdrop-filter\": [\"\", \"none\"]\n        }],\n        /**\n         * Backdrop Blur\n         * @see https://tailwindcss.com/docs/backdrop-blur\n         */\n        \"backdrop-blur\": [{\n          \"backdrop-blur\": [blur]\n        }],\n        /**\n         * Backdrop Brightness\n         * @see https://tailwindcss.com/docs/backdrop-brightness\n         */\n        \"backdrop-brightness\": [{\n          \"backdrop-brightness\": [brightness]\n        }],\n        /**\n         * Backdrop Contrast\n         * @see https://tailwindcss.com/docs/backdrop-contrast\n         */\n        \"backdrop-contrast\": [{\n          \"backdrop-contrast\": [contrast]\n        }],\n        /**\n         * Backdrop Grayscale\n         * @see https://tailwindcss.com/docs/backdrop-grayscale\n         */\n        \"backdrop-grayscale\": [{\n          \"backdrop-grayscale\": [grayscale]\n        }],\n        /**\n         * Backdrop Hue Rotate\n         * @see https://tailwindcss.com/docs/backdrop-hue-rotate\n         */\n        \"backdrop-hue-rotate\": [{\n          \"backdrop-hue-rotate\": [hueRotate]\n        }],\n        /**\n         * Backdrop Invert\n         * @see https://tailwindcss.com/docs/backdrop-invert\n         */\n        \"backdrop-invert\": [{\n          \"backdrop-invert\": [invert]\n        }],\n        /**\n         * Backdrop Opacity\n         * @see https://tailwindcss.com/docs/backdrop-opacity\n         */\n        \"backdrop-opacity\": [{\n          \"backdrop-opacity\": [opacity]\n        }],\n        /**\n         * Backdrop Saturate\n         * @see https://tailwindcss.com/docs/backdrop-saturate\n         */\n        \"backdrop-saturate\": [{\n          \"backdrop-saturate\": [saturate]\n        }],\n        /**\n         * Backdrop Sepia\n         * @see https://tailwindcss.com/docs/backdrop-sepia\n         */\n        \"backdrop-sepia\": [{\n          \"backdrop-sepia\": [sepia]\n        }],\n        // Tables\n        /**\n         * Border Collapse\n         * @see https://tailwindcss.com/docs/border-collapse\n         */\n        \"border-collapse\": [{\n          border: [\"collapse\", \"separate\"]\n        }],\n        /**\n         * Border Spacing\n         * @see https://tailwindcss.com/docs/border-spacing\n         */\n        \"border-spacing\": [{\n          \"border-spacing\": [borderSpacing]\n        }],\n        /**\n         * Border Spacing X\n         * @see https://tailwindcss.com/docs/border-spacing\n         */\n        \"border-spacing-x\": [{\n          \"border-spacing-x\": [borderSpacing]\n        }],\n        /**\n         * Border Spacing Y\n         * @see https://tailwindcss.com/docs/border-spacing\n         */\n        \"border-spacing-y\": [{\n          \"border-spacing-y\": [borderSpacing]\n        }],\n        /**\n         * Table Layout\n         * @see https://tailwindcss.com/docs/table-layout\n         */\n        \"table-layout\": [{\n          table: [\"auto\", \"fixed\"]\n        }],\n        /**\n         * Caption Side\n         * @see https://tailwindcss.com/docs/caption-side\n         */\n        caption: [{\n          caption: [\"top\", \"bottom\"]\n        }],\n        // Transitions and Animation\n        /**\n         * Tranisition Property\n         * @see https://tailwindcss.com/docs/transition-property\n         */\n        transition: [{\n          transition: [\"none\", \"all\", \"\", \"colors\", \"opacity\", \"shadow\", \"transform\", isArbitraryValue]\n        }],\n        /**\n         * Transition Duration\n         * @see https://tailwindcss.com/docs/transition-duration\n         */\n        duration: [{\n          duration: getNumberAndArbitrary()\n        }],\n        /**\n         * Transition Timing Function\n         * @see https://tailwindcss.com/docs/transition-timing-function\n         */\n        ease: [{\n          ease: [\"linear\", \"in\", \"out\", \"in-out\", isArbitraryValue]\n        }],\n        /**\n         * Transition Delay\n         * @see https://tailwindcss.com/docs/transition-delay\n         */\n        delay: [{\n          delay: getNumberAndArbitrary()\n        }],\n        /**\n         * Animation\n         * @see https://tailwindcss.com/docs/animation\n         */\n        animate: [{\n          animate: [\"none\", \"spin\", \"ping\", \"pulse\", \"bounce\", isArbitraryValue]\n        }],\n        // Transforms\n        /**\n         * Transform\n         * @see https://tailwindcss.com/docs/transform\n         */\n        transform: [{\n          transform: [\"\", \"gpu\", \"none\"]\n        }],\n        /**\n         * Scale\n         * @see https://tailwindcss.com/docs/scale\n         */\n        scale: [{\n          scale: [scale]\n        }],\n        /**\n         * Scale X\n         * @see https://tailwindcss.com/docs/scale\n         */\n        \"scale-x\": [{\n          \"scale-x\": [scale]\n        }],\n        /**\n         * Scale Y\n         * @see https://tailwindcss.com/docs/scale\n         */\n        \"scale-y\": [{\n          \"scale-y\": [scale]\n        }],\n        /**\n         * Rotate\n         * @see https://tailwindcss.com/docs/rotate\n         */\n        rotate: [{\n          rotate: [isInteger, isArbitraryValue]\n        }],\n        /**\n         * Translate X\n         * @see https://tailwindcss.com/docs/translate\n         */\n        \"translate-x\": [{\n          \"translate-x\": [translate]\n        }],\n        /**\n         * Translate Y\n         * @see https://tailwindcss.com/docs/translate\n         */\n        \"translate-y\": [{\n          \"translate-y\": [translate]\n        }],\n        /**\n         * Skew X\n         * @see https://tailwindcss.com/docs/skew\n         */\n        \"skew-x\": [{\n          \"skew-x\": [skew]\n        }],\n        /**\n         * Skew Y\n         * @see https://tailwindcss.com/docs/skew\n         */\n        \"skew-y\": [{\n          \"skew-y\": [skew]\n        }],\n        /**\n         * Transform Origin\n         * @see https://tailwindcss.com/docs/transform-origin\n         */\n        \"transform-origin\": [{\n          origin: [\"center\", \"top\", \"top-right\", \"right\", \"bottom-right\", \"bottom\", \"bottom-left\", \"left\", \"top-left\", isArbitraryValue]\n        }],\n        // Interactivity\n        /**\n         * Accent Color\n         * @see https://tailwindcss.com/docs/accent-color\n         */\n        accent: [{\n          accent: [\"auto\", colors]\n        }],\n        /**\n         * Appearance\n         * @see https://tailwindcss.com/docs/appearance\n         */\n        appearance: [{\n          appearance: [\"none\", \"auto\"]\n        }],\n        /**\n         * Cursor\n         * @see https://tailwindcss.com/docs/cursor\n         */\n        cursor: [{\n          cursor: [\"auto\", \"default\", \"pointer\", \"wait\", \"text\", \"move\", \"help\", \"not-allowed\", \"none\", \"context-menu\", \"progress\", \"cell\", \"crosshair\", \"vertical-text\", \"alias\", \"copy\", \"no-drop\", \"grab\", \"grabbing\", \"all-scroll\", \"col-resize\", \"row-resize\", \"n-resize\", \"e-resize\", \"s-resize\", \"w-resize\", \"ne-resize\", \"nw-resize\", \"se-resize\", \"sw-resize\", \"ew-resize\", \"ns-resize\", \"nesw-resize\", \"nwse-resize\", \"zoom-in\", \"zoom-out\", isArbitraryValue]\n        }],\n        /**\n         * Caret Color\n         * @see https://tailwindcss.com/docs/just-in-time-mode#caret-color-utilities\n         */\n        \"caret-color\": [{\n          caret: [colors]\n        }],\n        /**\n         * Pointer Events\n         * @see https://tailwindcss.com/docs/pointer-events\n         */\n        \"pointer-events\": [{\n          \"pointer-events\": [\"none\", \"auto\"]\n        }],\n        /**\n         * Resize\n         * @see https://tailwindcss.com/docs/resize\n         */\n        resize: [{\n          resize: [\"none\", \"y\", \"x\", \"\"]\n        }],\n        /**\n         * Scroll Behavior\n         * @see https://tailwindcss.com/docs/scroll-behavior\n         */\n        \"scroll-behavior\": [{\n          scroll: [\"auto\", \"smooth\"]\n        }],\n        /**\n         * Scroll Margin\n         * @see https://tailwindcss.com/docs/scroll-margin\n         */\n        \"scroll-m\": [{\n          \"scroll-m\": getSpacingWithArbitrary()\n        }],\n        /**\n         * Scroll Margin X\n         * @see https://tailwindcss.com/docs/scroll-margin\n         */\n        \"scroll-mx\": [{\n          \"scroll-mx\": getSpacingWithArbitrary()\n        }],\n        /**\n         * Scroll Margin Y\n         * @see https://tailwindcss.com/docs/scroll-margin\n         */\n        \"scroll-my\": [{\n          \"scroll-my\": getSpacingWithArbitrary()\n        }],\n        /**\n         * Scroll Margin Start\n         * @see https://tailwindcss.com/docs/scroll-margin\n         */\n        \"scroll-ms\": [{\n          \"scroll-ms\": getSpacingWithArbitrary()\n        }],\n        /**\n         * Scroll Margin End\n         * @see https://tailwindcss.com/docs/scroll-margin\n         */\n        \"scroll-me\": [{\n          \"scroll-me\": getSpacingWithArbitrary()\n        }],\n        /**\n         * Scroll Margin Top\n         * @see https://tailwindcss.com/docs/scroll-margin\n         */\n        \"scroll-mt\": [{\n          \"scroll-mt\": getSpacingWithArbitrary()\n        }],\n        /**\n         * Scroll Margin Right\n         * @see https://tailwindcss.com/docs/scroll-margin\n         */\n        \"scroll-mr\": [{\n          \"scroll-mr\": getSpacingWithArbitrary()\n        }],\n        /**\n         * Scroll Margin Bottom\n         * @see https://tailwindcss.com/docs/scroll-margin\n         */\n        \"scroll-mb\": [{\n          \"scroll-mb\": getSpacingWithArbitrary()\n        }],\n        /**\n         * Scroll Margin Left\n         * @see https://tailwindcss.com/docs/scroll-margin\n         */\n        \"scroll-ml\": [{\n          \"scroll-ml\": getSpacingWithArbitrary()\n        }],\n        /**\n         * Scroll Padding\n         * @see https://tailwindcss.com/docs/scroll-padding\n         */\n        \"scroll-p\": [{\n          \"scroll-p\": getSpacingWithArbitrary()\n        }],\n        /**\n         * Scroll Padding X\n         * @see https://tailwindcss.com/docs/scroll-padding\n         */\n        \"scroll-px\": [{\n          \"scroll-px\": getSpacingWithArbitrary()\n        }],\n        /**\n         * Scroll Padding Y\n         * @see https://tailwindcss.com/docs/scroll-padding\n         */\n        \"scroll-py\": [{\n          \"scroll-py\": getSpacingWithArbitrary()\n        }],\n        /**\n         * Scroll Padding Start\n         * @see https://tailwindcss.com/docs/scroll-padding\n         */\n        \"scroll-ps\": [{\n          \"scroll-ps\": getSpacingWithArbitrary()\n        }],\n        /**\n         * Scroll Padding End\n         * @see https://tailwindcss.com/docs/scroll-padding\n         */\n        \"scroll-pe\": [{\n          \"scroll-pe\": getSpacingWithArbitrary()\n        }],\n        /**\n         * Scroll Padding Top\n         * @see https://tailwindcss.com/docs/scroll-padding\n         */\n        \"scroll-pt\": [{\n          \"scroll-pt\": getSpacingWithArbitrary()\n        }],\n        /**\n         * Scroll Padding Right\n         * @see https://tailwindcss.com/docs/scroll-padding\n         */\n        \"scroll-pr\": [{\n          \"scroll-pr\": getSpacingWithArbitrary()\n        }],\n        /**\n         * Scroll Padding Bottom\n         * @see https://tailwindcss.com/docs/scroll-padding\n         */\n        \"scroll-pb\": [{\n          \"scroll-pb\": getSpacingWithArbitrary()\n        }],\n        /**\n         * Scroll Padding Left\n         * @see https://tailwindcss.com/docs/scroll-padding\n         */\n        \"scroll-pl\": [{\n          \"scroll-pl\": getSpacingWithArbitrary()\n        }],\n        /**\n         * Scroll Snap Align\n         * @see https://tailwindcss.com/docs/scroll-snap-align\n         */\n        \"snap-align\": [{\n          snap: [\"start\", \"end\", \"center\", \"align-none\"]\n        }],\n        /**\n         * Scroll Snap Stop\n         * @see https://tailwindcss.com/docs/scroll-snap-stop\n         */\n        \"snap-stop\": [{\n          snap: [\"normal\", \"always\"]\n        }],\n        /**\n         * Scroll Snap Type\n         * @see https://tailwindcss.com/docs/scroll-snap-type\n         */\n        \"snap-type\": [{\n          snap: [\"none\", \"x\", \"y\", \"both\"]\n        }],\n        /**\n         * Scroll Snap Type Strictness\n         * @see https://tailwindcss.com/docs/scroll-snap-type\n         */\n        \"snap-strictness\": [{\n          snap: [\"mandatory\", \"proximity\"]\n        }],\n        /**\n         * Touch Action\n         * @see https://tailwindcss.com/docs/touch-action\n         */\n        touch: [{\n          touch: [\"auto\", \"none\", \"manipulation\"]\n        }],\n        /**\n         * Touch Action X\n         * @see https://tailwindcss.com/docs/touch-action\n         */\n        \"touch-x\": [{\n          \"touch-pan\": [\"x\", \"left\", \"right\"]\n        }],\n        /**\n         * Touch Action Y\n         * @see https://tailwindcss.com/docs/touch-action\n         */\n        \"touch-y\": [{\n          \"touch-pan\": [\"y\", \"up\", \"down\"]\n        }],\n        /**\n         * Touch Action Pinch Zoom\n         * @see https://tailwindcss.com/docs/touch-action\n         */\n        \"touch-pz\": [\"touch-pinch-zoom\"],\n        /**\n         * User Select\n         * @see https://tailwindcss.com/docs/user-select\n         */\n        select: [{\n          select: [\"none\", \"text\", \"all\", \"auto\"]\n        }],\n        /**\n         * Will Change\n         * @see https://tailwindcss.com/docs/will-change\n         */\n        \"will-change\": [{\n          \"will-change\": [\"auto\", \"scroll\", \"contents\", \"transform\", isArbitraryValue]\n        }],\n        // SVG\n        /**\n         * Fill\n         * @see https://tailwindcss.com/docs/fill\n         */\n        fill: [{\n          fill: [colors, \"none\"]\n        }],\n        /**\n         * Stroke Width\n         * @see https://tailwindcss.com/docs/stroke-width\n         */\n        \"stroke-w\": [{\n          stroke: [isLength, isArbitraryLength, isArbitraryNumber]\n        }],\n        /**\n         * Stroke\n         * @see https://tailwindcss.com/docs/stroke\n         */\n        stroke: [{\n          stroke: [colors, \"none\"]\n        }],\n        // Accessibility\n        /**\n         * Screen Readers\n         * @see https://tailwindcss.com/docs/screen-readers\n         */\n        sr: [\"sr-only\", \"not-sr-only\"],\n        /**\n         * Forced Color Adjust\n         * @see https://tailwindcss.com/docs/forced-color-adjust\n         */\n        \"forced-color-adjust\": [{\n          \"forced-color-adjust\": [\"auto\", \"none\"]\n        }]\n      },\n      conflictingClassGroups: {\n        overflow: [\"overflow-x\", \"overflow-y\"],\n        overscroll: [\"overscroll-x\", \"overscroll-y\"],\n        inset: [\"inset-x\", \"inset-y\", \"start\", \"end\", \"top\", \"right\", \"bottom\", \"left\"],\n        \"inset-x\": [\"right\", \"left\"],\n        \"inset-y\": [\"top\", \"bottom\"],\n        flex: [\"basis\", \"grow\", \"shrink\"],\n        gap: [\"gap-x\", \"gap-y\"],\n        p: [\"px\", \"py\", \"ps\", \"pe\", \"pt\", \"pr\", \"pb\", \"pl\"],\n        px: [\"pr\", \"pl\"],\n        py: [\"pt\", \"pb\"],\n        m: [\"mx\", \"my\", \"ms\", \"me\", \"mt\", \"mr\", \"mb\", \"ml\"],\n        mx: [\"mr\", \"ml\"],\n        my: [\"mt\", \"mb\"],\n        size: [\"w\", \"h\"],\n        \"font-size\": [\"leading\"],\n        \"fvn-normal\": [\"fvn-ordinal\", \"fvn-slashed-zero\", \"fvn-figure\", \"fvn-spacing\", \"fvn-fraction\"],\n        \"fvn-ordinal\": [\"fvn-normal\"],\n        \"fvn-slashed-zero\": [\"fvn-normal\"],\n        \"fvn-figure\": [\"fvn-normal\"],\n        \"fvn-spacing\": [\"fvn-normal\"],\n        \"fvn-fraction\": [\"fvn-normal\"],\n        \"line-clamp\": [\"display\", \"overflow\"],\n        rounded: [\"rounded-s\", \"rounded-e\", \"rounded-t\", \"rounded-r\", \"rounded-b\", \"rounded-l\", \"rounded-ss\", \"rounded-se\", \"rounded-ee\", \"rounded-es\", \"rounded-tl\", \"rounded-tr\", \"rounded-br\", \"rounded-bl\"],\n        \"rounded-s\": [\"rounded-ss\", \"rounded-es\"],\n        \"rounded-e\": [\"rounded-se\", \"rounded-ee\"],\n        \"rounded-t\": [\"rounded-tl\", \"rounded-tr\"],\n        \"rounded-r\": [\"rounded-tr\", \"rounded-br\"],\n        \"rounded-b\": [\"rounded-br\", \"rounded-bl\"],\n        \"rounded-l\": [\"rounded-tl\", \"rounded-bl\"],\n        \"border-spacing\": [\"border-spacing-x\", \"border-spacing-y\"],\n        \"border-w\": [\"border-w-s\", \"border-w-e\", \"border-w-t\", \"border-w-r\", \"border-w-b\", \"border-w-l\"],\n        \"border-w-x\": [\"border-w-r\", \"border-w-l\"],\n        \"border-w-y\": [\"border-w-t\", \"border-w-b\"],\n        \"border-color\": [\"border-color-s\", \"border-color-e\", \"border-color-t\", \"border-color-r\", \"border-color-b\", \"border-color-l\"],\n        \"border-color-x\": [\"border-color-r\", \"border-color-l\"],\n        \"border-color-y\": [\"border-color-t\", \"border-color-b\"],\n        \"scroll-m\": [\"scroll-mx\", \"scroll-my\", \"scroll-ms\", \"scroll-me\", \"scroll-mt\", \"scroll-mr\", \"scroll-mb\", \"scroll-ml\"],\n        \"scroll-mx\": [\"scroll-mr\", \"scroll-ml\"],\n        \"scroll-my\": [\"scroll-mt\", \"scroll-mb\"],\n        \"scroll-p\": [\"scroll-px\", \"scroll-py\", \"scroll-ps\", \"scroll-pe\", \"scroll-pt\", \"scroll-pr\", \"scroll-pb\", \"scroll-pl\"],\n        \"scroll-px\": [\"scroll-pr\", \"scroll-pl\"],\n        \"scroll-py\": [\"scroll-pt\", \"scroll-pb\"],\n        touch: [\"touch-x\", \"touch-y\", \"touch-pz\"],\n        \"touch-x\": [\"touch\"],\n        \"touch-y\": [\"touch\"],\n        \"touch-pz\": [\"touch\"]\n      },\n      conflictingClassGroupModifiers: {\n        \"font-size\": [\"leading\"]\n      }\n    };\n  };\n  var twMerge = /* @__PURE__ */ createTailwindMerge(getDefaultConfig);\n\n  // src/web/utils/helpers.ts\n  var cn2 = (...inputs) => {\n    return twMerge(clsx(inputs));\n  };\n  typeof navigator !== \"undefined\" && navigator.userAgent.includes(\"Firefox\");\n  var throttle = (callback, delay) => {\n    let lastCall = 0;\n    return (e4) => {\n      const now = Date.now();\n      if (now - lastCall >= delay) {\n        lastCall = now;\n        return callback(e4);\n      }\n      return void 0;\n    };\n  };\n  var readLocalStorage = (storageKey) => {\n    if (!IS_CLIENT) return null;\n    try {\n      const stored = localStorage.getItem(storageKey);\n      return stored ? JSON.parse(stored) : null;\n    } catch {\n      return null;\n    }\n  };\n  var saveLocalStorage = (storageKey, state) => {\n    if (!IS_CLIENT) return;\n    try {\n      window.localStorage.setItem(storageKey, JSON.stringify(state));\n    } catch {\n    }\n  };\n  var removeLocalStorage = (storageKey) => {\n    if (!IS_CLIENT) return;\n    try {\n      window.localStorage.removeItem(storageKey);\n    } catch {\n    }\n  };\n  var LazyComponentTag2 = 24;\n  var ProfilerTag = 12;\n  var getExtendedDisplayName = (fiber) => {\n    if (!fiber) {\n      return {\n        name: \"Unknown\",\n        wrappers: [],\n        wrapperTypes: []\n      };\n    }\n    const { tag, type, elementType } = fiber;\n    let name = getDisplayName(type);\n    const wrappers = [];\n    const wrapperTypes = [];\n    if (hasMemoCache(fiber) || tag === SimpleMemoComponentTag || tag === MemoComponentTag || type?.$$typeof === Symbol.for(\"react.memo\") || elementType?.$$typeof === Symbol.for(\"react.memo\")) {\n      const compiler = hasMemoCache(fiber);\n      wrapperTypes.push({\n        type: \"memo\",\n        title: compiler ? \"This component has been auto-memoized by the React Compiler.\" : \"Memoized component that skips re-renders if props are the same\",\n        compiler\n      });\n    }\n    if (tag === LazyComponentTag2) {\n      wrapperTypes.push({\n        type: \"lazy\",\n        title: \"Lazily loaded component that supports code splitting\"\n      });\n    }\n    if (tag === SuspenseComponentTag) {\n      wrapperTypes.push({\n        type: \"suspense\",\n        title: \"Component that can suspend while content is loading\"\n      });\n    }\n    if (tag === ProfilerTag) {\n      wrapperTypes.push({\n        type: \"profiler\",\n        title: \"Component that measures rendering performance\"\n      });\n    }\n    if (typeof name === \"string\") {\n      const wrapperRegex = /^(\\w+)\\((.*)\\)$/;\n      let currentName = name;\n      while (wrapperRegex.test(currentName)) {\n        const match = currentName.match(wrapperRegex);\n        if (match?.[1] && match?.[2]) {\n          wrappers.unshift(match[1]);\n          currentName = match[2];\n        } else {\n          break;\n        }\n      }\n      name = currentName;\n    }\n    return {\n      name: name || \"Unknown\",\n      wrappers,\n      wrapperTypes\n    };\n  };\n\n  // src/web/state.ts\n  var signalIsSettingsOpen = /* @__PURE__ */ d3(false);\n  var signalRefWidget = /* @__PURE__ */ d3(\n    null\n  );\n  var defaultWidgetConfig = {\n    corner: \"bottom-right\",\n    dimensions: {\n      isFullWidth: false,\n      isFullHeight: false,\n      width: MIN_SIZE.width,\n      height: MIN_SIZE.height,\n      position: { x: SAFE_AREA, y: SAFE_AREA }\n    },\n    lastDimensions: {\n      isFullWidth: false,\n      isFullHeight: false,\n      width: MIN_SIZE.width,\n      height: MIN_SIZE.height,\n      position: { x: SAFE_AREA, y: SAFE_AREA }\n    },\n    componentsTree: {\n      width: MIN_CONTAINER_WIDTH\n    }\n  };\n  var getInitialWidgetConfig = () => {\n    const stored = readLocalStorage(LOCALSTORAGE_KEY);\n    if (!stored) {\n      saveLocalStorage(LOCALSTORAGE_KEY, {\n        corner: defaultWidgetConfig.corner,\n        dimensions: defaultWidgetConfig.dimensions,\n        lastDimensions: defaultWidgetConfig.lastDimensions,\n        componentsTree: defaultWidgetConfig.componentsTree\n      });\n      return defaultWidgetConfig;\n    }\n    return {\n      corner: stored.corner ?? defaultWidgetConfig.corner,\n      dimensions: stored.dimensions ?? defaultWidgetConfig.dimensions,\n      lastDimensions: stored.lastDimensions ?? stored.dimensions ?? defaultWidgetConfig.lastDimensions,\n      componentsTree: stored.componentsTree ?? defaultWidgetConfig.componentsTree\n    };\n  };\n  var signalWidget = d3(getInitialWidgetConfig());\n  var updateDimensions = () => {\n    if (!IS_CLIENT) return;\n    const { dimensions } = signalWidget.value;\n    const { width, height, position } = dimensions;\n    signalWidget.value = {\n      ...signalWidget.value,\n      dimensions: {\n        isFullWidth: width >= window.innerWidth - SAFE_AREA * 2,\n        isFullHeight: height >= window.innerHeight - SAFE_AREA * 2,\n        width,\n        height,\n        position\n      }\n    };\n  };\n  var signalWidgetViews = d3({\n    view: \"none\"\n  });\n  var storedCollapsed = readLocalStorage(\n    LOCALSTORAGE_COLLAPSED_KEY\n  );\n  var signalWidgetCollapsed = /* @__PURE__ */ d3(storedCollapsed ?? null);\n\n  // src/web/utils/preact/constant.ts\n  function CONSTANT_UPDATE() {\n    return false;\n  }\n  function constant(Component) {\n    function Memoed(props) {\n      this.shouldComponentUpdate = CONSTANT_UPDATE;\n      return _(Component, props);\n    }\n    Memoed.displayName = `Memo(${Component.displayName || Component.name})`;\n    Memoed.prototype.isReactComponent = true;\n    Memoed._forwarded = true;\n    return Memoed;\n  }\n\n  // src/web/hooks/use-virtual-list.ts\n  var useVirtualList = (options) => {\n    const { count, getScrollElement, estimateSize, overscan = 5 } = options;\n    const [scrollTop, setScrollTop] = d2(0);\n    const [containerHeight, setContainerHeight] = d2(0);\n    const refResizeObserver = A2();\n    const refScrollElement = A2(null);\n    const refRafId = A2(null);\n    const itemHeight = estimateSize();\n    const updateContainer = q2((entries) => {\n      if (!refScrollElement.current) return;\n      const height = entries?.[0]?.contentRect.height ?? refScrollElement.current.getBoundingClientRect().height;\n      setContainerHeight(height);\n    }, []);\n    const debouncedUpdateContainer = q2(() => {\n      if (refRafId.current !== null) {\n        cancelAnimationFrame(refRafId.current);\n      }\n      refRafId.current = requestAnimationFrame(() => {\n        updateContainer();\n        refRafId.current = null;\n      });\n    }, [updateContainer]);\n    y2(() => {\n      const element = getScrollElement();\n      if (!element) return;\n      refScrollElement.current = element;\n      const handleScroll = () => {\n        if (!refScrollElement.current) return;\n        setScrollTop(refScrollElement.current.scrollTop);\n      };\n      updateContainer();\n      if (!refResizeObserver.current) {\n        refResizeObserver.current = new ResizeObserver(() => {\n          debouncedUpdateContainer();\n        });\n      }\n      refResizeObserver.current.observe(element);\n      element.addEventListener(\"scroll\", handleScroll, { passive: true });\n      const mutationObserver = new MutationObserver(debouncedUpdateContainer);\n      mutationObserver.observe(element, {\n        attributes: true,\n        childList: true,\n        subtree: true\n      });\n      return () => {\n        element.removeEventListener(\"scroll\", handleScroll);\n        if (refResizeObserver.current) {\n          refResizeObserver.current.disconnect();\n        }\n        mutationObserver.disconnect();\n        if (refRafId.current !== null) {\n          cancelAnimationFrame(refRafId.current);\n        }\n      };\n    }, [getScrollElement, updateContainer, debouncedUpdateContainer]);\n    const visibleRange = T2(() => {\n      const start2 = Math.floor(scrollTop / itemHeight);\n      const visibleCount = Math.ceil(containerHeight / itemHeight);\n      return {\n        start: Math.max(0, start2 - overscan),\n        end: Math.min(count, start2 + visibleCount + overscan)\n      };\n    }, [scrollTop, itemHeight, containerHeight, count, overscan]);\n    const items = T2(() => {\n      const virtualItems = [];\n      for (let index = visibleRange.start; index < visibleRange.end; index++) {\n        virtualItems.push({\n          key: index,\n          index,\n          start: index * itemHeight\n        });\n      }\n      return virtualItems;\n    }, [visibleRange, itemHeight]);\n    return {\n      virtualItems: items,\n      totalSize: count * itemHeight,\n      scrollTop,\n      containerHeight\n    };\n  };\n\n  // src/web/utils/pin.ts\n  var getFiberPath = (fiber) => {\n    const pathSegments = [];\n    let currentFiber = fiber;\n    while (currentFiber) {\n      const elementType = currentFiber.elementType;\n      const name = typeof elementType === \"function\" ? elementType.displayName || elementType.name : typeof elementType === \"string\" ? elementType : \"Unknown\";\n      const index = currentFiber.index !== void 0 ? `[${currentFiber.index}]` : \"\";\n      pathSegments.unshift(`${name}${index}`);\n      currentFiber = currentFiber.return ?? null;\n    }\n    return pathSegments.join(\"::\");\n  };\n\n  // src/web/views/inspector/flash-overlay.ts\n  var fadeOutTimers = /* @__PURE__ */ new WeakMap();\n  var trackElementPosition = (element, callback) => {\n    const handleScroll = callback.bind(null, element);\n    document.addEventListener(\"scroll\", handleScroll, {\n      passive: true,\n      capture: true\n    });\n    return () => {\n      document.removeEventListener(\"scroll\", handleScroll, { capture: true });\n    };\n  };\n  var flashManager = {\n    activeFlashes: /* @__PURE__ */ new Map(),\n    create(container) {\n      const existingOverlay = container.querySelector(\n        \".react-scan-flash-overlay\"\n      );\n      const overlay = existingOverlay instanceof HTMLElement ? existingOverlay : (() => {\n        const newOverlay = document.createElement(\"div\");\n        newOverlay.className = \"react-scan-flash-overlay\";\n        container.appendChild(newOverlay);\n        const scrollCleanup = trackElementPosition(container, () => {\n          if (container.querySelector(\".react-scan-flash-overlay\")) {\n            this.create(container);\n          }\n        });\n        this.activeFlashes.set(container, {\n          element: container,\n          overlay: newOverlay,\n          scrollCleanup\n        });\n        return newOverlay;\n      })();\n      const existingTimer = fadeOutTimers.get(overlay);\n      if (existingTimer) {\n        clearTimeout(existingTimer);\n        fadeOutTimers.delete(overlay);\n      }\n      requestAnimationFrame(() => {\n        overlay.style.transition = \"none\";\n        overlay.style.opacity = \"0.9\";\n        const timerId = setTimeout(() => {\n          overlay.style.transition = \"opacity 150ms ease-out\";\n          overlay.style.opacity = \"0\";\n          const cleanupTimer = setTimeout(() => {\n            if (overlay.parentNode) {\n              overlay.parentNode.removeChild(overlay);\n            }\n            const entry = this.activeFlashes.get(container);\n            if (entry?.scrollCleanup) {\n              entry.scrollCleanup();\n            }\n            this.activeFlashes.delete(container);\n            fadeOutTimers.delete(overlay);\n          }, 150);\n          fadeOutTimers.set(overlay, cleanupTimer);\n        }, 300);\n        fadeOutTimers.set(overlay, timerId);\n      });\n    },\n    cleanup(container) {\n      const entry = this.activeFlashes.get(container);\n      if (entry) {\n        const existingTimer = fadeOutTimers.get(entry.overlay);\n        if (existingTimer) {\n          clearTimeout(existingTimer);\n          fadeOutTimers.delete(entry.overlay);\n        }\n        if (entry.overlay.parentNode) {\n          entry.overlay.parentNode.removeChild(entry.overlay);\n        }\n        if (entry.scrollCleanup) {\n          entry.scrollCleanup();\n        }\n        this.activeFlashes.delete(container);\n      }\n    },\n    cleanupAll() {\n      for (const [, entry] of this.activeFlashes) {\n        this.cleanup(entry.element);\n      }\n    }\n  };\n\n  // src/web/views/inspector/states.ts\n  var TIMELINE_MAX_UPDATES = 1e3;\n  var timelineStateDefault = {\n    updates: [],\n    currentFiber: null,\n    totalUpdates: 0,\n    windowOffset: 0,\n    currentIndex: 0,\n    isViewingHistory: false,\n    latestFiber: null,\n    isVisible: false,\n    playbackSpeed: 1\n  };\n  var timelineState = d3(timelineStateDefault);\n  var inspectorUpdateSignal = d3(0);\n  var pendingUpdates = [];\n  var batchTimeout = null;\n  var batchUpdates = () => {\n    if (pendingUpdates.length === 0) return;\n    const batchedUpdates = [...pendingUpdates];\n    const { updates, totalUpdates, currentIndex, isViewingHistory } = timelineState.value;\n    const newUpdates = [...updates];\n    let newTotalUpdates = totalUpdates;\n    for (const { update } of batchedUpdates) {\n      if (newUpdates.length >= TIMELINE_MAX_UPDATES) {\n        newUpdates.shift();\n      }\n      newUpdates.push(update);\n      newTotalUpdates++;\n    }\n    const newWindowOffset = Math.max(0, newTotalUpdates - TIMELINE_MAX_UPDATES);\n    let newCurrentIndex;\n    if (isViewingHistory) {\n      if (currentIndex === totalUpdates - 1) {\n        newCurrentIndex = newUpdates.length - 1;\n      } else if (currentIndex === 0) {\n        newCurrentIndex = 0;\n      } else {\n        if (newWindowOffset === 0) {\n          newCurrentIndex = currentIndex;\n        } else {\n          newCurrentIndex = currentIndex - 1;\n        }\n      }\n    } else {\n      newCurrentIndex = newUpdates.length - 1;\n    }\n    const lastUpdate = batchedUpdates[batchedUpdates.length - 1];\n    timelineState.value = {\n      ...timelineState.value,\n      latestFiber: lastUpdate.fiber,\n      updates: newUpdates,\n      totalUpdates: newTotalUpdates,\n      windowOffset: newWindowOffset,\n      currentIndex: newCurrentIndex,\n      isViewingHistory\n    };\n    pendingUpdates = pendingUpdates.slice(batchedUpdates.length);\n  };\n  var timelineActions = {\n    showTimeline: () => {\n      timelineState.value = {\n        ...timelineState.value,\n        isVisible: true\n      };\n    },\n    hideTimeline: () => {\n      timelineState.value = {\n        ...timelineState.value,\n        isVisible: false,\n        currentIndex: timelineState.value.updates.length - 1\n      };\n    },\n    updateFrame: (index, isViewingHistory) => {\n      timelineState.value = {\n        ...timelineState.value,\n        currentIndex: index,\n        isViewingHistory\n      };\n    },\n    updatePlaybackSpeed: (speed) => {\n      timelineState.value = {\n        ...timelineState.value,\n        playbackSpeed: speed\n      };\n    },\n    addUpdate: (update, latestFiber) => {\n      pendingUpdates.push({ update, fiber: latestFiber });\n      if (!batchTimeout) {\n        const processBatch = () => {\n          batchUpdates();\n          batchTimeout = null;\n          if (pendingUpdates.length > 0) {\n            batchTimeout = setTimeout(processBatch, 96);\n          }\n        };\n        batchTimeout = setTimeout(processBatch, 96);\n      }\n    },\n    reset: () => {\n      if (batchTimeout) {\n        clearTimeout(batchTimeout);\n        batchTimeout = null;\n      }\n      pendingUpdates = [];\n      timelineState.value = timelineStateDefault;\n    }\n  };\n\n  // src/web/views/inspector/components-tree/state.ts\n  var searchState = d3({\n    query: \"\",\n    matches: [],\n    currentMatchIndex: -1\n  });\n  var signalSkipTreeUpdate = /* @__PURE__ */ d3(false);\n\n  // src/web/views/inspector/components-tree/index.tsx\n  var flattenTree = (nodes, depth = 0, parentPath = null) => {\n    return nodes.reduce((acc, node, index) => {\n      const nodePath = node.element ? getFiberPath(node.fiber) : `${parentPath}-${index}`;\n      const renderData = node.fiber?.type ? getRenderData(node.fiber) : void 0;\n      const flatNode = {\n        ...node,\n        depth,\n        nodeId: nodePath,\n        parentId: parentPath,\n        fiber: node.fiber,\n        renderData\n      };\n      acc.push(flatNode);\n      if (node.children?.length) {\n        acc.push(...flattenTree(node.children, depth + 1, nodePath));\n      }\n      return acc;\n    }, []);\n  };\n  var getMaxDepth = (nodes) => {\n    return nodes.reduce((max, node) => Math.max(max, node.depth), 0);\n  };\n  var calculateIndentSize = (containerWidth, maxDepth) => {\n    const MIN_INDENT = 0;\n    const MAX_INDENT = 24;\n    const MIN_TOTAL_INDENT = 24;\n    if (maxDepth <= 0) return MAX_INDENT;\n    const availableSpace = Math.max(0, containerWidth - MIN_CONTAINER_WIDTH);\n    if (availableSpace < MIN_TOTAL_INDENT) return MIN_INDENT;\n    const targetTotalIndent = Math.min(\n      availableSpace * 0.3,\n      maxDepth * MAX_INDENT\n    );\n    const baseIndent = targetTotalIndent / maxDepth;\n    return Math.max(MIN_INDENT, Math.min(MAX_INDENT, baseIndent));\n  };\n  var VALID_TYPES = [\"memo\", \"forwardRef\", \"lazy\", \"suspense\"];\n  var parseTypeSearch = (query) => {\n    const typeMatch = query.match(/\\[(.*?)\\]/);\n    if (!typeMatch) return null;\n    const typeSearches = [];\n    const parts = typeMatch[1].split(\",\");\n    for (const part of parts) {\n      const trimmed = part.trim().toLowerCase();\n      if (trimmed) typeSearches.push(trimmed);\n    }\n    return typeSearches;\n  };\n  var isValidTypeSearch = (typeSearches) => {\n    if (typeSearches.length === 0) return false;\n    for (const search of typeSearches) {\n      let isValid = false;\n      for (const validType of VALID_TYPES) {\n        if (validType.toLowerCase().includes(search)) {\n          isValid = true;\n          break;\n        }\n      }\n      if (!isValid) return false;\n    }\n    return true;\n  };\n  var matchesTypeSearch = (typeSearches, wrapperTypes) => {\n    if (typeSearches.length === 0) return true;\n    if (!wrapperTypes.length) return false;\n    for (const search of typeSearches) {\n      let foundMatch = false;\n      for (const wrapper of wrapperTypes) {\n        if (wrapper.type.toLowerCase().includes(search)) {\n          foundMatch = true;\n          break;\n        }\n      }\n      if (!foundMatch) return false;\n    }\n    return true;\n  };\n  var useNodeHighlighting = (node, searchValue) => {\n    return T2(() => {\n      const { query, matches } = searchValue;\n      const isMatch = matches.some((match) => match.nodeId === node.nodeId);\n      const typeSearches = parseTypeSearch(query) || [];\n      const searchQuery = query ? query.replace(/\\[.*?\\]/, \"\").trim() : \"\";\n      if (!query || !isMatch) {\n        return {\n          highlightedText: /* @__PURE__ */ u4(\"span\", { className: \"truncate\", children: node.label }),\n          typeHighlight: false\n        };\n      }\n      let matchesType = true;\n      if (typeSearches.length > 0) {\n        if (!node.fiber) {\n          matchesType = false;\n        } else {\n          const { wrapperTypes } = getExtendedDisplayName(node.fiber);\n          matchesType = matchesTypeSearch(typeSearches, wrapperTypes);\n        }\n      }\n      let textContent = /* @__PURE__ */ u4(\"span\", { className: \"truncate\", children: node.label });\n      if (searchQuery) {\n        try {\n          if (searchQuery.startsWith(\"/\") && searchQuery.endsWith(\"/\")) {\n            const pattern = searchQuery.slice(1, -1);\n            const regex = new RegExp(`(${pattern})`, \"i\");\n            const parts = node.label.split(regex);\n            textContent = /* @__PURE__ */ u4(\"span\", { className: \"tree-node-search-highlight\", children: parts.map(\n              (part, index) => regex.test(part) ? /* @__PURE__ */ u4(\n                \"span\",\n                {\n                  className: cn2(\"regex\", {\n                    start: regex.test(part) && index === 0,\n                    middle: regex.test(part) && index % 2 === 1,\n                    end: regex.test(part) && index === parts.length - 1,\n                    \"!ml-0\": index === 1\n                  }),\n                  children: part\n                },\n                `${node.nodeId}-${part}`\n              ) : part\n            ) });\n          } else {\n            const lowerLabel = node.label.toLowerCase();\n            const lowerQuery = searchQuery.toLowerCase();\n            const index = lowerLabel.indexOf(lowerQuery);\n            if (index >= 0) {\n              textContent = /* @__PURE__ */ u4(\"span\", { className: \"tree-node-search-highlight\", children: [\n                node.label.slice(0, index),\n                /* @__PURE__ */ u4(\"span\", { className: \"single\", children: node.label.slice(index, index + searchQuery.length) }),\n                node.label.slice(index + searchQuery.length)\n              ] });\n            }\n          }\n        } catch {\n        }\n      }\n      return {\n        highlightedText: textContent,\n        typeHighlight: matchesType && typeSearches.length > 0\n      };\n    }, [node.label, node.nodeId, node.fiber, searchValue]);\n  };\n  var formatTime = (time) => {\n    if (time > 0) {\n      if (time < 0.1 - Number.EPSILON) {\n        return \"< 0.1\";\n      }\n      if (time < 1e3) {\n        return Number(time.toFixed(1)).toString();\n      }\n      return `${(time / 1e3).toFixed(1)}k`;\n    }\n    return \"0\";\n  };\n  var TreeNodeItem = ({\n    node,\n    nodeIndex,\n    hasChildren,\n    isCollapsed,\n    handleTreeNodeClick,\n    handleTreeNodeToggle,\n    searchValue\n  }) => {\n    const refRenderCount = A2(null);\n    const refPrevRenderCount = A2(node.renderData?.renderCount ?? 0);\n    const { highlightedText, typeHighlight } = useNodeHighlighting(\n      node,\n      searchValue\n    );\n    y2(() => {\n      const currentRenderCount = node.renderData?.renderCount;\n      const element = refRenderCount.current;\n      if (!element || !refPrevRenderCount.current || !currentRenderCount || refPrevRenderCount.current === currentRenderCount) {\n        return;\n      }\n      element.classList.remove(\"count-flash\");\n      void element.offsetWidth;\n      element.classList.add(\"count-flash\");\n      refPrevRenderCount.current = currentRenderCount;\n    }, [node.renderData?.renderCount]);\n    const renderTimeInfo = T2(() => {\n      if (!node.renderData) return null;\n      const { selfTime, totalTime, renderCount } = node.renderData;\n      if (!renderCount) {\n        return null;\n      }\n      return /* @__PURE__ */ u4(\n        \"span\",\n        {\n          className: cn2(\n            \"flex items-center gap-x-0.5 ml-1.5\",\n            \"text-[10px] text-neutral-400\"\n          ),\n          children: /* @__PURE__ */ u4(\n            \"span\",\n            {\n              ref: refRenderCount,\n              title: `Self time: ${formatTime(selfTime)}ms\nTotal time: ${formatTime(totalTime)}ms`,\n              className: \"count-badge\",\n              children: [\n                \"\\xD7\",\n                renderCount\n              ]\n            }\n          )\n        }\n      );\n    }, [node.renderData]);\n    const componentTypes = T2(() => {\n      if (!node.fiber) return null;\n      const { wrapperTypes } = getExtendedDisplayName(node.fiber);\n      const firstWrapperType = wrapperTypes[0];\n      return /* @__PURE__ */ u4(\n        \"span\",\n        {\n          className: cn2(\n            \"flex items-center gap-x-1\",\n            \"text-[10px] text-neutral-400 tracking-wide\",\n            \"overflow-hidden\"\n          ),\n          children: [\n            firstWrapperType && /* @__PURE__ */ u4(k, { children: [\n              /* @__PURE__ */ u4(\n                \"span\",\n                {\n                  title: firstWrapperType?.title,\n                  className: cn2(\n                    \"rounded py-[1px] px-1\",\n                    \"bg-neutral-700 text-neutral-300\",\n                    \"truncate\",\n                    firstWrapperType.type === \"memo\" && \"bg-[#8e61e3] text-white\",\n                    typeHighlight && \"bg-yellow-300 text-black\"\n                  ),\n                  children: firstWrapperType.type\n                },\n                firstWrapperType.type\n              ),\n              firstWrapperType.compiler && /* @__PURE__ */ u4(\"span\", { className: \"text-yellow-300 ml-1\", children: \"\\u2728\" })\n            ] }),\n            wrapperTypes.length > 1 && `\\xD7${wrapperTypes.length}`,\n            renderTimeInfo\n          ]\n        }\n      );\n    }, [node.fiber, typeHighlight, renderTimeInfo]);\n    return /* @__PURE__ */ u4(\n      \"button\",\n      {\n        type: \"button\",\n        title: node.title,\n        \"data-index\": nodeIndex,\n        className: cn2(\n          \"flex items-center gap-x-1\",\n          \"pl-1 pr-2\",\n          \"w-full h-7\",\n          \"text-left\",\n          \"rounded\",\n          \"cursor-pointer select-none\"\n        ),\n        onClick: handleTreeNodeClick,\n        children: [\n          /* @__PURE__ */ u4(\n            \"button\",\n            {\n              type: \"button\",\n              \"data-index\": nodeIndex,\n              onClick: handleTreeNodeToggle,\n              className: cn2(\"w-6 h-6 flex items-center justify-center\", \"text-left\"),\n              children: hasChildren && /* @__PURE__ */ u4(\n                Icon,\n                {\n                  name: \"icon-chevron-right\",\n                  size: 12,\n                  className: cn2(\"transition-transform\", !isCollapsed && \"rotate-90\")\n                }\n              )\n            }\n          ),\n          highlightedText,\n          componentTypes\n        ]\n      }\n    );\n  };\n  var ComponentsTree = () => {\n    const refContainer = A2(null);\n    const refMainContainer = A2(null);\n    const refSearchInputContainer = A2(null);\n    const refSearchInput = A2(null);\n    const refSelectedElement = A2(null);\n    const refMaxTreeDepth = A2(0);\n    const refIsHovering = A2(false);\n    const refIsResizing = A2(false);\n    const refResizeHandle = A2(null);\n    const [flattenedNodes, setFlattenedNodes] = d2([]);\n    const [collapsedNodes, setCollapsedNodes] = d2(/* @__PURE__ */ new Set());\n    const [selectedIndex, setSelectedIndex] = d2(\n      void 0\n    );\n    const [searchValue, setSearchValue] = d2(searchState.value);\n    const visibleNodes = T2(() => {\n      const visible = [];\n      const nodes = flattenedNodes;\n      const nodeMap = new Map(nodes.map((node) => [node.nodeId, node]));\n      for (const node of nodes) {\n        let isVisible = true;\n        let currentNode = node;\n        while (currentNode.parentId) {\n          const parent = nodeMap.get(currentNode.parentId);\n          if (!parent) break;\n          if (collapsedNodes.has(parent.nodeId)) {\n            isVisible = false;\n            break;\n          }\n          currentNode = parent;\n        }\n        if (isVisible) {\n          visible.push(node);\n        }\n      }\n      return visible;\n    }, [collapsedNodes, flattenedNodes]);\n    const ITEM_HEIGHT = 28;\n    const { virtualItems, totalSize } = useVirtualList({\n      count: visibleNodes.length,\n      getScrollElement: () => refContainer.current,\n      estimateSize: () => ITEM_HEIGHT,\n      overscan: 5\n    });\n    const handleElementClick = q2(\n      (element) => {\n        refIsHovering.current = true;\n        refSearchInput.current?.blur();\n        signalSkipTreeUpdate.value = true;\n        const { parentCompositeFiber } = getCompositeComponentFromElement(element);\n        if (!parentCompositeFiber) return;\n        Store.inspectState.value = {\n          kind: \"focused\",\n          focusedDomElement: element,\n          fiber: parentCompositeFiber\n        };\n        const nodeIndex = visibleNodes.findIndex(\n          (node) => node.element === element\n        );\n        if (nodeIndex !== -1) {\n          setSelectedIndex(nodeIndex);\n          const itemTop = nodeIndex * ITEM_HEIGHT;\n          const container = refContainer.current;\n          if (container) {\n            const containerHeight = container.clientHeight;\n            const scrollTop = container.scrollTop;\n            if (itemTop < scrollTop || itemTop + ITEM_HEIGHT > scrollTop + containerHeight) {\n              container.scrollTo({\n                top: Math.max(0, itemTop - containerHeight / 2),\n                behavior: \"instant\"\n              });\n            }\n          }\n        }\n      },\n      [visibleNodes]\n    );\n    const handleTreeNodeClick = q2(\n      (e4) => {\n        const target = e4.currentTarget;\n        const index = Number(target.dataset.index);\n        if (Number.isNaN(index)) return;\n        const element = visibleNodes[index].element;\n        if (!element) return;\n        handleElementClick(element);\n      },\n      [visibleNodes, handleElementClick]\n    );\n    const handleToggle = q2((nodeId) => {\n      setCollapsedNodes((prev) => {\n        const next = new Set(prev);\n        if (next.has(nodeId)) {\n          next.delete(nodeId);\n        } else {\n          next.add(nodeId);\n        }\n        return next;\n      });\n    }, []);\n    const handleTreeNodeToggle = q2(\n      (e4) => {\n        e4.stopPropagation();\n        const target = e4.target;\n        const index = Number(target.dataset.index);\n        if (Number.isNaN(index)) return;\n        const nodeId = visibleNodes[index].nodeId;\n        handleToggle(nodeId);\n      },\n      [visibleNodes, handleToggle]\n    );\n    const handleOnChangeSearch = q2(\n      (query) => {\n        refSearchInputContainer.current?.classList.remove(\"!border-red-500\");\n        const matches = [];\n        if (!query) {\n          searchState.value = { query, matches, currentMatchIndex: -1 };\n          return;\n        }\n        if (query.includes(\"[\") && !query.includes(\"]\")) {\n          if (query.length > query.indexOf(\"[\") + 1) {\n            refSearchInputContainer.current?.classList.add(\"!border-red-500\");\n            return;\n          }\n        }\n        const typeSearches = parseTypeSearch(query) || [];\n        if (query.includes(\"[\")) {\n          if (!isValidTypeSearch(typeSearches)) {\n            refSearchInputContainer.current?.classList.add(\"!border-red-500\");\n            return;\n          }\n        }\n        const searchQuery = query.replace(/\\[.*?\\]/, \"\").trim();\n        const isRegex = /^\\/.*\\/$/.test(searchQuery);\n        let matchesLabel = (_label) => false;\n        if (searchQuery.startsWith(\"/\") && !isRegex) {\n          if (searchQuery.length > 1) {\n            refSearchInputContainer.current?.classList.add(\"!border-red-500\");\n            return;\n          }\n        }\n        if (isRegex) {\n          try {\n            const pattern = searchQuery.slice(1, -1);\n            const regex = new RegExp(pattern, \"i\");\n            matchesLabel = (label) => regex.test(label);\n          } catch {\n            refSearchInputContainer.current?.classList.add(\"!border-red-500\");\n            return;\n          }\n        } else if (searchQuery) {\n          const lowerQuery = searchQuery.toLowerCase();\n          matchesLabel = (label) => label.toLowerCase().includes(lowerQuery);\n        }\n        for (const node of flattenedNodes) {\n          let matchesSearch = true;\n          if (searchQuery) {\n            matchesSearch = matchesLabel(node.label);\n          }\n          if (matchesSearch && typeSearches.length > 0) {\n            if (!node.fiber) {\n              matchesSearch = false;\n            } else {\n              const { wrapperTypes } = getExtendedDisplayName(node.fiber);\n              matchesSearch = matchesTypeSearch(typeSearches, wrapperTypes);\n            }\n          }\n          if (matchesSearch) {\n            matches.push(node);\n          }\n        }\n        searchState.value = {\n          query,\n          matches,\n          currentMatchIndex: matches.length > 0 ? 0 : -1\n        };\n        if (matches.length > 0) {\n          const firstMatch = matches[0];\n          const nodeIndex = visibleNodes.findIndex(\n            (node) => node.nodeId === firstMatch.nodeId\n          );\n          if (nodeIndex !== -1) {\n            const itemTop = nodeIndex * ITEM_HEIGHT;\n            const container = refContainer.current;\n            if (container) {\n              const containerHeight = container.clientHeight;\n              container.scrollTo({\n                top: Math.max(0, itemTop - containerHeight / 2),\n                behavior: \"instant\"\n              });\n            }\n          }\n        }\n      },\n      [flattenedNodes, visibleNodes]\n    );\n    const handleInputChange = q2(\n      (e4) => {\n        const target = e4.currentTarget;\n        if (!target) return;\n        handleOnChangeSearch(target.value);\n      },\n      [handleOnChangeSearch]\n    );\n    const navigateSearch = q2(\n      (direction) => {\n        const { matches, currentMatchIndex } = searchState.value;\n        if (matches.length === 0) return;\n        const newIndex = direction === \"next\" ? (currentMatchIndex + 1) % matches.length : (currentMatchIndex - 1 + matches.length) % matches.length;\n        searchState.value = {\n          ...searchState.value,\n          currentMatchIndex: newIndex\n        };\n        const currentMatch = matches[newIndex];\n        const nodeIndex = visibleNodes.findIndex(\n          (node) => node.nodeId === currentMatch.nodeId\n        );\n        if (nodeIndex !== -1) {\n          setSelectedIndex(nodeIndex);\n          const itemTop = nodeIndex * ITEM_HEIGHT;\n          const container = refContainer.current;\n          if (container) {\n            const containerHeight = container.clientHeight;\n            container.scrollTo({\n              top: Math.max(0, itemTop - containerHeight / 2),\n              behavior: \"instant\"\n            });\n          }\n        }\n      },\n      [visibleNodes]\n    );\n    const updateContainerWidths = q2((width) => {\n      if (refMainContainer.current) {\n        refMainContainer.current.style.width = `${width}px`;\n      }\n      if (refContainer.current) {\n        refContainer.current.style.width = `${width}px`;\n        const indentSize = calculateIndentSize(width, refMaxTreeDepth.current);\n        refContainer.current.style.setProperty(\n          \"--indentation-size\",\n          `${indentSize}px`\n        );\n      }\n    }, []);\n    const updateResizeDirection = q2((width) => {\n      if (!refResizeHandle.current) return;\n      const parentWidth = signalWidget.value.dimensions.width;\n      const maxWidth = Math.floor(parentWidth - MIN_CONTAINER_WIDTH / 2);\n      refResizeHandle.current.classList.remove(\n        \"cursor-ew-resize\",\n        \"cursor-w-resize\",\n        \"cursor-e-resize\"\n      );\n      if (width <= MIN_CONTAINER_WIDTH) {\n        refResizeHandle.current.classList.add(\"cursor-w-resize\");\n      } else if (width >= maxWidth) {\n        refResizeHandle.current.classList.add(\"cursor-e-resize\");\n      } else {\n        refResizeHandle.current.classList.add(\"cursor-ew-resize\");\n      }\n    }, []);\n    const handleResize = q2(\n      (e4) => {\n        e4.preventDefault();\n        e4.stopPropagation();\n        if (!refContainer.current) return;\n        refContainer.current.style.setProperty(\"pointer-events\", \"none\");\n        refIsResizing.current = true;\n        const startX = e4.clientX;\n        const startWidth = refContainer.current.offsetWidth;\n        const parentWidth = signalWidget.value.dimensions.width;\n        const maxWidth = Math.floor(parentWidth - MIN_CONTAINER_WIDTH / 2);\n        updateResizeDirection(startWidth);\n        const handlePointerMove = (e5) => {\n          const delta = startX - e5.clientX;\n          const newWidth = startWidth + delta;\n          updateResizeDirection(newWidth);\n          const clampedWidth = Math.min(\n            maxWidth,\n            Math.max(MIN_CONTAINER_WIDTH, newWidth)\n          );\n          updateContainerWidths(clampedWidth);\n        };\n        const handlePointerUp = () => {\n          if (!refContainer.current) return;\n          refContainer.current.style.removeProperty(\"pointer-events\");\n          document.removeEventListener(\"pointermove\", handlePointerMove);\n          document.removeEventListener(\"pointerup\", handlePointerUp);\n          signalWidget.value = {\n            ...signalWidget.value,\n            componentsTree: {\n              ...signalWidget.value.componentsTree,\n              width: refContainer.current.offsetWidth\n            }\n          };\n          saveLocalStorage(LOCALSTORAGE_KEY, signalWidget.value);\n          refIsResizing.current = false;\n        };\n        document.addEventListener(\"pointermove\", handlePointerMove);\n        document.addEventListener(\"pointerup\", handlePointerUp);\n      },\n      [updateContainerWidths, updateResizeDirection]\n    );\n    y2(() => {\n      if (!refContainer.current) return;\n      const currentWidth = refContainer.current.offsetWidth;\n      updateResizeDirection(currentWidth);\n      return signalWidget.subscribe(() => {\n        if (!refContainer.current) return;\n        updateResizeDirection(refContainer.current.offsetWidth);\n      });\n    }, [updateResizeDirection]);\n    const onPointerLeave = q2(() => {\n      refIsHovering.current = false;\n    }, []);\n    y2(() => {\n      let isInitialTreeBuild = true;\n      const buildTreeFromElements = (elements) => {\n        const nodeMap = /* @__PURE__ */ new Map();\n        const rootNodes = [];\n        for (const { element, name, fiber } of elements) {\n          if (!element) continue;\n          let title = name;\n          const { name: componentName, wrappers } = getExtendedDisplayName(fiber);\n          if (componentName) {\n            if (wrappers.length > 0) {\n              title = `${wrappers.join(\"(\")}(${componentName})${\")\".repeat(wrappers.length)}`;\n            } else {\n              title = componentName;\n            }\n          }\n          nodeMap.set(element, {\n            label: componentName || name,\n            title,\n            children: [],\n            element,\n            fiber\n          });\n        }\n        for (const { element, depth } of elements) {\n          if (!element) continue;\n          const node = nodeMap.get(element);\n          if (!node) continue;\n          if (depth === 0) {\n            rootNodes.push(node);\n          } else {\n            let parent = element.parentElement;\n            while (parent) {\n              const parentNode = nodeMap.get(parent);\n              if (parentNode) {\n                parentNode.children = parentNode.children || [];\n                parentNode.children.push(node);\n                break;\n              }\n              parent = parent.parentElement;\n            }\n          }\n        }\n        return rootNodes;\n      };\n      const updateTree = () => {\n        const element = refSelectedElement.current;\n        if (!element) return;\n        const inspectableElements = getInspectableElements();\n        const tree = buildTreeFromElements(inspectableElements);\n        if (tree.length > 0) {\n          const flattened = flattenTree(tree);\n          const newMaxDepth = getMaxDepth(flattened);\n          refMaxTreeDepth.current = newMaxDepth;\n          updateContainerWidths(signalWidget.value.componentsTree.width);\n          setFlattenedNodes(flattened);\n          if (isInitialTreeBuild) {\n            isInitialTreeBuild = false;\n            const focusedIndex = flattened.findIndex(\n              (node) => node.element === element\n            );\n            if (focusedIndex !== -1) {\n              const itemTop = focusedIndex * ITEM_HEIGHT;\n              const container = refContainer.current;\n              if (container) {\n                setTimeout(() => {\n                  container.scrollTo({\n                    top: itemTop,\n                    behavior: \"instant\"\n                  });\n                }, 96);\n              }\n            }\n          }\n        }\n      };\n      const unsubscribeStore = Store.inspectState.subscribe((state) => {\n        if (state.kind === \"focused\") {\n          if (signalSkipTreeUpdate.value) {\n            return;\n          }\n          handleOnChangeSearch(\"\");\n          refSelectedElement.current = state.focusedDomElement;\n          updateTree();\n        }\n      });\n      let rafId = 0;\n      const unsubscribeUpdates = inspectorUpdateSignal.subscribe(() => {\n        if (Store.inspectState.value.kind === \"focused\") {\n          cancelAnimationFrame(rafId);\n          if (refIsResizing.current) return;\n          rafId = requestAnimationFrame(() => {\n            signalSkipTreeUpdate.value = false;\n            updateTree();\n          });\n        }\n      });\n      return () => {\n        unsubscribeStore();\n        unsubscribeUpdates();\n        searchState.value = {\n          query: \"\",\n          matches: [],\n          currentMatchIndex: -1\n        };\n      };\n    }, []);\n    y2(() => {\n      const handleKeyDown = (e4) => {\n        if (!refIsHovering.current) return;\n        if (!selectedIndex) return;\n        switch (e4.key) {\n          case \"ArrowUp\": {\n            e4.preventDefault();\n            e4.stopPropagation();\n            if (selectedIndex > 0) {\n              const currentNode = visibleNodes[selectedIndex - 1];\n              if (currentNode?.element) {\n                handleElementClick(currentNode.element);\n              }\n            }\n            return;\n          }\n          case \"ArrowDown\": {\n            e4.preventDefault();\n            e4.stopPropagation();\n            if (selectedIndex < visibleNodes.length - 1) {\n              const currentNode = visibleNodes[selectedIndex + 1];\n              if (currentNode?.element) {\n                handleElementClick(currentNode.element);\n              }\n            }\n            return;\n          }\n          case \"ArrowLeft\": {\n            e4.preventDefault();\n            e4.stopPropagation();\n            const currentNode = visibleNodes[selectedIndex];\n            if (currentNode?.nodeId) {\n              handleToggle(currentNode.nodeId);\n            }\n            return;\n          }\n          case \"ArrowRight\": {\n            e4.preventDefault();\n            e4.stopPropagation();\n            const currentNode = visibleNodes[selectedIndex];\n            if (currentNode?.nodeId) {\n              handleToggle(currentNode.nodeId);\n            }\n            return;\n          }\n        }\n      };\n      document.addEventListener(\"keydown\", handleKeyDown);\n      return () => {\n        document.removeEventListener(\"keydown\", handleKeyDown);\n      };\n    }, [selectedIndex, visibleNodes, handleElementClick, handleToggle]);\n    y2(() => {\n      return searchState.subscribe(setSearchValue);\n    }, []);\n    y2(() => {\n      const unsubscribe = signalWidget.subscribe((state) => {\n        refMainContainer.current?.style.setProperty(\"transition\", \"width 0.1s\");\n        updateContainerWidths(state.componentsTree.width);\n        setTimeout(() => {\n          refMainContainer.current?.style.removeProperty(\"transition\");\n        }, 500);\n      });\n      return unsubscribe;\n    }, []);\n    return /* @__PURE__ */ u4(\"div\", { className: \"react-scan-components-tree flex\", children: [\n      /* @__PURE__ */ u4(\n        \"div\",\n        {\n          ref: refResizeHandle,\n          onPointerDown: handleResize,\n          className: \"relative resize-v-line\",\n          children: /* @__PURE__ */ u4(\"span\", { children: /* @__PURE__ */ u4(Icon, { name: \"icon-ellipsis\", size: 18 }) })\n        }\n      ),\n      /* @__PURE__ */ u4(\"div\", { ref: refMainContainer, className: \"flex flex-col h-full\", children: [\n        /* @__PURE__ */ u4(\"div\", { className: \"p-2 border-b border-[#1e1e1e]\", children: /* @__PURE__ */ u4(\n          \"div\",\n          {\n            ref: refSearchInputContainer,\n            title: `Search components by:\n\n\\u2022 Name (e.g., \"Button\") \\u2014 Case insensitive, matches any part\n\n\\u2022 Regular Expression (e.g., \"/^Button/\") \\u2014 Use forward slashes\n\n\\u2022 Wrapper Type (e.g., \"[memo,forwardRef]\"):\n   - Available types: memo, forwardRef, lazy, suspense\n   - Matches any part of type name (e.g., \"mo\" matches \"memo\")\n   - Use commas for multiple types\n\n\\u2022 Combined Search:\n   - Mix name/regex with type: \"button [for]\"\n   - Will match components satisfying both conditions\n\n\\u2022 Navigation:\n   - Enter \\u2192 Next match\n   - Shift + Enter \\u2192 Previous match\n   - Cmd/Ctrl + Enter \\u2192 Select and focus match\n`,\n            className: cn2(\n              \"relative\",\n              \"flex items-center gap-x-1 px-2\",\n              \"rounded\",\n              \"border border-transparent\",\n              \"focus-within:border-[#454545]\",\n              \"bg-[#1e1e1e] text-neutral-300\",\n              \"transition-colors\",\n              \"whitespace-nowrap\",\n              \"overflow-hidden\"\n            ),\n            children: [\n              /* @__PURE__ */ u4(Icon, { name: \"icon-search\", size: 12, className: \" text-neutral-500\" }),\n              /* @__PURE__ */ u4(\"div\", { className: \"relative flex-1 h-7 overflow-hidden\", children: /* @__PURE__ */ u4(\n                \"input\",\n                {\n                  ref: refSearchInput,\n                  type: \"text\",\n                  value: searchState.value.query,\n                  onClick: (e4) => {\n                    e4.stopPropagation();\n                    e4.currentTarget.focus();\n                  },\n                  onPointerDown: (e4) => {\n                    e4.stopPropagation();\n                  },\n                  onKeyDown: (e4) => {\n                    if (e4.key === \"Escape\") {\n                      e4.currentTarget.blur();\n                    }\n                    if (searchState.value.matches.length) {\n                      if (e4.key === \"Enter\" && e4.shiftKey) {\n                        navigateSearch(\"prev\");\n                      } else if (e4.key === \"Enter\") {\n                        if (e4.metaKey || e4.ctrlKey) {\n                          e4.preventDefault();\n                          e4.stopPropagation();\n                          handleElementClick(\n                            searchState.value.matches[searchState.value.currentMatchIndex].element\n                          );\n                          e4.currentTarget.focus();\n                        } else {\n                          navigateSearch(\"next\");\n                        }\n                      }\n                    }\n                  },\n                  onChange: handleInputChange,\n                  className: \"absolute inset-y-0 inset-x-1\",\n                  placeholder: \"Component name, /regex/, or [type]\"\n                }\n              ) }),\n              searchState.value.query ? /* @__PURE__ */ u4(k, { children: [\n                /* @__PURE__ */ u4(\"span\", { className: \"flex items-center gap-x-0.5 text-xs text-neutral-500\", children: [\n                  searchState.value.currentMatchIndex + 1,\n                  \"|\",\n                  searchState.value.matches.length\n                ] }),\n                !!searchState.value.matches.length && /* @__PURE__ */ u4(k, { children: [\n                  /* @__PURE__ */ u4(\n                    \"button\",\n                    {\n                      type: \"button\",\n                      onClick: (e4) => {\n                        e4.stopPropagation();\n                        navigateSearch(\"prev\");\n                      },\n                      className: \"button rounded w-4 h-4 flex items-center justify-center text-neutral-400 hover:text-neutral-300\",\n                      children: /* @__PURE__ */ u4(\n                        Icon,\n                        {\n                          name: \"icon-chevron-right\",\n                          className: \"-rotate-90\",\n                          size: 12\n                        }\n                      )\n                    }\n                  ),\n                  /* @__PURE__ */ u4(\n                    \"button\",\n                    {\n                      type: \"button\",\n                      onClick: (e4) => {\n                        e4.stopPropagation();\n                        navigateSearch(\"next\");\n                      },\n                      className: \"button rounded w-4 h-4 flex items-center justify-center text-neutral-400 hover:text-neutral-300\",\n                      children: /* @__PURE__ */ u4(\n                        Icon,\n                        {\n                          name: \"icon-chevron-right\",\n                          className: \"rotate-90\",\n                          size: 12\n                        }\n                      )\n                    }\n                  )\n                ] }),\n                /* @__PURE__ */ u4(\n                  \"button\",\n                  {\n                    type: \"button\",\n                    onClick: (e4) => {\n                      e4.stopPropagation();\n                      handleOnChangeSearch(\"\");\n                    },\n                    className: \"button rounded w-4 h-4 flex items-center justify-center text-neutral-400 hover:text-neutral-300\",\n                    children: /* @__PURE__ */ u4(Icon, { name: \"icon-close\", size: 12 })\n                  }\n                )\n              ] }) : !!flattenedNodes.length && /* @__PURE__ */ u4(\"span\", { className: \"text-xs text-neutral-500\", children: flattenedNodes.length })\n            ]\n          }\n        ) }),\n        /* @__PURE__ */ u4(\"div\", { className: \"flex-1 overflow-hidden\", children: /* @__PURE__ */ u4(\n          \"div\",\n          {\n            ref: refContainer,\n            onPointerLeave,\n            className: \"tree h-full overflow-auto will-change-transform\",\n            children: /* @__PURE__ */ u4(\n              \"div\",\n              {\n                className: \"relative w-full\",\n                style: {\n                  height: totalSize\n                },\n                children: virtualItems.map((virtualItem) => {\n                  const node = visibleNodes[virtualItem.index];\n                  if (!node) return null;\n                  const isSelected = Store.inspectState.value.kind === \"focused\" && node.element === Store.inspectState.value.focusedDomElement;\n                  const isKeyboardSelected = virtualItem.index === selectedIndex;\n                  return /* @__PURE__ */ u4(\n                    \"div\",\n                    {\n                      className: cn2(\n                        \"absolute left-0 w-full overflow-hidden\",\n                        \"text-neutral-400 hover:text-neutral-300\",\n                        \"bg-transparent hover:bg-[#5f3f9a]/20\",\n                        (isSelected || isKeyboardSelected) && \"text-neutral-300 bg-[#5f3f9a]/40 hover:bg-[#5f3f9a]/40\"\n                      ),\n                      style: {\n                        top: virtualItem.start,\n                        height: ITEM_HEIGHT\n                      },\n                      children: /* @__PURE__ */ u4(\n                        \"div\",\n                        {\n                          className: \"w-full h-full\",\n                          style: {\n                            paddingLeft: `calc(${node.depth} * var(--indentation-size))`\n                          },\n                          children: /* @__PURE__ */ u4(\n                            TreeNodeItem,\n                            {\n                              node,\n                              nodeIndex: virtualItem.index,\n                              hasChildren: !!node.children?.length,\n                              isCollapsed: collapsedNodes.has(node.nodeId),\n                              handleTreeNodeClick,\n                              handleTreeNodeToggle,\n                              searchValue\n                            }\n                          )\n                        }\n                      )\n                    },\n                    node.nodeId\n                  );\n                })\n              }\n            )\n          }\n        ) })\n      ] })\n    ] });\n  };\n\n  // src/web/components/copy-to-clipboard/index.tsx\n  var CopyToClipboard = /* @__PURE__ */ M2(\n    ({\n      text,\n      children,\n      onCopy,\n      className,\n      iconSize = 14\n    }) => {\n      const [isCopied, setIsCopied] = d2(false);\n      y2(() => {\n        if (isCopied) {\n          const timeout2 = setTimeout(() => setIsCopied(false), 600);\n          return () => {\n            clearTimeout(timeout2);\n          };\n        }\n      }, [isCopied]);\n      const copyToClipboard = q2(\n        (e4) => {\n          e4.preventDefault();\n          e4.stopPropagation();\n          navigator.clipboard.writeText(text).then(\n            () => {\n              setIsCopied(true);\n              onCopy?.(true, text);\n            },\n            () => {\n              onCopy?.(false, text);\n            }\n          );\n        },\n        [text, onCopy]\n      );\n      const ClipboardIcon = /* @__PURE__ */ u4(\n        \"button\",\n        {\n          onClick: copyToClipboard,\n          type: \"button\",\n          className: cn2(\n            \"z-10\",\n            \"flex items-center justify-center\",\n            \"hover:text-dev-pink-400\",\n            \"transition-colors duration-200 ease-in-out\",\n            \"cursor-pointer\",\n            `size-[${iconSize}px]`,\n            className\n          ),\n          children: /* @__PURE__ */ u4(\n            Icon,\n            {\n              name: `icon-${isCopied ? \"check\" : \"copy\"}`,\n              size: [iconSize],\n              className: cn2(isCopied && \"text-green-500\")\n            }\n          )\n        }\n      );\n      if (!children) {\n        return ClipboardIcon;\n      }\n      return children({\n        ClipboardIcon,\n        onClick: copyToClipboard\n      });\n    }\n  );\n\n  // src/web/views/inspector/diff-value.tsx\n  var ArrayHeader = ({\n    length,\n    expanded,\n    onToggle,\n    isNegative\n  }) => /* @__PURE__ */ u4(\"div\", { className: \"flex items-center gap-1\", children: [\n    /* @__PURE__ */ u4(\n      \"button\",\n      {\n        type: \"button\",\n        onClick: onToggle,\n        className: \"flex items-center p-0 opacity-50\",\n        children: /* @__PURE__ */ u4(\n          Icon,\n          {\n            name: \"icon-chevron-right\",\n            size: 12,\n            className: cn2(\n              \"transition-[color,transform]\",\n              isNegative ? \"text-[#f87171]\" : \"text-[#4ade80]\",\n              expanded && \"rotate-90\"\n            )\n          }\n        )\n      }\n    ),\n    /* @__PURE__ */ u4(\"span\", { children: [\n      \"Array(\",\n      length,\n      \")\"\n    ] })\n  ] });\n  var TreeNode = ({\n    value,\n    path,\n    isNegative\n  }) => {\n    const [isExpanded, setIsExpanded] = d2(false);\n    const canExpand = value !== null && typeof value === \"object\" && !(value instanceof Date);\n    if (!canExpand) {\n      return /* @__PURE__ */ u4(\"div\", { className: \"flex items-center gap-1\", children: [\n        /* @__PURE__ */ u4(\"span\", { className: \"text-gray-500\", children: [\n          path,\n          \":\"\n        ] }),\n        /* @__PURE__ */ u4(\"span\", { className: \"truncate\", children: formatValuePreview(value) })\n      ] });\n    }\n    const entries = Object.entries(value);\n    return /* @__PURE__ */ u4(\"div\", { className: \"flex flex-col\", children: [\n      /* @__PURE__ */ u4(\"div\", { className: \"flex items-center gap-1\", children: [\n        /* @__PURE__ */ u4(\n          \"button\",\n          {\n            type: \"button\",\n            onClick: () => setIsExpanded(!isExpanded),\n            className: \"flex items-center p-0 opacity-50\",\n            children: /* @__PURE__ */ u4(\n              Icon,\n              {\n                name: \"icon-chevron-right\",\n                size: 12,\n                className: cn2(\n                  \"transition-[color,transform]\",\n                  isNegative ? \"text-[#f87171]\" : \"text-[#4ade80]\",\n                  isExpanded && \"rotate-90\"\n                )\n              }\n            )\n          }\n        ),\n        /* @__PURE__ */ u4(\"span\", { className: \"text-gray-500\", children: [\n          path,\n          \":\"\n        ] }),\n        !isExpanded && /* @__PURE__ */ u4(\"span\", { className: \"truncate\", children: value instanceof Date ? formatValuePreview(value) : `{${Object.keys(value).join(\", \")}}` })\n      ] }),\n      isExpanded && /* @__PURE__ */ u4(\"div\", { className: \"pl-5 border-l border-[#333] mt-0.5 ml-1 flex flex-col gap-0.5\", children: entries.map(([key, val]) => /* @__PURE__ */ u4(\n        TreeNode,\n        {\n          value: val,\n          path: key,\n          isNegative\n        },\n        key\n      )) })\n    ] });\n  };\n  var DiffValueView = ({\n    value,\n    expanded,\n    onToggle,\n    isNegative\n  }) => {\n    const { value: safeValue, error } = safeGetValue(value);\n    if (error) {\n      return /* @__PURE__ */ u4(\"span\", { className: \"text-gray-500 font-italic\", children: error });\n    }\n    const isExpandable = safeValue !== null && typeof safeValue === \"object\" && !(safeValue instanceof Promise);\n    if (!isExpandable) {\n      return /* @__PURE__ */ u4(\"span\", { children: formatValuePreview(safeValue) });\n    }\n    if (Array.isArray(safeValue)) {\n      return /* @__PURE__ */ u4(\"div\", { className: \"flex flex-col gap-1 relative\", children: [\n        /* @__PURE__ */ u4(\n          ArrayHeader,\n          {\n            length: safeValue.length,\n            expanded,\n            onToggle,\n            isNegative\n          }\n        ),\n        expanded && /* @__PURE__ */ u4(\"div\", { className: \"pl-2 border-l border-[#333] mt-0.5 ml-1 flex flex-col gap-0.5\", children: safeValue.map((item, index) => /* @__PURE__ */ u4(\n          TreeNode,\n          {\n            value: item,\n            path: index.toString(),\n            isNegative\n          },\n          index.toString()\n        )) }),\n        /* @__PURE__ */ u4(\n          CopyToClipboard,\n          {\n            text: formatForClipboard(safeValue),\n            className: \"absolute top-0.5 right-0.5 opacity-0 transition-opacity group-hover:opacity-100 self-end\",\n            children: ({ ClipboardIcon }) => /* @__PURE__ */ u4(k, { children: ClipboardIcon })\n          }\n        )\n      ] });\n    }\n    return /* @__PURE__ */ u4(\"div\", { className: \"flex items-start gap-1 relative\", children: [\n      /* @__PURE__ */ u4(\n        \"button\",\n        {\n          type: \"button\",\n          onClick: onToggle,\n          className: cn2(\"flex items-center\", \"p-0 mt-0.5 mr-1\", \"opacity-50\"),\n          children: /* @__PURE__ */ u4(\n            Icon,\n            {\n              name: \"icon-chevron-right\",\n              size: 12,\n              className: cn2(\n                \"transition-[color,transform]\",\n                isNegative ? \"text-[#f87171]\" : \"text-[#4ade80]\",\n                expanded && \"rotate-90\"\n              )\n            }\n          )\n        }\n      ),\n      /* @__PURE__ */ u4(\"div\", { className: \"flex-1\", children: !expanded ? /* @__PURE__ */ u4(\"span\", { children: formatValuePreview(safeValue) }) : /* @__PURE__ */ u4(\"div\", { className: \"pl-2 border-l border-[#333] mt-0.5 ml-1 flex flex-col gap-0.5\", children: Object.entries(safeValue).map(([key, val]) => /* @__PURE__ */ u4(\n        TreeNode,\n        {\n          value: val,\n          path: key,\n          isNegative\n        },\n        key\n      )) }) }),\n      /* @__PURE__ */ u4(\n        CopyToClipboard,\n        {\n          text: formatForClipboard(safeValue),\n          className: \"absolute top-0.5 right-0.5 opacity-0 transition-opacity group-hover:opacity-100 self-end\",\n          children: ({ ClipboardIcon }) => /* @__PURE__ */ u4(k, { children: ClipboardIcon })\n        }\n      )\n    ] });\n  };\n\n  // src/web/views/inspector/whats-changed/use-change-store.ts\n  var CHANGES_QUEUE_INTERVAL = 50;\n  d3({\n    fiber: null,\n    fiberProps: { current: [], changes: /* @__PURE__ */ new Set() },\n    fiberState: { current: [], changes: /* @__PURE__ */ new Set() },\n    fiberContext: { current: [], changes: /* @__PURE__ */ new Set() }\n  });\n  var getContextChangesValue = (discriminated) => {\n    switch (discriminated.kind) {\n      case \"initialized\": {\n        return discriminated.changes.currentValue;\n      }\n      case \"partially-initialized\": {\n        return discriminated.value;\n      }\n    }\n  };\n  var processChanges = (changes, targetMap) => {\n    for (const change of changes) {\n      const existing = targetMap.get(change.name);\n      if (existing) {\n        targetMap.set(existing.name, {\n          count: existing.count + 1,\n          currentValue: change.value,\n          id: existing.name,\n          lastUpdated: Date.now(),\n          name: existing.name,\n          previousValue: change.prevValue\n        });\n        continue;\n      }\n      targetMap.set(change.name, {\n        count: 1,\n        currentValue: change.value,\n        id: change.name,\n        lastUpdated: Date.now(),\n        name: change.name,\n        previousValue: change.prevValue\n      });\n    }\n  };\n  var processContextChanges = (contextChanges, aggregatedChanges) => {\n    for (const change of contextChanges) {\n      const existing = aggregatedChanges.contextChanges.get(change.contextType);\n      if (existing) {\n        if (isEqual(getContextChangesValue(existing), change.value)) {\n          continue;\n        }\n        if (existing.kind === \"partially-initialized\") {\n          aggregatedChanges.contextChanges.set(change.contextType, {\n            kind: \"initialized\",\n            changes: {\n              count: 1,\n              currentValue: change.value,\n              id: change.contextType.toString(),\n              // come back to this why was this ever expected to be a number?\n              lastUpdated: Date.now(),\n              name: change.name,\n              previousValue: existing.value\n            }\n          });\n          continue;\n        }\n        aggregatedChanges.contextChanges.set(change.contextType, {\n          kind: \"initialized\",\n          changes: {\n            count: existing.changes.count + 1,\n            currentValue: change.value,\n            id: change.contextType.toString(),\n            lastUpdated: Date.now(),\n            name: change.name,\n            previousValue: existing.changes.currentValue\n          }\n        });\n        continue;\n      }\n      aggregatedChanges.contextChanges.set(change.contextType, {\n        kind: \"partially-initialized\",\n        id: change.contextType.toString(),\n        lastUpdated: Date.now(),\n        name: change.name,\n        value: change.value\n      });\n    }\n  };\n  var collapseQueue = (queue) => {\n    const localAggregatedChanges = {\n      contextChanges: /* @__PURE__ */ new Map(),\n      propsChanges: /* @__PURE__ */ new Map(),\n      stateChanges: /* @__PURE__ */ new Map()\n    };\n    queue.forEach((changes) => {\n      processContextChanges(changes.contextChanges, localAggregatedChanges);\n      processChanges(changes.stateChanges, localAggregatedChanges.stateChanges);\n      processChanges(changes.propsChanges, localAggregatedChanges.propsChanges);\n    });\n    return localAggregatedChanges;\n  };\n  var mergeSimpleChanges = (existingChanges, incomingChanges) => {\n    const mergedChanges = /* @__PURE__ */ new Map();\n    existingChanges.forEach((value, key) => {\n      mergedChanges.set(key, value);\n    });\n    incomingChanges.forEach((incomingChange, key) => {\n      const existing = mergedChanges.get(key);\n      if (!existing) {\n        mergedChanges.set(key, incomingChange);\n        return;\n      }\n      mergedChanges.set(key, {\n        count: existing.count + incomingChange.count,\n        currentValue: incomingChange.currentValue,\n        id: incomingChange.id,\n        lastUpdated: incomingChange.lastUpdated,\n        name: incomingChange.name,\n        previousValue: incomingChange.previousValue\n      });\n    });\n    return mergedChanges;\n  };\n  var mergeContextChanges = (existing, incoming) => {\n    const contextChanges = /* @__PURE__ */ new Map();\n    existing.contextChanges.forEach((value, key) => {\n      contextChanges.set(key, value);\n    });\n    incoming.contextChanges.forEach((incomingChange, key) => {\n      const existingChange = contextChanges.get(key);\n      if (!existingChange) {\n        contextChanges.set(key, incomingChange);\n        return;\n      }\n      if (getContextChangesValue(incomingChange) === getContextChangesValue(existingChange)) {\n        return;\n      }\n      switch (existingChange.kind) {\n        case \"initialized\": {\n          switch (incomingChange.kind) {\n            case \"initialized\": {\n              const preInitEntryOffset = 1;\n              contextChanges.set(key, {\n                kind: \"initialized\",\n                changes: {\n                  ...incomingChange.changes,\n                  // if existing was initialized, the pre-initialization done by the collapsed queue was not necessary, so we need to increment count to account for the preInit entry\n                  count: incomingChange.changes.count + existingChange.changes.count + preInitEntryOffset,\n                  currentValue: incomingChange.changes.currentValue,\n                  previousValue: incomingChange.changes.previousValue\n                  // we always want to show this value, since this will be the true state transition (if you make the previousValue the last seen currentValue, u will have weird behavior with primitive state updates)\n                }\n              });\n              return;\n            }\n            case \"partially-initialized\": {\n              contextChanges.set(key, {\n                kind: \"initialized\",\n                changes: {\n                  count: existingChange.changes.count + 1,\n                  currentValue: incomingChange.value,\n                  id: incomingChange.id,\n                  lastUpdated: incomingChange.lastUpdated,\n                  name: incomingChange.name,\n                  previousValue: existingChange.changes.currentValue\n                }\n              });\n              return;\n            }\n          }\n        }\n        case \"partially-initialized\": {\n          switch (incomingChange.kind) {\n            case \"initialized\": {\n              contextChanges.set(key, {\n                kind: \"initialized\",\n                changes: {\n                  count: incomingChange.changes.count + 1,\n                  currentValue: incomingChange.changes.currentValue,\n                  id: incomingChange.changes.id,\n                  lastUpdated: incomingChange.changes.lastUpdated,\n                  name: incomingChange.changes.name,\n                  previousValue: existingChange.value\n                }\n              });\n              return;\n            }\n            case \"partially-initialized\": {\n              contextChanges.set(key, {\n                kind: \"initialized\",\n                changes: {\n                  count: 1,\n                  currentValue: incomingChange.value,\n                  id: incomingChange.id,\n                  lastUpdated: incomingChange.lastUpdated,\n                  name: incomingChange.name,\n                  previousValue: existingChange.value\n                }\n              });\n              return;\n            }\n          }\n        }\n      }\n    });\n    return contextChanges;\n  };\n  var mergeChanges = (existing, incoming) => {\n    const contextChanges = mergeContextChanges(existing, incoming);\n    const propChanges = mergeSimpleChanges(\n      existing.propsChanges,\n      incoming.propsChanges\n    );\n    const stateChanges = mergeSimpleChanges(\n      existing.stateChanges,\n      incoming.stateChanges\n    );\n    return {\n      contextChanges,\n      propsChanges: propChanges,\n      stateChanges\n    };\n  };\n  var calculateTotalChanges = (changes) => {\n    return Array.from(changes.propsChanges.values()).reduce(\n      (acc, change) => acc + change.count,\n      0\n    ) + Array.from(changes.stateChanges.values()).reduce(\n      (acc, change) => acc + change.count,\n      0\n    ) + Array.from(changes.contextChanges.values()).filter(\n      (change) => change.kind === \"initialized\"\n    ).reduce((acc, change) => acc + change.changes.count, 0);\n  };\n  var useInspectedFiberChangeStore = (opts) => {\n    const pendingChanges = A2({ queue: [] });\n    const [aggregatedChanges, setAggregatedChanges] = d2({\n      propsChanges: /* @__PURE__ */ new Map(),\n      stateChanges: /* @__PURE__ */ new Map(),\n      contextChanges: /* @__PURE__ */ new Map()\n    });\n    const fiber = Store.inspectState.value.kind === \"focused\" ? Store.inspectState.value.fiber : null;\n    const fiberId2 = fiber ? getFiberId(fiber) : null;\n    y2(() => {\n      const interval = setInterval(() => {\n        if (pendingChanges.current.queue.length === 0) return;\n        setAggregatedChanges((prevAggregatedChanges) => {\n          const queueChanges = collapseQueue(pendingChanges.current.queue);\n          const merged = mergeChanges(prevAggregatedChanges, queueChanges);\n          calculateTotalChanges(prevAggregatedChanges);\n          calculateTotalChanges(merged);\n          return merged;\n        });\n        pendingChanges.current.queue = [];\n      }, CHANGES_QUEUE_INTERVAL);\n      return () => {\n        clearInterval(interval);\n      };\n    }, [fiber]);\n    y2(() => {\n      if (!fiberId2) {\n        return;\n      }\n      const listener = (change) => {\n        pendingChanges.current?.queue.push(change);\n      };\n      let listeners = Store.changesListeners.get(fiberId2);\n      if (!listeners) {\n        listeners = [];\n        Store.changesListeners.set(fiberId2, listeners);\n      }\n      listeners.push(listener);\n      return () => {\n        setAggregatedChanges({\n          propsChanges: /* @__PURE__ */ new Map(),\n          stateChanges: /* @__PURE__ */ new Map(),\n          contextChanges: /* @__PURE__ */ new Map()\n        });\n        pendingChanges.current.queue = [];\n        Store.changesListeners.set(\n          fiberId2,\n          Store.changesListeners.get(fiberId2)?.filter((l5) => l5 !== listener) ?? []\n        );\n      };\n    }, [fiberId2]);\n    y2(() => {\n      return () => {\n        setAggregatedChanges({\n          propsChanges: /* @__PURE__ */ new Map(),\n          stateChanges: /* @__PURE__ */ new Map(),\n          contextChanges: /* @__PURE__ */ new Map()\n        });\n        pendingChanges.current.queue = [];\n      };\n    }, [fiberId2]);\n    return aggregatedChanges;\n  };\n\n  // src/web/views/inspector/what-changed.tsx\n  var WhatChanged = /* @__PURE__ */ M2(() => {\n    const [isExpanded, setIsExpanded] = d2(true);\n    const aggregatedChanges = useInspectedFiberChangeStore();\n    const [hasInitialized, setHasInitialized] = d2(false);\n    const hasAnyChanges = calculateTotalChanges(aggregatedChanges) > 0;\n    y2(() => {\n      if (!hasInitialized && hasAnyChanges) {\n        const timer = setTimeout(() => {\n          setHasInitialized(true);\n          requestAnimationFrame(() => {\n            setIsExpanded(true);\n          });\n        }, 0);\n        return () => clearTimeout(timer);\n      }\n    }, [hasInitialized, hasAnyChanges]);\n    const initializedContextChanges = new Map(\n      Array.from(aggregatedChanges.contextChanges.entries()).filter(([, value]) => value.kind === \"initialized\").map(([key, value]) => [\n        key,\n        // oxlint-disable-next-line typescript/no-non-null-assertion\n        value.kind === \"partially-initialized\" ? null : value.changes\n      ])\n    );\n    const fiber = Store.inspectState.value.kind === \"focused\" ? Store.inspectState.value.fiber : null;\n    if (!fiber) {\n      return;\n    }\n    return /* @__PURE__ */ u4(k, { children: [\n      /* @__PURE__ */ u4(WhatsChangedHeader, {}),\n      /* @__PURE__ */ u4(\"div\", { className: \"overflow-hidden h-full flex flex-col gap-y-2\", children: [\n        /* @__PURE__ */ u4(\"div\", { className: \"flex flex-col gap-2 px-3 pt-2\", children: [\n          /* @__PURE__ */ u4(\"span\", { className: \"text-sm font-medium text-[#888]\", children: [\n            \"Why did\",\n            \" \",\n            /* @__PURE__ */ u4(\"span\", { className: \"text-[#A855F7]\", children: getDisplayName(fiber) }),\n            \" \",\n            \"render?\"\n          ] }),\n          !hasAnyChanges && /* @__PURE__ */ u4(\"div\", { className: \"text-sm text-[#737373] bg-[#1E1E1E] rounded-md p-4 flex flex-col gap-4\", children: [\n            /* @__PURE__ */ u4(\"div\", { children: \"No changes detected since selecting\" }),\n            /* @__PURE__ */ u4(\"div\", { children: \"The props, state, and context changes within your component will be reported here\" })\n          ] })\n        ] }),\n        /* @__PURE__ */ u4(\n          \"div\",\n          {\n            className: cn2(\n              \"flex flex-col gap-y-2 pl-3 relative overflow-y-auto h-full\"\n            ),\n            children: [\n              /* @__PURE__ */ u4(\n                Section,\n                {\n                  changes: aggregatedChanges.propsChanges,\n                  title: \"Changed Props\",\n                  isExpanded\n                }\n              ),\n              /* @__PURE__ */ u4(\n                Section,\n                {\n                  renderName: (name) => renderStateName(\n                    name,\n                    getDisplayName(getType(fiber)) ?? \"Unknown Component\"\n                  ),\n                  changes: aggregatedChanges.stateChanges,\n                  title: \"Changed State\",\n                  isExpanded\n                }\n              ),\n              /* @__PURE__ */ u4(\n                Section,\n                {\n                  changes: initializedContextChanges,\n                  title: \"Changed Context\",\n                  isExpanded\n                }\n              )\n            ]\n          }\n        )\n      ] })\n    ] });\n  });\n  var renderStateName = (key, componentName) => {\n    if (Number.isNaN(Number(key))) {\n      return key;\n    }\n    const n3 = Number.parseInt(key);\n    const getOrdinalSuffix = (num) => {\n      const lastDigit = num % 10;\n      const lastTwoDigits = num % 100;\n      if (lastTwoDigits >= 11 && lastTwoDigits <= 13) {\n        return \"th\";\n      }\n      switch (lastDigit) {\n        case 1:\n          return \"st\";\n        case 2:\n          return \"nd\";\n        case 3:\n          return \"rd\";\n        default:\n          return \"th\";\n      }\n    };\n    return /* @__PURE__ */ u4(\"span\", { className: \"truncate\", children: [\n      /* @__PURE__ */ u4(\"span\", { className: \"text-white\", children: [\n        n3,\n        getOrdinalSuffix(n3),\n        \" hook\",\n        \" \"\n      ] }),\n      /* @__PURE__ */ u4(\"span\", { style: { color: \"#666\" }, children: [\n        \"called in \",\n        /* @__PURE__ */ u4(\"i\", { className: \"text-[#A855F7] truncate\", children: componentName })\n      ] })\n    ] });\n  };\n  var WhatsChangedHeader = M2(() => {\n    const refProps = A2(null);\n    const refState = A2(null);\n    const refContext = A2(null);\n    const refStats = A2({\n      isPropsChanged: false,\n      isStateChanged: false,\n      isContextChanged: false\n    });\n    y2(() => {\n      const flash = throttle(() => {\n        const flashElements = [];\n        if (refProps.current?.dataset.flash === \"true\") {\n          flashElements.push(refProps.current);\n        }\n        if (refState.current?.dataset.flash === \"true\") {\n          flashElements.push(refState.current);\n        }\n        if (refContext.current?.dataset.flash === \"true\") {\n          flashElements.push(refContext.current);\n        }\n        for (const element of flashElements) {\n          element.classList.remove(\"count-flash-white\");\n          void element.offsetWidth;\n          element.classList.add(\"count-flash-white\");\n        }\n      }, 400);\n      const unsubscribe = timelineState.subscribe((state) => {\n        if (!refProps.current || !refState.current || !refContext.current) {\n          return;\n        }\n        const { currentIndex, updates } = state;\n        const currentUpdate = updates[currentIndex];\n        if (!currentUpdate || currentIndex === 0) {\n          return;\n        }\n        flash();\n        refStats.current = {\n          isPropsChanged: (currentUpdate.props?.changes?.size ?? 0) > 0,\n          isStateChanged: (currentUpdate.state?.changes?.size ?? 0) > 0,\n          isContextChanged: (currentUpdate.context?.changes?.size ?? 0) > 0\n        };\n        if (refProps.current.dataset.flash !== \"true\") {\n          refProps.current.dataset.flash = refStats.current.isPropsChanged.toString();\n        }\n        if (refState.current.dataset.flash !== \"true\") {\n          refState.current.dataset.flash = refStats.current.isStateChanged.toString();\n        }\n        if (refContext.current.dataset.flash !== \"true\") {\n          refContext.current.dataset.flash = refStats.current.isContextChanged.toString();\n        }\n      });\n      return unsubscribe;\n    }, []);\n    return /* @__PURE__ */ u4(\n      \"button\",\n      {\n        type: \"button\",\n        className: cn2(\n          \"react-section-header\",\n          \"overflow-hidden\",\n          \"max-h-0\",\n          \"transition-[max-height]\"\n        ),\n        children: /* @__PURE__ */ u4(\"div\", { className: cn2(\"flex-1 react-scan-expandable\"), children: /* @__PURE__ */ u4(\"div\", { className: \"overflow-hidden\", children: /* @__PURE__ */ u4(\"div\", { className: \"flex items-center whitespace-nowrap\", children: [\n          /* @__PURE__ */ u4(\"div\", { className: \"flex items-center gap-x-2\", children: \"What changed?\" }),\n          /* @__PURE__ */ u4(\n            \"div\",\n            {\n              className: cn2(\n                \"ml-auto\",\n                \"change-scope\",\n                \"transition-opacity duration-300 delay-150\"\n              ),\n              children: [\n                /* @__PURE__ */ u4(\"div\", { ref: refProps, children: \"props\" }),\n                /* @__PURE__ */ u4(\"div\", { ref: refState, children: \"state\" }),\n                /* @__PURE__ */ u4(\"div\", { ref: refContext, children: \"context\" })\n              ]\n            }\n          )\n        ] }) }) })\n      }\n    );\n  });\n  var identity = (x4) => x4;\n  var Section = /* @__PURE__ */ M2(\n    ({ title, changes, renderName = identity }) => {\n      const [expandedFns, setExpandedFns] = d2(/* @__PURE__ */ new Set());\n      const [expandedEntries, setExpandedEntries] = d2(/* @__PURE__ */ new Set());\n      const entries = Array.from(changes.entries());\n      if (changes.size === 0) {\n        return null;\n      }\n      return /* @__PURE__ */ u4(\"div\", { children: [\n        /* @__PURE__ */ u4(\"div\", { className: \"text-xs text-[#888] mb-1.5\", children: title }),\n        /* @__PURE__ */ u4(\"div\", { className: \"flex flex-col gap-2\", children: entries.map(([entryKey, change]) => {\n          const isEntryExpanded = expandedEntries.has(String(entryKey));\n          const { value: prevValue, error: prevError } = safeGetValue(\n            change.previousValue\n          );\n          const { value: currValue, error: currError } = safeGetValue(\n            change.currentValue\n          );\n          const diff = getObjectDiff(prevValue, currValue);\n          return /* @__PURE__ */ u4(\"div\", { children: [\n            /* @__PURE__ */ u4(\n              \"button\",\n              {\n                onClick: () => {\n                  setExpandedEntries((prev) => {\n                    const next = new Set(prev);\n                    if (next.has(String(entryKey))) {\n                      next.delete(String(entryKey));\n                    } else {\n                      next.add(String(entryKey));\n                    }\n                    return next;\n                  });\n                },\n                className: \"flex items-center gap-2 w-full bg-transparent border-none p-0 cursor-pointer text-white text-xs\",\n                children: /* @__PURE__ */ u4(\"div\", { className: \"flex items-center gap-1.5 flex-1\", children: [\n                  /* @__PURE__ */ u4(\n                    Icon,\n                    {\n                      name: \"icon-chevron-right\",\n                      size: 12,\n                      className: cn2(\n                        \"text-[#666] transition-transform duration-200 ease-[cubic-bezier(0.25,0.1,0.25,1)]\",\n                        {\n                          \"rotate-90\": isEntryExpanded\n                        }\n                      )\n                    }\n                  ),\n                  /* @__PURE__ */ u4(\"div\", { className: \"whitespace-pre-wrap break-words text-left font-medium flex items-center gap-x-1.5\", children: [\n                    renderName(change.name),\n                    /* @__PURE__ */ u4(\n                      CountBadge,\n                      {\n                        count: change.count,\n                        isFunction: typeof change.currentValue === \"function\",\n                        showWarning: diff.changes.length === 0,\n                        forceFlash: true\n                      }\n                    )\n                  ] })\n                ] })\n              }\n            ),\n            /* @__PURE__ */ u4(\n              \"div\",\n              {\n                className: cn2(\"react-scan-expandable\", {\n                  \"react-scan-expanded\": isEntryExpanded\n                }),\n                children: /* @__PURE__ */ u4(\"div\", { className: \"pl-3 text-xs font-mono border-l-1 border-[#333]\", children: /* @__PURE__ */ u4(\"div\", { className: \"flex flex-col gap-0.5\", children: prevError || currError ? /* @__PURE__ */ u4(\n                  AccessError,\n                  {\n                    currError,\n                    prevError\n                  }\n                ) : diff.changes.length > 0 ? /* @__PURE__ */ u4(\n                  DiffChange,\n                  {\n                    change,\n                    diff,\n                    expandedFns,\n                    renderName,\n                    setExpandedFns,\n                    title\n                  }\n                ) : /* @__PURE__ */ u4(\n                  ReferenceOnlyChange,\n                  {\n                    currValue,\n                    entryKey,\n                    expandedFns,\n                    prevValue,\n                    setExpandedFns\n                  }\n                ) }) })\n              }\n            )\n          ] }, entryKey);\n        }) })\n      ] });\n    }\n  );\n  var AccessError = ({\n    prevError,\n    currError\n  }) => {\n    return /* @__PURE__ */ u4(k, { children: [\n      prevError && /* @__PURE__ */ u4(\"div\", { className: \"text-[#f87171] bg-[#2a1515] pr-1.5 py-[3px] rounded italic\", children: prevError }),\n      currError && /* @__PURE__ */ u4(\"div\", { className: \"text-[#4ade80] bg-[#1a2a1a] pr-1.5 py-[3px] rounded italic mt-0.5\", children: currError })\n    ] });\n  };\n  var DiffChange = ({\n    diff,\n    title,\n    renderName,\n    change,\n    expandedFns,\n    setExpandedFns\n  }) => {\n    return diff.changes.map((diffChange, i5) => {\n      const { value: prevDiffValue, error: prevDiffError } = safeGetValue(\n        diffChange.prevValue\n      );\n      const { value: currDiffValue, error: currDiffError } = safeGetValue(\n        diffChange.currentValue\n      );\n      const isFunction = typeof prevDiffValue === \"function\" || typeof currDiffValue === \"function\";\n      let path;\n      if (title === \"Props\") {\n        path = diffChange.path.length > 0 ? `${renderName(String(change.name))}.${formatPath(diffChange.path)}` : void 0;\n      }\n      if (title === \"State\" && diffChange.path.length > 0) {\n        path = `state.${formatPath(diffChange.path)}`;\n      }\n      if (!path) {\n        path = formatPath(diffChange.path);\n      }\n      return /* @__PURE__ */ u4(\n        \"div\",\n        {\n          className: cn2(\n            \"flex flex-col gap-y-1\",\n            i5 < diff.changes.length - 1 && \"mb-4\"\n          ),\n          children: [\n            path && /* @__PURE__ */ u4(\"div\", { className: \"text-[#666] text-[10px]\", children: path }),\n            /* @__PURE__ */ u4(\n              \"button\",\n              {\n                type: \"button\",\n                className: cn2(\n                  \"group\",\n                  \"flex items-start\",\n                  \"py-[3px] px-1.5\",\n                  \"text-left text-[#f87171] bg-[#2a1515]\",\n                  \"rounded\",\n                  \"overflow-hidden break-all\",\n                  isFunction && \"cursor-pointer\"\n                ),\n                onClick: isFunction ? () => {\n                  const fnKey = `${formatPath(diffChange.path)}-prev`;\n                  setExpandedFns((prev) => {\n                    const next = new Set(prev);\n                    if (next.has(fnKey)) {\n                      next.delete(fnKey);\n                    } else {\n                      next.add(fnKey);\n                    }\n                    return next;\n                  });\n                } : void 0,\n                children: [\n                  /* @__PURE__ */ u4(\"span\", { className: \"w-3 flex items-center justify-center opacity-50\", children: \"-\" }),\n                  /* @__PURE__ */ u4(\"span\", { className: \"flex-1 whitespace-nowrap font-mono\", children: prevDiffError ? /* @__PURE__ */ u4(\"span\", { className: \"italic text-[#f87171]\", children: prevDiffError }) : isFunction ? /* @__PURE__ */ u4(\"div\", { className: \"flex gap-1 items-start flex-col\", children: [\n                    /* @__PURE__ */ u4(\"div\", { className: \"flex gap-1 items-start w-full\", children: [\n                      /* @__PURE__ */ u4(\"span\", { className: \"flex-1 max-h-40\", children: formatFunctionPreview(\n                        prevDiffValue,\n                        expandedFns.has(`${formatPath(diffChange.path)}-prev`)\n                      ) }),\n                      typeof prevDiffValue === \"function\" && /* @__PURE__ */ u4(\n                        CopyToClipboard,\n                        {\n                          text: prevDiffValue.toString(),\n                          className: \"opacity-0 transition-opacity group-hover:opacity-100\",\n                          children: ({ ClipboardIcon }) => /* @__PURE__ */ u4(k, { children: ClipboardIcon })\n                        }\n                      )\n                    ] }),\n                    prevDiffValue?.toString() === currDiffValue?.toString() && /* @__PURE__ */ u4(\"div\", { className: \"text-[10px] text-[#666] italic\", children: \"Function reference changed\" })\n                  ] }) : /* @__PURE__ */ u4(\n                    DiffValueView,\n                    {\n                      value: prevDiffValue,\n                      expanded: expandedFns.has(\n                        `${formatPath(diffChange.path)}-prev`\n                      ),\n                      onToggle: () => {\n                        const key = `${formatPath(diffChange.path)}-prev`;\n                        setExpandedFns((prev) => {\n                          const next = new Set(prev);\n                          if (next.has(key)) {\n                            next.delete(key);\n                          } else {\n                            next.add(key);\n                          }\n                          return next;\n                        });\n                      },\n                      isNegative: true\n                    }\n                  ) })\n                ]\n              }\n            ),\n            /* @__PURE__ */ u4(\n              \"button\",\n              {\n                type: \"button\",\n                className: cn2(\n                  \"group\",\n                  \"flex items-start\",\n                  \"py-[3px] px-1.5\",\n                  \"text-left text-[#4ade80] bg-[#1a2a1a]\",\n                  \"rounded\",\n                  \"overflow-hidden break-all\",\n                  isFunction && \"cursor-pointer\"\n                ),\n                onClick: isFunction ? () => {\n                  const fnKey = `${formatPath(diffChange.path)}-current`;\n                  setExpandedFns((prev) => {\n                    const next = new Set(prev);\n                    if (next.has(fnKey)) {\n                      next.delete(fnKey);\n                    } else {\n                      next.add(fnKey);\n                    }\n                    return next;\n                  });\n                } : void 0,\n                children: [\n                  /* @__PURE__ */ u4(\"span\", { className: \"w-3 flex items-center justify-center opacity-50\", children: \"+\" }),\n                  /* @__PURE__ */ u4(\"span\", { className: \"flex-1 whitespace-pre-wrap font-mono\", children: currDiffError ? /* @__PURE__ */ u4(\"span\", { className: \"italic text-[#4ade80]\", children: currDiffError }) : isFunction ? /* @__PURE__ */ u4(\"div\", { className: \"flex gap-1 items-start flex-col\", children: [\n                    /* @__PURE__ */ u4(\"div\", { className: \"flex gap-1 items-start w-full\", children: [\n                      /* @__PURE__ */ u4(\"span\", { className: \"flex-1\", children: formatFunctionPreview(\n                        currDiffValue,\n                        expandedFns.has(`${formatPath(diffChange.path)}-current`)\n                      ) }),\n                      typeof currDiffValue === \"function\" && /* @__PURE__ */ u4(\n                        CopyToClipboard,\n                        {\n                          text: currDiffValue.toString(),\n                          className: \"opacity-0 transition-opacity group-hover:opacity-100\",\n                          children: ({ ClipboardIcon }) => /* @__PURE__ */ u4(k, { children: ClipboardIcon })\n                        }\n                      )\n                    ] }),\n                    prevDiffValue?.toString() === currDiffValue?.toString() && /* @__PURE__ */ u4(\"div\", { className: \"text-[10px] text-[#666] italic\", children: \"Function reference changed\" })\n                  ] }) : /* @__PURE__ */ u4(\n                    DiffValueView,\n                    {\n                      value: currDiffValue,\n                      expanded: expandedFns.has(\n                        `${formatPath(diffChange.path)}-current`\n                      ),\n                      onToggle: () => {\n                        const key = `${formatPath(diffChange.path)}-current`;\n                        setExpandedFns((prev) => {\n                          const next = new Set(prev);\n                          if (next.has(key)) {\n                            next.delete(key);\n                          } else {\n                            next.add(key);\n                          }\n                          return next;\n                        });\n                      },\n                      isNegative: false\n                    }\n                  ) })\n                ]\n              }\n            )\n          ]\n        },\n        `${path}-${change.name}-${i5}`\n      );\n    });\n  };\n  var ReferenceOnlyChange = ({\n    prevValue,\n    currValue,\n    entryKey,\n    expandedFns,\n    setExpandedFns\n  }) => {\n    return /* @__PURE__ */ u4(k, { children: [\n      /* @__PURE__ */ u4(\"div\", { className: \"group flex gap-0.5 items-start text-[#f87171] bg-[#2a1515] py-[3px] px-1.5 rounded\", children: [\n        /* @__PURE__ */ u4(\"span\", { className: \"w-3 flex items-center justify-center opacity-50\", children: \"-\" }),\n        /* @__PURE__ */ u4(\"span\", { className: \"flex-1 overflow-hidden whitespace-pre-wrap font-mono\", children: /* @__PURE__ */ u4(\n          DiffValueView,\n          {\n            value: prevValue,\n            expanded: expandedFns.has(`${String(entryKey)}-prev`),\n            onToggle: () => {\n              const key = `${String(entryKey)}-prev`;\n              setExpandedFns((prev) => {\n                const next = new Set(prev);\n                if (next.has(key)) {\n                  next.delete(key);\n                } else {\n                  next.add(key);\n                }\n                return next;\n              });\n            },\n            isNegative: true\n          }\n        ) })\n      ] }),\n      /* @__PURE__ */ u4(\"div\", { className: \"group flex gap-0.5 items-start text-[#4ade80] bg-[#1a2a1a] py-[3px] px-1.5 rounded mt-0.5\", children: [\n        /* @__PURE__ */ u4(\"span\", { className: \"w-3 flex items-center justify-center opacity-50\", children: \"+\" }),\n        /* @__PURE__ */ u4(\"span\", { className: \"flex-1 overflow-hidden whitespace-pre-wrap font-mono\", children: /* @__PURE__ */ u4(\n          DiffValueView,\n          {\n            value: currValue,\n            expanded: expandedFns.has(`${String(entryKey)}-current`),\n            onToggle: () => {\n              const key = `${String(entryKey)}-current`;\n              setExpandedFns((prev) => {\n                const next = new Set(prev);\n                if (next.has(key)) {\n                  next.delete(key);\n                } else {\n                  next.add(key);\n                }\n                return next;\n              });\n            },\n            isNegative: false\n          }\n        ) })\n      ] }),\n      typeof currValue === \"object\" && currValue !== null && /* @__PURE__ */ u4(\"div\", { className: \"text-[#666] text-[10px] italic mt-1 flex items-center gap-x-1\", children: [\n        /* @__PURE__ */ u4(\n          Icon,\n          {\n            name: \"icon-triangle-alert\",\n            className: \"text-yellow-500 mb-px\",\n            size: 14\n          }\n        ),\n        /* @__PURE__ */ u4(\"span\", { children: \"Reference changed but objects are structurally the same\" })\n      ] })\n    ] });\n  };\n  var CountBadge = ({\n    count,\n    forceFlash,\n    isFunction,\n    showWarning\n  }) => {\n    const refIsFirstRender = A2(true);\n    const refBadge = A2(null);\n    const refPrevCount = A2(count);\n    y2(() => {\n      const element = refBadge.current;\n      if (!element || refPrevCount.current === count) {\n        return;\n      }\n      element.classList.remove(\"count-flash\");\n      void element.offsetWidth;\n      element.classList.add(\"count-flash\");\n      refPrevCount.current = count;\n    }, [count]);\n    y2(() => {\n      if (refIsFirstRender.current) {\n        refIsFirstRender.current = false;\n        return;\n      }\n      if (forceFlash) {\n        let timer = setTimeout(() => {\n          refBadge.current?.classList.add(\"count-flash-white\");\n          timer = setTimeout(() => {\n            refBadge.current?.classList.remove(\"count-flash-white\");\n          }, 300);\n        }, 500);\n        return () => {\n          clearTimeout(timer);\n        };\n      }\n    }, [forceFlash]);\n    return /* @__PURE__ */ u4(\"div\", { ref: refBadge, className: \"count-badge\", children: [\n      showWarning && /* @__PURE__ */ u4(\n        Icon,\n        {\n          name: \"icon-triangle-alert\",\n          className: \"text-yellow-500 mb-px\",\n          size: 14\n        }\n      ),\n      isFunction && /* @__PURE__ */ u4(Icon, { name: \"icon-function\", className: \"text-[#A855F7] mb-px\", size: 14 }),\n      \"x\",\n      count\n    ] });\n  };\n\n  // src/web/views/inspector/index.tsx\n  var globalInspectorState = {\n    lastRendered: /* @__PURE__ */ new Map(),\n    expandedPaths: /* @__PURE__ */ new Set(),\n    cleanup: () => {\n      globalInspectorState.lastRendered.clear();\n      globalInspectorState.expandedPaths.clear();\n      flashManager.cleanupAll();\n      resetTracking();\n      timelineActions.reset();\n    }\n  };\n  var InspectorErrorBoundary = class extends x {\n    constructor() {\n      super(...arguments);\n      this.state = {\n        hasError: false,\n        error: null\n      };\n      this.handleReset = () => {\n        this.setState({ hasError: false, error: null });\n        globalInspectorState.cleanup();\n      };\n    }\n    static getDerivedStateFromError(e4) {\n      return { hasError: true, error: e4 };\n    }\n    render() {\n      if (this.state.hasError) {\n        return /* @__PURE__ */ u4(\"div\", { className: \"p-4 bg-red-950/50 h-screen backdrop-blur-sm\", children: [\n          /* @__PURE__ */ u4(\"div\", { className: \"flex items-center gap-2 mb-3 text-red-400 font-medium\", children: [\n            /* @__PURE__ */ u4(Icon, { name: \"icon-flame\", className: \"text-red-500\", size: 16 }),\n            \"Something went wrong in the inspector\"\n          ] }),\n          /* @__PURE__ */ u4(\"div\", { className: \"p-3 bg-black/40 rounded font-mono text-xs text-red-300 mb-4 break-words\", children: this.state.error?.message || JSON.stringify(this.state.error) }),\n          /* @__PURE__ */ u4(\n            \"button\",\n            {\n              type: \"button\",\n              onClick: this.handleReset,\n              className: \"px-4 py-2 bg-red-500 hover:bg-red-600 text-white rounded-md text-sm font-medium transition-colors flex items-center justify-center gap-2\",\n              children: \"Reset Inspector\"\n            }\n          )\n        ] });\n      }\n      return this.props.children;\n    }\n  };\n  var inspectorContainerClassName = w3(\n    () => cn2(\n      \"react-scan-inspector\",\n      \"flex-1\",\n      \"opacity-0\",\n      \"overflow-y-auto overflow-x-hidden\",\n      \"transition-opacity delay-0\",\n      \"pointer-events-none\",\n      !signalIsSettingsOpen.value && \"opacity-100 delay-300 pointer-events-auto\"\n    )\n  );\n  var Inspector = /* @__PURE__ */ constant(() => {\n    const refLastInspectedFiber = A2(null);\n    const processUpdate = (fiber) => {\n      if (!fiber) return;\n      refLastInspectedFiber.current = fiber;\n      const { data: inspectorData, shouldUpdate } = collectInspectorData(fiber);\n      if (shouldUpdate) {\n        const update = {\n          timestamp: Date.now(),\n          fiberInfo: extractMinimalFiberInfo(fiber),\n          props: inspectorData.fiberProps,\n          state: inspectorData.fiberState,\n          context: inspectorData.fiberContext,\n          stateNames: getStateNames(fiber)\n        };\n        timelineActions.addUpdate(update, fiber);\n      }\n    };\n    useSignalEffect(() => {\n      const state = Store.inspectState.value;\n      n2(() => {\n        if (state.kind !== \"focused\" || !state.focusedDomElement) {\n          refLastInspectedFiber.current = null;\n          globalInspectorState.cleanup();\n          return;\n        }\n        if (state.kind === \"focused\") {\n          signalIsSettingsOpen.value = false;\n        }\n        const { parentCompositeFiber } = getCompositeFiberFromElement(\n          state.focusedDomElement,\n          state.fiber\n        );\n        if (!parentCompositeFiber) {\n          Store.inspectState.value = {\n            kind: \"inspect-off\"\n          };\n          signalWidgetViews.value = {\n            view: \"none\"\n          };\n          return;\n        }\n        const isNewComponent = refLastInspectedFiber.current?.type !== parentCompositeFiber.type;\n        if (isNewComponent) {\n          refLastInspectedFiber.current = parentCompositeFiber;\n          globalInspectorState.cleanup();\n          processUpdate(parentCompositeFiber);\n        }\n      });\n    });\n    useSignalEffect(() => {\n      inspectorUpdateSignal.value;\n      n2(() => {\n        const inspectState = Store.inspectState.value;\n        if (inspectState.kind !== \"focused\" || !inspectState.focusedDomElement) {\n          refLastInspectedFiber.current = null;\n          globalInspectorState.cleanup();\n          return;\n        }\n        const { parentCompositeFiber } = getCompositeFiberFromElement(\n          inspectState.focusedDomElement,\n          inspectState.fiber\n        );\n        if (!parentCompositeFiber) {\n          Store.inspectState.value = {\n            kind: \"inspect-off\"\n          };\n          signalWidgetViews.value = {\n            view: \"none\"\n          };\n          return;\n        }\n        processUpdate(parentCompositeFiber);\n        if (!inspectState.focusedDomElement.isConnected) {\n          refLastInspectedFiber.current = null;\n          globalInspectorState.cleanup();\n          Store.inspectState.value = {\n            kind: \"inspecting\",\n            hoveredDomElement: null\n          };\n        }\n      });\n    });\n    y2(() => {\n      return () => {\n        globalInspectorState.cleanup();\n      };\n    }, []);\n    return /* @__PURE__ */ u4(InspectorErrorBoundary, { children: /* @__PURE__ */ u4(\"div\", { className: inspectorContainerClassName, children: /* @__PURE__ */ u4(\"div\", { className: \"w-full h-full\", children: /* @__PURE__ */ u4(WhatChanged, {}) }) }) });\n  });\n  var ViewInspector = /* @__PURE__ */ constant(() => {\n    if (Store.inspectState.value.kind !== \"focused\") return null;\n    return /* @__PURE__ */ u4(InspectorErrorBoundary, { children: [\n      /* @__PURE__ */ u4(Inspector, {}),\n      /* @__PURE__ */ u4(ComponentsTree, {})\n    ] });\n  });\n\n  // src/web/views/inspector/utils.ts\n  var getFiberFromElement = (element) => {\n    if (\"__REACT_DEVTOOLS_GLOBAL_HOOK__\" in window) {\n      const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;\n      if (!hook?.renderers) return null;\n      for (const [, renderer] of Array.from(hook.renderers)) {\n        try {\n          const fiber = renderer.findFiberByHostInstance?.(element);\n          if (fiber) return fiber;\n        } catch {\n        }\n      }\n    }\n    if (\"_reactRootContainer\" in element) {\n      const elementWithRoot = element;\n      const rootContainer2 = elementWithRoot._reactRootContainer;\n      return rootContainer2?._internalRoot?.current?.child ?? null;\n    }\n    for (const key in element) {\n      if (key.startsWith(\"__reactInternalInstance$\") || key.startsWith(\"__reactFiber\")) {\n        const elementWithFiber = element;\n        return elementWithFiber[key];\n      }\n    }\n    return null;\n  };\n  var getFirstStateNode = (fiber) => {\n    let current = fiber;\n    while (current) {\n      if (current.stateNode instanceof Element) {\n        return current.stateNode;\n      }\n      if (!current.child) {\n        break;\n      }\n      current = current.child;\n    }\n    while (current) {\n      if (current.stateNode instanceof Element) {\n        return current.stateNode;\n      }\n      if (!current.return) {\n        break;\n      }\n      current = current.return;\n    }\n    return null;\n  };\n  var getNearestFiberFromElement = (element) => {\n    if (!element) return null;\n    try {\n      const fiber = getFiberFromElement(element);\n      if (!fiber) return null;\n      const res = getParentCompositeFiber(fiber);\n      return res ? res[0] : null;\n    } catch {\n      return null;\n    }\n  };\n  var getParentCompositeFiber = (fiber) => {\n    let current = fiber;\n    let prevHost = null;\n    while (current) {\n      if (isCompositeFiber(current)) return [current, prevHost];\n      if (isHostFiber(current) && !prevHost) prevHost = current;\n      current = current.return;\n    }\n    return null;\n  };\n  var isFiberInTree = (fiber, root) => {\n    {\n      const res = !!traverseFiber(root, (searchFiber) => searchFiber === fiber);\n      return res;\n    }\n  };\n  var getAssociatedFiberRect = async (element) => {\n    const associatedFiber = getNearestFiberFromElement(element);\n    if (!associatedFiber) return null;\n    const stateNode = getFirstStateNode(associatedFiber);\n    if (!stateNode) return null;\n    const rect = await new Promise((resolve) => {\n      const observer = new IntersectionObserver((entries) => {\n        observer.disconnect();\n        resolve(entries[0]?.boundingClientRect ?? null);\n      });\n      observer.observe(stateNode);\n    });\n    return rect;\n  };\n  var getCompositeComponentFromElement = (element) => {\n    const associatedFiber = getNearestFiberFromElement(element);\n    if (!associatedFiber) return {};\n    const stateNode = getFirstStateNode(associatedFiber);\n    if (!stateNode) return {};\n    const parentCompositeFiberInfo = getParentCompositeFiber(associatedFiber);\n    if (!parentCompositeFiberInfo) {\n      return {};\n    }\n    const [parentCompositeFiber] = parentCompositeFiberInfo;\n    return {\n      parentCompositeFiber\n    };\n  };\n  var getCompositeFiberFromElement = (element, knownFiber) => {\n    if (!element.isConnected) return {};\n    let fiber = knownFiber ?? getNearestFiberFromElement(element);\n    if (!fiber) return {};\n    let curr = fiber;\n    let rootFiber = null;\n    let currentRootFiber = null;\n    while (curr) {\n      if (!curr.stateNode) {\n        curr = curr.return;\n        continue;\n      }\n      if (ReactScanInternals.instrumentation?.fiberRoots.has(curr.stateNode)) {\n        rootFiber = curr;\n        currentRootFiber = curr.stateNode.current;\n        break;\n      }\n      curr = curr.return;\n    }\n    if (!rootFiber || !currentRootFiber) return {};\n    fiber = isFiberInTree(fiber, currentRootFiber) ? fiber : fiber.alternate ?? fiber;\n    if (!fiber) return {};\n    if (!getFirstStateNode(fiber)) return {};\n    const parentCompositeFiber = getParentCompositeFiber(fiber)?.[0];\n    if (!parentCompositeFiber) return {};\n    return {\n      parentCompositeFiber: isFiberInTree(parentCompositeFiber, currentRootFiber) ? parentCompositeFiber : parentCompositeFiber.alternate ?? parentCompositeFiber\n    };\n  };\n  var getChangedPropsDetailed = (fiber) => {\n    const currentProps = fiber.memoizedProps ?? {};\n    const previousProps = fiber.alternate?.memoizedProps ?? {};\n    const changes = [];\n    for (const key in currentProps) {\n      if (key === \"children\") continue;\n      const currentValue = currentProps[key];\n      const prevValue = previousProps[key];\n      if (!isEqual(currentValue, prevValue)) {\n        changes.push({\n          name: key,\n          value: currentValue,\n          prevValue,\n          type: 1 /* Props */\n        });\n      }\n    }\n    return changes;\n  };\n  var nonVisualTags = /* @__PURE__ */ new Set([\n    \"HTML\",\n    \"HEAD\",\n    \"META\",\n    \"TITLE\",\n    \"BASE\",\n    \"SCRIPT\",\n    \"SCRIPT\",\n    \"STYLE\",\n    \"LINK\",\n    \"NOSCRIPT\",\n    \"SOURCE\",\n    \"TRACK\",\n    \"EMBED\",\n    \"OBJECT\",\n    \"PARAM\",\n    \"TEMPLATE\",\n    \"PORTAL\",\n    \"SLOT\",\n    \"AREA\",\n    \"XML\",\n    \"DOCTYPE\",\n    \"COMMENT\"\n  ]);\n  var findComponentDOMNode = (fiber, excludeNonVisualTags = true) => {\n    if (fiber.stateNode && \"nodeType\" in fiber.stateNode) {\n      const element = fiber.stateNode;\n      if (excludeNonVisualTags && element.tagName && nonVisualTags.has(element.tagName.toLowerCase())) {\n        return null;\n      }\n      return element;\n    }\n    let child = fiber.child;\n    while (child) {\n      const result = findComponentDOMNode(child, excludeNonVisualTags);\n      if (result) return result;\n      child = child.sibling;\n    }\n    return null;\n  };\n  var getInspectableElements = (root = document.body) => {\n    const result = [];\n    const findInspectableFiber = (element) => {\n      if (!element) return null;\n      const { parentCompositeFiber } = getCompositeComponentFromElement(element);\n      if (!parentCompositeFiber) return null;\n      const componentRoot = findComponentDOMNode(parentCompositeFiber);\n      return componentRoot === element ? element : null;\n    };\n    const traverse = (element, depth = 0) => {\n      const inspectable = findInspectableFiber(element);\n      if (inspectable) {\n        const { parentCompositeFiber } = getCompositeComponentFromElement(inspectable);\n        if (!parentCompositeFiber) return;\n        result.push({\n          element: inspectable,\n          depth,\n          name: getDisplayName(parentCompositeFiber.type) ?? \"Unknown\",\n          fiber: parentCompositeFiber\n        });\n      }\n      for (const child of Array.from(element.children)) {\n        traverse(child, inspectable ? depth + 1 : depth);\n      }\n    };\n    traverse(root);\n    return result;\n  };\n  var formatForClipboard = (value) => {\n    try {\n      if (value === null) return \"null\";\n      if (value === void 0) return \"undefined\";\n      if (isPromise(value)) return \"Promise\";\n      if (typeof value === \"function\") {\n        const fnStr = value.toString();\n        try {\n          const formatted = fnStr.replace(/\\s+/g, \" \").replace(/{\\s+/g, \"{\\n  \").replace(/;\\s+/g, \";\\n  \").replace(/}\\s*$/g, \"\\n}\").replace(/\\(\\s+/g, \"(\").replace(/\\s+\\)/g, \")\").replace(/,\\s+/g, \", \");\n          return formatted;\n        } catch {\n          return fnStr;\n        }\n      }\n      switch (true) {\n        case value instanceof Date:\n          return value.toISOString();\n        case value instanceof RegExp:\n          return value.toString();\n        case value instanceof Error:\n          return `${value.name}: ${value.message}`;\n        case value instanceof Map:\n          return JSON.stringify(Array.from(value.entries()), null, 2);\n        case value instanceof Set:\n          return JSON.stringify(Array.from(value), null, 2);\n        case value instanceof DataView:\n          return JSON.stringify(\n            Array.from(new Uint8Array(value.buffer)),\n            null,\n            2\n          );\n        case value instanceof ArrayBuffer:\n          return JSON.stringify(Array.from(new Uint8Array(value)), null, 2);\n        case (ArrayBuffer.isView(value) && \"length\" in value):\n          return JSON.stringify(\n            Array.from(value),\n            null,\n            2\n          );\n        case Array.isArray(value):\n          return JSON.stringify(value, null, 2);\n        case typeof value === \"object\":\n          return JSON.stringify(value, null, 2);\n        default:\n          return String(value);\n      }\n    } catch {\n      return String(value);\n    }\n  };\n  var areFunctionsEqual = (prev, current) => {\n    try {\n      if (typeof prev !== \"function\" || typeof current !== \"function\") {\n        return false;\n      }\n      return prev.toString() === current.toString();\n    } catch {\n      return false;\n    }\n  };\n  var getObjectDiff = (prev, current, path = [], seen = /* @__PURE__ */ new WeakSet()) => {\n    if (prev === current) {\n      return { type: \"primitive\", changes: [], hasDeepChanges: false };\n    }\n    if (typeof prev === \"function\" && typeof current === \"function\") {\n      const isSameFunction = areFunctionsEqual(prev, current);\n      return {\n        type: \"primitive\",\n        changes: [\n          {\n            path,\n            prevValue: prev,\n            currentValue: current,\n            sameFunction: isSameFunction\n          }\n        ],\n        hasDeepChanges: !isSameFunction\n      };\n    }\n    if (prev === null || current === null || prev === void 0 || current === void 0 || typeof prev !== \"object\" || typeof current !== \"object\") {\n      return {\n        type: \"primitive\",\n        changes: [{ path, prevValue: prev, currentValue: current }],\n        hasDeepChanges: true\n      };\n    }\n    if (seen.has(prev) || seen.has(current)) {\n      return {\n        type: \"object\",\n        changes: [{ path, prevValue: \"[Circular]\", currentValue: \"[Circular]\" }],\n        hasDeepChanges: false\n      };\n    }\n    seen.add(prev);\n    seen.add(current);\n    const prevObj = prev;\n    const currentObj = current;\n    const allKeys = /* @__PURE__ */ new Set([\n      ...Object.keys(prevObj),\n      ...Object.keys(currentObj)\n    ]);\n    const changes = [];\n    let hasDeepChanges = false;\n    for (const key of allKeys) {\n      const prevValue = prevObj[key];\n      const currentValue = currentObj[key];\n      if (prevValue !== currentValue) {\n        if (typeof prevValue === \"object\" && typeof currentValue === \"object\" && prevValue !== null && currentValue !== null) {\n          const nestedDiff = getObjectDiff(\n            prevValue,\n            currentValue,\n            [...path, key],\n            seen\n          );\n          changes.push(...nestedDiff.changes);\n          if (nestedDiff.hasDeepChanges) {\n            hasDeepChanges = true;\n          }\n        } else {\n          changes.push({\n            path: [...path, key],\n            prevValue,\n            currentValue\n          });\n          hasDeepChanges = true;\n        }\n      }\n    }\n    return {\n      type: \"object\",\n      changes,\n      hasDeepChanges\n    };\n  };\n  var formatPath = (path) => {\n    if (path.length === 0) return \"\";\n    return path.reduce((acc, segment, i5) => {\n      if (/^\\d+$/.test(segment)) {\n        return `${acc}[${segment}]`;\n      }\n      return i5 === 0 ? segment : `${acc}.${segment}`;\n    }, \"\");\n  };\n  function hackyJsFormatter(code) {\n    const normalizedCode = code.replace(/\\s+/g, \" \").trim();\n    const rawTokens = [];\n    let current = \"\";\n    for (let i5 = 0; i5 < normalizedCode.length; i5++) {\n      const c4 = normalizedCode[i5];\n      if (c4 === \"=\" && normalizedCode[i5 + 1] === \">\") {\n        if (current.trim()) rawTokens.push(current.trim());\n        rawTokens.push(\"=>\");\n        current = \"\";\n        i5++;\n        continue;\n      }\n      if (/[(){}[\\];,<>:\\?!]/.test(c4)) {\n        if (current.trim()) {\n          rawTokens.push(current.trim());\n        }\n        rawTokens.push(c4);\n        current = \"\";\n      } else if (/\\s/.test(c4)) {\n        if (current.trim()) {\n          rawTokens.push(current.trim());\n        }\n        current = \"\";\n      } else {\n        current += c4;\n      }\n    }\n    if (current.trim()) {\n      rawTokens.push(current.trim());\n    }\n    const merged = [];\n    for (let i5 = 0; i5 < rawTokens.length; i5++) {\n      const t4 = rawTokens[i5];\n      const n3 = rawTokens[i5 + 1];\n      if (t4 === \"(\" && n3 === \")\" || t4 === \"[\" && n3 === \"]\" || t4 === \"{\" && n3 === \"}\" || t4 === \"<\" && n3 === \">\") {\n        merged.push(t4 + n3);\n        i5++;\n      } else {\n        merged.push(t4);\n      }\n    }\n    const arrowParamSet = /* @__PURE__ */ new Set();\n    const genericSet = /* @__PURE__ */ new Set();\n    function findMatchingPair(openTok, closeTok, startIndex) {\n      let depth = 0;\n      for (let j4 = startIndex; j4 < merged.length; j4++) {\n        const token = merged[j4];\n        if (token === openTok) depth++;\n        else if (token === closeTok) {\n          depth--;\n          if (depth === 0) return j4;\n        }\n      }\n      return -1;\n    }\n    for (let i5 = 0; i5 < merged.length; i5++) {\n      const t4 = merged[i5];\n      if (t4 === \"(\") {\n        const closeIndex = findMatchingPair(\"(\", \")\", i5);\n        if (closeIndex !== -1 && merged[closeIndex + 1] === \"=>\") {\n          for (let k3 = i5; k3 <= closeIndex; k3++) {\n            arrowParamSet.add(k3);\n          }\n        }\n      }\n    }\n    for (let i5 = 1; i5 < merged.length; i5++) {\n      const prev = merged[i5 - 1];\n      const t4 = merged[i5];\n      if (/^[a-zA-Z0-9_$]+$/.test(prev) && t4 === \"<\") {\n        const closeIndex = findMatchingPair(\"<\", \">\", i5);\n        if (closeIndex !== -1) {\n          for (let k3 = i5; k3 <= closeIndex; k3++) {\n            genericSet.add(k3);\n          }\n        }\n      }\n    }\n    let indentLevel = 0;\n    const indentStr = \"  \";\n    const lines = [];\n    let line = \"\";\n    function pushLine() {\n      if (line.trim()) {\n        lines.push(line.replace(/\\s+$/, \"\"));\n      }\n      line = \"\";\n    }\n    function newLine() {\n      pushLine();\n      line = indentStr.repeat(indentLevel);\n    }\n    const stack = [];\n    function stackTop() {\n      return stack.length ? stack[stack.length - 1] : null;\n    }\n    function placeToken(tok, noSpaceBefore = false) {\n      if (!line.trim()) {\n        line += tok;\n      } else {\n        if (noSpaceBefore || /^[),;:\\].}>]$/.test(tok)) {\n          line += tok;\n        } else {\n          line += ` ${tok}`;\n        }\n      }\n    }\n    for (let i5 = 0; i5 < merged.length; i5++) {\n      const tok = merged[i5];\n      const next = merged[i5 + 1] || \"\";\n      if ([\"(\", \"{\", \"[\", \"<\"].includes(tok)) {\n        placeToken(tok);\n        stack.push(tok);\n        if (tok === \"{\") {\n          indentLevel++;\n          newLine();\n        } else if (tok === \"(\" || tok === \"[\" || tok === \"<\") {\n          if (arrowParamSet.has(i5) && tok === \"(\" || genericSet.has(i5) && tok === \"<\") ; else {\n            const directClose = {\n              \"(\": \")\",\n              \"[\": \"]\",\n              \"<\": \">\"\n            }[tok];\n            if (next !== directClose && next !== \"()\" && next !== \"[]\" && next !== \"<>\") {\n              indentLevel++;\n              newLine();\n            }\n          }\n        }\n      } else if ([\")\", \"}\", \"]\", \">\"].includes(tok)) {\n        const opening = stackTop();\n        if (tok === \")\" && opening === \"(\" || tok === \"]\" && opening === \"[\" || tok === \">\" && opening === \"<\") {\n          if (!(arrowParamSet.has(i5) && tok === \")\") && !(genericSet.has(i5) && tok === \">\")) {\n            indentLevel = Math.max(indentLevel - 1, 0);\n            newLine();\n          }\n        } else if (tok === \"}\" && opening === \"{\") {\n          indentLevel = Math.max(indentLevel - 1, 0);\n          newLine();\n        }\n        stack.pop();\n        placeToken(tok);\n        if (tok === \"}\") {\n          newLine();\n        }\n      } else if (/^\\(\\)|\\[\\]|\\{\\}|\\<\\>$/.test(tok)) {\n        placeToken(tok);\n      } else if (tok === \"=>\") {\n        placeToken(tok);\n      } else if (tok === \";\") {\n        placeToken(tok, true);\n        newLine();\n      } else if (tok === \",\") {\n        placeToken(tok, true);\n        const top = stackTop();\n        if (!(arrowParamSet.has(i5) && top === \"(\") && !(genericSet.has(i5) && top === \"<\")) {\n          if (top && [\"{\", \"[\", \"(\", \"<\"].includes(top)) {\n            newLine();\n          }\n        }\n      } else {\n        placeToken(tok);\n      }\n    }\n    pushLine();\n    return lines.join(\"\\n\").replace(/\\n\\s*\\n+/g, \"\\n\").trim();\n  }\n  var formatFunctionPreview = (fn2, expanded = false) => {\n    try {\n      const fnStr = fn2.toString();\n      const match = fnStr.match(\n        /(?:function\\s*)?(?:\\(([^)]*)\\)|([^=>\\s]+))\\s*=>?/\n      );\n      if (!match) return \"\\u0192\";\n      const params = match[1] || match[2] || \"\";\n      const cleanParams = params.replace(/\\s+/g, \"\");\n      if (!expanded) {\n        return `\\u0192 (${cleanParams}) => ...`;\n      }\n      return hackyJsFormatter(fnStr);\n    } catch {\n      return \"\\u0192\";\n    }\n  };\n  var formatValuePreview = (value) => {\n    if (value === null) return \"null\";\n    if (value === void 0) return \"undefined\";\n    if (typeof value === \"string\")\n      return `\"${value.length > 150 ? `${value.slice(0, 20)}...` : value}\"`;\n    if (typeof value === \"number\" || typeof value === \"boolean\")\n      return String(value);\n    if (typeof value === \"function\") return formatFunctionPreview(value);\n    if (Array.isArray(value)) return `Array(${value.length})`;\n    if (value instanceof Map) return `Map(${value.size})`;\n    if (value instanceof Set) return `Set(${value.size})`;\n    if (value instanceof Date) return value.toISOString();\n    if (value instanceof RegExp) return value.toString();\n    if (value instanceof Error) return `${value.name}: ${value.message}`;\n    if (typeof value === \"object\") {\n      const keys = Object.keys(value);\n      return `{${keys.length > 2 ? `${keys.slice(0, 2).join(\", \")}, ...` : keys.join(\", \")}}`;\n    }\n    return String(value);\n  };\n  var safeGetValue = (value) => {\n    if (value === null || value === void 0) return { value };\n    if (typeof value === \"function\") return { value };\n    if (typeof value !== \"object\") return { value };\n    if (isPromise(value)) {\n      return { value: \"Promise\" };\n    }\n    try {\n      const proto = Object.getPrototypeOf(value);\n      if (proto === Promise.prototype || proto?.constructor?.name === \"Promise\") {\n        return { value: \"Promise\" };\n      }\n      return { value };\n    } catch {\n      return { value: null, error: \"Error accessing value\" };\n    }\n  };\n  var isPromise = (value) => {\n    return !!value && (value instanceof Promise || typeof value === \"object\" && \"then\" in value);\n  };\n  var extractMinimalFiberInfo = (fiber) => {\n    const timings = getTimings(fiber);\n    return {\n      displayName: getDisplayName(fiber) || \"Unknown\",\n      type: fiber.type,\n      key: fiber.key,\n      id: fiber.index,\n      selfTime: timings?.selfTime ?? null,\n      totalTime: timings?.totalTime ?? null\n    };\n  };\n\n  // src/web/views/inspector/timeline/utils.ts\n  var propsTracker = /* @__PURE__ */ new Map();\n  var stateTracker = /* @__PURE__ */ new Map();\n  var contextTracker = /* @__PURE__ */ new Map();\n  var lastComponentType = null;\n  var STATE_NAME_REGEX = /\\[(?<name>\\w+),\\s*set\\w+\\]/g;\n  var getStateNames = (fiber) => {\n    const componentSource = fiber.type?.toString?.() || \"\";\n    return componentSource ? Array.from(\n      componentSource.matchAll(STATE_NAME_REGEX),\n      (m3) => m3.groups?.name ?? \"\"\n    ) : [];\n  };\n  var resetTracking = () => {\n    propsTracker.clear();\n    stateTracker.clear();\n    contextTracker.clear();\n    lastComponentType = null;\n  };\n  var isInitialComponentUpdate = (fiber) => {\n    const isNewComponent = fiber.type !== lastComponentType;\n    lastComponentType = fiber.type;\n    return isNewComponent;\n  };\n  var trackChange = (tracker, key, currentValue, previousValue) => {\n    const existing = tracker.get(key);\n    const isInitialValue = tracker === propsTracker || tracker === contextTracker;\n    const hasChanged = !isEqual(currentValue, previousValue);\n    if (!existing) {\n      tracker.set(key, {\n        count: hasChanged && isInitialValue ? 1 : 0,\n        currentValue,\n        previousValue,\n        lastUpdated: Date.now()\n      });\n      return {\n        hasChanged,\n        count: hasChanged && isInitialValue ? 1 : isInitialValue ? 0 : 1\n      };\n    }\n    if (!isEqual(existing.currentValue, currentValue)) {\n      const newCount = existing.count + 1;\n      tracker.set(key, {\n        count: newCount,\n        currentValue,\n        previousValue: existing.currentValue,\n        lastUpdated: Date.now()\n      });\n      return { hasChanged: true, count: newCount };\n    }\n    return { hasChanged: false, count: existing.count };\n  };\n  var getStateFromFiber = (fiber) => {\n    if (!fiber) return {};\n    if (fiber.tag === FunctionComponentTag || fiber.tag === ForwardRefTag || fiber.tag === SimpleMemoComponentTag || fiber.tag === MemoComponentTag) {\n      let memoizedState = fiber.memoizedState;\n      const state = {};\n      let index = 0;\n      while (memoizedState) {\n        if (memoizedState.queue && memoizedState.memoizedState !== void 0) {\n          state[index] = memoizedState.memoizedState;\n        }\n        memoizedState = memoizedState.next;\n        index++;\n      }\n      return state;\n    }\n    if (fiber.tag === ClassComponentTag) {\n      return fiber.memoizedState || {};\n    }\n    return {};\n  };\n  var collectPropsChanges = (fiber) => {\n    const currentProps = fiber.memoizedProps || {};\n    const prevProps = fiber.alternate?.memoizedProps || {};\n    const current = {};\n    const prev = {};\n    const allProps = Object.keys(currentProps);\n    for (const key of allProps) {\n      if (key in currentProps) {\n        current[key] = currentProps[key];\n        prev[key] = prevProps[key];\n      }\n    }\n    const changes = getChangedPropsDetailed(fiber).map((change) => ({\n      name: change.name,\n      value: change.value,\n      prevValue: change.prevValue\n    }));\n    return { current, prev, changes };\n  };\n  var collectStateChanges = (fiber) => {\n    const current = getStateFromFiber(fiber);\n    const prev = fiber.alternate ? getStateFromFiber(fiber.alternate) : {};\n    const changes = [];\n    for (const [index, value] of Object.entries(current)) {\n      const stateKey = fiber.tag === ClassComponentTag ? index : Number(index);\n      if (fiber.alternate && !isEqual(prev[index], value)) {\n        changes.push({\n          name: stateKey,\n          value,\n          prevValue: prev[index]\n        });\n      }\n    }\n    return { current, prev, changes };\n  };\n  var collectContextChanges = (fiber) => {\n    const currentContexts = getAllFiberContexts(fiber);\n    const prevContexts = fiber.alternate ? getAllFiberContexts(fiber.alternate) : /* @__PURE__ */ new Map();\n    const current = {};\n    const prev = {};\n    const changes = [];\n    const seenContexts = /* @__PURE__ */ new Set();\n    for (const [contextType, ctx2] of currentContexts) {\n      const name = ctx2.displayName;\n      const contextKey = contextType;\n      if (seenContexts.has(contextKey)) continue;\n      seenContexts.add(contextKey);\n      current[name] = ctx2.value;\n      const prevCtx = prevContexts.get(contextType);\n      if (prevCtx) {\n        prev[name] = prevCtx.value;\n        if (!isEqual(prevCtx.value, ctx2.value)) {\n          changes.push({\n            name,\n            value: ctx2.value,\n            prevValue: prevCtx.value,\n            contextType\n          });\n        }\n      }\n    }\n    return { current, prev, changes };\n  };\n  var collectInspectorData = (fiber) => {\n    const emptySection = () => ({\n      current: [],\n      changes: /* @__PURE__ */ new Set(),\n      changesCounts: /* @__PURE__ */ new Map()\n    });\n    if (!fiber) {\n      return {\n        data: {\n          fiberProps: emptySection(),\n          fiberState: emptySection(),\n          fiberContext: emptySection()\n        },\n        shouldUpdate: false\n      };\n    }\n    let hasNewChanges = false;\n    const isInitialUpdate = isInitialComponentUpdate(fiber);\n    const propsData = emptySection();\n    if (fiber.memoizedProps) {\n      const { current, changes } = collectPropsChanges(fiber);\n      for (const [key, value] of Object.entries(current)) {\n        propsData.current.push({\n          name: key,\n          value: isPromise(value) ? { type: \"promise\", displayValue: \"Promise\" } : value\n        });\n      }\n      for (const change of changes) {\n        const { hasChanged, count } = trackChange(\n          propsTracker,\n          change.name,\n          change.value,\n          change.prevValue\n        );\n        if (hasChanged) {\n          hasNewChanges = true;\n          propsData.changes.add(change.name);\n          propsData.changesCounts.set(change.name, count);\n        }\n      }\n    }\n    const stateData = emptySection();\n    const { current: stateCurrent, changes: stateChanges } = collectStateChanges(fiber);\n    for (const [index, value] of Object.entries(stateCurrent)) {\n      const stateKey = fiber.tag === ClassComponentTag ? index : Number(index);\n      stateData.current.push({ name: stateKey, value });\n    }\n    for (const change of stateChanges) {\n      const { hasChanged, count } = trackChange(\n        stateTracker,\n        change.name,\n        change.value,\n        change.prevValue\n      );\n      if (hasChanged) {\n        hasNewChanges = true;\n        stateData.changes.add(change.name);\n        stateData.changesCounts.set(change.name, count);\n      }\n    }\n    const contextData = emptySection();\n    const { current: contextCurrent, changes: contextChanges } = collectContextChanges(fiber);\n    for (const [name, value] of Object.entries(contextCurrent)) {\n      contextData.current.push({ name, value });\n    }\n    if (!isInitialUpdate) {\n      for (const change of contextChanges) {\n        const { hasChanged, count } = trackChange(\n          contextTracker,\n          change.name,\n          change.value,\n          change.prevValue\n        );\n        if (hasChanged) {\n          hasNewChanges = true;\n          contextData.changes.add(change.name);\n          contextData.changesCounts.set(change.name, count);\n        }\n      }\n    }\n    if (!hasNewChanges && !isInitialUpdate) {\n      propsData.changes.clear();\n      stateData.changes.clear();\n      contextData.changes.clear();\n    }\n    return {\n      data: {\n        fiberProps: propsData,\n        fiberState: stateData,\n        fiberContext: contextData\n      },\n      shouldUpdate: hasNewChanges || isInitialUpdate\n    };\n  };\n  var fiberContextsCache = /* @__PURE__ */ new WeakMap();\n  var getAllFiberContexts = (fiber) => {\n    if (!fiber) {\n      return /* @__PURE__ */ new Map();\n    }\n    const cachedContexts = fiberContextsCache.get(fiber);\n    if (cachedContexts) {\n      return cachedContexts;\n    }\n    const contexts = /* @__PURE__ */ new Map();\n    let currentFiber = fiber;\n    while (currentFiber) {\n      const dependencies = currentFiber.dependencies;\n      if (dependencies?.firstContext) {\n        let contextItem = dependencies.firstContext;\n        while (contextItem) {\n          const memoizedValue = contextItem.memoizedValue;\n          const displayName = contextItem.context?.displayName;\n          if (!contexts.has(memoizedValue)) {\n            contexts.set(contextItem.context, {\n              value: memoizedValue,\n              displayName: displayName ?? \"UnnamedContext\",\n              contextType: null\n            });\n          }\n          if (contextItem === contextItem.next) {\n            break;\n          }\n          contextItem = contextItem.next;\n        }\n      }\n      currentFiber = currentFiber.return;\n    }\n    fiberContextsCache.set(fiber, contexts);\n    return contexts;\n  };\n  var collectInspectorDataWithoutCounts = (fiber) => {\n    const emptySection = () => ({\n      current: [],\n      changes: /* @__PURE__ */ new Set(),\n      changesCounts: /* @__PURE__ */ new Map()\n    });\n    if (!fiber) {\n      return {\n        fiberProps: emptySection(),\n        fiberState: emptySection(),\n        fiberContext: emptySection()\n      };\n    }\n    const propsData = emptySection();\n    if (fiber.memoizedProps) {\n      const { current: current2, changes: changes2 } = collectPropsChanges(fiber);\n      for (const [key, value] of Object.entries(current2)) {\n        propsData.current.push({\n          name: key,\n          value: isPromise(value) ? { type: \"promise\", displayValue: \"Promise\" } : value\n        });\n      }\n      for (const change of changes2) {\n        propsData.changes.add(change.name);\n        propsData.changesCounts.set(change.name, 1);\n      }\n    }\n    const stateData = emptySection();\n    if (fiber.memoizedState) {\n      const { current: current2, changes: changes2 } = collectStateChanges(fiber);\n      for (const [key, value] of Object.entries(current2)) {\n        stateData.current.push({\n          name: key,\n          value: isPromise(value) ? { type: \"promise\", displayValue: \"Promise\" } : value\n        });\n      }\n      for (const change of changes2) {\n        stateData.changes.add(change.name);\n        stateData.changesCounts.set(change.name, 1);\n      }\n    }\n    const contextData = emptySection();\n    const { current, changes } = collectContextChanges(fiber);\n    for (const [key, value] of Object.entries(current)) {\n      contextData.current.push({\n        name: key,\n        value: isPromise(value) ? { type: \"promise\", displayValue: \"Promise\" } : value\n      });\n    }\n    for (const change of changes) {\n      contextData.changes.add(change.name);\n      contextData.changesCounts.set(change.name, 1);\n    }\n    return {\n      // data: {\n      fiberProps: propsData,\n      fiberState: stateData,\n      fiberContext: contextData\n      // },\n    };\n  };\n\n  // src/core/instrumentation.ts\n  var RENDER_PHASE_STRING_TO_ENUM = {\n    mount: 1 /* Mount */,\n    update: 2 /* Update */,\n    unmount: 4 /* Unmount */\n  };\n  var fps = 0;\n  var lastTime = performance.now();\n  var frameCount = 0;\n  var initedFps = false;\n  var updateFPS = () => {\n    frameCount++;\n    const now = performance.now();\n    if (now - lastTime >= 1e3) {\n      fps = frameCount;\n      frameCount = 0;\n      lastTime = now;\n    }\n    requestAnimationFrame(updateFPS);\n  };\n  var getFPS = () => {\n    if (!initedFps) {\n      initedFps = true;\n      updateFPS();\n      fps = 60;\n    }\n    return fps;\n  };\n  var getStateChanges = (fiber) => {\n    if (!fiber) return [];\n    const changes = [];\n    if (fiber.tag === FunctionComponentTag || fiber.tag === ForwardRefTag || fiber.tag === SimpleMemoComponentTag || fiber.tag === MemoComponentTag) {\n      let memoizedState = fiber.memoizedState;\n      let prevState = fiber.alternate?.memoizedState;\n      let index = 0;\n      while (memoizedState) {\n        if (memoizedState.queue && memoizedState.memoizedState !== void 0) {\n          const change = {\n            type: 2 /* FunctionalState */,\n            name: index.toString(),\n            value: memoizedState.memoizedState,\n            prevValue: prevState?.memoizedState\n          };\n          if (!isEqual(change.prevValue, change.value)) {\n            changes.push(change);\n          }\n        }\n        memoizedState = memoizedState.next;\n        prevState = prevState?.next;\n        index++;\n      }\n      return changes;\n    }\n    if (fiber.tag === ClassComponentTag) {\n      const change = {\n        type: 3 /* ClassState */,\n        name: \"state\",\n        value: fiber.memoizedState,\n        prevValue: fiber.alternate?.memoizedState\n      };\n      if (!isEqual(change.prevValue, change.value)) {\n        changes.push(change);\n      }\n      return changes;\n    }\n    return changes;\n  };\n  var lastContextId = 0;\n  var contextIdMap = /* @__PURE__ */ new WeakMap();\n  var getContextId = (contextFiber) => {\n    const existing = contextIdMap.get(contextFiber);\n    if (existing) {\n      return existing;\n    }\n    lastContextId++;\n    contextIdMap.set(contextFiber, lastContextId);\n    return lastContextId;\n  };\n  function getContextChangesTraversal(nextValue, prevValue) {\n    if (!nextValue || !prevValue) return;\n    const nextMemoizedValue = nextValue.memoizedValue;\n    const change = {\n      type: 4 /* Context */,\n      name: nextValue.context.displayName ?? \"Context.Provider\",\n      value: nextMemoizedValue,\n      contextType: getContextId(nextValue.context)\n      // unstable: false,\n    };\n    this.push(change);\n  }\n  var getContextChanges = (fiber) => {\n    const changes = [];\n    traverseContexts(fiber, getContextChangesTraversal.bind(changes));\n    return changes;\n  };\n  var instrumentationInstances = /* @__PURE__ */ new Map();\n  var inited = false;\n  var getAllInstances = () => Array.from(instrumentationInstances.values());\n  var RENDER_DEBOUNCE_MS = 16;\n  var renderDataMap = /* @__PURE__ */ new WeakMap();\n  function getFiberIdentifier(fiber) {\n    return String(getFiberId(fiber));\n  }\n  function getRenderData(fiber) {\n    const id = getFiberIdentifier(fiber);\n    const keyMap = renderDataMap.get(getType(fiber));\n    if (keyMap) {\n      return keyMap.get(id);\n    }\n    return void 0;\n  }\n  function setRenderData(fiber, value) {\n    const type = getType(fiber.type);\n    const id = getFiberIdentifier(fiber);\n    let keyMap = renderDataMap.get(type);\n    if (!keyMap) {\n      keyMap = /* @__PURE__ */ new Map();\n      renderDataMap.set(type, keyMap);\n    }\n    keyMap.set(id, value);\n  }\n  var trackRender = (fiber, fiberSelfTime, fiberTotalTime, hasChanges, hasDomMutations) => {\n    const currentTimestamp = Date.now();\n    const existingData = getRenderData(fiber);\n    if ((hasChanges || hasDomMutations) && (!existingData || currentTimestamp - (existingData.lastRenderTimestamp || 0) > RENDER_DEBOUNCE_MS)) {\n      const renderData = existingData || {\n        selfTime: 0,\n        totalTime: 0,\n        renderCount: 0,\n        lastRenderTimestamp: currentTimestamp\n      };\n      renderData.renderCount = (renderData.renderCount || 0) + 1;\n      renderData.selfTime = fiberSelfTime || 0;\n      renderData.totalTime = fiberTotalTime || 0;\n      renderData.lastRenderTimestamp = currentTimestamp;\n      setRenderData(fiber, { ...renderData });\n    }\n  };\n  var createInstrumentation = (instanceKey, config) => {\n    const instrumentation = {\n      // this will typically be false, but in cases where a user provides showToolbar: true, this will be true\n      isPaused: d3(!ReactScanInternals.options.value.enabled),\n      fiberRoots: /* @__PURE__ */ new WeakSet()\n    };\n    instrumentationInstances.set(instanceKey, {\n      key: instanceKey,\n      config,\n      instrumentation\n    });\n    if (!inited) {\n      inited = true;\n      instrument({\n        name: \"react-scan\",\n        onActive: config.onActive,\n        onCommitFiberRoot(_rendererID, root) {\n          instrumentation.fiberRoots.add(root);\n          const allInstances = getAllInstances();\n          for (const instance of allInstances) {\n            instance.config.onCommitStart();\n          }\n          traverseRenderedFibers(\n            root.current,\n            (fiber, phase) => {\n              const type = getType(fiber.type);\n              if (!type) return null;\n              const allInstances2 = getAllInstances();\n              const validInstancesIndicies = [];\n              for (let i5 = 0, len = allInstances2.length; i5 < len; i5++) {\n                const instance = allInstances2[i5];\n                if (!instance.config.isValidFiber(fiber)) continue;\n                validInstancesIndicies.push(i5);\n              }\n              if (!validInstancesIndicies.length) return null;\n              const changes = [];\n              if (allInstances2.some((instance) => instance.config.trackChanges)) {\n                const changesProps = collectPropsChanges(fiber).changes;\n                const changesState = collectStateChanges(fiber).changes;\n                const changesContext = collectContextChanges(fiber).changes;\n                changes.push.apply(\n                  null,\n                  changesProps.map(\n                    (change) => ({\n                      type: 1 /* Props */,\n                      name: change.name,\n                      value: change.value\n                    })\n                  )\n                );\n                for (const change of changesState) {\n                  if (fiber.tag === ClassComponentTag) {\n                    changes.push({\n                      type: 3 /* ClassState */,\n                      name: change.name.toString(),\n                      value: change.value\n                    });\n                  } else {\n                    changes.push({\n                      type: 2 /* FunctionalState */,\n                      name: change.name.toString(),\n                      value: change.value\n                    });\n                  }\n                }\n                changes.push.apply(\n                  null,\n                  changesContext.map(\n                    (change) => ({\n                      type: 4 /* Context */,\n                      name: change.name,\n                      value: change.value,\n                      contextType: Number(change.contextType)\n                    })\n                  )\n                );\n              }\n              const { selfTime: fiberSelfTime, totalTime: fiberTotalTime } = getTimings(fiber);\n              const fps2 = getFPS();\n              const render = {\n                phase: RENDER_PHASE_STRING_TO_ENUM[phase],\n                componentName: getDisplayName(type),\n                count: 1,\n                changes,\n                time: fiberSelfTime,\n                forget: hasMemoCache(fiber),\n                // todo: allow this to be toggle-able through toolbar\n                // todo: performance optimization: if the last fiber measure was very off screen, do not run isRenderUnnecessary\n                unnecessary: null,\n                didCommit: didFiberCommit(fiber),\n                fps: fps2\n              };\n              const hasChanges = changes.length > 0;\n              const hasDomMutations = getMutatedHostFibers(fiber).length > 0;\n              if (phase === \"update\") {\n                trackRender(\n                  fiber,\n                  fiberSelfTime,\n                  fiberTotalTime,\n                  hasChanges,\n                  hasDomMutations\n                );\n              }\n              for (let i5 = 0, len = validInstancesIndicies.length; i5 < len; i5++) {\n                const index = validInstancesIndicies[i5];\n                const instance = allInstances2[index];\n                instance.config.onRender(fiber, [render]);\n              }\n            }\n          );\n          for (const instance of allInstances) {\n            instance.config.onCommitFinish();\n          }\n        },\n        onPostCommitFiberRoot() {\n          const allInstances = getAllInstances();\n          for (const instance of allInstances) {\n            instance.config.onPostCommitFiberRoot();\n          }\n        }\n      });\n    }\n    return instrumentation;\n  };\n\n  // src/web/utils/log.ts\n  var log = (renders) => {\n    const logMap = /* @__PURE__ */ new Map();\n    for (let i5 = 0, len = renders.length; i5 < len; i5++) {\n      const render = renders[i5];\n      if (!render.componentName) continue;\n      const changeLog = logMap.get(render.componentName) ?? [];\n      const labelText = getLabelText([\n        {\n          aggregatedCount: 1,\n          computedKey: null,\n          name: render.componentName,\n          frame: null,\n          ...render,\n          changes: {\n            // TODO(Alexis): use a faster reduction method\n            type: render.changes.reduce((set, change) => set | change.type, 0),\n            unstable: render.changes.some((change) => change.unstable)\n          },\n          phase: render.phase,\n          computedCurrent: null\n        }\n      ]);\n      if (!labelText) continue;\n      let prevChangedProps = null;\n      let nextChangedProps = null;\n      if (render.changes) {\n        for (let i6 = 0, len2 = render.changes.length; i6 < len2; i6++) {\n          const { name, prevValue, nextValue, unstable, type } = render.changes[i6];\n          if (type === 1 /* Props */) {\n            prevChangedProps ??= {};\n            nextChangedProps ??= {};\n            prevChangedProps[`${unstable ? \"\\u26A0\\uFE0F\" : \"\"}${name} (prev)`] = prevValue;\n            nextChangedProps[`${unstable ? \"\\u26A0\\uFE0F\" : \"\"}${name} (next)`] = nextValue;\n          } else {\n            changeLog.push({\n              prev: prevValue,\n              next: nextValue,\n              type: type === 4 /* Context */ ? \"context\" : \"state\",\n              unstable: unstable ?? false\n            });\n          }\n        }\n      }\n      if (prevChangedProps && nextChangedProps) {\n        changeLog.push({\n          prev: prevChangedProps,\n          next: nextChangedProps,\n          type: \"props\",\n          unstable: false\n        });\n      }\n      logMap.set(labelText, changeLog);\n    }\n    for (const [name, changeLog] of Array.from(logMap.entries())) {\n      console.group(\n        `%c${name}`,\n        \"background: hsla(0,0%,70%,.3); border-radius:3px; padding: 0 2px;\"\n      );\n      for (const { type, prev, next, unstable } of changeLog) {\n        console.log(`${type}:`, unstable ? \"\\u26A0\\uFE0F\" : \"\", prev, \"!==\", next);\n      }\n      console.groupEnd();\n    }\n  };\n  var logIntro = () => {\n    if (window.hideIntro) {\n      window.hideIntro = void 0;\n      return;\n    }\n    console.log(\n      \"%c[\\xB7] %cReact Scan\",\n      \"font-weight:bold;color:#7a68e8;font-size:20px;\",\n      \"font-weight:bold;font-size:14px;\"\n    );\n  };\n\n  // src/new-outlines/canvas.ts\n  var OUTLINE_ARRAY_SIZE = 7;\n  var MONO_FONT = \"Menlo,Consolas,Monaco,Liberation Mono,Lucida Console,monospace\";\n  var INTERPOLATION_SPEED = 0.2;\n  var SNAP_THRESHOLD = 0.5;\n  var lerp = (start2, end) => {\n    const delta = end - start2;\n    if (Math.abs(delta) < SNAP_THRESHOLD) return end;\n    return start2 + delta * INTERPOLATION_SPEED;\n  };\n  var MAX_PARTS_LENGTH = 4;\n  var MAX_LABEL_LENGTH = 40;\n  var TOTAL_FRAMES = 45;\n  var PRIMARY_COLOR = \"115,97,230\";\n  function sortEntry(prev, next) {\n    return next[0] - prev[0];\n  }\n  function getSortedEntries(countByNames) {\n    const entries = [...countByNames.entries()];\n    return entries.sort(sortEntry);\n  }\n  function getLabelTextPart([count, names]) {\n    let part = `${names.slice(0, MAX_PARTS_LENGTH).join(\", \")} \\xD7${count}`;\n    if (part.length > MAX_LABEL_LENGTH) {\n      part = `${part.slice(0, MAX_LABEL_LENGTH)}\\u2026`;\n    }\n    return part;\n  }\n  var getLabelText2 = (outlines) => {\n    const nameByCount = /* @__PURE__ */ new Map();\n    for (const { name, count } of outlines) {\n      nameByCount.set(name, (nameByCount.get(name) || 0) + count);\n    }\n    const countByNames = /* @__PURE__ */ new Map();\n    for (const [name, count] of nameByCount) {\n      const names = countByNames.get(count);\n      if (names) {\n        names.push(name);\n      } else {\n        countByNames.set(count, [name]);\n      }\n    }\n    const partsEntries = getSortedEntries(countByNames);\n    let labelText = getLabelTextPart(partsEntries[0]);\n    for (let i5 = 1, len = partsEntries.length; i5 < len; i5++) {\n      labelText += \", \" + getLabelTextPart(partsEntries[i5]);\n    }\n    if (labelText.length > MAX_LABEL_LENGTH) {\n      return `${labelText.slice(0, MAX_LABEL_LENGTH)}\\u2026`;\n    }\n    return labelText;\n  };\n  var getAreaFromOutlines = (outlines) => {\n    let area = 0;\n    for (const outline of outlines) {\n      area += outline.width * outline.height;\n    }\n    return area;\n  };\n  var updateOutlines = (activeOutlines2, outlines) => {\n    for (const { id, name, count, x: x4, y: y4, width, height, didCommit } of outlines) {\n      const outline = {\n        id,\n        name,\n        count,\n        x: x4,\n        y: y4,\n        width,\n        height,\n        frame: 0,\n        targetX: x4,\n        targetY: y4,\n        targetWidth: width,\n        targetHeight: height,\n        didCommit\n      };\n      const key = String(outline.id);\n      const existingOutline = activeOutlines2.get(key);\n      if (existingOutline) {\n        existingOutline.count++;\n        existingOutline.frame = 0;\n        existingOutline.targetX = x4;\n        existingOutline.targetY = y4;\n        existingOutline.targetWidth = width;\n        existingOutline.targetHeight = height;\n        existingOutline.didCommit = didCommit;\n      } else {\n        activeOutlines2.set(key, outline);\n      }\n    }\n  };\n  var updateScroll = (activeOutlines2, deltaX, deltaY) => {\n    for (const outline of activeOutlines2.values()) {\n      const newX = outline.x - deltaX;\n      const newY = outline.y - deltaY;\n      outline.targetX = newX;\n      outline.targetY = newY;\n    }\n  };\n  var initCanvas = (canvas2, dpr2) => {\n    const ctx2 = canvas2.getContext(\"2d\", { alpha: true });\n    if (ctx2) {\n      ctx2.scale(dpr2, dpr2);\n    }\n    return ctx2;\n  };\n  var drawCanvas = (ctx2, canvas2, dpr2, activeOutlines2) => {\n    ctx2.clearRect(0, 0, canvas2.width / dpr2, canvas2.height / dpr2);\n    const groupedOutlinesMap = /* @__PURE__ */ new Map();\n    const rectMap = /* @__PURE__ */ new Map();\n    for (const outline of activeOutlines2.values()) {\n      const {\n        x: x4,\n        y: y4,\n        width,\n        height,\n        targetX,\n        targetY,\n        targetWidth,\n        targetHeight,\n        frame\n      } = outline;\n      if (targetX !== x4) {\n        outline.x = lerp(x4, targetX);\n      }\n      if (targetY !== y4) {\n        outline.y = lerp(y4, targetY);\n      }\n      if (targetWidth !== width) {\n        outline.width = lerp(width, targetWidth);\n      }\n      if (targetHeight !== height) {\n        outline.height = lerp(height, targetHeight);\n      }\n      const labelKey = `${targetX ?? x4},${targetY ?? y4}`;\n      const rectKey = `${labelKey},${targetWidth ?? width},${targetHeight ?? height}`;\n      const outlines = groupedOutlinesMap.get(labelKey);\n      if (outlines) {\n        outlines.push(outline);\n      } else {\n        groupedOutlinesMap.set(labelKey, [outline]);\n      }\n      const alpha = 1 - frame / TOTAL_FRAMES;\n      outline.frame++;\n      const rect = rectMap.get(rectKey) || {\n        x: x4,\n        y: y4,\n        width,\n        height,\n        alpha\n      };\n      if (alpha > rect.alpha) {\n        rect.alpha = alpha;\n      }\n      rectMap.set(rectKey, rect);\n    }\n    for (const { x: x4, y: y4, width, height, alpha } of rectMap.values()) {\n      ctx2.strokeStyle = `rgba(${PRIMARY_COLOR},${alpha})`;\n      ctx2.lineWidth = 1;\n      const rx = Math.round(x4) + 0.5;\n      const ry = Math.round(y4) + 0.5;\n      const rw = Math.round(width);\n      const rh = Math.round(height);\n      ctx2.beginPath();\n      ctx2.rect(rx, ry, rw, rh);\n      ctx2.stroke();\n      ctx2.fillStyle = `rgba(${PRIMARY_COLOR},${alpha * 0.1})`;\n      ctx2.fill();\n    }\n    ctx2.font = `11px ${MONO_FONT}`;\n    const labelMap = /* @__PURE__ */ new Map();\n    ctx2.textRendering = \"optimizeSpeed\";\n    for (const outlines of groupedOutlinesMap.values()) {\n      const first = outlines[0];\n      const { x: x4, y: y4, frame } = first;\n      const alpha = 1 - frame / TOTAL_FRAMES;\n      const text = getLabelText2(outlines);\n      const { width } = ctx2.measureText(text);\n      const height = 11;\n      labelMap.set(`${x4},${y4},${width},${text}`, {\n        text,\n        width,\n        height,\n        alpha,\n        x: x4,\n        y: y4,\n        outlines\n      });\n      if (frame > TOTAL_FRAMES) {\n        for (const outline of outlines) {\n          activeOutlines2.delete(String(outline.id));\n        }\n      }\n    }\n    const sortedLabels = Array.from(labelMap.entries()).sort(\n      ([_4, a4], [__, b3]) => {\n        return getAreaFromOutlines(b3.outlines) - getAreaFromOutlines(a4.outlines);\n      }\n    );\n    for (const [labelKey, label] of sortedLabels) {\n      if (!labelMap.has(labelKey)) continue;\n      for (const [otherKey, otherLabel] of labelMap.entries()) {\n        if (labelKey === otherKey) continue;\n        const { x: x4, y: y4, width, height } = label;\n        const {\n          x: otherX,\n          y: otherY,\n          width: otherWidth,\n          height: otherHeight\n        } = otherLabel;\n        if (x4 + width > otherX && otherX + otherWidth > x4 && y4 + height > otherY && otherY + otherHeight > y4) {\n          label.text = getLabelText2(label.outlines.concat(otherLabel.outlines));\n          label.width = ctx2.measureText(label.text).width;\n          labelMap.delete(otherKey);\n        }\n      }\n    }\n    for (const label of labelMap.values()) {\n      const { x: x4, y: y4, alpha, width, height, text } = label;\n      let labelY = y4 - height - 4;\n      if (labelY < 0) {\n        labelY = 0;\n      }\n      ctx2.fillStyle = `rgba(${PRIMARY_COLOR},${alpha})`;\n      ctx2.fillRect(x4, labelY, width + 4, height + 4);\n      ctx2.fillStyle = `rgba(255,255,255,${alpha})`;\n      ctx2.fillText(text, x4 + 2, labelY + height);\n    }\n    return activeOutlines2.size > 0;\n  };\n\n  // src/new-outlines/index.ts\n  var workerCode = \"\\\"use strict\\\";(()=>{var D=\\\"Menlo,Consolas,Monaco,Liberation Mono,Lucida Console,monospace\\\";var T=(t,n)=>{let r=n-t;return Math.abs(r)<.5?n:t+r*.2};var x=\\\"115,97,230\\\";function P(t,n){return n[0]-t[0]}function F(t){return[...t.entries()].sort(P)}function v([t,n]){let r=`${n.slice(0,4).join(\\\", \\\")} \\\\xD7${t}`;return r.length>40&&(r=`${r.slice(0,40)}\\\\u2026`),r}var $=t=>{let n=new Map;for(let{name:e,count:u}of t)n.set(e,(n.get(e)||0)+u);let r=new Map;for(let[e,u]of n){let A=r.get(u);A?A.push(e):r.set(u,[e])}let d=F(r),a=v(d[0]);for(let e=1,u=d.length;e<u;e++)a+=\\\", \\\"+v(d[e]);return a.length>40?`${a.slice(0,40)}\\\\u2026`:a},H=t=>{let n=0;for(let r of t)n+=r.width*r.height;return n};var N=(t,n)=>{let r=t.getContext(\\\"2d\\\",{alpha:!0});return r&&r.scale(n,n),r},X=(t,n,r,d)=>{t.clearRect(0,0,n.width/r,n.height/r);let a=new Map,e=new Map;for(let i of d.values()){let{x:o,y:c,width:l,height:g,targetX:s,targetY:f,targetWidth:h,targetHeight:m,frame:O}=i;s!==o&&(i.x=T(o,s)),f!==c&&(i.y=T(c,f)),h!==l&&(i.width=T(l,h)),m!==g&&(i.height=T(g,m));let M=`${s??o},${f??c}`,L=`${M},${h??l},${m??g}`,S=a.get(M);S?S.push(i):a.set(M,[i]);let C=1-O/45;i.frame++;let _=e.get(L)||{x:o,y:c,width:l,height:g,alpha:C};C>_.alpha&&(_.alpha=C),e.set(L,_)}for(let{x:i,y:o,width:c,height:l,alpha:g}of e.values()){t.strokeStyle=`rgba(${x},${g})`,t.lineWidth=1;let s=Math.round(i)+.5,f=Math.round(o)+.5,h=Math.round(c),m=Math.round(l);t.beginPath(),t.rect(s,f,h,m),t.stroke(),t.fillStyle=`rgba(${x},${g*.1})`,t.fill()}t.font=`11px ${D}`;let u=new Map;t.textRendering=\\\"optimizeSpeed\\\";for(let i of a.values()){let o=i[0],{x:c,y:l,frame:g}=o,s=1-g/45,f=$(i),{width:h}=t.measureText(f),m=11;u.set(`${c},${l},${h},${f}`,{text:f,width:h,height:m,alpha:s,x:c,y:l,outlines:i});let O=l-m-4;if(O<0&&(O=0),g>45)for(let M of i)d.delete(String(M.id))}let A=Array.from(u.entries()).sort(([i,o],[c,l])=>H(l.outlines)-H(o.outlines));for(let[i,o]of A)if(u.has(i))for(let[c,l]of u.entries()){if(i===c)continue;let{x:g,y:s,width:f,height:h}=o,{x:m,y:O,width:M,height:L}=l;g+f>m&&m+M>g&&s+h>O&&O+L>s&&(o.text=$(o.outlines.concat(l.outlines)),o.width=t.measureText(o.text).width,u.delete(c))}for(let i of u.values()){let{x:o,y:c,alpha:l,width:g,height:s,text:f}=i,h=c-s-4;h<0&&(h=0),t.fillStyle=`rgba(${x},${l})`,t.fillRect(o,h,g+4,s+4),t.fillStyle=`rgba(255,255,255,${l})`,t.fillText(f,o+2,h+s)}return d.size>0};var p=null,w=null,b=1,y=new Map,E=null,R=()=>{if(!w||!p)return;X(w,p,b,y)?E=requestAnimationFrame(R):E=null};self.onmessage=t=>{let{type:n}=t.data;if(n===\\\"init\\\"&&(p=t.data.canvas,b=t.data.dpr,p&&(p.width=t.data.width,p.height=t.data.height,w=N(p,b))),!(!p||!w)){if(n===\\\"resize\\\"){b=t.data.dpr,p.width=t.data.width*b,p.height=t.data.height*b,w.resetTransform(),w.scale(b,b),R();return}if(n===\\\"draw-outlines\\\"){let{data:r,names:d}=t.data,a=new Float32Array(r);for(let e=0;e<a.length;e+=7){let u=a[e+2],A=a[e+3],i=a[e+4],o=a[e+5],c=a[e+6],l={id:a[e],name:d[e/7],count:a[e+1],x:u,y:A,width:i,height:o,frame:0,targetX:u,targetY:A,targetWidth:i,targetHeight:o,didCommit:c},g=String(l.id),s=y.get(g);s?(s.count++,s.frame=0,s.targetX=u,s.targetY=A,s.targetWidth=i,s.targetHeight=o,s.didCommit=c):y.set(g,l)}E||(E=requestAnimationFrame(R));return}if(n===\\\"scroll\\\"){let{deltaX:r,deltaY:d}=t.data;for(let a of y.values()){let e=a.x-r,u=a.y-d;a.targetX=e,a.targetY=u}}}};})();\\n\";\n  var worker = null;\n  var canvas = null;\n  var ctx = null;\n  var dpr = 1;\n  var animationFrameId = null;\n  var activeOutlines = /* @__PURE__ */ new Map();\n  var blueprintMap = /* @__PURE__ */ new Map();\n  var blueprintMapKeys = /* @__PURE__ */ new Set();\n  var outlineFiber = (fiber) => {\n    if (!isCompositeFiber(fiber)) return;\n    const name = typeof fiber.type === \"string\" ? fiber.type : getDisplayName(fiber);\n    if (!name) return;\n    const blueprint = blueprintMap.get(fiber);\n    const nearestFibers = getNearestHostFibers(fiber);\n    const didCommit = didFiberCommit(fiber);\n    if (!blueprint) {\n      blueprintMap.set(fiber, {\n        name,\n        count: 1,\n        elements: nearestFibers.map((fiber2) => fiber2.stateNode),\n        didCommit: didCommit ? 1 : 0\n      });\n      blueprintMapKeys.add(fiber);\n    } else {\n      blueprint.count++;\n    }\n  };\n  var mergeRects = (rects) => {\n    const firstRect = rects[0];\n    if (rects.length === 1) return firstRect;\n    let minX;\n    let minY;\n    let maxX;\n    let maxY;\n    for (let i5 = 0, len = rects.length; i5 < len; i5++) {\n      const rect = rects[i5];\n      minX = minX == null ? rect.x : Math.min(minX, rect.x);\n      minY = minY == null ? rect.y : Math.min(minY, rect.y);\n      maxX = maxX == null ? rect.x + rect.width : Math.max(maxX, rect.x + rect.width);\n      maxY = maxY == null ? rect.y + rect.height : Math.max(maxY, rect.y + rect.height);\n    }\n    if (minX == null || minY == null || maxX == null || maxY == null) {\n      return rects[0];\n    }\n    return new DOMRect(minX, minY, maxX - minX, maxY - minY);\n  };\n  function onIntersect(entries, observer) {\n    const newEntries = [];\n    for (const entry of entries) {\n      const element = entry.target;\n      if (!this.seenElements.has(element)) {\n        this.seenElements.add(element);\n        newEntries.push(entry);\n      }\n    }\n    if (newEntries.length > 0 && this.resolveNext) {\n      this.resolveNext(newEntries);\n      this.resolveNext = null;\n    }\n    if (this.seenElements.size === this.uniqueElements.size) {\n      observer.disconnect();\n      this.done = true;\n      if (this.resolveNext) {\n        this.resolveNext([]);\n      }\n    }\n  }\n  var getBatchedRectMap = async function* (elements) {\n    const state = {\n      uniqueElements: new Set(elements),\n      seenElements: /* @__PURE__ */ new Set(),\n      resolveNext: null,\n      done: false\n    };\n    const observer = new IntersectionObserver(onIntersect.bind(state));\n    for (const element of state.uniqueElements) {\n      observer.observe(element);\n    }\n    while (!state.done) {\n      const entries = await new Promise(\n        (resolve) => {\n          state.resolveNext = resolve;\n        }\n      );\n      if (entries.length > 0) {\n        yield entries;\n      }\n    }\n  };\n  var SupportedArrayBuffer = typeof SharedArrayBuffer !== \"undefined\" ? SharedArrayBuffer : ArrayBuffer;\n  var flushOutlines = async () => {\n    const elements = [];\n    for (const fiber of blueprintMapKeys) {\n      const blueprint = blueprintMap.get(fiber);\n      if (!blueprint) continue;\n      for (let i5 = 0; i5 < blueprint.elements.length; i5++) {\n        if (!(blueprint.elements[i5] instanceof Element)) {\n          continue;\n        }\n        elements.push(blueprint.elements[i5]);\n      }\n    }\n    const rectsMap = /* @__PURE__ */ new Map();\n    for await (const entries of getBatchedRectMap(elements)) {\n      for (const entry of entries) {\n        const element = entry.target;\n        const rect = entry.intersectionRect;\n        if (entry.isIntersecting && rect.width && rect.height) {\n          rectsMap.set(element, rect);\n        }\n      }\n      const blueprints = [];\n      const blueprintRects = [];\n      const blueprintIds = [];\n      for (const fiber of blueprintMapKeys) {\n        const blueprint = blueprintMap.get(fiber);\n        if (!blueprint) continue;\n        const rects = [];\n        for (let i5 = 0; i5 < blueprint.elements.length; i5++) {\n          const element = blueprint.elements[i5];\n          const rect = rectsMap.get(element);\n          if (!rect) continue;\n          rects.push(rect);\n        }\n        if (!rects.length) continue;\n        blueprints.push(blueprint);\n        blueprintRects.push(mergeRects(rects));\n        blueprintIds.push(getFiberId(fiber));\n      }\n      if (blueprints.length > 0) {\n        const arrayBuffer = new SupportedArrayBuffer(\n          blueprints.length * OUTLINE_ARRAY_SIZE * 4\n        );\n        const sharedView = new Float32Array(arrayBuffer);\n        const blueprintNames = new Array(blueprints.length);\n        let outlineData;\n        for (let i5 = 0, len = blueprints.length; i5 < len; i5++) {\n          const blueprint = blueprints[i5];\n          const id = blueprintIds[i5];\n          const { x: x4, y: y4, width, height } = blueprintRects[i5];\n          const { count, name, didCommit } = blueprint;\n          if (worker) {\n            const scaledIndex = i5 * OUTLINE_ARRAY_SIZE;\n            sharedView[scaledIndex] = id;\n            sharedView[scaledIndex + 1] = count;\n            sharedView[scaledIndex + 2] = x4;\n            sharedView[scaledIndex + 3] = y4;\n            sharedView[scaledIndex + 4] = width;\n            sharedView[scaledIndex + 5] = height;\n            sharedView[scaledIndex + 6] = didCommit;\n            blueprintNames[i5] = name;\n          } else {\n            outlineData ||= new Array(blueprints.length);\n            outlineData[i5] = {\n              id,\n              name,\n              count,\n              x: x4,\n              y: y4,\n              width,\n              height,\n              didCommit\n            };\n          }\n        }\n        if (worker) {\n          worker.postMessage({\n            type: \"draw-outlines\",\n            data: arrayBuffer,\n            names: blueprintNames\n          });\n        } else if (canvas && ctx && outlineData) {\n          updateOutlines(activeOutlines, outlineData);\n          if (!animationFrameId) {\n            animationFrameId = requestAnimationFrame(draw);\n          }\n        }\n      }\n    }\n    for (const fiber of blueprintMapKeys) {\n      blueprintMap.delete(fiber);\n      blueprintMapKeys.delete(fiber);\n    }\n  };\n  var draw = () => {\n    if (!ctx || !canvas) return;\n    const shouldContinue = drawCanvas(ctx, canvas, dpr, activeOutlines);\n    if (shouldContinue) {\n      animationFrameId = requestAnimationFrame(draw);\n    } else {\n      animationFrameId = null;\n    }\n  };\n  var IS_OFFSCREEN_CANVAS_WORKER_SUPPORTED = typeof OffscreenCanvas !== \"undefined\" && typeof Worker !== \"undefined\";\n  var getDpr = () => {\n    return Math.min(window.devicePixelRatio || 1, 2);\n  };\n  var getCanvasEl = () => {\n    cleanup();\n    const host = document.createElement(\"div\");\n    host.setAttribute(\"data-react-scan\", \"true\");\n    const shadowRoot2 = host.attachShadow({ mode: \"open\" });\n    const canvasEl = document.createElement(\"canvas\");\n    canvasEl.style.position = \"fixed\";\n    canvasEl.style.top = \"0\";\n    canvasEl.style.left = \"0\";\n    canvasEl.style.pointerEvents = \"none\";\n    canvasEl.style.zIndex = \"2147483646\";\n    canvasEl.setAttribute(\"aria-hidden\", \"true\");\n    shadowRoot2.appendChild(canvasEl);\n    if (!canvasEl) return null;\n    dpr = getDpr();\n    canvas = canvasEl;\n    const { innerWidth, innerHeight } = window;\n    canvasEl.style.width = `${innerWidth}px`;\n    canvasEl.style.height = `${innerHeight}px`;\n    const width = innerWidth * dpr;\n    const height = innerHeight * dpr;\n    canvasEl.width = width;\n    canvasEl.height = height;\n    if (IS_OFFSCREEN_CANVAS_WORKER_SUPPORTED && !window.__REACT_SCAN_EXTENSION__) {\n      try {\n        worker = new Worker(\n          URL.createObjectURL(\n            new Blob([workerCode], { type: \"application/javascript\" })\n          )\n        );\n        const offscreenCanvas = canvasEl.transferControlToOffscreen();\n        worker?.postMessage(\n          {\n            type: \"init\",\n            canvas: offscreenCanvas,\n            width: canvasEl.width,\n            height: canvasEl.height,\n            dpr\n          },\n          [offscreenCanvas]\n        );\n      } catch (e4) {\n        console.warn(\"Failed to initialize OffscreenCanvas worker:\", e4);\n      }\n    }\n    if (!worker) {\n      ctx = initCanvas(canvasEl, dpr);\n    }\n    let isResizeScheduled = false;\n    window.addEventListener(\"resize\", () => {\n      if (!isResizeScheduled) {\n        isResizeScheduled = true;\n        setTimeout(() => {\n          const width2 = window.innerWidth;\n          const height2 = window.innerHeight;\n          dpr = getDpr();\n          canvasEl.style.width = `${width2}px`;\n          canvasEl.style.height = `${height2}px`;\n          if (worker) {\n            worker.postMessage({\n              type: \"resize\",\n              width: width2,\n              height: height2,\n              dpr\n            });\n          } else {\n            canvasEl.width = width2 * dpr;\n            canvasEl.height = height2 * dpr;\n            if (ctx) {\n              ctx.resetTransform();\n              ctx.scale(dpr, dpr);\n            }\n            draw();\n          }\n          isResizeScheduled = false;\n        });\n      }\n    });\n    let prevScrollX = window.scrollX;\n    let prevScrollY = window.scrollY;\n    let isScrollScheduled = false;\n    window.addEventListener(\"scroll\", () => {\n      if (!isScrollScheduled) {\n        isScrollScheduled = true;\n        setTimeout(() => {\n          const { scrollX, scrollY } = window;\n          const deltaX = scrollX - prevScrollX;\n          const deltaY = scrollY - prevScrollY;\n          prevScrollX = scrollX;\n          prevScrollY = scrollY;\n          if (worker) {\n            worker.postMessage({\n              type: \"scroll\",\n              deltaX,\n              deltaY\n            });\n          } else {\n            requestAnimationFrame(\n              updateScroll.bind(null, activeOutlines, deltaX, deltaY)\n            );\n          }\n          isScrollScheduled = false;\n        }, 16 * 2);\n      }\n    });\n    setInterval(() => {\n      if (blueprintMapKeys.size) {\n        requestAnimationFrame(flushOutlines);\n      }\n    }, 16 * 2);\n    shadowRoot2.appendChild(canvasEl);\n    return host;\n  };\n  var hasStopped = () => {\n    return globalThis.__REACT_SCAN_STOP__;\n  };\n  var cleanup = () => {\n    const host = document.querySelector(\"[data-react-scan]\");\n    if (host) {\n      host.remove();\n    }\n  };\n  var reportRenderToListeners = (fiber) => {\n    if (isCompositeFiber(fiber)) {\n      if (ReactScanInternals.options.value.showToolbar !== false && Store.inspectState.value.kind === \"focused\") {\n        const reportFiber = fiber;\n        const { selfTime } = getTimings(fiber);\n        const displayName = getDisplayName(fiber.type);\n        const fiberId2 = getFiberId(reportFiber);\n        const currentData = Store.reportData.get(fiberId2);\n        const existingCount = currentData?.count ?? 0;\n        const existingTime = currentData?.time ?? 0;\n        const changes = [];\n        const listeners = Store.changesListeners.get(getFiberId(fiber));\n        if (listeners?.length) {\n          const propsChanges = getChangedPropsDetailed(\n            fiber\n          ).map((change) => ({\n            type: 1 /* Props */,\n            name: change.name,\n            value: change.value,\n            prevValue: change.prevValue,\n            unstable: false\n          }));\n          const stateChanges = getStateChanges(fiber);\n          const fiberContext = getContextChanges(fiber);\n          const contextChanges = fiberContext.map(\n            (info) => ({\n              name: info.name,\n              type: 4 /* Context */,\n              value: info.value,\n              contextType: info.contextType\n            })\n          );\n          listeners.forEach((listener) => {\n            listener({\n              propsChanges,\n              stateChanges,\n              contextChanges\n            });\n          });\n        }\n        const fiberData = {\n          count: existingCount + 1,\n          time: existingTime + selfTime || 0,\n          renders: [],\n          displayName,\n          type: getType(fiber.type) || null,\n          changes\n        };\n        Store.reportData.set(fiberId2, fiberData);\n        needsReport = true;\n      }\n    }\n  };\n  var needsReport = false;\n  var reportInterval;\n  var startReportInterval = () => {\n    clearInterval(reportInterval);\n    reportInterval = setInterval(() => {\n      if (needsReport) {\n        Store.lastReportTime.value = Date.now();\n        needsReport = false;\n      }\n    }, 50);\n  };\n  var isValidFiber2 = (fiber) => {\n    if (ignoredProps.has(fiber.memoizedProps)) {\n      return false;\n    }\n    return true;\n  };\n  var isInstrumentationInitialized = false;\n  var initReactScanInstrumentation = (setupToolbar) => {\n    if (hasStopped()) return;\n    if (isInstrumentationInitialized) return;\n    isInstrumentationInitialized = true;\n    let schedule;\n    let mounted = false;\n    const scheduleSetup = () => {\n      if (mounted) {\n        return;\n      }\n      if (schedule) {\n        cancelAnimationFrame(schedule);\n      }\n      schedule = requestAnimationFrame(() => {\n        mounted = true;\n        const host = getCanvasEl();\n        if (host) {\n          document.documentElement.appendChild(host);\n        }\n        setupToolbar();\n      });\n    };\n    const instrumentation = createInstrumentation(\"react-scan-devtools-0.1.0\", {\n      onCommitStart: () => {\n        ReactScanInternals.options.value.onCommitStart?.();\n      },\n      onActive: /* @__PURE__ */ (() => {\n        let didActivate = false;\n        return () => {\n          if (hasStopped()) return;\n          if (didActivate) return;\n          didActivate = true;\n          scheduleSetup();\n          if (!window.__REACT_SCAN_EXTENSION__) {\n            globalThis.__REACT_SCAN__ = {\n              ReactScanInternals\n            };\n          }\n          startReportInterval();\n          logIntro();\n        };\n      })(),\n      onError: () => {\n      },\n      isValidFiber: isValidFiber2,\n      onRender: (fiber, renders) => {\n        if (isCompositeFiber(fiber)) {\n          Store.interactionListeningForRenders?.(fiber, renders);\n        }\n        const isOverlayPaused = ReactScanInternals.instrumentation?.isPaused.value;\n        const isInspectorInactive = Store.inspectState.value.kind === \"inspect-off\" || Store.inspectState.value.kind === \"uninitialized\";\n        const shouldFullyAbort = isOverlayPaused && isInspectorInactive;\n        if (shouldFullyAbort) {\n          return;\n        }\n        if (!isOverlayPaused) {\n          outlineFiber(fiber);\n        }\n        if (ReactScanInternals.options.value.log) {\n          log(renders);\n        }\n        if (Store.inspectState.value.kind === \"focused\") {\n          inspectorUpdateSignal.value = Date.now();\n        }\n        if (!isInspectorInactive) {\n          reportRenderToListeners(fiber);\n        }\n        ReactScanInternals.options.value.onRender?.(fiber, renders);\n      },\n      onCommitFinish: () => {\n        scheduleSetup();\n        ReactScanInternals.options.value.onCommitFinish?.();\n      },\n      onPostCommitFiberRoot() {\n        scheduleSetup();\n      },\n      trackChanges: false\n    });\n    ReactScanInternals.instrumentation = instrumentation;\n  };\n\n  // src/web/assets/css/styles.css\n  var styles_default = \"*, ::before, ::after {\\n  --tw-border-spacing-x: 0;\\n  --tw-border-spacing-y: 0;\\n  --tw-translate-x: 0;\\n  --tw-translate-y: 0;\\n  --tw-rotate: 0;\\n  --tw-skew-x: 0;\\n  --tw-skew-y: 0;\\n  --tw-scale-x: 1;\\n  --tw-scale-y: 1;\\n  --tw-pan-x:  ;\\n  --tw-pan-y:  ;\\n  --tw-pinch-zoom:  ;\\n  --tw-scroll-snap-strictness: proximity;\\n  --tw-gradient-from-position:  ;\\n  --tw-gradient-via-position:  ;\\n  --tw-gradient-to-position:  ;\\n  --tw-ordinal:  ;\\n  --tw-slashed-zero:  ;\\n  --tw-numeric-figure:  ;\\n  --tw-numeric-spacing:  ;\\n  --tw-numeric-fraction:  ;\\n  --tw-ring-inset:  ;\\n  --tw-ring-offset-width: 0px;\\n  --tw-ring-offset-color: #fff;\\n  --tw-ring-color: rgb(59 130 246 / 0.5);\\n  --tw-ring-offset-shadow: 0 0 #0000;\\n  --tw-ring-shadow: 0 0 #0000;\\n  --tw-shadow: 0 0 #0000;\\n  --tw-shadow-colored: 0 0 #0000;\\n  --tw-blur:  ;\\n  --tw-brightness:  ;\\n  --tw-contrast:  ;\\n  --tw-grayscale:  ;\\n  --tw-hue-rotate:  ;\\n  --tw-invert:  ;\\n  --tw-saturate:  ;\\n  --tw-sepia:  ;\\n  --tw-drop-shadow:  ;\\n  --tw-backdrop-blur:  ;\\n  --tw-backdrop-brightness:  ;\\n  --tw-backdrop-contrast:  ;\\n  --tw-backdrop-grayscale:  ;\\n  --tw-backdrop-hue-rotate:  ;\\n  --tw-backdrop-invert:  ;\\n  --tw-backdrop-opacity:  ;\\n  --tw-backdrop-saturate:  ;\\n  --tw-backdrop-sepia:  ;\\n  --tw-contain-size:  ;\\n  --tw-contain-layout:  ;\\n  --tw-contain-paint:  ;\\n  --tw-contain-style:  ;\\n}\\n\\n::backdrop {\\n  --tw-border-spacing-x: 0;\\n  --tw-border-spacing-y: 0;\\n  --tw-translate-x: 0;\\n  --tw-translate-y: 0;\\n  --tw-rotate: 0;\\n  --tw-skew-x: 0;\\n  --tw-skew-y: 0;\\n  --tw-scale-x: 1;\\n  --tw-scale-y: 1;\\n  --tw-pan-x:  ;\\n  --tw-pan-y:  ;\\n  --tw-pinch-zoom:  ;\\n  --tw-scroll-snap-strictness: proximity;\\n  --tw-gradient-from-position:  ;\\n  --tw-gradient-via-position:  ;\\n  --tw-gradient-to-position:  ;\\n  --tw-ordinal:  ;\\n  --tw-slashed-zero:  ;\\n  --tw-numeric-figure:  ;\\n  --tw-numeric-spacing:  ;\\n  --tw-numeric-fraction:  ;\\n  --tw-ring-inset:  ;\\n  --tw-ring-offset-width: 0px;\\n  --tw-ring-offset-color: #fff;\\n  --tw-ring-color: rgb(59 130 246 / 0.5);\\n  --tw-ring-offset-shadow: 0 0 #0000;\\n  --tw-ring-shadow: 0 0 #0000;\\n  --tw-shadow: 0 0 #0000;\\n  --tw-shadow-colored: 0 0 #0000;\\n  --tw-blur:  ;\\n  --tw-brightness:  ;\\n  --tw-contrast:  ;\\n  --tw-grayscale:  ;\\n  --tw-hue-rotate:  ;\\n  --tw-invert:  ;\\n  --tw-saturate:  ;\\n  --tw-sepia:  ;\\n  --tw-drop-shadow:  ;\\n  --tw-backdrop-blur:  ;\\n  --tw-backdrop-brightness:  ;\\n  --tw-backdrop-contrast:  ;\\n  --tw-backdrop-grayscale:  ;\\n  --tw-backdrop-hue-rotate:  ;\\n  --tw-backdrop-invert:  ;\\n  --tw-backdrop-opacity:  ;\\n  --tw-backdrop-saturate:  ;\\n  --tw-backdrop-sepia:  ;\\n  --tw-contain-size:  ;\\n  --tw-contain-layout:  ;\\n  --tw-contain-paint:  ;\\n  --tw-contain-style:  ;\\n}/*\\n! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com\\n*//*\\n1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)\\n2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)\\n*/\\n\\n*,\\n::before,\\n::after {\\n  box-sizing: border-box; /* 1 */\\n  border-width: 0; /* 2 */\\n  border-style: solid; /* 2 */\\n  border-color: #e5e7eb; /* 2 */\\n}\\n\\n::before,\\n::after {\\n  --tw-content: '';\\n}\\n\\n/*\\n1. Use a consistent sensible line-height in all browsers.\\n2. Prevent adjustments of font size after orientation changes in iOS.\\n3. Use a more readable tab size.\\n4. Use the user's configured `sans` font-family by default.\\n5. Use the user's configured `sans` font-feature-settings by default.\\n6. Use the user's configured `sans` font-variation-settings by default.\\n7. Disable tap highlights on iOS\\n*/\\n\\nhtml,\\n:host {\\n  line-height: 1.5; /* 1 */\\n  -webkit-text-size-adjust: 100%; /* 2 */\\n  -moz-tab-size: 4; /* 3 */\\n  -o-tab-size: 4;\\n     tab-size: 4; /* 3 */\\n  font-family: ui-sans-serif, system-ui, sans-serif, \\\"Apple Color Emoji\\\", \\\"Segoe UI Emoji\\\", \\\"Segoe UI Symbol\\\", \\\"Noto Color Emoji\\\"; /* 4 */\\n  font-feature-settings: normal; /* 5 */\\n  font-variation-settings: normal; /* 6 */\\n  -webkit-tap-highlight-color: transparent; /* 7 */\\n}\\n\\n/*\\n1. Remove the margin in all browsers.\\n2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.\\n*/\\n\\nbody {\\n  margin: 0; /* 1 */\\n  line-height: inherit; /* 2 */\\n}\\n\\n/*\\n1. Add the correct height in Firefox.\\n2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)\\n3. Ensure horizontal rules are visible by default.\\n*/\\n\\nhr {\\n  height: 0; /* 1 */\\n  color: inherit; /* 2 */\\n  border-top-width: 1px; /* 3 */\\n}\\n\\n/*\\nAdd the correct text decoration in Chrome, Edge, and Safari.\\n*/\\n\\nabbr:where([title]) {\\n  -webkit-text-decoration: underline dotted;\\n          text-decoration: underline dotted;\\n}\\n\\n/*\\nRemove the default font size and weight for headings.\\n*/\\n\\nh1,\\nh2,\\nh3,\\nh4,\\nh5,\\nh6 {\\n  font-size: inherit;\\n  font-weight: inherit;\\n}\\n\\n/*\\nReset links to optimize for opt-in styling instead of opt-out.\\n*/\\n\\na {\\n  color: inherit;\\n  text-decoration: inherit;\\n}\\n\\n/*\\nAdd the correct font weight in Edge and Safari.\\n*/\\n\\nb,\\nstrong {\\n  font-weight: bolder;\\n}\\n\\n/*\\n1. Use the user's configured `mono` font-family by default.\\n2. Use the user's configured `mono` font-feature-settings by default.\\n3. Use the user's configured `mono` font-variation-settings by default.\\n4. Correct the odd `em` font sizing in all browsers.\\n*/\\n\\ncode,\\nkbd,\\nsamp,\\npre {\\n  font-family: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace; /* 1 */\\n  font-feature-settings: normal; /* 2 */\\n  font-variation-settings: normal; /* 3 */\\n  font-size: 1em; /* 4 */\\n}\\n\\n/*\\nAdd the correct font size in all browsers.\\n*/\\n\\nsmall {\\n  font-size: 80%;\\n}\\n\\n/*\\nPrevent `sub` and `sup` elements from affecting the line height in all browsers.\\n*/\\n\\nsub,\\nsup {\\n  font-size: 75%;\\n  line-height: 0;\\n  position: relative;\\n  vertical-align: baseline;\\n}\\n\\nsub {\\n  bottom: -0.25em;\\n}\\n\\nsup {\\n  top: -0.5em;\\n}\\n\\n/*\\n1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)\\n2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)\\n3. Remove gaps between table borders by default.\\n*/\\n\\ntable {\\n  text-indent: 0; /* 1 */\\n  border-color: inherit; /* 2 */\\n  border-collapse: collapse; /* 3 */\\n}\\n\\n/*\\n1. Change the font styles in all browsers.\\n2. Remove the margin in Firefox and Safari.\\n3. Remove default padding in all browsers.\\n*/\\n\\nbutton,\\ninput,\\noptgroup,\\nselect,\\ntextarea {\\n  font-family: inherit; /* 1 */\\n  font-feature-settings: inherit; /* 1 */\\n  font-variation-settings: inherit; /* 1 */\\n  font-size: 100%; /* 1 */\\n  font-weight: inherit; /* 1 */\\n  line-height: inherit; /* 1 */\\n  letter-spacing: inherit; /* 1 */\\n  color: inherit; /* 1 */\\n  margin: 0; /* 2 */\\n  padding: 0; /* 3 */\\n}\\n\\n/*\\nRemove the inheritance of text transform in Edge and Firefox.\\n*/\\n\\nbutton,\\nselect {\\n  text-transform: none;\\n}\\n\\n/*\\n1. Correct the inability to style clickable types in iOS and Safari.\\n2. Remove default button styles.\\n*/\\n\\nbutton,\\ninput:where([type='button']),\\ninput:where([type='reset']),\\ninput:where([type='submit']) {\\n  -webkit-appearance: button; /* 1 */\\n  background-color: transparent; /* 2 */\\n  background-image: none; /* 2 */\\n}\\n\\n/*\\nUse the modern Firefox focus style for all focusable elements.\\n*/\\n\\n:-moz-focusring {\\n  outline: auto;\\n}\\n\\n/*\\nRemove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)\\n*/\\n\\n:-moz-ui-invalid {\\n  box-shadow: none;\\n}\\n\\n/*\\nAdd the correct vertical alignment in Chrome and Firefox.\\n*/\\n\\nprogress {\\n  vertical-align: baseline;\\n}\\n\\n/*\\nCorrect the cursor style of increment and decrement buttons in Safari.\\n*/\\n\\n::-webkit-inner-spin-button,\\n::-webkit-outer-spin-button {\\n  height: auto;\\n}\\n\\n/*\\n1. Correct the odd appearance in Chrome and Safari.\\n2. Correct the outline style in Safari.\\n*/\\n\\n[type='search'] {\\n  -webkit-appearance: textfield; /* 1 */\\n  outline-offset: -2px; /* 2 */\\n}\\n\\n/*\\nRemove the inner padding in Chrome and Safari on macOS.\\n*/\\n\\n::-webkit-search-decoration {\\n  -webkit-appearance: none;\\n}\\n\\n/*\\n1. Correct the inability to style clickable types in iOS and Safari.\\n2. Change font properties to `inherit` in Safari.\\n*/\\n\\n::-webkit-file-upload-button {\\n  -webkit-appearance: button; /* 1 */\\n  font: inherit; /* 2 */\\n}\\n\\n/*\\nAdd the correct display in Chrome and Safari.\\n*/\\n\\nsummary {\\n  display: list-item;\\n}\\n\\n/*\\nRemoves the default spacing and border for appropriate elements.\\n*/\\n\\nblockquote,\\ndl,\\ndd,\\nh1,\\nh2,\\nh3,\\nh4,\\nh5,\\nh6,\\nhr,\\nfigure,\\np,\\npre {\\n  margin: 0;\\n}\\n\\nfieldset {\\n  margin: 0;\\n  padding: 0;\\n}\\n\\nlegend {\\n  padding: 0;\\n}\\n\\nol,\\nul,\\nmenu {\\n  list-style: none;\\n  margin: 0;\\n  padding: 0;\\n}\\n\\n/*\\nReset default styling for dialogs.\\n*/\\ndialog {\\n  padding: 0;\\n}\\n\\n/*\\nPrevent resizing textareas horizontally by default.\\n*/\\n\\ntextarea {\\n  resize: vertical;\\n}\\n\\n/*\\n1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)\\n2. Set the default placeholder color to the user's configured gray 400 color.\\n*/\\n\\ninput::-moz-placeholder, textarea::-moz-placeholder {\\n  opacity: 1; /* 1 */\\n  color: #9ca3af; /* 2 */\\n}\\n\\ninput::placeholder,\\ntextarea::placeholder {\\n  opacity: 1; /* 1 */\\n  color: #9ca3af; /* 2 */\\n}\\n\\n/*\\nSet the default cursor for buttons.\\n*/\\n\\nbutton,\\n[role=\\\"button\\\"] {\\n  cursor: pointer;\\n}\\n\\n/*\\nMake sure disabled buttons don't get the pointer cursor.\\n*/\\n:disabled {\\n  cursor: default;\\n}\\n\\n/*\\n1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)\\n2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)\\n   This can trigger a poorly considered lint error in some tools but is included by design.\\n*/\\n\\nimg,\\nsvg,\\nvideo,\\ncanvas,\\naudio,\\niframe,\\nembed,\\nobject {\\n  display: block; /* 1 */\\n  vertical-align: middle; /* 2 */\\n}\\n\\n/*\\nConstrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)\\n*/\\n\\nimg,\\nvideo {\\n  max-width: 100%;\\n  height: auto;\\n}\\n\\n/* Make elements with the HTML hidden attribute stay hidden by default */\\n[hidden]:where(:not([hidden=\\\"until-found\\\"])) {\\n  display: none;\\n}\\n.\\\\!container {\\n  width: 100% !important;\\n}\\n.container {\\n  width: 100%;\\n}\\n@media (min-width: 640px) {\\n\\n  .\\\\!container {\\n    max-width: 640px !important;\\n  }\\n\\n  .container {\\n    max-width: 640px;\\n  }\\n}\\n@media (min-width: 768px) {\\n\\n  .\\\\!container {\\n    max-width: 768px !important;\\n  }\\n\\n  .container {\\n    max-width: 768px;\\n  }\\n}\\n@media (min-width: 1024px) {\\n\\n  .\\\\!container {\\n    max-width: 1024px !important;\\n  }\\n\\n  .container {\\n    max-width: 1024px;\\n  }\\n}\\n@media (min-width: 1280px) {\\n\\n  .\\\\!container {\\n    max-width: 1280px !important;\\n  }\\n\\n  .container {\\n    max-width: 1280px;\\n  }\\n}\\n@media (min-width: 1536px) {\\n\\n  .\\\\!container {\\n    max-width: 1536px !important;\\n  }\\n\\n  .container {\\n    max-width: 1536px;\\n  }\\n}\\n.pointer-events-none {\\n  pointer-events: none;\\n}\\n.pointer-events-auto {\\n  pointer-events: auto;\\n}\\n.visible {\\n  visibility: visible;\\n}\\n.static {\\n  position: static;\\n}\\n.fixed {\\n  position: fixed;\\n}\\n.absolute {\\n  position: absolute;\\n}\\n.relative {\\n  position: relative;\\n}\\n.sticky {\\n  position: sticky;\\n}\\n.inset-0 {\\n  inset: 0px;\\n}\\n.inset-x-1 {\\n  left: 4px;\\n  right: 4px;\\n}\\n.inset-y-0 {\\n  top: 0px;\\n  bottom: 0px;\\n}\\n.-right-1 {\\n  right: -4px;\\n}\\n.-right-2\\\\.5 {\\n  right: -10px;\\n}\\n.-top-1 {\\n  top: -4px;\\n}\\n.-top-2\\\\.5 {\\n  top: -10px;\\n}\\n.bottom-0 {\\n  bottom: 0px;\\n}\\n.bottom-4 {\\n  bottom: 16px;\\n}\\n.left-0 {\\n  left: 0px;\\n}\\n.left-3 {\\n  left: 12px;\\n}\\n.right-0 {\\n  right: 0px;\\n}\\n.right-0\\\\.5 {\\n  right: 2px;\\n}\\n.right-2 {\\n  right: 8px;\\n}\\n.right-4 {\\n  right: 16px;\\n}\\n.top-0 {\\n  top: 0px;\\n}\\n.top-0\\\\.5 {\\n  top: 2px;\\n}\\n.top-1\\\\/2 {\\n  top: 50%;\\n}\\n.top-2 {\\n  top: 8px;\\n}\\n.z-10 {\\n  z-index: 10;\\n}\\n.z-100 {\\n  z-index: 100;\\n}\\n.z-50 {\\n  z-index: 50;\\n}\\n.z-\\\\[124124124124\\\\] {\\n  z-index: 124124124124;\\n}\\n.z-\\\\[214748365\\\\] {\\n  z-index: 214748365;\\n}\\n.z-\\\\[214748367\\\\] {\\n  z-index: 214748367;\\n}\\n.m-\\\\[2px\\\\] {\\n  margin: 2px;\\n}\\n.mx-0\\\\.5 {\\n  margin-left: 2px;\\n  margin-right: 2px;\\n}\\n.\\\\!ml-0 {\\n  margin-left: 0px !important;\\n}\\n.mb-1\\\\.5 {\\n  margin-bottom: 6px;\\n}\\n.mb-2 {\\n  margin-bottom: 8px;\\n}\\n.mb-3 {\\n  margin-bottom: 12px;\\n}\\n.mb-4 {\\n  margin-bottom: 16px;\\n}\\n.mb-px {\\n  margin-bottom: 1px;\\n}\\n.ml-1 {\\n  margin-left: 4px;\\n}\\n.ml-1\\\\.5 {\\n  margin-left: 6px;\\n}\\n.ml-auto {\\n  margin-left: auto;\\n}\\n.mr-0\\\\.5 {\\n  margin-right: 2px;\\n}\\n.mr-1 {\\n  margin-right: 4px;\\n}\\n.mr-1\\\\.5 {\\n  margin-right: 6px;\\n}\\n.mr-16 {\\n  margin-right: 64px;\\n}\\n.mr-auto {\\n  margin-right: auto;\\n}\\n.mt-0\\\\.5 {\\n  margin-top: 2px;\\n}\\n.mt-1 {\\n  margin-top: 4px;\\n}\\n.mt-4 {\\n  margin-top: 16px;\\n}\\n.block {\\n  display: block;\\n}\\n.inline {\\n  display: inline;\\n}\\n.flex {\\n  display: flex;\\n}\\n.hidden {\\n  display: none;\\n}\\n.aspect-square {\\n  aspect-ratio: 1 / 1;\\n}\\n.h-1 {\\n  height: 4px;\\n}\\n.h-1\\\\.5 {\\n  height: 6px;\\n}\\n.h-10 {\\n  height: 40px;\\n}\\n.h-12 {\\n  height: 48px;\\n}\\n.h-4 {\\n  height: 16px;\\n}\\n.h-4\\\\/5 {\\n  height: 80%;\\n}\\n.h-6 {\\n  height: 24px;\\n}\\n.h-7 {\\n  height: 28px;\\n}\\n.h-8 {\\n  height: 32px;\\n}\\n.h-\\\\[150px\\\\] {\\n  height: 150px;\\n}\\n.h-\\\\[235px\\\\] {\\n  height: 235px;\\n}\\n.h-\\\\[28px\\\\] {\\n  height: 28px;\\n}\\n.h-\\\\[48px\\\\] {\\n  height: 48px;\\n}\\n.h-\\\\[50px\\\\] {\\n  height: 50px;\\n}\\n.h-\\\\[calc\\\\(100\\\\%-150px\\\\)\\\\] {\\n  height: calc(100% - 150px);\\n}\\n.h-\\\\[calc\\\\(100\\\\%-200px\\\\)\\\\] {\\n  height: calc(100% - 200px);\\n}\\n.h-\\\\[calc\\\\(100\\\\%-25px\\\\)\\\\] {\\n  height: calc(100% - 25px);\\n}\\n.h-\\\\[calc\\\\(100\\\\%-40px\\\\)\\\\] {\\n  height: calc(100% - 40px);\\n}\\n.h-\\\\[calc\\\\(100\\\\%-48px\\\\)\\\\] {\\n  height: calc(100% - 48px);\\n}\\n.h-fit {\\n  height: -moz-fit-content;\\n  height: fit-content;\\n}\\n.h-full {\\n  height: 100%;\\n}\\n.h-screen {\\n  height: 100vh;\\n}\\n.max-h-0 {\\n  max-height: 0px;\\n}\\n.max-h-40 {\\n  max-height: 160px;\\n}\\n.max-h-9 {\\n  max-height: 36px;\\n}\\n.min-h-9 {\\n  min-height: 36px;\\n}\\n.min-h-\\\\[48px\\\\] {\\n  min-height: 48px;\\n}\\n.min-h-fit {\\n  min-height: -moz-fit-content;\\n  min-height: fit-content;\\n}\\n.w-1 {\\n  width: 4px;\\n}\\n.w-1\\\\/2 {\\n  width: 50%;\\n}\\n.w-1\\\\/3 {\\n  width: 33.333333%;\\n}\\n.w-2\\\\/4 {\\n  width: 50%;\\n}\\n.w-3 {\\n  width: 12px;\\n}\\n.w-4 {\\n  width: 16px;\\n}\\n.w-4\\\\/5 {\\n  width: 80%;\\n}\\n.w-6 {\\n  width: 24px;\\n}\\n.w-80 {\\n  width: 320px;\\n}\\n.w-\\\\[20px\\\\] {\\n  width: 20px;\\n}\\n.w-\\\\[72px\\\\] {\\n  width: 72px;\\n}\\n.w-\\\\[90\\\\%\\\\] {\\n  width: 90%;\\n}\\n.w-\\\\[calc\\\\(100\\\\%-200px\\\\)\\\\] {\\n  width: calc(100% - 200px);\\n}\\n.w-fit {\\n  width: -moz-fit-content;\\n  width: fit-content;\\n}\\n.w-full {\\n  width: 100%;\\n}\\n.w-px {\\n  width: 1px;\\n}\\n.w-screen {\\n  width: 100vw;\\n}\\n.min-w-0 {\\n  min-width: 0px;\\n}\\n.min-w-\\\\[200px\\\\] {\\n  min-width: 200px;\\n}\\n.min-w-fit {\\n  min-width: -moz-fit-content;\\n  min-width: fit-content;\\n}\\n.max-w-md {\\n  max-width: 448px;\\n}\\n.flex-1 {\\n  flex: 1 1 0%;\\n}\\n.shrink-0 {\\n  flex-shrink: 0;\\n}\\n.grow {\\n  flex-grow: 1;\\n}\\n.-translate-y-1\\\\/2 {\\n  --tw-translate-y: -50%;\\n  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n}\\n.-translate-y-\\\\[200\\\\%\\\\] {\\n  --tw-translate-y: -200%;\\n  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n}\\n.translate-y-0 {\\n  --tw-translate-y: 0px;\\n  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n}\\n.-rotate-90 {\\n  --tw-rotate: -90deg;\\n  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n}\\n.rotate-0 {\\n  --tw-rotate: 0deg;\\n  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n}\\n.rotate-180 {\\n  --tw-rotate: 180deg;\\n  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n}\\n.rotate-90 {\\n  --tw-rotate: 90deg;\\n  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n}\\n.scale-110 {\\n  --tw-scale-x: 1.1;\\n  --tw-scale-y: 1.1;\\n  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n}\\n.transform {\\n  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n}\\n@keyframes fadeIn {\\n\\n  0% {\\n    opacity: 0;\\n  }\\n\\n  100% {\\n    opacity: 1;\\n  }\\n}\\n.animate-fade-in {\\n  animation: fadeIn ease-in forwards;\\n}\\n.cursor-default {\\n  cursor: default;\\n}\\n.cursor-e-resize {\\n  cursor: e-resize;\\n}\\n.cursor-ew-resize {\\n  cursor: ew-resize;\\n}\\n.cursor-move {\\n  cursor: move;\\n}\\n.cursor-nesw-resize {\\n  cursor: nesw-resize;\\n}\\n.cursor-ns-resize {\\n  cursor: ns-resize;\\n}\\n.cursor-nwse-resize {\\n  cursor: nwse-resize;\\n}\\n.cursor-pointer {\\n  cursor: pointer;\\n}\\n.cursor-w-resize {\\n  cursor: w-resize;\\n}\\n.select-none {\\n  -webkit-user-select: none;\\n     -moz-user-select: none;\\n          user-select: none;\\n}\\n.resize {\\n  resize: both;\\n}\\n.appearance-none {\\n  -webkit-appearance: none;\\n     -moz-appearance: none;\\n          appearance: none;\\n}\\n.flex-col {\\n  flex-direction: column;\\n}\\n.items-start {\\n  align-items: flex-start;\\n}\\n.items-end {\\n  align-items: flex-end;\\n}\\n.items-center {\\n  align-items: center;\\n}\\n.items-stretch {\\n  align-items: stretch;\\n}\\n.justify-start {\\n  justify-content: flex-start;\\n}\\n.justify-end {\\n  justify-content: flex-end;\\n}\\n.justify-center {\\n  justify-content: center;\\n}\\n.justify-between {\\n  justify-content: space-between;\\n}\\n.gap-0\\\\.5 {\\n  gap: 2px;\\n}\\n.gap-1 {\\n  gap: 4px;\\n}\\n.gap-1\\\\.5 {\\n  gap: 6px;\\n}\\n.gap-2 {\\n  gap: 8px;\\n}\\n.gap-4 {\\n  gap: 16px;\\n}\\n.gap-x-0\\\\.5 {\\n  -moz-column-gap: 2px;\\n       column-gap: 2px;\\n}\\n.gap-x-1 {\\n  -moz-column-gap: 4px;\\n       column-gap: 4px;\\n}\\n.gap-x-1\\\\.5 {\\n  -moz-column-gap: 6px;\\n       column-gap: 6px;\\n}\\n.gap-x-2 {\\n  -moz-column-gap: 8px;\\n       column-gap: 8px;\\n}\\n.gap-x-3 {\\n  -moz-column-gap: 12px;\\n       column-gap: 12px;\\n}\\n.gap-x-4 {\\n  -moz-column-gap: 16px;\\n       column-gap: 16px;\\n}\\n.gap-y-0\\\\.5 {\\n  row-gap: 2px;\\n}\\n.gap-y-1 {\\n  row-gap: 4px;\\n}\\n.gap-y-2 {\\n  row-gap: 8px;\\n}\\n.gap-y-4 {\\n  row-gap: 16px;\\n}\\n.space-y-1\\\\.5 > :not([hidden]) ~ :not([hidden]) {\\n  --tw-space-y-reverse: 0;\\n  margin-top: calc(6px * calc(1 - var(--tw-space-y-reverse)));\\n  margin-bottom: calc(6px * var(--tw-space-y-reverse));\\n}\\n.divide-y > :not([hidden]) ~ :not([hidden]) {\\n  --tw-divide-y-reverse: 0;\\n  border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));\\n  border-bottom-width: calc(1px * var(--tw-divide-y-reverse));\\n}\\n.divide-zinc-800 > :not([hidden]) ~ :not([hidden]) {\\n  --tw-divide-opacity: 1;\\n  border-color: rgb(39 39 42 / var(--tw-divide-opacity, 1));\\n}\\n.place-self-center {\\n  place-self: center;\\n}\\n.self-end {\\n  align-self: flex-end;\\n}\\n.overflow-auto {\\n  overflow: auto;\\n}\\n.overflow-hidden {\\n  overflow: hidden;\\n}\\n.\\\\!overflow-visible {\\n  overflow: visible !important;\\n}\\n.overflow-x-auto {\\n  overflow-x: auto;\\n}\\n.overflow-y-auto {\\n  overflow-y: auto;\\n}\\n.overflow-x-hidden {\\n  overflow-x: hidden;\\n}\\n.truncate {\\n  overflow: hidden;\\n  text-overflow: ellipsis;\\n  white-space: nowrap;\\n}\\n.whitespace-nowrap {\\n  white-space: nowrap;\\n}\\n.whitespace-pre-wrap {\\n  white-space: pre-wrap;\\n}\\n.text-wrap {\\n  text-wrap: wrap;\\n}\\n.break-words {\\n  overflow-wrap: break-word;\\n}\\n.break-all {\\n  word-break: break-all;\\n}\\n.rounded {\\n  border-radius: 4px;\\n}\\n.rounded-full {\\n  border-radius: 9999px;\\n}\\n.rounded-lg {\\n  border-radius: 8px;\\n}\\n.rounded-md {\\n  border-radius: 6px;\\n}\\n.rounded-sm {\\n  border-radius: 2px;\\n}\\n.rounded-l-md {\\n  border-top-left-radius: 6px;\\n  border-bottom-left-radius: 6px;\\n}\\n.rounded-l-sm {\\n  border-top-left-radius: 2px;\\n  border-bottom-left-radius: 2px;\\n}\\n.rounded-r-md {\\n  border-top-right-radius: 6px;\\n  border-bottom-right-radius: 6px;\\n}\\n.rounded-r-sm {\\n  border-top-right-radius: 2px;\\n  border-bottom-right-radius: 2px;\\n}\\n.rounded-t-lg {\\n  border-top-left-radius: 8px;\\n  border-top-right-radius: 8px;\\n}\\n.rounded-t-sm {\\n  border-top-left-radius: 2px;\\n  border-top-right-radius: 2px;\\n}\\n.rounded-bl-lg {\\n  border-bottom-left-radius: 8px;\\n}\\n.rounded-br-lg {\\n  border-bottom-right-radius: 8px;\\n}\\n.rounded-tl-lg {\\n  border-top-left-radius: 8px;\\n}\\n.rounded-tr-lg {\\n  border-top-right-radius: 8px;\\n}\\n.border {\\n  border-width: 1px;\\n}\\n.border-4 {\\n  border-width: 4px;\\n}\\n.border-b {\\n  border-bottom-width: 1px;\\n}\\n.border-l {\\n  border-left-width: 1px;\\n}\\n.border-l-0 {\\n  border-left-width: 0px;\\n}\\n.border-l-1 {\\n  border-left-width: 1px;\\n}\\n.border-r {\\n  border-right-width: 1px;\\n}\\n.border-t {\\n  border-top-width: 1px;\\n}\\n.border-none {\\n  border-style: none;\\n}\\n.\\\\!border-red-500 {\\n  --tw-border-opacity: 1 !important;\\n  border-color: rgb(239 68 68 / var(--tw-border-opacity, 1)) !important;\\n}\\n.border-\\\\[\\\\#1e1e1e\\\\] {\\n  --tw-border-opacity: 1;\\n  border-color: rgb(30 30 30 / var(--tw-border-opacity, 1));\\n}\\n.border-\\\\[\\\\#222\\\\] {\\n  --tw-border-opacity: 1;\\n  border-color: rgb(34 34 34 / var(--tw-border-opacity, 1));\\n}\\n.border-\\\\[\\\\#27272A\\\\] {\\n  --tw-border-opacity: 1;\\n  border-color: rgb(39 39 42 / var(--tw-border-opacity, 1));\\n}\\n.border-\\\\[\\\\#333\\\\] {\\n  --tw-border-opacity: 1;\\n  border-color: rgb(51 51 51 / var(--tw-border-opacity, 1));\\n}\\n.border-transparent {\\n  border-color: transparent;\\n}\\n.border-zinc-800 {\\n  --tw-border-opacity: 1;\\n  border-color: rgb(39 39 42 / var(--tw-border-opacity, 1));\\n}\\n.bg-\\\\[\\\\#0A0A0A\\\\] {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(10 10 10 / var(--tw-bg-opacity, 1));\\n}\\n.bg-\\\\[\\\\#141414\\\\] {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(20 20 20 / var(--tw-bg-opacity, 1));\\n}\\n.bg-\\\\[\\\\#18181B\\\\] {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(24 24 27 / var(--tw-bg-opacity, 1));\\n}\\n.bg-\\\\[\\\\#18181B\\\\]\\\\/50 {\\n  background-color: rgb(24 24 27 / 0.5);\\n}\\n.bg-\\\\[\\\\#1D3A66\\\\] {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(29 58 102 / var(--tw-bg-opacity, 1));\\n}\\n.bg-\\\\[\\\\#1E1E1E\\\\] {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(30 30 30 / var(--tw-bg-opacity, 1));\\n}\\n.bg-\\\\[\\\\#1a2a1a\\\\] {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(26 42 26 / var(--tw-bg-opacity, 1));\\n}\\n.bg-\\\\[\\\\#1e1e1e\\\\] {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(30 30 30 / var(--tw-bg-opacity, 1));\\n}\\n.bg-\\\\[\\\\#214379d4\\\\] {\\n  background-color: #214379d4;\\n}\\n.bg-\\\\[\\\\#27272A\\\\] {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(39 39 42 / var(--tw-bg-opacity, 1));\\n}\\n.bg-\\\\[\\\\#2a1515\\\\] {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(42 21 21 / var(--tw-bg-opacity, 1));\\n}\\n.bg-\\\\[\\\\#412162\\\\] {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(65 33 98 / var(--tw-bg-opacity, 1));\\n}\\n.bg-\\\\[\\\\#44444a\\\\] {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(68 68 74 / var(--tw-bg-opacity, 1));\\n}\\n.bg-\\\\[\\\\#4b4b4b\\\\] {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(75 75 75 / var(--tw-bg-opacity, 1));\\n}\\n.bg-\\\\[\\\\#5f3f9a\\\\] {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(95 63 154 / var(--tw-bg-opacity, 1));\\n}\\n.bg-\\\\[\\\\#5f3f9a\\\\]\\\\/40 {\\n  background-color: rgb(95 63 154 / 0.4);\\n}\\n.bg-\\\\[\\\\#6a369e\\\\] {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(106 54 158 / var(--tw-bg-opacity, 1));\\n}\\n.bg-\\\\[\\\\#7521c8\\\\] {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(117 33 200 / var(--tw-bg-opacity, 1));\\n}\\n.bg-\\\\[\\\\#8e61e3\\\\] {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(142 97 227 / var(--tw-bg-opacity, 1));\\n}\\n.bg-\\\\[\\\\#EFD81A\\\\] {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(239 216 26 / var(--tw-bg-opacity, 1));\\n}\\n.bg-\\\\[\\\\#b77116\\\\] {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(183 113 22 / var(--tw-bg-opacity, 1));\\n}\\n.bg-\\\\[\\\\#b94040\\\\] {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(185 64 64 / var(--tw-bg-opacity, 1));\\n}\\n.bg-\\\\[\\\\#d36cff\\\\] {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(211 108 255 / var(--tw-bg-opacity, 1));\\n}\\n.bg-\\\\[\\\\#efd81a6b\\\\] {\\n  background-color: #efd81a6b;\\n}\\n.bg-black {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(0 0 0 / var(--tw-bg-opacity, 1));\\n}\\n.bg-black\\\\/40 {\\n  background-color: rgb(0 0 0 / 0.4);\\n}\\n.bg-gray-200 {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(229 231 235 / var(--tw-bg-opacity, 1));\\n}\\n.bg-green-500\\\\/50 {\\n  background-color: rgb(34 197 94 / 0.5);\\n}\\n.bg-green-500\\\\/60 {\\n  background-color: rgb(34 197 94 / 0.6);\\n}\\n.bg-neutral-700 {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(64 64 64 / var(--tw-bg-opacity, 1));\\n}\\n.bg-purple-500 {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(168 85 247 / var(--tw-bg-opacity, 1));\\n}\\n.bg-purple-500\\\\/90 {\\n  background-color: rgb(168 85 247 / 0.9);\\n}\\n.bg-purple-800 {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(107 33 168 / var(--tw-bg-opacity, 1));\\n}\\n.bg-red-500 {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(239 68 68 / var(--tw-bg-opacity, 1));\\n}\\n.bg-red-500\\\\/90 {\\n  background-color: rgb(239 68 68 / 0.9);\\n}\\n.bg-red-950\\\\/50 {\\n  background-color: rgb(69 10 10 / 0.5);\\n}\\n.bg-transparent {\\n  background-color: transparent;\\n}\\n.bg-white {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));\\n}\\n.bg-yellow-300 {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(253 224 71 / var(--tw-bg-opacity, 1));\\n}\\n.bg-zinc-800 {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(39 39 42 / var(--tw-bg-opacity, 1));\\n}\\n.bg-zinc-900\\\\/30 {\\n  background-color: rgb(24 24 27 / 0.3);\\n}\\n.bg-zinc-900\\\\/50 {\\n  background-color: rgb(24 24 27 / 0.5);\\n}\\n.p-0 {\\n  padding: 0px;\\n}\\n.p-1 {\\n  padding: 4px;\\n}\\n.p-2 {\\n  padding: 8px;\\n}\\n.p-3 {\\n  padding: 12px;\\n}\\n.p-4 {\\n  padding: 16px;\\n}\\n.p-5 {\\n  padding: 20px;\\n}\\n.p-6 {\\n  padding: 24px;\\n}\\n.px-1 {\\n  padding-left: 4px;\\n  padding-right: 4px;\\n}\\n.px-1\\\\.5 {\\n  padding-left: 6px;\\n  padding-right: 6px;\\n}\\n.px-2 {\\n  padding-left: 8px;\\n  padding-right: 8px;\\n}\\n.px-2\\\\.5 {\\n  padding-left: 10px;\\n  padding-right: 10px;\\n}\\n.px-3 {\\n  padding-left: 12px;\\n  padding-right: 12px;\\n}\\n.px-4 {\\n  padding-left: 16px;\\n  padding-right: 16px;\\n}\\n.py-0\\\\.5 {\\n  padding-top: 2px;\\n  padding-bottom: 2px;\\n}\\n.py-1 {\\n  padding-top: 4px;\\n  padding-bottom: 4px;\\n}\\n.py-1\\\\.5 {\\n  padding-top: 6px;\\n  padding-bottom: 6px;\\n}\\n.py-2 {\\n  padding-top: 8px;\\n  padding-bottom: 8px;\\n}\\n.py-3 {\\n  padding-top: 12px;\\n  padding-bottom: 12px;\\n}\\n.py-4 {\\n  padding-top: 16px;\\n  padding-bottom: 16px;\\n}\\n.py-\\\\[1px\\\\] {\\n  padding-top: 1px;\\n  padding-bottom: 1px;\\n}\\n.py-\\\\[3px\\\\] {\\n  padding-top: 3px;\\n  padding-bottom: 3px;\\n}\\n.py-\\\\[5px\\\\] {\\n  padding-top: 5px;\\n  padding-bottom: 5px;\\n}\\n.pb-2 {\\n  padding-bottom: 8px;\\n}\\n.pl-1 {\\n  padding-left: 4px;\\n}\\n.pl-2 {\\n  padding-left: 8px;\\n}\\n.pl-2\\\\.5 {\\n  padding-left: 10px;\\n}\\n.pl-3 {\\n  padding-left: 12px;\\n}\\n.pl-5 {\\n  padding-left: 20px;\\n}\\n.pl-6 {\\n  padding-left: 24px;\\n}\\n.pr-1 {\\n  padding-right: 4px;\\n}\\n.pr-1\\\\.5 {\\n  padding-right: 6px;\\n}\\n.pr-2 {\\n  padding-right: 8px;\\n}\\n.pr-2\\\\.5 {\\n  padding-right: 10px;\\n}\\n.pt-0 {\\n  padding-top: 0px;\\n}\\n.pt-2 {\\n  padding-top: 8px;\\n}\\n.pt-5 {\\n  padding-top: 20px;\\n}\\n.text-left {\\n  text-align: left;\\n}\\n.font-mono {\\n  font-family: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace;\\n}\\n.text-\\\\[10px\\\\] {\\n  font-size: 10px;\\n}\\n.text-\\\\[11px\\\\] {\\n  font-size: 11px;\\n}\\n.text-\\\\[13px\\\\] {\\n  font-size: 13px;\\n}\\n.text-\\\\[14px\\\\] {\\n  font-size: 14px;\\n}\\n.text-\\\\[17px\\\\] {\\n  font-size: 17px;\\n}\\n.text-\\\\[8px\\\\] {\\n  font-size: 8px;\\n}\\n.text-sm {\\n  font-size: 14px;\\n  line-height: 20px;\\n}\\n.text-xs {\\n  font-size: 12px;\\n  line-height: 16px;\\n}\\n.font-bold {\\n  font-weight: 700;\\n}\\n.font-medium {\\n  font-weight: 500;\\n}\\n.font-semibold {\\n  font-weight: 600;\\n}\\n.uppercase {\\n  text-transform: uppercase;\\n}\\n.capitalize {\\n  text-transform: capitalize;\\n}\\n.italic {\\n  font-style: italic;\\n}\\n.leading-6 {\\n  line-height: 24px;\\n}\\n.leading-none {\\n  line-height: 1;\\n}\\n.tracking-wide {\\n  letter-spacing: 0.025em;\\n}\\n.text-\\\\[\\\\#4ade80\\\\] {\\n  --tw-text-opacity: 1;\\n  color: rgb(74 222 128 / var(--tw-text-opacity, 1));\\n}\\n.text-\\\\[\\\\#5a5a5a\\\\] {\\n  --tw-text-opacity: 1;\\n  color: rgb(90 90 90 / var(--tw-text-opacity, 1));\\n}\\n.text-\\\\[\\\\#65656D\\\\] {\\n  --tw-text-opacity: 1;\\n  color: rgb(101 101 109 / var(--tw-text-opacity, 1));\\n}\\n.text-\\\\[\\\\#666\\\\] {\\n  --tw-text-opacity: 1;\\n  color: rgb(102 102 102 / var(--tw-text-opacity, 1));\\n}\\n.text-\\\\[\\\\#6E6E77\\\\] {\\n  --tw-text-opacity: 1;\\n  color: rgb(110 110 119 / var(--tw-text-opacity, 1));\\n}\\n.text-\\\\[\\\\#6F6F78\\\\] {\\n  --tw-text-opacity: 1;\\n  color: rgb(111 111 120 / var(--tw-text-opacity, 1));\\n}\\n.text-\\\\[\\\\#7346a0\\\\] {\\n  --tw-text-opacity: 1;\\n  color: rgb(115 70 160 / var(--tw-text-opacity, 1));\\n}\\n.text-\\\\[\\\\#737373\\\\] {\\n  --tw-text-opacity: 1;\\n  color: rgb(115 115 115 / var(--tw-text-opacity, 1));\\n}\\n.text-\\\\[\\\\#888\\\\] {\\n  --tw-text-opacity: 1;\\n  color: rgb(136 136 136 / var(--tw-text-opacity, 1));\\n}\\n.text-\\\\[\\\\#8E61E3\\\\] {\\n  --tw-text-opacity: 1;\\n  color: rgb(142 97 227 / var(--tw-text-opacity, 1));\\n}\\n.text-\\\\[\\\\#999\\\\] {\\n  --tw-text-opacity: 1;\\n  color: rgb(153 153 153 / var(--tw-text-opacity, 1));\\n}\\n.text-\\\\[\\\\#A1A1AA\\\\] {\\n  --tw-text-opacity: 1;\\n  color: rgb(161 161 170 / var(--tw-text-opacity, 1));\\n}\\n.text-\\\\[\\\\#A855F7\\\\] {\\n  --tw-text-opacity: 1;\\n  color: rgb(168 85 247 / var(--tw-text-opacity, 1));\\n}\\n.text-\\\\[\\\\#E4E4E7\\\\] {\\n  --tw-text-opacity: 1;\\n  color: rgb(228 228 231 / var(--tw-text-opacity, 1));\\n}\\n.text-\\\\[\\\\#d36cff\\\\] {\\n  --tw-text-opacity: 1;\\n  color: rgb(211 108 255 / var(--tw-text-opacity, 1));\\n}\\n.text-\\\\[\\\\#f87171\\\\] {\\n  --tw-text-opacity: 1;\\n  color: rgb(248 113 113 / var(--tw-text-opacity, 1));\\n}\\n.text-black {\\n  --tw-text-opacity: 1;\\n  color: rgb(0 0 0 / var(--tw-text-opacity, 1));\\n}\\n.text-gray-100 {\\n  --tw-text-opacity: 1;\\n  color: rgb(243 244 246 / var(--tw-text-opacity, 1));\\n}\\n.text-gray-300 {\\n  --tw-text-opacity: 1;\\n  color: rgb(209 213 219 / var(--tw-text-opacity, 1));\\n}\\n.text-gray-400 {\\n  --tw-text-opacity: 1;\\n  color: rgb(156 163 175 / var(--tw-text-opacity, 1));\\n}\\n.text-gray-500 {\\n  --tw-text-opacity: 1;\\n  color: rgb(107 114 128 / var(--tw-text-opacity, 1));\\n}\\n.text-green-500 {\\n  --tw-text-opacity: 1;\\n  color: rgb(34 197 94 / var(--tw-text-opacity, 1));\\n}\\n.text-neutral-300 {\\n  --tw-text-opacity: 1;\\n  color: rgb(212 212 212 / var(--tw-text-opacity, 1));\\n}\\n.text-neutral-400 {\\n  --tw-text-opacity: 1;\\n  color: rgb(163 163 163 / var(--tw-text-opacity, 1));\\n}\\n.text-neutral-500 {\\n  --tw-text-opacity: 1;\\n  color: rgb(115 115 115 / var(--tw-text-opacity, 1));\\n}\\n.text-purple-400 {\\n  --tw-text-opacity: 1;\\n  color: rgb(192 132 252 / var(--tw-text-opacity, 1));\\n}\\n.text-red-300 {\\n  --tw-text-opacity: 1;\\n  color: rgb(252 165 165 / var(--tw-text-opacity, 1));\\n}\\n.text-red-400 {\\n  --tw-text-opacity: 1;\\n  color: rgb(248 113 113 / var(--tw-text-opacity, 1));\\n}\\n.text-red-500 {\\n  --tw-text-opacity: 1;\\n  color: rgb(239 68 68 / var(--tw-text-opacity, 1));\\n}\\n.text-white {\\n  --tw-text-opacity: 1;\\n  color: rgb(255 255 255 / var(--tw-text-opacity, 1));\\n}\\n.text-white\\\\/30 {\\n  color: rgb(255 255 255 / 0.3);\\n}\\n.text-white\\\\/70 {\\n  color: rgb(255 255 255 / 0.7);\\n}\\n.text-yellow-300 {\\n  --tw-text-opacity: 1;\\n  color: rgb(253 224 71 / var(--tw-text-opacity, 1));\\n}\\n.text-yellow-500 {\\n  --tw-text-opacity: 1;\\n  color: rgb(234 179 8 / var(--tw-text-opacity, 1));\\n}\\n.text-zinc-200 {\\n  --tw-text-opacity: 1;\\n  color: rgb(228 228 231 / var(--tw-text-opacity, 1));\\n}\\n.text-zinc-400 {\\n  --tw-text-opacity: 1;\\n  color: rgb(161 161 170 / var(--tw-text-opacity, 1));\\n}\\n.text-zinc-500 {\\n  --tw-text-opacity: 1;\\n  color: rgb(113 113 122 / var(--tw-text-opacity, 1));\\n}\\n.text-zinc-600 {\\n  --tw-text-opacity: 1;\\n  color: rgb(82 82 91 / var(--tw-text-opacity, 1));\\n}\\n.opacity-0 {\\n  opacity: 0;\\n}\\n.opacity-100 {\\n  opacity: 1;\\n}\\n.opacity-50 {\\n  opacity: 0.5;\\n}\\n.shadow-lg {\\n  --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);\\n  --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);\\n  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);\\n}\\n.outline {\\n  outline-style: solid;\\n}\\n.ring-1 {\\n  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);\\n  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);\\n  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);\\n}\\n.ring-white\\\\/\\\\[0\\\\.08\\\\] {\\n  --tw-ring-color: rgb(255 255 255 / 0.08);\\n}\\n.blur {\\n  --tw-blur: blur(8px);\\n  filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);\\n}\\n.\\\\!filter {\\n  filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow) !important;\\n}\\n.filter {\\n  filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);\\n}\\n.backdrop-blur-sm {\\n  --tw-backdrop-blur: blur(4px);\\n  -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);\\n  backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);\\n}\\n.transition {\\n  transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;\\n  transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;\\n  transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;\\n  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n  transition-duration: 150ms;\\n}\\n.transition-\\\\[border-radius\\\\] {\\n  transition-property: border-radius;\\n  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n  transition-duration: 150ms;\\n}\\n.transition-\\\\[color\\\\2c transform\\\\] {\\n  transition-property: color,transform;\\n  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n  transition-duration: 150ms;\\n}\\n.transition-\\\\[max-height\\\\] {\\n  transition-property: max-height;\\n  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n  transition-duration: 150ms;\\n}\\n.transition-\\\\[opacity\\\\] {\\n  transition-property: opacity;\\n  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n  transition-duration: 150ms;\\n}\\n.transition-all {\\n  transition-property: all;\\n  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n  transition-duration: 150ms;\\n}\\n.transition-colors {\\n  transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;\\n  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n  transition-duration: 150ms;\\n}\\n.transition-none {\\n  transition-property: none;\\n}\\n.transition-opacity {\\n  transition-property: opacity;\\n  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n  transition-duration: 150ms;\\n}\\n.transition-transform {\\n  transition-property: transform;\\n  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n  transition-duration: 150ms;\\n}\\n.delay-0 {\\n  transition-delay: 0s;\\n}\\n.delay-150 {\\n  transition-delay: 150ms;\\n}\\n.delay-300 {\\n  transition-delay: 300ms;\\n}\\n.\\\\!duration-0 {\\n  transition-duration: 0s !important;\\n}\\n.duration-0 {\\n  transition-duration: 0s;\\n}\\n.duration-200 {\\n  transition-duration: 200ms;\\n}\\n.duration-300 {\\n  transition-duration: 300ms;\\n}\\n.ease-\\\\[cubic-bezier\\\\(0\\\\.25\\\\2c 0\\\\.1\\\\2c 0\\\\.25\\\\2c 1\\\\)\\\\] {\\n  transition-timing-function: cubic-bezier(0.25,0.1,0.25,1);\\n}\\n.ease-in-out {\\n  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n}\\n.ease-out {\\n  transition-timing-function: cubic-bezier(0, 0, 0.2, 1);\\n}\\n.will-change-transform {\\n  will-change: transform;\\n}\\n.animation-duration-300 {\\n  animation-duration: .3s;\\n}\\n.animation-delay-300 {\\n  animation-delay: .3s;\\n}\\n.\\\\[touch-action\\\\:none\\\\] {\\n  touch-action: none;\\n}\\n\\n* {\\n  outline: none !important;\\n  text-rendering: optimizeLegibility;\\n  -webkit-font-smoothing: antialiased;\\n  -moz-osx-font-smoothing: grayscale;\\n\\n  /* WebKit (Chrome, Safari, Edge) specific scrollbar styles */\\n  &::-webkit-scrollbar {\\n    width: 6px;\\n    height: 6px;\\n  }\\n\\n  &::-webkit-scrollbar-track {\\n    border-radius: 10px;\\n    background: transparent;\\n  }\\n\\n  &::-webkit-scrollbar-thumb {\\n    border-radius: 10px;\\n    background: rgba(255, 255, 255, 0.3);\\n  }\\n\\n  &::-webkit-scrollbar-thumb:hover {\\n    background: rgba(255, 255, 255, 0.4);\\n  }\\n\\n  &::-webkit-scrollbar-corner {\\n    background: transparent;\\n  }\\n}\\n\\n@-moz-document url-prefix() {\\n  * {\\n    scrollbar-width: thin;\\n    scrollbar-color: rgba(255, 255, 255, 0.4) transparent;\\n    scrollbar-width: 6px;\\n  }\\n}\\n\\nbutton:hover {\\n  background-image: none;\\n}\\n\\nbutton {\\n  outline: 2px solid transparent;\\n  outline-offset: 2px;\\n  border-style: none;\\n  transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;\\n  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n  transition-duration: 150ms;\\n  transition-timing-function: cubic-bezier(0, 0, 0.2, 1);\\n  cursor: pointer;\\n}\\n\\ninput {\\n  outline: 2px solid transparent;\\n  outline-offset: 2px;\\n  border-style: none;\\n  background-color: transparent;\\n  background-image: none;\\n}\\n\\ninput::-moz-placeholder {\\n  font-size: 12px;\\n  line-height: 16px;\\n  font-style: italic;\\n  --tw-text-opacity: 1;\\n  color: rgb(115 115 115 / var(--tw-text-opacity, 1));\\n}\\n\\ninput::placeholder {\\n  font-size: 12px;\\n  line-height: 16px;\\n  font-style: italic;\\n  --tw-text-opacity: 1;\\n  color: rgb(115 115 115 / var(--tw-text-opacity, 1));\\n}\\n\\ninput:-moz-placeholder {\\n  overflow: hidden;\\n  text-overflow: ellipsis;\\n  white-space: nowrap;\\n}\\n\\ninput:placeholder-shown {\\n  overflow: hidden;\\n  text-overflow: ellipsis;\\n  white-space: nowrap;\\n}\\n\\nsvg {\\n  height: auto;\\n  width: auto;\\n  pointer-events: none;\\n}\\n\\n/*\\n  Using CSS content with data attributes is more performant than:\\n  1. React re-renders with JSX text content\\n  2. Direct DOM manipulation methods:\\n     - element.textContent (creates/updates text nodes, triggers repaint)\\n     - element.innerText (triggers reflow by computing styles & layout)\\n     - element.innerHTML (heavy parsing, triggers reflow, security risks)\\n  3. Multiple data attributes with complex CSS concatenation\\n\\n  This approach:\\n  - Avoids React reconciliation\\n  - Uses browser's native CSS engine (optimized content updates)\\n  - Minimizes main thread work\\n  - Reduces DOM operations\\n  - Avoids forced reflows (layout recalculation)\\n  - Only triggers necessary repaints\\n  - Keeps pseudo-element updates in render layer\\n*/\\n.with-data-text {\\n  overflow: hidden;\\n  &::before {\\n    content: attr(data-text);\\n  }\\n  &::before {\\n    display: block;\\n  }\\n  &::before {\\n    overflow: hidden;\\n    text-overflow: ellipsis;\\n    white-space: nowrap;\\n  }\\n}\\n\\n#react-scan-toolbar {\\n  position: fixed;\\n  left: 0px;\\n  top: 0px;\\n  display: flex;\\n  flex-direction: column;\\n  --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);\\n  --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);\\n  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);\\n  font-family: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace;\\n  font-size: 13px;\\n  --tw-text-opacity: 1;\\n  color: rgb(255 255 255 / var(--tw-text-opacity, 1));\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(0 0 0 / var(--tw-bg-opacity, 1));\\n  -webkit-user-select: none;\\n     -moz-user-select: none;\\n          user-select: none;\\n  cursor: move;\\n  opacity: 0;\\n  z-index: 2147483678;\\n}\\n\\n@keyframes fadeIn {\\n\\n  0% {\\n    opacity: 0;\\n  }\\n\\n  100% {\\n    opacity: 1;\\n  }\\n}\\n\\n#react-scan-toolbar {\\n  animation: fadeIn ease-in forwards;\\n  animation-duration: .3s;\\n  animation-delay: .3s;\\n  --tw-shadow: 0 4px 12px rgba(0,0,0,0.2);\\n  --tw-shadow-colored: 0 4px 12px var(--tw-shadow-color);\\n  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);\\n  place-self: start;\\n\\n  will-change: transform;\\n  backface-visibility: hidden;\\n}\\n\\n.button {\\n  &:hover {\\n    background: rgba(255, 255, 255, 0.1);\\n  }\\n\\n  &:active {\\n    background: rgba(255, 255, 255, 0.15);\\n  }\\n}\\n\\n.resize-line-wrapper {\\n  position: absolute;\\n  overflow: hidden;\\n}\\n\\n.resize-line {\\n  position: absolute;\\n  inset: 0px;\\n  overflow: hidden;\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(0 0 0 / var(--tw-bg-opacity, 1));\\n  transition-property: all;\\n  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n  transition-duration: 150ms;\\n\\n  svg {\\n    position: absolute;\\n  }\\n\\n  svg {\\n    top: 50%;\\n  }\\n\\n  svg {\\n    left: 50%;\\n  }\\n\\n  svg {\\n    --tw-translate-x: -50%;\\n    transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n  }\\n\\n  svg {\\n    --tw-translate-y: -50%;\\n    transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n  }\\n}\\n\\n.resize-right,\\n.resize-left {\\n  top: 0px;\\n  bottom: 0px;\\n  width: 24px;\\n  cursor: ew-resize;\\n\\n  .resize-line-wrapper {\\n    top: 0px;\\n    bottom: 0px;\\n  }\\n\\n  .resize-line-wrapper {\\n    width: 50%;\\n  }\\n\\n  &:hover {\\n    .resize-line {\\n      --tw-translate-x: 0px;\\n      transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n    }\\n  }\\n}\\n.resize-right {\\n  right: 0px;\\n  --tw-translate-x: 50%;\\n  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n\\n  .resize-line-wrapper {\\n    right: 0px;\\n  }\\n  .resize-line {\\n    border-top-right-radius: 8px;\\n    border-bottom-right-radius: 8px;\\n  }\\n  .resize-line {\\n    --tw-translate-x: -100%;\\n    transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n  }\\n}\\n\\n.resize-left {\\n  left: 0px;\\n  --tw-translate-x: -50%;\\n  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n\\n  .resize-line-wrapper {\\n    left: 0px;\\n  }\\n  .resize-line {\\n    border-top-left-radius: 8px;\\n    border-bottom-left-radius: 8px;\\n  }\\n  .resize-line {\\n    --tw-translate-x: 100%;\\n    transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n  }\\n}\\n\\n.resize-top,\\n.resize-bottom {\\n  left: 0px;\\n  right: 0px;\\n  height: 24px;\\n  cursor: ns-resize;\\n\\n  .resize-line-wrapper {\\n    left: 0px;\\n    right: 0px;\\n  }\\n\\n  .resize-line-wrapper {\\n    height: 50%;\\n  }\\n\\n  &:hover {\\n    .resize-line {\\n      --tw-translate-y: 0px;\\n      transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n    }\\n  }\\n}\\n.resize-top {\\n  top: 0px;\\n  --tw-translate-y: -50%;\\n  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n\\n  .resize-line-wrapper {\\n    top: 0px;\\n  }\\n  .resize-line {\\n    border-top-left-radius: 8px;\\n    border-top-right-radius: 8px;\\n  }\\n  .resize-line {\\n    --tw-translate-y: 100%;\\n    transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n  }\\n}\\n\\n.resize-bottom {\\n  bottom: 0px;\\n  --tw-translate-y: 50%;\\n  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n\\n  .resize-line-wrapper {\\n    bottom: 0px;\\n  }\\n  .resize-line {\\n    border-bottom-right-radius: 8px;\\n    border-bottom-left-radius: 8px;\\n  }\\n  .resize-line {\\n    --tw-translate-y: -100%;\\n    transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n  }\\n}\\n\\n.react-scan-header {\\n  display: flex;\\n  align-items: center;\\n  -moz-column-gap: 8px;\\n       column-gap: 8px;\\n  padding-left: 12px;\\n  padding-right: 8px;\\n  min-height: 36px;\\n  border-bottom-width: 1px;\\n  --tw-border-opacity: 1;\\n  border-color: rgb(34 34 34 / var(--tw-border-opacity, 1));\\n  overflow: hidden;\\n  white-space: nowrap;\\n}\\n\\n.react-scan-replay-button,\\n.react-scan-close-button {\\n  display: flex;\\n  align-items: center;\\n  padding: 4px;\\n  min-width: -moz-fit-content;\\n  min-width: fit-content;\\n  border-radius: 4px;\\n  transition-property: all;\\n  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n  transition-duration: 300ms;\\n}\\n\\n.react-scan-replay-button {\\n  position: relative;\\n  overflow: hidden;\\n  background-color: rgb(168 85 247 / 0.5) !important;\\n\\n  &:hover {\\n    background-color: rgb(168 85 247 / 0.25);\\n  }\\n\\n  &.disabled {\\n    opacity: 0.5;\\n  }\\n\\n  &.disabled {\\n    pointer-events: none;\\n  }\\n\\n  &:before {\\n    content: \\\"\\\";\\n  }\\n\\n  &:before {\\n    position: absolute;\\n  }\\n\\n  &:before {\\n    inset: 0px;\\n  }\\n\\n  &:before {\\n    --tw-translate-x: -100%;\\n    transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n  }\\n\\n  &:before {\\n    animation: shimmer 2s infinite;\\n    background: linear-gradient(\\n      to right,\\n      transparent,\\n      rgba(142, 97, 227, 0.3),\\n      transparent\\n    );\\n  }\\n}\\n\\n.react-scan-close-button {\\n  background-color: rgb(255 255 255 / 0.1);\\n\\n  &:hover {\\n    background-color: rgb(255 255 255 / 0.15);\\n  }\\n}\\n\\n@keyframes shimmer {\\n  100% {\\n    --tw-translate-x: 100%;\\n    transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n  }\\n}\\n\\n.react-section-header {\\n  position: sticky;\\n  z-index: 100;\\n  display: flex;\\n  align-items: center;\\n  -moz-column-gap: 8px;\\n       column-gap: 8px;\\n  padding-left: 12px;\\n  padding-right: 12px;\\n  height: 28px;\\n  width: 100%;\\n  overflow: hidden;\\n  text-overflow: ellipsis;\\n  white-space: nowrap;\\n  --tw-text-opacity: 1;\\n  color: rgb(136 136 136 / var(--tw-text-opacity, 1));\\n  border-bottom-width: 1px;\\n  --tw-border-opacity: 1;\\n  border-color: rgb(34 34 34 / var(--tw-border-opacity, 1));\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(10 10 10 / var(--tw-bg-opacity, 1));\\n}\\n\\n.react-scan-section {\\n  display: flex;\\n  flex-direction: column;\\n  padding-left: 8px;\\n  padding-right: 8px;\\n  --tw-text-opacity: 1;\\n  color: rgb(136 136 136 / var(--tw-text-opacity, 1));\\n}\\n\\n.react-scan-section::before {\\n  --tw-text-opacity: 1;\\n  color: rgb(107 114 128 / var(--tw-text-opacity, 1));\\n  --tw-content: attr(data-section);\\n  content: var(--tw-content);\\n}\\n\\n.react-scan-section {\\n  font-size: 12px;\\n  line-height: 16px;\\n\\n  > .react-scan-property {\\n    margin-left: -14px;\\n  }\\n}\\n\\n.react-scan-property {\\n  position: relative;\\n  display: flex;\\n  flex-direction: column;\\n  padding-left: 32px;\\n  border-left-width: 1px;\\n  border-color: transparent;\\n  overflow: hidden;\\n}\\n\\n.react-scan-property-content {\\n  display: flex;\\n  flex: 1 1 0%;\\n  flex-direction: column;\\n  min-height: 28px;\\n  max-width: 100%;\\n  overflow: hidden;\\n}\\n\\n.react-scan-string {\\n  color: #9ecbff;\\n}\\n\\n.react-scan-number {\\n  color: #79c7ff;\\n}\\n\\n.react-scan-boolean {\\n  color: #56b6c2;\\n}\\n\\n.react-scan-key {\\n  width: -moz-fit-content;\\n  width: fit-content;\\n  max-width: 240px;\\n  white-space: nowrap;\\n  --tw-text-opacity: 1;\\n  color: rgb(255 255 255 / var(--tw-text-opacity, 1));\\n}\\n\\n.react-scan-input {\\n  --tw-text-opacity: 1;\\n  color: rgb(255 255 255 / var(--tw-text-opacity, 1));\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(0 0 0 / var(--tw-bg-opacity, 1));\\n}\\n\\n@keyframes blink {\\n  from {\\n    opacity: 1;\\n  }\\n  to {\\n    opacity: 0;\\n  }\\n}\\n\\n.react-scan-arrow {\\n  position: absolute;\\n  top: 0px;\\n  left: 28px;\\n  display: flex;\\n  align-items: center;\\n  justify-content: center;\\n  cursor: pointer;\\n  height: 28px;\\n  width: 24px;\\n  --tw-translate-x: -100%;\\n  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n  z-index: 10;\\n\\n  > svg {\\n    transition-property: transform;\\n    transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n    transition-duration: 150ms;\\n  }\\n}\\n\\n.react-scan-nested {\\n  position: relative;\\n  overflow: hidden;\\n\\n  &:before {\\n    content: \\\"\\\";\\n  }\\n\\n  &:before {\\n    position: absolute;\\n  }\\n\\n  &:before {\\n    top: 0px;\\n  }\\n\\n  &:before {\\n    left: 0px;\\n  }\\n\\n  &:before {\\n    height: 100%;\\n  }\\n\\n  &:before {\\n    width: 1px;\\n  }\\n\\n  &:before {\\n    background-color: rgb(107 114 128 / 0.3);\\n  }\\n}\\n\\n.react-scan-settings {\\n  position: absolute;\\n  inset: 0px;\\n  display: flex;\\n  flex-direction: column;\\n  gap: 16px;\\n  padding-top: 8px;\\n  padding-bottom: 8px;\\n  padding-left: 16px;\\n  padding-right: 16px;\\n  --tw-text-opacity: 1;\\n  color: rgb(136 136 136 / var(--tw-text-opacity, 1));\\n\\n  > div {\\n    display: flex;\\n  }\\n\\n  > div {\\n    align-items: center;\\n  }\\n\\n  > div {\\n    justify-content: space-between;\\n  }\\n\\n  > div {\\n    transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;\\n    transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n    transition-duration: 150ms;\\n  }\\n\\n  > div {\\n    transition-duration: 300ms;\\n  }\\n}\\n\\n.react-scan-preview-line {\\n  position: relative;\\n  display: flex;\\n  min-height: 28px;\\n  align-items: center;\\n  -moz-column-gap: 8px;\\n       column-gap: 8px;\\n}\\n\\n.react-scan-flash-overlay {\\n  position: absolute;\\n  inset: 0px;\\n  opacity: 0;\\n  z-index: 50;\\n  pointer-events: none;\\n  transition-property: opacity;\\n  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n  transition-duration: 150ms;\\n  mix-blend-mode: multiply;\\n  background-color: rgb(168 85 247 / 0.9);\\n}\\n\\n.react-scan-toggle {\\n  position: relative;\\n  display: inline-flex;\\n  height: 24px;\\n  width: 40px;\\n\\n  input {\\n    position: absolute;\\n  }\\n\\n  input {\\n    inset: 0px;\\n  }\\n\\n  input {\\n    z-index: 20;\\n  }\\n\\n  input {\\n    opacity: 0;\\n  }\\n\\n  input {\\n    cursor: pointer;\\n  }\\n\\n  input {\\n    height: 100%;\\n  }\\n\\n  input {\\n    width: 100%;\\n  }\\n\\n  input:checked {\\n    + div {\\n      --tw-bg-opacity: 1;\\n      background-color: rgb(95 63 154 / var(--tw-bg-opacity, 1));\\n    }\\n    + div {\\n\\n      &::before {\\n        --tw-translate-x: 100%;\\n        transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n      }\\n\\n      &::before {\\n        left: auto;\\n      }\\n\\n      &::before {\\n        --tw-border-opacity: 1;\\n        border-color: rgb(95 63 154 / var(--tw-border-opacity, 1));\\n      }\\n    }\\n  }\\n\\n  > div {\\n    position: absolute;\\n  }\\n\\n  > div {\\n    inset: 4px;\\n  }\\n\\n  > div {\\n    --tw-bg-opacity: 1;\\n    background-color: rgb(64 64 64 / var(--tw-bg-opacity, 1));\\n  }\\n\\n  > div {\\n    border-radius: 9999px;\\n  }\\n\\n  > div {\\n    pointer-events: none;\\n  }\\n\\n  > div {\\n    transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;\\n    transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n    transition-duration: 150ms;\\n  }\\n\\n  > div {\\n    transition-duration: 300ms;\\n  }\\n\\n  > div {\\n\\n    &:before {\\n      --tw-content: '';\\n      content: var(--tw-content);\\n    }\\n\\n    &:before {\\n      position: absolute;\\n    }\\n\\n    &:before {\\n      top: 50%;\\n    }\\n\\n    &:before {\\n      left: 0px;\\n    }\\n\\n    &:before {\\n      --tw-translate-y: -50%;\\n      transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n    }\\n\\n    &:before {\\n      height: 16px;\\n    }\\n\\n    &:before {\\n      width: 16px;\\n    }\\n\\n    &:before {\\n      --tw-bg-opacity: 1;\\n      background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));\\n    }\\n\\n    &:before {\\n      border-width: 2px;\\n    }\\n\\n    &:before {\\n      --tw-border-opacity: 1;\\n      border-color: rgb(64 64 64 / var(--tw-border-opacity, 1));\\n    }\\n\\n    &:before {\\n      border-radius: 9999px;\\n    }\\n\\n    &:before {\\n      --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);\\n      --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);\\n      box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);\\n    }\\n\\n    &:before {\\n      transition-property: all;\\n      transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n      transition-duration: 150ms;\\n    }\\n\\n    &:before {\\n      transition-duration: 300ms;\\n    }\\n  }\\n}\\n\\n.react-scan-flash-active {\\n  opacity: 0.4;\\n  transition-property: opacity;\\n  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n  transition-duration: 300ms;\\n}\\n\\n.react-scan-inspector-overlay {\\n  display: flex;\\n  flex-direction: column;\\n  opacity: 0;\\n  transition-property: opacity;\\n  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n  transition-duration: 200ms;\\n  transition-timing-function: cubic-bezier(0, 0, 0.2, 1);\\n  will-change: opacity;\\n\\n  &.fade-out {\\n    opacity: 0;\\n  }\\n\\n  &.fade-in {\\n    opacity: 1;\\n  }\\n}\\n\\n.react-scan-what-changed {\\n  ul {\\n    list-style-type: disc;\\n  }\\n  ul {\\n    padding-left: 16px;\\n  }\\n\\n  li {\\n    white-space: nowrap;\\n  }\\n\\n  li {\\n    > div {\\n      display: flex;\\n    }\\n    > div {\\n      align-items: center;\\n    }\\n    > div {\\n      justify-content: space-between;\\n    }\\n    > div {\\n      -moz-column-gap: 8px;\\n           column-gap: 8px;\\n    }\\n  }\\n}\\n\\n.count-badge {\\n  display: flex;\\n  align-items: center;\\n  -moz-column-gap: 8px;\\n       column-gap: 8px;\\n  padding-left: 6px;\\n  padding-right: 6px;\\n  padding-top: 2px;\\n  padding-bottom: 2px;\\n  border-radius: 4px;\\n  font-size: 12px;\\n  line-height: 16px;\\n  font-weight: 500;\\n  --tw-numeric-spacing: tabular-nums;\\n  font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction);\\n  --tw-text-opacity: 1;\\n  color: rgb(168 85 247 / var(--tw-text-opacity, 1));\\n  background-color: rgb(168 85 247 / 0.1);\\n  transform-origin: center;\\n  transition-property: all;\\n  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n  transition-delay: 150ms;\\n  transition-duration: 300ms;\\n}\\n\\n@keyframes countFlash {\\n\\n  0% {\\n    background-color: rgba(168, 85, 247, 0.3);\\n    transform: scale(1.05);\\n  }\\n\\n  100% {\\n    background-color: rgba(168, 85, 247, 0.1);\\n    transform: scale(1);\\n  }\\n}\\n\\n.count-flash {\\n  animation: countFlash .3s ease-out forwards;\\n}\\n\\n@keyframes countFlashShake {\\n\\n  0% {\\n    transform: translateX(0);\\n  }\\n\\n  25% {\\n    transform: translateX(-5px);\\n  }\\n\\n  50% {\\n    transform: translateX(5px) scale(1.1);\\n  }\\n\\n  75% {\\n    transform: translateX(-5px);\\n  }\\n\\n  100% {\\n    transform: translateX(0);\\n  }\\n}\\n\\n.count-flash-white {\\n  animation: countFlashShake .3s ease-out forwards;\\n  transition-delay: 500ms !important;\\n}\\n\\n.change-scope {\\n  display: flex;\\n  align-items: center;\\n  -moz-column-gap: 4px;\\n       column-gap: 4px;\\n  --tw-text-opacity: 1;\\n  color: rgb(102 102 102 / var(--tw-text-opacity, 1));\\n  font-size: 12px;\\n  line-height: 16px;\\n  font-family: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace;\\n\\n  > div {\\n    padding-left: 6px;\\n    padding-right: 6px;\\n  }\\n\\n  > div {\\n    padding-top: 2px;\\n    padding-bottom: 2px;\\n  }\\n\\n  > div {\\n    border-radius: 4px;\\n  }\\n\\n  > div {\\n    font-size: 12px;\\n    line-height: 16px;\\n  }\\n\\n  > div {\\n    font-weight: 500;\\n  }\\n\\n  > div {\\n    --tw-numeric-spacing: tabular-nums;\\n    font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction);\\n  }\\n\\n  > div {\\n    transform-origin: center;\\n  }\\n\\n  > div {\\n    transition-property: all;\\n    transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n    transition-duration: 150ms;\\n  }\\n\\n  > div {\\n    transition-delay: 150ms;\\n  }\\n\\n  > div {\\n    transition-duration: 300ms;\\n  }\\n\\n  > div {\\n\\n    &[data-flash=\\\"true\\\"] {\\n      background-color: rgb(168 85 247 / 0.1);\\n    }\\n\\n    &[data-flash=\\\"true\\\"] {\\n      --tw-text-opacity: 1;\\n      color: rgb(168 85 247 / var(--tw-text-opacity, 1));\\n    }\\n  }\\n}\\n\\n.react-scan-slider {\\n  position: relative;\\n  min-height: 24px;\\n\\n  > input {\\n    position: absolute;\\n  }\\n\\n  > input {\\n    inset: 0px;\\n  }\\n\\n  > input {\\n    opacity: 0;\\n  }\\n\\n  &:before {\\n    --tw-content: '';\\n    content: var(--tw-content);\\n  }\\n\\n  &:before {\\n    position: absolute;\\n  }\\n\\n  &:before {\\n    left: 0px;\\n    right: 0px;\\n  }\\n\\n  &:before {\\n    top: 50%;\\n  }\\n\\n  &:before {\\n    --tw-translate-y: -50%;\\n    transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n  }\\n\\n  &:before {\\n    height: 6px;\\n  }\\n\\n  &:before {\\n    background-color: rgb(142 97 227 / 0.4);\\n  }\\n\\n  &:before {\\n    border-radius: 8px;\\n  }\\n\\n  &:before {\\n    pointer-events: none;\\n  }\\n\\n  &:after {\\n    --tw-content: '';\\n    content: var(--tw-content);\\n  }\\n\\n  &:after {\\n    position: absolute;\\n  }\\n\\n  &:after {\\n    left: 0px;\\n    right: 0px;\\n  }\\n\\n  &:after {\\n    top: -8px;\\n    bottom: -8px;\\n  }\\n\\n  &:after {\\n    z-index: -10;\\n  }\\n\\n  span {\\n    position: absolute;\\n  }\\n\\n  span {\\n    left: 0px;\\n  }\\n\\n  span {\\n    top: 50%;\\n  }\\n\\n  span {\\n    --tw-translate-y: -50%;\\n    transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n  }\\n\\n  span {\\n    height: 10px;\\n  }\\n\\n  span {\\n    width: 10px;\\n  }\\n\\n  span {\\n    border-radius: 8px;\\n  }\\n\\n  span {\\n    --tw-bg-opacity: 1;\\n    background-color: rgb(142 97 227 / var(--tw-bg-opacity, 1));\\n  }\\n\\n  span {\\n    pointer-events: none;\\n  }\\n\\n  span {\\n    transition-property: transform;\\n    transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n    transition-duration: 150ms;\\n  }\\n\\n  span {\\n    transition-duration: 75ms;\\n  }\\n}\\n\\n.resize-v-line {\\n  display: flex;\\n  align-items: center;\\n  justify-content: center;\\n  min-width: 4px;\\n  max-width: 4px;\\n  height: 100%;\\n  width: 100%;\\n  transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;\\n  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n  transition-duration: 150ms;\\n\\n  &:hover,\\n  &:active {\\n    > span {\\n      --tw-bg-opacity: 1;\\n      background-color: rgb(34 34 34 / var(--tw-bg-opacity, 1));\\n    }\\n\\n    svg {\\n      opacity: 1;\\n    }\\n  }\\n\\n  &::before {\\n    --tw-content: \\\"\\\";\\n    content: var(--tw-content);\\n  }\\n\\n  &::before {\\n    position: absolute;\\n  }\\n\\n  &::before {\\n    inset: 0px;\\n  }\\n\\n  &::before {\\n    left: 50%;\\n  }\\n\\n  &::before {\\n    --tw-translate-x: -50%;\\n    transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n  }\\n\\n  &::before {\\n    width: 1px;\\n  }\\n\\n  &::before {\\n    --tw-bg-opacity: 1;\\n    background-color: rgb(34 34 34 / var(--tw-bg-opacity, 1));\\n  }\\n\\n  &::before {\\n    transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;\\n    transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n    transition-duration: 150ms;\\n  }\\n\\n  > span {\\n    position: absolute;\\n  }\\n\\n  > span {\\n    left: 50%;\\n  }\\n\\n  > span {\\n    top: 50%;\\n  }\\n\\n  > span {\\n    --tw-translate-x: -50%;\\n    transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n  }\\n\\n  > span {\\n    --tw-translate-y: -50%;\\n    transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n  }\\n\\n  > span {\\n    height: 18px;\\n  }\\n\\n  > span {\\n    width: 6px;\\n  }\\n\\n  > span {\\n    border-radius: 4px;\\n  }\\n\\n  > span {\\n    transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;\\n    transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n    transition-duration: 150ms;\\n  }\\n\\n  svg {\\n    position: absolute;\\n  }\\n\\n  svg {\\n    left: 50%;\\n  }\\n\\n  svg {\\n    top: 50%;\\n  }\\n\\n  svg {\\n    --tw-translate-x: -50%;\\n    transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n  }\\n\\n  svg {\\n    --tw-translate-y: -50%;\\n    transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n  }\\n\\n  svg {\\n    --tw-rotate: 90deg;\\n    transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n  }\\n\\n  svg {\\n    --tw-text-opacity: 1;\\n    color: rgb(163 163 163 / var(--tw-text-opacity, 1));\\n  }\\n\\n  svg {\\n    opacity: 0;\\n  }\\n\\n  svg {\\n    transition-property: opacity;\\n    transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n    transition-duration: 150ms;\\n  }\\n\\n  svg {\\n    z-index: 50;\\n  }\\n}\\n\\n.tree-node-search-highlight {\\n  overflow: hidden;\\n  text-overflow: ellipsis;\\n  white-space: nowrap;\\n\\n  span {\\n    padding-top: 1px;\\n    padding-bottom: 1px;\\n  }\\n\\n  span {\\n    border-radius: 2px;\\n  }\\n\\n  span {\\n    --tw-bg-opacity: 1;\\n    background-color: rgb(253 224 71 / var(--tw-bg-opacity, 1));\\n  }\\n\\n  span {\\n    font-weight: 500;\\n  }\\n\\n  span {\\n    --tw-text-opacity: 1;\\n    color: rgb(0 0 0 / var(--tw-text-opacity, 1));\\n  }\\n\\n  .single {\\n    margin-right: 1px;\\n  }\\n\\n  .single {\\n    padding-left: 2px;\\n    padding-right: 2px;\\n  }\\n\\n  .regex {\\n    padding-left: 2px;\\n    padding-right: 2px;\\n  }\\n\\n  .start {\\n    margin-left: 1px;\\n  }\\n\\n  .start {\\n    border-top-left-radius: 2px;\\n    border-bottom-left-radius: 2px;\\n  }\\n\\n  .end {\\n    margin-right: 1px;\\n  }\\n\\n  .end {\\n    border-top-right-radius: 2px;\\n    border-bottom-right-radius: 2px;\\n  }\\n\\n  .middle {\\n    margin-left: 1px;\\n    margin-right: 1px;\\n  }\\n\\n  .middle {\\n    border-radius: 2px;\\n  }\\n}\\n\\n.react-scan-toolbar-notification {\\n  position: absolute;\\n  left: 0px;\\n  right: 0px;\\n  display: flex;\\n  align-items: center;\\n  -moz-column-gap: 8px;\\n       column-gap: 8px;\\n  padding: 4px;\\n  padding-left: 8px;\\n  font-size: 10px;\\n  --tw-text-opacity: 1;\\n  color: rgb(212 212 212 / var(--tw-text-opacity, 1));\\n  background-color: rgb(0 0 0 / 0.9);\\n  transition-property: transform;\\n  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n  transition-duration: 150ms;\\n\\n  &:before {\\n    --tw-content: '';\\n    content: var(--tw-content);\\n  }\\n\\n  &:before {\\n    position: absolute;\\n  }\\n\\n  &:before {\\n    left: 0px;\\n    right: 0px;\\n  }\\n\\n  &:before {\\n    --tw-bg-opacity: 1;\\n    background-color: rgb(0 0 0 / var(--tw-bg-opacity, 1));\\n  }\\n\\n  &:before {\\n    height: 8px;\\n  }\\n\\n  &.position-top {\\n    top: 100%;\\n  }\\n\\n  &.position-top {\\n    --tw-translate-y: -100%;\\n    transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n  }\\n\\n  &.position-top {\\n    border-bottom-right-radius: 8px;\\n    border-bottom-left-radius: 8px;\\n  }\\n\\n  &.position-top {\\n\\n    &::before {\\n      top: 0px;\\n    }\\n\\n    &::before {\\n      --tw-translate-y: -100%;\\n      transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n    }\\n  }\\n\\n  &.position-bottom {\\n    bottom: 100%;\\n  }\\n\\n  &.position-bottom {\\n    --tw-translate-y: 100%;\\n    transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n  }\\n\\n  &.position-bottom {\\n    border-top-left-radius: 8px;\\n    border-top-right-radius: 8px;\\n  }\\n\\n  &.position-bottom {\\n\\n    &::before {\\n      bottom: 0px;\\n    }\\n\\n    &::before {\\n      --tw-translate-y: 100%;\\n      transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n    }\\n  }\\n\\n  &.is-open {\\n    --tw-translate-y: 0px;\\n    transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n  }\\n}\\n\\n.react-scan-header-item {\\n  position: absolute;\\n  inset: 0px;\\n  --tw-translate-y: -200%;\\n  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n  transition-property: transform;\\n  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n  transition-duration: 300ms;\\n\\n  &.is-visible {\\n    --tw-translate-y: 0px;\\n    transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n  }\\n}\\n\\n.react-scan-components-tree:has(.resize-v-line:hover, .resize-v-line:active)\\n  .tree {\\n  overflow: hidden;\\n}\\n\\n.react-scan-expandable {\\n  display: grid;\\n  grid-template-rows: 0fr;\\n  overflow: hidden;\\n  transition-property: all;\\n  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n  transition-duration: 75ms;\\n  transition-timing-function: ease-out;\\n\\n  > * {\\n    min-height: 0;\\n  }\\n\\n  &.react-scan-expanded {\\n    grid-template-rows: 1fr;\\n    transition-duration: 100ms;\\n  }\\n}\\n\\n.after\\\\:absolute::after {\\n  content: var(--tw-content);\\n  position: absolute;\\n}\\n\\n.after\\\\:inset-0::after {\\n  content: var(--tw-content);\\n  inset: 0px;\\n}\\n\\n.after\\\\:left-1\\\\/2::after {\\n  content: var(--tw-content);\\n  left: 50%;\\n}\\n\\n.after\\\\:top-\\\\[100\\\\%\\\\]::after {\\n  content: var(--tw-content);\\n  top: 100%;\\n}\\n\\n.after\\\\:h-\\\\[6px\\\\]::after {\\n  content: var(--tw-content);\\n  height: 6px;\\n}\\n\\n.after\\\\:w-\\\\[10px\\\\]::after {\\n  content: var(--tw-content);\\n  width: 10px;\\n}\\n\\n.after\\\\:-translate-x-1\\\\/2::after {\\n  content: var(--tw-content);\\n  --tw-translate-x: -50%;\\n  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\\n}\\n\\n@keyframes fadeOut {\\n\\n  0% {\\n    content: var(--tw-content);\\n    opacity: 1;\\n  }\\n\\n  100% {\\n    content: var(--tw-content);\\n    opacity: 0;\\n  }\\n}\\n\\n.after\\\\:animate-\\\\[fadeOut_1s_ease-out_forwards\\\\]::after {\\n  content: var(--tw-content);\\n  animation: fadeOut 1s ease-out forwards;\\n}\\n\\n.after\\\\:border-l-\\\\[5px\\\\]::after {\\n  content: var(--tw-content);\\n  border-left-width: 5px;\\n}\\n\\n.after\\\\:border-r-\\\\[5px\\\\]::after {\\n  content: var(--tw-content);\\n  border-right-width: 5px;\\n}\\n\\n.after\\\\:border-t-\\\\[6px\\\\]::after {\\n  content: var(--tw-content);\\n  border-top-width: 6px;\\n}\\n\\n.after\\\\:border-l-transparent::after {\\n  content: var(--tw-content);\\n  border-left-color: transparent;\\n}\\n\\n.after\\\\:border-r-transparent::after {\\n  content: var(--tw-content);\\n  border-right-color: transparent;\\n}\\n\\n.after\\\\:border-t-white::after {\\n  content: var(--tw-content);\\n  --tw-border-opacity: 1;\\n  border-top-color: rgb(255 255 255 / var(--tw-border-opacity, 1));\\n}\\n\\n.after\\\\:bg-purple-500\\\\/30::after {\\n  content: var(--tw-content);\\n  background-color: rgb(168 85 247 / 0.3);\\n}\\n\\n.after\\\\:content-\\\\[\\\\\\\"\\\\\\\"\\\\]::after {\\n  --tw-content: \\\"\\\";\\n  content: var(--tw-content);\\n}\\n\\n.focus-within\\\\:border-\\\\[\\\\#454545\\\\]:focus-within {\\n  --tw-border-opacity: 1;\\n  border-color: rgb(69 69 69 / var(--tw-border-opacity, 1));\\n}\\n\\n.hover\\\\:bg-\\\\[\\\\#0f0f0f\\\\]:hover {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(15 15 15 / var(--tw-bg-opacity, 1));\\n}\\n\\n.hover\\\\:bg-\\\\[\\\\#18181B\\\\]:hover {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(24 24 27 / var(--tw-bg-opacity, 1));\\n}\\n\\n.hover\\\\:bg-\\\\[\\\\#34343b\\\\]:hover {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(52 52 59 / var(--tw-bg-opacity, 1));\\n}\\n\\n.hover\\\\:bg-\\\\[\\\\#5f3f9a\\\\]\\\\/20:hover {\\n  background-color: rgb(95 63 154 / 0.2);\\n}\\n\\n.hover\\\\:bg-\\\\[\\\\#5f3f9a\\\\]\\\\/40:hover {\\n  background-color: rgb(95 63 154 / 0.4);\\n}\\n\\n.hover\\\\:bg-red-600:hover {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(220 38 38 / var(--tw-bg-opacity, 1));\\n}\\n\\n.hover\\\\:bg-zinc-700:hover {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(63 63 70 / var(--tw-bg-opacity, 1));\\n}\\n\\n.hover\\\\:bg-zinc-800\\\\/50:hover {\\n  background-color: rgb(39 39 42 / 0.5);\\n}\\n\\n.hover\\\\:text-neutral-300:hover {\\n  --tw-text-opacity: 1;\\n  color: rgb(212 212 212 / var(--tw-text-opacity, 1));\\n}\\n\\n.hover\\\\:text-white:hover {\\n  --tw-text-opacity: 1;\\n  color: rgb(255 255 255 / var(--tw-text-opacity, 1));\\n}\\n\\n.group:hover .group-hover\\\\:bg-\\\\[\\\\#21437982\\\\] {\\n  background-color: #21437982;\\n}\\n\\n.group:hover .group-hover\\\\:bg-\\\\[\\\\#5b2d89\\\\] {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(91 45 137 / var(--tw-bg-opacity, 1));\\n}\\n\\n.group:hover .group-hover\\\\:bg-\\\\[\\\\#6a6a6a\\\\] {\\n  --tw-bg-opacity: 1;\\n  background-color: rgb(106 106 106 / var(--tw-bg-opacity, 1));\\n}\\n\\n.group:hover .group-hover\\\\:bg-\\\\[\\\\#efda1a2f\\\\] {\\n  background-color: #efda1a2f;\\n}\\n\\n.group:hover .group-hover\\\\:opacity-100 {\\n  opacity: 1;\\n}\\n\\n.peer\\\\/bottom:hover ~ .peer-hover\\\\/bottom\\\\:rounded-b-none {\\n  border-bottom-right-radius: 0px;\\n  border-bottom-left-radius: 0px;\\n}\\n\\n.peer\\\\/left:hover ~ .peer-hover\\\\/left\\\\:rounded-l-none {\\n  border-top-left-radius: 0px;\\n  border-bottom-left-radius: 0px;\\n}\\n\\n.peer\\\\/right:hover ~ .peer-hover\\\\/right\\\\:rounded-r-none {\\n  border-top-right-radius: 0px;\\n  border-bottom-right-radius: 0px;\\n}\\n\\n.peer\\\\/top:hover ~ .peer-hover\\\\/top\\\\:rounded-t-none {\\n  border-top-left-radius: 0px;\\n  border-top-right-radius: 0px;\\n}\\n\";\n\n  // src/web/hooks/use-delayed-value.ts\n  var useDelayedValue = (value, onDelay, offDelay = onDelay) => {\n    const [delayedValue, setDelayedValue] = d2(value);\n    y2(() => {\n      if (value === delayedValue) return;\n      const delay = value ? onDelay : offDelay;\n      const timeout2 = setTimeout(() => setDelayedValue(value), delay);\n      return () => clearTimeout(timeout2);\n    }, [value, onDelay, offDelay]);\n    return delayedValue;\n  };\n\n  // src/web/views/inspector/header.tsx\n  var headerInspectClassName = w3(\n    () => cn2(\n      \"absolute inset-0 flex items-center gap-x-2\",\n      \"translate-y-0\",\n      \"transition-transform duration-300\",\n      signalIsSettingsOpen.value && \"-translate-y-[200%]\"\n    )\n  );\n  var HeaderInspect = () => {\n    const refReRenders = A2(null);\n    const refTiming = A2(null);\n    const [currentFiber, setCurrentFiber] = d2(null);\n    useSignalEffect(() => {\n      const state = Store.inspectState.value;\n      if (state.kind === \"focused\") {\n        setCurrentFiber(state.fiber);\n      }\n    });\n    useSignalEffect(() => {\n      const state = timelineState.value;\n      n2(() => {\n        if (Store.inspectState.value.kind !== \"focused\") return;\n        if (!refReRenders.current || !refTiming.current) return;\n        const { totalUpdates, currentIndex, updates, isVisible, windowOffset } = state;\n        const reRenders = Math.max(0, totalUpdates - 1);\n        const headerText = isVisible ? `#${windowOffset + currentIndex} Re-render` : reRenders > 0 ? `\\xD7${reRenders}` : \"\";\n        let formattedTime;\n        if (reRenders > 0 && currentIndex >= 0 && currentIndex < updates.length) {\n          const time = updates[currentIndex]?.fiberInfo?.selfTime;\n          formattedTime = time > 0 ? time < 0.1 - Number.EPSILON ? \"< 0.1ms\" : `${Number(time.toFixed(1))}ms` : void 0;\n        }\n        refReRenders.current.dataset.text = headerText ? ` \\u2022 ${headerText}` : \"\";\n        refTiming.current.dataset.text = formattedTime ? ` \\u2022 ${formattedTime}` : \"\";\n      });\n    });\n    const componentName = T2(() => {\n      if (!currentFiber) return null;\n      const { name, wrappers, wrapperTypes } = getExtendedDisplayName(currentFiber);\n      const title = wrappers.length ? `${wrappers.join(\"(\")}(${name})${\")\".repeat(wrappers.length)}` : name ?? \"\";\n      const firstWrapperType = wrapperTypes[0];\n      return /* @__PURE__ */ u4(\"span\", { title, className: \"flex items-center gap-x-1\", children: [\n        name ?? \"Unknown\",\n        /* @__PURE__ */ u4(\n          \"span\",\n          {\n            title: firstWrapperType?.title,\n            className: \"flex items-center gap-x-1 text-[10px] text-purple-400\",\n            children: !!firstWrapperType && /* @__PURE__ */ u4(k, { children: [\n              /* @__PURE__ */ u4(\n                \"span\",\n                {\n                  className: cn2(\n                    \"rounded py-[1px] px-1\",\n                    \"truncate\",\n                    firstWrapperType.compiler && \"bg-purple-800 text-neutral-400\",\n                    !firstWrapperType.compiler && \"bg-neutral-700 text-neutral-300\",\n                    firstWrapperType.type === \"memo\" && \"bg-[#5f3f9a] text-white\"\n                  ),\n                  children: firstWrapperType.type\n                },\n                firstWrapperType.type\n              ),\n              firstWrapperType.compiler && /* @__PURE__ */ u4(\"span\", { className: \"text-yellow-300\", children: \"\\u2728\" })\n            ] })\n          }\n        ),\n        wrapperTypes.length > 1 && /* @__PURE__ */ u4(\"span\", { className: \"text-[10px] text-neutral-400\", children: [\n          \"\\xD7\",\n          wrapperTypes.length - 1\n        ] })\n      ] });\n    }, [currentFiber]);\n    return /* @__PURE__ */ u4(\"div\", { className: headerInspectClassName, children: [\n      componentName,\n      /* @__PURE__ */ u4(\"div\", { className: \"flex items-center gap-x-2 mr-auto text-xs text-[#888]\", children: [\n        /* @__PURE__ */ u4(\n          \"span\",\n          {\n            ref: refReRenders,\n            className: \"with-data-text cursor-pointer !overflow-visible\",\n            title: \"Click to toggle between rerenders and total renders\"\n          }\n        ),\n        /* @__PURE__ */ u4(\"span\", { ref: refTiming, className: \"with-data-text !overflow-visible\" })\n      ] })\n    ] });\n  };\n\n  // src/web/widget/header.tsx\n  var Header = () => {\n    const isInitialView = useDelayedValue(\n      Store.inspectState.value.kind === \"focused\",\n      150,\n      0\n    );\n    const handleClose = () => {\n      signalWidgetViews.value = {\n        view: \"none\"\n      };\n      Store.inspectState.value = {\n        kind: \"inspect-off\"\n      };\n    };\n    const isHeaderIsNotifications = signalWidgetViews.value.view === \"notifications\";\n    if (isHeaderIsNotifications) {\n      return;\n    }\n    return /* @__PURE__ */ u4(\"div\", { className: \"react-scan-header\", children: [\n      /* @__PURE__ */ u4(\"div\", { className: \"relative flex-1 h-full\", children: /* @__PURE__ */ u4(\n        \"div\",\n        {\n          className: cn2(\n            \"react-scan-header-item is-visible\",\n            !isInitialView && \"!duration-0\"\n          ),\n          children: /* @__PURE__ */ u4(HeaderInspect, {})\n        }\n      ) }),\n      /* @__PURE__ */ u4(\n        \"button\",\n        {\n          type: \"button\",\n          title: \"Close\",\n          className: \"react-scan-close-button\",\n          onClick: handleClose,\n          children: /* @__PURE__ */ u4(Icon, { name: \"icon-close\" })\n        }\n      )\n    ] });\n  };\n\n  // src/web/components/toggle/index.tsx\n  var Toggle = ({\n    className,\n    ...props\n  }) => {\n    return /* @__PURE__ */ u4(\"div\", { className: cn2(\"react-scan-toggle\", className), children: [\n      /* @__PURE__ */ u4(\n        \"input\",\n        {\n          type: \"checkbox\",\n          ...props\n        }\n      ),\n      /* @__PURE__ */ u4(\"div\", {})\n    ] });\n  };\n\n  // src/web/widget/fps-meter.tsx\n  var FpsMeterInner = ({ fps: fps2 }) => {\n    const getColor = (fps3) => {\n      if (fps3 < 30) return \"#EF4444\";\n      if (fps3 < 50) return \"#F59E0B\";\n      return \"rgb(214,132,245)\";\n    };\n    return /* @__PURE__ */ u4(\n      \"div\",\n      {\n        className: cn2(\n          \"flex items-center gap-x-1 px-2 w-full\",\n          \"h-6\",\n          \"rounded-md\",\n          \"font-mono leading-none\",\n          \"bg-[#141414]\",\n          \"ring-1 ring-white/[0.08]\"\n        ),\n        children: [\n          /* @__PURE__ */ u4(\n            \"div\",\n            {\n              style: { color: getColor(fps2) },\n              className: \"text-sm font-semibold tracking-wide transition-colors ease-in-out w-full flex justify-center items-center\",\n              children: fps2\n            }\n          ),\n          /* @__PURE__ */ u4(\"span\", { className: \"text-white/30 text-[11px] font-medium tracking-wide ml-auto min-w-fit\", children: \"FPS\" })\n        ]\n      }\n    );\n  };\n  var FPSMeter = () => {\n    const [fps2, setFps] = d2(null);\n    y2(() => {\n      const intervalId = setInterval(() => {\n        setFps(getFPS());\n      }, 200);\n      return () => clearInterval(intervalId);\n    }, []);\n    return /* @__PURE__ */ u4(\n      \"div\",\n      {\n        className: cn2(\n          \"flex items-center justify-end gap-x-2 px-1 ml-1 w-[72px]\",\n          \"whitespace-nowrap text-sm text-white\"\n        ),\n        children: fps2 === null ? /* @__PURE__ */ u4(k, { children: \"\\uFE0F\" }) : /* @__PURE__ */ u4(FpsMeterInner, { fps: fps2 })\n      }\n    );\n  };\n  var iife = (fn2) => fn2();\n  var BoundedArray = class _BoundedArray extends Array {\n    constructor(capacity = 25) {\n      super();\n      this.capacity = capacity;\n    }\n    push(...items) {\n      const result = super.push(...items);\n      while (this.length > this.capacity) {\n        this.shift();\n      }\n      return result;\n    }\n    // do not couple capacity with a default param, it must be explicit\n    static fromArray(array, capacity) {\n      const arr = new _BoundedArray(capacity);\n      arr.push(...array);\n      return arr;\n    }\n  };\n\n  // src/core/notifications/interaction-store.ts\n  var Store2 = class {\n    constructor(initialValue) {\n      this.subscribers = /* @__PURE__ */ new Set();\n      this.currentValue = initialValue;\n    }\n    subscribe(subscriber) {\n      this.subscribers.add(subscriber);\n      subscriber(this.currentValue);\n      return () => {\n        this.subscribers.delete(subscriber);\n      };\n    }\n    setState(data) {\n      this.currentValue = data;\n      this.subscribers.forEach((subscriber) => subscriber(data));\n    }\n    getCurrentState() {\n      return this.currentValue;\n    }\n  };\n  var MAX_INTERACTION_BATCH = 150;\n  var interactionStore = new Store2(\n    new BoundedArray(MAX_INTERACTION_BATCH)\n  );\n\n  // src/core/notifications/performance-store.ts\n  var MAX_CHANNEL_SIZE = 50;\n  var PerformanceEntryChannels = class {\n    constructor() {\n      this.channels = {};\n    }\n    publish(item, to, createIfNoChannel = true) {\n      const existingChannel = this.channels[to];\n      if (!existingChannel) {\n        if (!createIfNoChannel) {\n          return;\n        }\n        this.channels[to] = {\n          callbacks: new BoundedArray(MAX_CHANNEL_SIZE),\n          state: new BoundedArray(MAX_CHANNEL_SIZE)\n        };\n        this.channels[to].state.push(item);\n        return;\n      }\n      existingChannel.state.push(item);\n      existingChannel.callbacks.forEach((cb) => cb(item));\n    }\n    getAvailableChannels() {\n      return BoundedArray.fromArray(Object.keys(this.channels), MAX_CHANNEL_SIZE);\n    }\n    subscribe(to, cb, dropFirst = false) {\n      const defer = () => {\n        if (!dropFirst) {\n          this.channels[to].state.forEach((item) => {\n            cb(item);\n          });\n        }\n        return () => {\n          const filtered = this.channels[to].callbacks.filter(\n            (subscribed) => subscribed !== cb\n          );\n          this.channels[to].callbacks = BoundedArray.fromArray(\n            filtered,\n            MAX_CHANNEL_SIZE\n          );\n        };\n      };\n      const existing = this.channels[to];\n      if (!existing) {\n        this.channels[to] = {\n          callbacks: new BoundedArray(MAX_CHANNEL_SIZE),\n          state: new BoundedArray(MAX_CHANNEL_SIZE)\n        };\n        this.channels[to].callbacks.push(cb);\n        return defer();\n      }\n      existing.callbacks.push(cb);\n      return defer();\n    }\n    updateChannelState(channel, updater, createIfNoChannel = true) {\n      const existingChannel = this.channels[channel];\n      if (!existingChannel) {\n        if (!createIfNoChannel) {\n          return;\n        }\n        const state = new BoundedArray(MAX_CHANNEL_SIZE);\n        const newChannel = {\n          callbacks: new BoundedArray(MAX_CHANNEL_SIZE),\n          state\n        };\n        this.channels[channel] = newChannel;\n        newChannel.state = updater(state);\n        return;\n      }\n      existingChannel.state = updater(existingChannel.state);\n    }\n    getChannelState(channel) {\n      return this.channels[channel].state ?? new BoundedArray(MAX_CHANNEL_SIZE);\n    }\n  };\n  var performanceEntryChannels = new PerformanceEntryChannels();\n\n  // src/core/notifications/performance.ts\n  var DEFAULT_PATH_FILTERS = {\n    skipProviders: true,\n    skipHocs: true,\n    skipContainers: true,\n    skipMinified: true,\n    skipUtilities: true,\n    skipBoundaries: true\n  };\n  var PATH_FILTER_PATTERNS = {\n    providers: [/Provider$/, /^Provider$/, /^Context$/],\n    hocs: [/^with[A-Z]/, /^forward(?:Ref)?$/i, /^Forward(?:Ref)?\\(/],\n    containers: [/^(?:App)?Container$/, /^Root$/, /^ReactDev/],\n    utilities: [\n      /^Fragment$/,\n      /^Suspense$/,\n      /^ErrorBoundary$/,\n      /^Portal$/,\n      /^Consumer$/,\n      /^Layout$/,\n      /^Router/,\n      /^Hydration/\n    ],\n    boundaries: [/^Boundary$/, /Boundary$/, /^Provider$/, /Provider$/]\n  };\n  var shouldIncludeInPath = (name, filters = DEFAULT_PATH_FILTERS) => {\n    const patternsToCheck = [];\n    if (filters.skipProviders) patternsToCheck.push(...PATH_FILTER_PATTERNS.providers);\n    if (filters.skipHocs) patternsToCheck.push(...PATH_FILTER_PATTERNS.hocs);\n    if (filters.skipContainers) patternsToCheck.push(...PATH_FILTER_PATTERNS.containers);\n    if (filters.skipUtilities) patternsToCheck.push(...PATH_FILTER_PATTERNS.utilities);\n    if (filters.skipBoundaries) patternsToCheck.push(...PATH_FILTER_PATTERNS.boundaries);\n    return !patternsToCheck.some((pattern) => pattern.test(name));\n  };\n  var minifiedPatterns = [\n    /^[a-z]$/,\n    /^[a-z][0-9]$/,\n    /^_+$/,\n    /^[A-Za-z][_$]$/,\n    /^[a-z]{1,2}$/\n  ];\n  var isMinified = (name) => {\n    for (let i5 = 0; i5 < minifiedPatterns.length; i5++) {\n      if (minifiedPatterns[i5].test(name)) return true;\n    }\n    const hasNoVowels = !/[aeiou]/i.test(name);\n    const hasMostlyNumbers = (name.match(/\\d/g)?.length ?? 0) > name.length / 2;\n    const isSingleWordLowerCase = /^[a-z]+$/.test(name);\n    const hasRandomLookingChars = /[$_]{2,}/.test(name);\n    return Number(hasNoVowels) + Number(hasMostlyNumbers) + Number(isSingleWordLowerCase) + Number(hasRandomLookingChars) >= 2;\n  };\n  var getCleanComponentName = (component) => {\n    const name = getDisplayName(component);\n    if (!name) return \"\";\n    return name.replace(\n      /^(?:Memo|Forward(?:Ref)?|With.*?)\\((?<inner>.*?)\\)$/,\n      \"$<inner>\"\n    );\n  };\n  var getInteractionPath = (initialFiber, filters = DEFAULT_PATH_FILTERS) => {\n    if (!initialFiber) return [];\n    const currentName = getDisplayName(initialFiber.type);\n    if (!currentName) return [];\n    const stack = new Array();\n    let fiber = initialFiber;\n    while (fiber.return) {\n      const name = getCleanComponentName(fiber.type);\n      if (name && !isMinified(name) && shouldIncludeInPath(name, filters) && name.toLowerCase() !== name) {\n        stack.push(name);\n      }\n      fiber = fiber.return;\n    }\n    const fullPath = new Array(stack.length);\n    for (let i5 = 0; i5 < stack.length; i5++) {\n      fullPath[i5] = stack[stack.length - i5 - 1];\n    }\n    return fullPath;\n  };\n  var getFirstNameFromAncestor = (fiber, accept = () => true) => {\n    let curr = fiber;\n    while (curr) {\n      const currName = getDisplayName(curr.type);\n      if (currName && accept(currName)) {\n        return currName;\n      }\n      curr = curr.return;\n    }\n    return null;\n  };\n  var unsubscribeTrackVisibilityChange;\n  var lastVisibilityHiddenAt = \"never-hidden\";\n  var trackVisibilityChange = () => {\n    unsubscribeTrackVisibilityChange?.();\n    const onVisibilityChange = () => {\n      if (document.hidden) {\n        lastVisibilityHiddenAt = Date.now();\n      }\n    };\n    document.addEventListener(\"visibilitychange\", onVisibilityChange);\n    unsubscribeTrackVisibilityChange = () => {\n      document.removeEventListener(\"visibilitychange\", onVisibilityChange);\n    };\n  };\n  var getInteractionType = (eventName) => {\n    if ([\"pointerup\", \"click\"].includes(eventName)) {\n      return \"pointer\";\n    }\n    if (eventName.includes(\"key\")) ;\n    if ([\"keydown\", \"keyup\"].includes(eventName)) {\n      return \"keyboard\";\n    }\n    return null;\n  };\n  var onEntryAnimationId = null;\n  var setupPerformanceListener = (onEntry) => {\n    trackVisibilityChange();\n    const interactionMap = /* @__PURE__ */ new Map();\n    const interactionTargetMap = /* @__PURE__ */ new Map();\n    const processInteractionEntry = (entry) => {\n      if (!entry.interactionId) return;\n      if (entry.interactionId && entry.target && !interactionTargetMap.has(entry.interactionId)) {\n        interactionTargetMap.set(entry.interactionId, entry.target);\n      }\n      if (entry.target) {\n        let current = entry.target;\n        while (current) {\n          if (current.id === \"react-scan-toolbar-root\" || current.id === \"react-scan-root\") {\n            return;\n          }\n          current = current.parentElement;\n        }\n      }\n      const existingInteraction = interactionMap.get(entry.interactionId);\n      if (existingInteraction) {\n        if (entry.duration > existingInteraction.latency) {\n          existingInteraction.entries = [entry];\n          existingInteraction.latency = entry.duration;\n        } else if (entry.duration === existingInteraction.latency && entry.startTime === existingInteraction.entries[0].startTime) {\n          existingInteraction.entries.push(entry);\n        }\n      } else {\n        const interactionType = getInteractionType(entry.name);\n        if (!interactionType) {\n          return;\n        }\n        const interaction = {\n          id: entry.interactionId,\n          latency: entry.duration,\n          entries: [entry],\n          target: entry.target,\n          type: interactionType,\n          startTime: entry.startTime,\n          endTime: Date.now(),\n          processingStart: entry.processingStart,\n          processingEnd: entry.processingEnd,\n          duration: entry.duration,\n          inputDelay: entry.processingStart - entry.startTime,\n          processingDuration: entry.processingEnd - entry.processingStart,\n          presentationDelay: entry.duration - (entry.processingEnd - entry.startTime),\n          // componentPath:\n          timestamp: Date.now(),\n          timeSinceTabInactive: lastVisibilityHiddenAt === \"never-hidden\" ? \"never-hidden\" : Date.now() - lastVisibilityHiddenAt,\n          visibilityState: document.visibilityState,\n          timeOrigin: performance.timeOrigin,\n          referrer: document.referrer\n        };\n        interactionMap.set(interaction.id, interaction);\n        if (!onEntryAnimationId) {\n          onEntryAnimationId = requestAnimationFrame(() => {\n            requestAnimationFrame(() => {\n              onEntry(interactionMap.get(interaction.id));\n              onEntryAnimationId = null;\n            });\n          });\n        }\n      }\n    };\n    const po = new PerformanceObserver((list) => {\n      const entries = list.getEntries();\n      for (let i5 = 0, len = entries.length; i5 < len; i5++) {\n        const entry = entries[i5];\n        processInteractionEntry(entry);\n      }\n    });\n    try {\n      po.observe({\n        type: \"event\",\n        buffered: true,\n        durationThreshold: 16\n      });\n      po.observe({\n        type: \"first-input\",\n        buffered: true\n      });\n    } catch {\n    }\n    return () => po.disconnect();\n  };\n  var setupPerformancePublisher = () => {\n    return setupPerformanceListener((entry) => {\n      performanceEntryChannels.publish(\n        {\n          kind: \"entry-received\",\n          entry\n        },\n        \"recording\"\n      );\n    });\n  };\n  var MAX_INTERACTION_TASKS = 25;\n  var tasks = new BoundedArray(MAX_INTERACTION_TASKS);\n  var getAssociatedDetailedTimingInteraction = (entry, activeTasks) => {\n    let closestTask = null;\n    for (const task of activeTasks) {\n      if (task.type !== entry.type) {\n        continue;\n      }\n      if (closestTask === null) {\n        closestTask = task;\n        continue;\n      }\n      const getAbsoluteDiff = (task2, entry2) => Math.abs(task2.startDateTime) - (entry2.startTime + entry2.timeOrigin);\n      if (getAbsoluteDiff(task, entry) < getAbsoluteDiff(closestTask, entry)) {\n        closestTask = task;\n      }\n    }\n    return closestTask;\n  };\n  var listenForPerformanceEntryInteractions = (onComplete) => {\n    const unsubscribe = performanceEntryChannels.subscribe(\n      \"recording\",\n      (event) => {\n        const associatedDetailedInteraction = event.kind === \"auto-complete-race\" ? tasks.find((task) => task.interactionUUID === event.interactionUUID) : getAssociatedDetailedTimingInteraction(event.entry, tasks);\n        if (!associatedDetailedInteraction) {\n          return;\n        }\n        const completedInteraction = associatedDetailedInteraction.completeInteraction(event);\n        onComplete(completedInteraction);\n      }\n    );\n    return unsubscribe;\n  };\n  var trackDetailedTiming = ({\n    onMicroTask,\n    onRAF,\n    onTimeout,\n    abort\n  }) => {\n    queueMicrotask(() => {\n      if (abort?.() === true) {\n        return;\n      }\n      if (!onMicroTask()) {\n        return;\n      }\n      requestAnimationFrame(() => {\n        if (abort?.() === true) {\n          return;\n        }\n        if (!onRAF()) {\n          return;\n        }\n        setTimeout(() => {\n          if (abort?.() === true) {\n            return;\n          }\n          onTimeout();\n        }, 0);\n      });\n    });\n  };\n  var getTargetInteractionDetails = (target) => {\n    const associatedFiber = getFiberFromElement(target);\n    if (!associatedFiber) {\n      return;\n    }\n    let componentName = associatedFiber ? getDisplayName(associatedFiber?.type) : \"N/A\";\n    if (!componentName) {\n      componentName = getFirstNameFromAncestor(associatedFiber, (name) => name.length > 2) ?? \"N/A\";\n    }\n    if (!componentName) {\n      return;\n    }\n    const componentPath = getInteractionPath(associatedFiber);\n    return {\n      componentPath,\n      childrenTree: {},\n      componentName,\n      elementFiber: associatedFiber\n    };\n  };\n  var setupDetailedPointerTimingListener = (kind, options) => {\n    let instrumentationIdInControl = null;\n    const getEvent = (info) => {\n      switch (kind) {\n        case \"pointer\": {\n          if (info.phase === \"start\") {\n            return \"pointerup\";\n          }\n          if (info.target instanceof HTMLInputElement || info.target instanceof HTMLSelectElement) {\n            return \"change\";\n          }\n          return \"click\";\n        }\n        case \"keyboard\": {\n          if (info.phase === \"start\") {\n            return \"keydown\";\n          }\n          return \"change\";\n        }\n      }\n    };\n    const lastInteractionRef = {\n      current: {\n        kind: \"uninitialized-stage\",\n        interactionUUID: not_globally_unique_generateId(),\n        // the first interaction uses this\n        stageStart: Date.now(),\n        interactionType: kind\n      }\n    };\n    const onInteractionStart = (e4) => {\n      const path = e4.composedPath();\n      if (path.some(\n        (el) => el instanceof Element && el.id === \"react-scan-toolbar-root\"\n      )) {\n        return;\n      }\n      if (Date.now() - lastInteractionRef.current.stageStart > 2e3) {\n        lastInteractionRef.current = {\n          kind: \"uninitialized-stage\",\n          interactionUUID: not_globally_unique_generateId(),\n          stageStart: Date.now(),\n          interactionType: kind\n        };\n      }\n      if (lastInteractionRef.current.kind !== \"uninitialized-stage\") {\n        return;\n      }\n      const pointerUpStart = performance.now();\n      options?.onStart?.(lastInteractionRef.current.interactionUUID);\n      const details = getTargetInteractionDetails(e4.target);\n      if (!details) {\n        options?.onError?.(lastInteractionRef.current.interactionUUID);\n        return;\n      }\n      const fiberRenders = {};\n      const stopListeningForRenders = listenForRenders(fiberRenders);\n      lastInteractionRef.current = {\n        ...lastInteractionRef.current,\n        interactionType: kind,\n        blockingTimeStart: Date.now(),\n        childrenTree: details.childrenTree,\n        componentName: details.componentName,\n        componentPath: details.componentPath,\n        fiberRenders,\n        kind: \"interaction-start\",\n        interactionStartDetail: pointerUpStart,\n        stopListeningForRenders\n      };\n      const event = getEvent({ phase: \"end\", target: e4.target });\n      document.addEventListener(event, onLastJS, {\n        once: true\n      });\n      requestAnimationFrame(() => {\n        document.removeEventListener(event, onLastJS);\n      });\n    };\n    document.addEventListener(\n      getEvent({ phase: \"start\" }),\n      // oxlint-disable-next-line typescript/no-explicit-any\n      onInteractionStart,\n      {\n        capture: true\n      }\n    );\n    const onLastJS = (e4, instrumentationId, abort) => {\n      if (lastInteractionRef.current.kind !== \"interaction-start\" && instrumentationId === instrumentationIdInControl) {\n        if (kind === \"pointer\" && e4.target instanceof HTMLSelectElement) {\n          lastInteractionRef.current = {\n            kind: \"uninitialized-stage\",\n            interactionUUID: not_globally_unique_generateId(),\n            stageStart: Date.now(),\n            interactionType: kind\n          };\n          return;\n        }\n        options?.onError?.(lastInteractionRef.current.interactionUUID);\n        lastInteractionRef.current = {\n          kind: \"uninitialized-stage\",\n          interactionUUID: not_globally_unique_generateId(),\n          stageStart: Date.now(),\n          interactionType: kind\n        };\n        return;\n      }\n      instrumentationIdInControl = instrumentationId;\n      trackDetailedTiming({\n        abort,\n        onMicroTask: () => {\n          if (lastInteractionRef.current.kind === \"uninitialized-stage\") {\n            return false;\n          }\n          lastInteractionRef.current = {\n            ...lastInteractionRef.current,\n            kind: \"js-end-stage\",\n            jsEndDetail: performance.now()\n          };\n          return true;\n        },\n        onRAF: () => {\n          if (lastInteractionRef.current.kind !== \"js-end-stage\" && lastInteractionRef.current.kind !== \"raf-stage\") {\n            options?.onError?.(lastInteractionRef.current.interactionUUID);\n            lastInteractionRef.current = {\n              kind: \"uninitialized-stage\",\n              interactionUUID: not_globally_unique_generateId(),\n              stageStart: Date.now(),\n              interactionType: kind\n            };\n            return false;\n          }\n          lastInteractionRef.current = {\n            ...lastInteractionRef.current,\n            kind: \"raf-stage\",\n            rafStart: performance.now()\n          };\n          return true;\n        },\n        onTimeout: () => {\n          if (lastInteractionRef.current.kind !== \"raf-stage\") {\n            options?.onError?.(lastInteractionRef.current.interactionUUID);\n            lastInteractionRef.current = {\n              kind: \"uninitialized-stage\",\n              interactionUUID: not_globally_unique_generateId(),\n              stageStart: Date.now(),\n              interactionType: kind\n            };\n            return;\n          }\n          const now = Date.now();\n          const timeoutStage = Object.freeze({\n            ...lastInteractionRef.current,\n            kind: \"timeout-stage\",\n            blockingTimeEnd: now,\n            commitEnd: performance.now()\n          });\n          lastInteractionRef.current = {\n            kind: \"uninitialized-stage\",\n            interactionUUID: not_globally_unique_generateId(),\n            stageStart: now,\n            interactionType: kind\n          };\n          let completed = false;\n          const completeInteraction = (event) => {\n            completed = true;\n            const latency = event.kind === \"auto-complete-race\" ? event.detailedTiming.commitEnd - event.detailedTiming.interactionStartDetail : event.entry.latency;\n            const finalInteraction = {\n              detailedTiming: timeoutStage,\n              latency,\n              completedAt: Date.now(),\n              flushNeeded: true\n            };\n            options?.onComplete?.(\n              timeoutStage.interactionUUID,\n              finalInteraction,\n              event\n            );\n            const newTasks = tasks.filter(\n              (task2) => task2.interactionUUID !== timeoutStage.interactionUUID\n            );\n            tasks = BoundedArray.fromArray(newTasks, MAX_INTERACTION_TASKS);\n            return finalInteraction;\n          };\n          const task = {\n            completeInteraction,\n            endDateTime: Date.now(),\n            startDateTime: timeoutStage.blockingTimeStart,\n            type: kind,\n            interactionUUID: timeoutStage.interactionUUID\n          };\n          tasks.push(task);\n          if (!isPerformanceEventAvailable()) {\n            const newTasks = tasks.filter(\n              (task2) => task2.interactionUUID !== timeoutStage.interactionUUID\n            );\n            tasks = BoundedArray.fromArray(newTasks, MAX_INTERACTION_TASKS);\n            completeInteraction({\n              kind: \"auto-complete-race\",\n              // redundant\n              detailedTiming: timeoutStage,\n              interactionUUID: timeoutStage.interactionUUID\n            });\n          } else {\n            setTimeout(() => {\n              if (completed) {\n                return;\n              }\n              completeInteraction({\n                kind: \"auto-complete-race\",\n                // redundant\n                detailedTiming: timeoutStage,\n                interactionUUID: timeoutStage.interactionUUID\n              });\n              const newTasks = tasks.filter(\n                (task2) => task2.interactionUUID !== timeoutStage.interactionUUID\n              );\n              tasks = BoundedArray.fromArray(newTasks, MAX_INTERACTION_TASKS);\n            }, 1e3);\n          }\n        }\n      });\n    };\n    const onKeyPress = (e4) => {\n      const id = not_globally_unique_generateId();\n      onLastJS(e4, id, () => id !== instrumentationIdInControl);\n    };\n    if (kind === \"keyboard\") {\n      document.addEventListener(\"keypress\", onKeyPress);\n    }\n    return () => {\n      document.removeEventListener(\n        getEvent({ phase: \"start\" }),\n        // oxlint-disable-next-line typescript/no-explicit-any\n        onInteractionStart,\n        {\n          capture: true\n        }\n      );\n      document.removeEventListener(\"keypress\", onKeyPress);\n    };\n  };\n  var getHostFromFiber = (fiber) => {\n    return traverseFiber(fiber, (node) => {\n      if (isHostFiber(node)) {\n        return true;\n      }\n    })?.stateNode;\n  };\n  var isPerformanceEventAvailable = () => {\n    return \"PerformanceEventTiming\" in globalThis;\n  };\n  var listenForRenders = (fiberRenders) => {\n    const listener = (fiber) => {\n      const displayName = getDisplayName(fiber.type);\n      if (!displayName) {\n        return;\n      }\n      const existing = fiberRenders[displayName];\n      if (!existing) {\n        const parents = /* @__PURE__ */ new Set();\n        const res = fiber.return && getParentCompositeFiber(fiber.return);\n        const parentCompositeName = res && getDisplayName(res[0]);\n        if (parentCompositeName) {\n          parents.add(parentCompositeName);\n        }\n        const { selfTime: selfTime2, totalTime: totalTime2 } = getTimings(fiber);\n        const newChanges2 = collectInspectorDataWithoutCounts(fiber);\n        const emptySection2 = {\n          current: [],\n          changes: /* @__PURE__ */ new Set(),\n          changesCounts: /* @__PURE__ */ new Map()\n        };\n        const changes = {\n          fiberProps: newChanges2.fiberProps || emptySection2,\n          fiberState: newChanges2.fiberState || emptySection2,\n          fiberContext: newChanges2.fiberContext || emptySection2\n        };\n        fiberRenders[displayName] = {\n          renderCount: 1,\n          hasMemoCache: hasMemoCache(fiber),\n          wasFiberRenderMount: wasFiberRenderMount(fiber),\n          parents,\n          selfTime: selfTime2,\n          totalTime: totalTime2,\n          nodeInfo: [\n            {\n              element: getHostFromFiber(fiber),\n              name: getDisplayName(fiber.type) ?? \"Unknown\",\n              selfTime: getTimings(fiber).selfTime\n            }\n          ],\n          changes\n        };\n        return;\n      }\n      const parentType = getParentCompositeFiber(fiber)?.[0]?.type;\n      if (parentType) {\n        const res = fiber.return && getParentCompositeFiber(fiber.return);\n        const parentCompositeName = res && getDisplayName(res[0]);\n        if (parentCompositeName) {\n          existing.parents.add(parentCompositeName);\n        }\n      }\n      const { selfTime, totalTime } = getTimings(fiber);\n      const newChanges = collectInspectorDataWithoutCounts(fiber);\n      if (!newChanges) return;\n      const emptySection = {\n        current: [],\n        changes: /* @__PURE__ */ new Set(),\n        changesCounts: /* @__PURE__ */ new Map()\n      };\n      existing.wasFiberRenderMount = existing.wasFiberRenderMount || wasFiberRenderMount(fiber);\n      existing.hasMemoCache = existing.hasMemoCache || hasMemoCache(fiber);\n      existing.changes = {\n        fiberProps: mergeSectionData(\n          existing.changes?.fiberProps || emptySection,\n          newChanges.fiberProps || emptySection\n        ),\n        fiberState: mergeSectionData(\n          existing.changes?.fiberState || emptySection,\n          newChanges.fiberState || emptySection\n        ),\n        fiberContext: mergeSectionData(\n          existing.changes?.fiberContext || emptySection,\n          newChanges.fiberContext || emptySection\n        )\n      };\n      existing.renderCount += 1;\n      existing.selfTime += selfTime;\n      existing.totalTime += totalTime;\n      existing.nodeInfo.push({\n        element: getHostFromFiber(fiber),\n        name: getDisplayName(fiber.type) ?? \"Unknown\",\n        selfTime: getTimings(fiber).selfTime\n      });\n    };\n    Store.interactionListeningForRenders = listener;\n    return () => {\n      if (Store.interactionListeningForRenders === listener) {\n        Store.interactionListeningForRenders = null;\n      }\n    };\n  };\n  var mergeSectionData = (existing, newData) => {\n    const mergedSection = {\n      current: [...existing.current],\n      changes: /* @__PURE__ */ new Set(),\n      changesCounts: /* @__PURE__ */ new Map()\n    };\n    for (const value of newData.current) {\n      if (!mergedSection.current.some((item) => item.name === value.name)) {\n        mergedSection.current.push(value);\n      }\n    }\n    for (const change of newData.changes) {\n      if (typeof change === \"string\" || typeof change === \"number\") {\n        mergedSection.changes.add(change);\n        const existingCount = existing.changesCounts.get(change) || 0;\n        const newCount = newData.changesCounts.get(change) || 0;\n        mergedSection.changesCounts.set(change, existingCount + newCount);\n      }\n    }\n    return mergedSection;\n  };\n  var wasFiberRenderMount = (fiber) => {\n    if (!fiber.alternate) {\n      return true;\n    }\n    const prevFiber = fiber.alternate;\n    const wasMounted = prevFiber && prevFiber.memoizedState != null && prevFiber.memoizedState.element != null && prevFiber.memoizedState.isDehydrated !== true;\n    const isMounted = fiber.memoizedState != null && fiber.memoizedState.element != null && fiber.memoizedState.isDehydrated !== true;\n    return !wasMounted && isMounted;\n  };\n\n  // src/web/utils/create-store.ts\n  var createStoreImpl = (createState) => {\n    let state;\n    const listeners = /* @__PURE__ */ new Set();\n    const setState = (partial, replace) => {\n      const nextState = typeof partial === \"function\" ? partial(state) : partial;\n      if (!Object.is(nextState, state)) {\n        const previousState = state;\n        state = replace ?? (typeof nextState !== \"object\" || nextState === null) ? nextState : Object.assign({}, state, nextState);\n        listeners.forEach((listener) => listener(state, previousState));\n      }\n    };\n    const getState = () => state;\n    const getInitialState = () => initialState;\n    const subscribe = (selectorOrListener, listener) => {\n      let selector;\n      let actualListener;\n      if (listener) {\n        selector = selectorOrListener;\n        actualListener = listener;\n      } else {\n        actualListener = selectorOrListener;\n      }\n      let currentSlice = selector ? selector(state) : void 0;\n      const wrappedListener = (newState, previousState) => {\n        if (selector) {\n          const nextSlice = selector(newState);\n          const prevSlice = selector(previousState);\n          if (!Object.is(currentSlice, nextSlice)) {\n            currentSlice = nextSlice;\n            actualListener(nextSlice, prevSlice);\n          }\n        } else {\n          actualListener(newState, previousState);\n        }\n      };\n      listeners.add(wrappedListener);\n      return () => listeners.delete(wrappedListener);\n    };\n    const api = { setState, getState, getInitialState, subscribe };\n    const initialState = state = createState(setState, getState, api);\n    return api;\n  };\n  var createStore = (createState) => createStoreImpl;\n\n  // src/core/notifications/event-tracking.ts\n  var accumulatedFiberRendersOverTask = null;\n  createStore()((set) => ({\n    state: {\n      events: []\n    },\n    actions: {\n      addEvent: (event) => {\n        set((store) => ({\n          state: {\n            events: [...store.state.events, event]\n          }\n        }));\n      },\n      clear: () => {\n        set({\n          state: {\n            events: []\n          }\n        });\n      }\n    }\n  }));\n  var EVENT_STORE_CAPACITY = 200;\n  var toolbarEventStore = createStore()(\n    (set, get) => {\n      const listeners = /* @__PURE__ */ new Set();\n      return {\n        state: {\n          events: new BoundedArray(EVENT_STORE_CAPACITY)\n        },\n        actions: {\n          addEvent: (event) => {\n            listeners.forEach((listener) => listener(event));\n            const events = [...get().state.events, event];\n            const applyOverlapCheckToLongRenderEvent = (longRenderEvent, onOverlap) => {\n              const overlapsWith = events.find((event2) => {\n                if (event2.kind === \"long-render\") {\n                  return;\n                }\n                if (event2.id === longRenderEvent.id) {\n                  return;\n                }\n                if (longRenderEvent.data.startAt <= event2.data.startAt && longRenderEvent.data.endAt <= event2.data.endAt && longRenderEvent.data.endAt >= event2.data.startAt) {\n                  return true;\n                }\n                if (event2.data.startAt <= longRenderEvent.data.startAt && event2.data.endAt >= longRenderEvent.data.startAt) {\n                  return true;\n                }\n                if (longRenderEvent.data.startAt <= event2.data.startAt && longRenderEvent.data.endAt >= event2.data.endAt) {\n                  return true;\n                }\n              });\n              if (overlapsWith) {\n                onOverlap(overlapsWith);\n              }\n            };\n            const toRemove = /* @__PURE__ */ new Set();\n            events.forEach((event2) => {\n              if (event2.kind === \"interaction\") return;\n              applyOverlapCheckToLongRenderEvent(event2, () => {\n                toRemove.add(event2.id);\n              });\n            });\n            const withRemovedEvents = events.filter(\n              (event2) => !toRemove.has(event2.id)\n            );\n            set(() => ({\n              state: {\n                events: BoundedArray.fromArray(\n                  withRemovedEvents,\n                  EVENT_STORE_CAPACITY\n                )\n              }\n            }));\n          },\n          addListener: (listener) => {\n            listeners.add(listener);\n            return () => {\n              listeners.delete(listener);\n            };\n          },\n          clear: () => {\n            set({\n              state: {\n                events: new BoundedArray(EVENT_STORE_CAPACITY)\n              }\n            });\n          }\n        }\n      };\n    }\n  );\n  var useToolbarEventLog = () => {\n    return C3(\n      toolbarEventStore.subscribe,\n      toolbarEventStore.getState\n    );\n  };\n  var taskDirtyAt = null;\n  var taskDirtyOrigin = null;\n  var previousTrackCurrentMouseOverElementCallback = null;\n  var overToolbar;\n  var trackCurrentMouseOverToolbar = () => {\n    const callback = (e4) => {\n      overToolbar = e4.composedPath().map((path) => path.id).filter(Boolean).includes(\"react-scan-toolbar\");\n    };\n    document.addEventListener(\"mouseover\", callback);\n    previousTrackCurrentMouseOverElementCallback = callback;\n    return () => {\n      if (previousTrackCurrentMouseOverElementCallback) {\n        document.removeEventListener(\n          \"mouseover\",\n          previousTrackCurrentMouseOverElementCallback\n        );\n      }\n    };\n  };\n  var startDirtyTaskTracking = () => {\n    const onVisibilityChange = () => {\n      taskDirtyAt = performance.now();\n      taskDirtyOrigin = performance.timeOrigin;\n    };\n    document.addEventListener(\"visibilitychange\", onVisibilityChange);\n    return () => {\n      document.removeEventListener(\"visibilitychange\", onVisibilityChange);\n    };\n  };\n  var HIGH_SEVERITY_FPS_DROP_TIME = 150;\n  var framesDrawnInTheLastSecond = [];\n  function startLongPipelineTracking() {\n    let rafHandle;\n    let timeoutHandle;\n    function measure() {\n      let unSub = null;\n      accumulatedFiberRendersOverTask = null;\n      accumulatedFiberRendersOverTask = {};\n      unSub = listenForRenders(accumulatedFiberRendersOverTask);\n      const startOrigin = performance.timeOrigin;\n      const startTime = performance.now();\n      rafHandle = requestAnimationFrame(() => {\n        timeoutHandle = setTimeout(() => {\n          const endNow = performance.now();\n          const duration = endNow - startTime;\n          const endOrigin = performance.timeOrigin;\n          framesDrawnInTheLastSecond.push(endNow + endOrigin);\n          const framesInTheLastSecond = framesDrawnInTheLastSecond.filter(\n            (frameAt) => endNow + endOrigin - frameAt <= 1e3\n          );\n          const fps2 = framesInTheLastSecond.length;\n          framesDrawnInTheLastSecond = framesInTheLastSecond;\n          const taskConsideredDirty = taskDirtyAt !== null && taskDirtyOrigin !== null ? endNow + endOrigin - (taskDirtyOrigin + taskDirtyAt) < 100 : null;\n          const wasTaskInfluencedByToolbar = overToolbar !== null && overToolbar;\n          if (duration > HIGH_SEVERITY_FPS_DROP_TIME && !taskConsideredDirty && document.visibilityState === \"visible\" && !wasTaskInfluencedByToolbar) {\n            const endAt = endOrigin + endNow;\n            const startAt = startTime + startOrigin;\n            toolbarEventStore.getState().actions.addEvent({\n              kind: \"long-render\",\n              id: not_globally_unique_generateId(),\n              data: {\n                endAt,\n                startAt,\n                meta: {\n                  // oxlint-disable-next-line typescript/no-non-null-assertion\n                  fiberRenders: accumulatedFiberRendersOverTask,\n                  latency: duration,\n                  fps: fps2\n                }\n              }\n            });\n          }\n          taskDirtyAt = null;\n          taskDirtyOrigin = null;\n          unSub?.();\n          measure();\n        }, 0);\n      });\n      return unSub;\n    }\n    const measureUnSub = measure();\n    return () => {\n      measureUnSub();\n      cancelAnimationFrame(rafHandle);\n      clearTimeout(timeoutHandle);\n    };\n  }\n  var startTimingTracking = () => {\n    const unSubPerformance = setupPerformancePublisher();\n    const unSubMouseOver = trackCurrentMouseOverToolbar();\n    const unSubDirtyTaskTracking = startDirtyTaskTracking();\n    const unSubLongPipelineTracking = startLongPipelineTracking();\n    const onComplete = async (_4, finalInteraction, event) => {\n      toolbarEventStore.getState().actions.addEvent({\n        kind: \"interaction\",\n        id: not_globally_unique_generateId(),\n        data: {\n          startAt: finalInteraction.detailedTiming.blockingTimeStart,\n          endAt: performance.now() + performance.timeOrigin,\n          meta: { ...finalInteraction, kind: event.kind }\n          // TODO, will need interaction specific metadata here\n        }\n      });\n      const existingCompletedInteractions = performanceEntryChannels.getChannelState(\"recording\");\n      finalInteraction.detailedTiming.stopListeningForRenders();\n      if (existingCompletedInteractions.length) {\n        performanceEntryChannels.updateChannelState(\n          \"recording\",\n          () => new BoundedArray(MAX_CHANNEL_SIZE)\n        );\n      }\n    };\n    const unSubDetailedPointerTiming = setupDetailedPointerTimingListener(\n      \"pointer\",\n      {\n        onComplete\n      }\n    );\n    const unSubDetailedKeyboardTiming = setupDetailedPointerTimingListener(\n      \"keyboard\",\n      {\n        onComplete\n      }\n    );\n    const unSubInteractions = listenForPerformanceEntryInteractions(\n      (completedInteraction) => {\n        interactionStore.setState(\n          BoundedArray.fromArray(\n            interactionStore.getCurrentState().concat(completedInteraction),\n            MAX_INTERACTION_BATCH\n          )\n        );\n      }\n    );\n    return () => {\n      unSubMouseOver();\n      unSubDirtyTaskTracking();\n      unSubLongPipelineTracking();\n      unSubPerformance();\n      unSubDetailedPointerTiming();\n      unSubInteractions();\n      unSubDetailedKeyboardTiming();\n    };\n  };\n\n  // src/web/views/notifications/data.ts\n  var getComponentName = (path) => {\n    const filteredPath = path.filter((item) => item.length > 2);\n    if (filteredPath.length === 0) {\n      return path.at(-1) ?? \"Unknown\";\n    }\n    return filteredPath.at(-1);\n  };\n  var getTotalTime = (timing) => {\n    switch (timing.kind) {\n      case \"interaction\": {\n        const {\n          renderTime,\n          otherJSTime,\n          framePreparation,\n          frameConstruction,\n          frameDraw\n        } = timing;\n        return renderTime + otherJSTime + framePreparation + frameConstruction + (frameDraw ?? 0);\n      }\n      case \"dropped-frames\": {\n        return timing.otherTime + timing.renderTime;\n      }\n    }\n  };\n  var isRenderMemoizable = (groupedFiberRender) => {\n    if (groupedFiberRender.wasFiberRenderMount) {\n      return false;\n    }\n    if (groupedFiberRender.hasMemoCache) {\n      return false;\n    }\n    return groupedFiberRender.changes.context.length === 0 && groupedFiberRender.changes.props.length === 0 && groupedFiberRender.changes.state.length === 0;\n  };\n  var getEventSeverity = (event) => {\n    const totalTime = getTotalTime(event.timing);\n    switch (event.kind) {\n      case \"interaction\": {\n        if (totalTime < 200) return \"low\";\n        if (totalTime < 500) return \"needs-improvement\";\n        return \"high\";\n      }\n      case \"dropped-frames\": {\n        if (totalTime < 50) return \"low\";\n        if (totalTime < HIGH_SEVERITY_FPS_DROP_TIME) return \"needs-improvement\";\n        return \"high\";\n      }\n    }\n  };\n  var useNotificationsContext = () => x2(NotificationStateContext);\n  var NotificationStateContext = K(null);\n\n  // src/web/views/notifications/icons.tsx\n  var ChevronRight = ({\n    size = 24,\n    className\n  }) => /* @__PURE__ */ u4(\n    \"svg\",\n    {\n      xmlns: \"http://www.w3.org/2000/svg\",\n      width: size,\n      height: size,\n      viewBox: \"0 0 24 24\",\n      fill: \"none\",\n      stroke: \"currentColor\",\n      \"stroke-width\": \"2\",\n      \"stroke-linecap\": \"round\",\n      \"stroke-linejoin\": \"round\",\n      className: cn2([\"lucide lucide-chevron-right\", className]),\n      children: /* @__PURE__ */ u4(\"path\", { d: \"m9 18 6-6-6-6\" })\n    }\n  );\n  var Notification = ({\n    className = \"\",\n    size = 24,\n    events = []\n  }) => {\n    const hasHighSeverity = events.includes(true);\n    const totalSevere = events.filter((e4) => e4).length;\n    const displayCount = totalSevere > 99 ? \">99\" : totalSevere;\n    const badgeSize = hasHighSeverity ? Math.max(size * 0.6, 14) : Math.max(size * 0.4, 6);\n    return /* @__PURE__ */ u4(\"div\", { className: \"relative\", children: [\n      /* @__PURE__ */ u4(\n        \"svg\",\n        {\n          xmlns: \"http://www.w3.org/2000/svg\",\n          width: size,\n          height: size,\n          viewBox: \"0 0 24 24\",\n          fill: \"none\",\n          stroke: \"currentColor\",\n          \"stroke-width\": \"2\",\n          \"stroke-linecap\": \"round\",\n          \"stroke-linejoin\": \"round\",\n          className: `lucide lucide-bell ${className}`,\n          children: [\n            /* @__PURE__ */ u4(\"path\", { d: \"M10.268 21a2 2 0 0 0 3.464 0\" }),\n            /* @__PURE__ */ u4(\"path\", { d: \"M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326\" })\n          ]\n        }\n      ),\n      events.length > 0 && totalSevere > 0 && ReactScanInternals.options.value.showNotificationCount && /* @__PURE__ */ u4(\n        \"div\",\n        {\n          className: cn2([\n            \"absolute\",\n            hasHighSeverity ? \"-top-2.5 -right-2.5\" : \"-top-1 -right-1\",\n            \"rounded-full\",\n            \"flex items-center justify-center\",\n            \"text-[8px] font-medium text-white\",\n            \"aspect-square\",\n            hasHighSeverity ? \"bg-red-500/90\" : \"bg-purple-500/90\"\n          ]),\n          style: {\n            width: `${badgeSize}px`,\n            height: `${badgeSize}px`,\n            padding: hasHighSeverity ? \"0.5px\" : \"0\"\n          },\n          children: hasHighSeverity && displayCount\n        }\n      )\n    ] });\n  };\n  var CloseIcon = ({\n    className = \"\",\n    size = 24\n  }) => /* @__PURE__ */ u4(\n    \"svg\",\n    {\n      xmlns: \"http://www.w3.org/2000/svg\",\n      width: size,\n      height: size,\n      viewBox: \"0 0 24 24\",\n      fill: \"none\",\n      stroke: \"currentColor\",\n      \"stroke-width\": \"2\",\n      \"stroke-linecap\": \"round\",\n      \"stroke-linejoin\": \"round\",\n      className,\n      children: [\n        /* @__PURE__ */ u4(\"path\", { d: \"M18 6 6 18\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"m6 6 12 12\" })\n      ]\n    }\n  );\n  var VolumeOnIcon = ({\n    className = \"\",\n    size = 24\n  }) => /* @__PURE__ */ u4(\n    \"svg\",\n    {\n      xmlns: \"http://www.w3.org/2000/svg\",\n      width: size,\n      height: size,\n      viewBox: \"0 0 24 24\",\n      fill: \"none\",\n      stroke: \"currentColor\",\n      \"stroke-width\": \"2\",\n      \"stroke-linecap\": \"round\",\n      \"stroke-linejoin\": \"round\",\n      className,\n      children: [\n        /* @__PURE__ */ u4(\"path\", { d: \"M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M16 9a5 5 0 0 1 0 6\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M19.364 18.364a9 9 0 0 0 0-12.728\" })\n      ]\n    }\n  );\n  var VolumeOffIcon = ({\n    className = \"\",\n    size = 24\n  }) => /* @__PURE__ */ u4(\n    \"svg\",\n    {\n      xmlns: \"http://www.w3.org/2000/svg\",\n      width: size,\n      height: size,\n      viewBox: \"0 0 24 24\",\n      fill: \"none\",\n      stroke: \"currentColor\",\n      \"stroke-width\": \"2\",\n      \"stroke-linecap\": \"round\",\n      \"stroke-linejoin\": \"round\",\n      className,\n      children: [\n        /* @__PURE__ */ u4(\"path\", { d: \"M16 9a5 5 0 0 1 .95 2.293\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M19.364 5.636a9 9 0 0 1 1.889 9.96\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"m2 2 20 20\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"m7 7-.587.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298V11\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M9.828 4.172A.686.686 0 0 1 11 4.657v.686\" })\n      ]\n    }\n  );\n  var ArrowLeft = ({\n    size = 24,\n    className\n  }) => /* @__PURE__ */ u4(\n    \"svg\",\n    {\n      xmlns: \"http://www.w3.org/2000/svg\",\n      width: size,\n      height: size,\n      viewBox: \"0 0 24 24\",\n      fill: \"none\",\n      stroke: \"currentColor\",\n      \"stroke-width\": \"2\",\n      \"stroke-linecap\": \"round\",\n      \"stroke-linejoin\": \"round\",\n      className: cn2([\"lucide lucide-arrow-left\", className]),\n      children: [\n        /* @__PURE__ */ u4(\"path\", { d: \"m12 19-7-7 7-7\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M19 12H5\" })\n      ]\n    }\n  );\n  var PointerIcon = ({\n    className = \"\",\n    size = 24\n  }) => /* @__PURE__ */ u4(\n    \"svg\",\n    {\n      xmlns: \"http://www.w3.org/2000/svg\",\n      width: size,\n      height: size,\n      viewBox: \"0 0 24 24\",\n      fill: \"none\",\n      stroke: \"currentColor\",\n      \"stroke-width\": \"2\",\n      \"stroke-linecap\": \"round\",\n      \"stroke-linejoin\": \"round\",\n      className,\n      children: [\n        /* @__PURE__ */ u4(\"path\", { d: \"M14 4.1 12 6\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"m5.1 8-2.9-.8\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"m6 12-1.9 2\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M7.2 2.2 8 5.1\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M9.037 9.69a.498.498 0 0 1 .653-.653l11 4.5a.5.5 0 0 1-.074.949l-4.349 1.041a1 1 0 0 0-.74.739l-1.04 4.35a.5.5 0 0 1-.95.074z\" })\n      ]\n    }\n  );\n  var KeyboardIcon = ({\n    className = \"\",\n    size = 24\n  }) => /* @__PURE__ */ u4(\n    \"svg\",\n    {\n      xmlns: \"http://www.w3.org/2000/svg\",\n      width: size,\n      height: size,\n      viewBox: \"0 0 24 24\",\n      fill: \"none\",\n      stroke: \"currentColor\",\n      \"stroke-width\": \"2\",\n      \"stroke-linecap\": \"round\",\n      \"stroke-linejoin\": \"round\",\n      className,\n      children: [\n        /* @__PURE__ */ u4(\"path\", { d: \"M10 8h.01\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M12 12h.01\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M14 8h.01\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M16 12h.01\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M18 8h.01\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M6 8h.01\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M7 16h10\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M8 12h.01\" }),\n        /* @__PURE__ */ u4(\"rect\", { width: \"20\", height: \"16\", x: \"2\", y: \"4\", rx: \"2\" })\n      ]\n    }\n  );\n  var ClearIcon = ({\n    className = \"\",\n    size = 24\n  }) => {\n    return /* @__PURE__ */ u4(\n      \"svg\",\n      {\n        xmlns: \"http://www.w3.org/2000/svg\",\n        width: size,\n        height: size,\n        viewBox: \"0 0 24 24\",\n        fill: \"none\",\n        stroke: \"currentColor\",\n        \"stroke-width\": \"2\",\n        \"stroke-linecap\": \"round\",\n        \"stroke-linejoin\": \"round\",\n        className,\n        style: { transform: \"rotate(180deg)\" },\n        children: [\n          /* @__PURE__ */ u4(\"circle\", { cx: \"12\", cy: \"12\", r: \"10\" }),\n          /* @__PURE__ */ u4(\"path\", { d: \"m4.9 4.9 14.2 14.2\" })\n        ]\n      }\n    );\n  };\n  var TrendingDownIcon = ({\n    className = \"\",\n    size = 24\n  }) => /* @__PURE__ */ u4(\n    \"svg\",\n    {\n      xmlns: \"http://www.w3.org/2000/svg\",\n      width: size,\n      height: size,\n      viewBox: \"0 0 24 24\",\n      fill: \"none\",\n      stroke: \"currentColor\",\n      strokeWidth: \"2\",\n      strokeLinecap: \"round\",\n      strokeLinejoin: \"round\",\n      className,\n      children: [\n        /* @__PURE__ */ u4(\"polyline\", { points: \"22 17 13.5 8.5 8.5 13.5 2 7\" }),\n        /* @__PURE__ */ u4(\"polyline\", { points: \"16 17 22 17 22 11\" })\n      ]\n    }\n  );\n\n  // src/web/views/notifications/popover.tsx\n  var Popover = ({\n    children,\n    triggerContent,\n    wrapperProps\n  }) => {\n    const [popoverState, setPopoverState] = d2(\"closed\");\n    const [elBoundingRect, setElBoundingRect] = d2(null);\n    const [viewportSize, setViewportSize] = d2({\n      width: window.innerWidth,\n      height: window.innerHeight\n    });\n    const triggerRef = A2(null);\n    const popoverRef = A2(null);\n    const portalEl = x2(ToolbarElementContext);\n    const isHoveredRef = A2(false);\n    y2(() => {\n      const handleResize = () => {\n        setViewportSize({\n          width: window.innerWidth,\n          height: window.innerHeight\n        });\n        updateRect();\n      };\n      window.addEventListener(\"resize\", handleResize);\n      return () => window.removeEventListener(\"resize\", handleResize);\n    }, []);\n    const updateRect = () => {\n      if (triggerRef.current && portalEl) {\n        const triggerRect = triggerRef.current.getBoundingClientRect();\n        const portalRect = portalEl.getBoundingClientRect();\n        const centerX = triggerRect.left + triggerRect.width / 2;\n        const centerY = triggerRect.top;\n        const rect = new DOMRect(\n          centerX - portalRect.left,\n          centerY - portalRect.top,\n          triggerRect.width,\n          triggerRect.height\n        );\n        setElBoundingRect(rect);\n      }\n    };\n    y2(() => {\n      updateRect();\n    }, [triggerRef.current]);\n    y2(() => {\n      if (popoverState === \"opening\") {\n        const timer = setTimeout(() => setPopoverState(\"open\"), 120);\n        return () => clearTimeout(timer);\n      } else if (popoverState === \"closing\") {\n        const timer = setTimeout(() => setPopoverState(\"closed\"), 120);\n        return () => clearTimeout(timer);\n      }\n    }, [popoverState]);\n    y2(() => {\n      const interval = setInterval(() => {\n        if (!isHoveredRef.current && popoverState !== \"closed\") {\n          setPopoverState(\"closing\");\n        }\n      }, 1e3);\n      return () => clearInterval(interval);\n    }, [popoverState]);\n    const handleMouseEnter = () => {\n      isHoveredRef.current = true;\n      updateRect();\n      setPopoverState(\"opening\");\n    };\n    const handleMouseLeave = () => {\n      isHoveredRef.current = false;\n      updateRect();\n      setPopoverState(\"closing\");\n    };\n    const getPopoverPosition = () => {\n      if (!elBoundingRect || !portalEl) return { top: 0, left: 0 };\n      const portalRect = portalEl.getBoundingClientRect();\n      const popoverWidth = 175;\n      const popoverHeight = popoverRef.current?.offsetHeight || 40;\n      const safeArea = 5;\n      const viewportX = elBoundingRect.x + portalRect.left;\n      const viewportY = elBoundingRect.y + portalRect.top;\n      let left = viewportX;\n      let top = viewportY - 4;\n      if (left - popoverWidth / 2 < safeArea) {\n        left = safeArea + popoverWidth / 2;\n      } else if (left + popoverWidth / 2 > viewportSize.width - safeArea) {\n        left = viewportSize.width - safeArea - popoverWidth / 2;\n      }\n      if (top - popoverHeight < safeArea) {\n        top = viewportY + elBoundingRect.height + 4;\n      }\n      return {\n        top: top - portalRect.top,\n        left: left - portalRect.left\n      };\n    };\n    const popoverPosition = getPopoverPosition();\n    return /* @__PURE__ */ u4(k, { children: [\n      portalEl && elBoundingRect && popoverState !== \"closed\" && $2(\n        /* @__PURE__ */ u4(\n          \"div\",\n          {\n            ref: popoverRef,\n            className: cn2([\n              \"absolute z-100 bg-white text-black rounded-lg px-3 py-2 shadow-lg\",\n              \"transition-[opacity] duration-120 ease-out\",\n              'after:content-[\"\"] after:absolute after:top-[100%]',\n              \"after:left-1/2 after:-translate-x-1/2\",\n              \"after:w-[10px] after:h-[6px]\",\n              \"after:border-l-[5px] after:border-l-transparent\",\n              \"after:border-r-[5px] after:border-r-transparent\",\n              \"after:border-t-[6px] after:border-t-white\",\n              \"pointer-events-none\",\n              popoverState === \"opening\" || popoverState === \"closing\" ? \"opacity-0\" : \"opacity-100\"\n            ]),\n            style: {\n              top: popoverPosition.top + \"px\",\n              left: popoverPosition.left + \"px\",\n              transform: `translate(-50%, calc(-100% - 4px)) scale(${popoverState === \"open\" ? 1 : 0.97})`,\n              minWidth: \"175px\",\n              willChange: \"opacity, transform\"\n            },\n            children\n          }\n        ),\n        portalEl\n      ),\n      /* @__PURE__ */ u4(\n        \"div\",\n        {\n          ref: triggerRef,\n          onMouseEnter: handleMouseEnter,\n          onMouseLeave: handleMouseLeave,\n          ...wrapperProps,\n          children: triggerContent\n        }\n      )\n    ] });\n  };\n\n  // src/web/views/notifications/notification-tabs.tsx\n  var NotificationTabs = ({\n    selectedEvent: _4\n  }) => {\n    const { notificationState, setNotificationState, setRoute } = useNotificationsContext();\n    return /* @__PURE__ */ u4(\n      \"div\",\n      {\n        className: cn2([\n          \"flex w-full justify-between items-center px-3 py-2 text-xs\"\n        ]),\n        children: [\n          /* @__PURE__ */ u4(\n            \"div\",\n            {\n              className: cn2([\n                \"bg-[#18181B] flex items-center gap-x-1 p-1 rounded-sm\"\n              ]),\n              children: [\n                /* @__PURE__ */ u4(\n                  \"button\",\n                  {\n                    onClick: () => {\n                      setRoute({\n                        route: \"render-visualization\",\n                        routeMessage: null\n                      });\n                    },\n                    className: cn2([\n                      \"w-1/2 flex items-center justify-center whitespace-nowrap py-[5px] px-1 gap-x-1\",\n                      notificationState.route === \"render-visualization\" || notificationState.route === \"render-explanation\" ? \"text-white bg-[#7521c8] rounded-sm\" : \"text-[#6E6E77] bg-[#18181B] rounded-sm\"\n                    ]),\n                    children: \"Ranked\"\n                  }\n                ),\n                /* @__PURE__ */ u4(\n                  \"button\",\n                  {\n                    onClick: () => {\n                      setRoute({\n                        route: \"other-visualization\",\n                        routeMessage: null\n                      });\n                    },\n                    className: cn2([\n                      \"w-1/2 flex items-center justify-center whitespace-nowrap py-[5px] px-1 gap-x-1\",\n                      notificationState.route === \"other-visualization\" ? \"text-white bg-[#7521c8] rounded-sm\" : \"text-[#6E6E77] bg-[#18181B] rounded-sm\"\n                    ]),\n                    children: \"Overview\"\n                  }\n                ),\n                /* @__PURE__ */ u4(\n                  \"button\",\n                  {\n                    onClick: () => {\n                      setRoute({\n                        route: \"optimize\",\n                        routeMessage: null\n                      });\n                    },\n                    className: cn2([\n                      \"w-1/2 flex items-center justify-center whitespace-nowrap py-[5px] px-1 gap-x-1\",\n                      notificationState.route === \"optimize\" ? \"text-white bg-[#7521c8] rounded-sm\" : \"text-[#6E6E77] bg-[#18181B] rounded-sm\"\n                    ]),\n                    children: /* @__PURE__ */ u4(\"span\", { children: \"Prompts\" })\n                  }\n                )\n              ]\n            }\n          ),\n          /* @__PURE__ */ u4(\n            Popover,\n            {\n              triggerContent: /* @__PURE__ */ u4(\n                \"button\",\n                {\n                  onClick: () => {\n                    setNotificationState((prev) => {\n                      if (prev.audioNotificationsOptions.enabled && prev.audioNotificationsOptions.audioContext.state !== \"closed\") {\n                        prev.audioNotificationsOptions.audioContext.close();\n                      }\n                      const prevEnabledState = prev.audioNotificationsOptions.enabled;\n                      localStorage.setItem(\n                        \"react-scan-notifications-audio\",\n                        String(!prevEnabledState)\n                      );\n                      const audioContext = new AudioContext();\n                      if (!prev.audioNotificationsOptions.enabled) {\n                        playNotificationSound(audioContext);\n                      }\n                      if (prevEnabledState) {\n                        audioContext.close();\n                      }\n                      return {\n                        ...prev,\n                        audioNotificationsOptions: prevEnabledState ? {\n                          audioContext: null,\n                          enabled: false\n                        } : {\n                          audioContext,\n                          enabled: true\n                        }\n                      };\n                    });\n                  },\n                  className: \"ml-auto\",\n                  children: /* @__PURE__ */ u4(\n                    \"div\",\n                    {\n                      className: cn2([\n                        \"flex gap-x-2 justify-center items-center text-[#6E6E77]\"\n                      ]),\n                      children: [\n                        /* @__PURE__ */ u4(\"span\", { children: \"Alerts\" }),\n                        notificationState.audioNotificationsOptions.enabled ? /* @__PURE__ */ u4(VolumeOnIcon, { size: 16, className: \"text-[#6E6E77]\" }) : /* @__PURE__ */ u4(VolumeOffIcon, { size: 16, className: \"text-[#6E6E77]\" })\n                      ]\n                    }\n                  )\n                }\n              ),\n              children: /* @__PURE__ */ u4(k, { children: \"Play a chime when a slowdown is recorded\" })\n            }\n          )\n        ]\n      }\n    );\n  };\n\n  // src/web/views/notifications/optimize.tsx\n  var formatReactData = (groupedFiberRenders) => {\n    let text = \"\";\n    const filteredFibers = groupedFiberRenders.toSorted((a4, b3) => b3.totalTime - a4.totalTime).slice(0, 30).filter((fiber) => fiber.totalTime > 5);\n    filteredFibers.forEach((fiberRender) => {\n      let localText = \"\";\n      localText += \"Component Name:\";\n      localText += fiberRender.name;\n      localText += \"\\n\";\n      localText += `Rendered: ${fiberRender.count} times\n`;\n      localText += `Sum of self times for ${fiberRender.name} is ${fiberRender.totalTime.toFixed(0)}ms\n`;\n      if (fiberRender.changes.props.length > 0) {\n        localText += `Changed props for all ${fiberRender.name} instances (\"name:count\" pairs)\n`;\n        fiberRender.changes.props.forEach((change) => {\n          localText += `${change.name}:${change.count}x\n`;\n        });\n      }\n      if (fiberRender.changes.state.length > 0) {\n        localText += `Changed state for all ${fiberRender.name} instances (\"hook index:count\" pairs)\n`;\n        fiberRender.changes.state.forEach((change) => {\n          localText += `${change.index}:${change.count}x\n`;\n        });\n      }\n      if (fiberRender.changes.context.length > 0) {\n        localText += `Changed context for all ${fiberRender.name} instances (\"context display name (if exists):count\" pairs)\n`;\n        fiberRender.changes.context.forEach((change) => {\n          localText += `${change.name}:${change.count}x\n`;\n        });\n      }\n      text += localText;\n      text += \"\\n\";\n    });\n    return text;\n  };\n  var generateInteractionDataPrompt = ({\n    renderTime,\n    eHandlerTimeExcludingRenders,\n    toRafTime,\n    commitTime,\n    framePresentTime,\n    formattedReactData\n  }) => {\n    return `I will provide you with a set of high level, and low level performance data about an interaction in a React App:\n### High level\n- react component render time: ${renderTime.toFixed(0)}ms\n- how long it took to run javascript event handlers (EXCLUDING REACT RENDERS): ${eHandlerTimeExcludingRenders.toFixed(0)}ms\n- how long it took from the last event handler time, to the last request animation frame: ${toRafTime.toFixed(0)}ms\n\t- things like prepaint, style recalculations, layerization, async web API's like observers may occur during this time\n- how long it took from the last request animation frame to when the dom was committed: ${commitTime.toFixed(0)}ms\n\t- during this period you will see paint, commit, potential style recalcs, and other misc browser activity. Frequently high times here imply css that makes the browser do a lot of work, or mutating expensive dom properties during the event handler stage. This can be many things, but it narrows the problem scope significantly when this is high\n${framePresentTime === null ? \"\" : `- how long it took from dom commit for the frame to be presented: ${framePresentTime.toFixed(0)}ms. This is when information about how to paint the next frame is sent to the compositor threads, and when the GPU does work. If this is high, look for issues that may be a bottleneck for operations occurring during this time`}\n\n### Low level\nWe also have lower level information about react components, such as their render time, and which props/state/context changed when they re-rendered.\n${formattedReactData}`;\n  };\n  var generateInteractionOptimizationPrompt = ({\n    interactionType,\n    name,\n    componentPath,\n    time,\n    renderTime,\n    eHandlerTimeExcludingRenders,\n    toRafTime,\n    commitTime,\n    framePresentTime,\n    formattedReactData\n  }) => `You will attempt to implement a performance improvement to a user interaction in a React app. You will be provided with data about the interaction, and the slow down.\n\nYour should split your goals into 2 parts:\n- identifying the problem\n- fixing the problem\n\t- it is okay to implement a fix even if you aren't 100% sure the fix solves the performance problem. When you aren't sure, you should tell the user to try repeating the interaction, and feeding the \"Formatted Data\" in the React Scan notifications optimize tab. This allows you to start a debugging flow with the user, where you attempt a fix, and observe the result. The user may make a mistake when they pass you the formatted data, so must make sure, given the data passed to you, that the associated data ties to the same interaction you were trying to debug.\n\n\nMake sure to check if the user has the react compiler enabled (project dependent, configured through build tool), so you don't unnecessarily memoize components. If it is, you do not need to worry about memoizing user components\n\nOne challenge you may face is the performance problem lies in a node_module, not in user code. If you are confident the problem originates because of a node_module, there are multiple strategies, which are context dependent:\n- you can try to work around the problem, knowing which module is slow\n- you can determine if its possible to resolve the problem in the node_module by modifying non node_module code\n- you can monkey patch the node_module to experiment and see if it's really the problem (you can modify a functions properties to hijack the call for example)\n- you can determine if it's feasible to replace whatever node_module is causing the problem with a performant option (this is an extreme)\n\nThe interaction was a ${interactionType} on the component named ${name}. This component has the following ancestors ${componentPath}. This is the path from the component, to the root. This should be enough information to figure out where this component is in the user's code base\n\nThis path is the component that was clicked, so it should tell you roughly where component had an event handler that triggered a state change.\n\nPlease note that the leaf node of this path might not be user code (if they use a UI library), and they may contain many wrapper components that just pass through children that aren't relevant to the actual click. So make you sure analyze the path and understand what the user code is doing\n\nWe have a set of high level, and low level data about the performance issue.\n\nThe click took ${time.toFixed(0)}ms from interaction start, to when a new frame was presented to a user.\n\nWe also provide you with a breakdown of what the browser spent time on during the period of interaction start to frame presentation.\n\n- react component render time: ${renderTime.toFixed(0)}ms\n- how long it took to run javascript event handlers (EXCLUDING REACT RENDERS): ${eHandlerTimeExcludingRenders.toFixed(0)}ms\n- how long it took from the last event handler time, to the last request animation frame: ${toRafTime.toFixed(0)}ms\n\t- things like prepaint, style recalculations, layerization, async web API's like observers may occur during this time\n- how long it took from the last request animation frame to when the dom was committed: ${commitTime.toFixed(0)}ms\n\t- during this period you will see paint, commit, potential style recalcs, and other misc browser activity. Frequently high times here imply css that makes the browser do a lot of work, or mutating expensive dom properties during the event handler stage. This can be many things, but it narrows the problem scope significantly when this is high\n${framePresentTime === null ? \"\" : `- how long it took from dom commit for the frame to be presented: ${framePresentTime.toFixed(0)}ms. This is when information about how to paint the next frame is sent to the compositor threads, and when the GPU does work. If this is high, look for issues that may be a bottleneck for operations occurring during this time`}\n\n\nWe also have lower level information about react components, such as their render time, and which props/state/context changed when they re-rendered.\n\n${formattedReactData}\n\nYou may notice components have many renders, but much fewer props/state/context changes. This normally implies most of the components could of been memoized to avoid computation\n\nIt's also important to remember if a component had no props/state/context change, and it was memoized, it would not render. So the flow should be:\n- find the most expensive components\n- see what's causing them to render\n- determine how you can make those state/props/context not change for a large set of the renders\n- once there are no more changes left, you can memoize the component so it no longer unnecessarily re-renders. \n\nAn important thing to note is that if you see a lot of react renders (some components with very high render counts), but javascript excluding renders is much higher than render time, it is possible that the components with lots of renders run hooks like useEffect/useLayoutEffect, which run during the JS event handler period.\n\nIt's also good to note that react profiles hook times in development, and if many hooks are called (lets say 5,000 components all called a useEffect), it will have to profile every single one. And it may also be the case the comparison of the hooks dependency can be expensive, and that would not be tracked in render time.\n\nIf a node_module is the component with high renders, you can experiment to see if that component is the root issue (because of hooks). You should use the same instructions for node_module debugging mentioned previously.\n\n`;\n  var generateFrameDropOptimizationPrompt = ({\n    renderTime,\n    otherTime,\n    formattedReactData\n  }) => `You will attempt to implement a performance improvement to a large slowdown in a react app\n\nYour should split your goals into 2 parts:\n- identifying the problem\n- fixing the problem\n\t- it is okay to implement a fix even if you aren't 100% sure the fix solves the performance problem. When you aren't sure, you should tell the user to try repeating the interaction, and feeding the \"Formatted Data\" in the React Scan notifications optimize tab. This allows you to start a debugging flow with the user, where you attempt a fix, and observe the result. The user may make a mistake when they pass you the formatted data, so must make sure, given the data passed to you, that the associated data ties to the same interaction you were trying to debug.\n\nMake sure to check if the user has the react compiler enabled (project dependent, configured through build tool), so you don't unnecessarily memoize components. If it is, you do not need to worry about memoizing user components\n\nOne challenge you may face is the performance problem lies in a node_module, not in user code. If you are confident the problem originates because of a node_module, there are multiple strategies, which are context dependent:\n- you can try to work around the problem, knowing which module is slow\n- you can determine if its possible to resolve the problem in the node_module by modifying non node_module code\n- you can monkey patch the node_module to experiment and see if it's really the problem (you can modify a functions properties to hijack the call for example)\n- you can determine if it's feasible to replace whatever node_module is causing the problem with a performant option (this is an extreme)\n\n\nWe have the high level time of how much react spent rendering, and what else the browser spent time on during this slowdown\n\n- react component render time: ${renderTime.toFixed(0)}ms\n- other time: ${otherTime}ms\n\n\nWe also have lower level information about react components, such as their render time, and which props/state/context changed when they re-rendered.\n\n${formattedReactData}\n\nYou may notice components have many renders, but much fewer props/state/context changes. This normally implies most of the components could of been memoized to avoid computation\n\nIt's also important to remember if a component had no props/state/context change, and it was memoized, it would not render. So the flow should be:\n- find the most expensive components\n- see what's causing them to render\n- determine how you can make those state/props/context not change for a large set of the renders\n- once there are no more changes left, you can memoize the component so it no longer unnecessarily re-renders. \n\nAn important thing to note is that if you see a lot of react renders (some components with very high render counts), but other time is much higher than render time, it is possible that the components with lots of renders run hooks like useEffect/useLayoutEffect, which run outside of what we profile (just react render time).\n\nIt's also good to note that react profiles hook times in development, and if many hooks are called (lets say 5,000 components all called a useEffect), it will have to profile every single one. And it may also be the case the comparison of the hooks dependency can be expensive, and that would not be tracked in render time.\n\nIf a node_module is the component with high renders, you can experiment to see if that component is the root issue (because of hooks). You should use the same instructions for node_module debugging mentioned previously.\n\nIf renders don't seem to be the problem, see if there are any expensive CSS properties being added/mutated, or any expensive DOM Element mutations/new elements being created that could cause this slowdown. \n`;\n  var generateFrameDropExplanationPrompt = ({\n    renderTime,\n    otherTime,\n    formattedReactData\n  }) => `Your goal will be to help me find the source of a performance problem in a React App. I collected a large dataset about this specific performance problem.\n\nWe have the high level time of how much react spent rendering, and what else the browser spent time on during this slowdown\n\n- react component render time: ${renderTime.toFixed(0)}ms\n- other time (other JavaScript, hooks like useEffect, style recalculations, layerization, paint & commit and everything else the browser might do to draw a new frame after javascript mutates the DOM): ${otherTime}ms\n\n\nWe also have lower level information about react components, such as their render time, and which props/state/context changed when they re-rendered.\n\n${formattedReactData}\n\nYou may notice components have many renders, but much fewer props/state/context changes. This normally implies most of the components could of been memoized to avoid computation\n\nIt's also important to remember if a component had no props/state/context change, and it was memoized, it would not render. So a flow we can go through is:\n- find the most expensive components\n- see what's causing them to render\n- determine how you can make those state/props/context not change for a large set of the renders\n- once there are no more changes left, you can memoize the component so it no longer unnecessarily re-renders. \n\n\nAn important thing to note is that if you see a lot of react renders (some components with very high render counts), but other time is much higher than render time, it is possible that the components with lots of renders run hooks like useEffect/useLayoutEffect, which run outside of what we profile (just react render time).\n\nIt's also good to note that react profiles hook times in development, and if many hooks are called (lets say 5,000 components all called a useEffect), it will have to profile every single one, and this can add significant overhead when thousands of effects ran.\n\nIf it's not possible to explain the root problem from this data, please ask me for more data explicitly, and what we would need to know to find the source of the performance problem.\n`;\n  var generateFrameDropDataPrompt = ({\n    renderTime,\n    otherTime,\n    formattedReactData\n  }) => `I will provide you with a set of high level, and low level performance data about a large frame drop in a React App:\n### High level\n- react component render time: ${renderTime.toFixed(0)}ms\n- how long it took to run everything else (other JavaScript, hooks like useEffect, style recalculations, layerization, paint & commit and everything else the browser might do to draw a new frame after javascript mutates the DOM): ${otherTime}ms\n\n### Low level\nWe also have lower level information about react components, such as their render time, and which props/state/context changed when they re-rendered.\n${formattedReactData}`;\n  var generateInteractionExplanationPrompt = ({\n    interactionType,\n    name,\n    time,\n    renderTime,\n    eHandlerTimeExcludingRenders,\n    toRafTime,\n    commitTime,\n    framePresentTime,\n    formattedReactData\n  }) => `Your goal will be to help me find the source of a performance problem. I collected a large dataset about this specific performance problem.\n\nThere was a ${interactionType} on a component named ${name}. This means, roughly, the component that handled the ${interactionType} event was named ${name}.\n\nWe have a set of high level, and low level data about the performance issue.\n\nThe click took ${time.toFixed(0)}ms from interaction start, to when a new frame was presented to a user.\n\nWe also provide you with a breakdown of what the browser spent time on during the period of interaction start to frame presentation.\n\n- react component render time: ${renderTime.toFixed(0)}ms\n- how long it took to run javascript event handlers (EXCLUDING REACT RENDERS): ${eHandlerTimeExcludingRenders.toFixed(0)}ms\n- how long it took from the last event handler time, to the last request animation frame: ${toRafTime.toFixed(0)}ms\n\t- things like prepaint, style recalculations, layerization, async web API's like observers may occur during this time\n- how long it took from the last request animation frame to when the dom was committed: ${commitTime.toFixed(0)}ms\n\t- during this period you will see paint, commit, potential style recalcs, and other misc browser activity. Frequently high times here imply css that makes the browser do a lot of work, or mutating expensive dom properties during the event handler stage. This can be many things, but it narrows the problem scope significantly when this is high\n${framePresentTime === null ? \"\" : `- how long it took from dom commit for the frame to be presented: ${framePresentTime.toFixed(0)}ms. This is when information about how to paint the next frame is sent to the compositor threads, and when the GPU does work. If this is high, look for issues that may be a bottleneck for operations occurring during this time`}\n\nWe also have lower level information about react components, such as their render time, and which props/state/context changed when they re-rendered.\n\n${formattedReactData}\n\n\nYou may notice components have many renders, but much fewer props/state/context changes. This normally implies most of the components could of been memoized to avoid computation\n\nIt's also important to remember if a component had no props/state/context change, and it was memoized, it would not render. So a flow we can go through is:\n- find the most expensive components\n- see what's causing them to render\n- determine how you can make those state/props/context not change for a large set of the renders\n- once there are no more changes left, you can memoize the component so it no longer unnecessarily re-renders. \n\n\nAn important thing to note is that if you see a lot of react renders (some components with very high render counts), but javascript excluding renders is much higher than render time, it is possible that the components with lots of renders run hooks like useEffect/useLayoutEffect, which run during the JS event handler period.\n\nIt's also good to note that react profiles hook times in development, and if many hooks are called (lets say 5,000 components all called a useEffect), it will have to profile every single one. And it may also be the case the comparison of the hooks dependency can be expensive, and that would not be tracked in render time.\n\nIf it's not possible to explain the root problem from this data, please ask me for more data explicitly, and what we would need to know to find the source of the performance problem.\n`;\n  var getLLMPrompt = (activeTab, selectedEvent) => iife(() => {\n    switch (activeTab) {\n      case \"data\": {\n        switch (selectedEvent.kind) {\n          case \"dropped-frames\": {\n            return generateFrameDropDataPrompt({\n              formattedReactData: formatReactData(\n                selectedEvent.groupedFiberRenders\n              ),\n              renderTime: selectedEvent.groupedFiberRenders.reduce(\n                (prev, curr) => prev + curr.totalTime,\n                0\n              ),\n              otherTime: selectedEvent.timing.otherTime\n            });\n          }\n          case \"interaction\": {\n            return generateInteractionDataPrompt({\n              commitTime: selectedEvent.timing.frameConstruction,\n              eHandlerTimeExcludingRenders: selectedEvent.timing.otherJSTime,\n              formattedReactData: formatReactData(\n                selectedEvent.groupedFiberRenders\n              ),\n              framePresentTime: selectedEvent.timing.frameDraw,\n              renderTime: selectedEvent.groupedFiberRenders.reduce(\n                (prev, curr) => prev + curr.totalTime,\n                0\n              ),\n              toRafTime: selectedEvent.timing.framePreparation\n            });\n          }\n        }\n      }\n      case \"explanation\": {\n        switch (selectedEvent.kind) {\n          case \"dropped-frames\": {\n            return generateFrameDropExplanationPrompt({\n              formattedReactData: formatReactData(\n                selectedEvent.groupedFiberRenders\n              ),\n              renderTime: selectedEvent.groupedFiberRenders.reduce(\n                (prev, curr) => prev + curr.totalTime,\n                0\n              ),\n              otherTime: selectedEvent.timing.otherTime\n            });\n          }\n          case \"interaction\": {\n            return generateInteractionExplanationPrompt({\n              commitTime: selectedEvent.timing.frameConstruction,\n              eHandlerTimeExcludingRenders: selectedEvent.timing.otherJSTime,\n              formattedReactData: formatReactData(\n                selectedEvent.groupedFiberRenders\n              ),\n              framePresentTime: selectedEvent.timing.frameDraw,\n              interactionType: selectedEvent.type,\n              name: getComponentName(selectedEvent.componentPath),\n              renderTime: selectedEvent.groupedFiberRenders.reduce(\n                (prev, curr) => prev + curr.totalTime,\n                0\n              ),\n              time: getTotalTime(selectedEvent.timing),\n              toRafTime: selectedEvent.timing.framePreparation\n            });\n          }\n        }\n      }\n      case \"fix\": {\n        switch (selectedEvent.kind) {\n          case \"dropped-frames\": {\n            return generateFrameDropOptimizationPrompt({\n              formattedReactData: formatReactData(\n                selectedEvent.groupedFiberRenders\n              ),\n              renderTime: selectedEvent.groupedFiberRenders.reduce(\n                (prev, curr) => prev + curr.totalTime,\n                0\n              ),\n              otherTime: selectedEvent.timing.otherTime\n            });\n          }\n          case \"interaction\": {\n            return generateInteractionOptimizationPrompt({\n              commitTime: selectedEvent.timing.frameConstruction,\n              componentPath: selectedEvent.componentPath.join(\">\"),\n              eHandlerTimeExcludingRenders: selectedEvent.timing.otherJSTime,\n              formattedReactData: formatReactData(\n                selectedEvent.groupedFiberRenders\n              ),\n              framePresentTime: selectedEvent.timing.frameDraw,\n              interactionType: selectedEvent.type,\n              name: getComponentName(selectedEvent.componentPath),\n              renderTime: selectedEvent.groupedFiberRenders.reduce(\n                (prev, curr) => prev + curr.totalTime,\n                0\n              ),\n              time: getTotalTime(selectedEvent.timing),\n              toRafTime: selectedEvent.timing.framePreparation\n            });\n          }\n        }\n      }\n    }\n  });\n  var Optimize = ({\n    selectedEvent\n  }) => {\n    const [activeTab, setActiveTab] = d2(\n      \"fix\"\n    );\n    const [copying, setCopying] = d2(false);\n    return /* @__PURE__ */ u4(\"div\", { className: cn2([\"w-full h-full\"]), children: [\n      /* @__PURE__ */ u4(\n        \"div\",\n        {\n          className: cn2([\n            \"border border-[#27272A] rounded-sm h-4/5 text-xs overflow-hidden\"\n          ]),\n          children: [\n            /* @__PURE__ */ u4(\"div\", { className: cn2([\"bg-[#18181B] p-1 rounded-t-sm\"]), children: /* @__PURE__ */ u4(\"div\", { className: cn2([\"flex items-center gap-x-1\"]), children: [\n              /* @__PURE__ */ u4(\n                \"button\",\n                {\n                  onClick: () => setActiveTab(\"fix\"),\n                  className: cn2([\n                    \"flex items-center justify-center whitespace-nowrap py-1.5 px-3 rounded-sm\",\n                    activeTab === \"fix\" ? \"text-white bg-[#7521c8]\" : \"text-[#6E6E77] hover:text-white\"\n                  ]),\n                  children: \"Fix\"\n                }\n              ),\n              /* @__PURE__ */ u4(\n                \"button\",\n                {\n                  onClick: () => setActiveTab(\"explanation\"),\n                  className: cn2([\n                    \"flex items-center justify-center whitespace-nowrap py-1.5 px-3 rounded-sm\",\n                    activeTab === \"explanation\" ? \"text-white bg-[#7521c8]\" : \"text-[#6E6E77] hover:text-white\"\n                  ]),\n                  children: \"Explanation\"\n                }\n              ),\n              /* @__PURE__ */ u4(\n                \"button\",\n                {\n                  onClick: () => setActiveTab(\"data\"),\n                  className: cn2([\n                    \"flex items-center justify-center whitespace-nowrap py-1.5 px-3 rounded-sm\",\n                    activeTab === \"data\" ? \"text-white bg-[#7521c8]\" : \"text-[#6E6E77] hover:text-white\"\n                  ]),\n                  children: \"Data\"\n                }\n              )\n            ] }) }),\n            /* @__PURE__ */ u4(\"div\", { className: cn2([\"overflow-y-auto h-full\"]), children: /* @__PURE__ */ u4(\n              \"pre\",\n              {\n                className: cn2([\n                  \"p-2 h-full\",\n                  \"whitespace-pre-wrap break-words\",\n                  \"text-gray-300 font-mono \"\n                ]),\n                children: getLLMPrompt(activeTab, selectedEvent)\n              }\n            ) })\n          ]\n        }\n      ),\n      /* @__PURE__ */ u4(\n        \"button\",\n        {\n          onClick: async () => {\n            const text = getLLMPrompt(activeTab, selectedEvent);\n            await navigator.clipboard.writeText(text);\n            setCopying(true);\n            setTimeout(() => setCopying(false), 1e3);\n          },\n          className: cn2([\n            \"mt-4 px-4 py-2 bg-[#18181B] text-[#6E6E77] rounded-sm\",\n            \"hover:text-white transition-colors duration-200\",\n            \"flex items-center justify-center gap-x-2 text-xs\"\n          ]),\n          children: [\n            /* @__PURE__ */ u4(\"span\", { children: copying ? \"Copied!\" : \"Copy Prompt\" }),\n            /* @__PURE__ */ u4(\n              \"svg\",\n              {\n                xmlns: \"http://www.w3.org/2000/svg\",\n                width: \"16\",\n                height: \"16\",\n                viewBox: \"0 0 24 24\",\n                fill: \"none\",\n                stroke: \"currentColor\",\n                strokeWidth: \"2\",\n                strokeLinecap: \"round\",\n                strokeLinejoin: \"round\",\n                className: cn2([\n                  \"transition-transform duration-200\",\n                  copying && \"scale-110\"\n                ]),\n                children: copying ? /* @__PURE__ */ u4(\"path\", { d: \"M20 6L9 17l-5-5\" }) : /* @__PURE__ */ u4(k, { children: [\n                  /* @__PURE__ */ u4(\"rect\", { width: \"14\", height: \"14\", x: \"8\", y: \"8\", rx: \"2\", ry: \"2\" }),\n                  /* @__PURE__ */ u4(\"path\", { d: \"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\" })\n                ] })\n              }\n            )\n          ]\n        }\n      )\n    ] });\n  };\n\n  // src/web/views/notifications/other-visualization.tsx\n  var getTimeData = (selectedEvent, isProduction2) => {\n    switch (selectedEvent.kind) {\n      // todo: push instead of conditional spread\n      case \"dropped-frames\": {\n        const timeData = [\n          ...isProduction2 ? [\n            {\n              name: \"Total Processing Time\",\n              time: getTotalTime(selectedEvent.timing),\n              color: \"bg-red-500\",\n              kind: \"total-processing-time\"\n            }\n          ] : [\n            {\n              name: \"Renders\",\n              time: selectedEvent.timing.renderTime,\n              color: \"bg-purple-500\",\n              kind: \"render\"\n            },\n            {\n              name: \"JavaScript, DOM updates, Draw Frame\",\n              time: selectedEvent.timing.otherTime,\n              color: \"bg-[#4b4b4b]\",\n              kind: \"other-frame-drop\"\n            }\n          ]\n        ];\n        return timeData;\n      }\n      case \"interaction\": {\n        const timeData = [\n          ...!isProduction2 ? [\n            {\n              name: \"Renders\",\n              time: selectedEvent.timing.renderTime,\n              color: \"bg-purple-500\",\n              kind: \"render\"\n            }\n          ] : [],\n          {\n            name: isProduction2 ? \"React Renders, Hooks, Other JavaScript\" : \"JavaScript/React Hooks \",\n            time: selectedEvent.timing.otherJSTime,\n            color: \"bg-[#EFD81A]\",\n            kind: \"other-javascript\"\n          },\n          {\n            name: \"Update DOM and Draw New Frame\",\n            time: getTotalTime(selectedEvent.timing) - selectedEvent.timing.renderTime - selectedEvent.timing.otherJSTime,\n            color: \"bg-[#1D3A66]\",\n            kind: \"other-not-javascript\"\n          }\n        ];\n        return timeData;\n      }\n    }\n  };\n  var OtherVisualization = ({\n    selectedEvent\n  }) => {\n    const [isProduction2] = d2(getIsProduction() ?? false);\n    const { notificationState } = useNotificationsContext();\n    const [expandedItems, setExpandedItems] = d2(\n      notificationState.routeMessage?.name ? [notificationState.routeMessage.name] : []\n    );\n    const timeData = getTimeData(selectedEvent, isProduction2);\n    const root = x2(ToolbarElementContext);\n    y2(() => {\n      if (notificationState.routeMessage?.name) {\n        const container = root?.querySelector(\"#overview-scroll-container\");\n        const element = root?.querySelector(\n          `#react-scan-overview-bar-${notificationState.routeMessage.name}`\n        );\n        if (container && element) {\n          const elementTop = element.getBoundingClientRect().top;\n          const containerTop = container.getBoundingClientRect().top;\n          const scrollOffset = elementTop - containerTop;\n          container.scrollTop = container.scrollTop + scrollOffset;\n        }\n      }\n    }, [notificationState.route]);\n    y2(() => {\n      if (notificationState.route === \"other-visualization\") {\n        setExpandedItems(\n          (prev) => notificationState.routeMessage?.name ? [notificationState.routeMessage.name] : prev\n        );\n      }\n    }, [notificationState.route]);\n    const totalTime = timeData.reduce((acc, item) => acc + item.time, 0);\n    return /* @__PURE__ */ u4(\"div\", { className: \"rounded-sm border border-zinc-800 text-xs\", children: [\n      /* @__PURE__ */ u4(\"div\", { className: \"p-2 border-b border-zinc-800 bg-zinc-900/50\", children: /* @__PURE__ */ u4(\"div\", { className: \"flex items-center justify-between\", children: [\n        /* @__PURE__ */ u4(\"h3\", { className: \"text-xs font-medium\", children: \"What was time spent on?\" }),\n        /* @__PURE__ */ u4(\"span\", { className: \"text-xs text-zinc-400\", children: [\n          \"Total: \",\n          totalTime.toFixed(0),\n          \"ms\"\n        ] })\n      ] }) }),\n      /* @__PURE__ */ u4(\"div\", { className: \"divide-y divide-zinc-800\", children: timeData.map((entry) => {\n        const isExpanded = expandedItems.includes(entry.kind);\n        return /* @__PURE__ */ u4(\"div\", { id: `react-scan-overview-bar-${entry.kind}`, children: [\n          /* @__PURE__ */ u4(\n            \"button\",\n            {\n              onClick: () => setExpandedItems(\n                (prev) => prev.includes(entry.kind) ? prev.filter((item) => item !== entry.kind) : [...prev, entry.kind]\n              ),\n              className: \"w-full px-3 py-2 flex items-center gap-4 hover:bg-zinc-800/50 transition-colors\",\n              children: /* @__PURE__ */ u4(\"div\", { className: \"flex-1\", children: [\n                /* @__PURE__ */ u4(\"div\", { className: \"flex items-center justify-between mb-2\", children: [\n                  /* @__PURE__ */ u4(\"div\", { className: \"flex items-center gap-0.5\", children: [\n                    /* @__PURE__ */ u4(\n                      \"svg\",\n                      {\n                        className: `h-4 w-4 text-zinc-400 transition-transform ${isExpanded ? \"rotate-90\" : \"\"}`,\n                        fill: \"none\",\n                        stroke: \"currentColor\",\n                        viewBox: \"0 0 24 24\",\n                        children: /* @__PURE__ */ u4(\n                          \"path\",\n                          {\n                            strokeLinecap: \"round\",\n                            strokeLinejoin: \"round\",\n                            strokeWidth: 2,\n                            d: \"M9 5l7 7-7 7\"\n                          }\n                        )\n                      }\n                    ),\n                    /* @__PURE__ */ u4(\"span\", { className: \"font-medium flex items-center text-left\", children: entry.name })\n                  ] }),\n                  /* @__PURE__ */ u4(\"span\", { className: \" text-zinc-400\", children: [\n                    entry.time.toFixed(0),\n                    \"ms\"\n                  ] })\n                ] }),\n                /* @__PURE__ */ u4(\"div\", { className: \"h-1 bg-zinc-800 rounded-full overflow-hidden\", children: /* @__PURE__ */ u4(\n                  \"div\",\n                  {\n                    className: `h-full ${entry.color} transition-all`,\n                    style: {\n                      width: `${entry.time / totalTime * 100}%`\n                    }\n                  }\n                ) })\n              ] })\n            }\n          ),\n          isExpanded && /* @__PURE__ */ u4(\"div\", { className: \"bg-zinc-900/30 border-t border-zinc-800 px-2.5 py-3\", children: /* @__PURE__ */ u4(\"p\", { className: \" text-zinc-400 mb-4 text-xs\", children: iife(() => {\n            switch (selectedEvent.kind) {\n              case \"interaction\": {\n                switch (entry.kind) {\n                  case \"render\": {\n                    return /* @__PURE__ */ u4(\n                      Explanation,\n                      {\n                        input: getRenderInput(selectedEvent)\n                      }\n                    );\n                  }\n                  case \"other-javascript\": {\n                    return /* @__PURE__ */ u4(\n                      Explanation,\n                      {\n                        input: getJSInput(selectedEvent)\n                      }\n                    );\n                  }\n                  case \"other-not-javascript\": {\n                    return /* @__PURE__ */ u4(\n                      Explanation,\n                      {\n                        input: getDrawInput(selectedEvent)\n                      }\n                    );\n                  }\n                }\n              }\n              case \"dropped-frames\": {\n                switch (entry.kind) {\n                  case \"total-processing-time\": {\n                    return /* @__PURE__ */ u4(\n                      Explanation,\n                      {\n                        input: {\n                          kind: \"total-processing\",\n                          data: {\n                            time: getTotalTime(selectedEvent.timing)\n                          }\n                        }\n                      }\n                    );\n                  }\n                  case \"render\": {\n                    return /* @__PURE__ */ u4(k, { children: /* @__PURE__ */ u4(\n                      Explanation,\n                      {\n                        input: {\n                          kind: \"render\",\n                          data: {\n                            topByTime: selectedEvent.groupedFiberRenders.toSorted(\n                              (a4, b3) => b3.totalTime - a4.totalTime\n                            ).slice(0, 3).map((render) => ({\n                              name: render.name,\n                              percentage: render.totalTime / getTotalTime(\n                                selectedEvent.timing\n                              )\n                            }))\n                          }\n                        }\n                      }\n                    ) });\n                  }\n                  case \"other-frame-drop\": {\n                    return /* @__PURE__ */ u4(\n                      Explanation,\n                      {\n                        input: {\n                          kind: \"other\"\n                        }\n                      }\n                    );\n                  }\n                }\n              }\n            }\n          }) }) })\n        ] }, entry.kind);\n      }) })\n    ] });\n  };\n  var getDrawInput = (event) => {\n    const renderCount = event.groupedFiberRenders.reduce(\n      (prev, curr) => prev + curr.count,\n      0\n    );\n    const renderTime = event.timing.renderTime;\n    const totalTime = getTotalTime(event.timing);\n    const renderPercentage = renderTime / totalTime * 100;\n    if (renderCount > 100) {\n      return {\n        kind: \"high-render-count-update-dom-draw-frame\",\n        data: {\n          count: renderCount,\n          percentageOfTotal: renderPercentage,\n          copyButton: /* @__PURE__ */ u4(CopyPromptButton, {})\n        }\n      };\n    }\n    return {\n      kind: \"update-dom-draw-frame\",\n      data: {\n        copyButton: /* @__PURE__ */ u4(CopyPromptButton, {})\n      }\n    };\n  };\n  var CopyPromptButton = () => {\n    const [copying, setCopying] = d2(false);\n    const { notificationState } = useNotificationsContext();\n    return /* @__PURE__ */ u4(\n      \"button\",\n      {\n        onClick: async () => {\n          if (!notificationState.selectedEvent) {\n            return;\n          }\n          await navigator.clipboard.writeText(\n            getLLMPrompt(\"explanation\", notificationState.selectedEvent)\n          );\n          setCopying(true);\n          setTimeout(() => setCopying(false), 1e3);\n        },\n        className: \"bg-zinc-800 flex hover:bg-zinc-700 text-zinc-200 px-2 py-1 rounded gap-x-3\",\n        children: [\n          /* @__PURE__ */ u4(\"span\", { children: copying ? \"Copied!\" : \"Copy Prompt\" }),\n          /* @__PURE__ */ u4(\n            \"svg\",\n            {\n              xmlns: \"http://www.w3.org/2000/svg\",\n              width: \"16\",\n              height: \"16\",\n              viewBox: \"0 0 24 24\",\n              fill: \"none\",\n              stroke: \"currentColor\",\n              strokeWidth: \"2\",\n              strokeLinecap: \"round\",\n              strokeLinejoin: \"round\",\n              className: cn2([\n                \"transition-transform duration-200\",\n                copying && \"scale-110\"\n              ]),\n              children: copying ? /* @__PURE__ */ u4(\"path\", { d: \"M20 6L9 17l-5-5\" }) : /* @__PURE__ */ u4(k, { children: [\n                /* @__PURE__ */ u4(\"rect\", { width: \"14\", height: \"14\", x: \"8\", y: \"8\", rx: \"2\", ry: \"2\" }),\n                /* @__PURE__ */ u4(\"path\", { d: \"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\" })\n              ] })\n            }\n          )\n        ]\n      }\n    );\n  };\n  var getRenderInput = (event) => {\n    if (event.timing.renderTime / getTotalTime(event.timing) > 0.3) {\n      return {\n        kind: \"render\",\n        data: {\n          topByTime: event.groupedFiberRenders.toSorted((a4, b3) => b3.totalTime - a4.totalTime).slice(0, 3).map((e4) => ({\n            percentage: e4.totalTime / getTotalTime(event.timing),\n            name: e4.name\n          }))\n        }\n      };\n    }\n    return {\n      kind: \"other\"\n    };\n  };\n  var getJSInput = (event) => {\n    const renderCount = event.groupedFiberRenders.reduce(\n      (prev, curr) => prev + curr.count,\n      0\n    );\n    if (event.timing.otherJSTime / getTotalTime(event.timing) < 0.2) {\n      return {\n        kind: \"js-explanation-base\"\n      };\n    }\n    if (event.groupedFiberRenders.find((render) => render.count > 200) || event.groupedFiberRenders.reduce((prev, curr) => prev + curr.count, 0) > 500) {\n      return {\n        kind: \"high-render-count-high-js\",\n        data: {\n          renderCount,\n          topByCount: event.groupedFiberRenders.filter((groupedRender) => groupedRender.count > 100).toSorted((a4, b3) => b3.count - a4.count).slice(0, 3)\n        }\n      };\n    }\n    if (event.timing.otherJSTime / getTotalTime(event.timing) > 0.3) {\n      if (event.timing.renderTime > 0.2) {\n        return {\n          kind: \"js-explanation-base\"\n        };\n      }\n      return {\n        kind: \"low-render-count-high-js\",\n        data: {\n          renderCount\n        }\n      };\n    }\n    return {\n      kind: \"js-explanation-base\"\n    };\n  };\n  var Explanation = ({ input }) => {\n    switch (input.kind) {\n      case \"total-processing\": {\n        return /* @__PURE__ */ u4(\n          \"div\",\n          {\n            className: cn2([\n              \"text-[#E4E4E7] text-[10px] leading-6 flex flex-col gap-y-2\"\n            ]),\n            children: [\n              /* @__PURE__ */ u4(\"p\", { children: [\n                \"This is the time it took to draw the entire frame that was presented to the user. To be at 60FPS, this number needs to be \",\n                \"<=16ms\"\n              ] }),\n              /* @__PURE__ */ u4(\"p\", { children: 'To debug the issue, check the \"Ranked\" tab to see if there are significant component renders' }),\n              /* @__PURE__ */ u4(\"p\", { children: \"On a production React build, React Scan can't access the time it took for component to render. To get that information, run React Scan on a development build\" }),\n              /* @__PURE__ */ u4(\"p\", { children: [\n                \"To understand precisely what caused the slowdown while in production, use the \",\n                /* @__PURE__ */ u4(\"strong\", { children: \"Chrome profiler\" }),\n                \" and analyze the function call times.\"\n              ] }),\n              /* @__PURE__ */ u4(\"p\", {})\n            ]\n          }\n        );\n      }\n      case \"render\": {\n        return /* @__PURE__ */ u4(\n          \"div\",\n          {\n            className: cn2([\n              \"text-[#E4E4E7] text-[10px] leading-6 flex flex-col gap-y-2\"\n            ]),\n            children: [\n              /* @__PURE__ */ u4(\"p\", { children: \"This is the time it took React to run components, and internal logic to handle the output of your component.\" }),\n              /* @__PURE__ */ u4(\"div\", { className: cn2([\"flex flex-col\"]), children: [\n                /* @__PURE__ */ u4(\"p\", { children: \"The slowest components for this time period were:\" }),\n                input.data.topByTime.map((item) => /* @__PURE__ */ u4(\"div\", { children: [\n                  /* @__PURE__ */ u4(\"strong\", { children: item.name }),\n                  \":\",\n                  \" \",\n                  (item.percentage * 100).toFixed(0),\n                  \"% of total\"\n                ] }, item.name))\n              ] }),\n              /* @__PURE__ */ u4(\"p\", { children: 'To view the render times of all your components, and what caused them to render, go to the \"Ranked\" tab' }),\n              /* @__PURE__ */ u4(\"p\", { children: 'The \"Ranked\" tab shows the render times of every component.' }),\n              /* @__PURE__ */ u4(\"p\", { children: \"The render times of the same components are grouped together into one bar.\" }),\n              /* @__PURE__ */ u4(\"p\", { children: \"Clicking the component will show you what props, state, or context caused the component to re-render.\" })\n            ]\n          }\n        );\n      }\n      case \"js-explanation-base\": {\n        return /* @__PURE__ */ u4(\n          \"div\",\n          {\n            className: cn2([\n              \"text-[#E4E4E7] text-[10px] leading-6 flex flex-col gap-y-2\"\n            ]),\n            children: [\n              /* @__PURE__ */ u4(\"p\", { children: \"This is the period when JavaScript hooks and other JavaScript outside of React Renders run.\" }),\n              /* @__PURE__ */ u4(\"p\", { children: [\n                \"The most common culprit for high JS time is expensive hooks, like expensive callbacks inside of \",\n                /* @__PURE__ */ u4(\"code\", { children: \"useEffect\" }),\n                \"'s or a large number of useEffect's called, but this can also be JavaScript event handlers (\",\n                /* @__PURE__ */ u4(\"code\", { children: \"'onclick'\" }),\n                \", \",\n                /* @__PURE__ */ u4(\"code\", { children: \"'onchange'\" }),\n                \") that performed expensive computation.\"\n              ] }),\n              /* @__PURE__ */ u4(\"p\", { children: \"If you have lots of components rendering that call hooks, like useEffect, it can add significant overhead even if the callbacks are not expensive. If this is the case, you can try optimizing the renders of those components to avoid the hook from having to run.\" }),\n              /* @__PURE__ */ u4(\"p\", { children: [\n                \"You should profile your app using the\",\n                \" \",\n                /* @__PURE__ */ u4(\"strong\", { children: \"Chrome DevTools profiler\" }),\n                \" to learn exactly which functions took the longest to execute.\"\n              ] })\n            ]\n          }\n        );\n      }\n      case \"high-render-count-high-js\": {\n        return /* @__PURE__ */ u4(\n          \"div\",\n          {\n            className: cn2([\n              \"text-[#E4E4E7] text-[10px] leading-6 flex flex-col gap-y-2\"\n            ]),\n            children: [\n              /* @__PURE__ */ u4(\"p\", { children: \"This is the period when JavaScript hooks and other JavaScript outside of React Renders run.\" }),\n              input.data.renderCount === 0 ? /* @__PURE__ */ u4(k, { children: [\n                /* @__PURE__ */ u4(\"p\", { children: \"There were no renders, which means nothing related to React caused this slowdown. The most likely cause of the slowdown is a slow JavaScript event handler, or code related to a Web API\" }),\n                /* @__PURE__ */ u4(\"p\", { children: [\n                  \"You should try to reproduce the slowdown while profiling your website with the\",\n                  /* @__PURE__ */ u4(\"strong\", { children: \"Chrome DevTools profiler\" }),\n                  \" to see exactly what functions took the longest to execute.\"\n                ] })\n              ] }) : /* @__PURE__ */ u4(k, { children: [\n                \" \",\n                /* @__PURE__ */ u4(\"p\", { children: [\n                  \"There were \",\n                  /* @__PURE__ */ u4(\"strong\", { children: input.data.renderCount }),\n                  \" renders, which could have contributed to the high JavaScript/Hook time if they ran lots of hooks, like \",\n                  /* @__PURE__ */ u4(\"code\", { children: \"useEffects\" }),\n                  \".\"\n                ] }),\n                /* @__PURE__ */ u4(\"div\", { className: cn2([\"flex flex-col\"]), children: [\n                  /* @__PURE__ */ u4(\"p\", { children: \"You should try optimizing the renders of:\" }),\n                  input.data.topByCount.map((item) => /* @__PURE__ */ u4(\"div\", { children: [\n                    \"- \",\n                    /* @__PURE__ */ u4(\"strong\", { children: item.name }),\n                    \" (rendered \",\n                    item.count,\n                    \"x)\"\n                  ] }, item.name))\n                ] }),\n                \"and then checking if the problem still exists.\",\n                /* @__PURE__ */ u4(\"p\", { children: [\n                  \"You can also try profiling your app using the\",\n                  \" \",\n                  /* @__PURE__ */ u4(\"strong\", { children: \"Chrome DevTools profiler\" }),\n                  \" to see exactly what functions took the longest to execute.\"\n                ] })\n              ] })\n            ]\n          }\n        );\n      }\n      case \"low-render-count-high-js\": {\n        return /* @__PURE__ */ u4(\n          \"div\",\n          {\n            className: cn2([\n              \"text-[#E4E4E7] text-[10px] leading-6 flex flex-col gap-y-2\"\n            ]),\n            children: [\n              /* @__PURE__ */ u4(\"p\", { children: \"This is the period when JavaScript hooks and other JavaScript outside of React Renders run.\" }),\n              /* @__PURE__ */ u4(\"p\", { children: [\n                \"There were only \",\n                /* @__PURE__ */ u4(\"strong\", { children: input.data.renderCount }),\n                \" renders detected, which means either you had very expensive hooks like\",\n                \" \",\n                /* @__PURE__ */ u4(\"code\", { children: \"useEffect\" }),\n                \"/\",\n                /* @__PURE__ */ u4(\"code\", { children: \"useLayoutEffect\" }),\n                \", or there is other JavaScript running during this interaction that took up the majority of the time.\"\n              ] }),\n              /* @__PURE__ */ u4(\"p\", { children: [\n                \"To understand precisely what caused the slowdown, use the\",\n                \" \",\n                /* @__PURE__ */ u4(\"strong\", { children: \"Chrome profiler\" }),\n                \" and analyze the function call times.\"\n              ] })\n            ]\n          }\n        );\n      }\n      case \"high-render-count-update-dom-draw-frame\": {\n        return /* @__PURE__ */ u4(\n          \"div\",\n          {\n            className: cn2([\n              \"text-[#E4E4E7] text-[10px] leading-6 flex flex-col gap-y-2\"\n            ]),\n            children: [\n              /* @__PURE__ */ u4(\"p\", { children: \"These are the calculations the browser is forced to do in response to the JavaScript that ran during the interaction.\" }),\n              /* @__PURE__ */ u4(\"p\", { children: \"This can be caused by CSS updates/CSS recalculations, or new DOM elements/DOM mutations.\" }),\n              /* @__PURE__ */ u4(\"p\", { children: [\n                \"During this interaction, there were\",\n                \" \",\n                /* @__PURE__ */ u4(\"strong\", { children: input.data.count }),\n                \" renders, which was\",\n                \" \",\n                /* @__PURE__ */ u4(\"strong\", { children: [\n                  input.data.percentageOfTotal.toFixed(0),\n                  \"%\"\n                ] }),\n                \" of the time spent processing\"\n              ] }),\n              /* @__PURE__ */ u4(\"p\", { children: \"The work performed as a result of the renders may have forced the browser to spend a lot of time to draw the next frame.\" }),\n              /* @__PURE__ */ u4(\"p\", { children: 'You can try optimizing the renders to see if the performance problem still exists using the \"Ranked\" tab.' }),\n              /* @__PURE__ */ u4(\"p\", { children: \"If you use an AI-based code editor, you can export the performance data collected as a prompt.\" }),\n              /* @__PURE__ */ u4(\"p\", { children: input.data.copyButton }),\n              /* @__PURE__ */ u4(\"p\", { children: \"Provide this formatted data to the model and ask it to find, or fix, what could be causing this performance problem.\" }),\n              /* @__PURE__ */ u4(\"p\", { children: 'For a larger selection of prompts, try the \"Prompts\" tab' })\n            ]\n          }\n        );\n      }\n      case \"update-dom-draw-frame\": {\n        return /* @__PURE__ */ u4(\n          \"div\",\n          {\n            className: cn2([\n              \"text-[#E4E4E7] text-[10px] leading-6 flex flex-col gap-y-2\"\n            ]),\n            children: [\n              /* @__PURE__ */ u4(\"p\", { children: \"These are the calculations the browser is forced to do in response to the JavaScript that ran during the interaction.\" }),\n              /* @__PURE__ */ u4(\"p\", { children: \"This can be caused by CSS updates/CSS recalculations, or new DOM elements/DOM mutations.\" }),\n              /* @__PURE__ */ u4(\"p\", { children: \"If you use an AI-based code editor, you can export the performance data collected as a prompt.\" }),\n              /* @__PURE__ */ u4(\"p\", { children: input.data.copyButton }),\n              /* @__PURE__ */ u4(\"p\", { children: \"Provide this formatted data to the model and ask it to find, or fix, what could be causing this performance problem.\" }),\n              /* @__PURE__ */ u4(\"p\", { children: 'For a larger selection of prompts, try the \"Prompts\" tab' })\n            ]\n          }\n        );\n      }\n      case \"other\": {\n        return /* @__PURE__ */ u4(\n          \"div\",\n          {\n            className: cn2([\n              \"text-[#E4E4E7] text-[10px] leading-6 flex flex-col gap-y-2\"\n            ]),\n            children: [\n              /* @__PURE__ */ u4(\"p\", { children: [\n                \"This is the time it took to run everything other than React renders. This can be hooks like \",\n                /* @__PURE__ */ u4(\"code\", { children: \"useEffect\" }),\n                \", other JavaScript not part of React, or work the browser has to do to update the DOM and draw the next frame.\"\n              ] }),\n              /* @__PURE__ */ u4(\"p\", { children: [\n                \"To get a better picture of what happened, profile your app using the\",\n                \" \",\n                /* @__PURE__ */ u4(\"strong\", { children: \"Chrome profiler\" }),\n                \" when the performance problem arises.\"\n              ] })\n            ]\n          }\n        );\n      }\n    }\n  };\n\n  // src/core/notifications/outline-overlay.ts\n  var highlightCanvas = null;\n  var highlightCtx = null;\n  var HighlightStore = d3({\n    kind: \"idle\",\n    current: null\n  });\n  var currFrame = null;\n  var lastFrameTime = 0;\n  var FADE_SPEED = 1.8;\n  var MAX_DELTA = 0.05;\n  var DEFAULT_DELTA = 1 / 60;\n  var drawHighlights = () => {\n    if (currFrame) {\n      cancelAnimationFrame(currFrame);\n    }\n    currFrame = requestAnimationFrame((timestamp) => {\n      if (!highlightCanvas || !highlightCtx) {\n        return;\n      }\n      const dt = lastFrameTime ? Math.min((timestamp - lastFrameTime) / 1e3, MAX_DELTA) : DEFAULT_DELTA;\n      lastFrameTime = timestamp;\n      const step = FADE_SPEED * dt;\n      highlightCtx.clearRect(0, 0, highlightCanvas.width, highlightCanvas.height);\n      const color = \"hsl(271, 76%, 53%)\";\n      const state = HighlightStore.value;\n      const { alpha, current } = iife(() => {\n        switch (state.kind) {\n          case \"transition\": {\n            const current2 = state.current?.alpha && state.current.alpha > 0 ? state.current : state.transitionTo;\n            return {\n              alpha: current2 ? current2.alpha : 0,\n              current: current2\n            };\n          }\n          case \"move-out\": {\n            return { alpha: state.current?.alpha ?? 0, current: state.current };\n          }\n          case \"idle\": {\n            return { alpha: 1, current: state.current };\n          }\n        }\n      });\n      current?.rects.forEach((rect) => {\n        if (!highlightCtx) {\n          return;\n        }\n        highlightCtx.shadowColor = color;\n        highlightCtx.shadowBlur = 6;\n        highlightCtx.strokeStyle = color;\n        highlightCtx.lineWidth = 2;\n        highlightCtx.globalAlpha = alpha;\n        highlightCtx.beginPath();\n        highlightCtx.rect(rect.left, rect.top, rect.width, rect.height);\n        highlightCtx.stroke();\n        highlightCtx.shadowBlur = 0;\n        highlightCtx.beginPath();\n        highlightCtx.rect(rect.left, rect.top, rect.width, rect.height);\n        highlightCtx.stroke();\n      });\n      switch (state.kind) {\n        case \"move-out\": {\n          if (state.current.alpha === 0) {\n            HighlightStore.value = {\n              kind: \"idle\",\n              current: null\n            };\n            lastFrameTime = 0;\n            return;\n          }\n          if (state.current.alpha <= 0.01) {\n            state.current.alpha = 0;\n          }\n          state.current.alpha = Math.max(0, state.current.alpha - step);\n          drawHighlights();\n          return;\n        }\n        case \"transition\": {\n          if (state.current && state.current.alpha > 0) {\n            state.current.alpha = Math.max(0, state.current.alpha - step);\n            drawHighlights();\n            return;\n          }\n          if (state.transitionTo.alpha === 1) {\n            HighlightStore.value = {\n              kind: \"idle\",\n              current: state.transitionTo\n            };\n            lastFrameTime = 0;\n            return;\n          }\n          state.transitionTo.alpha = Math.min(state.transitionTo.alpha + step, 1);\n          drawHighlights();\n        }\n        case \"idle\": {\n          lastFrameTime = 0;\n          return;\n        }\n      }\n    });\n  };\n  var handleResizeListener = null;\n  var createHighlightCanvas = (root) => {\n    highlightCanvas = document.createElement(\"canvas\");\n    highlightCtx = highlightCanvas.getContext(\"2d\", { alpha: true });\n    if (!highlightCtx) return null;\n    const dpr2 = window.devicePixelRatio || 1;\n    const { innerWidth, innerHeight } = window;\n    highlightCanvas.style.width = `${innerWidth}px`;\n    highlightCanvas.style.height = `${innerHeight}px`;\n    highlightCanvas.width = innerWidth * dpr2;\n    highlightCanvas.height = innerHeight * dpr2;\n    highlightCanvas.style.position = \"fixed\";\n    highlightCanvas.style.left = \"0\";\n    highlightCanvas.style.top = \"0\";\n    highlightCanvas.style.pointerEvents = \"none\";\n    highlightCanvas.style.zIndex = \"2147483600\";\n    highlightCtx.scale(dpr2, dpr2);\n    root.appendChild(highlightCanvas);\n    if (handleResizeListener) {\n      window.removeEventListener(\"resize\", handleResizeListener);\n    }\n    const handleResize = () => {\n      if (!highlightCanvas || !highlightCtx) return;\n      const dpr3 = window.devicePixelRatio || 1;\n      const { innerWidth: innerWidth2, innerHeight: innerHeight2 } = window;\n      highlightCanvas.style.width = `${innerWidth2}px`;\n      highlightCanvas.style.height = `${innerHeight2}px`;\n      highlightCanvas.width = innerWidth2 * dpr3;\n      highlightCanvas.height = innerHeight2 * dpr3;\n      highlightCtx.scale(dpr3, dpr3);\n      drawHighlights();\n    };\n    handleResizeListener = handleResize;\n    window.addEventListener(\"resize\", handleResize);\n    HighlightStore.subscribe(() => {\n      requestAnimationFrame(() => {\n        drawHighlights();\n      });\n    });\n    return cleanup2;\n  };\n  function cleanup2() {\n    if (highlightCanvas?.parentNode) {\n      highlightCanvas.parentNode.removeChild(highlightCanvas);\n    }\n    highlightCanvas = null;\n    highlightCtx = null;\n  }\n\n  // src/web/views/notifications/render-bar-chart.tsx\n  var fadeOutHighlights = () => {\n    const curr = HighlightStore.value.current ? HighlightStore.value.current : HighlightStore.value.kind === \"transition\" ? HighlightStore.value.transitionTo : null;\n    if (!curr) {\n      return;\n    }\n    if (HighlightStore.value.kind === \"transition\") {\n      HighlightStore.value = {\n        kind: \"move-out\",\n        // because we want to dynamically fade this value\n        current: HighlightStore.value.current?.alpha === 0 ? (\n          // we want to only start fading from transition if current is done animating out\n          HighlightStore.value.transitionTo\n        ) : (\n          // if current doesn't exist then transition must exist\n          HighlightStore.value.current ?? HighlightStore.value.transitionTo\n        )\n      };\n      return;\n    }\n    HighlightStore.value = {\n      kind: \"move-out\",\n      current: {\n        alpha: 0,\n        ...curr\n      }\n    };\n  };\n  var RenderBarChart = ({\n    selectedEvent\n  }) => {\n    const totalInteractionTime = getTotalTime(selectedEvent.timing);\n    const nonRender = totalInteractionTime - selectedEvent.timing.renderTime;\n    const [isProduction2] = d2(getIsProduction());\n    const events = selectedEvent.groupedFiberRenders;\n    const bars = events.map((event) => ({\n      event,\n      kind: \"render\",\n      totalTime: isProduction2 ? event.count : event.totalTime\n    }));\n    const isShowingExtraInfo = iife(() => {\n      switch (selectedEvent.kind) {\n        case \"dropped-frames\": {\n          return selectedEvent.timing.renderTime / totalInteractionTime < 0.1;\n        }\n        case \"interaction\": {\n          return (selectedEvent.timing.otherJSTime + selectedEvent.timing.renderTime) / totalInteractionTime < 0.2;\n        }\n      }\n    });\n    if (selectedEvent.kind === \"interaction\" && !isProduction2) {\n      bars.push({\n        kind: \"other-javascript\",\n        totalTime: selectedEvent.timing.otherJSTime\n      });\n    }\n    if (isShowingExtraInfo && !isProduction2) {\n      if (selectedEvent.kind === \"interaction\") {\n        bars.push({\n          kind: \"other-not-javascript\",\n          totalTime: getTotalTime(selectedEvent.timing) - selectedEvent.timing.renderTime - selectedEvent.timing.otherJSTime\n        });\n      } else {\n        bars.push({\n          kind: \"other-frame-drop\",\n          totalTime: nonRender\n        });\n      }\n    }\n    const debouncedMouseEnter = A2({\n      lastCallAt: null,\n      timer: null\n    });\n    const totalBarTime = bars.reduce((prev, curr) => prev + curr.totalTime, 0);\n    return /* @__PURE__ */ u4(\"div\", { className: cn2([\"flex flex-col h-full w-full gap-y-1\"]), children: [\n      iife(() => {\n        if (isProduction2 && bars.length === 0) {\n          return /* @__PURE__ */ u4(\"div\", { className: \"flex flex-col items-center justify-center h-full text-zinc-400\", children: [\n            /* @__PURE__ */ u4(\"p\", { className: \"text-sm w-full text-left text-white mb-1.5\", children: \"No data available\" }),\n            /* @__PURE__ */ u4(\"p\", { className: \"text-x w-full text-lefts\", children: \"No data was collected during this period\" })\n          ] });\n        }\n        if (bars.length === 0) {\n          return /* @__PURE__ */ u4(\"div\", { className: \"flex flex-col items-center justify-center h-full text-zinc-400\", children: [\n            /* @__PURE__ */ u4(\"p\", { className: \"text-sm w-full text-left text-white mb-1.5\", children: \"No renders collected\" }),\n            /* @__PURE__ */ u4(\"p\", { className: \"text-x w-full text-lefts\", children: \"There were no renders during this period\" })\n          ] });\n        }\n      }),\n      bars.toSorted((a4, b3) => b3.totalTime - a4.totalTime).map((bar) => /* @__PURE__ */ u4(\n        RenderBar,\n        {\n          bars,\n          bar,\n          debouncedMouseEnter,\n          totalBarTime,\n          isProduction: isProduction2\n        },\n        bar.kind === \"render\" ? bar.event.id : bar.kind\n      ))\n    ] });\n  };\n  var getTransitionState = (state) => {\n    if (!state.current) {\n      return \"fading-in\";\n    }\n    if (state.current.alpha > 0) {\n      return \"fading-out\";\n    }\n    return \"fading-in\";\n  };\n  var RenderBar = ({\n    bar,\n    debouncedMouseEnter,\n    totalBarTime,\n    isProduction: isProduction2,\n    bars,\n    depth = 0\n  }) => {\n    const { setNotificationState, setRoute } = useNotificationsContext();\n    const [isExpanded, setIsExpanded] = d2(false);\n    const isLeaf = bar.kind === \"render\" ? bar.event.parents.size === 0 : true;\n    const parentBars = bars.filter(\n      (otherBar) => otherBar.kind === \"render\" && bar.kind === \"render\" ? bar.event.parents.has(otherBar.event.name) && otherBar.event.name !== bar.event.name : false\n    );\n    const missingParentNames = bar.kind === \"render\" ? Array.from(bar.event.parents).filter(\n      (parentName) => !bars.some(\n        (b3) => b3.kind === \"render\" && b3.event.name === parentName\n      )\n    ) : [];\n    const handleBarClick = () => {\n      if (bar.kind === \"render\") {\n        setNotificationState((prev) => ({\n          ...prev,\n          selectedFiber: bar.event\n        }));\n        setRoute({\n          route: \"render-explanation\",\n          routeMessage: null\n        });\n      } else {\n        setRoute({\n          route: \"other-visualization\",\n          routeMessage: {\n            kind: \"auto-open-overview-accordion\",\n            name: bar.kind\n          }\n        });\n      }\n    };\n    return /* @__PURE__ */ u4(\"div\", { className: \"w-full\", children: [\n      /* @__PURE__ */ u4(\n        \"div\",\n        {\n          className: cn2([\"w-full flex items-center relative text-xs min-w-0\"]),\n          children: [\n            /* @__PURE__ */ u4(\n              \"button\",\n              {\n                onMouseLeave: () => {\n                  debouncedMouseEnter.current.timer && clearTimeout(debouncedMouseEnter.current.timer);\n                  fadeOutHighlights();\n                },\n                onMouseEnter: async () => {\n                  const highlightBars = async () => {\n                    debouncedMouseEnter.current.lastCallAt = Date.now();\n                    if (bar.kind !== \"render\") {\n                      const curr = HighlightStore.value.current ? HighlightStore.value.current : HighlightStore.value.kind === \"transition\" ? HighlightStore.value.transitionTo : null;\n                      if (!curr) {\n                        HighlightStore.value = {\n                          kind: \"idle\",\n                          current: null\n                        };\n                        return;\n                      }\n                      HighlightStore.value = {\n                        kind: \"move-out\",\n                        current: {\n                          alpha: 0,\n                          ...curr\n                        }\n                      };\n                      return;\n                    }\n                    const state = HighlightStore.value;\n                    const currentState = iife(() => {\n                      switch (state.kind) {\n                        case \"transition\": {\n                          return state.transitionTo;\n                        }\n                        case \"idle\":\n                        case \"move-out\": {\n                          return state.current;\n                        }\n                      }\n                    });\n                    const stateRects = [];\n                    if (state.kind === \"transition\") {\n                      const transitionState = getTransitionState(state);\n                      iife(() => {\n                        switch (transitionState) {\n                          case \"fading-in\": {\n                            HighlightStore.value = {\n                              kind: \"transition\",\n                              current: state.transitionTo,\n                              transitionTo: {\n                                rects: stateRects,\n                                alpha: 0,\n                                name: bar.event.name\n                              }\n                            };\n                            return;\n                          }\n                          case \"fading-out\": {\n                            HighlightStore.value = {\n                              kind: \"transition\",\n                              current: HighlightStore.value.current ? {\n                                alpha: 0,\n                                ...HighlightStore.value.current\n                              } : null,\n                              transitionTo: {\n                                rects: stateRects,\n                                alpha: 0,\n                                name: bar.event.name\n                              }\n                            };\n                            return;\n                          }\n                        }\n                      });\n                    } else {\n                      HighlightStore.value = {\n                        kind: \"transition\",\n                        transitionTo: {\n                          rects: stateRects,\n                          alpha: 0,\n                          name: bar.event.name\n                        },\n                        current: currentState ? {\n                          alpha: 0,\n                          ...currentState\n                        } : null\n                      };\n                    }\n                    const trueElements = bar.event.elements.filter(\n                      (element) => element instanceof Element\n                    );\n                    for await (const entries of getBatchedRectMap(trueElements)) {\n                      entries.forEach(({ boundingClientRect }) => {\n                        stateRects.push(boundingClientRect);\n                      });\n                      drawHighlights();\n                    }\n                  };\n                  if (debouncedMouseEnter.current.lastCallAt && Date.now() - debouncedMouseEnter.current.lastCallAt < 200) {\n                    debouncedMouseEnter.current.timer && clearTimeout(debouncedMouseEnter.current.timer);\n                    debouncedMouseEnter.current.timer = setTimeout(() => {\n                      highlightBars();\n                    }, 200);\n                    return;\n                  }\n                  highlightBars();\n                },\n                onClick: handleBarClick,\n                className: cn2([\n                  \"h-full w-[90%] flex items-center hover:bg-[#0f0f0f] rounded-l-md min-w-0 relative\"\n                ]),\n                children: [\n                  /* @__PURE__ */ u4(\n                    \"div\",\n                    {\n                      style: {\n                        minWidth: \"fit-content\",\n                        width: `${bar.totalTime / totalBarTime * 100}%`\n                      },\n                      className: cn2([\n                        \"flex items-center rounded-sm text-white text-xs h-[28px] shrink-0\",\n                        bar.kind === \"render\" && \"bg-[#412162] group-hover:bg-[#5b2d89]\",\n                        bar.kind === \"other-frame-drop\" && \"bg-[#44444a] group-hover:bg-[#6a6a6a]\",\n                        bar.kind === \"other-javascript\" && \"bg-[#efd81a6b] group-hover:bg-[#efda1a2f]\",\n                        bar.kind === \"other-not-javascript\" && \"bg-[#214379d4] group-hover:bg-[#21437982]\"\n                      ])\n                    }\n                  ),\n                  /* @__PURE__ */ u4(\n                    \"div\",\n                    {\n                      className: cn2([\n                        \"absolute inset-0 flex items-center px-2\",\n                        \"min-w-0\"\n                      ]),\n                      children: /* @__PURE__ */ u4(\"div\", { className: \"flex items-center gap-x-2 min-w-0 w-full\", children: [\n                        /* @__PURE__ */ u4(\"span\", { className: cn2([\"truncate\"]), children: iife(() => {\n                          switch (bar.kind) {\n                            case \"other-frame-drop\": {\n                              return \"JavaScript, DOM updates, Draw Frame\";\n                            }\n                            case \"other-javascript\": {\n                              return \"JavaScript/React Hooks\";\n                            }\n                            case \"other-not-javascript\": {\n                              return \"Update DOM and Draw New Frame\";\n                            }\n                            case \"render\": {\n                              return bar.event.name;\n                            }\n                          }\n                        }) }),\n                        bar.kind === \"render\" && isRenderMemoizable(bar.event) && /* @__PURE__ */ u4(\n                          \"div\",\n                          {\n                            style: {\n                              lineHeight: \"10px\"\n                            },\n                            className: cn2([\n                              \"px-1 py-0.5 bg-[#6a369e] flex items-center rounded-sm font-semibold text-[8px] shrink-0\"\n                            ]),\n                            children: \"Memoizable\"\n                          }\n                        )\n                      ] })\n                    }\n                  )\n                ]\n              }\n            ),\n            /* @__PURE__ */ u4(\n              \"button\",\n              {\n                onClick: () => bar.kind === \"render\" && !isLeaf && setIsExpanded(!isExpanded),\n                className: cn2([\n                  \"flex items-center min-w-fit shrink-0 rounded-r-md h-[28px]\",\n                  !isLeaf && \"hover:bg-[#0f0f0f]\",\n                  bar.kind === \"render\" && !isLeaf ? \"cursor-pointer\" : \"cursor-default\"\n                ]),\n                children: [\n                  /* @__PURE__ */ u4(\"div\", { className: \"w-[20px] flex items-center justify-center\", children: bar.kind === \"render\" && !isLeaf && /* @__PURE__ */ u4(\n                    ChevronRight,\n                    {\n                      className: cn2(\n                        \"transition-transform\",\n                        isExpanded && \"rotate-90\"\n                      ),\n                      size: 16\n                    }\n                  ) }),\n                  /* @__PURE__ */ u4(\n                    \"div\",\n                    {\n                      style: {\n                        minWidth: isLeaf ? \"fit-content\" : isProduction2 ? \"30px\" : \"60px\"\n                      },\n                      className: \"flex items-center justify-end gap-x-1\",\n                      children: [\n                        bar.kind === \"render\" && /* @__PURE__ */ u4(\"span\", { className: cn2([\"text-[10px]\"]), children: [\n                          \"x\",\n                          bar.event.count\n                        ] }),\n                        (bar.kind !== \"render\" || !isProduction2) && /* @__PURE__ */ u4(\"span\", { className: \"text-[10px] text-[#7346a0] pr-1\", children: [\n                          bar.totalTime < 1 ? \"<1\" : bar.totalTime.toFixed(0),\n                          \"ms\"\n                        ] })\n                      ]\n                    }\n                  )\n                ]\n              }\n            ),\n            depth === 0 && /* @__PURE__ */ u4(\n              \"div\",\n              {\n                className: cn2([\n                  \"absolute right-0 top-1/2 transition-none -translate-y-1/2 bg-white text-black px-2 py-1 rounded text-xs opacity-0 group-hover:opacity-100 transition-opacity mr-16\",\n                  \"pointer-events-none\"\n                ]),\n                children: \"Click to learn more\"\n              }\n            )\n          ]\n        }\n      ),\n      isExpanded && (parentBars.length > 0 || missingParentNames.length > 0) && /* @__PURE__ */ u4(\"div\", { className: \"pl-3 flex flex-col gap-y-1 mt-1\", children: [\n        parentBars.toSorted((a4, b3) => b3.totalTime - a4.totalTime).map((parentBar, i5) => /* @__PURE__ */ u4(\n          RenderBar,\n          {\n            depth: depth + 1,\n            bar: parentBar,\n            debouncedMouseEnter,\n            totalBarTime,\n            isProduction: isProduction2,\n            bars\n          },\n          i5\n        )),\n        missingParentNames.map((parentName) => /* @__PURE__ */ u4(\"div\", { className: \"w-full\", children: /* @__PURE__ */ u4(\"div\", { className: \"w-full flex items-center relative text-xs\", children: /* @__PURE__ */ u4(\"div\", { className: \"h-full w-full flex items-center relative\", children: [\n          /* @__PURE__ */ u4(\"div\", { className: \"flex items-center rounded-sm text-white text-xs h-[28px] w-full\" }),\n          /* @__PURE__ */ u4(\"div\", { className: \"absolute inset-0 flex items-center px-2\", children: /* @__PURE__ */ u4(\"span\", { className: \"truncate whitespace-nowrap text-white/70 w-full\", children: parentName }) })\n        ] }) }) }, parentName))\n      ] })\n    ] });\n  };\n\n  // src/web/views/notifications/render-explanation.tsx\n  var RenderExplanation = ({\n    selectedEvent: _4,\n    selectedFiber\n  }) => {\n    const { setRoute } = useNotificationsContext();\n    const [tipisShown, setTipIsShown] = d2(true);\n    const [isProduction2] = d2(getIsProduction());\n    _2(() => {\n      const res = localStorage.getItem(\"react-scan-tip-shown\");\n      const asBool = res === \"true\" ? true : res === \"false\" ? false : null;\n      if (asBool === null) {\n        setTipIsShown(true);\n        localStorage.setItem(\"react-scan-tip-is-shown\", \"true\");\n        return;\n      }\n      if (!asBool) {\n        setTipIsShown(false);\n      }\n    }, []);\n    const isMemoizable = selectedFiber.changes.context.length === 0 && selectedFiber.changes.props.length === 0 && selectedFiber.changes.state.length === 0;\n    return /* @__PURE__ */ u4(\n      \"div\",\n      {\n        className: cn2([\n          \"w-full min-h-fit h-full flex flex-col py-4 pt-0 rounded-sm\"\n        ]),\n        children: [\n          /* @__PURE__ */ u4(\"div\", { className: cn2([\"flex items-start gap-x-4 \"]), children: [\n            /* @__PURE__ */ u4(\n              \"button\",\n              {\n                onClick: () => {\n                  setRoute({\n                    route: \"render-visualization\",\n                    routeMessage: null\n                  });\n                },\n                className: cn2([\n                  \"text-white hover:bg-[#34343b] flex gap-x-1 justify-center items-center mb-4 w-fit px-2.5 py-1.5 text-xs rounded-sm bg-[#18181B]\"\n                ]),\n                children: [\n                  /* @__PURE__ */ u4(ArrowLeft, { size: 14 }),\n                  \" \",\n                  /* @__PURE__ */ u4(\"span\", { children: \"Overview\" })\n                ]\n              }\n            ),\n            /* @__PURE__ */ u4(\"div\", { className: cn2([\"flex flex-col gap-y-1\"]), children: [\n              /* @__PURE__ */ u4(\n                \"div\",\n                {\n                  className: cn2([\"text-sm font-bold text-white overflow-x-hidden\"]),\n                  children: /* @__PURE__ */ u4(\"div\", { className: \"flex items-center gap-x-2 truncate\", children: selectedFiber.name })\n                }\n              ),\n              /* @__PURE__ */ u4(\"div\", { className: cn2([\"flex gap-x-2\"]), children: [\n                !isProduction2 && /* @__PURE__ */ u4(k, { children: /* @__PURE__ */ u4(\"div\", { className: cn2([\"text-xs text-gray-400\"]), children: [\n                  \"\\u2022 Render time: \",\n                  selectedFiber.totalTime.toFixed(0),\n                  \"ms\"\n                ] }) }),\n                /* @__PURE__ */ u4(\"div\", { className: cn2([\"text-xs text-gray-400 mb-4\"]), children: [\n                  \"\\u2022 Renders: \",\n                  selectedFiber.count,\n                  \"x\"\n                ] })\n              ] })\n            ] })\n          ] }),\n          tipisShown && !isMemoizable && /* @__PURE__ */ u4(\n            \"div\",\n            {\n              className: cn2([\n                \"w-full mb-4 bg-[#0A0A0A] border border-[#27272A] rounded-sm overflow-hidden flex relative\"\n              ]),\n              children: [\n                /* @__PURE__ */ u4(\n                  \"button\",\n                  {\n                    onClick: () => {\n                      setTipIsShown(false);\n                      localStorage.setItem(\"react-scan-tip-shown\", \"false\");\n                    },\n                    className: cn2([\n                      \"absolute right-2 top-2 rounded-sm p-1 hover:bg-[#18181B]\"\n                    ]),\n                    children: /* @__PURE__ */ u4(CloseIcon, { size: 12 })\n                  }\n                ),\n                /* @__PURE__ */ u4(\"div\", { className: cn2([\"w-1 bg-[#d36cff]\"]) }),\n                /* @__PURE__ */ u4(\"div\", { className: cn2([\"flex-1\"]), children: [\n                  /* @__PURE__ */ u4(\n                    \"div\",\n                    {\n                      className: cn2([\"px-3 py-2 text-gray-100 text-xs font-semibold\"]),\n                      children: \"How to stop renders\"\n                    }\n                  ),\n                  /* @__PURE__ */ u4(\"div\", { className: cn2([\"px-3 pb-2 text-gray-400 text-[10px]\"]), children: \"Stop the following props, state and context from changing between renders, and wrap the component in React.memo if not already\" })\n                ] })\n              ]\n            }\n          ),\n          isMemoizable && /* @__PURE__ */ u4(\n            \"div\",\n            {\n              className: cn2([\n                \"w-full mb-4 bg-[#0A0A0A] border border-[#27272A] rounded-sm overflow-hidden flex\"\n              ]),\n              children: [\n                /* @__PURE__ */ u4(\"div\", { className: cn2([\"w-1 bg-[#d36cff]\"]) }),\n                /* @__PURE__ */ u4(\"div\", { className: cn2([\"flex-1\"]), children: [\n                  /* @__PURE__ */ u4(\n                    \"div\",\n                    {\n                      className: cn2([\"px-3 py-2 text-gray-100 text-sm font-semibold\"]),\n                      children: \"No changes detected\"\n                    }\n                  ),\n                  /* @__PURE__ */ u4(\"div\", { className: cn2([\"px-3 pb-2 text-gray-400 text-xs\"]), children: \"This component would not have rendered if it was memoized\" })\n                ] })\n              ]\n            }\n          ),\n          /* @__PURE__ */ u4(\"div\", { className: cn2([\"flex w-full\"]), children: [\n            /* @__PURE__ */ u4(\n              \"div\",\n              {\n                className: cn2([\n                  \"flex flex-col border border-[#27272A] rounded-l-sm overflow-hidden w-1/3\"\n                ]),\n                children: [\n                  /* @__PURE__ */ u4(\n                    \"div\",\n                    {\n                      className: cn2([\n                        \"text-[14px] font-semibold px-2 py-2 bg-[#18181B] text-white flex justify-center\"\n                      ]),\n                      children: \"Changed Props\"\n                    }\n                  ),\n                  selectedFiber.changes.props.length > 0 ? selectedFiber.changes.props.toSorted((a4, b3) => b3.count - a4.count).map((change) => /* @__PURE__ */ u4(\n                    \"div\",\n                    {\n                      className: cn2([\n                        \"flex flex-col justify-between items-center border-t overflow-x-auto border-[#27272A] px-1 py-1 text-wrap bg-[#0A0A0A] text-[10px]\"\n                      ]),\n                      children: [\n                        /* @__PURE__ */ u4(\"span\", { className: cn2([\"text-white \"]), children: change.name }),\n                        /* @__PURE__ */ u4(\n                          \"div\",\n                          {\n                            className: cn2([\" text-[8px]  text-[#d36cff] pl-1 py-1 \"]),\n                            children: [\n                              change.count,\n                              \"/\",\n                              selectedFiber.count,\n                              \"x\"\n                            ]\n                          }\n                        )\n                      ]\n                    },\n                    change.name\n                  )) : /* @__PURE__ */ u4(\n                    \"div\",\n                    {\n                      className: cn2([\n                        \"flex items-center justify-center h-full bg-[#0A0A0A] text-[#A1A1AA] border-t border-[#27272A]\"\n                      ]),\n                      children: \"No changes\"\n                    }\n                  )\n                ]\n              }\n            ),\n            /* @__PURE__ */ u4(\n              \"div\",\n              {\n                className: cn2([\n                  \"flex flex-col border border-[#27272A] border-l-0 overflow-hidden w-1/3\"\n                ]),\n                children: [\n                  /* @__PURE__ */ u4(\n                    \"div\",\n                    {\n                      className: cn2([\n                        \" text-[14px] font-semibold px-2 py-2 bg-[#18181B] text-white flex justify-center\"\n                      ]),\n                      children: \"Changed State\"\n                    }\n                  ),\n                  selectedFiber.changes.state.length > 0 ? selectedFiber.changes.state.toSorted((a4, b3) => b3.count - a4.count).map((change) => /* @__PURE__ */ u4(\n                    \"div\",\n                    {\n                      className: cn2([\n                        \"flex flex-col justify-between items-center border-t overflow-x-auto border-[#27272A] px-1 py-1 text-wrap bg-[#0A0A0A] text-[10px]\"\n                      ]),\n                      children: [\n                        /* @__PURE__ */ u4(\"span\", { className: cn2([\"text-white \"]), children: [\n                          \"index \",\n                          change.index\n                        ] }),\n                        /* @__PURE__ */ u4(\n                          \"div\",\n                          {\n                            className: cn2([\n                              \"rounded-full  text-[#d36cff] pl-1 py-1 text-[8px]\"\n                            ]),\n                            children: [\n                              change.count,\n                              \"/\",\n                              selectedFiber.count,\n                              \"x\"\n                            ]\n                          }\n                        )\n                      ]\n                    },\n                    change.index\n                  )) : /* @__PURE__ */ u4(\n                    \"div\",\n                    {\n                      className: cn2([\n                        \"flex items-center justify-center h-full bg-[#0A0A0A] text-[#A1A1AA] border-t border-[#27272A]\"\n                      ]),\n                      children: \"No changes\"\n                    }\n                  )\n                ]\n              }\n            ),\n            /* @__PURE__ */ u4(\n              \"div\",\n              {\n                className: cn2([\n                  \"flex flex-col border border-[#27272A] border-l-0 rounded-r-sm overflow-hidden w-1/3\"\n                ]),\n                children: [\n                  /* @__PURE__ */ u4(\n                    \"div\",\n                    {\n                      className: cn2([\n                        \" text-[14px] font-semibold px-2 py-2 bg-[#18181B] text-white flex justify-center\"\n                      ]),\n                      children: \"Changed Context\"\n                    }\n                  ),\n                  selectedFiber.changes.context.length > 0 ? selectedFiber.changes.context.toSorted((a4, b3) => b3.count - a4.count).map((change) => /* @__PURE__ */ u4(\n                    \"div\",\n                    {\n                      className: cn2([\n                        \"flex flex-col justify-between items-center border-t  border-[#27272A] px-1 py-1 bg-[#0A0A0A] text-[10px] overflow-x-auto\"\n                      ]),\n                      children: [\n                        /* @__PURE__ */ u4(\"span\", { className: cn2([\"text-white \"]), children: change.name }),\n                        /* @__PURE__ */ u4(\n                          \"div\",\n                          {\n                            className: cn2([\n                              \"rounded-full text-[#d36cff] pl-1 py-1 text-[8px] text-wrap\"\n                            ]),\n                            children: [\n                              change.count,\n                              \"/\",\n                              selectedFiber.count,\n                              \"x\"\n                            ]\n                          }\n                        )\n                      ]\n                    },\n                    change.name\n                  )) : /* @__PURE__ */ u4(\n                    \"div\",\n                    {\n                      className: cn2([\n                        \"flex items-center justify-center h-full bg-[#0A0A0A] text-[#A1A1AA] border-t border-[#27272A] py-2\"\n                      ]),\n                      children: \"No changes\"\n                    }\n                  )\n                ]\n              }\n            )\n          ] })\n        ]\n      }\n    );\n  };\n\n  // src/web/views/notifications/details-routes.tsx\n  var DetailsRoutes = () => {\n    const { notificationState, setNotificationState } = useNotificationsContext();\n    const [dots, setDots] = d2(\"...\");\n    const containerRef = A2(null);\n    y2(() => {\n      const interval = setInterval(() => {\n        setDots((prev) => {\n          if (prev === \"...\") return \"\";\n          return prev + \".\";\n        });\n      }, 500);\n      return () => clearInterval(interval);\n    }, []);\n    if (!notificationState.selectedEvent) {\n      return /* @__PURE__ */ u4(\n        \"div\",\n        {\n          ref: containerRef,\n          className: cn2([\n            \"h-full w-full flex flex-col items-center justify-center relative py-2 px-4\"\n          ]),\n          children: [\n            /* @__PURE__ */ u4(\n              \"div\",\n              {\n                className: cn2([\n                  \"p-2 flex justify-center items-center border-[#27272A] absolute top-0 right-0\"\n                ]),\n                children: /* @__PURE__ */ u4(\n                  \"button\",\n                  {\n                    onClick: () => {\n                      signalWidgetViews.value = {\n                        view: \"none\"\n                      };\n                    },\n                    children: /* @__PURE__ */ u4(CloseIcon, { size: 18, className: \"text-[#6F6F78]\" })\n                  }\n                )\n              }\n            ),\n            /* @__PURE__ */ u4(\n              \"div\",\n              {\n                className: cn2([\n                  \"flex flex-col items-start pt-5 bg-[#0A0A0A] p-5 rounded-sm max-w-md\",\n                  \" shadow-lg\"\n                ]),\n                children: /* @__PURE__ */ u4(\"div\", { className: cn2([\"flex flex-col items-start gap-y-4\"]), children: [\n                  /* @__PURE__ */ u4(\"div\", { className: cn2([\"flex items-center\"]), children: /* @__PURE__ */ u4(\"span\", { className: cn2([\"text-zinc-400 font-medium text-[17px]\"]), children: [\n                    \"Scanning for slowdowns\",\n                    dots\n                  ] }) }),\n                  notificationState.events.length !== 0 && /* @__PURE__ */ u4(\"p\", { className: cn2([\"text-xs\"]), children: [\n                    \"Click on an item in the\",\n                    \" \",\n                    /* @__PURE__ */ u4(\"span\", { className: cn2([\"text-purple-400\"]), children: \"History\" }),\n                    \" list to get started\"\n                  ] }),\n                  /* @__PURE__ */ u4(\"p\", { className: cn2([\"text-zinc-600 text-xs\"]), children: \"You don't need to keep this panel open for React Scan to record slowdowns\" }),\n                  /* @__PURE__ */ u4(\"p\", { className: cn2([\"text-zinc-600 text-xs\"]), children: \"Enable audio alerts to hear a delightful ding every time a large slowdown is recorded\" }),\n                  /* @__PURE__ */ u4(\n                    \"button\",\n                    {\n                      onClick: () => {\n                        if (notificationState.audioNotificationsOptions.enabled) {\n                          setNotificationState((prev) => {\n                            if (prev.audioNotificationsOptions.audioContext?.state !== \"closed\") {\n                              prev.audioNotificationsOptions.audioContext?.close();\n                            }\n                            localStorage.setItem(\"react-scan-notifications-audio\", \"false\");\n                            return {\n                              ...prev,\n                              audioNotificationsOptions: {\n                                audioContext: null,\n                                enabled: false\n                              }\n                            };\n                          });\n                          return;\n                        }\n                        localStorage.setItem(\"react-scan-notifications-audio\", \"true\");\n                        const audioContext = new AudioContext();\n                        playNotificationSound(audioContext);\n                        setNotificationState((prev) => ({\n                          ...prev,\n                          audioNotificationsOptions: {\n                            enabled: true,\n                            audioContext\n                          }\n                        }));\n                      },\n                      className: cn2([\n                        \"px-4 py-2 bg-zinc-800 hover:bg-zinc-700 rounded-sm w-full\",\n                        \" text-sm flex items-center gap-x-2 justify-center\"\n                      ]),\n                      children: notificationState.audioNotificationsOptions.enabled ? /* @__PURE__ */ u4(k, { children: /* @__PURE__ */ u4(\"span\", { className: \"flex items-center gap-x-1\", children: \"Disable audio alerts\" }) }) : /* @__PURE__ */ u4(k, { children: /* @__PURE__ */ u4(\"span\", { className: \"flex items-center gap-x-1\", children: \"Enable audio alerts\" }) })\n                    }\n                  )\n                ] })\n              }\n            )\n          ]\n        }\n      );\n    }\n    switch (notificationState.route) {\n      case \"render-visualization\": {\n        return /* @__PURE__ */ u4(TabLayout, { children: /* @__PURE__ */ u4(RenderBarChart, { selectedEvent: notificationState.selectedEvent }) });\n      }\n      case \"render-explanation\": {\n        if (!notificationState.selectedFiber) {\n          throw new Error(\n            \"Invariant: must have selected fiber when viewing render explanation\"\n          );\n        }\n        return /* @__PURE__ */ u4(TabLayout, { children: /* @__PURE__ */ u4(\n          RenderExplanation,\n          {\n            selectedFiber: notificationState.selectedFiber,\n            selectedEvent: notificationState.selectedEvent\n          }\n        ) });\n      }\n      case \"other-visualization\": {\n        return /* @__PURE__ */ u4(TabLayout, { children: /* @__PURE__ */ u4(\n          \"div\",\n          {\n            className: cn2([\"flex w-full h-full flex-col overflow-y-auto\"]),\n            id: \"overview-scroll-container\",\n            children: /* @__PURE__ */ u4(\n              OtherVisualization,\n              {\n                selectedEvent: notificationState.selectedEvent\n              }\n            )\n          }\n        ) });\n      }\n      case \"optimize\": {\n        return /* @__PURE__ */ u4(TabLayout, { children: /* @__PURE__ */ u4(Optimize, { selectedEvent: notificationState.selectedEvent }) });\n      }\n    }\n    notificationState.route;\n  };\n  var TabLayout = ({ children }) => {\n    const { notificationState } = useNotificationsContext();\n    if (!notificationState.selectedEvent) {\n      throw new Error(\n        \"Invariant: d must have selected event when viewing render explanation\"\n      );\n    }\n    return /* @__PURE__ */ u4(\"div\", { className: cn2([`w-full h-full flex flex-col gap-y-2`]), children: [\n      /* @__PURE__ */ u4(\"div\", { className: cn2([\"h-[50px] w-full\"]), children: /* @__PURE__ */ u4(NotificationTabs, { selectedEvent: notificationState.selectedEvent }) }),\n      /* @__PURE__ */ u4(\n        \"div\",\n        {\n          className: cn2([\"h-calc(100%-50px) flex flex-col overflow-y-auto px-3\"]),\n          children\n        }\n      )\n    ] });\n  };\n\n  // src/web/views/notifications/notification-header.tsx\n  var NotificationHeader = ({\n    selectedEvent\n  }) => {\n    const severity = getEventSeverity(selectedEvent);\n    switch (selectedEvent.kind) {\n      case \"interaction\": {\n        return (\n          // h-[48px] is a hack to adjust for header size\n          /* @__PURE__ */ u4(\n            \"div\",\n            {\n              className: cn2([`w-full flex border-b border-[#27272A] min-h-[48px]`]),\n              children: /* @__PURE__ */ u4(\n                \"div\",\n                {\n                  className: cn2([\n                    \"min-w-fit w-full justify-start flex items-center border-r border-[#27272A] pl-5 pr-2 text-sm gap-x-4\"\n                  ]),\n                  children: [\n                    /* @__PURE__ */ u4(\"div\", { className: cn2([\"flex items-center gap-x-2 \"]), children: [\n                      /* @__PURE__ */ u4(\"span\", { className: cn2([\"text-[#5a5a5a] mr-0.5\"]), children: selectedEvent.type === \"click\" ? \"Clicked \" : \"Typed in \" }),\n                      /* @__PURE__ */ u4(\"span\", { children: getComponentName(selectedEvent.componentPath) }),\n                      /* @__PURE__ */ u4(\n                        \"div\",\n                        {\n                          className: cn2([\n                            \"w-fit flex items-center justify-center h-fit text-white px-1 rounded-sm font-semibold text-[10px] whitespace-nowrap\",\n                            severity === \"low\" && \"bg-green-500/50\",\n                            severity === \"needs-improvement\" && \"bg-[#b77116]\",\n                            severity === \"high\" && \"bg-[#b94040]\"\n                          ]),\n                          children: [\n                            getTotalTime(selectedEvent.timing).toFixed(0),\n                            \"ms processing time\"\n                          ]\n                        }\n                      )\n                    ] }),\n                    /* @__PURE__ */ u4(\n                      \"div\",\n                      {\n                        className: cn2([\"flex items-center gap-x-2  justify-end ml-auto\"]),\n                        children: /* @__PURE__ */ u4(\n                          \"div\",\n                          {\n                            className: cn2([\n                              \"p-2 flex justify-center items-center border-[#27272A]\"\n                            ]),\n                            children: /* @__PURE__ */ u4(\n                              \"button\",\n                              {\n                                onClick: () => {\n                                  signalWidgetViews.value = {\n                                    view: \"none\"\n                                  };\n                                },\n                                title: \"Close\",\n                                children: /* @__PURE__ */ u4(CloseIcon, { size: 18, className: \"text-[#6F6F78]\" })\n                              }\n                            )\n                          }\n                        )\n                      }\n                    )\n                  ]\n                }\n              )\n            }\n          )\n        );\n      }\n      case \"dropped-frames\": {\n        return /* @__PURE__ */ u4(\n          \"div\",\n          {\n            className: cn2([`w-full flex border-b border-[#27272A] min-h-[48px]`]),\n            children: /* @__PURE__ */ u4(\n              \"div\",\n              {\n                className: cn2([\n                  \"min-w-fit w-full justify-start flex items-center border-r border-[#27272A] pl-5 pr-2 text-sm gap-x-4\"\n                ]),\n                children: [\n                  /* @__PURE__ */ u4(\"div\", { className: cn2([\"flex items-center gap-x-2 \"]), children: [\n                    \"FPS Drop\",\n                    /* @__PURE__ */ u4(\n                      \"div\",\n                      {\n                        className: cn2([\n                          \"w-fit flex items-center justify-center h-fit text-white px-1 rounded-sm font-semibold text-[10px] whitespace-nowrap\",\n                          severity === \"low\" && \"bg-green-500/50\",\n                          severity === \"needs-improvement\" && \"bg-[#b77116]\",\n                          severity === \"high\" && \"bg-[#b94040]\"\n                        ]),\n                        children: [\n                          \"dropped to \",\n                          selectedEvent.fps,\n                          \" FPS\"\n                        ]\n                      }\n                    )\n                  ] }),\n                  /* @__PURE__ */ u4(\n                    \"div\",\n                    {\n                      className: cn2([\n                        \"flex items-center gap-x-2 w-2/4 justify-end ml-auto\"\n                      ]),\n                      children: /* @__PURE__ */ u4(\n                        \"div\",\n                        {\n                          className: cn2([\n                            \"p-2 flex justify-center items-center border-[#27272A]\"\n                          ]),\n                          children: /* @__PURE__ */ u4(\n                            \"button\",\n                            {\n                              onClick: () => {\n                                signalWidgetViews.value = {\n                                  view: \"none\"\n                                };\n                              },\n                              children: /* @__PURE__ */ u4(CloseIcon, { size: 18, className: \"text-[#6F6F78]\" })\n                            }\n                          )\n                        }\n                      )\n                    }\n                  )\n                ]\n              }\n            )\n          }\n        );\n      }\n    }\n  };\n\n  // src/web/views/notifications/collapsed-event.tsx\n  var useNestedFlash = ({\n    flashingItemsCount,\n    totalEvents\n  }) => {\n    const [newFlash, setNewFlash] = d2(false);\n    const flashedFor = A2(0);\n    const lastFlashTime = A2(0);\n    y2(() => {\n      if (flashedFor.current >= totalEvents) {\n        return;\n      }\n      const now = Date.now();\n      const debounceTime = 250;\n      const timeSinceLastFlash = now - lastFlashTime.current;\n      if (timeSinceLastFlash >= debounceTime) {\n        setNewFlash(false);\n        const timeout2 = setTimeout(() => {\n          flashedFor.current = totalEvents;\n          lastFlashTime.current = Date.now();\n          setNewFlash(true);\n          setTimeout(() => {\n            setNewFlash(false);\n          }, 2e3);\n        }, 50);\n        return () => clearTimeout(timeout2);\n      } else {\n        const delayNeeded = debounceTime - timeSinceLastFlash;\n        const timeout2 = setTimeout(() => {\n          setNewFlash(false);\n          setTimeout(() => {\n            flashedFor.current = totalEvents;\n            lastFlashTime.current = Date.now();\n            setNewFlash(true);\n            setTimeout(() => {\n              setNewFlash(false);\n            }, 2e3);\n          }, 50);\n        }, delayNeeded);\n        return () => clearTimeout(timeout2);\n      }\n    }, [flashingItemsCount]);\n    return newFlash;\n  };\n  var CollapsedItem = ({\n    item,\n    shouldFlash\n  }) => {\n    const [expanded, setExpanded] = d2(false);\n    const severity = item.events.map(getEventSeverity).reduce((prev, curr) => {\n      switch (curr) {\n        case \"high\": {\n          return \"high\";\n        }\n        case \"needs-improvement\": {\n          return prev === \"high\" ? \"high\" : \"needs-improvement\";\n        }\n        case \"low\": {\n          return prev;\n        }\n      }\n    }, \"low\");\n    const flashingItemsCount = item.events.reduce(\n      (prev, curr) => shouldFlash(curr.id) ? prev + 1 : prev,\n      0\n    );\n    const shouldFlashAgain = useNestedFlash({\n      flashingItemsCount,\n      totalEvents: item.events.length\n    });\n    return /* @__PURE__ */ u4(\"div\", { className: cn2([\"flex flex-col gap-y-0.5\"]), children: [\n      /* @__PURE__ */ u4(\n        \"button\",\n        {\n          onClick: () => setExpanded((expanded2) => !expanded2),\n          className: cn2([\n            \"pl-2 py-1.5  text-sm flex items-center rounded-sm hover:bg-[#18181B] relative overflow-hidden\",\n            shouldFlashAgain && !expanded && \"after:absolute after:inset-0 after:bg-purple-500/30 after:animate-[fadeOut_1s_ease-out_forwards]\"\n          ]),\n          children: [\n            /* @__PURE__ */ u4(\n              \"div\",\n              {\n                className: cn2([\n                  \"w-4/5 flex items-center justify-start h-full text-xs truncate gap-x-1.5\"\n                ]),\n                children: [\n                  /* @__PURE__ */ u4(\"span\", { className: cn2([\"min-w-fit\"]), children: /* @__PURE__ */ u4(\n                    ChevronRight,\n                    {\n                      className: cn2([\n                        \"text-[#A1A1AA] transition-transform\",\n                        expanded ? \"rotate-90\" : \"\"\n                      ]),\n                      size: 14\n                    },\n                    `chevron-${item.timestamp}`\n                  ) }),\n                  /* @__PURE__ */ u4(\"span\", { className: cn2([\"text-xs\"]), children: item.kind === \"collapsed-frame-drops\" ? \"FPS Drops\" : getComponentName(item.events.at(0)?.componentPath ?? []) })\n                ]\n              }\n            ),\n            /* @__PURE__ */ u4(\n              \"div\",\n              {\n                className: cn2([\"ml-auto min-w-fit flex justify-end items-center\"]),\n                children: /* @__PURE__ */ u4(\n                  \"div\",\n                  {\n                    style: {\n                      lineHeight: \"10px\"\n                    },\n                    className: cn2([\n                      \"w-fit flex items-center text-[10px] justify-center h-full text-white px-1 py-1 rounded-sm font-semibold\",\n                      severity === \"low\" && \"bg-green-500/60\",\n                      severity === \"needs-improvement\" && \"bg-[#b77116] text-[10px]\",\n                      severity === \"high\" && \"bg-[#b94040]\"\n                    ]),\n                    children: [\n                      \"x\",\n                      item.events.length\n                    ]\n                  }\n                )\n              }\n            )\n          ]\n        }\n      ),\n      expanded && /* @__PURE__ */ u4(IndentedContent, { children: item.events.toSorted((a4, b3) => b3.timestamp - a4.timestamp).map((event) => /* @__PURE__ */ u4(\n        SlowdownHistoryItem,\n        {\n          event,\n          shouldFlash: shouldFlash(event.id)\n        }\n      )) })\n    ] });\n  };\n  var IndentedContent = ({\n    children\n  }) => /* @__PURE__ */ u4(\"div\", { className: \"relative pl-6 flex flex-col gap-y-1\", children: [\n    /* @__PURE__ */ u4(\"div\", { className: \"absolute left-3 top-0 bottom-0 w-px bg-[#27272A]\" }),\n    children\n  ] });\n\n  // src/web/views/notifications/slowdown-history.tsx\n  var useFlashManager = (events) => {\n    const prevEventsRef = A2([]);\n    const [newEventIds, setNewEventIds] = d2(/* @__PURE__ */ new Set());\n    const isInitialMount = A2(true);\n    y2(() => {\n      if (isInitialMount.current) {\n        isInitialMount.current = false;\n        prevEventsRef.current = events;\n        return;\n      }\n      const currentIds = new Set(events.map((e4) => e4.id));\n      const prevIds = new Set(prevEventsRef.current.map((e4) => e4.id));\n      const newIds = /* @__PURE__ */ new Set();\n      currentIds.forEach((id) => {\n        if (!prevIds.has(id)) {\n          newIds.add(id);\n        }\n      });\n      if (newIds.size > 0) {\n        setNewEventIds(newIds);\n        setTimeout(() => {\n          setNewEventIds(/* @__PURE__ */ new Set());\n        }, 2e3);\n      }\n      prevEventsRef.current = events;\n    }, [events]);\n    return (id) => newEventIds.has(id);\n  };\n  var useFlash = ({ shouldFlash }) => {\n    const [isFlashing, setIsFlashing] = d2(shouldFlash);\n    y2(() => {\n      if (shouldFlash) {\n        setIsFlashing(true);\n        const timer = setTimeout(() => {\n          setIsFlashing(false);\n        }, 1e3);\n        return () => clearTimeout(timer);\n      }\n    }, [shouldFlash]);\n    return isFlashing;\n  };\n  var SlowdownHistoryItem = ({\n    event,\n    shouldFlash\n  }) => {\n    const { notificationState, setNotificationState } = useNotificationsContext();\n    const severity = getEventSeverity(event);\n    const isFlashing = useFlash({ shouldFlash });\n    switch (event.kind) {\n      case \"interaction\": {\n        return /* @__PURE__ */ u4(\n          \"button\",\n          {\n            onClick: () => {\n              setNotificationState((prev) => ({\n                ...prev,\n                selectedEvent: event,\n                route: \"render-visualization\",\n                selectedFiber: null\n              }));\n            },\n            className: cn2([\n              \"pl-2 py-1.5  text-sm flex w-full items-center rounded-sm hover:bg-[#18181B] relative overflow-hidden\",\n              event.id === notificationState.selectedEvent?.id && \"bg-[#18181B]\",\n              isFlashing && \"after:absolute after:inset-0 after:bg-purple-500/30 after:animate-[fadeOut_1s_ease-out_forwards]\"\n            ]),\n            children: [\n              /* @__PURE__ */ u4(\n                \"div\",\n                {\n                  className: cn2([\n                    \"w-4/5 flex items-center justify-start h-full gap-x-1.5\"\n                  ]),\n                  children: [\n                    /* @__PURE__ */ u4(\"span\", { className: cn2([\"min-w-fit text-xs\"]), children: iife(() => {\n                      switch (event.type) {\n                        case \"click\": {\n                          return /* @__PURE__ */ u4(PointerIcon, { size: 14 });\n                        }\n                        case \"keyboard\": {\n                          return /* @__PURE__ */ u4(KeyboardIcon, { size: 14 });\n                        }\n                      }\n                    }) }),\n                    /* @__PURE__ */ u4(\"span\", { className: cn2([\"text-xs pr-1 truncate\"]), children: getComponentName(event.componentPath) })\n                  ]\n                }\n              ),\n              /* @__PURE__ */ u4(\n                \"div\",\n                {\n                  className: cn2([\" min-w-fit flex justify-end items-center ml-auto\"]),\n                  children: /* @__PURE__ */ u4(\n                    \"div\",\n                    {\n                      style: {\n                        lineHeight: \"10px\"\n                      },\n                      className: cn2([\n                        \"gap-x-0.5 w-fit flex items-end justify-center h-full text-white px-1 py-1 rounded-sm font-semibold text-[10px]\",\n                        severity === \"low\" && \"bg-green-500/50\",\n                        severity === \"needs-improvement\" && \"bg-[#b77116] text-[10px]\",\n                        severity === \"high\" && \"bg-[#b94040]\"\n                      ]),\n                      children: /* @__PURE__ */ u4(\n                        \"div\",\n                        {\n                          style: {\n                            lineHeight: \"10px\"\n                          },\n                          className: cn2([\"text-[10px] text-white flex items-end\"]),\n                          children: [\n                            getTotalTime(event.timing).toFixed(0),\n                            \"ms\"\n                          ]\n                        }\n                      )\n                    }\n                  )\n                }\n              )\n            ]\n          }\n        );\n      }\n      case \"dropped-frames\": {\n        return /* @__PURE__ */ u4(\n          \"button\",\n          {\n            onClick: () => {\n              setNotificationState((prev) => ({\n                ...prev,\n                selectedEvent: event,\n                // explicitly force back to render-visualization since the user might get confused when they don't see the detailed view immediately when clicking the view\n                route: \"render-visualization\",\n                selectedFiber: null\n              }));\n            },\n            className: cn2([\n              \"pl-2 py-1.5  w-full text-sm flex items-center rounded-sm hover:bg-[#18181B] relative overflow-hidden\",\n              event.id === notificationState.selectedEvent?.id && \"bg-[#18181B]\",\n              isFlashing && \"after:absolute after:inset-0 after:bg-purple-500/30 after:animate-[fadeOut_1s_ease-out_forwards]\"\n            ]),\n            children: [\n              /* @__PURE__ */ u4(\n                \"div\",\n                {\n                  className: cn2([\n                    \"w-4/5 flex items-center justify-start h-full text-xs truncate\"\n                  ]),\n                  children: [\n                    /* @__PURE__ */ u4(TrendingDownIcon, { size: 14, className: \"mr-1.5\" }),\n                    \" FPS Drop\"\n                  ]\n                }\n              ),\n              /* @__PURE__ */ u4(\n                \"div\",\n                {\n                  className: cn2([\" min-w-fit flex justify-end items-center ml-auto\"]),\n                  children: /* @__PURE__ */ u4(\n                    \"div\",\n                    {\n                      style: {\n                        lineHeight: \"10px\"\n                      },\n                      className: cn2([\n                        \"w-fit flex items-center justify-center h-full text-white px-1 py-1 rounded-sm text-[10px] font-bold\",\n                        severity === \"low\" && \"bg-green-500/60\",\n                        severity === \"needs-improvement\" && \"bg-[#b77116] text-[10px]\",\n                        severity === \"high\" && \"bg-[#b94040]\"\n                      ]),\n                      children: [\n                        event.fps,\n                        \" FPS\"\n                      ]\n                    }\n                  )\n                }\n              )\n            ]\n          }\n        );\n      }\n    }\n  };\n  var collapseEvents = (events) => {\n    const newEvents = events.reduce((prev, curr) => {\n      const lastEvent = prev.at(-1);\n      if (!lastEvent) {\n        return [\n          {\n            kind: \"single\",\n            event: curr,\n            timestamp: curr.timestamp\n          }\n        ];\n      }\n      switch (lastEvent.kind) {\n        case \"collapsed-keyboard\": {\n          if (curr.kind === \"interaction\" && curr.type === \"keyboard\" && // must be on the same semantic component, it would be ideal to compare on fiberId, but i digress\n          curr.componentPath.join(\"-\") === lastEvent.events[0].componentPath.join(\"-\")) {\n            const eventsWithoutLast = prev.filter((e4) => e4 !== lastEvent);\n            return [\n              ...eventsWithoutLast,\n              {\n                kind: \"collapsed-keyboard\",\n                events: [...lastEvent.events, curr],\n                timestamp: Math.max(\n                  ...[...lastEvent.events, curr].map((e4) => e4.timestamp)\n                )\n              }\n            ];\n          }\n          return [\n            ...prev,\n            {\n              kind: \"single\",\n              event: curr,\n              timestamp: curr.timestamp\n            }\n          ];\n        }\n        case \"single\": {\n          if (lastEvent.event.kind === \"interaction\" && lastEvent.event.type === \"keyboard\" && curr.kind === \"interaction\" && curr.type === \"keyboard\" && lastEvent.event.componentPath.join(\"-\") === curr.componentPath.join(\"-\")) {\n            const eventsWithoutLast = prev.filter((e4) => e4 !== lastEvent);\n            return [\n              ...eventsWithoutLast,\n              {\n                kind: \"collapsed-keyboard\",\n                events: [lastEvent.event, curr],\n                timestamp: Math.max(lastEvent.event.timestamp, curr.timestamp)\n              }\n            ];\n          }\n          if (lastEvent.event.kind === \"dropped-frames\" && curr.kind === \"dropped-frames\") {\n            const eventsWithoutLast = prev.filter((e4) => e4 !== lastEvent);\n            return [\n              ...eventsWithoutLast,\n              {\n                kind: \"collapsed-frame-drops\",\n                events: [lastEvent.event, curr],\n                timestamp: Math.max(lastEvent.event.timestamp, curr.timestamp)\n              }\n            ];\n          }\n          return [\n            ...prev,\n            {\n              kind: \"single\",\n              event: curr,\n              timestamp: curr.timestamp\n            }\n          ];\n        }\n        case \"collapsed-frame-drops\": {\n          if (curr.kind === \"dropped-frames\") {\n            const eventsWithoutLast = prev.filter((e4) => e4 !== lastEvent);\n            return [\n              ...eventsWithoutLast,\n              {\n                kind: \"collapsed-frame-drops\",\n                events: [...lastEvent.events, curr],\n                timestamp: Math.max(\n                  ...[...lastEvent.events, curr].map((e4) => e4.timestamp)\n                )\n              }\n            ];\n          }\n          return [\n            ...prev,\n            {\n              kind: \"single\",\n              event: curr,\n              timestamp: curr.timestamp\n            }\n          ];\n        }\n      }\n    }, []);\n    return newEvents;\n  };\n  var useLaggedEvents = (lagMs = 150) => {\n    const { notificationState } = useNotificationsContext();\n    const [laggedEvents, setLaggedEvents] = d2(notificationState.events);\n    y2(() => {\n      setTimeout(() => {\n        setLaggedEvents(notificationState.events);\n      }, lagMs);\n    }, [notificationState.events]);\n    return [laggedEvents, setLaggedEvents];\n  };\n  var SlowdownHistory = () => {\n    const { notificationState, setNotificationState } = useNotificationsContext();\n    const shouldFlash = useFlashManager(notificationState.events);\n    const [laggedEvents, setLaggedEvents] = useLaggedEvents();\n    const collapsedEvents = collapseEvents(laggedEvents).toSorted(\n      (a4, b3) => b3.timestamp - a4.timestamp\n    );\n    return /* @__PURE__ */ u4(\n      \"div\",\n      {\n        className: cn2([\n          `w-full h-full gap-y-2 flex flex-col border-r border-[#27272A] overflow-y-auto`\n        ]),\n        children: [\n          /* @__PURE__ */ u4(\n            \"div\",\n            {\n              className: cn2([\n                \"text-sm text-[#65656D] pl-3 pr-1 w-full flex items-center justify-between\"\n              ]),\n              children: [\n                /* @__PURE__ */ u4(\"span\", { children: \"History\" }),\n                /* @__PURE__ */ u4(\n                  Popover,\n                  {\n                    wrapperProps: {\n                      className: \"h-full flex items-center justify-center ml-auto\"\n                    },\n                    triggerContent: /* @__PURE__ */ u4(\n                      \"button\",\n                      {\n                        className: cn2([\"hover:bg-[#18181B] rounded-full p-2\"]),\n                        title: \"Clear all events\",\n                        onClick: () => {\n                          toolbarEventStore.getState().actions.clear();\n                          setNotificationState((prev) => ({\n                            ...prev,\n                            selectedEvent: null,\n                            selectedFiber: null,\n                            route: prev.route === \"other-visualization\" ? \"other-visualization\" : \"render-visualization\"\n                          }));\n                          setLaggedEvents([]);\n                        },\n                        children: /* @__PURE__ */ u4(ClearIcon, { className: cn2([\"\"]), size: 16 })\n                      }\n                    ),\n                    children: /* @__PURE__ */ u4(\"div\", { className: cn2([\"w-full flex justify-center\"]), children: \"Clear all events\" })\n                  }\n                )\n              ]\n            }\n          ),\n          /* @__PURE__ */ u4(\"div\", { className: cn2([\"flex flex-col px-1 gap-y-1\"]), children: [\n            collapsedEvents.length === 0 && /* @__PURE__ */ u4(\n              \"div\",\n              {\n                className: cn2([\n                  \"flex items-center justify-center text-zinc-500 text-sm py-4\"\n                ]),\n                children: \"No Events\"\n              }\n            ),\n            collapsedEvents.map(\n              (historyItem) => iife(() => {\n                switch (historyItem.kind) {\n                  case \"collapsed-keyboard\": {\n                    return /* @__PURE__ */ u4(CollapsedItem, { shouldFlash, item: historyItem });\n                  }\n                  case \"single\": {\n                    return /* @__PURE__ */ u4(\n                      SlowdownHistoryItem,\n                      {\n                        event: historyItem.event,\n                        shouldFlash: shouldFlash(historyItem.event.id)\n                      },\n                      historyItem.event.id\n                    );\n                  }\n                  case \"collapsed-frame-drops\": {\n                    return /* @__PURE__ */ u4(CollapsedItem, { shouldFlash, item: historyItem });\n                  }\n                }\n              })\n            )\n          ] })\n        ]\n      }\n    );\n  };\n\n  // src/web/views/notifications/notifications.tsx\n  var getGroupedFiberRenders = (fiberRenders) => {\n    const res = Object.values(fiberRenders).map((render) => ({\n      id: not_globally_unique_generateId(),\n      totalTime: render.nodeInfo.reduce((prev, curr) => prev + curr.selfTime, 0),\n      count: render.nodeInfo.length,\n      name: render.nodeInfo[0].name,\n      // invariant, at least one exists,\n      deletedAll: false,\n      parents: render.parents,\n      hasMemoCache: render.hasMemoCache,\n      wasFiberRenderMount: render.wasFiberRenderMount,\n      // it would be nice if we calculated the % of components memoizable, but this would have to be calculated downstream before it got aggregated\n      elements: render.nodeInfo.map((node) => node.element),\n      changes: {\n        context: render.changes.fiberContext.current.filter(\n          (change) => render.changes.fiberContext.changesCounts.get(change.name)\n        ).map((change) => ({\n          name: String(change.name),\n          count: render.changes.fiberContext.changesCounts.get(change.name) ?? 0\n        })),\n        props: render.changes.fiberProps.current.filter(\n          (change) => render.changes.fiberProps.changesCounts.get(change.name)\n        ).map((change) => ({\n          name: String(change.name),\n          count: render.changes.fiberProps.changesCounts.get(change.name) ?? 0\n        })),\n        state: render.changes.fiberState.current.filter(\n          (change) => render.changes.fiberState.changesCounts.get(Number(change.name))\n        ).map((change) => ({\n          index: change.name,\n          count: render.changes.fiberState.changesCounts.get(Number(change.name)) ?? 0\n        }))\n      }\n    }));\n    return res;\n  };\n  var useGarbageCollectElements = (notificationEvents) => {\n    y2(() => {\n      const checkElementsExistence = () => {\n        notificationEvents.forEach((event) => {\n          if (!event.groupedFiberRenders) return;\n          event.groupedFiberRenders.forEach((render) => {\n            if (render.deletedAll) return;\n            if (!render.elements || render.elements.length === 0) {\n              render.deletedAll = true;\n              return;\n            }\n            const initialLength = render.elements.length;\n            render.elements = render.elements.filter((element) => {\n              return element && element.isConnected;\n            });\n            if (render.elements.length === 0 && initialLength > 0) {\n              render.deletedAll = true;\n            }\n          });\n        });\n      };\n      const intervalId = setInterval(checkElementsExistence, 5e3);\n      return () => {\n        clearInterval(intervalId);\n      };\n    }, [notificationEvents]);\n  };\n  var useAppNotifications = () => {\n    const log2 = useToolbarEventLog();\n    const notificationEvents = [];\n    useGarbageCollectElements(notificationEvents);\n    log2.state.events.forEach((event) => {\n      const fiberRenders = event.kind === \"interaction\" ? event.data.meta.detailedTiming.fiberRenders : event.data.meta.fiberRenders;\n      const groupedFiberRenders = getGroupedFiberRenders(fiberRenders);\n      const renderTime = groupedFiberRenders.reduce(\n        (prev, curr) => prev + curr.totalTime,\n        0\n      );\n      switch (event.kind) {\n        case \"interaction\": {\n          const { commitEnd, jsEndDetail, interactionStartDetail, rafStart } = event.data.meta.detailedTiming;\n          const otherJSTime = Math.max(\n            0,\n            jsEndDetail - interactionStartDetail - renderTime\n          );\n          const frameDraw = Math.max(\n            event.data.meta.latency - (commitEnd - interactionStartDetail),\n            0\n          );\n          notificationEvents.push({\n            componentPath: event.data.meta.detailedTiming.componentPath,\n            groupedFiberRenders,\n            id: event.id,\n            kind: \"interaction\",\n            memory: null,\n            timestamp: event.data.startAt,\n            type: event.data.meta.detailedTiming.interactionType === \"keyboard\" ? \"keyboard\" : \"click\",\n            timing: {\n              renderTime,\n              kind: \"interaction\",\n              otherJSTime,\n              framePreparation: rafStart - jsEndDetail,\n              frameConstruction: commitEnd - rafStart,\n              frameDraw\n            }\n          });\n          return;\n        }\n        case \"long-render\": {\n          notificationEvents.push({\n            kind: \"dropped-frames\",\n            id: event.id,\n            memory: null,\n            timing: {\n              kind: \"dropped-frames\",\n              renderTime,\n              otherTime: event.data.meta.latency\n            },\n            groupedFiberRenders,\n            timestamp: event.data.startAt,\n            fps: event.data.meta.fps\n          });\n          return;\n        }\n      }\n    });\n    return notificationEvents;\n  };\n  var timeout = 1e3;\n  var NotificationAudio = () => {\n    const { notificationState, setNotificationState } = useNotificationsContext();\n    const playedFor = A2(null);\n    const debounceTimeout = A2(null);\n    const lastPlayedTime = A2(0);\n    const [laggedEvents] = useLaggedEvents();\n    const alertEventsCount = laggedEvents.filter(\n      // todo: make this configurable\n      (event) => getEventSeverity(event) === \"high\"\n    ).length;\n    y2(() => {\n      const audioEnabledString = localStorage.getItem(\n        \"react-scan-notifications-audio\"\n      );\n      if (audioEnabledString !== \"false\" && audioEnabledString !== \"true\") {\n        localStorage.setItem(\"react-scan-notifications-audio\", \"false\");\n        return;\n      }\n      const audioEnabled = audioEnabledString === \"false\" ? false : true;\n      if (audioEnabled) {\n        setNotificationState((prev) => {\n          if (prev.audioNotificationsOptions.enabled) {\n            return prev;\n          }\n          return {\n            ...prev,\n            audioNotificationsOptions: {\n              enabled: true,\n              audioContext: new AudioContext()\n            }\n          };\n        });\n        return;\n      }\n    }, []);\n    y2(() => {\n      const { audioNotificationsOptions } = notificationState;\n      if (!audioNotificationsOptions.enabled) {\n        return;\n      }\n      if (alertEventsCount === 0) {\n        return;\n      }\n      if (playedFor.current && playedFor.current >= alertEventsCount) {\n        return;\n      }\n      if (debounceTimeout.current) {\n        clearTimeout(debounceTimeout.current);\n      }\n      const now = Date.now();\n      const timeSinceLastPlay = now - lastPlayedTime.current;\n      const remainingDebounceTime = Math.max(0, timeout - timeSinceLastPlay);\n      debounceTimeout.current = setTimeout(() => {\n        playNotificationSound(audioNotificationsOptions.audioContext);\n        playedFor.current = alertEventsCount;\n        lastPlayedTime.current = Date.now();\n        debounceTimeout.current = null;\n      }, remainingDebounceTime);\n    }, [alertEventsCount]);\n    y2(() => {\n      if (alertEventsCount !== 0) {\n        return;\n      }\n      playedFor.current = null;\n    }, [alertEventsCount]);\n    y2(() => {\n      return () => {\n        if (debounceTimeout.current) {\n          clearTimeout(debounceTimeout.current);\n        }\n      };\n    }, []);\n    return null;\n  };\n  var NotificationWrapper = D3((_4, ref) => {\n    const events = useAppNotifications();\n    const [notificationState, setNotificationState] = d2({\n      detailsExpanded: false,\n      events,\n      filterBy: \"latest\",\n      moreInfoExpanded: false,\n      route: \"render-visualization\",\n      selectedEvent: events.toSorted((a4, b3) => a4.timestamp - b3.timestamp).at(-1) ?? null,\n      selectedFiber: null,\n      routeMessage: null,\n      audioNotificationsOptions: {\n        enabled: false,\n        audioContext: null\n      }\n    });\n    notificationState.events = events;\n    return /* @__PURE__ */ u4(\n      NotificationStateContext.Provider,\n      {\n        value: {\n          notificationState,\n          setNotificationState,\n          setRoute: ({ route, routeMessage }) => {\n            setNotificationState((prev) => {\n              const newState = { ...prev, route, routeMessage };\n              switch (route) {\n                case \"render-visualization\": {\n                  fadeOutHighlights();\n                  return {\n                    ...newState,\n                    selectedFiber: null\n                  };\n                }\n                case \"optimize\": {\n                  fadeOutHighlights();\n                  return {\n                    ...newState,\n                    selectedFiber: null\n                  };\n                }\n                case \"other-visualization\": {\n                  fadeOutHighlights();\n                  return {\n                    ...newState,\n                    selectedFiber: null\n                  };\n                }\n                case \"render-explanation\": {\n                  fadeOutHighlights();\n                  return newState;\n                }\n              }\n            });\n          }\n        },\n        children: [\n          /* @__PURE__ */ u4(NotificationAudio, {}),\n          /* @__PURE__ */ u4(Notifications, { ref })\n        ]\n      }\n    );\n  });\n  var Notifications = D3((_4, ref) => {\n    const { notificationState } = useNotificationsContext();\n    return /* @__PURE__ */ u4(\"div\", { ref, className: cn2([\"h-full w-full flex flex-col\"]), children: [\n      notificationState.selectedEvent && /* @__PURE__ */ u4(\n        \"div\",\n        {\n          className: cn2([\n            \"w-full h-[48px] flex flex-col\",\n            notificationState.moreInfoExpanded && \"h-[235px]\",\n            notificationState.moreInfoExpanded && notificationState.selectedEvent.kind === \"dropped-frames\" && \"h-[150px]\"\n          ]),\n          children: [\n            /* @__PURE__ */ u4(NotificationHeader, { selectedEvent: notificationState.selectedEvent }),\n            notificationState.moreInfoExpanded && /* @__PURE__ */ u4(MoreInfo, {})\n          ]\n        }\n      ),\n      /* @__PURE__ */ u4(\n        \"div\",\n        {\n          className: cn2([\n            \"flex \",\n            notificationState.selectedEvent ? \"h-[calc(100%-48px)]\" : \"h-full\",\n            notificationState.moreInfoExpanded && \"h-[calc(100%-200px)]\",\n            notificationState.moreInfoExpanded && notificationState.selectedEvent?.kind === \"dropped-frames\" && \"h-[calc(100%-150px)]\"\n          ]),\n          children: [\n            /* @__PURE__ */ u4(\"div\", { className: cn2([\"h-full min-w-[200px]\"]), children: /* @__PURE__ */ u4(SlowdownHistory, {}) }),\n            /* @__PURE__ */ u4(\"div\", { className: cn2([\"w-[calc(100%-200px)] h-full overflow-y-auto\"]), children: /* @__PURE__ */ u4(DetailsRoutes, {}) })\n          ]\n        }\n      )\n    ] });\n  });\n  var MoreInfo = () => {\n    const { notificationState } = useNotificationsContext();\n    if (!notificationState.selectedEvent) {\n      throw new Error(\"Invariant must have selected event for more info\");\n    }\n    const event = notificationState.selectedEvent;\n    return /* @__PURE__ */ u4(\n      \"div\",\n      {\n        className: cn2([\n          \"px-4 py-2 border-b border-[#27272A] bg-[#18181B]/50 h-[calc(100%-40px)]\",\n          event.kind === \"dropped-frames\" && `h-[calc(100%-25px)]`\n        ]),\n        children: /* @__PURE__ */ u4(\"div\", { className: cn2([\"flex flex-col gap-y-4 h-full\"]), children: iife(() => {\n          switch (event.kind) {\n            case \"interaction\": {\n              return /* @__PURE__ */ u4(k, { children: [\n                /* @__PURE__ */ u4(\"div\", { className: cn2([\"flex items-center gap-x-3\"]), children: [\n                  /* @__PURE__ */ u4(\"span\", { className: \"text-[#6F6F78] text-xs font-medium\", children: event.type === \"click\" ? \"Clicked component location\" : \"Typed in component location\" }),\n                  /* @__PURE__ */ u4(\"div\", { className: \"font-mono text-[#E4E4E7] flex items-center bg-[#27272A] pl-2 py-1 rounded-sm overflow-x-auto\", children: event.componentPath.toReversed().map((part, i5) => /* @__PURE__ */ u4(k, { children: [\n                    /* @__PURE__ */ u4(\n                      \"span\",\n                      {\n                        style: {\n                          lineHeight: \"14px\"\n                        },\n                        className: \"text-[10px] whitespace-nowrap\",\n                        children: part\n                      },\n                      part\n                    ),\n                    i5 < event.componentPath.length - 1 && /* @__PURE__ */ u4(\"span\", { className: \"text-[#6F6F78] mx-0.5\", children: \"\\u2039\" })\n                  ] })) })\n                ] }),\n                /* @__PURE__ */ u4(\"div\", { className: cn2([\"flex items-center gap-x-3\"]), children: [\n                  /* @__PURE__ */ u4(\"span\", { className: \"text-[#6F6F78] text-xs font-medium\", children: \"Total Time\" }),\n                  /* @__PURE__ */ u4(\"span\", { className: \"text-[#E4E4E7] bg-[#27272A] px-1.5 py-1 rounded-sm text-xs\", children: [\n                    getTotalTime(event.timing).toFixed(0),\n                    \"ms\"\n                  ] })\n                ] }),\n                /* @__PURE__ */ u4(\"div\", { className: cn2([\"flex items-center gap-x-3\"]), children: [\n                  /* @__PURE__ */ u4(\"span\", { className: \"text-[#6F6F78] text-xs font-medium\", children: \"Occurred\" }),\n                  /* @__PURE__ */ u4(\"span\", { className: \"text-[#E4E4E7] bg-[#27272A] px-1.5 py-1 rounded-sm text-xs\", children: `${((Date.now() - event.timestamp) / 1e3).toFixed(0)}s ago` })\n                ] })\n              ] });\n            }\n            case \"dropped-frames\": {\n              return /* @__PURE__ */ u4(k, { children: [\n                /* @__PURE__ */ u4(\"div\", { className: cn2([\"flex items-center gap-x-3\"]), children: [\n                  /* @__PURE__ */ u4(\"span\", { className: \"text-[#6F6F78] text-xs font-medium\", children: \"Total Time\" }),\n                  /* @__PURE__ */ u4(\"span\", { className: \"text-[#E4E4E7] bg-[#27272A] px-1.5 py-1 rounded-sm text-xs\", children: [\n                    getTotalTime(event.timing).toFixed(0),\n                    \"ms\"\n                  ] })\n                ] }),\n                /* @__PURE__ */ u4(\"div\", { className: cn2([\"flex items-center gap-x-3\"]), children: [\n                  /* @__PURE__ */ u4(\"span\", { className: \"text-[#6F6F78] text-xs font-medium\", children: \"Occurred\" }),\n                  /* @__PURE__ */ u4(\"span\", { className: \"text-[#E4E4E7] bg-[#27272A] px-1.5 py-1 rounded-sm text-xs\", children: `${((Date.now() - event.timestamp) / 1e3).toFixed(0)}s ago` })\n                ] })\n              ] });\n            }\n          }\n        }) })\n      }\n    );\n  };\n\n  // src/web/views/toolbar/index.tsx\n  var Toolbar = constant(() => {\n    const events = useAppNotifications();\n    const [laggedEvents, setLaggedEvents] = d2(events);\n    y2(() => {\n      const timeout2 = setTimeout(() => {\n        setLaggedEvents(events);\n      }, 500 + 100);\n      return () => {\n        clearTimeout(timeout2);\n      };\n    }, [events]);\n    const inspectState = Store.inspectState;\n    const isInspectActive = inspectState.value.kind === \"inspecting\";\n    const isInspectFocused = inspectState.value.kind === \"focused\";\n    const [seenEvents, setSeenEvents] = d2([]);\n    const onToggleInspect = q2(() => {\n      const currentState = Store.inspectState.value;\n      switch (currentState.kind) {\n        case \"inspecting\": {\n          signalWidgetViews.value = {\n            view: \"none\"\n          };\n          Store.inspectState.value = {\n            kind: \"inspect-off\"\n          };\n          return;\n        }\n        case \"focused\": {\n          signalWidgetViews.value = {\n            view: \"inspector\"\n          };\n          Store.inspectState.value = {\n            kind: \"inspecting\",\n            hoveredDomElement: null\n          };\n          return;\n        }\n        // todo: auto select the root fibers first stateNode, and tell the user to select the element\n        case \"inspect-off\": {\n          signalWidgetViews.value = {\n            view: \"none\"\n          };\n          Store.inspectState.value = {\n            kind: \"inspecting\",\n            hoveredDomElement: null\n          };\n          return;\n        }\n        case \"uninitialized\": {\n          return;\n        }\n      }\n    }, []);\n    const onToggleActive = q2((e4) => {\n      e4.preventDefault();\n      e4.stopPropagation();\n      if (!ReactScanInternals.instrumentation) {\n        return;\n      }\n      const isPaused = !ReactScanInternals.instrumentation.isPaused.value;\n      ReactScanInternals.instrumentation.isPaused.value = isPaused;\n      const existingLocalStorageOptions = readLocalStorage(\"react-scan-options\");\n      saveLocalStorage(\"react-scan-options\", {\n        ...existingLocalStorageOptions,\n        enabled: !isPaused\n      });\n    }, []);\n    useSignalEffect(() => {\n      const state = Store.inspectState.value;\n      if (state.kind === \"uninitialized\") {\n        Store.inspectState.value = {\n          kind: \"inspect-off\"\n        };\n      }\n    });\n    let inspectIcon = null;\n    let inspectColor = \"#999\";\n    if (isInspectActive) {\n      inspectIcon = /* @__PURE__ */ u4(Icon, { name: \"icon-inspect\" });\n      inspectColor = \"#8e61e3\";\n    } else if (isInspectFocused) {\n      inspectIcon = /* @__PURE__ */ u4(Icon, { name: \"icon-focus\" });\n      inspectColor = \"#8e61e3\";\n    } else {\n      inspectIcon = /* @__PURE__ */ u4(Icon, { name: \"icon-inspect\" });\n      inspectColor = \"#999\";\n    }\n    _2(() => {\n      if (signalWidgetViews.value.view !== \"notifications\") {\n        return;\n      }\n      const ids = new Set(events.map((event) => event.id));\n      setSeenEvents([...ids.values()]);\n    }, [events.length, signalWidgetViews.value.view]);\n    return /* @__PURE__ */ u4(\"div\", { className: \"flex max-h-9 min-h-9 flex-1 items-stretch overflow-hidden\", children: [\n      /* @__PURE__ */ u4(\"div\", { className: \"h-full flex items-center min-w-fit\", children: /* @__PURE__ */ u4(\n        \"button\",\n        {\n          type: \"button\",\n          id: \"react-scan-inspect-element\",\n          title: \"Inspect element\",\n          onClick: onToggleInspect,\n          className: \"button flex items-center justify-center h-full w-full pl-3 pr-2.5\",\n          style: { color: inspectColor },\n          children: inspectIcon\n        }\n      ) }),\n      /* @__PURE__ */ u4(\"div\", { className: \"h-full flex items-center justify-center\", children: /* @__PURE__ */ u4(\n        \"button\",\n        {\n          type: \"button\",\n          id: \"react-scan-notifications\",\n          title: \"Notifications\",\n          onClick: () => {\n            if (Store.inspectState.value.kind !== \"inspect-off\") {\n              Store.inspectState.value = {\n                kind: \"inspect-off\"\n              };\n            }\n            switch (signalWidgetViews.value.view) {\n              case \"inspector\": {\n                Store.inspectState.value = {\n                  kind: \"inspect-off\"\n                };\n                const ids = new Set(events.map((event) => event.id));\n                setSeenEvents([...ids.values()]);\n                signalWidgetViews.value = {\n                  view: \"notifications\"\n                };\n                return;\n              }\n              case \"notifications\": {\n                signalWidgetViews.value = {\n                  view: \"none\"\n                };\n                return;\n              }\n              case \"none\": {\n                const ids = new Set(events.map((event) => event.id));\n                setSeenEvents([...ids.values()]);\n                signalWidgetViews.value = {\n                  view: \"notifications\"\n                };\n                return;\n              }\n            }\n          },\n          className: \"button flex items-center justify-center h-full pl-2.5 pr-2.5\",\n          style: { color: inspectColor },\n          children: /* @__PURE__ */ u4(\n            Notification,\n            {\n              events: laggedEvents.filter((event) => !seenEvents.includes(event.id)).map((event) => getEventSeverity(event) === \"high\"),\n              size: 16,\n              className: cn2([\n                \"text-[#999]\",\n                signalWidgetViews.value.view === \"notifications\" && \"text-[#8E61E3]\"\n              ])\n            }\n          )\n        }\n      ) }),\n      /* @__PURE__ */ u4(\n        Toggle,\n        {\n          checked: !ReactScanInternals.instrumentation?.isPaused.value,\n          onChange: onToggleActive,\n          className: \"place-self-center\",\n          title: \"Outline Re-renders\"\n        }\n      ),\n      ReactScanInternals.options.value.showFPS && /* @__PURE__ */ u4(FPSMeter, {})\n    ] });\n  });\n\n  // src/web/views/index.tsx\n  var isInspecting = w3(\n    () => Store.inspectState.value.kind === \"inspecting\"\n  );\n  var headerClassName = w3(\n    () => cn2(\n      \"relative\",\n      \"flex-1\",\n      \"flex flex-col\",\n      \"rounded-t-lg\",\n      \"overflow-hidden\",\n      \"opacity-100\",\n      \"transition-[opacity]\",\n      isInspecting.value && \"opacity-0 duration-0 delay-0\"\n    )\n  );\n  var isInspectorViewOpen = w3(\n    () => signalWidgetViews.value.view === \"inspector\"\n  );\n  var isNotificationsViewOpen = w3(\n    () => signalWidgetViews.value.view === \"notifications\"\n  );\n  var Content = () => {\n    return /* @__PURE__ */ u4(\n      \"div\",\n      {\n        className: cn2(\n          \"flex flex-1 flex-col\",\n          \"overflow-hidden z-10\",\n          \"rounded-lg\",\n          \"bg-black\",\n          \"opacity-100\",\n          \"transition-[border-radius]\",\n          \"peer-hover/left:rounded-l-none\",\n          \"peer-hover/right:rounded-r-none\",\n          \"peer-hover/top:rounded-t-none\",\n          \"peer-hover/bottom:rounded-b-none\"\n        ),\n        children: [\n          /* @__PURE__ */ u4(\"div\", { className: headerClassName, children: [\n            /* @__PURE__ */ u4(Header, {}),\n            /* @__PURE__ */ u4(\n              \"div\",\n              {\n                className: cn2(\n                  \"relative\",\n                  \"flex-1 flex\",\n                  \"text-white\",\n                  \"bg-[#0A0A0A]\",\n                  \"transition-opacity delay-150\",\n                  \"overflow-hidden\",\n                  \"border-b border-[#222]\"\n                ),\n                children: [\n                  /* @__PURE__ */ u4(ContentView, { isOpen: isInspectorViewOpen, children: /* @__PURE__ */ u4(ViewInspector, {}) }),\n                  /* @__PURE__ */ u4(ContentView, { isOpen: isNotificationsViewOpen, children: /* @__PURE__ */ u4(NotificationWrapper, {}) })\n                ]\n              }\n            )\n          ] }),\n          /* @__PURE__ */ u4(Toolbar, {})\n        ]\n      }\n    );\n  };\n  var ContentView = ({ isOpen, children }) => {\n    return /* @__PURE__ */ u4(\n      \"div\",\n      {\n        className: cn2(\n          \"flex-1\",\n          \"opacity-0\",\n          \"overflow-y-auto overflow-x-hidden\",\n          \"transition-opacity delay-0\",\n          \"pointer-events-none\",\n          isOpen.value && \"opacity-100 delay-150 pointer-events-auto\"\n        ),\n        children: /* @__PURE__ */ u4(\"div\", { className: \"absolute inset-0 flex\", children })\n      }\n    );\n  };\n\n  // src/web/views/inspector/overlay/index.tsx\n  var lerp2 = (start2, end, t4) => start2 + (end - start2) * t4;\n  var ANIMATION_CONFIG = {\n    frameInterval: 1e3 / 60,\n    speeds: {\n      fast: 0.51,\n      slow: 0.1,\n      off: 0\n    }\n  };\n  var OVERLAY_DPR = IS_CLIENT ? window.devicePixelRatio || 1 : 1;\n  var ScanOverlay = () => {\n    const refCanvas = A2(null);\n    const refEventCatcher = A2(null);\n    const refCurrentRect = A2(null);\n    const refCurrentLockIconRect = A2(null);\n    const refLastHoveredElement = A2(null);\n    const refRafId = A2(0);\n    const refTimeout = A2();\n    const refCleanupMap = A2(\n      /* @__PURE__ */ new Map()\n    );\n    const refIsFadingOut = A2(false);\n    const refLastFrameTime = A2(0);\n    const drawLockIcon = (ctx2, x4, y4, size) => {\n      ctx2.save();\n      ctx2.strokeStyle = \"white\";\n      ctx2.fillStyle = \"white\";\n      ctx2.lineWidth = 1.5;\n      const shackleWidth = size * 0.6;\n      const shackleHeight = size * 0.5;\n      const shackleX = x4 + (size - shackleWidth) / 2;\n      const shackleY = y4;\n      ctx2.beginPath();\n      ctx2.arc(\n        shackleX + shackleWidth / 2,\n        shackleY + shackleHeight / 2,\n        shackleWidth / 2,\n        Math.PI,\n        0,\n        false\n      );\n      ctx2.stroke();\n      const bodyWidth = size * 0.8;\n      const bodyHeight = size * 0.5;\n      const bodyX = x4 + (size - bodyWidth) / 2;\n      const bodyY = y4 + shackleHeight / 2;\n      ctx2.fillRect(bodyX, bodyY, bodyWidth, bodyHeight);\n      ctx2.restore();\n    };\n    const drawStatsPill = (ctx2, rect, kind, fiber) => {\n      if (!fiber) return;\n      const pillHeight = 24;\n      const pillPadding = 8;\n      const componentName = (fiber?.type && getDisplayName(fiber.type)) ?? \"Unknown\";\n      const text = componentName;\n      ctx2.save();\n      ctx2.font = \"12px system-ui, -apple-system, sans-serif\";\n      const textMetrics = ctx2.measureText(text);\n      const textWidth = textMetrics.width;\n      const lockIconSize = kind === \"locked\" ? 14 : 0;\n      const lockIconPadding = kind === \"locked\" ? 6 : 0;\n      const pillWidth = textWidth + pillPadding * 2 + lockIconSize + lockIconPadding;\n      const pillX = rect.left;\n      const pillY = rect.top - pillHeight - 4;\n      ctx2.fillStyle = \"rgb(37, 37, 38, .75)\";\n      ctx2.beginPath();\n      ctx2.roundRect(pillX, pillY, pillWidth, pillHeight, 3);\n      ctx2.fill();\n      if (kind === \"locked\") {\n        const lockX = pillX + pillPadding;\n        const lockY = pillY + (pillHeight - lockIconSize) / 2 + 2;\n        drawLockIcon(ctx2, lockX, lockY, lockIconSize);\n        refCurrentLockIconRect.current = {\n          x: lockX,\n          y: lockY,\n          width: lockIconSize,\n          height: lockIconSize\n        };\n      } else {\n        refCurrentLockIconRect.current = null;\n      }\n      ctx2.fillStyle = \"white\";\n      ctx2.textBaseline = \"middle\";\n      const textX = pillX + pillPadding + (kind === \"locked\" ? lockIconSize + lockIconPadding : 0);\n      ctx2.fillText(text, textX, pillY + pillHeight / 2);\n      ctx2.restore();\n    };\n    const drawRect = (canvas2, ctx2, kind, fiber) => {\n      if (!refCurrentRect.current) return;\n      const rect = refCurrentRect.current;\n      ctx2.clearRect(0, 0, canvas2.width, canvas2.height);\n      ctx2.strokeStyle = \"rgba(142, 97, 227, 0.5)\";\n      ctx2.fillStyle = \"rgba(173, 97, 230, 0.10)\";\n      if (kind === \"locked\") {\n        ctx2.setLineDash([]);\n      } else {\n        ctx2.setLineDash([4]);\n      }\n      ctx2.lineWidth = 1;\n      ctx2.fillRect(rect.left, rect.top, rect.width, rect.height);\n      ctx2.strokeRect(rect.left, rect.top, rect.width, rect.height);\n      drawStatsPill(ctx2, rect, kind, fiber);\n    };\n    const animate = (canvas2, ctx2, targetRect, kind, parentCompositeFiber, onComplete) => {\n      const speed = ReactScanInternals.options.value.animationSpeed;\n      const t4 = ANIMATION_CONFIG.speeds[speed] ?? ANIMATION_CONFIG.speeds.off;\n      const animationFrame2 = (timestamp) => {\n        if (timestamp - refLastFrameTime.current < ANIMATION_CONFIG.frameInterval) {\n          refRafId.current = requestAnimationFrame(animationFrame2);\n          return;\n        }\n        refLastFrameTime.current = timestamp;\n        if (!refCurrentRect.current) {\n          cancelAnimationFrame(refRafId.current);\n          return;\n        }\n        refCurrentRect.current = {\n          left: lerp2(refCurrentRect.current.left, targetRect.left, t4),\n          top: lerp2(refCurrentRect.current.top, targetRect.top, t4),\n          width: lerp2(refCurrentRect.current.width, targetRect.width, t4),\n          height: lerp2(refCurrentRect.current.height, targetRect.height, t4)\n        };\n        drawRect(canvas2, ctx2, kind, parentCompositeFiber);\n        const stillMoving = Math.abs(refCurrentRect.current.left - targetRect.left) > 0.1 || Math.abs(refCurrentRect.current.top - targetRect.top) > 0.1 || Math.abs(refCurrentRect.current.width - targetRect.width) > 0.1 || Math.abs(refCurrentRect.current.height - targetRect.height) > 0.1;\n        if (stillMoving) {\n          refRafId.current = requestAnimationFrame(animationFrame2);\n        } else {\n          refCurrentRect.current = targetRect;\n          drawRect(canvas2, ctx2, kind, parentCompositeFiber);\n          cancelAnimationFrame(refRafId.current);\n          ctx2.restore();\n        }\n      };\n      cancelAnimationFrame(refRafId.current);\n      clearTimeout(refTimeout.current);\n      refRafId.current = requestAnimationFrame(animationFrame2);\n      refTimeout.current = setTimeout(() => {\n        cancelAnimationFrame(refRafId.current);\n        refCurrentRect.current = targetRect;\n        drawRect(canvas2, ctx2, kind, parentCompositeFiber);\n        ctx2.restore();\n      }, 1e3);\n    };\n    const setupOverlayAnimation = (canvas2, ctx2, targetRect, kind, parentCompositeFiber) => {\n      ctx2.save();\n      if (!refCurrentRect.current) {\n        refCurrentRect.current = targetRect;\n        drawRect(canvas2, ctx2, kind, parentCompositeFiber);\n        ctx2.restore();\n        return;\n      }\n      animate(canvas2, ctx2, targetRect, kind, parentCompositeFiber);\n    };\n    const drawHoverOverlay = async (overlayElement, canvas2, ctx2, kind) => {\n      if (!overlayElement || !canvas2 || !ctx2) return;\n      const { parentCompositeFiber } = getCompositeComponentFromElement(overlayElement);\n      const targetRect = await getAssociatedFiberRect(overlayElement);\n      if (!parentCompositeFiber || !targetRect) return;\n      setupOverlayAnimation(canvas2, ctx2, targetRect, kind, parentCompositeFiber);\n    };\n    const unsubscribeAll = () => {\n      for (const cleanup3 of refCleanupMap.current.values()) {\n        cleanup3?.();\n      }\n    };\n    const cleanupCanvas = (canvas2) => {\n      const ctx2 = canvas2.getContext(\"2d\");\n      if (ctx2) {\n        ctx2.clearRect(0, 0, canvas2.width, canvas2.height);\n      }\n      refCurrentRect.current = null;\n      refCurrentLockIconRect.current = null;\n      refLastHoveredElement.current = null;\n      canvas2.classList.remove(\"fade-in\");\n      refIsFadingOut.current = false;\n    };\n    const startFadeOut = (onComplete) => {\n      if (!refCanvas.current || refIsFadingOut.current) return;\n      const handleTransitionEnd = (e4) => {\n        if (!refCanvas.current || e4.propertyName !== \"opacity\" || !refIsFadingOut.current) {\n          return;\n        }\n        refCanvas.current.removeEventListener(\n          \"transitionend\",\n          handleTransitionEnd\n        );\n        cleanupCanvas(refCanvas.current);\n        onComplete?.();\n      };\n      const existingListener = refCleanupMap.current.get(\"fade-out\");\n      if (existingListener) {\n        existingListener();\n        refCleanupMap.current.delete(\"fade-out\");\n      }\n      refCanvas.current.addEventListener(\"transitionend\", handleTransitionEnd);\n      refCleanupMap.current.set(\"fade-out\", () => {\n        refCanvas.current?.removeEventListener(\n          \"transitionend\",\n          handleTransitionEnd\n        );\n      });\n      refIsFadingOut.current = true;\n      refCanvas.current.classList.remove(\"fade-in\");\n      requestAnimationFrame(() => {\n        refCanvas.current?.classList.add(\"fade-out\");\n      });\n    };\n    const startFadeIn = () => {\n      if (!refCanvas.current) return;\n      refIsFadingOut.current = false;\n      refCanvas.current.classList.remove(\"fade-out\");\n      requestAnimationFrame(() => {\n        refCanvas.current?.classList.add(\"fade-in\");\n      });\n    };\n    const handleHoverableElement = (componentElement) => {\n      if (componentElement === refLastHoveredElement.current) return;\n      refLastHoveredElement.current = componentElement;\n      if (nonVisualTags.has(componentElement.tagName)) {\n        startFadeOut();\n      } else {\n        startFadeIn();\n      }\n      Store.inspectState.value = {\n        kind: \"inspecting\",\n        hoveredDomElement: componentElement\n      };\n    };\n    const handleNonHoverableArea = () => {\n      if (!refCurrentRect.current || !refCanvas.current || refIsFadingOut.current) {\n        return;\n      }\n      startFadeOut();\n    };\n    const handlePointerMove = throttle((e4) => {\n      const state = Store.inspectState.peek();\n      if (state.kind !== \"inspecting\" || !refEventCatcher.current) return;\n      refEventCatcher.current.style.pointerEvents = \"none\";\n      const element = document.elementFromPoint(e4?.clientX ?? 0, e4?.clientY ?? 0);\n      refEventCatcher.current.style.removeProperty(\"pointer-events\");\n      clearTimeout(refTimeout.current);\n      if (element && element !== refCanvas.current) {\n        const { parentCompositeFiber } = getCompositeComponentFromElement(\n          element\n        );\n        if (parentCompositeFiber) {\n          const componentElement = findComponentDOMNode(parentCompositeFiber);\n          if (componentElement) {\n            handleHoverableElement(componentElement);\n            return;\n          }\n        }\n      }\n      handleNonHoverableArea();\n    }, 32);\n    const isClickInLockIcon = (e4, canvas2) => {\n      const currentRect = refCurrentLockIconRect.current;\n      if (!currentRect) return false;\n      const rect = canvas2.getBoundingClientRect();\n      const scaleX = canvas2.width / rect.width;\n      const scaleY = canvas2.height / rect.height;\n      const x4 = (e4.clientX - rect.left) * scaleX;\n      const y4 = (e4.clientY - rect.top) * scaleY;\n      const adjustedX = x4 / OVERLAY_DPR;\n      const adjustedY = y4 / OVERLAY_DPR;\n      return adjustedX >= currentRect.x && adjustedX <= currentRect.x + currentRect.width && adjustedY >= currentRect.y && adjustedY <= currentRect.y + currentRect.height;\n    };\n    const handleLockIconClick = (state) => {\n      if (state.kind === \"focused\") {\n        Store.inspectState.value = {\n          kind: \"inspecting\",\n          hoveredDomElement: state.focusedDomElement\n        };\n      }\n    };\n    const handleElementClick = (e4) => {\n      const clickableElements = [\n        \"react-scan-inspect-element\",\n        \"react-scan-power\"\n      ];\n      if (e4.target instanceof HTMLElement && clickableElements.includes(e4.target.id)) {\n        return;\n      }\n      const tagName = refLastHoveredElement.current?.tagName;\n      if (tagName && nonVisualTags.has(tagName)) {\n        return;\n      }\n      e4.preventDefault();\n      e4.stopPropagation();\n      const element = refLastHoveredElement.current ?? document.elementFromPoint(e4.clientX, e4.clientY);\n      if (!element) return;\n      const clickedEl = e4.composedPath().at(0);\n      if (clickedEl instanceof HTMLElement && clickableElements.includes(clickedEl.id)) {\n        const syntheticEvent = new MouseEvent(e4.type, e4);\n        syntheticEvent.__reactScanSyntheticEvent = true;\n        clickedEl.dispatchEvent(syntheticEvent);\n        return;\n      }\n      const { parentCompositeFiber } = getCompositeComponentFromElement(\n        element\n      );\n      if (!parentCompositeFiber) return;\n      const componentElement = findComponentDOMNode(parentCompositeFiber);\n      if (!componentElement) {\n        refLastHoveredElement.current = null;\n        Store.inspectState.value = {\n          kind: \"inspect-off\"\n        };\n        return;\n      }\n      Store.inspectState.value = {\n        kind: \"focused\",\n        focusedDomElement: componentElement,\n        fiber: parentCompositeFiber\n      };\n    };\n    const handleClick = (e4) => {\n      if (e4.__reactScanSyntheticEvent) {\n        return;\n      }\n      const state = Store.inspectState.peek();\n      const canvas2 = refCanvas.current;\n      if (!canvas2 || !refEventCatcher.current) return;\n      if (isClickInLockIcon(e4, canvas2)) {\n        e4.preventDefault();\n        e4.stopPropagation();\n        handleLockIconClick(state);\n        return;\n      }\n      if (state.kind === \"inspecting\") {\n        handleElementClick(e4);\n      }\n    };\n    const handleKeyDown = (e4) => {\n      if (e4.key !== \"Escape\") return;\n      const state = Store.inspectState.peek();\n      const canvas2 = refCanvas.current;\n      if (!canvas2) return;\n      if (document.activeElement?.id === \"react-scan-root\") {\n        return;\n      }\n      signalWidgetViews.value = {\n        view: \"none\"\n      };\n      if (state.kind === \"focused\" || state.kind === \"inspecting\") {\n        e4.preventDefault();\n        e4.stopPropagation();\n        switch (state.kind) {\n          case \"focused\": {\n            startFadeIn();\n            refCurrentRect.current = null;\n            refLastHoveredElement.current = state.focusedDomElement;\n            Store.inspectState.value = {\n              kind: \"inspecting\",\n              hoveredDomElement: state.focusedDomElement\n            };\n            break;\n          }\n          case \"inspecting\": {\n            startFadeOut(() => {\n              signalIsSettingsOpen.value = false;\n              Store.inspectState.value = {\n                kind: \"inspect-off\"\n              };\n            });\n            break;\n          }\n        }\n      }\n    };\n    const handleStateChange = (state, canvas2, ctx2) => {\n      refCleanupMap.current.get(state.kind)?.();\n      if (refEventCatcher.current) {\n        if (state.kind !== \"inspecting\") {\n          refEventCatcher.current.style.pointerEvents = \"none\";\n        }\n      }\n      if (refRafId.current) {\n        cancelAnimationFrame(refRafId.current);\n      }\n      let unsubReport;\n      switch (state.kind) {\n        case \"inspect-off\":\n          startFadeOut();\n          return;\n        case \"inspecting\":\n          drawHoverOverlay(state.hoveredDomElement, canvas2, ctx2, \"inspecting\");\n          break;\n        case \"focused\":\n          if (!state.focusedDomElement) return;\n          if (refLastHoveredElement.current !== state.focusedDomElement) {\n            refLastHoveredElement.current = state.focusedDomElement;\n          }\n          signalWidgetViews.value = {\n            view: \"inspector\"\n          };\n          drawHoverOverlay(state.focusedDomElement, canvas2, ctx2, \"locked\");\n          unsubReport = Store.lastReportTime.subscribe(() => {\n            if (refRafId.current && refCurrentRect.current) {\n              const { parentCompositeFiber } = getCompositeComponentFromElement(\n                state.focusedDomElement\n              );\n              if (parentCompositeFiber) {\n                drawHoverOverlay(state.focusedDomElement, canvas2, ctx2, \"locked\");\n              }\n            }\n          });\n          if (unsubReport) {\n            refCleanupMap.current.set(state.kind, unsubReport);\n          }\n          break;\n      }\n    };\n    const updateCanvasSize = (canvas2, ctx2) => {\n      const rect = canvas2.getBoundingClientRect();\n      canvas2.width = rect.width * OVERLAY_DPR;\n      canvas2.height = rect.height * OVERLAY_DPR;\n      ctx2.scale(OVERLAY_DPR, OVERLAY_DPR);\n      ctx2.save();\n    };\n    const handleResizeOrScroll = () => {\n      const state = Store.inspectState.peek();\n      const canvas2 = refCanvas.current;\n      if (!canvas2) return;\n      const ctx2 = canvas2?.getContext(\"2d\");\n      if (!ctx2) return;\n      cancelAnimationFrame(refRafId.current);\n      clearTimeout(refTimeout.current);\n      updateCanvasSize(canvas2, ctx2);\n      refCurrentRect.current = null;\n      if (state.kind === \"focused\" && state.focusedDomElement) {\n        drawHoverOverlay(state.focusedDomElement, canvas2, ctx2, \"locked\");\n      } else if (state.kind === \"inspecting\" && state.hoveredDomElement) {\n        drawHoverOverlay(state.hoveredDomElement, canvas2, ctx2, \"inspecting\");\n      }\n    };\n    const handlePointerDown = (e4) => {\n      const state = Store.inspectState.peek();\n      const canvas2 = refCanvas.current;\n      if (!canvas2) return;\n      if (state.kind === \"inspecting\" || isClickInLockIcon(e4, canvas2)) {\n        e4.preventDefault();\n        e4.stopPropagation();\n        e4.stopImmediatePropagation();\n      }\n    };\n    y2(() => {\n      const canvas2 = refCanvas.current;\n      if (!canvas2) return;\n      const ctx2 = canvas2?.getContext(\"2d\");\n      if (!ctx2) return;\n      updateCanvasSize(canvas2, ctx2);\n      const unSubState = Store.inspectState.subscribe((state) => {\n        handleStateChange(state, canvas2, ctx2);\n      });\n      window.addEventListener(\"scroll\", handleResizeOrScroll, { passive: true });\n      window.addEventListener(\"resize\", handleResizeOrScroll, { passive: true });\n      document.addEventListener(\"pointermove\", handlePointerMove, {\n        passive: true,\n        capture: true\n      });\n      document.addEventListener(\"pointerdown\", handlePointerDown, {\n        capture: true\n      });\n      document.addEventListener(\"click\", handleClick, { capture: true });\n      document.addEventListener(\"keydown\", handleKeyDown, { capture: true });\n      return () => {\n        unsubscribeAll();\n        unSubState();\n        window.removeEventListener(\"scroll\", handleResizeOrScroll);\n        window.removeEventListener(\"resize\", handleResizeOrScroll);\n        document.removeEventListener(\"pointermove\", handlePointerMove, {\n          capture: true\n        });\n        document.removeEventListener(\"click\", handleClick, { capture: true });\n        document.removeEventListener(\"pointerdown\", handlePointerDown, {\n          capture: true\n        });\n        document.removeEventListener(\"keydown\", handleKeyDown, { capture: true });\n        if (refRafId.current) {\n          cancelAnimationFrame(refRafId.current);\n        }\n        clearTimeout(refTimeout.current);\n      };\n    }, []);\n    return /* @__PURE__ */ u4(k, { children: [\n      /* @__PURE__ */ u4(\n        \"div\",\n        {\n          ref: refEventCatcher,\n          className: cn2(\"fixed top-0 left-0 w-screen h-screen\", \"z-[214748365]\"),\n          style: {\n            pointerEvents: \"none\"\n          }\n        }\n      ),\n      /* @__PURE__ */ u4(\n        \"canvas\",\n        {\n          ref: refCanvas,\n          dir: \"ltr\",\n          className: cn2(\n            \"react-scan-inspector-overlay\",\n            \"fixed top-0 left-0 w-screen h-screen\",\n            \"pointer-events-none\",\n            \"z-[214748367]\"\n          )\n        }\n      )\n    ] });\n  };\n\n  // src/web/widget/helpers.ts\n  var WindowDimensions = class {\n    constructor(width, height) {\n      this.width = width;\n      this.height = height;\n      this.maxWidth = width - SAFE_AREA * 2;\n      this.maxHeight = height - SAFE_AREA * 2;\n    }\n    rightEdge(width) {\n      return this.width - width - SAFE_AREA;\n    }\n    bottomEdge(height) {\n      return this.height - height - SAFE_AREA;\n    }\n    isFullWidth(width) {\n      return width >= this.maxWidth;\n    }\n    isFullHeight(height) {\n      return height >= this.maxHeight;\n    }\n  };\n  var cachedWindowDimensions;\n  var getWindowDimensions = () => {\n    const currentWidth = window.innerWidth;\n    const currentHeight = window.innerHeight;\n    if (cachedWindowDimensions && cachedWindowDimensions.width === currentWidth && cachedWindowDimensions.height === currentHeight) {\n      return cachedWindowDimensions;\n    }\n    cachedWindowDimensions = new WindowDimensions(currentWidth, currentHeight);\n    return cachedWindowDimensions;\n  };\n  var getOppositeCorner = (position, currentCorner, isFullScreen, isFullWidth, isFullHeight) => {\n    if (isFullScreen) {\n      if (position === \"top-left\") return \"bottom-right\";\n      if (position === \"top-right\") return \"bottom-left\";\n      if (position === \"bottom-left\") return \"top-right\";\n      if (position === \"bottom-right\") return \"top-left\";\n      const [vertical, horizontal] = currentCorner.split(\"-\");\n      if (position === \"left\") return `${vertical}-right`;\n      if (position === \"right\") return `${vertical}-left`;\n      if (position === \"top\") return `bottom-${horizontal}`;\n      if (position === \"bottom\") return `top-${horizontal}`;\n    }\n    if (isFullWidth) {\n      if (position === \"left\")\n        return `${currentCorner.split(\"-\")[0]}-right`;\n      if (position === \"right\")\n        return `${currentCorner.split(\"-\")[0]}-left`;\n    }\n    if (isFullHeight) {\n      if (position === \"top\")\n        return `bottom-${currentCorner.split(\"-\")[1]}`;\n      if (position === \"bottom\")\n        return `top-${currentCorner.split(\"-\")[1]}`;\n    }\n    return currentCorner;\n  };\n  var calculatePosition = (corner, width, height) => {\n    const isRTL = getComputedStyle(document.body).direction === \"rtl\";\n    const windowWidth = window.innerWidth;\n    const windowHeight = window.innerHeight;\n    const isMinimized = width === MIN_SIZE.width;\n    const effectiveWidth = isMinimized ? width : Math.min(width, windowWidth - SAFE_AREA * 2);\n    const effectiveHeight = isMinimized ? height : Math.min(height, windowHeight - SAFE_AREA * 2);\n    let x4;\n    let y4;\n    let leftBound = SAFE_AREA;\n    let rightBound = windowWidth - effectiveWidth - SAFE_AREA;\n    let topBound = SAFE_AREA;\n    let bottomBound = windowHeight - effectiveHeight - SAFE_AREA;\n    switch (corner) {\n      case \"top-right\":\n        x4 = isRTL ? -leftBound : rightBound;\n        y4 = topBound;\n        break;\n      case \"bottom-right\":\n        x4 = isRTL ? -leftBound : rightBound;\n        y4 = bottomBound;\n        break;\n      case \"bottom-left\":\n        x4 = isRTL ? -rightBound : leftBound;\n        y4 = bottomBound;\n        break;\n      case \"top-left\":\n        x4 = isRTL ? -rightBound : leftBound;\n        y4 = topBound;\n        break;\n      default:\n        x4 = leftBound;\n        y4 = topBound;\n        break;\n    }\n    if (isMinimized) {\n      if (isRTL) {\n        x4 = Math.min(\n          -leftBound,\n          Math.max(x4, -rightBound)\n        );\n      } else {\n        x4 = Math.max(\n          leftBound,\n          Math.min(x4, rightBound)\n        );\n      }\n      y4 = Math.max(\n        topBound,\n        Math.min(y4, bottomBound)\n      );\n    }\n    return { x: x4, y: y4 };\n  };\n  var positionMatchesCorner = (position, corner) => {\n    const [vertical, horizontal] = corner.split(\"-\");\n    return position !== vertical && position !== horizontal;\n  };\n  var getHandleVisibility = (position, corner, isFullWidth, isFullHeight) => {\n    if (isFullWidth && isFullHeight) {\n      return true;\n    }\n    if (!isFullWidth && !isFullHeight) {\n      return positionMatchesCorner(position, corner);\n    }\n    if (isFullWidth) {\n      return position !== corner.split(\"-\")[0];\n    }\n    if (isFullHeight) {\n      return position !== corner.split(\"-\")[1];\n    }\n    return false;\n  };\n  var calculateBoundedSize = (currentSize, delta, isWidth) => {\n    const min = isWidth ? MIN_SIZE.width : MIN_SIZE.initialHeight;\n    const max = isWidth ? getWindowDimensions().maxWidth : getWindowDimensions().maxHeight;\n    const newSize = currentSize + delta;\n    return Math.min(Math.max(min, newSize), max);\n  };\n  var calculateNewSizeAndPosition = (position, initialSize, initialPosition, deltaX, deltaY) => {\n    const isRTL = getComputedStyle(document.body).direction === \"rtl\";\n    const maxWidth = window.innerWidth - SAFE_AREA * 2;\n    const maxHeight = window.innerHeight - SAFE_AREA * 2;\n    let newWidth = initialSize.width;\n    let newHeight = initialSize.height;\n    let newX = initialPosition.x;\n    let newY = initialPosition.y;\n    if (isRTL && position.includes(\"right\")) {\n      const availableWidth = -initialPosition.x + initialSize.width - SAFE_AREA;\n      const proposedWidth = Math.min(initialSize.width + deltaX, availableWidth);\n      newWidth = Math.min(maxWidth, Math.max(MIN_SIZE.width, proposedWidth));\n      newX = initialPosition.x + (newWidth - initialSize.width);\n    }\n    if (isRTL && position.includes(\"left\")) {\n      const availableWidth = window.innerWidth - initialPosition.x - SAFE_AREA;\n      const proposedWidth = Math.min(initialSize.width - deltaX, availableWidth);\n      newWidth = Math.min(maxWidth, Math.max(MIN_SIZE.width, proposedWidth));\n    }\n    if (!isRTL && position.includes(\"right\")) {\n      const availableWidth = window.innerWidth - initialPosition.x - SAFE_AREA;\n      const proposedWidth = Math.min(initialSize.width + deltaX, availableWidth);\n      newWidth = Math.min(maxWidth, Math.max(MIN_SIZE.width, proposedWidth));\n    }\n    if (!isRTL && position.includes(\"left\")) {\n      const availableWidth = initialPosition.x + initialSize.width - SAFE_AREA;\n      const proposedWidth = Math.min(initialSize.width - deltaX, availableWidth);\n      newWidth = Math.min(maxWidth, Math.max(MIN_SIZE.width, proposedWidth));\n      newX = initialPosition.x - (newWidth - initialSize.width);\n    }\n    if (position.includes(\"bottom\")) {\n      const availableHeight = window.innerHeight - initialPosition.y - SAFE_AREA;\n      const proposedHeight = Math.min(\n        initialSize.height + deltaY,\n        availableHeight\n      );\n      newHeight = Math.min(\n        maxHeight,\n        Math.max(MIN_SIZE.initialHeight, proposedHeight)\n      );\n    }\n    if (position.includes(\"top\")) {\n      const availableHeight = initialPosition.y + initialSize.height - SAFE_AREA;\n      const proposedHeight = Math.min(\n        initialSize.height - deltaY,\n        availableHeight\n      );\n      newHeight = Math.min(\n        maxHeight,\n        Math.max(MIN_SIZE.initialHeight, proposedHeight)\n      );\n      newY = initialPosition.y - (newHeight - initialSize.height);\n    }\n    let leftBound = SAFE_AREA;\n    let rightBound = window.innerWidth - SAFE_AREA - newWidth;\n    let topBound = SAFE_AREA;\n    let bottomBound = window.innerHeight - SAFE_AREA - newHeight;\n    if (isRTL) {\n      newX = Math.min(\n        -leftBound,\n        Math.max(newX, -rightBound)\n      );\n    } else {\n      newX = Math.max(\n        leftBound,\n        Math.min(newX, rightBound)\n      );\n    }\n    newY = Math.max(\n      topBound,\n      Math.min(newY, bottomBound)\n    );\n    return {\n      newSize: { width: newWidth, height: newHeight },\n      newPosition: { x: newX, y: newY }\n    };\n  };\n  var getClosestCorner = (position) => {\n    const windowDims = getWindowDimensions();\n    const distances = {\n      \"top-left\": Math.hypot(position.x, position.y),\n      \"top-right\": Math.hypot(windowDims.maxWidth - position.x, position.y),\n      \"bottom-left\": Math.hypot(position.x, windowDims.maxHeight - position.y),\n      \"bottom-right\": Math.hypot(\n        windowDims.maxWidth - position.x,\n        windowDims.maxHeight - position.y\n      )\n    };\n    let closest = \"top-left\";\n    for (const key in distances) {\n      if (distances[key] < distances[closest]) {\n        closest = key;\n      }\n    }\n    return closest;\n  };\n  var getBestCorner = (mouseX, mouseY, initialMouseX, initialMouseY, threshold = 100) => {\n    const deltaX = initialMouseX !== void 0 ? mouseX - initialMouseX : 0;\n    const deltaY = initialMouseY !== void 0 ? mouseY - initialMouseY : 0;\n    const windowCenterX = window.innerWidth / 2;\n    const windowCenterY = window.innerHeight / 2;\n    const movingRight = deltaX > threshold;\n    const movingLeft = deltaX < -threshold;\n    const movingDown = deltaY > threshold;\n    const movingUp = deltaY < -threshold;\n    if (movingRight || movingLeft) {\n      const isBottom = mouseY > windowCenterY;\n      return movingRight ? isBottom ? \"bottom-right\" : \"top-right\" : isBottom ? \"bottom-left\" : \"top-left\";\n    }\n    if (movingDown || movingUp) {\n      const isRight = mouseX > windowCenterX;\n      return movingDown ? isRight ? \"bottom-right\" : \"bottom-left\" : isRight ? \"top-right\" : \"top-left\";\n    }\n    return mouseX > windowCenterX ? mouseY > windowCenterY ? \"bottom-right\" : \"top-right\" : mouseY > windowCenterY ? \"bottom-left\" : \"top-left\";\n  };\n\n  // src/web/widget/resize-handle.tsx\n  var ResizeHandle = ({ position }) => {\n    const refContainer = A2(null);\n    const prevWidth = A2(null);\n    const prevHeight = A2(null);\n    const prevCorner = A2(null);\n    y2(() => {\n      const container = refContainer.current;\n      if (!container) return;\n      const updateVisibility = () => {\n        container.classList.remove(\"pointer-events-none\");\n        const isFocused = Store.inspectState.value.kind === \"focused\";\n        const shouldShow = signalWidgetViews.value.view !== \"none\";\n        const isVisible = (isFocused || shouldShow) && getHandleVisibility(\n          position,\n          signalWidget.value.corner,\n          signalWidget.value.dimensions.isFullWidth,\n          signalWidget.value.dimensions.isFullHeight\n        );\n        if (isVisible) {\n          container.classList.remove(\n            \"hidden\",\n            \"pointer-events-none\",\n            \"opacity-0\"\n          );\n        } else {\n          container.classList.add(\"hidden\", \"pointer-events-none\", \"opacity-0\");\n        }\n      };\n      const unsubscribeSignalWidget = signalWidget.subscribe((state) => {\n        if (prevWidth.current !== null && prevHeight.current !== null && prevCorner.current !== null && state.dimensions.width === prevWidth.current && state.dimensions.height === prevHeight.current && state.corner === prevCorner.current) {\n          return;\n        }\n        updateVisibility();\n        prevWidth.current = state.dimensions.width;\n        prevHeight.current = state.dimensions.height;\n        prevCorner.current = state.corner;\n      });\n      const unsubscribeInspectState = Store.inspectState.subscribe(() => {\n        updateVisibility();\n      });\n      return () => {\n        unsubscribeSignalWidget();\n        unsubscribeInspectState();\n        prevWidth.current = null;\n        prevHeight.current = null;\n        prevCorner.current = null;\n      };\n    }, []);\n    const handleResize = q2(\n      (e4) => {\n        e4.preventDefault();\n        e4.stopPropagation();\n        const widget = signalRefWidget.value;\n        if (!widget) return;\n        const containerStyle = widget.style;\n        const { dimensions } = signalWidget.value;\n        const initialX = e4.clientX;\n        const initialY = e4.clientY;\n        const initialWidth = dimensions.width;\n        const initialHeight = dimensions.height;\n        const initialPosition = dimensions.position;\n        signalWidget.value = {\n          ...signalWidget.value,\n          dimensions: {\n            ...dimensions,\n            isFullWidth: false,\n            isFullHeight: false,\n            width: initialWidth,\n            height: initialHeight,\n            position: initialPosition\n          }\n        };\n        let rafId = null;\n        const handlePointerMove = (e5) => {\n          if (rafId) return;\n          containerStyle.transition = \"none\";\n          rafId = requestAnimationFrame(() => {\n            const { newSize, newPosition } = calculateNewSizeAndPosition(\n              position,\n              { width: initialWidth, height: initialHeight },\n              initialPosition,\n              e5.clientX - initialX,\n              e5.clientY - initialY\n            );\n            containerStyle.transform = `translate3d(${newPosition.x}px, ${newPosition.y}px, 0)`;\n            containerStyle.width = `${newSize.width}px`;\n            containerStyle.height = `${newSize.height}px`;\n            const maxTreeWidth = Math.floor(newSize.width - MIN_CONTAINER_WIDTH / 2);\n            const currentTreeWidth = signalWidget.value.componentsTree.width;\n            const newTreeWidth = Math.min(\n              maxTreeWidth,\n              Math.max(MIN_CONTAINER_WIDTH, currentTreeWidth)\n            );\n            signalWidget.value = {\n              ...signalWidget.value,\n              dimensions: {\n                isFullWidth: false,\n                isFullHeight: false,\n                width: newSize.width,\n                height: newSize.height,\n                position: newPosition\n              },\n              componentsTree: {\n                ...signalWidget.value.componentsTree,\n                width: newTreeWidth\n              }\n            };\n            rafId = null;\n          });\n        };\n        const handlePointerUp = () => {\n          if (rafId) {\n            cancelAnimationFrame(rafId);\n            rafId = null;\n          }\n          document.removeEventListener(\"pointermove\", handlePointerMove);\n          document.removeEventListener(\"pointerup\", handlePointerUp);\n          const { dimensions: dimensions2, corner } = signalWidget.value;\n          const windowDims = getWindowDimensions();\n          const isCurrentFullWidth = windowDims.isFullWidth(dimensions2.width);\n          const isCurrentFullHeight = windowDims.isFullHeight(dimensions2.height);\n          const isFullScreen = isCurrentFullWidth && isCurrentFullHeight;\n          let newCorner = corner;\n          if (isFullScreen || isCurrentFullWidth || isCurrentFullHeight) {\n            newCorner = getClosestCorner(dimensions2.position);\n          }\n          const newPosition = calculatePosition(\n            newCorner,\n            dimensions2.width,\n            dimensions2.height\n          );\n          const onTransitionEnd = () => {\n            widget.removeEventListener(\"transitionend\", onTransitionEnd);\n          };\n          widget.addEventListener(\"transitionend\", onTransitionEnd);\n          containerStyle.transform = `translate3d(${newPosition.x}px, ${newPosition.y}px, 0)`;\n          signalWidget.value = {\n            ...signalWidget.value,\n            corner: newCorner,\n            dimensions: {\n              isFullWidth: isCurrentFullWidth,\n              isFullHeight: isCurrentFullHeight,\n              width: dimensions2.width,\n              height: dimensions2.height,\n              position: newPosition\n            },\n            lastDimensions: {\n              isFullWidth: isCurrentFullWidth,\n              isFullHeight: isCurrentFullHeight,\n              width: dimensions2.width,\n              height: dimensions2.height,\n              position: newPosition\n            }\n          };\n          saveLocalStorage(LOCALSTORAGE_KEY, {\n            corner: newCorner,\n            dimensions: signalWidget.value.dimensions,\n            lastDimensions: signalWidget.value.lastDimensions,\n            componentsTree: signalWidget.value.componentsTree\n          });\n        };\n        document.addEventListener(\"pointermove\", handlePointerMove, {\n          passive: true\n        });\n        document.addEventListener(\"pointerup\", handlePointerUp);\n      },\n      []\n    );\n    const handleDoubleClick = q2(\n      (e4) => {\n        e4.preventDefault();\n        e4.stopPropagation();\n        const widget = signalRefWidget.value;\n        if (!widget) return;\n        const containerStyle = widget.style;\n        const { dimensions, corner } = signalWidget.value;\n        const windowDims = getWindowDimensions();\n        const isCurrentFullWidth = windowDims.isFullWidth(dimensions.width);\n        const isCurrentFullHeight = windowDims.isFullHeight(dimensions.height);\n        const isFullScreen = isCurrentFullWidth && isCurrentFullHeight;\n        const isPartiallyMaximized = (isCurrentFullWidth || isCurrentFullHeight) && !isFullScreen;\n        let newWidth = dimensions.width;\n        let newHeight = dimensions.height;\n        const newCorner = getOppositeCorner(\n          position,\n          corner,\n          isFullScreen,\n          isCurrentFullWidth,\n          isCurrentFullHeight\n        );\n        if (position === \"left\" || position === \"right\") {\n          newWidth = isCurrentFullWidth ? dimensions.width : windowDims.maxWidth;\n          if (isPartiallyMaximized) {\n            newWidth = isCurrentFullWidth ? MIN_SIZE.width : windowDims.maxWidth;\n          }\n        } else {\n          newHeight = isCurrentFullHeight ? dimensions.height : windowDims.maxHeight;\n          if (isPartiallyMaximized) {\n            newHeight = isCurrentFullHeight ? MIN_SIZE.initialHeight : windowDims.maxHeight;\n          }\n        }\n        if (isFullScreen) {\n          if (position === \"left\" || position === \"right\") {\n            newWidth = MIN_SIZE.width;\n          } else {\n            newHeight = MIN_SIZE.initialHeight;\n          }\n        }\n        const newPosition = calculatePosition(newCorner, newWidth, newHeight);\n        const newDimensions = {\n          isFullWidth: windowDims.isFullWidth(newWidth),\n          isFullHeight: windowDims.isFullHeight(newHeight),\n          width: newWidth,\n          height: newHeight,\n          position: newPosition\n        };\n        const maxTreeWidth = Math.floor(newWidth - MIN_SIZE.width / 2);\n        const currentTreeWidth = signalWidget.value.componentsTree.width;\n        const defaultWidth = Math.floor(newWidth * 0.3);\n        const newTreeWidth = isCurrentFullWidth ? MIN_CONTAINER_WIDTH : (position === \"left\" || position === \"right\") && !isCurrentFullWidth ? Math.min(maxTreeWidth, Math.max(MIN_CONTAINER_WIDTH, defaultWidth)) : Math.min(\n          maxTreeWidth,\n          Math.max(MIN_CONTAINER_WIDTH, currentTreeWidth)\n        );\n        requestAnimationFrame(() => {\n          signalWidget.value = {\n            corner: newCorner,\n            dimensions: newDimensions,\n            lastDimensions: dimensions,\n            componentsTree: {\n              ...signalWidget.value.componentsTree,\n              width: newTreeWidth\n            }\n          };\n          containerStyle.transition = \"all 0.25s cubic-bezier(0, 0, 0.2, 1)\";\n          containerStyle.width = `${newWidth}px`;\n          containerStyle.height = `${newHeight}px`;\n          containerStyle.transform = `translate3d(${newPosition.x}px, ${newPosition.y}px, 0)`;\n        });\n        saveLocalStorage(LOCALSTORAGE_KEY, {\n          corner: newCorner,\n          dimensions: newDimensions,\n          lastDimensions: dimensions,\n          componentsTree: {\n            ...signalWidget.value.componentsTree,\n            width: newTreeWidth\n          }\n        });\n      },\n      []\n    );\n    return /* @__PURE__ */ u4(\n      \"div\",\n      {\n        ref: refContainer,\n        onPointerDown: handleResize,\n        onDblClick: handleDoubleClick,\n        className: cn2(\n          \"absolute z-50\",\n          \"flex items-center justify-center\",\n          \"group\",\n          \"transition-colors select-none\",\n          \"peer\",\n          {\n            \"resize-left peer/left\": position === \"left\",\n            \"resize-right peer/right z-10\": position === \"right\",\n            \"resize-top peer/top\": position === \"top\",\n            \"resize-bottom peer/bottom\": position === \"bottom\"\n          }\n        ),\n        children: /* @__PURE__ */ u4(\"span\", { className: \"resize-line-wrapper\", children: /* @__PURE__ */ u4(\"span\", { className: \"resize-line\", children: /* @__PURE__ */ u4(\n          Icon,\n          {\n            name: \"icon-ellipsis\",\n            size: 18,\n            className: cn2(\n              \"text-neutral-400\",\n              (position === \"left\" || position === \"right\") && \"rotate-90\"\n            )\n          }\n        ) }) })\n      }\n    );\n  };\n\n  // src/web/widget/index.tsx\n  var COLLAPSED_SIZE = {\n    horizontal: { width: 20, height: 48 },\n    vertical: { width: 48, height: 20 }\n  };\n  var Widget = () => {\n    const refWidget = A2(null);\n    const refShouldOpen = A2(false);\n    const refInitialMinimizedWidth = A2(0);\n    const refInitialMinimizedHeight = A2(0);\n    const refExpandingFromCollapsed = A2(false);\n    const updateWidgetPosition = q2((shouldSave = true) => {\n      if (!refWidget.current) return;\n      const { corner } = signalWidget.value;\n      let newWidth;\n      let newHeight;\n      if (signalWidgetCollapsed.value) {\n        const orientation = signalWidgetCollapsed.value.orientation || \"horizontal\";\n        const size = COLLAPSED_SIZE[orientation];\n        newWidth = size.width;\n        newHeight = size.height;\n      } else if (refShouldOpen.current) {\n        const lastDims = signalWidget.value.lastDimensions;\n        newWidth = calculateBoundedSize(lastDims.width, 0, true);\n        newHeight = calculateBoundedSize(lastDims.height, 0, false);\n        if (refExpandingFromCollapsed.current) {\n          refExpandingFromCollapsed.current = false;\n        }\n      } else {\n        newWidth = refInitialMinimizedWidth.current;\n        newHeight = refInitialMinimizedHeight.current;\n      }\n      const newPosition = calculatePosition(corner, newWidth, newHeight);\n      let finalPosition = newPosition;\n      if (signalWidgetCollapsed.value) {\n        const { corner: collapsedCorner, orientation = \"horizontal\" } = signalWidgetCollapsed.value;\n        const size = COLLAPSED_SIZE[orientation];\n        switch (collapsedCorner) {\n          case \"top-left\":\n            finalPosition = orientation === \"horizontal\" ? { x: -1, y: SAFE_AREA } : { x: SAFE_AREA, y: -1 };\n            break;\n          case \"bottom-left\":\n            finalPosition = orientation === \"horizontal\" ? { x: -1, y: window.innerHeight - size.height - SAFE_AREA } : { x: SAFE_AREA, y: window.innerHeight - size.height + 1 };\n            break;\n          case \"top-right\":\n            finalPosition = orientation === \"horizontal\" ? { x: window.innerWidth - size.width + 1, y: SAFE_AREA } : { x: window.innerWidth - size.width - SAFE_AREA, y: -1 };\n            break;\n          case \"bottom-right\":\n          default:\n            finalPosition = orientation === \"horizontal\" ? {\n              x: window.innerWidth - size.width + 1,\n              y: window.innerHeight - size.height - SAFE_AREA\n            } : {\n              x: window.innerWidth - size.width - SAFE_AREA,\n              y: window.innerHeight - size.height + 1\n            };\n            break;\n        }\n      }\n      const isTooSmall = newWidth < MIN_SIZE.width || newHeight < MIN_SIZE.initialHeight;\n      const shouldPersist = shouldSave && !isTooSmall;\n      const container = refWidget.current;\n      const containerStyle = container.style;\n      let rafId = null;\n      const onTransitionEnd = () => {\n        updateDimensions();\n        container.removeEventListener(\"transitionend\", onTransitionEnd);\n        if (rafId) {\n          cancelAnimationFrame(rafId);\n          rafId = null;\n        }\n      };\n      container.addEventListener(\"transitionend\", onTransitionEnd);\n      containerStyle.transition = \"all 0.25s cubic-bezier(0, 0, 0.2, 1)\";\n      rafId = requestAnimationFrame(() => {\n        containerStyle.width = `${newWidth}px`;\n        containerStyle.height = `${newHeight}px`;\n        containerStyle.transform = `translate3d(${finalPosition.x}px, ${finalPosition.y}px, 0)`;\n        rafId = null;\n      });\n      const newDimensions = {\n        isFullWidth: newWidth >= window.innerWidth - SAFE_AREA * 2,\n        isFullHeight: newHeight >= window.innerHeight - SAFE_AREA * 2,\n        width: newWidth,\n        height: newHeight,\n        position: finalPosition\n      };\n      signalWidget.value = {\n        corner,\n        dimensions: newDimensions,\n        lastDimensions: refShouldOpen ? signalWidget.value.lastDimensions : newWidth > refInitialMinimizedWidth.current ? newDimensions : signalWidget.value.lastDimensions,\n        componentsTree: signalWidget.value.componentsTree\n      };\n      if (shouldPersist) {\n        saveLocalStorage(LOCALSTORAGE_KEY, {\n          corner: signalWidget.value.corner,\n          dimensions: signalWidget.value.dimensions,\n          lastDimensions: signalWidget.value.lastDimensions,\n          componentsTree: signalWidget.value.componentsTree\n        });\n      }\n      updateDimensions();\n    }, []);\n    const handleDrag = q2(\n      (e4) => {\n        e4.preventDefault();\n        if (!refWidget.current || e4.target.closest(\"button\"))\n          return;\n        const container = refWidget.current;\n        const containerStyle = container.style;\n        const { dimensions } = signalWidget.value;\n        const initialMouseX = e4.clientX;\n        const initialMouseY = e4.clientY;\n        const initialX = dimensions.position.x;\n        const initialY = dimensions.position.y;\n        let currentX = initialX;\n        let currentY = initialY;\n        let rafId = null;\n        let hasMoved = false;\n        let lastMouseX = initialMouseX;\n        let lastMouseY = initialMouseY;\n        const handlePointerMove = (e5) => {\n          if (rafId) return;\n          hasMoved = true;\n          lastMouseX = e5.clientX;\n          lastMouseY = e5.clientY;\n          rafId = requestAnimationFrame(() => {\n            const deltaX = lastMouseX - initialMouseX;\n            const deltaY = lastMouseY - initialMouseY;\n            currentX = Number(initialX) + deltaX;\n            currentY = Number(initialY) + deltaY;\n            containerStyle.transition = \"none\";\n            containerStyle.transform = `translate3d(${currentX}px, ${currentY}px, 0)`;\n            const widgetRight = currentX + dimensions.width;\n            const widgetBottom = currentY + dimensions.height;\n            const outsideLeft = Math.max(0, -currentX);\n            const outsideRight = Math.max(0, widgetRight - window.innerWidth);\n            const outsideTop = Math.max(0, -currentY);\n            const outsideBottom = Math.max(0, widgetBottom - window.innerHeight);\n            const horizontalOutside = Math.min(\n              dimensions.width,\n              outsideLeft + outsideRight\n            );\n            const verticalOutside = Math.min(\n              dimensions.height,\n              outsideTop + outsideBottom\n            );\n            const areaOutside = horizontalOutside * dimensions.height + verticalOutside * dimensions.width - horizontalOutside * verticalOutside;\n            const totalArea = dimensions.width * dimensions.height;\n            let shouldCollapse = areaOutside > totalArea * 0.35;\n            if (!shouldCollapse && ReactScanInternals.options.value.showFPS) {\n              const fpsRight = currentX + dimensions.width;\n              const fpsLeft = fpsRight - 100;\n              const fpsFullyOutside = fpsRight <= 0 || fpsLeft >= window.innerWidth || currentY + dimensions.height <= 0 || currentY >= window.innerHeight;\n              shouldCollapse = fpsFullyOutside;\n            }\n            if (shouldCollapse) {\n              const widgetCenterX = currentX + dimensions.width / 2;\n              const widgetCenterY = currentY + dimensions.height / 2;\n              const screenCenterX = window.innerWidth / 2;\n              const screenCenterY = window.innerHeight / 2;\n              let targetCorner;\n              if (widgetCenterX < screenCenterX) {\n                targetCorner = widgetCenterY < screenCenterY ? \"top-left\" : \"bottom-left\";\n              } else {\n                targetCorner = widgetCenterY < screenCenterY ? \"top-right\" : \"bottom-right\";\n              }\n              let orientation;\n              const horizontalOverflow = Math.max(outsideLeft, outsideRight);\n              const verticalOverflow = Math.max(outsideTop, outsideBottom);\n              orientation = horizontalOverflow > verticalOverflow ? \"horizontal\" : \"vertical\";\n              signalWidget.value = {\n                ...signalWidget.value,\n                corner: targetCorner,\n                lastDimensions: {\n                  ...dimensions,\n                  position: calculatePosition(\n                    targetCorner,\n                    dimensions.width,\n                    dimensions.height\n                  )\n                }\n              };\n              const collapsedPosition = {\n                corner: targetCorner,\n                orientation\n              };\n              signalWidgetCollapsed.value = collapsedPosition;\n              saveLocalStorage(LOCALSTORAGE_COLLAPSED_KEY, collapsedPosition);\n              saveLocalStorage(LOCALSTORAGE_KEY, signalWidget.value);\n              updateWidgetPosition(false);\n              document.removeEventListener(\"pointermove\", handlePointerMove);\n              document.removeEventListener(\"pointerup\", handlePointerEnd);\n              if (rafId) {\n                cancelAnimationFrame(rafId);\n                rafId = null;\n              }\n            }\n            rafId = null;\n          });\n        };\n        const handlePointerEnd = () => {\n          if (!container) return;\n          if (rafId) {\n            cancelAnimationFrame(rafId);\n            rafId = null;\n          }\n          document.removeEventListener(\"pointermove\", handlePointerMove);\n          document.removeEventListener(\"pointerup\", handlePointerEnd);\n          const totalDeltaX = Math.abs(lastMouseX - initialMouseX);\n          const totalDeltaY = Math.abs(lastMouseY - initialMouseY);\n          const totalMovement = Math.sqrt(\n            totalDeltaX * totalDeltaX + totalDeltaY * totalDeltaY\n          );\n          if (!hasMoved || totalMovement < 60) return;\n          const newCorner = getBestCorner(\n            lastMouseX,\n            lastMouseY,\n            initialMouseX,\n            initialMouseY,\n            Store.inspectState.value.kind === \"focused\" ? 80 : 40\n          );\n          if (newCorner === signalWidget.value.corner) {\n            containerStyle.transition = \"transform 0.25s cubic-bezier(0, 0, 0.2, 1)\";\n            const currentPosition = signalWidget.value.dimensions.position;\n            requestAnimationFrame(() => {\n              containerStyle.transform = `translate3d(${currentPosition.x}px, ${currentPosition.y}px, 0)`;\n            });\n            return;\n          }\n          const snappedPosition = calculatePosition(\n            newCorner,\n            dimensions.width,\n            dimensions.height\n          );\n          if (currentX === initialX && currentY === initialY) return;\n          const onTransitionEnd = () => {\n            containerStyle.transition = \"none\";\n            updateDimensions();\n            container.removeEventListener(\"transitionend\", onTransitionEnd);\n            if (rafId) {\n              cancelAnimationFrame(rafId);\n              rafId = null;\n            }\n          };\n          container.addEventListener(\"transitionend\", onTransitionEnd);\n          containerStyle.transition = \"transform 0.25s cubic-bezier(0, 0, 0.2, 1)\";\n          requestAnimationFrame(() => {\n            containerStyle.transform = `translate3d(${snappedPosition.x}px, ${snappedPosition.y}px, 0)`;\n          });\n          signalWidget.value = {\n            corner: newCorner,\n            dimensions: {\n              isFullWidth: dimensions.isFullWidth,\n              isFullHeight: dimensions.isFullHeight,\n              width: dimensions.width,\n              height: dimensions.height,\n              position: snappedPosition\n            },\n            lastDimensions: signalWidget.value.lastDimensions,\n            componentsTree: signalWidget.value.componentsTree\n          };\n          saveLocalStorage(LOCALSTORAGE_KEY, {\n            corner: newCorner,\n            dimensions: signalWidget.value.dimensions,\n            lastDimensions: signalWidget.value.lastDimensions,\n            componentsTree: signalWidget.value.componentsTree\n          });\n        };\n        document.addEventListener(\"pointermove\", handlePointerMove);\n        document.addEventListener(\"pointerup\", handlePointerEnd);\n      },\n      []\n    );\n    const handleCollapsedDrag = q2(\n      (e4) => {\n        e4.preventDefault();\n        if (!refWidget.current || !signalWidgetCollapsed.value) return;\n        const { corner: collapsedCorner, orientation = \"horizontal\" } = signalWidgetCollapsed.value;\n        const initialMouseX = e4.clientX;\n        const initialMouseY = e4.clientY;\n        let rafId = null;\n        let hasExpanded = false;\n        const DRAG_THRESHOLD = 50;\n        const handlePointerMove = (e5) => {\n          if (hasExpanded || rafId) return;\n          const deltaX = e5.clientX - initialMouseX;\n          const deltaY = e5.clientY - initialMouseY;\n          let shouldExpand = false;\n          if (orientation === \"horizontal\") {\n            if (collapsedCorner.endsWith(\"left\") && deltaX > DRAG_THRESHOLD) {\n              shouldExpand = true;\n            } else if (collapsedCorner.endsWith(\"right\") && deltaX < -DRAG_THRESHOLD) {\n              shouldExpand = true;\n            }\n          } else {\n            if (collapsedCorner.startsWith(\"top\") && deltaY > DRAG_THRESHOLD) {\n              shouldExpand = true;\n            } else if (collapsedCorner.startsWith(\"bottom\") && deltaY < -DRAG_THRESHOLD) {\n              shouldExpand = true;\n            }\n          }\n          if (shouldExpand) {\n            hasExpanded = true;\n            signalWidgetCollapsed.value = null;\n            saveLocalStorage(LOCALSTORAGE_COLLAPSED_KEY, null);\n            if (refInitialMinimizedWidth.current === 0 && refWidget.current) {\n              requestAnimationFrame(() => {\n                if (refWidget.current) {\n                  refWidget.current.style.width = \"min-content\";\n                  const naturalWidth = refWidget.current.offsetWidth;\n                  refInitialMinimizedWidth.current = naturalWidth || 300;\n                  const lastDims = signalWidget.value.lastDimensions;\n                  const targetWidth = calculateBoundedSize(\n                    lastDims.width,\n                    0,\n                    true\n                  );\n                  const targetHeight = calculateBoundedSize(\n                    lastDims.height,\n                    0,\n                    false\n                  );\n                  let newX = e5.clientX - targetWidth / 2;\n                  let newY = e5.clientY - targetHeight / 2;\n                  newX = Math.max(\n                    SAFE_AREA,\n                    Math.min(newX, window.innerWidth - targetWidth - SAFE_AREA)\n                  );\n                  newY = Math.max(\n                    SAFE_AREA,\n                    Math.min(newY, window.innerHeight - targetHeight - SAFE_AREA)\n                  );\n                  signalWidget.value = {\n                    ...signalWidget.value,\n                    dimensions: {\n                      ...signalWidget.value.dimensions,\n                      position: { x: newX, y: newY }\n                    }\n                  };\n                  updateWidgetPosition(true);\n                  const savedView = readLocalStorage(\n                    LOCALSTORAGE_LAST_VIEW_KEY\n                  );\n                  signalWidgetViews.value = savedView || { view: \"none\" };\n                  setTimeout(() => {\n                    if (refWidget.current) {\n                      const dragEvent = new PointerEvent(\"pointerdown\", {\n                        clientX: e5.clientX,\n                        clientY: e5.clientY,\n                        pointerId: e5.pointerId,\n                        bubbles: true\n                      });\n                      refWidget.current.dispatchEvent(dragEvent);\n                    }\n                  }, 100);\n                }\n              });\n            } else {\n              updateWidgetPosition(true);\n              const savedView = readLocalStorage(\n                LOCALSTORAGE_LAST_VIEW_KEY\n              );\n              signalWidgetViews.value = savedView || { view: \"none\" };\n            }\n            document.removeEventListener(\"pointermove\", handlePointerMove);\n            document.removeEventListener(\"pointerup\", handlePointerEnd);\n          }\n        };\n        const handlePointerEnd = () => {\n          document.removeEventListener(\"pointermove\", handlePointerMove);\n          document.removeEventListener(\"pointerup\", handlePointerEnd);\n        };\n        document.addEventListener(\"pointermove\", handlePointerMove);\n        document.addEventListener(\"pointerup\", handlePointerEnd);\n      },\n      []\n    );\n    y2(() => {\n      if (!refWidget.current) return;\n      removeLocalStorage(LOCALSTORAGE_LAST_VIEW_KEY);\n      if (!signalWidgetCollapsed.value) {\n        refWidget.current.style.width = \"min-content\";\n        refInitialMinimizedHeight.current = 36;\n        refInitialMinimizedWidth.current = refWidget.current.offsetWidth;\n      } else {\n        refInitialMinimizedHeight.current = 36;\n        refInitialMinimizedWidth.current = 0;\n      }\n      refWidget.current.style.maxWidth = `calc(100vw - ${SAFE_AREA * 2}px)`;\n      refWidget.current.style.maxHeight = `calc(100vh - ${SAFE_AREA * 2}px)`;\n      updateWidgetPosition();\n      if (Store.inspectState.value.kind !== \"focused\" && !signalWidgetCollapsed.value && !refExpandingFromCollapsed.current) {\n        signalWidget.value = {\n          ...signalWidget.value,\n          dimensions: {\n            isFullWidth: false,\n            isFullHeight: false,\n            width: refInitialMinimizedWidth.current,\n            height: refInitialMinimizedHeight.current,\n            position: signalWidget.value.dimensions.position\n          }\n        };\n      }\n      signalRefWidget.value = refWidget.current;\n      const unsubscribeSignalWidget = signalWidget.subscribe((widget) => {\n        if (!refWidget.current) return;\n        const { x: x4, y: y4 } = widget.dimensions.position;\n        const { width, height } = widget.dimensions;\n        const container = refWidget.current;\n        requestAnimationFrame(() => {\n          container.style.transform = `translate3d(${x4}px, ${y4}px, 0)`;\n          container.style.width = `${width}px`;\n          container.style.height = `${height}px`;\n        });\n      });\n      const unsubscribeSignalWidgetViews = signalWidgetViews.subscribe(\n        (state) => {\n          refShouldOpen.current = state.view !== \"none\";\n          updateWidgetPosition();\n          if (!signalWidgetCollapsed.value) {\n            if (state.view !== \"none\") {\n              saveLocalStorage(LOCALSTORAGE_LAST_VIEW_KEY, state);\n            } else {\n              removeLocalStorage(LOCALSTORAGE_LAST_VIEW_KEY);\n            }\n          }\n        }\n      );\n      const unsubscribeStoreInspectState = Store.inspectState.subscribe(\n        (state) => {\n          refShouldOpen.current = state.kind === \"focused\";\n          updateWidgetPosition();\n        }\n      );\n      const handleWindowResize = () => {\n        updateWidgetPosition(true);\n      };\n      window.addEventListener(\"resize\", handleWindowResize, { passive: true });\n      return () => {\n        window.removeEventListener(\"resize\", handleWindowResize);\n        unsubscribeSignalWidgetViews();\n        unsubscribeStoreInspectState();\n        unsubscribeSignalWidget();\n        saveLocalStorage(LOCALSTORAGE_KEY, {\n          ...defaultWidgetConfig,\n          corner: signalWidget.value.corner\n        });\n      };\n    }, []);\n    const [_4, setTriggerRender] = d2(false);\n    y2(() => {\n      setTriggerRender(true);\n    }, []);\n    const isCollapsed = signalWidgetCollapsed.value;\n    let arrowRotationClass = \"\";\n    if (isCollapsed) {\n      const { orientation = \"horizontal\", corner } = isCollapsed;\n      if (orientation === \"horizontal\") {\n        arrowRotationClass = corner?.endsWith(\"right\") ? \"rotate-180\" : \"\";\n      } else {\n        arrowRotationClass = corner?.startsWith(\"bottom\") ? \"-rotate-90\" : \"rotate-90\";\n      }\n    }\n    return /* @__PURE__ */ u4(k, { children: [\n      /* @__PURE__ */ u4(ScanOverlay, {}),\n      /* @__PURE__ */ u4(ToolbarElementContext.Provider, { value: refWidget.current, children: /* @__PURE__ */ u4(\n        \"div\",\n        {\n          id: \"react-scan-toolbar\",\n          dir: \"ltr\",\n          ref: refWidget,\n          onPointerDown: !isCollapsed ? handleDrag : handleCollapsedDrag,\n          className: cn2(\n            \"fixed inset-0\",\n            isCollapsed ? (() => {\n              const { orientation = \"horizontal\", corner } = isCollapsed;\n              if (orientation === \"horizontal\") {\n                return corner?.endsWith(\"right\") ? \"rounded-tl-lg rounded-bl-lg shadow-lg\" : \"rounded-tr-lg rounded-br-lg shadow-lg\";\n              } else {\n                return corner?.startsWith(\"bottom\") ? \"rounded-tl-lg rounded-tr-lg shadow-lg\" : \"rounded-bl-lg rounded-br-lg shadow-lg\";\n              }\n            })() : \"rounded-lg shadow-lg\",\n            \"flex flex-col\",\n            \"font-mono text-[13px]\",\n            \"user-select-none\",\n            \"opacity-0\",\n            isCollapsed ? \"cursor-pointer\" : \"cursor-move\",\n            \"z-[124124124124]\",\n            \"animate-fade-in animation-duration-300 animation-delay-300\",\n            \"will-change-transform\",\n            \"[touch-action:none]\"\n          ),\n          style: { WebkitAppRegion: \"no-drag\" },\n          children: isCollapsed ? /* @__PURE__ */ u4(\n            \"button\",\n            {\n              type: \"button\",\n              onClick: () => {\n                signalWidgetCollapsed.value = null;\n                saveLocalStorage(LOCALSTORAGE_COLLAPSED_KEY, null);\n                if (refInitialMinimizedWidth.current === 0 && refWidget.current) {\n                  requestAnimationFrame(() => {\n                    if (refWidget.current) {\n                      refWidget.current.style.width = \"min-content\";\n                      const naturalWidth = refWidget.current.offsetWidth;\n                      refInitialMinimizedWidth.current = naturalWidth || 300;\n                      updateWidgetPosition(true);\n                    }\n                  });\n                }\n                const savedView = readLocalStorage(\n                  LOCALSTORAGE_LAST_VIEW_KEY\n                );\n                signalWidgetViews.value = savedView || { view: \"none\" };\n              },\n              className: \"flex items-center justify-center w-full h-full text-white\",\n              title: \"Expand toolbar\",\n              children: /* @__PURE__ */ u4(\n                Icon,\n                {\n                  name: \"icon-chevron-right\",\n                  size: 16,\n                  className: cn2(\"transition-transform\", arrowRotationClass)\n                }\n              )\n            }\n          ) : /* @__PURE__ */ u4(k, { children: [\n            /* @__PURE__ */ u4(ResizeHandle, { position: \"top\" }),\n            /* @__PURE__ */ u4(ResizeHandle, { position: \"bottom\" }),\n            /* @__PURE__ */ u4(ResizeHandle, { position: \"left\" }),\n            /* @__PURE__ */ u4(ResizeHandle, { position: \"right\" }),\n            /* @__PURE__ */ u4(Content, {})\n          ] })\n        }\n      ) })\n    ] });\n  };\n  var ToolbarElementContext = K(null);\n\n  // src/web/components/svg-sprite/index.tsx\n  var SvgSprite = () => {\n    return /* @__PURE__ */ u4(\"svg\", { xmlns: \"http://www.w3.org/2000/svg\", style: \"display: none;\", children: [\n      /* @__PURE__ */ u4(\"title\", { children: \"React Scan Icons\" }),\n      /* @__PURE__ */ u4(\"symbol\", { id: \"icon-inspect\", viewBox: \"0 0 24 24\", fill: \"none\", \"stroke-width\": \"2\", \"stroke-linecap\": \"round\", \"stroke-linejoin\": \"round\", children: [\n        /* @__PURE__ */ u4(\"path\", { d: \"M12.034 12.681a.498.498 0 0 1 .647-.647l9 3.5a.5.5 0 0 1-.033.943l-3.444 1.068a1 1 0 0 0-.66.66l-1.067 3.443a.5.5 0 0 1-.943.033z\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M5 3a2 2 0 0 0-2 2\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M19 3a2 2 0 0 1 2 2\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M5 21a2 2 0 0 1-2-2\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M9 3h1\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M9 21h2\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M14 3h1\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M3 9v1\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M21 9v2\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M3 14v1\" })\n      ] }),\n      /* @__PURE__ */ u4(\"symbol\", { id: \"icon-focus\", viewBox: \"0 0 24 24\", fill: \"none\", \"stroke-width\": \"2\", \"stroke-linecap\": \"round\", \"stroke-linejoin\": \"round\", children: [\n        /* @__PURE__ */ u4(\"path\", { d: \"M12.034 12.681a.498.498 0 0 1 .647-.647l9 3.5a.5.5 0 0 1-.033.943l-3.444 1.068a1 1 0 0 0-.66.66l-1.067 3.443a.5.5 0 0 1-.943.033z\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M21 11V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h6\" })\n      ] }),\n      /* @__PURE__ */ u4(\"symbol\", { id: \"icon-next\", viewBox: \"0 0 24 24\", fill: \"none\", \"stroke-width\": \"2\", \"stroke-linecap\": \"round\", \"stroke-linejoin\": \"round\", children: /* @__PURE__ */ u4(\"path\", { d: \"M6 9h6V5l7 7-7 7v-4H6V9z\" }) }),\n      /* @__PURE__ */ u4(\"symbol\", { id: \"icon-previous\", viewBox: \"0 0 24 24\", fill: \"none\", \"stroke-width\": \"2\", \"stroke-linecap\": \"round\", \"stroke-linejoin\": \"round\", children: /* @__PURE__ */ u4(\"path\", { d: \"M18 15h-6v4l-7-7 7-7v4h6v6z\" }) }),\n      /* @__PURE__ */ u4(\"symbol\", { id: \"icon-close\", viewBox: \"0 0 24 24\", fill: \"none\", \"stroke-width\": \"2\", \"stroke-linecap\": \"round\", \"stroke-linejoin\": \"round\", children: [\n        /* @__PURE__ */ u4(\"line\", { x1: \"18\", y1: \"6\", x2: \"6\", y2: \"18\" }),\n        /* @__PURE__ */ u4(\"line\", { x1: \"6\", y1: \"6\", x2: \"18\", y2: \"18\" })\n      ] }),\n      /* @__PURE__ */ u4(\"symbol\", { id: \"icon-replay\", viewBox: \"0 0 24 24\", fill: \"none\", \"stroke-width\": \"2\", \"stroke-linecap\": \"round\", \"stroke-linejoin\": \"round\", children: [\n        /* @__PURE__ */ u4(\"path\", { d: \"M3 7V5a2 2 0 0 1 2-2h2\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M17 3h2a2 2 0 0 1 2 2v2\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M21 17v2a2 2 0 0 1-2 2h-2\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M7 21H5a2 2 0 0 1-2-2v-2\" }),\n        /* @__PURE__ */ u4(\"circle\", { cx: \"12\", cy: \"12\", r: \"1\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M18.944 12.33a1 1 0 0 0 0-.66 7.5 7.5 0 0 0-13.888 0 1 1 0 0 0 0 .66 7.5 7.5 0 0 0 13.888 0\" })\n      ] }),\n      /* @__PURE__ */ u4(\"symbol\", { id: \"icon-ellipsis\", viewBox: \"0 0 24 24\", fill: \"none\", stroke: \"currentColor\", \"stroke-width\": \"2\", \"stroke-linecap\": \"round\", \"stroke-linejoin\": \"round\", children: [\n        /* @__PURE__ */ u4(\"circle\", { cx: \"12\", cy: \"12\", r: \"1\" }),\n        /* @__PURE__ */ u4(\"circle\", { cx: \"19\", cy: \"12\", r: \"1\" }),\n        /* @__PURE__ */ u4(\"circle\", { cx: \"5\", cy: \"12\", r: \"1\" })\n      ] }),\n      /* @__PURE__ */ u4(\"symbol\", { id: \"icon-copy\", viewBox: \"0 0 24 24\", fill: \"none\", stroke: \"currentColor\", \"stroke-width\": \"2\", \"stroke-linecap\": \"round\", \"stroke-linejoin\": \"round\", children: [\n        /* @__PURE__ */ u4(\"rect\", { width: \"14\", height: \"14\", x: \"8\", y: \"8\", rx: \"2\", ry: \"2\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\" })\n      ] }),\n      /* @__PURE__ */ u4(\"symbol\", { id: \"icon-check\", viewBox: \"0 0 24 24\", fill: \"none\", stroke: \"currentColor\", \"stroke-width\": \"2\", \"stroke-linecap\": \"round\", \"stroke-linejoin\": \"round\", children: /* @__PURE__ */ u4(\"path\", { d: \"M20 6 9 17l-5-5\" }) }),\n      /* @__PURE__ */ u4(\"symbol\", { id: \"icon-chevron-right\", viewBox: \"0 0 24 24\", fill: \"none\", stroke: \"currentColor\", \"stroke-width\": \"2\", \"stroke-linecap\": \"round\", \"stroke-linejoin\": \"round\", children: /* @__PURE__ */ u4(\"path\", { d: \"m9 18 6-6-6-6\" }) }),\n      /* @__PURE__ */ u4(\"symbol\", { id: \"icon-settings\", viewBox: \"0 0 24 24\", fill: \"none\", stroke: \"currentColor\", \"stroke-width\": \"2\", \"stroke-linecap\": \"round\", \"stroke-linejoin\": \"round\", children: [\n        /* @__PURE__ */ u4(\"path\", { d: \"M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z\" }),\n        /* @__PURE__ */ u4(\"circle\", { cx: \"12\", cy: \"12\", r: \"3\" })\n      ] }),\n      /* @__PURE__ */ u4(\"symbol\", { id: \"icon-flame\", viewBox: \"0 0 24 24\", children: /* @__PURE__ */ u4(\"path\", { d: \"M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z\" }) }),\n      /* @__PURE__ */ u4(\"symbol\", { id: \"icon-function\", viewBox: \"0 0 24 24\", fill: \"none\", stroke: \"currentColor\", \"stroke-width\": \"2\", \"stroke-linecap\": \"round\", \"stroke-linejoin\": \"round\", children: [\n        /* @__PURE__ */ u4(\"rect\", { width: \"18\", height: \"18\", x: \"3\", y: \"3\", rx: \"2\", ry: \"2\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M9 17c2 0 2.8-1 2.8-2.8V10c0-2 1-3.3 3.2-3\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M9 11.2h5.7\" })\n      ] }),\n      /* @__PURE__ */ u4(\"symbol\", { id: \"icon-triangle-alert\", viewBox: \"0 0 24 24\", fill: \"none\", stroke: \"currentColor\", \"stroke-width\": \"2\", \"stroke-linecap\": \"round\", \"stroke-linejoin\": \"round\", children: [\n        /* @__PURE__ */ u4(\"path\", { d: \"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M12 9v4\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M12 17h.01\" })\n      ] }),\n      /* @__PURE__ */ u4(\"symbol\", { id: \"icon-gallery-horizontal-end\", viewBox: \"0 0 24 24\", fill: \"none\", stroke: \"currentColor\", \"stroke-width\": \"2\", \"stroke-linecap\": \"round\", \"stroke-linejoin\": \"round\", children: [\n        /* @__PURE__ */ u4(\"path\", { d: \"M2 7v10\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M6 5v14\" }),\n        /* @__PURE__ */ u4(\"rect\", { width: \"12\", height: \"18\", x: \"10\", y: \"3\", rx: \"2\" })\n      ] }),\n      /* @__PURE__ */ u4(\"symbol\", { id: \"icon-search\", viewBox: \"0 0 24 24\", fill: \"none\", stroke: \"currentColor\", \"stroke-width\": \"2\", \"stroke-linecap\": \"round\", \"stroke-linejoin\": \"round\", children: [\n        /* @__PURE__ */ u4(\"circle\", { cx: \"11\", cy: \"11\", r: \"8\" }),\n        /* @__PURE__ */ u4(\"line\", { x1: \"21\", y1: \"21\", x2: \"16.65\", y2: \"16.65\" })\n      ] }),\n      /* @__PURE__ */ u4(\"symbol\", { id: \"icon-lock\", viewBox: \"0 0 24 24\", fill: \"none\", stroke: \"currentColor\", \"stroke-width\": \"2\", \"stroke-linecap\": \"round\", \"stroke-linejoin\": \"round\", children: [\n        /* @__PURE__ */ u4(\"rect\", { width: \"18\", height: \"11\", x: \"3\", y: \"11\", rx: \"2\", ry: \"2\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M7 11V7a5 5 0 0 1 10 0v4\" })\n      ] }),\n      /* @__PURE__ */ u4(\"symbol\", { id: \"icon-lock-open\", viewBox: \"0 0 24 24\", fill: \"none\", stroke: \"currentColor\", \"stroke-width\": \"2\", \"stroke-linecap\": \"round\", \"stroke-linejoin\": \"round\", children: [\n        /* @__PURE__ */ u4(\"rect\", { width: \"18\", height: \"11\", x: \"3\", y: \"11\", rx: \"2\", ry: \"2\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M7 11V7a5 5 0 0 1 9.9-1\" })\n      ] }),\n      /* @__PURE__ */ u4(\"symbol\", { id: \"icon-sanil\", viewBox: \"0 0 24 24\", fill: \"none\", stroke: \"currentColor\", \"stroke-width\": \"2\", \"stroke-linecap\": \"round\", \"stroke-linejoin\": \"round\", children: [\n        /* @__PURE__ */ u4(\"path\", { d: \"M2 13a6 6 0 1 0 12 0 4 4 0 1 0-8 0 2 2 0 0 0 4 0\" }),\n        /* @__PURE__ */ u4(\"circle\", { cx: \"10\", cy: \"13\", r: \"8\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M2 21h12c4.4 0 8-3.6 8-8V7a2 2 0 1 0-4 0v6\" }),\n        /* @__PURE__ */ u4(\"path\", { d: \"M18 3 19.1 5.2\" })\n      ] })\n    ] });\n  };\n\n  // src/web/toolbar.tsx\n  var ToolbarErrorBoundary = class extends x {\n    constructor() {\n      super(...arguments);\n      this.state = { hasError: false, error: null };\n      this.handleReset = () => {\n        this.setState({ hasError: false, error: null });\n      };\n    }\n    static getDerivedStateFromError(error) {\n      return { hasError: true, error };\n    }\n    render() {\n      if (this.state.hasError) {\n        return /* @__PURE__ */ u4(\"div\", { className: \"fixed bottom-4 right-4 z-[124124124124]\", children: /* @__PURE__ */ u4(\"div\", { className: \"p-3 bg-black rounded-lg shadow-lg w-80\", children: [\n          /* @__PURE__ */ u4(\"div\", { className: \"flex items-center gap-2 mb-2 text-red-400 text-sm font-medium\", children: [\n            /* @__PURE__ */ u4(Icon, { name: \"icon-flame\", className: \"text-red-500\", size: 14 }),\n            \"React Scan ran into a problem\"\n          ] }),\n          /* @__PURE__ */ u4(\"div\", { className: \"p-2 bg-black rounded font-mono text-xs text-red-300 mb-3 break-words\", children: this.state.error?.message || JSON.stringify(this.state.error) }),\n          /* @__PURE__ */ u4(\n            \"button\",\n            {\n              type: \"button\",\n              onClick: this.handleReset,\n              className: \"px-3 py-1.5 bg-red-500 hover:bg-red-600 text-white rounded text-xs font-medium transition-colors flex items-center justify-center gap-1.5\",\n              children: \"Restart\"\n            }\n          )\n        ] }) });\n      }\n      return this.props.children;\n    }\n  };\n  var createToolbar = (root) => {\n    const container = document.createElement(\"div\");\n    container.id = \"react-scan-toolbar-root\";\n    window.__REACT_SCAN_TOOLBAR_CONTAINER__ = container;\n    root.appendChild(container);\n    E(\n      /* @__PURE__ */ u4(ToolbarErrorBoundary, { children: /* @__PURE__ */ u4(k, { children: [\n        /* @__PURE__ */ u4(SvgSprite, {}),\n        /* @__PURE__ */ u4(Widget, {})\n      ] }) }),\n      container\n    );\n    const originalRemove = container.remove.bind(container);\n    container.remove = () => {\n      window.__REACT_SCAN_TOOLBAR_CONTAINER__ = void 0;\n      if (container.hasChildNodes()) {\n        E(null, container);\n        E(null, container);\n      }\n      originalRemove();\n    };\n    return container;\n  };\n\n  // package.json\n  var package_default = {\n    version: \"0.5.2\"};\n\n  // src/core/index.ts\n  var rootContainer = null;\n  var shadowRoot = null;\n  var initRootContainer = () => {\n    if (rootContainer && shadowRoot) {\n      return { rootContainer, shadowRoot };\n    }\n    rootContainer = document.createElement(\"div\");\n    rootContainer.id = \"react-scan-root\";\n    shadowRoot = rootContainer.attachShadow({ mode: \"open\" });\n    const cssStyles = document.createElement(\"style\");\n    cssStyles.textContent = styles_default;\n    shadowRoot.appendChild(cssStyles);\n    document.documentElement.appendChild(rootContainer);\n    return { rootContainer, shadowRoot };\n  };\n  var Store = {\n    wasDetailsOpen: d3(true),\n    isInIframe: d3(IS_CLIENT && window.self !== window.top),\n    inspectState: d3({\n      kind: \"uninitialized\"\n    }),\n    fiberRoots: /* @__PURE__ */ new Set(),\n    reportData: /* @__PURE__ */ new Map(),\n    legacyReportData: /* @__PURE__ */ new Map(),\n    lastReportTime: d3(0),\n    interactionListeningForRenders: null,\n    changesListeners: /* @__PURE__ */ new Map()\n  };\n  var ReactScanInternals = {\n    instrumentation: null,\n    componentAllowList: null,\n    options: d3({\n      enabled: true,\n      log: false,\n      showToolbar: true,\n      animationSpeed: \"fast\",\n      dangerouslyForceRunInProduction: false,\n      showFPS: true,\n      showNotificationCount: true,\n      allowInIframe: false\n    }),\n    runInAllEnvironments: false,\n    onRender: null,\n    Store,\n    version: package_default.version\n  };\n  if (IS_CLIENT && window.__REACT_SCAN_EXTENSION__) {\n    window.__REACT_SCAN_VERSION__ = ReactScanInternals.version;\n  }\n  var applyLocalStorageOptions = (options) => {\n    const {\n      onCommitStart,\n      onRender: onRender2,\n      onCommitFinish,\n      ...rest\n    } = options;\n    return rest;\n  };\n  var validateOptions = (options) => {\n    const errors = [];\n    const validOptions = {};\n    for (const key in options) {\n      const value = options[key];\n      switch (key) {\n        case \"enabled\":\n        case \"log\":\n        case \"showToolbar\":\n        case \"showNotificationCount\":\n        case \"dangerouslyForceRunInProduction\":\n        case \"showFPS\":\n        case \"allowInIframe\":\n          if (typeof value !== \"boolean\") {\n            errors.push(`- ${key} must be a boolean. Got \"${value}\"`);\n          } else {\n            validOptions[key] = value;\n          }\n          break;\n        case \"animationSpeed\":\n          if (![\"slow\", \"fast\", \"off\"].includes(value)) {\n            errors.push(\n              `- Invalid animation speed \"${value}\". Using default \"fast\"`\n            );\n          } else {\n            validOptions[key] = value;\n          }\n          break;\n        case \"onCommitStart\":\n          if (typeof value !== \"function\") {\n            errors.push(`- ${key} must be a function. Got \"${value}\"`);\n          } else {\n            validOptions.onCommitStart = value;\n          }\n          break;\n        case \"onCommitFinish\":\n          if (typeof value !== \"function\") {\n            errors.push(`- ${key} must be a function. Got \"${value}\"`);\n          } else {\n            validOptions.onCommitFinish = value;\n          }\n          break;\n        case \"onRender\":\n          if (typeof value !== \"function\") {\n            errors.push(`- ${key} must be a function. Got \"${value}\"`);\n          } else {\n            validOptions.onRender = value;\n          }\n          break;\n        default:\n          errors.push(`- Unknown option \"${key}\"`);\n      }\n    }\n    if (errors.length > 0) {\n      console.warn(`[React Scan] Invalid options:\n${errors.join(\"\\n\")}`);\n    }\n    return validOptions;\n  };\n  var getReport = (type) => {\n    if (type) {\n      for (const reportData of Array.from(Store.legacyReportData.values())) {\n        if (reportData.type === type) {\n          return reportData;\n        }\n      }\n      return null;\n    }\n    return Store.legacyReportData;\n  };\n  var setOptions = (userOptions) => {\n    try {\n      const validOptions = validateOptions(userOptions);\n      if (Object.keys(validOptions).length === 0) {\n        return;\n      }\n      const shouldInitToolbar = \"showToolbar\" in validOptions && validOptions.showToolbar !== void 0;\n      const newOptions = {\n        ...ReactScanInternals.options.value,\n        ...validOptions\n      };\n      const { instrumentation } = ReactScanInternals;\n      if (instrumentation && \"enabled\" in validOptions) {\n        instrumentation.isPaused.value = validOptions.enabled === false;\n      }\n      ReactScanInternals.options.value = newOptions;\n      try {\n        const existing = readLocalStorage(\n          \"react-scan-options\"\n        )?.enabled;\n        if (typeof existing === \"boolean\") {\n          newOptions.enabled = existing;\n        }\n      } catch (e4) {\n        if (ReactScanInternals.options.value._debug === \"verbose\") {\n          console.error(\n            \"[React Scan Internal Error]\",\n            \"Failed to create notifications outline canvas\",\n            e4\n          );\n        }\n      }\n      saveLocalStorage(\n        \"react-scan-options\",\n        applyLocalStorageOptions(newOptions)\n      );\n      if (shouldInitToolbar) {\n        initToolbar(!!newOptions.showToolbar);\n      }\n      return newOptions;\n    } catch (e4) {\n      if (ReactScanInternals.options.value._debug === \"verbose\") {\n        console.error(\n          \"[React Scan Internal Error]\",\n          \"Failed to create notifications outline canvas\",\n          e4\n        );\n      }\n    }\n  };\n  var getOptions = () => ReactScanInternals.options;\n  var isProduction = null;\n  var rdtHook;\n  var getIsProduction = () => {\n    if (isProduction !== null) {\n      return isProduction;\n    }\n    rdtHook ??= getRDTHook();\n    for (const renderer of rdtHook.renderers.values()) {\n      const buildType = detectReactBuildType(renderer);\n      if (buildType === \"production\") {\n        isProduction = true;\n      }\n    }\n    return isProduction;\n  };\n  var start = () => {\n    try {\n      if (!IS_CLIENT) {\n        return;\n      }\n      if (!ReactScanInternals.runInAllEnvironments && getIsProduction() && !ReactScanInternals.options.value.dangerouslyForceRunInProduction) {\n        return;\n      }\n      const localStorageOptions = readLocalStorage(\"react-scan-options\");\n      if (localStorageOptions) {\n        const validLocalOptions = validateOptions(localStorageOptions);\n        if (Object.keys(validLocalOptions).length > 0) {\n          ReactScanInternals.options.value = {\n            ...ReactScanInternals.options.value,\n            ...validLocalOptions\n          };\n        }\n      }\n      const options = getOptions();\n      initReactScanInstrumentation(() => {\n        initToolbar(!!options.value.showToolbar);\n      });\n      if (IS_CLIENT) {\n        setTimeout(() => {\n          if (isInstrumentationActive()) return;\n          console.error(\n            \"[React Scan] Failed to load. Must import React Scan before React runs.\"\n          );\n        }, 5e3);\n      }\n    } catch (e4) {\n      if (ReactScanInternals.options.value._debug === \"verbose\") {\n        console.error(\n          \"[React Scan Internal Error]\",\n          \"Failed to create notifications outline canvas\",\n          e4\n        );\n      }\n    }\n  };\n  var initToolbar = (showToolbar) => {\n    window.reactScanCleanupListeners?.();\n    const cleanupTimingTracking = startTimingTracking();\n    const cleanupOutlineCanvas = createNotificationsOutlineCanvas();\n    window.reactScanCleanupListeners = () => {\n      cleanupTimingTracking();\n      cleanupOutlineCanvas?.();\n    };\n    const windowToolbarContainer = window.__REACT_SCAN_TOOLBAR_CONTAINER__;\n    if (!showToolbar) {\n      windowToolbarContainer?.remove();\n      return;\n    }\n    windowToolbarContainer?.remove();\n    const { shadowRoot: shadowRoot2 } = initRootContainer();\n    createToolbar(shadowRoot2);\n  };\n  var createNotificationsOutlineCanvas = () => {\n    try {\n      const highlightRoot = document.documentElement;\n      return createHighlightCanvas(highlightRoot);\n    } catch (e4) {\n      if (ReactScanInternals.options.value._debug === \"verbose\") {\n        console.error(\n          \"[React Scan Internal Error]\",\n          \"Failed to create notifications outline canvas\",\n          e4\n        );\n      }\n    }\n  };\n  var scan = (options = {}) => {\n    setOptions(options);\n    const isInIframe = Store.isInIframe.value;\n    if (isInIframe && !ReactScanInternals.options.value.allowInIframe && !ReactScanInternals.runInAllEnvironments) {\n      return;\n    }\n    if (options.enabled === false && options.showToolbar !== true) {\n      return;\n    }\n    start();\n  };\n  var useScan = (options = {}) => {\n    setOptions(options);\n    start();\n  };\n  var onRender = (type, _onRender) => {\n    const prevOnRender = ReactScanInternals.onRender;\n    ReactScanInternals.onRender = (fiber, renders) => {\n      prevOnRender?.(fiber, renders);\n      if (getType(fiber.type) === type) {\n        _onRender(fiber, renders);\n      }\n    };\n  };\n  var ignoredProps = /* @__PURE__ */ new WeakSet();\n  var ignoreScan = (node) => {\n    if (node && typeof node === \"object\") {\n      ignoredProps.add(node);\n    }\n  };\n\n  // src/auto.ts\n  if (IS_CLIENT) {\n    scan();\n    window.reactScan = scan;\n  }\n  /*! Bundled license information:\n\n  bippy/dist/src-8Vg0r_-J.js:\n  bippy/dist/index.js:\n    (**\n     * @license bippy\n     *\n     * Copyright (c) Aiden Bai\n     *\n     * This source code is licensed under the MIT license found in the\n     * LICENSE file in the root directory of this source tree.\n     *)\n  */\n\n  exports.ReactScanInternals = ReactScanInternals;\n  exports.Store = Store;\n  exports.getIsProduction = getIsProduction;\n  exports.getOptions = getOptions;\n  exports.getReport = getReport;\n  exports.ignoreScan = ignoreScan;\n  exports.ignoredProps = ignoredProps;\n  exports.onRender = onRender;\n  exports.scan = scan;\n  exports.setOptions = setOptions;\n  exports.start = start;\n  exports.useScan = useScan;\n\n  return exports;\n\n})({});\n"
  },
  {
    "path": "packages/website/tailwind.config.ts",
    "content": "import type { Config } from 'tailwindcss';\n\nconst config: Config = {\n  content: [\n    './pages/**/*.{js,ts,jsx,tsx,mdx}',\n    './components/**/*.{js,ts,jsx,tsx,mdx}',\n    './app/**/*.{js,ts,jsx,tsx,mdx}',\n  ],\n  plugins: [],\n  theme: {\n    extend: {\n      colors: {\n        scan: {\n          50: 'oklch(96.51% 0.015 290.31 / <alpha-value>)',\n          100: 'oklch(91.77% 0.035 292.75 / <alpha-value>)',\n          200: 'oklch(83.48% 0.073 291.8 / <alpha-value>)',\n          300: 'oklch(75.37% 0.11 290.05 / <alpha-value>)',\n          400: 'oklch(68.25% 0.145 288.86 / <alpha-value>)',\n          500: 'oklch(59.88% 0.185 285.85 / <alpha-value>)',\n          600: 'oklch(50.07% 0.181 284.57 / <alpha-value>)',\n          700: 'oklch(41.78% 0.117 287.1 / <alpha-value>)',\n          800: 'oklch(34.52% 0.047 290.52 / <alpha-value>)',\n          900: 'oklch(24.54% 0.004 308.28 / <alpha-value>)',\n          950: 'oklch(18.22% 0 308.28 / <alpha-value>)',\n        },\n      },\n    },\n  },\n};\n\nexport default config;\n"
  },
  {
    "path": "packages/website/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2017\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"noEmit\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"jsx\": \"preserve\",\n    \"incremental\": true,\n    \"plugins\": [\n      {\n        \"name\": \"next\"\n      }\n    ],\n    \"paths\": {\n      \"@/*\": [\"./*\"]\n    },\n    \"baseUrl\": \".\"\n  },\n  \"include\": [\"next-env.d.ts\", \"**/*.ts\", \"**/*.tsx\", \".next/types/**/*.ts\"],\n  \"exclude\": [\n    \"node_modules\",\n    \"dist\"\n  ]\n}\n"
  },
  {
    "path": "playwright.config.ts",
    "content": "import { defineConfig, devices } from '@playwright/test';\n\nconst PORT = 5173;\n\nexport default defineConfig({\n  testDir: './e2e',\n  fullyParallel: true,\n  forbidOnly: !!process.env.CI,\n  retries: process.env.CI ? 2 : 0,\n  workers: process.env.CI ? 1 : undefined,\n  reporter: process.env.CI ? 'github' : 'list',\n  timeout: 30_000,\n  expect: {\n    timeout: 10_000,\n  },\n\n  use: {\n    baseURL: `http://localhost:${PORT}`,\n    trace: 'on-first-retry',\n    screenshot: 'only-on-failure',\n  },\n\n  projects: [\n    {\n      name: 'chromium',\n      use: { ...devices['Desktop Chrome'] },\n    },\n  ],\n\n  webServer: {\n    command: 'pnpm --filter @react-scan/kitchen-sink dev',\n    url: `http://localhost:${PORT}`,\n    reuseExistingServer: !process.env.CI,\n    timeout: 30_000,\n  },\n});\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "packages:\n  - 'packages/*'\n  - 'kitchen-sink'\n  "
  },
  {
    "path": "scripts/build-worker.ts",
    "content": "import * as esbuild from 'esbuild';\n\nawait esbuild.build({\n  entryPoints: ['src/new-outlines/offscreen-canvas.worker.ts'],\n  bundle: true,\n  format: 'iife',\n  outfile: 'dist/offscreen-canvas.worker.js',\n  minify: true,\n}); "
  },
  {
    "path": "scripts/bump-version.js",
    "content": "import fs from 'node:fs';\nimport path from 'node:path';\n\n// Read the current version from scan package.json\nconst scanPackagePath = path.join(__dirname, '../packages/scan/package.json');\nconst scanPackage = JSON.parse(fs.readFileSync(scanPackagePath, 'utf8'));\n\n// Bump patch version\nconst version = scanPackage.version.split('.');\nversion[2] = Number.parseInt(version[2]) + 1;\nconst newVersion = version.join('.');\n\n// Update the version in package.json\nscanPackage.version = newVersion;\n\n// Write back to package.json\nfs.writeFileSync(scanPackagePath, `${JSON.stringify(scanPackage, null, 2)}\\n`);\n\n// oxlint-disable-next-line no-console\nconsole.log(`Bumped version to ${newVersion}`);\n"
  },
  {
    "path": "scripts/version-warning.mjs",
    "content": "import { readFileSync, readdirSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport boxen from 'boxen';\nimport chalk from 'chalk';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nconst pEnd = 38;\n\n// Styling constants\nconst styles = {\n  title: '#7B65D0',\n  header: chalk.hex('#C4B5FD').bold,\n  text: chalk.hex('#E4E4E7'),\n  arrow: chalk.hex('#C4B5FD'),\n  version: chalk.hex('#C4B5FD'),\n  border: '#503C9B',\n  dim: chalk.hex('#A1A1AA'),\n};\n\nconst MESSAGES = {\n  workspace: {\n    header: '📦 Workspace Packages',\n    text: 'Make sure to bump versions if publishing',\n  },\n  package: {\n    text: 'Make sure to bump version if publishing',\n  },\n};\n\nfunction getWorkspacePackages() {\n  const packagesDir = resolve(__dirname, '../packages');\n  const packages = {};\n\n  try {\n    const dirs = readdirSync(packagesDir, { withFileTypes: true }).filter(\n      (dirent) => dirent.isDirectory(),\n    );\n\n    for (const dir of dirs) {\n      const pkgPath = resolve(packagesDir, dir.name, 'package.json');\n      try {\n        const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));\n        if (pkg.version) {\n          packages[pkg.name] = pkg.version;\n        }\n      } catch {\n        // Skip directories without a valid package.json\n      }\n    }\n\n    return packages;\n  } catch (err) {\n    // oxlint-disable-next-line no-console\n    console.error('Error reading packages directory:', err);\n    process.exit(1);\n  }\n}\n\nfunction getPackageInfo() {\n  const cwd = process.cwd();\n  const isWorkspacePackage = cwd.includes('packages/');\n  const isRootDir = cwd === resolve(__dirname, '..');\n  const isDirectPackageBuild =\n    isWorkspacePackage && process.env.WORKSPACE_BUILD !== '1';\n\n  if (isDirectPackageBuild) {\n    const pkgPath = resolve(cwd, 'package.json');\n    const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));\n    return { name: pkg.name, version: pkg.version };\n  }\n\n  if (isRootDir) {\n    return {\n      name: 'Workspace Packages',\n      versions: getWorkspacePackages(),\n    };\n  }\n\n  process.exit(0);\n}\n\nconst pkgInfo = getPackageInfo();\n\nconst message = pkgInfo.versions\n  ? `${styles.text(MESSAGES.workspace.text)}\\n\\n${styles.header(MESSAGES.workspace.header)}\\n${Object.entries(\n      pkgInfo.versions,\n    )\n      .sort(([a], [b]) => a.localeCompare(b))\n      .map(([pkg, version], index, array) => {\n        const prevPkg = index > 0 ? array[index - 1][0] : '';\n        const needsSpace = prevPkg.startsWith('@') && pkg === 'react-scan';\n        return `${needsSpace ? '\\n' : ''}${styles.dim(pkg.padEnd(pEnd))}${styles.version(`v${version}`)}`;\n      })\n      .join('\\n')}`\n  : `${styles.text(MESSAGES.package.text)}\\n\\n${styles.dim(pkgInfo.name.padEnd(pEnd))}${styles.version(`v${pkgInfo.version}`)}`;\n\n// oxlint-disable-next-line no-console\nconsole.log(\n  boxen(message, {\n    padding: 1,\n    margin: 1,\n    borderStyle: 'round',\n    borderColor: styles.border,\n    title: chalk.hex(styles.title)('⚡ Version Check'),\n    titleAlignment: 'left',\n    float: 'left',\n  }),\n);\n"
  },
  {
    "path": "scripts/workspace.mjs",
    "content": "import { execSync, spawn } from 'node:child_process';\n\nconst runCommand = (command, filters = []) => {\n  const filterArgs = filters.map((filter) => `--filter ${filter}`).join(' ');\n  execSync(`pnpm ${filterArgs} ${command}`, {\n    stdio: 'inherit',\n    env: { ...process.env, WORKSPACE_BUILD: '1' },\n  });\n};\n\nconst buildAll = () => {\n  runCommand('build', ['react-scan']);\n  runCommand('build', ['./packages/*', '!react-scan']);\n};\n\nconst devAll = () => {\n  // Start scan build with pipe to capture output\n  const scanProcess = spawn('pnpm', ['--filter', 'react-scan', 'dev'], {\n    stdio: 'inherit', // Inherit all streams to preserve colors\n    shell: true,\n    env: { ...process.env, WORKSPACE_BUILD: '1' },\n  });\n\n  // Start other processes after a delay to ensure scan builds first\n  setTimeout(() => {\n    // Start other processes after initial build\n    const otherProcess = spawn(\n      'pnpm',\n      [\n        '--filter',\n        '\"./packages/*\"',\n        '--filter',\n        '\"!react-scan\"',\n        '--parallel',\n        'dev',\n      ],\n      {\n        stdio: 'inherit',\n        shell: true,\n      },\n    );\n\n    // Handle Ctrl+C for both processes\n    process.on('SIGINT', () => {\n      scanProcess.kill('SIGINT');\n      otherProcess.kill('SIGINT');\n      process.exit(0);\n    });\n  }, 1000); // Wait 1 second before starting other processes\n};\n\nconst packAll = () => {\n  runCommand('pack', ['react-scan']);\n  runCommand('--parallel pack', ['./packages/*', '!react-scan']);\n};\n\n// Parse command-line arguments\nconst args = process.argv.slice(2);\nif (args.includes('build')) buildAll();\nelse if (args.includes('dev')) devAll();\nelse if (args.includes('pack')) packAll();\n// oxlint-disable-next-line no-console\nelse console.error('Invalid command. Use: node workspace.mjs [build|dev|pack]');\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"CommonJS\",\n    \"target\": \"ES2018\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"jsx\": \"react-jsx\",\n    \"jsxImportSource\": \"preact\",\n    \"moduleResolution\": \"node\",\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"sourceMap\": true,\n    \"strict\": true,\n    \"skipLibCheck\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"resolveJsonModule\": true,\n    \"types\": [\n      \"node\"\n    ]\n  },\n  \"include\": [\n    \"packages\",\n  ],\n  \"exclude\": [\n    \"node_modules\"\n  ],\n}\n"
  }
]