[
  {
    "path": ".eslintignore",
    "content": "# Dependencies\nnode_modules/\n\n# Build outputs\nbuild/\ndist/\n.next/\nout/\n\n# IDE Specific\n.idea/\n.vscode/\n\n# Specific files with TypeScript issues\nsrc/components/external-dash/LogConsole.tsx "
  },
  {
    "path": ".eslintrc.json",
    "content": "{\n  \"env\": {\n    \"browser\": true,\n    \"es6\": true,\n    \"node\": true\n  },\n  \"extends\": [\n    \"eslint:recommended\",\n    \"plugin:@typescript-eslint/eslint-recommended\",\n    \"plugin:@typescript-eslint/recommended\",\n    \"plugin:import/recommended\",\n    \"plugin:import/electron\",\n    \"plugin:import/typescript\"\n  ],\n  \"parser\": \"@typescript-eslint/parser\",\n  \"settings\": {\n    \"import/resolver\": {\n      \"node\": {\n        \"extensions\": [\".js\", \".jsx\", \".ts\", \".tsx\"]\n      }\n    },\n    \"import/ignore\": [\"@tanstack/react-query-devtools\"]\n  },\n  \"rules\": {\n    \"import/no-unresolved\": [\n      \"error\",\n      {\n        \"ignore\": [\"^@tanstack/react-query-devtools\"]\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "---\nname: Build/release\n\non:\n  push:\n    tags:\n      - \"v*\"\n\n# Add permissions block\npermissions:\n  contents: write\n\njobs:\n  build:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os: [macos-latest, ubuntu-latest, windows-latest]\n        arch: [x64, arm64]\n\n    steps:\n      - name: Check out Git repository\n        uses: actions/checkout@v4\n\n      - name: Install pnpm\n        uses: pnpm/action-setup@v3\n        with:\n          version: 10.4.1\n\n      - name: Install Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: 20\n          cache: \"pnpm\"\n\n      - name: Install dependencies\n        run: pnpm install\n\n      - name: Import certificates\n        if: runner.os == 'macOS'\n        env:\n          MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}\n          MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }}\n          APPLE_ID: ${{ secrets.APPLE_ID }}\n          APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}\n          APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}\n          KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}\n        run: |\n          # Write certificate to file and decode it\n          echo \"$MACOS_CERTIFICATE\" | base64 -D > certificate.p12\n\n          # Create keychain\n          security create-keychain -p \"$KEYCHAIN_PASSWORD\" build.keychain\n          security default-keychain -s build.keychain\n          security unlock-keychain -p \"$KEYCHAIN_PASSWORD\" build.keychain\n\n          # Import certificate\n          security import certificate.p12 -k build.keychain -P \"$MACOS_CERTIFICATE_PWD\" -T /usr/bin/codesign\n          security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k \"$KEYCHAIN_PASSWORD\" build.keychain\n\n          # Verify certificate import\n          security find-identity -v -p codesigning build.keychain\n\n      - name: Build and package app\n        env:\n          APPLE_ID: ${{ secrets.APPLE_ID }}\n          APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}\n          APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}\n          ARCH: ${{ matrix.arch }}\n        run: pnpm run make --arch=${{ matrix.arch }}\n\n      - name: Upload artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: app-${{ matrix.os }}-${{ matrix.arch }}\n          path: \"out/**/*.zip\"\n          if-no-files-found: error\n\n  release:\n    needs: build\n    runs-on: ubuntu-latest\n    steps:\n      - name: Download all uploaded artifacts\n        uses: actions/download-artifact@v4\n        with:\n          path: ./downloaded-artifacts\n\n      - name: Create or update release\n        uses: ncipollo/release-action@v1\n        if: startsWith(github.ref, 'refs/tags/')\n        with:\n          allowUpdates: true\n          replacesArtifacts: true\n          removeArtifacts: true\n          artifacts: \"downloaded-artifacts/**/*.zip\"\n          draft: false\n          prerelease: false\n          token: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n.DS_Store\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# TypeScript v1 declaration files\ntypings/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional REPL history\n.node_repl_history\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n.env.test\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n\n# next.js build output\n.next\n\n# nuxt.js build output\n.nuxt\n\n# vuepress build output\n.vuepress/dist\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# Webpack\n.webpack/\n\n# Vite\n.vite/\n\n# Electron-Forge\nout/\n\n# Yalc\n.yalc/.env\n\n# Environment variables\n.env.local\n.env.*\n"
  },
  {
    "path": ".npmrc",
    "content": "node-linker = hoisted\n"
  },
  {
    "path": "DEVELOPMENT.md",
    "content": "# Development Guide\n\nThis guide covers everything you need to know about developing React Native DevTools.\n\n## 🛠 Prerequisites\n\n- Node.js 20 or later\n- pnpm 10.4.1 or later\n- macOS for building and signing\n- Apple Developer account for signing\n- GitHub CLI (`gh`) for releases\n\n## 🚀 Getting Started\n\n1. Clone the repository:\n\n   ```bash\n   git clone https://github.com/LovesWorking/rn-better-dev-tools.git\n   cd rn-better-dev-tools\n   ```\n\n2. Install dependencies:\n\n   ```bash\n   pnpm install\n   ```\n\n3. Create a `.env` file based on `.env.example`:\n\n   ```bash\n   cp .env.example .env\n   ```\n\n4. Fill in your Apple Developer credentials in `.env`:\n   ```\n   APPLE_ID=your.email@example.com\n   APPLE_PASSWORD=app-specific-password\n   APPLE_TEAM_ID=your-team-id\n   ```\n\n## 🏗 Building\n\n### Development Build\n\n```bash\n# Start in development mode with hot reload\npnpm start\n\n# Build and copy to desktop for testing\npnpm run make:desktop\n\n# Build only\npnpm run make\n```\n\n### Release Build\n\nWe provide an automated release script that:\n\n- Bumps version (minor)\n- Builds locally to verify\n- Commits changes\n- Creates and pushes tag\n- Monitors GitHub Action progress\n\n```bash\n# Using npm script\npnpm run auto-release\n\n# Or directly\n./auto-release.sh\n```\n\n## 🐛 Debugging\n\n### Enable Debug Logs\n\nAdd to your `.env`:\n\n```bash\nDEBUG=electron-osx-sign*\n```\n\n### Common Issues\n\n1. **Build Hanging on \"Finalizing package\"**\n\n   - Check Apple Developer credentials\n   - Verify keychain access\n   - Run with debug logs enabled\n\n2. **Permission Issues**\n\n   ```bash\n   # Fix directory permissions\n   sudo chown -R $(whoami) .\n\n   # Clean build artifacts\n   rm -rf .vite out\n   ```\n\n3. **Certificate Issues**\n   - Verify Apple Developer membership is active\n   - Check Team ID matches in `.env`\n   - Ensure app-specific password is correct\n\n### Development Commands\n\n```bash\n# Clean install\npnpm run nuke\n\n# Package without making distributables\npnpm run package\n\n# Run linter\npnpm run lint\n```\n\n## 📦 Project Structure\n\n```\n.\n├── src/                  # Source code\n│   ├── main.ts          # Main process\n│   ├── preload.ts       # Preload scripts\n│   └── components/      # React components\n├── assets/              # Static assets\n├── .github/workflows/   # GitHub Actions\n└── forge.config.ts      # Electron Forge config\n```\n\n## 🔄 Release Process\n\n### Automatic Release\n\n```bash\n./auto-release.sh\n```\n\n### Manual Release Steps\n\n1. Update version in `package.json`\n2. Build and test locally\n3. Create and push tag\n4. GitHub Action will build and publish\n\n## 🧪 Testing\n\nBefore submitting a PR:\n\n1. Test in development mode (`pnpm start`)\n2. Build and test locally (`pnpm run make:desktop`)\n3. Verify all features work\n4. Check console for errors\n5. Run linter (`pnpm run lint`)\n"
  },
  {
    "path": "GITHUB_RELEASE.md",
    "content": "# GitHub Release Process\n\nThis guide explains how to release updates to GitHub and enable auto-updating for React Native DevTools.\n\n## Automated Release Process\n\nThe recommended way to create a new release is to use the automated script:\n\n1. Make sure your changes are committed to the main branch\n2. Run the release script:\n   ```bash\n   pnpm run release\n   # or directly with\n   ./release-version.sh\n   ```\n3. Follow the interactive prompts to:\n   - Select the version bump type (patch, minor, major, or custom)\n   - Enter release notes\n   - Confirm the release\n4. The script will:\n   - Update the version in package.json\n   - Commit and push the changes\n   - Create and push a git tag\n   - Monitor the GitHub Actions workflow\n   - Automatically publish the release when complete\n\n## Manual Releases\n\nIf you need to manually publish a release:\n\n1. Ensure you have a GitHub access token with \"repo\" permissions\n2. Set the token as an environment variable:\n   ```bash\n   export GITHUB_TOKEN=your_github_token\n   ```\n3. Run the pack script:\n   ```bash\n   pnpm run pack\n   # or directly with\n   ./build-and-pack.sh\n   ```\n\n## GitHub Actions Automated Releases\n\nFor automated releases using GitHub Actions:\n\n1. Create a new tag following semantic versioning:\n\n   ```bash\n   git tag v1.x.x\n   git push origin v1.x.x\n   ```\n\n2. The GitHub Actions workflow will automatically build and release the app\n3. The release will initially be created as a draft\n4. Review the release and publish it when ready\n\n## Auto-Updates\n\nThe app is configured to automatically check for updates when running. When a new version is released on GitHub:\n\n1. Users with the previous version will automatically receive update notifications\n2. Updates are downloaded in the background\n3. The update will be installed when the user restarts the app\n\n## Configuration Files\n\nThe auto-update system is configured in several files:\n\n- `forge.config.ts`: Contains the GitHub publisher configuration\n- `package.json`: Contains the electron-builder configuration\n- `src/auto-updater.ts`: Implements the auto-update checking logic\n- `.github/workflows/build.yml`: Defines the GitHub Actions workflow for automated builds\n\n## Troubleshooting\n\n- If the auto-updater isn't working, check the log file at:\n  - macOS: `~/Library/Logs/react-native-devtools/main.log`\n- Make sure your repository is public, or if it's private, ensure users have proper access tokens configured\n\n- To debug update issues, run the app with the `DEBUG` environment variable:\n  ```bash\n  DEBUG=electron-updater npm start\n  ```\n\n## Version Updates\n\nThe automated release script handles version updates for you, but if you need to do it manually:\n\n1. Update the version in `package.json`\n2. Commit the changes\n3. Create a new tag matching the version number\n4. Push the tag to GitHub\n\nThe version format should follow semantic versioning: `MAJOR.MINOR.PATCH`\n"
  },
  {
    "path": "README.md",
    "content": "# React Native DevTools\n\nEnhanced developer tools for React Native applications, supporting React Query DevTools and device storage monitoring with a beautiful native interface.\n\n![ios pokemon](https://github.com/user-attachments/assets/25ffb38c-2e41-4aa9-a3c7-6f74383a75fc)\n\nhttps://github.com/user-attachments/assets/5c0c5748-e031-427a-8ebf-9c085434e8ba\n\n## Example app\n\nhttps://github.com/LovesWorking/RN-Dev-Tools-Example\n\n### If you need internal React Query dev tools within the device you can use my other package here!\n\nhttps://github.com/LovesWorking/react-native-react-query-devtools\n\n## ✨ Features\n\n- 🔄 Real-time React Query state monitoring\n- 💾 **Device storage monitoring with CRUD operations** - MMKV, AsyncStorage, and SecureStorage\n- 🌐 **Environment variables monitoring** - View and track public environment variables\n- 🎨 Beautiful native macOS interface\n- 🚀 Automatic connection to React apps\n- 📊 Query status visualization\n- 🔌 Socket.IO integration for reliable communication\n- ⚡️ Simple setup with NPM package\n- 📱 Works with **any React-based platform**: React Native, React Web, Next.js, Expo, tvOS, VR, etc.\n- 🛑 Zero-config production safety - automatically disabled in production builds\n\n## 📦 Installation\n\n### DevTools Desktop Application (macOS)\n\n> **⚠️ Important**: The desktop app has currently only been tested on Apple Silicon Macs (M1/M2/M3).\n> If you encounter issues on Intel-based Macs, please [open an issue](https://github.com/LovesWorking/rn-better-dev-tools/issues)\n> and we'll work together to fix it.\n\n1. Download the latest release from the [Releases page](https://github.com/LovesWorking/rn-better-dev-tools/releases)\n2. Extract the ZIP file\n3. Move the app to your Applications folder\n4. Launch the app\n\n### React Application Integration\n\nThe easiest way to connect your React application to the DevTools is by installing the npm package:\n\n```bash\n# Using npm\nnpm install --save-dev react-query-external-sync socket.io-client\nnpm install expo-device  # For automatic device detection\n\n# Using yarn\nyarn add -D react-query-external-sync socket.io-client\nyarn add expo-device  # For automatic device detection\n\n# Using pnpm (recommended)\npnpm add -D react-query-external-sync socket.io-client\npnpm add expo-device  # For automatic device detection\n```\n\n## 🚀 Quick Start\n\n1. Download and launch the React Native DevTools application\n2. Add the hook to your application where you set up your React Query context:\n\n```jsx\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport { useSyncQueriesExternal } from \"react-query-external-sync\";\n// Import Platform for React Native or use other platform detection for web/desktop\nimport { Platform } from \"react-native\";\nimport AsyncStorage from \"@react-native-async-storage/async-storage\";\nimport * as SecureStore from \"expo-secure-store\";\nimport * as ExpoDevice from \"expo-device\";\nimport { storage } from \"./mmkv\"; // Your MMKV instance\n\n// Create your query client\nconst queryClient = new QueryClient();\n\nfunction App() {\n  return (\n    <QueryClientProvider client={queryClient}>\n      <AppContent />\n    </QueryClientProvider>\n  );\n}\n\nfunction AppContent() {\n  // Set up the sync hook - automatically disabled in production!\n  useSyncQueriesExternal({\n    queryClient,\n    socketURL: \"http://localhost:42831\", // Default port for React Native DevTools\n    deviceName: Platform?.OS || \"web\", // Platform detection\n    platform: Platform?.OS || \"web\", // Use appropriate platform identifier\n    deviceId: Platform?.OS || \"web\", // Use a PERSISTENT identifier (see note below)\n    isDevice: ExpoDevice.isDevice, // Automatically detects real devices vs emulators\n    extraDeviceInfo: {\n      // Optional additional info about your device\n      appVersion: \"1.0.0\",\n      // Add any relevant platform info\n    },\n    enableLogs: false,\n    envVariables: {\n      NODE_ENV: process.env.NODE_ENV,\n      // Add any private environment variables you want to monitor\n      // Public environment variables are automatically loaded\n    },\n    // Storage monitoring with CRUD operations\n    mmkvStorage: storage, // MMKV storage for ['#storage', 'mmkv', 'key'] queries + monitoring\n    asyncStorage: AsyncStorage, // AsyncStorage for ['#storage', 'async', 'key'] queries + monitoring\n    secureStorage: SecureStore, // SecureStore for ['#storage', 'secure', 'key'] queries + monitoring\n    secureStorageKeys: [\n      \"userToken\",\n      \"refreshToken\",\n      \"biometricKey\",\n      \"deviceId\",\n    ], // SecureStore keys to monitor\n  });\n\n  // Your app content\n  return <YourApp />;\n}\n```\n\n3. Start your React application\n4. DevTools will automatically detect and connect to your running application\n\n## 🔒 Production Safety\n\nThe React Query External Sync package is automatically disabled in production builds.\n\n```jsx\n// The package handles this internally:\nif (process.env.NODE_ENV !== \"production\") {\n  useSyncQueries = require(\"./new-sync/useSyncQueries\").useSyncQueries;\n} else {\n  // In production, this becomes a no-op function\n  useSyncQueries = () => ({\n    isConnected: false,\n    connect: () => {},\n    disconnect: () => {},\n    socket: null,\n    users: [],\n  });\n}\n```\n\n### 📱 Using with Real Devices (Local Network)\n\nWhen testing on real devices connected to your local network, you'll need to use your host machine's IP address instead of `localhost`. Here's a helpful setup for Expo apps (contributed by [ShoeBoom](https://github.com/ShoeBoom)):\n\n```jsx\nimport Constants from \"expo-constants\";\nimport AsyncStorage from \"@react-native-async-storage/async-storage\";\nimport * as SecureStore from \"expo-secure-store\";\nimport { storage } from \"./mmkv\"; // Your MMKV instance\n\n// Get the host IP address dynamically\nconst hostIP =\n  Constants.expoGoConfig?.debuggerHost?.split(`:`)[0] ||\n  Constants.expoConfig?.hostUri?.split(`:`)[0];\n\nfunction AppContent() {\n  useSyncQueriesExternal({\n    queryClient,\n    socketURL: `http://${hostIP}:42831`, // Use local network IP\n    deviceName: Platform?.OS || \"web\",\n    platform: Platform?.OS || \"web\",\n    deviceId: Platform?.OS || \"web\",\n    extraDeviceInfo: {\n      appVersion: \"1.0.0\",\n    },\n    enableLogs: false,\n    envVariables: {\n      NODE_ENV: process.env.NODE_ENV,\n    },\n    // Storage monitoring\n    mmkvStorage: storage,\n    asyncStorage: AsyncStorage,\n    secureStorage: SecureStore,\n    secureStorageKeys: [\"userToken\", \"refreshToken\"],\n  });\n\n  return <YourApp />;\n}\n```\n\n> **Note**: For optimal connection, launch DevTools before starting your application.\n\n## 💡 Usage Tips\n\n- Keep DevTools running while developing\n- Monitor query states in real-time\n- View detailed query information\n- Track cache updates and invalidations\n- **Monitor device storage**: View and modify MMKV, AsyncStorage, and SecureStorage data in real-time\n- **Track environment variables**: Monitor public environment variables across your application\n- **Use storage queries**: Access storage data via React Query with keys like `['#storage', 'mmkv', 'key']`\n- The hook is automatically disabled in production builds, no configuration needed\n\n## 📱 Platform Support\n\nReact Native DevTools works with **any React-based application**, regardless of platform:\n\n- 📱 Mobile: iOS, Android\n- 🖥️ Web: React, Next.js, Remix, etc.\n- 🖥️ Desktop: Electron, Tauri\n- 📺 TV: tvOS, Android TV\n- 🥽 VR/AR: Meta Quest, etc.\n- 💻 Cross-platform: Expo, React Native Web\n\nIf your platform can run React and connect to a socket server, it will work with these DevTools!\n\n## 💾 Storage Monitoring\n\nReact Native DevTools now includes powerful storage monitoring capabilities with full CRUD operations:\n\n### Supported Storage Types\n\n- **MMKV**: High-performance key-value storage\n- **AsyncStorage**: React Native's standard async storage\n- **SecureStorage**: Secure storage for sensitive data (Expo SecureStore)\n\n### Features\n\n- **Real-time monitoring**: See storage changes as they happen\n- **CRUD operations**: Create, read, update, and delete storage entries directly from DevTools\n- **React Query integration**: Access storage data via queries like `['#storage', 'mmkv', 'keyName']`\n- **Type-safe operations**: Automatic serialization/deserialization of complex data types\n- **Secure data handling**: SecureStorage keys are monitored securely\n\n### Usage Example\n\n```jsx\n// In your app, use React Query to interact with storage\nimport { useQuery, useMutation, useQueryClient } from \"@tanstack/react-query\";\n\n// Read from MMKV storage\nconst { data: userData } = useQuery({\n  queryKey: [\"#storage\", \"mmkv\", \"user\"],\n  // Data will be automatically synced with DevTools\n});\n\n// Write to AsyncStorage\nconst queryClient = useQueryClient();\nconst updateAsyncStorage = useMutation({\n  mutationFn: async ({ key, value }) => {\n    await AsyncStorage.setItem(key, JSON.stringify(value));\n    // Invalidate to trigger sync\n    queryClient.invalidateQueries([\"#storage\", \"async\", key]);\n  },\n});\n```\n\n## ⚙️ Configuration Options\n\nThe `useSyncQueriesExternal` hook accepts the following options:\n\n| Option              | Type         | Required | Description                                                                                                       |\n| ------------------- | ------------ | -------- | ----------------------------------------------------------------------------------------------------------------- |\n| `queryClient`       | QueryClient  | Yes      | Your React Query client instance                                                                                  |\n| `socketURL`         | string       | Yes      | URL of the socket server (e.g., 'http://localhost:42831')                                                         |\n| `deviceName`        | string       | Yes      | Human-readable name for your device                                                                               |\n| `platform`          | string       | Yes      | Platform identifier ('ios', 'android', 'web', 'macos', 'windows', etc.)                                           |\n| `deviceId`          | string       | Yes      | Unique identifier for your device                                                                                 |\n| `isDevice`          | boolean      | No       | Whether running on a real device (true) or emulator (false). Used for Android socket URL handling (default: true) |\n| `extraDeviceInfo`   | object       | No       | Additional device metadata to display in DevTools                                                                 |\n| `enableLogs`        | boolean      | No       | Enable console logging for debugging (default: false)                                                             |\n| `envVariables`      | object       | No       | Private environment variables to sync with DevTools (public vars are auto-loaded)                                 |\n| `mmkvStorage`       | MmkvStorage  | No       | MMKV storage instance for real-time monitoring                                                                    |\n| `asyncStorage`      | AsyncStorage | No       | AsyncStorage instance for polling-based monitoring                                                                |\n| `secureStorage`     | SecureStore  | No       | SecureStore instance for secure data monitoring                                                                   |\n| `secureStorageKeys` | string[]     | No       | Array of SecureStore keys to monitor (required if using secureStorage)                                            |\n\n## 🔮 Future Plans\n\nReact Native DevTools is actively being developed with exciting features on the roadmap:\n\n- ✅ **Storage Viewers**: Beautiful interfaces for viewing and modifying storage (AsyncStorage, MMKV, SecureStorage) - **Now Available!**\n- 🌐 **Network Request Monitoring**: Track API calls, WebSockets, and GraphQL requests\n- ❌ **Failed Request Tracking**: Easily identify and debug network failures\n- 🔄 **Remote Expo DevTools**: Trigger Expo DevTools commands remotely without using the command line\n- 🧩 **Plugin System**: Allow community extensions for specialized debugging tasks\n- 🗄️ **Drizzle Studio Plugin**: Integration with Drizzle ORM for database management\n\nStay tuned for updates!\n\n## 🤝 Contributing\n\nI welcome contributions! See [Development Guide](DEVELOPMENT.md) for details on:\n\n- Setting up the development environment\n- Building and testing\n- Release process\n- Contribution guidelines\n\n## 🐛 Troubleshooting\n\nHaving issues? Check these common solutions:\n\n1. **App Not Connecting**\n\n   - Ensure DevTools is launched before your React app\n   - Check that your React app is running\n   - Verify you're on the same network\n   - Make sure the `socketURL` is correctly pointing to localhost:42831\n   - Verify the Socket.IO client is properly installed in your app\n   - Check that the `useSyncQueriesExternal` hook is properly implemented\n\n2. **App Not Starting**\n\n   - Verify you're using the latest version\n   - Check system requirements (macOS with Apple Silicon chip)\n   - Try reinstalling the application\n   - If using an Intel Mac and encountering issues, please report them\n\n3. **Socket Connection Issues**\n\n   - Make sure no firewall is blocking the connection on port 42831\n   - Restart both the DevTools app and your React app\n   - Check the console logs with `enableLogs: true` for any error messages\n   - If the React Query data is too large the socket connection will crash! If you see the device connect and then disconnect with no logs this is what's happening. You'll need to fix your query cache to not be so large.\n\n4. **Data Not Syncing**\n\n   - Confirm you're passing the correct `queryClient` instance\n   - Set `enableLogs: true` to see connection information\n\n5. **Android Real Device Connection Issues**\n\n   - If using a real Android device with React Native CLI and ADB, ensure `isDevice: true`\n   - The package transforms `localhost` to `10.0.2.2` for emulators only\n   - Use `ExpoDevice.isDevice` for automatic detection: `import * as ExpoDevice from \"expo-device\"`\n   - Check network connectivity between your device and development machine\n\n6. **DevTools App Issues**\n\n   - Make sure your `deviceId` is persistent (see below)\n   - Verify you're using the latest version\n   - Check system requirements (macOS with Apple Silicon chip)\n   - Try reinstalling the application\n   - If using an Intel Mac and encountering issues, please report them\n\n7. **Storage Monitoring Issues**\n\n   - Ensure storage instances are properly passed to the hook\n   - For SecureStorage, make sure `secureStorageKeys` array is provided\n   - Check that storage permissions are granted on the device\n   - Verify storage libraries are properly installed and configured\n   - Use `enableLogs: true` to see storage sync information\n\nThat's it! If you're still having issues, visit the [GitHub repository](https://github.com/LovesWorking/rn-better-dev-tools/issues) for support.\n\n## 🏷️ Device Type Detection\n\nThe `isDevice` prop helps the DevTools distinguish between real devices and simulators/emulators. This is **crucial for Android connectivity** - the package automatically handles URL transformation for Android emulators (localhost → 10.0.2.2) but needs to know if you're running on a real device to avoid this transformation.\n\n### ⚠️ Android Connection Issue\n\nOn real Android devices using React Native CLI and ADB, the automatic emulator detection can incorrectly transform `localhost` to `10.0.2.2`, breaking WebSocket connections. Setting `isDevice: true` prevents this transformation.\n\n**Recommended approaches:**\n\n```jsx\n// Best approach using Expo Device (works with bare React Native too)\nimport * as ExpoDevice from \"expo-device\";\n\nuseSyncQueriesExternal({\n  queryClient,\n  socketURL: \"http://localhost:42831\",\n  deviceName: Platform.OS,\n  platform: Platform.OS,\n  deviceId: Platform.OS,\n  isDevice: ExpoDevice.isDevice, // Automatically detects real devices vs emulators\n  // ... other props\n});\n\n// Alternative: Simple approach using React Native's __DEV__ flag\nisDevice: !__DEV__, // true for production/real devices, false for development/simulators\n\n// Alternative: More sophisticated detection using react-native-device-info\nimport DeviceInfo from 'react-native-device-info';\nisDevice: !DeviceInfo.isEmulator(), // Automatically detects if running on emulator\n\n// Manual control for specific scenarios\nisDevice: Platform.OS === 'ios' ? !Platform.isPad : Platform.OS !== 'web',\n```\n\n## ⚠️ Important Note About Device IDs\n\nThe `deviceId` parameter must be **persistent** across app restarts and re-renders. Using a value that changes (like `Date.now()`) will cause each render to be treated as a new device.\n\n**Recommended approaches:**\n\n```jsx\n// Simple approach for single devices\ndeviceId: Platform.OS, // Works if you only have one device per platform\n\n// Better approach for multiple simulators/devices of same type\n// Using AsyncStorage, MMKV, or another storage solution\nconst [deviceId, setDeviceId] = useState(Platform.OS);\n\nuseEffect(() => {\n  const loadOrCreateDeviceId = async () => {\n    // Try to load existing ID\n    const storedId = await AsyncStorage.getItem('deviceId');\n\n    if (storedId) {\n      setDeviceId(storedId);\n    } else {\n      // First launch - generate and store a persistent ID\n      const newId = `${Platform.OS}-${Date.now()}`;\n      await AsyncStorage.setItem('deviceId', newId);\n      setDeviceId(newId);\n    }\n  };\n\n  loadOrCreateDeviceId();\n}, []);\n```\n\nFor more detailed troubleshooting, see our [Development Guide](DEVELOPMENT.md).\n\n## 📄 License\n\nMIT\n---\n\nMade with ❤️ by [LovesWorking](https://github.com/LovesWorking)\n\n\n\n## 🚀 More\n\n**Take a shortcut from web developer to mobile development fluency with guided learning**\n\nEnjoyed this project? Learn to use React Native to build production-ready, native mobile apps for both iOS and Android based on your existing web development skills.\n\n<img width=\"1800\" height=\"520\" alt=\"banner\" src=\"https://github.com/user-attachments/assets/cdf63dea-464f-44fe-bed1-a517785bfd99\" />\n"
  },
  {
    "path": "RELEASE.md",
    "content": "# Releasing React Native DevTools\n\nThis document outlines the process for creating a new release of React Native DevTools.\n\n## Automated Release Process (Recommended)\n\nThe easiest way to create a new release is to use our automated script:\n\n```bash\npnpm run release\n```\n\nThis interactive script will:\n\n1. Prompt you to select a version bump type (patch, minor, major, or custom)\n2. Update the version in package.json\n3. Ask for release notes\n4. Commit and push the changes\n5. Create and push a git tag\n6. Monitor the GitHub Actions workflow\n7. Automatically publish the release when complete\n\n## Manual Release Process\n\nIf you need to perform the release manually, follow these steps:\n\n1. Update the version in `package.json`:\n\n```json\n{\n  \"version\": \"x.y.z\",\n  ...\n}\n```\n\n2. Commit your changes:\n\n```bash\ngit add package.json\ngit commit -m \"Bump version to x.y.z\"\n```\n\n3. Create and push a new tag:\n\n```bash\ngit tag -a vx.y.z -m \"Version x.y.z\"\ngit push origin vx.y.z\n```\n\n4. The GitHub Actions workflow will automatically:\n\n   - Build the app for macOS\n   - Create a draft release with all the built installers\n   - Add the release notes\n\n5. Go to the GitHub Releases page, review the draft release, add any additional notes, and publish it.\n\n## Local Build and Package\n\nIf you want to build the app locally without publishing:\n\n```bash\npnpm run pack\n```\n\nThis will:\n\n- Build the app\n- Create installation packages\n- Copy them to your Desktop in a \"release rn better tools\" folder\n\n## Testing Before Release\n\nBefore creating a new release tag, make sure to:\n\n1. Test the app thoroughly on your local machine\n2. Run `pnpm run make` locally to ensure the build process completes without errors\n3. Test the generated installers\n\n## Troubleshooting\n\nIf the GitHub Actions build fails:\n\n1. Check the workflow logs for errors\n2. Make sure the repository has the necessary secrets and permissions set up\n3. Try running the build locally to isolate the issue\n\n## Auto-Update Feature\n\nThe app includes auto-update functionality. When a new release is published:\n\n1. Existing users will be automatically notified of the update\n2. The update will be downloaded in the background\n3. The update will be installed when the user restarts the app\n\nSee GITHUB_RELEASE.md for more details on the auto-update configuration.\n"
  },
  {
    "path": "assets/icon.icns",
    "content": "// This is a placeholder - you need to replace this with an actual .icns file\n// For now, you'll need to create or convert your icon to .icns format manually\n// You can use tools like iconutil or online converters to create .icns files "
  },
  {
    "path": "auto-release.sh",
    "content": "#!/bin/bash\n\n# Set error handling\nset -e\n\n# Colors for better logging\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'\nBLUE='\\033[0;34m'\nNC='\\033[0m' # No Color\n\n# Function for logging\nlog() {\n    echo -e \"${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1\"\n}\n\nerror() {\n    echo -e \"${RED}[ERROR]${NC} $1\"\n    exit 1\n}\n\nsuccess() {\n    echo -e \"${GREEN}[SUCCESS]${NC} $1\"\n}\n\nwarning() {\n    echo -e \"${YELLOW}[WARNING]${NC} $1\"\n}\n\n# Check if we're in the right directory\nif [ ! -f \"package.json\" ]; then\n    error \"Must be run from project root directory\"\nfi\n\n# Get current version from package.json\nCURRENT_VERSION=$(node -p \"require('./package.json').version\")\nlog \"Current version: $CURRENT_VERSION\"\n\n# Calculate new version (minor bump)\nNEW_VERSION=$(node -p \"\n    const [major, minor, patch] = '$CURRENT_VERSION'.split('.');\n    \\`\\${major}.\\${parseInt(minor) + 1}.0\\`\n\")\nlog \"New version will be: $NEW_VERSION\"\n\n# Update version in package.json\nlog \"Updating package.json version...\"\nnpm version $NEW_VERSION --no-git-tag-version\n\n# Build locally first to ensure everything works\nlog \"Building locally to verify...\"\npnpm run make:desktop || error \"Local build failed\"\nsuccess \"Local build successful!\"\n\n# Stage all changes\nlog \"Staging changes...\"\ngit add . || error \"Failed to stage changes\"\n\n# Commit with conventional commit message\nlog \"Committing changes...\"\ngit commit -m \"chore: release v$NEW_VERSION\" || error \"Failed to commit changes\"\n\n# Create and push tag\nlog \"Creating tag v$NEW_VERSION...\"\ngit tag -d \"v$NEW_VERSION\" 2>/dev/null || true\ngit push origin \":refs/tags/v$NEW_VERSION\" 2>/dev/null || true\ngit tag \"v$NEW_VERSION\" || error \"Failed to create tag\"\n\n# Push changes and tags\nlog \"Pushing changes and tags...\"\ngit push && git push --tags || error \"Failed to push changes\"\nsuccess \"Changes pushed successfully!\"\n\n# Wait for GitHub Action to start\nlog \"Waiting for GitHub Action to start...\"\nsleep 5\n\n# Monitor GitHub Action progress\nMAX_ATTEMPTS=30\nATTEMPT=1\nwhile [ $ATTEMPT -le $MAX_ATTEMPTS ]; do\n    STATUS=$(gh run list --json status,name --jq '.[0].status' 2>/dev/null || echo \"unknown\")\n    if [ \"$STATUS\" = \"completed\" ]; then\n        success \"GitHub Action completed successfully!\"\n        break\n    elif [ \"$STATUS\" = \"failed\" ]; then\n        error \"GitHub Action failed. Check the logs with: gh run view\"\n    fi\n    log \"Build still in progress... (Attempt $ATTEMPT/$MAX_ATTEMPTS)\"\n    sleep 10\n    ATTEMPT=$((ATTEMPT + 1))\ndone\n\nif [ $ATTEMPT -gt $MAX_ATTEMPTS ]; then\n    warning \"Timed out waiting for GitHub Action to complete. Check status manually with: gh run view\"\nfi\n\nsuccess \"Release process completed!\"\nlog \"New version v$NEW_VERSION is being published\"\nlog \"You can check the release status at: https://github.com/LovesWorking/rn-better-dev-tools/releases\" "
  },
  {
    "path": "build-and-pack.sh",
    "content": "#!/bin/bash\n\n# Exit on any error\nset -e\n\n# Display what's happening\necho \"🚀 Building and packaging React Native DevTools...\"\n\n# Variables\nVERSION=$(node -e \"console.log(require('./package.json').version)\")\nOUTPUT_DIR=\"$HOME/Desktop/release rn better tools\"\nAPP_NAME=\"React Native DevTools\"\n\n# Ensure the output directory exists\nmkdir -p \"$OUTPUT_DIR\"\n\n# Clean previous builds\necho \"🧹 Cleaning previous builds...\"\nrm -rf out/\n\n# Check if GITHUB_TOKEN is set\nif [ -n \"$GITHUB_TOKEN\" ]; then\n  # If GITHUB_TOKEN is set, publish to GitHub\n  echo \"🌐 Publishing to GitHub...\"\n  npx electron-forge publish\nelse\n  # If GITHUB_TOKEN is not set, just build locally\n  echo \"🔨 Building application locally...\"\n  npx electron-forge make --targets=@electron-forge/maker-zip\n\n  # Copy the output to the destination folder\n  echo \"📦 Copying packaged app to destination folder...\"\n  cp \"out/make/zip/darwin/arm64/$APP_NAME-darwin-arm64-$VERSION.zip\" \"$OUTPUT_DIR/\"\n\n  # Create a dated copy for versioning/archiving purposes\n  DATED_FILENAME=\"$APP_NAME-darwin-arm64-$VERSION-$(date +%Y%m%d%H%M).zip\"\n  cp \"out/make/zip/darwin/arm64/$APP_NAME-darwin-arm64-$VERSION.zip\" \"$OUTPUT_DIR/$DATED_FILENAME\"\n\n  echo \"✅ Build and packaging complete!\"\n  echo \"📝 Files created:\"\n  echo \"   - $OUTPUT_DIR/$APP_NAME-darwin-arm64-$VERSION.zip\"\n  echo \"   - $OUTPUT_DIR/$DATED_FILENAME\"\n  echo \"\"\n  echo \"🖥️  Open folder using: open \\\"$OUTPUT_DIR\\\"\"\n  echo \"\"\n  echo \"⚠️  Note: To publish to GitHub, set the GITHUB_TOKEN environment variable and run this script again.\"\nfi "
  },
  {
    "path": "build-macos.sh",
    "content": "#!/bin/bash\n\n# Exit on error\nset -e\n\necho \"=== Building React Native DevTools for macOS ===\"\n\n# Clean previous builds\nrm -rf out || true\necho \"✅ Cleaned previous builds\"\n\n# Install dependencies\npnpm install\necho \"✅ Dependencies installed\"\n\n# Build package\necho \"🔨 Building macOS package...\"\npnpm run make\n\n# Check if ZIP was created\nZIP_PATH=$(find out/make -name \"*.zip\" | head -n 1)\n\nif [ -f \"$ZIP_PATH\" ]; then\n    echo \"✅ ZIP package created at: $ZIP_PATH\"\n    echo \"✅ Total size: $(du -h \"$ZIP_PATH\" | cut -f1)\"\n    \n    echo \"\"\n    echo \"To run the app:\"\n    echo \"1. Extract the ZIP file\"\n    echo \"2. Move the app to your Applications folder\"\n    echo \"3. Open the app (you may need to right-click and select Open for the first time)\"\n    \n    echo \"\"\n    echo \"To release:\"\n    echo \"1. Update version in package.json\"\n    echo \"2. Commit changes\"\n    echo \"3. Create and push tag: git tag -a v1.0.0 -m 'v1.0.0' && git push origin v1.0.0\"\n    \n    echo \"\"\n    echo \"Build complete! 🎉\"\nelse\n    echo \"❌ ZIP package was not created. Check for errors above.\"\n    exit 1\nfi "
  },
  {
    "path": "config.js",
    "content": "/**\n * Socket.IO Test Client Config\n * CommonJS version for use with the test script\n */\n\n// Server configuration\nconst SERVER_PORT = 42831; // Using an uncommon port to avoid conflicts\n\n// Client configuration\nconst CLIENT_URL = `http://localhost:${SERVER_PORT}`;\n\nmodule.exports = {\n  SERVER_PORT,\n  CLIENT_URL,\n};\n"
  },
  {
    "path": "copy-to-desktop.sh",
    "content": "#!/bin/bash\n\n# Get the version from package.json\nVERSION=$(node -p \"require('./package.json').version\")\n\n# Create desktop directory if it doesn't exist\nDESKTOP_DIR=\"$HOME/Desktop/rn-dev-tools-releases\"\nmkdir -p \"$DESKTOP_DIR\"\n\n# Copy the zip file to desktop\ncp \"out/make/zip/darwin/arm64/React Native DevTools-darwin-arm64-$VERSION.zip\" \"$DESKTOP_DIR/\"\n\necho \"✅ Release copied to $DESKTOP_DIR\" "
  },
  {
    "path": "entitlements.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n  <dict>\n    <key>com.apple.security.cs.allow-jit</key>\n    <true/>\n    <key>com.apple.security.cs.allow-unsigned-executable-memory</key>\n    <true/>\n    <key>com.apple.security.cs.disable-library-validation</key>\n    <true/>\n    <key>com.apple.security.device.audio-input</key>\n    <true/>\n    <key>com.apple.security.device.bluetooth</key>\n    <true/>\n    <key>com.apple.security.device.camera</key>\n    <true/>\n    <key>com.apple.security.device.print</key>\n    <true/>\n    <key>com.apple.security.device.usb</key>\n    <true/>\n    <key>com.apple.security.personal-information.location</key>\n    <true/>\n  </dict>\n</plist> "
  },
  {
    "path": "forge.config.ts",
    "content": "import { config as dotenvConfig } from \"dotenv\";\nimport * as path from \"path\";\n\n// Load .env file from the project root\ndotenvConfig({ path: path.resolve(process.cwd(), \".env\") });\n\n// Debug logging to verify environment variables\nconsole.log(\"Environment variables loaded:\", {\n  APPLE_ID: process.env.APPLE_ID,\n  TEAM_ID: process.env.APPLE_TEAM_ID,\n  DEBUG: process.env.DEBUG,\n});\n\nimport type { ForgeConfig } from \"@electron-forge/shared-types\";\nimport { MakerZIP } from \"@electron-forge/maker-zip\";\nimport { VitePlugin } from \"@electron-forge/plugin-vite\";\nimport { FusesPlugin } from \"@electron-forge/plugin-fuses\";\nimport { FuseV1Options, FuseVersion } from \"@electron/fuses\";\nimport { PublisherGithub } from \"@electron-forge/publisher-github\";\n\nconst config: ForgeConfig = {\n  packagerConfig: {\n    asar: true,\n    icon: \"./assets/icon\", // No file extension required\n    appBundleId: \"com.lovesworking.rn-dev-tools\",\n    appCategoryType: \"public.app-category.developer-tools\",\n    executableName: \"React Native DevTools\",\n    osxSign: {\n      identity: \"6EC9AE0A608BB7CBBA6BCC7936689773E76D63F0\",\n    },\n    // The osxSign config comes with defaults that work out of the box in most cases, so we recommend you start with an empty configuration object.\n    // For a full list of configuration options, see  https://js.electronforge.io/modules/_electron_forge_shared_types.InternalOptions.html#OsxSignOptions\n    osxNotarize: {\n      appleId: process.env.APPLE_ID!,\n      appleIdPassword: process.env.APPLE_PASSWORD!,\n      teamId: process.env.APPLE_TEAM_ID!,\n    },\n  },\n  rebuildConfig: {},\n  makers: [\n    // Build a ZIP for all platforms\n    new MakerZIP({}, [\"darwin\", \"linux\", \"win32\"]),\n    // The following makers are commented out for now\n    // new MakerSquirrel({}),\n    // new MakerRpm({}),\n    // new MakerDeb({})\n  ],\n  publishers: [\n    new PublisherGithub({\n      repository: {\n        owner: \"lovesworking\", // Replace with your GitHub username or organization\n        name: \"rn-better-dev-tools\", // Replace with your repository name\n      },\n      prerelease: false, // Set to true if you want to mark releases as pre-releases\n    }),\n  ],\n  plugins: [\n    new VitePlugin({\n      // `build` can specify multiple entry builds, which can be Main process, Preload scripts, Worker process, etc.\n      // If you are familiar with Vite configuration, it will look really familiar.\n      build: [\n        {\n          // `entry` is just an alias for `build.lib.entry` in the corresponding file of `config`.\n          entry: \"src/main.ts\",\n          config: \"vite.main.config.ts\",\n          target: \"main\",\n        },\n        {\n          entry: \"src/preload.ts\",\n          config: \"vite.preload.config.ts\",\n          target: \"preload\",\n        },\n      ],\n      renderer: [\n        {\n          name: \"main_window\",\n          config: \"vite.renderer.config.ts\",\n        },\n      ],\n    }),\n    // Fuses are used to enable/disable various Electron functionality\n    // at package time, before code signing the application\n    new FusesPlugin({\n      version: FuseVersion.V1,\n      [FuseV1Options.RunAsNode]: false,\n      [FuseV1Options.EnableCookieEncryption]: true,\n      [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,\n      [FuseV1Options.EnableNodeCliInspectArguments]: false,\n      [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,\n      [FuseV1Options.OnlyLoadAppFromAsar]: true,\n    }),\n  ],\n};\n\nexport default config;\n"
  },
  {
    "path": "forge.env.d.ts",
    "content": "/// <reference types=\"@electron-forge/plugin-vite/forge-vite-env\" />\n"
  },
  {
    "path": "index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"UTF-8\" />\n    <title>React Native Dev Tools</title>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/renderer.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"rn-better-dev-tools\",\n  \"productName\": \"React Native DevTools\",\n  \"version\": \"1.1.0\",\n  \"description\": \"Enhanced developer tools for React Native applications (Only supports React Query DevTools Right Now)\",\n  \"main\": \".vite/build/main.js\",\n  \"scripts\": {\n    \"release\": \"./release-version.sh\",\n    \"pack\": \"./build-and-pack.sh\",\n    \"nuke\": \"rm -rf node_modules .vite/build pnpm-lock.yaml && pnpm store prune && pnpm cache clean && pnpm install\",\n    \"start\": \"electron-forge start\",\n    \"package\": \"electron-forge package\",\n    \"make\": \"electron-forge make\",\n    \"make:desktop\": \"pnpm run make && ./copy-to-desktop.sh\",\n    \"publish\": \"electron-forge publish\",\n    \"lint\": \"eslint --ext .ts,.tsx .\",\n    \"auto-release\": \"./auto-release.sh\"\n  },\n  \"keywords\": [],\n  \"author\": {\n    \"name\": \"lovesworking\",\n    \"email\": \"austinlovesworking@gmail.com\"\n  },\n  \"license\": \"MIT\",\n  \"pnpm\": {\n    \"onlyBuiltDependencies\": [\n      \"electron\"\n    ]\n  },\n  \"packageManager\": \"pnpm@10.4.1+sha512.c753b6c3ad7afa13af388fa6d808035a008e30ea9993f58c6663e2bc5ff21679aa834db094987129aa4d488b86df57f7b634981b2f827cdcacc698cc0cfb88af\",\n  \"devDependencies\": {\n    \"@electron-forge/cli\": \"^7.8.0\",\n    \"@electron-forge/maker-deb\": \"^7.8.0\",\n    \"@electron-forge/maker-dmg\": \"^7.8.0\",\n    \"@electron-forge/maker-rpm\": \"^7.8.0\",\n    \"@electron-forge/maker-squirrel\": \"^7.8.0\",\n    \"@electron-forge/maker-zip\": \"^7.8.0\",\n    \"@electron-forge/plugin-auto-unpack-natives\": \"^7.8.0\",\n    \"@electron-forge/plugin-fuses\": \"^7.8.0\",\n    \"@electron-forge/plugin-vite\": \"^7.8.0\",\n    \"@electron-forge/publisher-github\": \"^7.8.0\",\n    \"@electron/fuses\": \"^1.8.0\",\n    \"@types/express\": \"^5.0.1\",\n    \"@types/react\": \"^19.0.12\",\n    \"@types/react-dom\": \"^19.0.4\",\n    \"@types/socket.io\": \"^3.0.2\",\n    \"@typescript-eslint/eslint-plugin\": \"^5.0.0\",\n    \"@typescript-eslint/parser\": \"^5.0.0\",\n    \"@vitejs/plugin-react\": \"^4.3.4\",\n    \"autoprefixer\": \"^10.4.16\",\n    \"dotenv\": \"^16.4.7\",\n    \"electron\": \"35.1.2\",\n    \"eslint\": \"^8.0.1\",\n    \"eslint-plugin-import\": \"^2.25.0\",\n    \"postcss\": \"^8.4.31\",\n    \"tailwindcss\": \"^3.3.5\",\n    \"ts-node\": \"^10.0.0\",\n    \"typescript\": \"~4.5.4\",\n    \"vite\": \"^5.0.12\"\n  },\n  \"dependencies\": {\n    \"@tanstack/query-devtools\": \"file:tanstack-query-devtools-5.74.7.tgz\",\n    \"@tanstack/react-query\": \"^5.75.7\",\n    \"@tanstack/react-query-devtools\": \"file:tanstack-react-query-devtools-5.75.7.tgz\",\n    \"bufferutil\": \"^4.0.9\",\n    \"electron-log\": \"^5.3.3\",\n    \"electron-squirrel-startup\": \"^1.0.1\",\n    \"electron-updater\": \"^6.6.2\",\n    \"express\": \"^4.21.2\",\n    \"react\": \"^19.1.0\",\n    \"react-dom\": \"^19.1.0\",\n    \"react-use\": \"^17.6.0\",\n    \"socket.io\": \"^4.8.1\",\n    \"socket.io-client\": \"^4.8.1\",\n    \"utf-8-validate\": \"^6.0.5\",\n    \"zustand\": \"^5.0.3\"\n  },\n  \"build\": {\n    \"appId\": \"com.lovesworking.rn-dev-tools\",\n    \"productName\": \"React Native DevTools\",\n    \"mac\": {\n      \"category\": \"public.app-category.developer-tools\",\n      \"target\": \"zip\"\n    },\n    \"publish\": {\n      \"provider\": \"github\",\n      \"owner\": \"lovesworking\",\n      \"repo\": \"rn-better-dev-tools\"\n    }\n  }\n}\n"
  },
  {
    "path": "postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n};\n"
  },
  {
    "path": "release-version.sh",
    "content": "#!/bin/bash\n\n# Exit on any error\nset -e\n\n# Colors for better output\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'\nBLUE='\\033[0;34m'\nNC='\\033[0m' # No Color\n\n# Function to display status messages\nfunction echo_status() {\n  echo -e \"${BLUE}[INFO]${NC} $1\"\n}\n\n# Function to display success messages\nfunction echo_success() {\n  echo -e \"${GREEN}[SUCCESS]${NC} $1\"\n}\n\n# Function to display error messages\nfunction echo_error() {\n  echo -e \"${RED}[ERROR]${NC} $1\"\n}\n\n# Function to display warning messages\nfunction echo_warning() {\n  echo -e \"${YELLOW}[WARNING]${NC} $1\"\n}\n\n# Check if gh CLI is installed\nif ! command -v gh &> /dev/null; then\n  echo_error \"GitHub CLI (gh) is not installed. Please install it first.\"\n  echo \"You can install it with: brew install gh\"\n  exit 1\nfi\n\n# Check if gh is authenticated\nif ! gh auth status &> /dev/null; then\n  echo_error \"You're not authenticated with GitHub CLI.\"\n  echo \"Please run: gh auth login\"\n  exit 1\nfi\n\n# Ensure we're on the main branch\ncurrent_branch=$(git rev-parse --abbrev-ref HEAD)\nif [ \"$current_branch\" != \"main\" ]; then\n  echo_warning \"You are not on the main branch. Switching to main...\"\n  git checkout main\nfi\n\n# Make sure the working directory is clean\nif ! git diff-index --quiet HEAD --; then\n  echo_error \"Your working directory has uncommitted changes.\"\n  echo \"Please commit or stash them before running this script.\"\n  exit 1\nfi\n\n# Pull latest changes\necho_status \"Pulling latest changes from main...\"\ngit pull origin main\n\n# Get current version from package.json\ncurrent_version=$(node -e \"console.log(require('./package.json').version)\")\necho_status \"Current version: ${current_version}\"\n\n# Prompt for version bump type\necho \"Select version bump type:\"\necho \"1) Patch (1.0.0 -> 1.0.1) - For bug fixes\"\necho \"2) Minor (1.0.0 -> 1.1.0) - For new features\"\necho \"3) Major (1.0.0 -> 2.0.0) - For breaking changes\"\necho \"4) Custom (Enter a specific version)\"\n\nread -p \"Enter your choice (1-4): \" version_choice\n\ncase $version_choice in\n  1)\n    bump_type=\"patch\"\n    ;;\n  2)\n    bump_type=\"minor\"\n    ;;\n  3)\n    bump_type=\"major\"\n    ;;\n  4)\n    read -p \"Enter the new version (e.g., 1.2.3): \" custom_version\n    bump_type=\"custom\"\n    new_version=$custom_version\n    ;;\n  *)\n    echo_error \"Invalid choice. Exiting.\"\n    exit 1\n    ;;\nesac\n\n# Calculate new version for non-custom bumps\nif [ \"$bump_type\" != \"custom\" ]; then\n  # Split version by dots\n  IFS='.' read -ra VERSION_PARTS <<< \"$current_version\"\n  \n  major=${VERSION_PARTS[0]}\n  minor=${VERSION_PARTS[1]}\n  patch=${VERSION_PARTS[2]}\n  \n  case $bump_type in\n    patch)\n      patch=$((patch + 1))\n      ;;\n    minor)\n      minor=$((minor + 1))\n      patch=0\n      ;;\n    major)\n      major=$((major + 1))\n      minor=0\n      patch=0\n      ;;\n  esac\n  \n  new_version=\"${major}.${minor}.${patch}\"\nfi\n\necho_status \"New version will be: ${new_version}\"\n\n# Prompt for confirmation\nread -p \"Proceed with release? (y/n): \" confirm\nif [[ $confirm != \"y\" && $confirm != \"Y\" ]]; then\n  echo_warning \"Release canceled.\"\n  exit 0\nfi\n\n# Update version in package.json\necho_status \"Updating package.json version to ${new_version}...\"\n# Use a temporary file to avoid issues with inline editing\nnode -e \"\nconst fs = require('fs');\nconst packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));\npackageJson.version = '${new_version}';\nfs.writeFileSync('./package.json', JSON.stringify(packageJson, null, 2) + '\\n');\n\"\n\n# Prompt for release notes\necho \"Enter release notes (or leave empty for default message):\"\necho \"Type your notes and press Ctrl+D when finished (or Ctrl+C to cancel)\"\nrelease_notes=$(cat)\n\nif [ -z \"$release_notes\" ]; then\n  release_notes=\"Version ${new_version}\"\nfi\n\n# Commit the version change\necho_status \"Committing version change...\"\ngit add package.json\ngit commit -m \"Bump version to ${new_version}\"\n\n# Push the changes\necho_status \"Pushing changes to main...\"\ngit push origin main\n\n# Create and push tag\ntag_name=\"v${new_version}\"\necho_status \"Creating and pushing tag ${tag_name}...\"\ngit tag $tag_name\ngit push origin $tag_name\n\necho_success \"Version ${new_version} has been released!\"\necho \"GitHub Actions should now be building and publishing your release.\"\n\n# Monitor the GitHub Actions workflow\necho_status \"Monitoring workflow run...\"\nsleep 3  # Give GitHub a moment to register the workflow\n\n# Find the latest run ID for the tag we just pushed\necho_status \"Getting the latest workflow run ID...\"\nrun_id=$(gh run list --workflow \"Build and Release macOS App\" --limit 1 --json databaseId --jq \".[0].databaseId\")\n\nif [ -z \"$run_id\" ]; then\n  echo_warning \"Could not find the workflow run. Please check GitHub Actions manually.\"\nelse\n  echo_status \"Workflow run ID: ${run_id}\"\n  echo_status \"Watching workflow run (press Ctrl+C to stop watching)...\"\n  gh run watch $run_id\n\n  # Check the status of the run\n  run_status=$(gh run view $run_id --json conclusion --jq \".conclusion\")\n  \n  if [ \"$run_status\" == \"success\" ]; then\n    echo_success \"Workflow completed successfully!\"\n    \n    # Update the release notes\n    echo_status \"Updating release notes...\"\n    gh release edit $tag_name --title \"Version ${new_version}\" --notes \"$release_notes\"\n    \n    # Publish the release (remove the draft status)\n    echo_status \"Publishing release...\"\n    gh release edit $tag_name --draft=false\n    \n    echo_success \"Release v${new_version} has been published!\"\n    echo \"URL: https://github.com/lovesworking/rn-better-dev-tools/releases/tag/${tag_name}\"\n  else\n    echo_error \"Workflow failed or was canceled. Please check GitHub Actions.\"\n    echo \"URL: https://github.com/lovesworking/rn-better-dev-tools/actions\"\n  fi\nfi "
  },
  {
    "path": "socket-client.js",
    "content": "/**\n * Socket.IO Test Client\n *\n * This simple client connects to the Socket.IO server and allows\n * sending messages from the command line.\n */\n\n// We need to use CommonJS require for this standalone script\nconst { io } = require(\"socket.io-client\");\n\n// Import config values from CommonJS config file\nconst { CLIENT_URL } = require(\"./config.js\");\n\nconsole.log(\"Starting Socket.IO test client...\");\nconsole.log(`Connecting to: ${CLIENT_URL}`);\n\n// Connect to the Socket.IO server\nconst socket = io(CLIENT_URL);\n\n// Event handler for successful connection\nsocket.on(\"connect\", () => {\n  console.log(\"✅ Connected to Socket.IO server\");\n  console.log(\"Socket ID:\", socket.id);\n\n  // Send a test message after connection\n  setTimeout(() => {\n    sendMessage(\"Hello from test client!\");\n  }, 1000);\n});\n\n// Event handler for disconnection\nsocket.on(\"disconnect\", () => {\n  console.log(\"❌ Disconnected from Socket.IO server\");\n});\n\n// Event handler for connection errors\nsocket.on(\"connect_error\", (err) => {\n  console.error(\"❌ Connection error:\", err.message);\n});\n\n// Event handler for receiving messages\nsocket.on(\"message\", (data) => {\n  console.log(`📨 Received: \"${data}\"`);\n});\n\n// Add handler for query-action events\nsocket.on(\"query-action\", (data) => {\n  console.log(`📨 Received query-action:`, data);\n});\n\n// Function to send a message\nfunction sendMessage(message) {\n  socket.emit(\"message\", message);\n  console.log(`📤 Sent: \"${message}\"`);\n}\n\n// Allow sending messages from the command line\nprocess.stdin.on(\"data\", (data) => {\n  const message = data.toString().trim();\n  if (message) {\n    sendMessage(message);\n  }\n});\n\nconsole.log(\"Type messages and press Enter to send. Press Ctrl+C to exit.\");\n"
  },
  {
    "path": "src/auto-updater.ts",
    "content": "import { autoUpdater, UpdateInfo, ProgressInfo } from \"electron-updater\";\nimport log from \"electron-log\";\n\n// Configure logger\nlog.transports.file.level = \"info\";\nautoUpdater.logger = log;\n\nexport function setupAutoUpdater() {\n  // Check for updates on startup\n  autoUpdater.checkForUpdatesAndNotify();\n\n  // Set up auto updater events\n  autoUpdater.on(\"checking-for-update\", () => {\n    log.info(\"Checking for update...\");\n  });\n\n  autoUpdater.on(\"update-available\", (info: UpdateInfo) => {\n    log.info(\"Update available:\", info);\n  });\n\n  autoUpdater.on(\"update-not-available\", (info: UpdateInfo) => {\n    log.info(\"Update not available:\", info);\n  });\n\n  autoUpdater.on(\"error\", (err: Error) => {\n    log.error(\"Error in auto-updater:\", err);\n  });\n\n  autoUpdater.on(\"download-progress\", (progressObj: ProgressInfo) => {\n    let logMessage = `Download speed: ${progressObj.bytesPerSecond}`;\n    logMessage = `${logMessage} - Downloaded ${progressObj.percent}%`;\n    logMessage = `${logMessage} (${progressObj.transferred}/${progressObj.total})`;\n    log.info(logMessage);\n  });\n\n  autoUpdater.on(\"update-downloaded\", (info: UpdateInfo) => {\n    log.info(\"Update downloaded:\", info);\n    // Install the update when the app is quit\n    // Alternatively, you could prompt the user here\n  });\n\n  // Check for updates periodically\n  const CHECK_INTERVAL = 1000 * 60 * 60; // Check every hour\n  setInterval(() => {\n    autoUpdater.checkForUpdatesAndNotify();\n  }, CHECK_INTERVAL);\n}\n"
  },
  {
    "path": "src/components/App.tsx",
    "content": "import Providers from \"./external-dash/providers\";\nimport Main from \"./external-dash/Main\";\nexport const App: React.FC = () => {\n  return (\n    <Providers>\n      <Main />\n    </Providers>\n  );\n};\n"
  },
  {
    "path": "src/components/external-dash/Dash.tsx",
    "content": "import React, { useEffect, useState } from \"react\";\nimport { User } from \"./types/User\";\nimport \"../../index.css\";\nimport { ReactQueryDevtools } from \"@tanstack/react-query-devtools/production\";\nimport { useLogStore } from \"./utils/logStore\";\nimport { onDevToolsEvent } from \"./utils/devToolsEvents\";\nimport { useDevToolsEventHandler } from \"./hooks/useDevToolsEventHandler\";\n\nimport { DeviceSelection } from \"./DeviceSelection\";\nimport { UserInfo } from \"./UserInfo\";\nimport { LogConsole } from \"./LogConsole\";\nimport { NoDevicesConnected } from \"./NoDevicesConnected\";\nimport { StorageControlsSection } from \"./UserInfo/StorageControlsSection\";\n\nexport const PlatformIcon: React.FC<{ platform: string }> = ({ platform }) => {\n  const normalizedPlatform = platform?.toLowerCase() || \"\";\n\n  switch (normalizedPlatform) {\n    case \"ios\":\n      return (\n        <svg className=\"w-3 h-3\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n          <path d=\"M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.81-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z\" />\n        </svg>\n      );\n    case \"android\":\n      return (\n        <svg className=\"w-3 h-3\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n          <path d=\"M16.61 15.15c-.46 0-.84-.37-.84-.83s.37-.83.84-.83.83.37.83.83-.37.83-.83.83m-9.22 0c-.46 0-.83-.37-.83-.83s.37-.83.83-.83.84.37.84.83-.37.83-.84.83m9.42-5.89l1.67-2.89c.09-.17.03-.38-.13-.47-.17-.09-.38-.03-.47.13l-1.69 2.93A9.973 9.973 0 0012 7.75c-1.89 0-3.63.52-5.19 1.37L5.12 6.19c-.09-.17-.3-.22-.47-.13-.17.09-.22.3-.13.47l1.67 2.89C3.44 11.15 1.62 14.56 1.62 18h20.76c0-3.44-1.82-6.85-4.57-8.74z\" />\n        </svg>\n      );\n    case \"web\":\n      return (\n        <svg className=\"w-3 h-3\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n          <path d=\"M16.36 14c.08-.66.14-1.32.14-2 0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2m-5.15 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95a8.03 8.03 0 01-4.33 3.56M14.34 14H9.66c-.1-.66-.16-1.32-.16-2 0-.68.06-1.35.16-2h4.68c.09.65.16 1.32.16 2 0 .68-.07 1.34-.16 2M12 19.96c-.83-1.2-1.5-2.53-1.91-3.96h3.82c-.41 1.43-1.08 2.76-1.91 3.96M8 8H5.08A7.923 7.923 0 019.4 4.44C8.8 5.55 8.35 6.75 8 8m-2.92 8H8c.35 1.25.8 2.45 1.4 3.56A8.008 8.008 0 015.08 16m-.82-2C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2 0 .68.06 1.34.14 2M12 4.03c.83 1.2 1.5 2.54 1.91 3.97h-3.82c.41-1.43 1.08-2.77 1.91-3.97M18.92 8h-2.95a15.65 15.65 0 00-1.38-3.56c1.84.63 3.37 1.9 4.33 3.56M12 2C6.47 2 2 6.5 2 12a10 10 0 0010 10 10 10 0 0010-10A10 10 0 0012 2z\" />\n        </svg>\n      );\n    case \"tv\":\n    case \"tvos\":\n      return (\n        <svg className=\"w-3 h-3\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n          <path d=\"M21 17H3V5h18m0-2H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h5v2h8v-2h5c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z\" />\n        </svg>\n      );\n    default:\n      return (\n        <svg className=\"w-3 h-3\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n          <path d=\"M17.25 18H6.75V4h10.5M14 21h-4v-1h4m2-19H8C6.34 1 5 2.34 5 4v16c0 1.66 1.34 3 3 3h8c1.66 0 3-1.34 3-3V4c0-1.66-1.34-3-3-3z\" />\n        </svg>\n      );\n  }\n};\n\nexport const getPlatformColor = (platform: string): string => {\n  const normalizedPlatform = platform?.toLowerCase() || \"\";\n  switch (normalizedPlatform) {\n    case \"ios\":\n      return \"text-gray-100\";\n    case \"android\":\n      return \"text-green-300\";\n    case \"web\":\n      return \"text-blue-300\";\n    case \"tv\":\n    case \"tvos\":\n      return \"text-purple-300\";\n    default:\n      return \"text-gray-300\";\n  }\n};\n\nexport const getPlatformBgColor = (platform: string): string => {\n  const normalizedPlatform = platform?.toLowerCase() || \"\";\n  switch (normalizedPlatform) {\n    case \"ios\":\n      return \"bg-blue-900/30 text-blue-200\";\n    case \"android\":\n      return \"bg-green-900/30 text-green-200\";\n    case \"web\":\n      return \"bg-cyan-900/30 text-cyan-200\";\n    case \"tv\":\n    case \"tvos\":\n      return \"bg-purple-900/30 text-purple-200\";\n    default:\n      return \"bg-gray-800/60 text-gray-300\";\n  }\n};\n\ninterface DashProps {\n  allDevices: User[];\n  isDashboardConnected: boolean;\n  targetDevice: User;\n  setTargetDevice: (device: User) => void;\n}\n\nexport const Dash: React.FC<DashProps> = ({\n  isDashboardConnected,\n  allDevices,\n  targetDevice,\n  setTargetDevice,\n}) => {\n  const [showOfflineDevices, setShowOfflineDevices] = useState(true);\n  const { isEnabled, setIsEnabled, isVisible, setIsVisible } = useLogStore();\n\n  const filteredDevices = showOfflineDevices\n    ? allDevices\n    : allDevices.filter((device) => {\n        if (typeof device === \"string\") {\n          return false;\n        }\n        return device.isConnected;\n      });\n\n  // Find the target device\n  useEffect(() => {\n    const foundDevice = filteredDevices?.find((device) => {\n      return device.deviceId === targetDevice.deviceId;\n    });\n    foundDevice && setTargetDevice(foundDevice);\n  }, [setTargetDevice, filteredDevices, targetDevice]);\n\n  useDevToolsEventHandler();\n\n  return (\n    <div className=\"font-sf-pro\">\n      <div className=\"flex flex-col w-full h-screen overflow-hidden bg-[#0A0A0C] text-white\">\n        <header className=\"w-full px-5 py-4 border-b border-[#2D2D2F]/50 flex justify-between items-center flex-shrink-0 bg-[#0A0A0C]/95 sticky top-0 z-20 shadow-[0_0.5rem_1rem_rgba(0,0,0,0.2)]\">\n          <div className=\"flex items-center gap-4\">\n            {/* Connection Status */}\n            <div\n              className={`flex items-center gap-2 px-3 py-1.5 rounded-full  ${\n                isDashboardConnected\n                  ? \"bg-gradient-to-r from-green-500/10 to-green-500/5 border border-green-500/20 shadow-[0_0_10px_rgba(74,222,128,0.1)]\"\n                  : \"bg-gradient-to-r from-red-500/10 to-red-500/5 border border-red-500/20 shadow-[0_0_10px_rgba(248,113,113,0.1)]\"\n              }`}\n            >\n              <div className=\"relative flex items-center justify-center\">\n                <div\n                  className={`w-2 h-2 rounded-full ${\n                    isDashboardConnected ? \"bg-green-400\" : \"bg-red-400\"\n                  }`}\n                >\n                  {/* Simple dot with no animations */}\n                </div>\n              </div>\n              <span\n                className={`text-xs font-medium tracking-wide ${\n                  isDashboardConnected ? \"text-green-300\" : \"text-red-300\"\n                }`}\n              >\n                {isDashboardConnected ? \"Connected\" : \"Disconnected\"}\n              </span>\n            </div>\n          </div>\n\n          {/* Right Section */}\n          <div className=\"flex items-center gap-5\">\n            {/* Offline Toggle */}\n            <div className=\"flex items-center gap-4\">\n              <button\n                onClick={() => setShowOfflineDevices(!showOfflineDevices)}\n                className=\"group flex items-center gap-3 px-3 py-1.5 rounded-full transition-all duration-500 ease-out hover:bg-[#1D1D1F]/40 border border-transparent hover:border-[#2D2D2F]/70\"\n                aria-pressed={showOfflineDevices}\n                role=\"switch\"\n              >\n                <div\n                  className={`w-8 h-4 rounded-full flex items-center px-0.5 transition-all duration-500 ease-out ${\n                    showOfflineDevices ? \"bg-blue-500\" : \"bg-[#3D3D3F]\"\n                  }`}\n                >\n                  <div\n                    className={`w-3 h-3 rounded-full bg-white shadow-md transform transition-all duration-500 ease-out ${\n                      showOfflineDevices ? \"translate-x-4\" : \"translate-x-0\"\n                    }`}\n                  />\n                </div>\n                <span className=\"text-xs font-medium text-[#A1A1A6] group-hover:text-[#F5F5F7] transition-colors duration-300\">\n                  Show Offline Devices\n                </span>\n              </button>\n\n              {/* Logs Toggle */}\n              <button\n                onClick={() => setIsEnabled(!isEnabled)}\n                className=\"group flex items-center gap-3 px-3 py-1.5 rounded-full transition-all duration-500 ease-out hover:bg-[#1D1D1F]/40 border border-transparent hover:border-[#2D2D2F]/70\"\n                aria-pressed={isEnabled}\n                role=\"switch\"\n              >\n                <div\n                  className={`w-8 h-4 rounded-full flex items-center px-0.5 transition-all duration-500 ease-out ${\n                    isEnabled ? \"bg-blue-500\" : \"bg-[#3D3D3F]\"\n                  }`}\n                >\n                  <div\n                    className={`w-3 h-3 rounded-full bg-white shadow-md transform transition-all duration-500 ease-out ${\n                      isEnabled ? \"translate-x-4\" : \"translate-x-0\"\n                    }`}\n                  />\n                </div>\n                <span className=\"text-xs font-medium text-[#A1A1A6] group-hover:text-[#F5F5F7] transition-colors duration-300\">\n                  Logs\n                </span>\n              </button>\n\n              {/* Storage Controls */}\n              <StorageControlsSection />\n            </div>\n\n            {/* Separator */}\n            <div className=\"h-5 w-px bg-[#2D2D2F]/70\"></div>\n\n            {/* Device Selection */}\n            <div className=\"flex-shrink-0\">\n              <DeviceSelection\n                selectedDevice={targetDevice}\n                setSelectedDevice={setTargetDevice}\n                allDevices={filteredDevices}\n              />\n            </div>\n          </div>\n        </header>\n\n        <main className=\"flex-1 overflow-y-auto p-6 pb-72 bg-gradient-to-b from-[#0A0A0C] to-[#121214]\">\n          <div className=\"px-2 max-w-3xl mx-auto\">\n            {/* Device count and stats */}\n            {filteredDevices.length > 0 ? (\n              <>\n                <div className=\"mb-6 bg-[#1A1A1C] border border-[#2D2D2F]/60 rounded-2xl p-5 shadow-[0_0.5rem_1.5rem_rgba(0,0,0,0.25)] transition-all duration-500 ease-out hover:shadow-[0_0.5rem_2rem_rgba(0,0,0,0.35)]\">\n                  <div className=\"flex items-center justify-between\">\n                    <div className=\"flex items-center gap-3\">\n                      <div className=\"text-sm flex items-center gap-2 px-3.5 py-1.5 bg-[#0A0A0C]/80 rounded-full border border-[#2D2D2F]/50 shadow-inner\">\n                        <span className=\"text-blue-300 font-mono bg-blue-500/10 px-2 py-0.5 rounded-md border border-blue-500/20 text-xs\">\n                          {filteredDevices.length}\n                        </span>\n                        <span className=\"text-white font-medium\">\n                          {filteredDevices.length === 1 ? \"device\" : \"devices\"}\n                        </span>\n                      </div>\n\n                      <div className=\"flex items-center gap-2 text-sm\">\n                        <div className=\"flex items-center gap-2 px-3.5 py-1.5 bg-green-900/20 rounded-full border border-green-900/30 shadow-inner\">\n                          <div className=\"w-1.5 h-1.5 rounded-full bg-green-500\"></div>\n                          <span className=\"text-green-300 font-medium\">\n                            {allDevices.filter((d) => d.isConnected).length}{\" \"}\n                            online\n                          </span>\n                        </div>\n                        <div className=\"flex items-center gap-2 px-3.5 py-1.5 bg-red-900/20 rounded-full border border-red-900/30 shadow-inner\">\n                          <div className=\"w-1.5 h-1.5 rounded-full bg-red-500\"></div>\n                          <span className=\"text-red-300 font-medium\">\n                            {allDevices.filter((d) => !d.isConnected).length}{\" \"}\n                            offline\n                          </span>\n                        </div>\n                      </div>\n                    </div>\n\n                    {targetDevice && (\n                      <div className=\"flex items-center gap-3 px-4 py-2 bg-[#0A0A0C]/60 rounded-full border border-[#2D2D2F]/50 shadow-sm \">\n                        <div className=\"flex items-center gap-2\">\n                          <div\n                            className={`flex items-center gap-2 ${\n                              targetDevice.deviceId === \"All\"\n                                ? \"text-blue-300\"\n                                : targetDevice.isConnected\n                                ? \"text-green-300\"\n                                : \"text-red-300\"\n                            }`}\n                          >\n                            <div className=\"w-1.5 h-1.5 rounded-full bg-current\"></div>\n                            <span className=\"text-xs font-medium uppercase tracking-wider text-[#A1A1A6]\">\n                              Target\n                            </span>\n                          </div>\n                          <span className=\"text-sm font-medium text-white\">\n                            {targetDevice.deviceId === \"All\"\n                              ? \"All Devices\"\n                              : targetDevice.deviceName}\n                          </span>\n                        </div>\n                        {targetDevice.deviceId !== \"All\" &&\n                          targetDevice.platform && (\n                            <div\n                              className={`flex items-center ${getPlatformColor(\n                                targetDevice.platform\n                              )}`}\n                            >\n                              <PlatformIcon platform={targetDevice.platform} />\n                            </div>\n                          )}\n                      </div>\n                    )}\n                  </div>\n                </div>\n\n                {/* Device List */}\n                <div className=\"space-y-5 transition-all duration-300\">\n                  {filteredDevices.map((device) => (\n                    <UserInfo\n                      key={device.id}\n                      userData={device}\n                      isTargeted={\n                        targetDevice.deviceId === \"All\" ||\n                        targetDevice.deviceId === device.deviceId\n                      }\n                    />\n                  ))}\n                </div>\n              </>\n            ) : (\n              <NoDevicesConnected />\n            )}\n          </div>\n        </main>\n\n        {/* Console Panel */}\n        <div className=\"fixed bottom-4 right-4 z-40\">\n          {isVisible ? (\n            <div className=\"fixed inset-x-0 bottom-0 z-40 bg-[#0A0A0C] border-t border-[#2D2D2F]/50 shadow-2xl transition-all duration-500 ease-out transform animate-slideUpFade\">\n              <LogConsole\n                onClose={() => setIsVisible(false)}\n                allDevices={allDevices}\n              />\n            </div>\n          ) : (\n            <button\n              onClick={() => setIsVisible(true)}\n              className=\"flex items-center justify-center w-12 h-12 bg-[#1A1A1C] hover:bg-[#2D2D2F] text-[#F5F5F7] rounded-full shadow-[0_0.5rem_1rem_rgba(0,0,0,0.2)] border border-[#2D2D2F]/50 transition-all duration-500 ease-out hover:scale-110 hover:shadow-[0_0.5rem_1.5rem_rgba(59,130,246,0.2)]\"\n            >\n              <svg\n                className=\"w-5 h-5\"\n                viewBox=\"0 0 24 24\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                strokeWidth=\"2\"\n              >\n                <path\n                  strokeLinecap=\"round\"\n                  strokeLinejoin=\"round\"\n                  d=\"M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z\"\n                />\n              </svg>\n            </button>\n          )}\n        </div>\n\n        <div className=\"fixed bottom-20 right-4 z-50\">\n          <ReactQueryDevtools\n            initialIsOpen={false}\n            position=\"bottom\"\n            buttonPosition=\"relative\"\n          />\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "src/components/external-dash/DeviceSelection.tsx",
    "content": "import React, { useEffect, useState, useRef } from \"react\";\nimport { User } from \"./types/User\";\nimport { PlatformIcon } from \"./utils/platformUtils\";\n\ninterface Props {\n  selectedDevice: User;\n  setSelectedDevice: (device: User) => void;\n  allDevices?: User[];\n}\n\ninterface DeviceOption {\n  value: string;\n  label: string;\n  isOffline?: boolean;\n  platform?: string;\n}\n\nexport const DeviceSelection: React.FC<Props> = ({\n  selectedDevice,\n  setSelectedDevice,\n  allDevices = [],\n}: Props) => {\n  const [isOpen, setIsOpen] = useState(false);\n  const ref = useRef<HTMLDivElement>(null);\n\n  // Close dropdown when clicking outside\n  useEffect(() => {\n    const handleClickOutside = (event: MouseEvent) => {\n      if (ref.current && !ref.current.contains(event.target as Node)) {\n        setIsOpen(false);\n      }\n    };\n\n    document.addEventListener(\"mousedown\", handleClickOutside);\n    return () => {\n      document.removeEventListener(\"mousedown\", handleClickOutside);\n    };\n  }, []);\n\n  // Generate options\n  const generateOptions = (): DeviceOption[] => {\n    if (allDevices.length === 0) {\n      return [\n        {\n          value: \"All\",\n          label: \"No Devices\",\n        },\n      ];\n    }\n\n    if (allDevices.length === 1) {\n      const device = allDevices[0];\n      return [\n        {\n          value: device.deviceId,\n          label: device.deviceName,\n          isOffline: !device.isConnected,\n          platform: device.platform,\n        },\n      ];\n    }\n\n    // Multiple devices - add 'All' option\n    return [\n      {\n        value: \"All\",\n        label: \"All Devices\",\n      },\n      ...allDevices.map((device) => ({\n        value: device.deviceId,\n        label: device.deviceName,\n        isOffline: !device.isConnected,\n        platform: device.platform,\n      })),\n    ];\n  };\n\n  const deviceOptions = generateOptions();\n\n  // If there are no devices, select \"All\"\n  useEffect(() => {\n    if (!selectedDevice || !selectedDevice.deviceId) {\n      if (deviceOptions.length > 0) {\n        const foundDevice = allDevices.find(\n          (device) => device.deviceId === \"All\"\n        );\n        if (foundDevice) {\n          setSelectedDevice(foundDevice);\n        }\n      }\n    }\n\n    // If the selected device is no longer in the device list, select \"All\"\n    if (\n      selectedDevice &&\n      selectedDevice.deviceId !== \"All\" &&\n      !allDevices.find((device) => device.deviceId === selectedDevice.deviceId)\n    ) {\n      const foundDevice = allDevices.find(\n        (device) => device.deviceId === \"All\"\n      );\n      if (foundDevice) {\n        setSelectedDevice(foundDevice);\n      }\n    }\n  }, [allDevices, selectedDevice, setSelectedDevice, deviceOptions]);\n\n  // Handle device selection\n  const handleSelect = (deviceId: string) => {\n    if (deviceId === \"All\") {\n      setSelectedDevice({\n        id: \"all-devices\",\n        deviceId: \"All\",\n        deviceName: \"All Devices\",\n        isConnected: true,\n        platform: undefined,\n      });\n    } else {\n      const device = allDevices.find((d) => d.deviceId === deviceId);\n      if (device) {\n        setSelectedDevice(device);\n      }\n    }\n    setIsOpen(false);\n  };\n\n  // Get selected device label\n  const getSelectedDeviceLabel = (): string => {\n    if (!selectedDevice || !selectedDevice.deviceId) {\n      return deviceOptions[0]?.label || \"All Devices\";\n    }\n\n    return selectedDevice.deviceName || \"Unknown Device\";\n  };\n\n  const StatusDot: React.FC<{ isOffline?: boolean }> = ({ isOffline }) => (\n    <span\n      className={`w-1.5 h-1.5 rounded-full ${\n        isOffline ? \"bg-red-500\" : \"bg-green-500\"\n      }`}\n    />\n  );\n\n  return (\n    <div ref={ref} className=\"relative w-48 font-sf-pro\">\n      <button\n        type=\"button\"\n        onClick={() => setIsOpen(!isOpen)}\n        className={`flex items-center justify-between w-full px-4 py-2.5 text-sm font-medium transition-all duration-300 \n        ${\n          isOpen\n            ? \"text-white bg-[#2D2D2F] border-blue-500/40 ring-2 ring-blue-500/10 shadow-[0_0_12px_rgba(59,130,246,0.15)]\"\n            : \"text-white bg-[#1A1A1C] hover:bg-[#2D2D2F] border-[#2D2D2F]\"\n        } border rounded-xl`}\n        aria-haspopup=\"listbox\"\n        aria-expanded={isOpen}\n      >\n        <div className=\"flex items-center gap-2 truncate\">\n          {selectedDevice?.platform && selectedDevice.deviceId !== \"All\" ? (\n            <>\n              <StatusDot isOffline={!selectedDevice.isConnected} />\n              <span className=\"flex items-center justify-center w-5 h-5 rounded-full bg-[#0A0A0C]\">\n                <PlatformIcon\n                  platform={selectedDevice.platform}\n                  className=\"w-3 h-3\"\n                />\n              </span>\n            </>\n          ) : (\n            <>\n              <span className=\"w-1.5\" /> {/* Spacer for alignment */}\n              <span className=\"flex items-center justify-center w-5 h-5 rounded-full bg-[#0A0A0C]\">\n                <svg\n                  className=\"w-3 h-3 text-blue-400\"\n                  viewBox=\"0 0 24 24\"\n                  fill=\"none\"\n                  stroke=\"currentColor\"\n                  strokeWidth=\"2\"\n                >\n                  <path\n                    strokeLinecap=\"round\"\n                    strokeLinejoin=\"round\"\n                    d=\"M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z\"\n                  />\n                </svg>\n              </span>\n            </>\n          )}\n          <span className=\"truncate\">{getSelectedDeviceLabel()}</span>\n        </div>\n        <svg\n          className={`w-4 h-4 ml-2 transition-transform duration-300 ${\n            isOpen ? \"rotate-180 text-blue-400\" : \"text-[#A1A1A6]\"\n          }`}\n          viewBox=\"0 0 24 24\"\n          fill=\"none\"\n          stroke=\"currentColor\"\n          strokeWidth=\"2\"\n        >\n          <path\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n            d=\"M19 9l-7 7-7-7\"\n          />\n        </svg>\n      </button>\n\n      {isOpen && (\n        <div className=\"absolute z-50 w-full mt-2 animate-scaleIn\">\n          <div className=\"py-1 overflow-hidden bg-[#1A1A1C] rounded-xl shadow-lg border border-[#2D2D2F] ring-1 ring-black/5 shadow-[0_0.5rem_1rem_rgba(0,0,0,0.3)]\">\n            {/* Sticky header for All Devices */}\n            {deviceOptions.length > 1 && deviceOptions[0].value === \"All\" && (\n              <div className=\"sticky top-0 z-10 bg-[#1A1A1C] border-b border-[#2D2D2F]/70\">\n                <button\n                  type=\"button\"\n                  className={`flex items-center w-full px-4 py-3 text-sm font-medium border-b border-[#2D2D2F] transition-colors duration-300 ${\n                    selectedDevice?.deviceId === \"All\"\n                      ? \"bg-blue-900/20 text-blue-300\"\n                      : \"text-white hover:bg-[#2D2D2F]\"\n                  }`}\n                  onClick={() => handleSelect(\"All\")}\n                >\n                  <span className=\"w-1.5\" /> {/* Spacer for alignment */}\n                  <span className=\"flex items-center justify-center w-5 h-5 mr-2 rounded-full bg-[#0A0A0C]\">\n                    <svg\n                      className=\"w-3 h-3 text-blue-400\"\n                      viewBox=\"0 0 24 24\"\n                      fill=\"none\"\n                      stroke=\"currentColor\"\n                      strokeWidth=\"2\"\n                    >\n                      <path\n                        strokeLinecap=\"round\"\n                        strokeLinejoin=\"round\"\n                        d=\"M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z\"\n                      />\n                    </svg>\n                  </span>\n                  All Devices\n                  {selectedDevice?.deviceId === \"All\" && (\n                    <svg\n                      className=\"w-4 h-4 ml-auto text-blue-400\"\n                      viewBox=\"0 0 24 24\"\n                      fill=\"none\"\n                      stroke=\"currentColor\"\n                      strokeWidth=\"2\"\n                    >\n                      <path\n                        strokeLinecap=\"round\"\n                        strokeLinejoin=\"round\"\n                        d=\"M5 13l4 4L19 7\"\n                      />\n                    </svg>\n                  )}\n                </button>\n              </div>\n            )}\n\n            {/* Individual devices */}\n            <div className=\"max-h-56 overflow-y-auto p-1\">\n              {deviceOptions\n                .filter((option) => option.value !== \"All\")\n                .map((option, index) => (\n                  <button\n                    key={option.value}\n                    type=\"button\"\n                    className={`flex items-center w-full px-3 py-2 text-sm rounded-lg my-0.5 transition-all duration-300 ${\n                      selectedDevice?.deviceId === option.value\n                        ? \"bg-blue-500/10 text-blue-300 ring-1 ring-blue-500/20\"\n                        : option.isOffline\n                        ? \"text-gray-400 hover:bg-[#2D2D2F]/50\"\n                        : \"text-white hover:bg-[#2D2D2F]/70\"\n                    }`}\n                    onClick={() => handleSelect(option.value)}\n                    style={{ animationDelay: `${index * 30}ms` }}\n                  >\n                    <div className=\"flex items-center gap-2 truncate\">\n                      <StatusDot isOffline={option.isOffline} />\n                      {option.platform ? (\n                        <span className=\"flex items-center justify-center w-5 h-5 rounded-full bg-[#0A0A0C]\">\n                          <PlatformIcon\n                            platform={option.platform}\n                            className=\"w-3 h-3\"\n                          />\n                        </span>\n                      ) : (\n                        <span className=\"flex items-center justify-center w-5 h-5 rounded-full bg-[#0A0A0C]\">\n                          <svg\n                            className=\"w-3 h-3 text-gray-400\"\n                            viewBox=\"0 0 24 24\"\n                            fill=\"none\"\n                            stroke=\"currentColor\"\n                            strokeWidth=\"2\"\n                          >\n                            <path\n                              strokeLinecap=\"round\"\n                              strokeLinejoin=\"round\"\n                              d=\"M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z\"\n                            />\n                          </svg>\n                        </span>\n                      )}\n                      <span className=\"truncate\">{option.label}</span>\n\n                      {/* Selected checkmark */}\n                      {selectedDevice?.deviceId === option.value && (\n                        <svg\n                          className=\"w-4 h-4 ml-auto text-blue-400\"\n                          viewBox=\"0 0 24 24\"\n                          fill=\"none\"\n                          stroke=\"currentColor\"\n                          strokeWidth=\"2\"\n                        >\n                          <path\n                            strokeLinecap=\"round\"\n                            strokeLinejoin=\"round\"\n                            d=\"M5 13l4 4L19 7\"\n                          />\n                        </svg>\n                      )}\n                    </div>\n                  </button>\n                ))}\n            </div>\n          </div>\n        </div>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "src/components/external-dash/LogConsole.tsx",
    "content": "import React, { useEffect, useRef, useState } from \"react\";\nimport { useLogStore, LogEntry, LogLevel } from \"./utils/logStore\";\nimport { PlatformIcon } from \"./utils/platformUtils\";\nimport { User } from \"./types/User\";\n\n// Get log level color\nconst getLogLevelColor = (level: LogLevel): string => {\n  switch (level) {\n    case \"error\":\n      return \"text-red-400\";\n    case \"warn\":\n      return \"text-yellow-400\";\n    case \"debug\":\n      return \"text-purple-400\";\n    case \"info\":\n    default:\n      return \"text-blue-400\";\n  }\n};\n\nconst formatTimestamp = (date: Date): string => {\n  return date.toLocaleTimeString([], {\n    hour12: false,\n    hour: \"2-digit\",\n    minute: \"2-digit\",\n    second: \"2-digit\",\n  });\n};\n\ninterface LogEntryItemProps {\n  log: LogEntry;\n}\n\nconst LogEntryItem: React.FC<LogEntryItemProps> = ({ log }) => {\n  return (\n    <div className=\"py-1 font-mono text-xs border-b border-gray-800 hover:bg-gray-800/30 transition-colors\">\n      <div className=\"flex items-start gap-2\">\n        {/* Timestamp */}\n        <span className=\"text-gray-500 whitespace-nowrap min-w-[5rem] pl-2\">\n          {formatTimestamp(log.timestamp)}\n        </span>\n\n        {/* Log level */}\n        <span\n          className={`uppercase font-bold min-w-[3rem] ${getLogLevelColor(\n            log.level\n          )}`}\n        >\n          {log.level}\n        </span>\n\n        {/* Device info if available */}\n        {log.platform && (\n          <span className=\"flex items-center gap-1 text-gray-400 min-w-[8rem]\">\n            <PlatformIcon platform={log.platform} className=\"w-3 h-3\" />\n            <span className=\"truncate max-w-[7rem]\">\n              {log.deviceName || \"Unknown\"}\n            </span>\n          </span>\n        )}\n\n        {/* Log message */}\n        <span className=\"text-gray-300 break-words flex-1\">{log.message}</span>\n      </div>\n    </div>\n  );\n};\n\ninterface DeviceOption {\n  value: string;\n  label: string;\n  disabled?: boolean;\n  isOffline?: boolean;\n  platform?: string;\n}\n\ninterface LogConsoleProps {\n  onClose: () => void;\n  allDevices: User[];\n}\n\nexport const LogConsole: React.FC<LogConsoleProps> = ({\n  onClose,\n  allDevices,\n}) => {\n  const logs = useLogStore((state: { logs: LogEntry[] }) => state.logs);\n  const clearLogs = useLogStore(\n    (state: { clearLogs: () => void }) => state.clearLogs\n  );\n  const [filter, setFilter] = useState<LogLevel | \"all\">(\"all\");\n  const [deviceFilter, setDeviceFilter] = useState<string>(\"all\");\n\n  // Auto-scroll functionality\n  const scrollRef = useRef<HTMLDivElement>(null);\n  const [autoScroll, setAutoScroll] = useState(true);\n\n  // Resizable functionality\n  const [height, setHeight] = useState(320); // Default height in pixels\n  const resizableRef = useRef<HTMLDivElement>(null);\n  const [isDragging, setIsDragging] = useState(false);\n\n  // Calculate max height based on window height\n  const calculateMaxHeight = () => {\n    // Leave space for the header (approximately 64px) and some padding\n    return window.innerHeight - 80;\n  };\n\n  const handleResizeStart = (e: React.MouseEvent) => {\n    e.preventDefault(); // Prevent text selection\n    if (e.button !== 0) return; // Only handle left mouse button\n\n    setIsDragging(true);\n    const startY = e.clientY;\n    const startHeight = height;\n\n    const handleResizeMove = (e: MouseEvent) => {\n      const deltaY = startY - e.clientY;\n      const maxHeight = calculateMaxHeight();\n      const newHeight = Math.max(\n        200,\n        Math.min(maxHeight, startHeight + deltaY)\n      );\n      setHeight(newHeight);\n    };\n\n    const handleResizeEnd = () => {\n      setIsDragging(false);\n      document.removeEventListener(\"mousemove\", handleResizeMove);\n      document.removeEventListener(\"mouseup\", handleResizeEnd);\n      document.body.style.cursor = \"default\";\n    };\n\n    document.addEventListener(\"mousemove\", handleResizeMove);\n    document.addEventListener(\"mouseup\", handleResizeEnd);\n    document.body.style.cursor = \"ns-resize\";\n  };\n\n  // Update max height on window resize\n  useEffect(() => {\n    const handleResize = () => {\n      const maxHeight = calculateMaxHeight();\n      if (height > maxHeight) {\n        setHeight(maxHeight);\n      }\n    };\n\n    window.addEventListener(\"resize\", handleResize);\n    return () => window.removeEventListener(\"resize\", handleResize);\n  }, [height]);\n\n  useEffect(() => {\n    if (autoScroll && scrollRef.current) {\n      scrollRef.current.scrollTop = 0;\n    }\n  }, [logs, autoScroll]);\n\n  // Reset device filter if selected device is no longer available\n  useEffect(() => {\n    if (deviceFilter !== \"all\") {\n      const deviceExists = allDevices.some(\n        (device) => device.deviceId === deviceFilter\n      );\n      if (!deviceExists) {\n        setDeviceFilter(\"all\");\n      }\n    }\n  }, [allDevices, deviceFilter]);\n\n  // Filter logs based on level and device\n  const filteredLogs = logs.filter((log: LogEntry) => {\n    const matchesLevel = filter === \"all\" || log.level === filter;\n    const matchesDevice =\n      deviceFilter === \"all\" || log.deviceId === deviceFilter;\n    return matchesLevel && matchesDevice;\n  });\n\n  // Generate device options based on available devices\n  const deviceOptions: DeviceOption[] = (() => {\n    if (allDevices?.length === 0) {\n      return [{ value: \"all\", label: \"All Devices\" }];\n    } else if (allDevices?.length === 1) {\n      // Only one device, no need for \"All\" option\n      const device = allDevices[0];\n      return [\n        {\n          value: device.deviceId,\n          label: device.deviceName || \"Unknown Device\",\n          isOffline: !device.isConnected,\n          platform: device.platform,\n        },\n      ];\n    } else {\n      // Multiple devices, include \"All\" option\n      return [\n        { value: \"all\", label: \"All Devices\" },\n        ...allDevices.map((device) => ({\n          value: device.deviceId,\n          label: device.deviceName || \"Unknown Device\",\n          isOffline: !device.isConnected,\n          platform: device.platform,\n        })),\n      ];\n    }\n  })();\n\n  const [isDeviceDropdownOpen, setIsDeviceDropdownOpen] = useState(false);\n  const [isLevelDropdownOpen, setIsLevelDropdownOpen] = useState(false);\n  const deviceDropdownRef = useRef<HTMLDivElement>(null);\n  const levelDropdownRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    const handleClickOutside = (event: MouseEvent) => {\n      if (\n        deviceDropdownRef.current &&\n        !deviceDropdownRef.current.contains(event.target as Node)\n      ) {\n        setIsDeviceDropdownOpen(false);\n      }\n      if (\n        levelDropdownRef.current &&\n        !levelDropdownRef.current.contains(event.target as Node)\n      ) {\n        setIsLevelDropdownOpen(false);\n      }\n    };\n\n    document.addEventListener(\"mousedown\", handleClickOutside);\n    return () => document.removeEventListener(\"mousedown\", handleClickOutside);\n  }, []);\n\n  const levelOptions = [\n    { value: \"all\", label: \"All\" },\n    { value: \"info\", label: \"Info\" },\n    { value: \"warn\", label: \"Warn\" },\n    { value: \"error\", label: \"Error\" },\n    { value: \"debug\", label: \"Debug\" },\n  ];\n\n  return (\n    <>\n      {/* Resize handle */}\n      <div\n        className=\"h-1 bg-transparent hover:bg-gray-600/50 cursor-ns-resize relative group\"\n        onMouseDown={handleResizeStart}\n      >\n        <div className=\"absolute inset-x-0 h-0.5 bottom-0 bg-gray-700/50 group-hover:bg-gray-500/50\" />\n      </div>\n\n      {/* Header */}\n      <div className=\"flex items-center justify-between px-4 py-2 border-b border-gray-700/50\">\n        <div className=\"flex items-center gap-2\">\n          <svg\n            className=\"w-4 h-4 text-gray-400\"\n            viewBox=\"0 0 24 24\"\n            fill=\"none\"\n            stroke=\"currentColor\"\n            strokeWidth=\"2\"\n          >\n            <path\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n              d=\"M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z\"\n            />\n          </svg>\n          <span className=\"text-sm font-medium text-gray-200\">\n            Console ({filteredLogs.length} logs)\n          </span>\n        </div>\n\n        <div className=\"flex items-center gap-4\">\n          {/* Device Filter */}\n          <div className=\"flex items-center gap-2 text-xs relative\">\n            <span className=\"text-gray-400\">Device:</span>\n            <div className=\"relative\" ref={deviceDropdownRef}>\n              <button\n                onClick={() => setIsDeviceDropdownOpen(!isDeviceDropdownOpen)}\n                className=\"bg-gray-800 border border-gray-700 rounded px-2 py-1 text-gray-300 min-w-[8rem] flex items-center justify-between gap-2 select-none\"\n              >\n                <div className=\"flex items-center gap-2\">\n                  <span>\n                    {\n                      deviceOptions.find((opt) => opt.value === deviceFilter)\n                        ?.label\n                    }\n                  </span>\n                  {deviceFilter !== \"all\" && (\n                    <span className=\"text-gray-300\">\n                      <PlatformIcon\n                        platform={\n                          allDevices.find((d) => d.deviceId === deviceFilter)\n                            ?.platform || \"\"\n                        }\n                      />\n                    </span>\n                  )}\n                </div>\n                <svg\n                  className=\"w-4 h-4\"\n                  viewBox=\"0 0 24 24\"\n                  fill=\"none\"\n                  stroke=\"currentColor\"\n                  strokeWidth=\"2\"\n                >\n                  <path\n                    strokeLinecap=\"round\"\n                    strokeLinejoin=\"round\"\n                    d=\"M19 9l-7 7-7-7\"\n                  />\n                </svg>\n              </button>\n              {isDeviceDropdownOpen && (\n                <div className=\"absolute top-full left-0 mt-1 w-full bg-gray-800 border border-gray-700 rounded-md shadow-lg z-50\">\n                  {deviceOptions.map((option) => (\n                    <button\n                      key={option.value}\n                      onClick={() => {\n                        setDeviceFilter(option.value);\n                        setIsDeviceDropdownOpen(false);\n                      }}\n                      className={`w-full px-2 py-1.5 text-left flex items-center justify-between gap-2 hover:bg-gray-700/50 ${\n                        deviceFilter === option.value ? \"bg-gray-700/30\" : \"\"\n                      } ${\n                        option.isOffline ? \"text-gray-500\" : \"text-gray-300\"\n                      } select-none`}\n                    >\n                      <div className=\"flex items-center gap-2\">\n                        {option.label}\n                        {option.platform && (\n                          <span\n                            className={`${\n                              option.isOffline\n                                ? \"text-gray-500\"\n                                : \"text-gray-300\"\n                            }`}\n                          >\n                            <PlatformIcon platform={option.platform} />\n                          </span>\n                        )}\n                      </div>\n                      {option.value !== \"all\" && (\n                        <div\n                          className={`w-1.5 h-1.5 rounded-full ${\n                            option.isOffline ? \"bg-red-500\" : \"bg-green-500\"\n                          }`}\n                        />\n                      )}\n                    </button>\n                  ))}\n                </div>\n              )}\n            </div>\n          </div>\n\n          {/* Log Level Filter */}\n          <div className=\"flex items-center gap-2 text-xs\">\n            <span className=\"text-gray-400\">Level:</span>\n            <div className=\"relative\" ref={levelDropdownRef}>\n              <button\n                onClick={() => setIsLevelDropdownOpen(!isLevelDropdownOpen)}\n                className=\"bg-gray-800 border border-gray-700 rounded px-2 py-1 text-gray-300 min-w-[6rem] flex items-center justify-between gap-2 select-none\"\n              >\n                <span>\n                  {levelOptions.find((opt) => opt.value === filter)?.label}\n                </span>\n                <svg\n                  className=\"w-4 h-4\"\n                  viewBox=\"0 0 24 24\"\n                  fill=\"none\"\n                  stroke=\"currentColor\"\n                  strokeWidth=\"2\"\n                >\n                  <path\n                    strokeLinecap=\"round\"\n                    strokeLinejoin=\"round\"\n                    d=\"M19 9l-7 7-7-7\"\n                  />\n                </svg>\n              </button>\n              {isLevelDropdownOpen && (\n                <div className=\"absolute top-full left-0 mt-1 w-full bg-gray-800 border border-gray-700 rounded-md shadow-lg z-50\">\n                  {levelOptions.map((option) => (\n                    <button\n                      key={option.value}\n                      onClick={() => {\n                        setFilter(option.value as LogLevel | \"all\");\n                        setIsLevelDropdownOpen(false);\n                      }}\n                      className={`w-full px-2 py-1.5 text-left hover:bg-gray-700/50 ${\n                        filter === option.value ? \"bg-gray-700/30\" : \"\"\n                      } text-gray-300 select-none`}\n                    >\n                      {option.label}\n                    </button>\n                  ))}\n                </div>\n              )}\n            </div>\n          </div>\n\n          {/* Auto-scroll toggle */}\n          <div className=\"flex items-center gap-2 text-xs\">\n            <span className=\"text-gray-400\">Auto-scroll:</span>\n            <button\n              onClick={() => setAutoScroll(!autoScroll)}\n              className={`w-10 h-5 rounded-full flex items-center transition-colors ${\n                autoScroll ? \"bg-blue-600\" : \"bg-gray-700\"\n              }`}\n            >\n              <span\n                className={`w-4 h-4 rounded-full bg-white shadow-sm transform transition-transform ${\n                  autoScroll ? \"translate-x-5\" : \"translate-x-1\"\n                }`}\n              />\n            </button>\n          </div>\n\n          {/* Clear button */}\n          <button\n            onClick={clearLogs}\n            className=\"text-xs px-3 py-1 bg-red-600/20 text-red-400 rounded hover:bg-red-600/30 transition-colors\"\n          >\n            Clear\n          </button>\n\n          {/* Close button */}\n          <button\n            onClick={onClose}\n            className=\"p-1 hover:bg-gray-800 rounded-md transition-colors duration-200\"\n          >\n            <svg\n              className=\"w-4 h-4 text-gray-400\"\n              viewBox=\"0 0 24 24\"\n              fill=\"none\"\n              stroke=\"currentColor\"\n              strokeWidth=\"2\"\n            >\n              <path\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n                d=\"M19 9l-7 7-7-7\"\n              />\n            </svg>\n          </button>\n        </div>\n      </div>\n\n      {/* Log entries container with dynamic height */}\n      <div\n        ref={scrollRef}\n        style={{ height: `${height}px` }}\n        className={`overflow-y-auto flex flex-col-reverse select-text ${\n          isDragging ? \"pointer-events-none\" : \"\"\n        }`}\n        onKeyDown={(e) => {\n          if ((e.ctrlKey || e.metaKey) && e.key === \"a\") {\n            e.preventDefault();\n            const selection = window.getSelection();\n            const range = document.createRange();\n            range.selectNodeContents(e.currentTarget);\n            selection?.removeAllRanges();\n            selection?.addRange(range);\n          }\n        }}\n        tabIndex={0}\n      >\n        {filteredLogs.length > 0 ? (\n          filteredLogs.map((log: LogEntry) => (\n            <LogEntryItem key={log.id} log={log} />\n          ))\n        ) : (\n          <div className=\"flex items-center justify-center h-full text-gray-500 italic\">\n            No logs to display\n          </div>\n        )}\n      </div>\n    </>\n  );\n};\n"
  },
  {
    "path": "src/components/external-dash/Main.tsx",
    "content": "import { useState } from \"react\";\nimport useConnectedUsers from \"./_hooks/useConnectedUsers\";\nimport { useSyncQueriesWeb } from \"./useSyncQueriesWeb\";\nimport { Dash } from \"./Dash\";\nimport { User } from \"./types/User\";\n\nexport default function Main() {\n  const [targetDevice, setTargetDevice] = useState<User>({\n    deviceId: \"Please select a device\",\n    deviceName: \"Please select a device\",\n    isConnected: false,\n    id: \"Please select a device\",\n  });\n  const { allDevices, isDashboardConnected } = useConnectedUsers();\n  useSyncQueriesWeb({ targetDevice, allDevices });\n\n  return (\n    <Dash\n      allDevices={allDevices}\n      isDashboardConnected={isDashboardConnected}\n      targetDevice={targetDevice}\n      setTargetDevice={setTargetDevice}\n    />\n  );\n}\n"
  },
  {
    "path": "src/components/external-dash/NoDevicesConnected.tsx",
    "content": "import React, { useState } from \"react\";\n\nexport const NoDevicesConnected = () => {\n  const [copiedText, setCopiedText] = useState<string | null>(null);\n\n  const handleCopy = (text: string, label: string) => {\n    navigator.clipboard.writeText(text);\n    setCopiedText(label);\n    setTimeout(() => setCopiedText(null), 2000);\n  };\n\n  return (\n    <>\n      <div className=\"flex flex-col items-center justify-center min-h-[calc(100vh-10rem)] text-center\">\n        <div className=\"bg-[#1A1A1C] rounded-3xl p-8 border border-[#2D2D2F]/50 max-w-md shadow-[0_1rem_3rem_rgba(0,0,0,0.3)] transition-shadow duration-700 ease-out hover:shadow-[0_1rem_3rem_rgba(59,130,246,0.1)] hover:scale-[1.01]\">\n          <svg\n            className=\"w-16 h-16 text-[#9E9EA0] mx-auto mb-6 opacity-80\"\n            viewBox=\"0 0 24 24\"\n            fill=\"none\"\n            stroke=\"currentColor\"\n            strokeWidth=\"1.5\"\n          >\n            <path\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n              d=\"M13.5 16.875h3.375m0 0h3.375m-3.375 0V13.5m0 3.375v3.375M6 10.5h2.25a2.25 2.25 0 002.25-2.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v2.25A2.25 2.25 0 006 10.5zm0 9.75h2.25A2.25 2.25 0 0010.5 18v-2.25a2.25 2.25 0 00-2.25-2.25H6a2.25 2.25 0 00-2.25 2.25V18A2.25 2.25 0 006 20.25zm9.75-9.75H18a2.25 2.25 0 002.25-2.25V6A2.25 2.25 0 0018 3.75h-2.25A2.25 2.25 0 0013.5 6v2.25a2.25 2.25 0 002.25 2.25z\"\n            />\n          </svg>\n          <h3 className=\"text-xl font-medium text-white mb-3\">\n            No Devices Connected\n          </h3>\n          <p className=\"text-gray-100 text-sm leading-relaxed mb-6\">\n            Please ensure this app is running and then start or restart your\n            devices to establish a connection.\n          </p>\n          <div className=\"text-sm text-gray-100 bg-[#26262A] rounded-xl p-5 border border-[#3D3D42] shadow-inner\">\n            <p className=\"font-semibold mb-3 text-white\">\n              Troubleshooting steps:\n            </p>\n            <ol className=\"list-decimal list-inside space-y-3 text-left\">\n              <li className=\"pl-1\">Verify the app is running</li>\n              <li className=\"pl-1\">Restart your development devices</li>\n              <li className=\"flex items-start gap-1.5 flex-wrap pl-1\">\n                <span>Please read the</span>\n                <button\n                  onClick={() =>\n                    handleCopy(\n                      \"https://github.com/LovesWorking/rn-better-dev-tools\",\n                      \"Documentation\"\n                    )\n                  }\n                  className=\"text-blue-300 hover:text-blue-200 hover:underline cursor-pointer inline-flex items-center gap-1 relative group transition-colors duration-300 font-medium\"\n                >\n                  documentation\n                  <svg\n                    className=\"w-3 h-3\"\n                    viewBox=\"0 0 24 24\"\n                    fill=\"none\"\n                    stroke=\"currentColor\"\n                    strokeWidth=\"2\"\n                  >\n                    <path\n                      strokeLinecap=\"round\"\n                      strokeLinejoin=\"round\"\n                      d=\"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z\"\n                    />\n                  </svg>\n                  {copiedText === \"Documentation\" && (\n                    <span className=\"absolute -top-8 left-1/2 transform -translate-x-1/2 px-2 py-1 bg-[#0A0A0C] text-xs text-white rounded-lg shadow-lg border border-[#2D2D2F]/50 animate-fadeIn\">\n                      Copied!\n                    </span>\n                  )}\n                </button>\n                <span>or</span>\n                <button\n                  onClick={() =>\n                    handleCopy(\n                      \"https://github.com/LovesWorking/rn-better-dev-tools/issues\",\n                      \"Issues\"\n                    )\n                  }\n                  className=\"text-blue-300 hover:text-blue-200 hover:underline cursor-pointer inline-flex items-center gap-1 relative group transition-colors duration-300 font-medium\"\n                >\n                  create an issue\n                  <svg\n                    className=\"w-3 h-3\"\n                    viewBox=\"0 0 24 24\"\n                    fill=\"none\"\n                    stroke=\"currentColor\"\n                    strokeWidth=\"2\"\n                  >\n                    <path\n                      strokeLinecap=\"round\"\n                      strokeLinejoin=\"round\"\n                      d=\"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z\"\n                    />\n                  </svg>\n                  {copiedText === \"Issues\" && (\n                    <span className=\"absolute -top-8 left-1/2 transform -translate-x-1/2 px-2 py-1 bg-[#0A0A0C] text-xs text-white rounded-lg shadow-lg border border-[#2D2D2F]/50 animate-fadeIn\">\n                      Copied!\n                    </span>\n                  )}\n                </button>\n                <span>for help</span>\n              </li>\n            </ol>\n          </div>\n        </div>\n      </div>\n    </>\n  );\n};\n"
  },
  {
    "path": "src/components/external-dash/UserInfo/DeviceSpecificationsSection.tsx",
    "content": "import React, { useState } from \"react\";\nimport { InfoRow } from \"./InfoRow\";\n\ninterface Props {\n  extraDeviceInfo: Record<string, any>;\n}\n\nconst formatValue = (value: any, key?: string): React.ReactElement => {\n  // Special handling for NODE_ENV\n  if (key === \"NODE_ENV\" && typeof value === \"string\") {\n    const env = value.toLowerCase();\n    let envStyles = \"\";\n\n    switch (env) {\n      case \"development\":\n      case \"dev\":\n        envStyles =\n          \"bg-green-900/80 text-green-300 shadow-[0_0_8px_rgba(74,222,128,0.1)]\";\n        break;\n      case \"production\":\n      case \"prod\":\n        envStyles =\n          \"bg-blue-900/80 text-blue-300 shadow-[0_0_8px_rgba(59,130,246,0.1)]\";\n        break;\n      case \"staging\":\n      case \"stage\":\n        envStyles =\n          \"bg-yellow-900/80 text-yellow-300 shadow-[0_0_8px_rgba(250,204,21,0.1)]\";\n        break;\n      case \"test\":\n      case \"testing\":\n        envStyles =\n          \"bg-red-900/80 text-red-300 shadow-[0_0_8px_rgba(248,113,113,0.1)]\";\n        break;\n      default:\n        envStyles =\n          \"bg-purple-900/80 text-purple-300 shadow-[0_0_8px_rgba(147,51,234,0.1)]\";\n    }\n\n    return (\n      <span\n        className={`inline-block px-3 py-1 text-xs font-medium rounded-full transition-all duration-500 ease-out break-words max-w-full ${envStyles}`}\n      >\n        {value}\n      </span>\n    );\n  }\n\n  // Check if it's a string boolean first\n  if (\n    typeof value === \"string\" &&\n    (value.toLowerCase() === \"true\" || value.toLowerCase() === \"false\")\n  ) {\n    const boolValue = value.toLowerCase() === \"true\";\n    return (\n      <span\n        className={`inline-block px-3 py-1 text-xs font-medium rounded-full transition-all duration-500 ease-out break-words max-w-full ${\n          boolValue\n            ? \"bg-green-900/80 text-green-300 shadow-[0_0_8px_rgba(74,222,128,0.1)]\"\n            : \"bg-red-900/80 text-red-300 shadow-[0_0_8px_rgba(248,113,113,0.1)]\"\n        }`}\n      >\n        {boolValue ? \"true\" : \"false\"}\n      </span>\n    );\n  }\n\n  if (typeof value === \"boolean\") {\n    return (\n      <span\n        className={`inline-block px-3 py-1 text-xs font-medium rounded-full transition-all duration-500 ease-out break-words max-w-full ${\n          value\n            ? \"bg-green-900/80 text-green-300 shadow-[0_0_8px_rgba(74,222,128,0.1)]\"\n            : \"bg-red-900/80 text-red-300 shadow-[0_0_8px_rgba(248,113,113,0.1)]\"\n        }`}\n      >\n        {value ? \"true\" : \"false\"}\n      </span>\n    );\n  }\n\n  if (typeof value === \"string\") {\n    return (\n      <span className=\"inline-block px-2 py-1 text-xs font-medium rounded-md bg-red-900/20 text-red-300 border border-red-900/30 shadow-inner break-words word-break-all max-w-full\">\n        {value}\n      </span>\n    );\n  }\n\n  if (typeof value === \"number\") {\n    return (\n      <span className=\"inline-block px-2 py-1 text-xs font-medium rounded-md bg-blue-900/20 text-blue-300 border border-blue-900/30 shadow-inner break-words max-w-full\">\n        {value}\n      </span>\n    );\n  }\n\n  if (value === null) {\n    return (\n      <span className=\"inline-block px-2 py-1 text-xs font-medium rounded-md bg-gray-900/20 text-gray-400 border border-gray-900/30 shadow-inner italic break-words max-w-full\">\n        null\n      </span>\n    );\n  }\n\n  if (value === undefined) {\n    return (\n      <span className=\"inline-block px-2 py-1 text-xs font-medium rounded-md bg-gray-900/20 text-gray-400 border border-gray-900/30 shadow-inner italic break-words max-w-full\">\n        undefined\n      </span>\n    );\n  }\n\n  // For objects, arrays, etc.\n  return (\n    <span className=\"inline-block px-2 py-1 text-xs font-medium rounded-md bg-yellow-900/20 text-yellow-300 border border-yellow-900/30 shadow-inner break-words word-break-all max-w-full\">\n      {JSON.stringify(value)}\n    </span>\n  );\n};\n\nexport const DeviceSpecificationsSection: React.FC<Props> = ({\n  extraDeviceInfo,\n}) => {\n  const hasDeviceInfo = Object.keys(extraDeviceInfo).length > 0;\n  const [isExpanded, setIsExpanded] = useState(false);\n\n  return (\n    <>\n      <div className=\"col-span-2 border-t border-[#2D2D2F]/70 my-3\" />\n      <div className=\"col-span-2 mb-2\">\n        <div\n          className=\"flex items-center gap-1.5 text-[#F5F5F7] font-medium cursor-pointer hover:text-white transition-colors duration-200\"\n          onClick={() => setIsExpanded(!isExpanded)}\n        >\n          <svg\n            className={`w-4 h-4 ${\n              hasDeviceInfo ? \"text-blue-400\" : \"text-amber-400\"\n            }`}\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={\n                hasDeviceInfo\n                  ? \"M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2z\"\n                  : \"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n              }\n            />\n          </svg>\n          <span>Custom Properties</span>\n          <svg\n            className={`w-4 h-4 ml-auto transition-transform duration-200 ${\n              isExpanded ? \"rotate-180\" : \"\"\n            }`}\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=\"M19 9l-7 7-7-7\"\n            />\n          </svg>\n        </div>\n      </div>\n\n      {isExpanded && (\n        <>\n          {hasDeviceInfo ? (\n            <>\n              {Object.entries(extraDeviceInfo).map(([key, value]) => (\n                <div\n                  key={key}\n                  className=\"col-span-2 grid grid-cols-2 gap-3 mb-2 min-w-0\"\n                >\n                  <div className=\"text-[#A1A1A6] text-sm break-words overflow-hidden min-w-0\">\n                    <span className=\"font-mono\">{key}</span>\n                  </div>\n                  <div className=\"text-sm break-words overflow-hidden font-mono min-w-0\">\n                    {formatValue(value, key)}\n                  </div>\n                </div>\n              ))}\n            </>\n          ) : (\n            <div className=\"col-span-2 text-xs text-[#A1A1A6]\">\n              <div className=\"mb-2\">\n                No custom properties available. Pass additional info via the{\" \"}\n                <code className=\"px-1.5 py-0.5 bg-[#0A0A0C] rounded text-blue-300\">\n                  extraDeviceInfo\n                </code>{\" \"}\n                prop:\n              </div>\n              <code className=\"text-xs bg-[#0A0A0C]/70 rounded-lg p-3 font-mono text-[#F5F5F7] block w-full\">\n                extraDeviceInfo: {\"{\"}\n                \"Environment\": \"staging\", \"Version\": \"1.2.3\", \"Feature_Flag\":\n                true, ...\n                {\"}\"}\n              </code>\n            </div>\n          )}\n        </>\n      )}\n    </>\n  );\n};\n"
  },
  {
    "path": "src/components/external-dash/UserInfo/EnvironmentVariablesSection.tsx",
    "content": "import React, { useState } from \"react\";\nimport { InfoRow } from \"./InfoRow\";\n\ninterface Props {\n  envVariables: Record<string, any>;\n}\n\nconst formatValue = (value: any, key?: string): React.ReactElement => {\n  // Special handling for NODE_ENV\n  if (key === \"NODE_ENV\" && typeof value === \"string\") {\n    const env = value.toLowerCase();\n    let envStyles = \"\";\n\n    switch (env) {\n      case \"development\":\n      case \"dev\":\n        envStyles =\n          \"bg-green-900/80 text-green-300 shadow-[0_0_8px_rgba(74,222,128,0.1)]\";\n        break;\n      case \"production\":\n      case \"prod\":\n        envStyles =\n          \"bg-blue-900/80 text-blue-300 shadow-[0_0_8px_rgba(59,130,246,0.1)]\";\n        break;\n      case \"staging\":\n      case \"stage\":\n        envStyles =\n          \"bg-yellow-900/80 text-yellow-300 shadow-[0_0_8px_rgba(250,204,21,0.1)]\";\n        break;\n      case \"test\":\n      case \"testing\":\n        envStyles =\n          \"bg-red-900/80 text-red-300 shadow-[0_0_8px_rgba(248,113,113,0.1)]\";\n        break;\n      default:\n        envStyles =\n          \"bg-purple-900/80 text-purple-300 shadow-[0_0_8px_rgba(147,51,234,0.1)]\";\n    }\n\n    return (\n      <span\n        className={`inline-block px-3 py-1 text-xs font-medium rounded-full transition-all duration-500 ease-out break-words max-w-full ${envStyles}`}\n      >\n        {value}\n      </span>\n    );\n  }\n\n  // Check if it's a string boolean first\n  if (\n    typeof value === \"string\" &&\n    (value.toLowerCase() === \"true\" || value.toLowerCase() === \"false\")\n  ) {\n    const boolValue = value.toLowerCase() === \"true\";\n    return (\n      <span\n        className={`inline-block px-3 py-1 text-xs font-medium rounded-full transition-all duration-500 ease-out break-words max-w-full ${\n          boolValue\n            ? \"bg-green-900/80 text-green-300 shadow-[0_0_8px_rgba(74,222,128,0.1)]\"\n            : \"bg-red-900/80 text-red-300 shadow-[0_0_8px_rgba(248,113,113,0.1)]\"\n        }`}\n      >\n        {boolValue ? \"true\" : \"false\"}\n      </span>\n    );\n  }\n\n  if (typeof value === \"boolean\") {\n    return (\n      <span\n        className={`inline-block px-3 py-1 text-xs font-medium rounded-full transition-all duration-500 ease-out break-words max-w-full ${\n          value\n            ? \"bg-green-900/80 text-green-300 shadow-[0_0_8px_rgba(74,222,128,0.1)]\"\n            : \"bg-red-900/80 text-red-300 shadow-[0_0_8px_rgba(248,113,113,0.1)]\"\n        }`}\n      >\n        {value ? \"true\" : \"false\"}\n      </span>\n    );\n  }\n\n  if (typeof value === \"string\") {\n    return (\n      <span className=\"inline-block px-2 py-1 text-xs font-medium rounded-md bg-red-900/20 text-red-300 border border-red-900/30 shadow-inner break-words word-break-all max-w-full\">\n        {value}\n      </span>\n    );\n  }\n\n  if (typeof value === \"number\") {\n    return (\n      <span className=\"inline-block px-2 py-1 text-xs font-medium rounded-md bg-blue-900/20 text-blue-300 border border-blue-900/30 shadow-inner break-words max-w-full\">\n        {value}\n      </span>\n    );\n  }\n\n  if (value === null) {\n    return (\n      <span className=\"inline-block px-2 py-1 text-xs font-medium rounded-md bg-gray-900/20 text-gray-400 border border-gray-900/30 shadow-inner italic break-words max-w-full\">\n        null\n      </span>\n    );\n  }\n\n  if (value === undefined) {\n    return (\n      <span className=\"inline-block px-2 py-1 text-xs font-medium rounded-md bg-gray-900/20 text-gray-400 border border-gray-900/30 shadow-inner italic break-words max-w-full\">\n        undefined\n      </span>\n    );\n  }\n\n  // For objects, arrays, etc.\n  return (\n    <span className=\"inline-block px-2 py-1 text-xs font-medium rounded-md bg-yellow-900/20 text-yellow-300 border border-yellow-900/30 shadow-inner break-words word-break-all max-w-full\">\n      {JSON.stringify(value)}\n    </span>\n  );\n};\n\nexport const EnvironmentVariablesSection: React.FC<Props> = ({\n  envVariables,\n}) => {\n  const hasEnvVariables = Object.keys(envVariables).length > 0;\n  const [isExpanded, setIsExpanded] = useState(false);\n\n  return (\n    <>\n      <div className=\"col-span-2 border-t border-[#2D2D2F]/70 my-3\" />\n      <div className=\"col-span-2 mb-2\">\n        <div\n          className=\"flex items-center gap-1.5 text-[#F5F5F7] font-medium cursor-pointer hover:text-white transition-colors duration-200\"\n          onClick={() => setIsExpanded(!isExpanded)}\n        >\n          <svg\n            className={`w-4 h-4 ${\n              hasEnvVariables ? \"text-green-400\" : \"text-amber-400\"\n            }`}\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=\"M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4\"\n            />\n          </svg>\n          <span>Environment Variables</span>\n          <svg\n            className={`w-4 h-4 ml-auto transition-transform duration-200 ${\n              isExpanded ? \"rotate-180\" : \"\"\n            }`}\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=\"M19 9l-7 7-7-7\"\n            />\n          </svg>\n        </div>\n      </div>\n\n      {isExpanded && (\n        <>\n          {hasEnvVariables ? (\n            <>\n              {Object.entries(envVariables).map(([key, value]) => (\n                <div\n                  key={key}\n                  className=\"col-span-2 grid grid-cols-2 gap-3 mb-2 min-w-0\"\n                >\n                  <div className=\"text-[#A1A1A6] text-sm break-words overflow-hidden min-w-0\">\n                    <span className=\"font-mono\">{key}</span>\n                  </div>\n                  <div className=\"text-sm break-words overflow-hidden font-mono min-w-0\">\n                    {formatValue(value, key)}\n                  </div>\n                </div>\n              ))}\n            </>\n          ) : (\n            <div className=\"col-span-2 text-xs text-[#A1A1A6]\">\n              <div className=\"mb-2\">\n                No environment variables available. Pass ENV variables via the{\" \"}\n                <code className=\"px-1.5 py-0.5 bg-[#0A0A0C] rounded text-green-300\">\n                  envVariables\n                </code>{\" \"}\n                prop:\n              </div>\n              <code className=\"text-xs bg-[#0A0A0C]/70 rounded-lg p-3 font-mono text-[#F5F5F7] block w-full\">\n                envVariables: {\"{\"}\n                \"NODE_ENV\": \"development\", \"API_URL\": \"https://api.example.com\",\n                ...\n                {\"}\"}\n              </code>\n            </div>\n          )}\n        </>\n      )}\n    </>\n  );\n};\n"
  },
  {
    "path": "src/components/external-dash/UserInfo/InfoRow.tsx",
    "content": "import React from \"react\";\nimport { PlatformIcon } from \"../utils/platformUtils\";\n\ninterface Props {\n  label: string;\n  value: string;\n  monospace?: boolean;\n  className?: string;\n  labelClassName?: string;\n}\n\nexport const InfoRow: React.FC<Props> = ({\n  label,\n  value,\n  monospace,\n  className = \"text-[#F5F5F7]\",\n  labelClassName = \"text-[#A1A1A6]\",\n}) => (\n  <>\n    <div className={`${labelClassName} font-medium antialiased`}>{label}:</div>\n    <div\n      className={`${className} ${\n        monospace ? \"font-mono\" : \"\"\n      } overflow-hidden text-ellipsis antialiased flex items-center gap-1.5`}\n    >\n      {label === \"Platform\" && <PlatformIcon platform={value} />}\n      {value}\n    </div>\n  </>\n);\n"
  },
  {
    "path": "src/components/external-dash/UserInfo/StorageControlsSection.tsx",
    "content": "import React, { useCallback, useMemo, useState } from \"react\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { useStorageStore } from \"../utils/storageStore\";\nimport {\n  StorageType,\n  isStorageQuery,\n  getStorageType,\n} from \"../utils/storageQueryKeys\";\n\ninterface StorageControlsSectionProps {\n  deviceId?: string; // Made optional for global usage\n}\n\nexport const StorageControlsSection: React.FC<StorageControlsSectionProps> =\n  React.memo(({ deviceId }) => {\n    const queryClient = useQueryClient();\n    const [isExpanded, setIsExpanded] = useState(false);\n\n    // Get the entire store state to ensure reactivity\n    const { enabledStorageTypes, setStorageTypeEnabled } = useStorageStore();\n\n    const handleStorageToggle = useCallback(\n      (storageType: StorageType) => {\n        const isCurrentlyEnabled = enabledStorageTypes.has(storageType);\n        const newEnabledState = !isCurrentlyEnabled;\n\n        setStorageTypeEnabled(storageType, newEnabledState);\n\n        if (!newEnabledState) {\n          // Use requestAnimationFrame to defer heavy query operations\n          // This prevents blocking the UI during transitions\n          requestAnimationFrame(() => {\n            const queryCache = queryClient.getQueryCache();\n            const allQueries = queryCache.getAll();\n\n            allQueries.forEach((query) => {\n              if (\n                isStorageQuery(query.queryKey) &&\n                getStorageType(query.queryKey) === storageType\n              ) {\n                queryClient.removeQueries({ queryKey: query.queryKey });\n              }\n            });\n          });\n        }\n      },\n      [enabledStorageTypes, setStorageTypeEnabled, queryClient]\n    );\n\n    const storageTypes = useMemo(\n      () => [\n        {\n          type: \"mmkv\" as StorageType,\n          label: \"MMKV\",\n          description: \"High-performance key-value storage\",\n          icon: \"⚡\",\n        },\n        {\n          type: \"async\" as StorageType,\n          label: \"AsyncStorage\",\n          description: \"React Native async storage\",\n          icon: \"📱\",\n        },\n        {\n          type: \"secure\" as StorageType,\n          label: \"SecureStorage\",\n          description: \"Encrypted secure storage\",\n          icon: \"🔒\",\n        },\n      ],\n      []\n    );\n\n    const enabledCount = enabledStorageTypes.size;\n    const totalCount = storageTypes.length;\n\n    // If deviceId is provided, render the old card-style layout for user info\n    if (deviceId) {\n      return (\n        <>\n          <div className=\"col-span-2 border-t border-[#2D2D2F]/70 my-3\" />\n          <div className=\"col-span-2 mb-2\">\n            <div\n              className=\"flex items-center gap-1.5 text-[#F5F5F7] font-medium cursor-pointer hover:text-white transition-colors duration-200\"\n              onClick={() => setIsExpanded(!isExpanded)}\n            >\n              <svg\n                className=\"w-4 h-4 text-blue-400\"\n                viewBox=\"0 0 24 24\"\n                fill=\"currentColor\"\n              >\n                <path d=\"M4 6h16v2H4zm0 5h16v2H4zm0 5h16v2H4z\" />\n              </svg>\n              <span>Storage Query Controls</span>\n              <span className=\"text-xs text-[#86868B] ml-2 font-mono bg-[#2D2D2F]/50 px-2 py-0.5 rounded\">\n                {enabledCount}/{totalCount} enabled\n              </span>\n              <svg\n                className={`w-4 h-4 ml-auto transition-transform duration-200 ${\n                  isExpanded ? \"rotate-180\" : \"\"\n                }`}\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=\"M19 9l-7 7-7-7\"\n                />\n              </svg>\n            </div>\n          </div>\n\n          {isExpanded && (\n            <div className=\"col-span-2\">\n              <div className=\"space-y-2\">\n                {storageTypes.map(({ type, label, description, icon }) => {\n                  const isEnabled = enabledStorageTypes.has(type);\n\n                  return (\n                    <button\n                      key={type}\n                      onClick={() => handleStorageToggle(type)}\n                      className={`w-full p-4 rounded-xl border transition-all duration-300 ease-out ${\n                        isEnabled\n                          ? \"bg-blue-500/10 border-blue-500/30 hover:bg-blue-500/15 hover:border-blue-500/40\"\n                          : \"bg-red-500/10 border-red-500/30 hover:bg-red-500/15 hover:border-red-500/40\"\n                      }`}\n                      aria-pressed={isEnabled}\n                      role=\"switch\"\n                      type=\"button\"\n                      title={isEnabled ? `Disable ${label}` : `Enable ${label}`}\n                    >\n                      <div className=\"flex items-center justify-between\">\n                        <div className=\"flex items-center gap-3\">\n                          <div\n                            className={`flex items-center justify-center w-10 h-10 rounded-xl transition-colors duration-300 ${\n                              isEnabled\n                                ? \"bg-blue-500/20 text-blue-300\"\n                                : \"bg-red-500/20 text-red-300\"\n                            }`}\n                          >\n                            <span className=\"text-xl\">{icon}</span>\n                          </div>\n                          <div className=\"text-left\">\n                            <div\n                              className={`font-medium transition-colors duration-300 ${\n                                isEnabled ? \"text-blue-200\" : \"text-red-200\"\n                              }`}\n                            >\n                              {label}\n                            </div>\n                            <div className=\"text-xs text-[#A1A1A6] leading-relaxed\">\n                              {description}\n                            </div>\n                          </div>\n                        </div>\n\n                        <div className=\"flex items-center\">\n                          <div\n                            className={`w-12 h-6 rounded-full flex items-center px-1 transition-all duration-300 ease-out ${\n                              isEnabled ? \"bg-blue-500\" : \"bg-red-500\"\n                            }`}\n                          >\n                            <div\n                              className={`w-4 h-4 rounded-full bg-white shadow-md transform transition-transform duration-300 ease-out ${\n                                isEnabled ? \"translate-x-6\" : \"translate-x-0\"\n                              }`}\n                            />\n                          </div>\n                        </div>\n                      </div>\n                    </button>\n                  );\n                })}\n              </div>\n\n              <div className=\"mt-4 pt-4 border-t border-[#2D2D2F]/50\">\n                <div className=\"text-xs text-[#86868B] leading-relaxed\">\n                  <div className=\"flex items-start gap-2\">\n                    <svg\n                      className=\"w-3 h-3 mt-0.5 text-blue-400 flex-shrink-0\"\n                      viewBox=\"0 0 24 24\"\n                      fill=\"currentColor\"\n                    >\n                      <path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\" />\n                    </svg>\n                    <span>\n                      Disabling a storage type will clear its queries from React\n                      Query DevTools and filter out new queries. Re-enabling\n                      will show new queries but won't restore previously cleared\n                      ones.\n                    </span>\n                  </div>\n                </div>\n              </div>\n            </div>\n          )}\n        </>\n      );\n    }\n\n    // Global header layout - compact dropdown style\n    return (\n      <div className=\"relative\">\n        <button\n          onClick={() => setIsExpanded(!isExpanded)}\n          className=\"group flex items-center gap-2 px-3 py-1.5 rounded-full transition-all duration-500 ease-out hover:bg-[#1D1D1F]/40 border border-transparent hover:border-[#2D2D2F]/70\"\n        >\n          <svg\n            className=\"w-3 h-3 text-blue-400\"\n            viewBox=\"0 0 24 24\"\n            fill=\"currentColor\"\n          >\n            <path d=\"M4 6h16v2H4zm0 5h16v2H4zm0 5h16v2H4z\" />\n          </svg>\n          <span className=\"text-xs font-medium text-[#A1A1A6] group-hover:text-[#F5F5F7] transition-colors duration-300\">\n            #storage\n          </span>\n          <span className=\"text-xs text-[#86868B] font-mono bg-[#2D2D2F]/50 px-1.5 py-0.5 rounded text-[10px]\">\n            {enabledCount}/{totalCount}\n          </span>\n          <svg\n            className={`w-3 h-3 transition-transform duration-300 ${\n              isExpanded ? \"rotate-180\" : \"\"\n            }`}\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=\"M19 9l-7 7-7-7\"\n            />\n          </svg>\n        </button>\n\n        {isExpanded && (\n          <div className=\"absolute top-full right-0 mt-2 w-80 bg-[#1A1A1C] border border-[#2D2D2F]/60 rounded-xl shadow-[0_0.5rem_1.5rem_rgba(0,0,0,0.25)] z-50\">\n            <div className=\"p-4\">\n              <div className=\"space-y-3\">\n                {storageTypes.map(({ type, label, description, icon }) => {\n                  const isEnabled = enabledStorageTypes.has(type);\n\n                  return (\n                    <button\n                      key={type}\n                      onClick={() => handleStorageToggle(type)}\n                      className={`w-full p-4 rounded-lg border transition-all duration-300 ease-out ${\n                        isEnabled\n                          ? \"bg-blue-500/10 border-blue-500/30 hover:bg-blue-500/15 hover:border-blue-500/40\"\n                          : \"bg-red-500/10 border-red-500/30 hover:bg-red-500/15 hover:border-red-500/40\"\n                      }`}\n                      aria-pressed={isEnabled}\n                      role=\"switch\"\n                      type=\"button\"\n                      title={isEnabled ? `Disable ${label}` : `Enable ${label}`}\n                    >\n                      <div className=\"flex items-center justify-between\">\n                        <div className=\"flex items-center gap-3\">\n                          <div\n                            className={`flex items-center justify-center w-10 h-10 rounded-lg transition-colors duration-300 ${\n                              isEnabled\n                                ? \"bg-blue-500/20 text-blue-300\"\n                                : \"bg-red-500/20 text-red-300\"\n                            }`}\n                          >\n                            <span className=\"text-xl\">{icon}</span>\n                          </div>\n                          <div className=\"text-left\">\n                            <div\n                              className={`text-sm font-medium transition-colors duration-300 ${\n                                isEnabled ? \"text-blue-200\" : \"text-red-200\"\n                              }`}\n                            >\n                              {label}\n                            </div>\n                            <div className=\"text-xs text-[#A1A1A6] leading-relaxed\">\n                              {description}\n                            </div>\n                          </div>\n                        </div>\n\n                        <div className=\"flex items-center\">\n                          <div\n                            className={`w-12 h-6 rounded-full flex items-center px-1 transition-all duration-300 ease-out ${\n                              isEnabled ? \"bg-blue-500\" : \"bg-red-500\"\n                            }`}\n                          >\n                            <div\n                              className={`w-4 h-4 rounded-full bg-white shadow-md transform transition-transform duration-300 ease-out ${\n                                isEnabled ? \"translate-x-6\" : \"translate-x-0\"\n                              }`}\n                            />\n                          </div>\n                        </div>\n                      </div>\n                    </button>\n                  );\n                })}\n              </div>\n\n              <div className=\"mt-4 pt-4 border-t border-[#2D2D2F]/50\">\n                <div className=\"text-xs text-[#86868B] leading-relaxed\">\n                  <div className=\"flex items-start gap-2\">\n                    <svg\n                      className=\"w-3 h-3 mt-0.5 text-blue-400 flex-shrink-0\"\n                      viewBox=\"0 0 24 24\"\n                      fill=\"currentColor\"\n                    >\n                      <path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\" />\n                    </svg>\n                    <span>\n                      Global setting that affects storage queries in React Query\n                      DevTools for all devices.\n                    </span>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        )}\n      </div>\n    );\n  });\n"
  },
  {
    "path": "src/components/external-dash/UserInfo/TargetGlowEffect.tsx",
    "content": "import React from \"react\";\n\ninterface Props {\n  isTargeted: boolean;\n}\n\nexport const TargetGlowEffect: React.FC<Props> = ({ isTargeted }) => {\n  if (!isTargeted) return null;\n\n  return (\n    <>\n      {/* Extended glow effect - furthest back */}\n      <div\n        className=\"absolute -inset-[3px] bg-gradient-to-r from-red-500/10 via-violet-500/10 to-blue-500/10 rounded-2xl blur-xl animate-gradient opacity-70 transition-opacity duration-500 ease-out\"\n        aria-hidden=\"true\"\n      />\n\n      {/* Outer glow effect */}\n      <div\n        className=\"absolute -inset-[2px] bg-gradient-to-r from-red-500/30 via-violet-500/30 to-blue-500/30 rounded-2xl blur-md animate-gradient transition-opacity duration-500 ease-out\"\n        aria-hidden=\"true\"\n      />\n\n      {/* Primary glowing border */}\n      <div\n        className=\"absolute -inset-[1px] bg-gradient-to-r from-red-500/80 via-violet-500/80 to-blue-500/80 rounded-2xl opacity-90 animate-gradient transition-opacity duration-500 ease-out\"\n        aria-hidden=\"true\"\n      />\n    </>\n  );\n};\n"
  },
  {
    "path": "src/components/external-dash/UserInfo/UserCardDetails.tsx",
    "content": "import React from \"react\";\nimport { User } from \"../types/User\";\nimport { InfoRow } from \"./InfoRow\";\nimport { EnvironmentVariablesSection } from \"./EnvironmentVariablesSection\";\nimport { DeviceSpecificationsSection } from \"./DeviceSpecificationsSection\";\n\ninterface Props {\n  userData: User;\n  isTargeted: boolean;\n  envVariables: Record<string, any>;\n  extraDeviceInfo: Record<string, any>;\n}\n\nexport const UserCardDetails: React.FC<Props> = ({\n  userData,\n  isTargeted,\n  envVariables,\n  extraDeviceInfo,\n}) => {\n  const platform = userData.platform || \"Unknown\";\n  const isConnected =\n    userData.isConnected !== undefined ? userData.isConnected : true;\n  const connectionStatusText = isConnected ? \"Connected\" : \"Disconnected\";\n\n  return (\n    <div\n      className=\"grid grid-cols-2 gap-3 text-sm px-5 pb-5 animate-fadeIn border-t border-[#2D2D2F]/70 pt-4 select-text\"\n      onClick={(e) => e.stopPropagation()}\n    >\n      <InfoRow label=\"Socket ID\" value={userData.id} monospace />\n\n      {userData.deviceId && (\n        <InfoRow label=\"Device ID\" value={userData.deviceId} monospace />\n      )}\n\n      <InfoRow label=\"Platform\" value={platform} />\n\n      <InfoRow\n        label=\"Connection Status\"\n        value={connectionStatusText}\n        className={isConnected ? \"text-green-400\" : \"text-red-400\"}\n      />\n\n      <InfoRow\n        label=\"Connection Type\"\n        value={\n          userData.deviceId ? \"Persistent Connection\" : \"Standard Connection\"\n        }\n      />\n\n      {isTargeted && (\n        <InfoRow\n          label=\"Target Status\"\n          value=\"Currently Targeted\"\n          className=\"text-blue-300\"\n          labelClassName=\"text-blue-400\"\n        />\n      )}\n\n      <EnvironmentVariablesSection envVariables={envVariables} />\n\n      <DeviceSpecificationsSection extraDeviceInfo={extraDeviceInfo} />\n\n      <div className=\"col-span-2 text-xs text-[#A1A1A6] mt-3 flex items-center justify-center space-x-2\">\n        <svg\n          className=\"w-4 h-4\"\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=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n          />\n        </svg>\n        <span>Click header to collapse</span>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "src/components/external-dash/UserInfo/UserCardHeader.tsx",
    "content": "import React from \"react\";\nimport { User } from \"../types/User\";\nimport {\n  PlatformIcon,\n  getDisplayPlatform,\n  getPlatformBgColor,\n} from \"../utils/platformUtils\";\n\ninterface Props {\n  userData: User;\n  isTargeted: boolean;\n  expanded: boolean;\n  onToggleExpanded: () => void;\n}\n\nexport const UserCardHeader: React.FC<Props> = ({\n  userData,\n  isTargeted,\n  expanded,\n  onToggleExpanded,\n}) => {\n  const platform = userData.platform || \"Unknown\";\n  const displayPlatform = getDisplayPlatform(platform);\n  const isConnected =\n    userData.isConnected !== undefined ? userData.isConnected : true;\n  const connectionStatusText = isConnected ? \"Connected\" : \"Disconnected\";\n\n  return (\n    <div\n      className=\"flex justify-between items-center cursor-pointer group select-none p-5 w-full\"\n      onClick={(e) => {\n        e.stopPropagation();\n        onToggleExpanded();\n      }}\n    >\n      <div className=\"flex items-center space-x-4\">\n        <div className=\"relative\">\n          <div\n            className={`w-2 h-2 rounded-full transition-colors duration-500 ease-out\n              ${isConnected ? \"bg-green-500\" : \"bg-red-500\"}\n             `}\n          >\n            {isConnected && (\n              <div className=\"absolute -inset-1 rounded-full bg-green-500/30 animate-pulse\"></div>\n            )}\n          </div>\n        </div>\n\n        <h2\n          className={`text-lg font-medium tracking-tight antialiased transition-all duration-500 ease-out\n          ${\n            isTargeted\n              ? \"text-blue-300 drop-shadow-[0_0_12px_rgba(59,130,246,0.3)]\"\n              : \"text-[#F5F5F7]\"\n          }`}\n        >\n          {userData.deviceName}\n        </h2>\n\n        <span\n          className={`px-3 py-1 text-xs font-medium rounded-full flex items-center gap-1.5 transition-all duration-500 ease-out\n          ${getPlatformBgColor(platform)}\n          ${\n            isTargeted\n              ? \"ring-1 ring-blue-400/30 shadow-[0_0_8px_rgba(59,130,246,0.2)]\"\n              : \"\"\n          }`}\n        >\n          <PlatformIcon platform={platform} />\n          {displayPlatform}\n        </span>\n      </div>\n\n      <div className=\"flex items-center space-x-4\">\n        <span\n          className={`px-3 py-1 text-xs font-medium rounded-full transition-all duration-500 ease-out\n            ${\n              userData.deviceId\n                ? isConnected\n                  ? \"bg-green-900/80 text-green-300 shadow-[0_0_8px_rgba(74,222,128,0.1)]\"\n                  : \"bg-red-900/80 text-red-300 shadow-[0_0_8px_rgba(248,113,113,0.1)]\"\n                : \"bg-yellow-900/80 text-yellow-300 shadow-[0_0_8px_rgba(250,204,21,0.1)]\"\n            }\n          `}\n        >\n          {userData.deviceId ? connectionStatusText : \"Legacy\"}\n        </span>\n\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          className={`h-4 w-4 transition-all duration-500 ease-out\n            ${expanded ? \"rotate-180\" : \"\"}\n            ${isTargeted ? \"text-blue-400\" : \"text-[#A1A1A6]\"}\n            group-hover:text-[#F5F5F7]`}\n          fill=\"none\"\n          viewBox=\"0 0 24 24\"\n          stroke=\"currentColor\"\n        >\n          <path\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n            strokeWidth=\"2\"\n            d=\"M19 9l-7 7-7-7\"\n          />\n        </svg>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "src/components/external-dash/UserInfo/index.ts",
    "content": "export { TargetGlowEffect } from \"./TargetGlowEffect\";\nexport { UserCardHeader } from \"./UserCardHeader\";\nexport { UserCardDetails } from \"./UserCardDetails\";\nexport { EnvironmentVariablesSection } from \"./EnvironmentVariablesSection\";\nexport { DeviceSpecificationsSection } from \"./DeviceSpecificationsSection\";\nexport { StorageControlsSection } from \"./StorageControlsSection\";\nexport { InfoRow } from \"./InfoRow\";\n"
  },
  {
    "path": "src/components/external-dash/UserInfo.tsx",
    "content": "import React, { useState } from \"react\";\nimport { User } from \"./types/User\";\nimport {\n  TargetGlowEffect,\n  UserCardHeader,\n  UserCardDetails,\n} from \"./UserInfo/index\";\n\ninterface Props {\n  userData: User;\n  isTargeted?: boolean;\n}\n\nexport const UserInfo: React.FC<Props> = ({ userData, isTargeted = false }) => {\n  const [expanded, setExpanded] = useState(false);\n\n  // Parse extraDeviceInfo if it exists and is not null/undefined\n  const extraDeviceInfo = (() => {\n    try {\n      return userData.extraDeviceInfo &&\n        userData.extraDeviceInfo !== \"undefined\" &&\n        userData.extraDeviceInfo.trim() !== \"\"\n        ? JSON.parse(userData.extraDeviceInfo)\n        : {};\n    } catch (error) {\n      console.warn(\"Failed to parse extraDeviceInfo:\", error);\n      return {};\n    }\n  })();\n\n  // Parse envVariables if it exists and is not null/undefined\n  const envVariables = (() => {\n    try {\n      return userData.envVariables &&\n        userData.envVariables !== \"undefined\" &&\n        userData.envVariables.trim() !== \"\"\n        ? JSON.parse(userData.envVariables)\n        : {};\n    } catch (error) {\n      console.warn(\"Failed to parse envVariables:\", error);\n      return {};\n    }\n  })();\n\n  return (\n    <div className=\"relative isolate w-full\">\n      <TargetGlowEffect isTargeted={isTargeted} />\n\n      <div\n        className={`relative bg-[#1A1A1C] transition-all duration-500 ease-out\n          ${\n            expanded\n              ? \"scale-[1.01] shadow-[0_0.75rem_2.5rem_rgba(0,0,0,0.25)]\"\n              : \"scale-100 cursor-pointer shadow-[0_0.5rem_1.5rem_rgba(0,0,0,0.15)]\"\n          }\n          border border-[#2D2D2F]/70 ${\n            isTargeted ? \"border-opacity-0\" : \"border-opacity-70\"\n          }\n          rounded-2xl hover:shadow-[0_1rem_3rem_rgba(0,0,0,0.3)]`}\n      >\n        <UserCardHeader\n          userData={userData}\n          isTargeted={isTargeted}\n          expanded={expanded}\n          onToggleExpanded={() => setExpanded(!expanded)}\n        />\n\n        {expanded && (\n          <UserCardDetails\n            userData={userData}\n            isTargeted={isTargeted}\n            envVariables={envVariables}\n            extraDeviceInfo={extraDeviceInfo}\n          />\n        )}\n      </div>\n\n      <div\n        className={`absolute inset-0 -z-10 bg-gradient-to-b from-[#1A1A1C]/30 to-transparent rounded-2xl transition-opacity duration-500 ease-out\n          ${expanded ? \"opacity-100\" : \"opacity-0\"}`}\n        aria-hidden=\"true\"\n      />\n    </div>\n  );\n};\n"
  },
  {
    "path": "src/components/external-dash/_hooks/useConnectedUsers.ts",
    "content": "import io, { Socket } from \"socket.io-client\";\nimport { useEffect, useState } from \"react\";\nimport { User } from \"../types/User\";\n\nlet socket = null as Socket | null; // Module-level variable to store the socket instance\nexport default function useConnectedUsers() {\n  const socketURL = \"http://localhost:42831\";\n  const [isDashboardConnected, setIsDashboardConnected] = useState(\n    !!socket?.connected\n  );\n  const [allDevices, setAllDevices] = useState<User[]>([]);\n\n  // Ensure we're properly identifying as a dashboard client with correct query params\n  if (!socket) {\n    // Include the \"Dashboard\" identifier explicitly in query params\n    const enhancedQuery = {\n      deviceName: \"Dashboard\",\n    };\n\n    console.log(\"[DASHBOARD] Initializing socket with query:\", enhancedQuery);\n\n    // Initialize the socket only if it hasn't been already initialized\n    socket = io(socketURL, {\n      autoConnect: false, // Initially prevent automatic connection\n      query: enhancedQuery,\n      reconnection: true,\n      reconnectionAttempts: 10,\n      reconnectionDelay: 1000,\n    });\n  }\n\n  function connect() {\n    if (!socket.connected) {\n      console.log(\"[DASHBOARD] Connecting socket...\");\n      socket?.connect();\n    } else {\n      console.log(\"[DASHBOARD] Socket already connected:\", socket.id);\n    }\n  }\n\n  function disconnect() {\n    console.log(\"[DASHBOARD] Disconnecting socket...\");\n    socket?.disconnect();\n  }\n\n  useEffect(() => {\n    function onConnect() {\n      console.log(\"[DASHBOARD] Socket connected with ID:\", socket?.id);\n      setIsDashboardConnected(true);\n    }\n\n    function onDisconnect() {\n      console.log(\"[DASHBOARD] Socket disconnected\");\n      setIsDashboardConnected(false);\n    }\n\n    function onConnectError(error: Error) {\n      console.error(\"[DASHBOARD] Connection error:\", error.message);\n    }\n\n    // Make sure we're connected\n    !socket.connected && connect();\n\n    // Listen for all devices updates (including offline devices)\n    socket.on(\"all-devices-update\", (devices: User[]) => {\n      console.log(\n        \"[DASHBOARD] Received all-devices-update:\",\n        devices.length,\n        \"devices\"\n      );\n      setAllDevices(devices);\n    });\n\n    // Add listeners for connection events\n    socket?.on(\"connect\", onConnect);\n    socket?.on(\"disconnect\", onDisconnect);\n    socket?.on(\"connect_error\", onConnectError);\n\n    // If already connected, log the ID\n    if (socket.connected) {\n      console.log(\n        \"[DASHBOARD] Socket already connected on mount with ID:\",\n        socket.id\n      );\n    }\n\n    return () => {\n      socket.off(\"all-devices-update\");\n      socket.off(\"connect\");\n      socket.off(\"disconnect\");\n      socket.off(\"connect_error\");\n      // Don't disconnect on cleanup - this would break the persistent connection\n    };\n  }, []);\n\n  return {\n    socket,\n    connect,\n    disconnect,\n    isDashboardConnected,\n    allDevices,\n  };\n}\n"
  },
  {
    "path": "src/components/external-dash/hooks/useDevToolsEventHandler.ts",
    "content": "import { useEffect, useRef } from \"react\";\nimport { onDevToolsEvent } from \"../utils/devToolsEvents\";\nimport type { DevToolsActionType } from \"../utils/devToolsEvents\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { Socket } from \"socket.io-client\";\nimport type { User } from \"../types/User\";\nimport type { QueryKey } from \"@tanstack/react-query\";\nimport { logger } from \"../utils/logger\";\n\ninterface UseDevToolsEventHandlerProps {\n  isConnected?: boolean;\n  socket?: Socket;\n  selectedDevice?: User;\n  sendQueryAction?: (\n    socket: Socket,\n    targetDevice: User,\n    action: string,\n    query: {\n      queryHash: string;\n      queryKey: QueryKey;\n      state: { data: unknown };\n    }\n  ) => void;\n}\n\nconst actionTypeToQueryAction: Record<DevToolsActionType, string> = {\n  REFETCH: \"ACTION-REFETCH\",\n  INVALIDATE: \"ACTION-INVALIDATE\",\n  RESET: \"ACTION-RESET\",\n  REMOVE: \"ACTION-REMOVE\",\n  TRIGGER_ERROR: \"ACTION-TRIGGER-ERROR\",\n  RESTORE_ERROR: \"ACTION-RESTORE-ERROR\",\n  TRIGGER_LOADING: \"ACTION-TRIGGER-LOADING\",\n  RESTORE_LOADING: \"ACTION-RESTORE-LOADING\",\n  CLEAR_MUTATION_CACHE: \"ACTION-CLEAR-MUTATION-CACHE\",\n  CLEAR_QUERY_CACHE: \"ACTION-CLEAR-QUERY-CACHE\",\n};\n\nexport const useDevToolsEventHandler = ({\n  isConnected,\n  socket,\n  selectedDevice,\n  sendQueryAction,\n}: UseDevToolsEventHandlerProps = {}) => {\n  const queryClient = useQueryClient();\n  const isReadyRef = useRef(false);\n\n  // Track when all dependencies are ready\n  useEffect(() => {\n    isReadyRef.current = !!(\n      isConnected &&\n      socket &&\n      selectedDevice &&\n      sendQueryAction\n    );\n    logger.debug(\"🔍 DevToolsEventHandler Ready State:\", {\n      deviceId: selectedDevice?.deviceId,\n      deviceName: selectedDevice?.deviceName,\n      platform: selectedDevice?.platform,\n    });\n  }, [isConnected, socket, selectedDevice, sendQueryAction]);\n\n  useEffect(() => {\n    // Only set up the event handler if all dependencies are available\n    if (!isReadyRef.current) {\n      logger.debug(\n        \"🔄 Waiting for dependencies to be ready before setting up event handler\",\n        {\n          deviceId: selectedDevice?.deviceId,\n          deviceName: selectedDevice?.deviceName,\n          platform: selectedDevice?.platform,\n        }\n      );\n      return;\n    }\n\n    logger.debug(\n      \"🔄 Setting up dev tools event handler - all dependencies ready\",\n      {\n        deviceId: selectedDevice?.deviceId,\n        deviceName: selectedDevice?.deviceName,\n        platform: selectedDevice?.platform,\n      }\n    );\n\n    const cleanup = onDevToolsEvent(function (type, queryHash, metadata) {\n      const actionName = type.toLowerCase().replace(/_/g, \" \");\n      logger.info(`🎯 Dev Tools Action Handled: ${actionName}`, {\n        deviceId: selectedDevice?.deviceId,\n        deviceName: selectedDevice?.deviceName,\n        platform: selectedDevice?.platform,\n      });\n\n      // Convert DevTools action type to Query action type\n      const queryActionType = actionTypeToQueryAction[type];\n      if (!queryActionType) {\n        logger.warn(\"⚠️ Unknown action type: \" + type, {\n          deviceId: selectedDevice?.deviceId,\n          deviceName: selectedDevice?.deviceName,\n          platform: selectedDevice?.platform,\n        });\n        return;\n      }\n\n      // Get the query from cache if we have a hash\n      const query = queryHash\n        ? queryClient.getQueryCache().get(queryHash)\n        : undefined;\n      if (\n        !query &&\n        type !== \"CLEAR_QUERY_CACHE\" &&\n        type !== \"CLEAR_MUTATION_CACHE\"\n      ) {\n        logger.warn(\"⚠️ Query not found in cache: \" + queryHash, {\n          deviceId: selectedDevice?.deviceId,\n          deviceName: selectedDevice?.deviceName,\n          platform: selectedDevice?.platform,\n        });\n        return;\n      }\n\n      logger.info(\n        `📤 Forwarding ${queryActionType} to device: ${\n          selectedDevice.deviceName ?? \"Unknown Device\"\n        }`,\n        {\n          deviceId: selectedDevice?.deviceId,\n          deviceName: selectedDevice?.deviceName,\n          platform: selectedDevice?.platform,\n        }\n      );\n\n      // Special handling for cache clear actions which don't require a query\n      if (type === \"CLEAR_QUERY_CACHE\" || type === \"CLEAR_MUTATION_CACHE\") {\n        sendQueryAction(socket, selectedDevice, queryActionType, {\n          queryHash: \"\",\n          queryKey: undefined,\n          state: { data: undefined },\n        });\n        return;\n      }\n\n      // For all other actions, ensure we have a valid query\n      if (!query || !queryHash) {\n        logger.warn(\"⚠️ Skipping action due to missing query or queryHash\", {\n          deviceId: selectedDevice?.deviceId,\n          deviceName: selectedDevice?.deviceName,\n          platform: selectedDevice?.platform,\n        });\n        return;\n      }\n\n      sendQueryAction(socket, selectedDevice, queryActionType, {\n        queryHash: query.queryHash,\n        queryKey: query.queryKey,\n        state: { data: query.state.data },\n      });\n    });\n\n    return () => {\n      logger.debug(\"🧹 Cleaning up dev tools event handler\", {\n        deviceId: selectedDevice?.deviceId,\n        deviceName: selectedDevice?.deviceName,\n        platform: selectedDevice?.platform,\n      });\n      cleanup();\n    };\n  }, [isConnected, socket, selectedDevice, sendQueryAction, queryClient]);\n};\n"
  },
  {
    "path": "src/components/external-dash/providers.tsx",
    "content": "// We can not useState or useRef in a server component, which is why we are\n// extracting this part out into it's own file with 'use client' on top\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport { useState } from \"react\";\ninterface Props {\n  children: React.ReactNode;\n}\n\nexport default function Providers({ children }: Props) {\n  const [queryClient] = useState(\n    () =>\n      new QueryClient({\n        defaultOptions: {\n          queries: {\n            queryFn: async ({ queryKey }) => {\n              console.log(\"queryFn\", queryKey);\n              // Prevent refetch from throwing an error\n              return Promise.resolve(null);\n            },\n          },\n        },\n      })\n  );\n\n  return (\n    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>\n  );\n}\n"
  },
  {
    "path": "src/components/external-dash/shared/hydration.ts",
    "content": "import type {\n  DefaultError,\n  MutationOptions,\n  QueryOptions,\n  Query,\n  QueryClient,\n  Mutation,\n} from \"@tanstack/react-query\";\nimport { QueryObserver } from \"@tanstack/react-query\";\n\nimport { DehydratedState, ObserverState } from \"./types\";\nimport { shouldFilterStorageQuery } from \"../utils/storageQueryKeys\";\nimport { useStorageStore } from \"../utils/storageStore\";\n\ntype TransformerFn = (data: any) => any;\nfunction defaultTransformerFn(data: any): any {\n  return data;\n}\n\nconst mockQueryFn = () => {\n  return Promise.resolve(null);\n};\n\nexport function Hydrate(\n  client: QueryClient,\n  dehydratedState: DehydratedState,\n  options?: HydrateOptions\n): void {\n  if (typeof dehydratedState !== \"object\" || dehydratedState === null) {\n    console.log(\"dehydratedState is not an object or null\");\n    return;\n  }\n  const queryCache = client.getQueryCache();\n  const mutationCache = client.getMutationCache();\n  const deserializeData =\n    options?.defaultOptions?.deserializeData ?? defaultTransformerFn;\n\n  const dehydratedMutations = dehydratedState.mutations || [];\n  const dehydratedQueries = dehydratedState.queries || [];\n\n  // Get current storage preferences\n  const enabledStorageTypes = useStorageStore.getState().enabledStorageTypes;\n\n  // Sync mutations\n  dehydratedMutations.forEach(({ state, ...mutationOptions }) => {\n    const existingMutation = mutationCache.getAll().find(\n      // @ts-expect-error -  mutation.options.mutationId does exist we add it in the dehydrated state\n      (mutation) => mutation.options.mutationId === mutationOptions.mutationId\n    );\n\n    if (existingMutation) {\n      existingMutation.state = state;\n    } else {\n      mutationCache.build(\n        client,\n        {\n          ...client.getDefaultOptions().hydrate?.mutations,\n          ...options?.defaultOptions?.mutations,\n          ...mutationOptions,\n        },\n        state\n      );\n    }\n  });\n\n  // Hydrate queries - filter out disabled storage queries\n  dehydratedQueries.forEach(\n    ({ queryKey, state, queryHash, meta, promise, observers, gcTime }) => {\n      // Filter out storage queries that are disabled\n      if (shouldFilterStorageQuery(queryKey, enabledStorageTypes)) {\n        console.log(`Filtering out disabled storage query:`, queryKey);\n        return;\n      }\n\n      let query = queryCache.get(queryHash);\n      const data =\n        state.data === undefined ? state.data : deserializeData(state.data);\n      // Do not hydrate if an existing query exists with newer data\n      if (query) {\n        if (\n          query.state.dataUpdatedAt < state.dataUpdatedAt ||\n          query.state.fetchStatus !== state.fetchStatus\n        ) {\n          query.setState({\n            ...state,\n            data,\n          });\n          query.setOptions({\n            ...query.options,\n            queryFn: mockQueryFn,\n            retry: 0,\n            gcTime: gcTime ?? 0,\n          });\n        }\n      } else {\n        // Restore query\n        query = queryCache.build(\n          client,\n          {\n            ...client.getDefaultOptions().hydrate?.queries,\n            ...options?.defaultOptions?.queries,\n            queryKey,\n            queryHash,\n            meta,\n            queryFn: mockQueryFn,\n            gcTime: gcTime ?? 0,\n          },\n          {\n            ...state,\n            data,\n          }\n        );\n      }\n      cleanUpObservers(query);\n      recreateObserver(client, observers, query);\n\n      if (promise) {\n        // Note: `Promise.resolve` required cause\n        // RSC transformed promises are not thenable\n        const initialPromise = Promise.resolve(promise).then(deserializeData);\n\n        // this doesn't actually fetch - it just creates a retryer\n        // which will re-use the passed `initialPromise`\n        void query.fetch(undefined, { initialPromise });\n      }\n    }\n  );\n  // @ts-expect-error - Refresh mutation state\n  mutationCache.notify({ type: \"observerResultsUpdated\" });\n}\n// Clean up existing observers\nfunction cleanUpObservers(query: Query) {\n  const observers = query.observers;\n  observers.forEach((observer) => {\n    query.removeObserver(observer);\n  });\n}\n\nfunction recreateObserver(\n  queryClient: QueryClient,\n  observers: ObserverState[],\n  query: Query\n) {\n  observers.forEach((observerState) => {\n    // Create a new options object without the unwanted properties\n    const cleanedOptions = { ...observerState.options };\n    // @ts-expect-error - This prevents infinite queries from being refetched in dev tools\n    delete cleanedOptions?.initialPageParam;\n    delete cleanedOptions?.behavior;\n    // Replace the queryFn with a mock function to prevent errors when restoring error\n    cleanedOptions.queryFn = mockQueryFn;\n    const observer = new QueryObserver(queryClient, cleanedOptions);\n    query.addObserver(observer);\n  });\n}\n\nexport interface DehydrateOptions {\n  serializeData?: TransformerFn;\n  shouldDehydrateMutation?: (mutation: Mutation) => boolean;\n  shouldDehydrateQuery?: (query: Query) => boolean;\n  shouldRedactErrors?: (error: unknown) => boolean;\n}\n\nexport interface HydrateOptions {\n  defaultOptions?: {\n    deserializeData?: TransformerFn;\n    queries?: QueryOptions;\n    mutations?: MutationOptions<unknown, DefaultError, unknown, unknown>;\n  };\n}\n"
  },
  {
    "path": "src/components/external-dash/shared/types.ts",
    "content": "import {\n  DefaultError,\n  MutationMeta,\n  MutationScope,\n  MutationState,\n  QueryKey,\n  QueryMeta,\n  QueryObserverOptions,\n  QueryState,\n  MutationKey,\n} from \"@tanstack/react-query\";\n\n// Define a simplified version of DehydratedState that both versions can work with\nexport interface SimpleDehydratedState {\n  mutations: unknown[];\n  queries: unknown[];\n}\n\nexport interface SyncMessage {\n  type: \"dehydrated-state\";\n  state: DehydratedState;\n  isOnlineManagerOnline: boolean;\n  persistentDeviceId: string;\n}\n\nexport interface DehydratedState {\n  mutations: DehydratedMutation[];\n  queries: DehydratedQuery[];\n}\n\nexport interface DehydratedMutation {\n  mutationId: number;\n  mutationKey?: MutationKey;\n  state: MutationState;\n  meta?: MutationMeta;\n  scope?: MutationScope;\n}\nexport interface DehydratedQuery {\n  queryHash: string;\n  queryKey: QueryKey;\n  state: QueryState;\n  promise?: Promise<unknown>;\n  meta?: QueryMeta;\n  observers: ObserverState[];\n  gcTime?: number;\n}\nexport interface ObserverState<\n  TQueryFnData = unknown,\n  TError = DefaultError,\n  TData = TQueryFnData,\n  TQueryData = TQueryFnData,\n  TQueryKey extends QueryKey = QueryKey\n> {\n  queryHash: string;\n  options: QueryObserverOptions<\n    TQueryFnData,\n    TError,\n    TData,\n    TQueryData,\n    TQueryKey\n  >;\n}\n"
  },
  {
    "path": "src/components/external-dash/types/ClientQuery.ts",
    "content": "export interface ClientQuery {\n  deviceName: string;\n}\n"
  },
  {
    "path": "src/components/external-dash/types/User.ts",
    "content": "export interface User {\n  id: string;\n  deviceName: string;\n  deviceId: string; // Persisted device ID\n  platform?: string; // Device platform (iOS, Android, Web)\n  isConnected?: boolean; // Whether the device is currently connected\n  extraDeviceInfo?: string; // json string of additional device information as key-value pairs\n  envVariables?: string; // json string of environment variables from the mobile app\n}\n"
  },
  {
    "path": "src/components/external-dash/useSyncQueriesWeb.ts",
    "content": "import {\n  onlineManager,\n  QueryClient,\n  QueryKey,\n  useQueryClient,\n} from \"@tanstack/react-query\";\nimport { useEffect, useRef, useCallback } from \"react\";\nimport { Hydrate } from \"./shared/hydration\";\nimport { SyncMessage } from \"./shared/types\";\nimport useConnectedUsers from \"./_hooks/useConnectedUsers\";\nimport { User } from \"./types/User\";\nimport { Socket } from \"socket.io-client\";\nimport { logger, createDeviceLogger } from \"./utils/logger\";\nimport { useDevToolsEventHandler } from \"./hooks/useDevToolsEventHandler\";\n\n/**\n * Query actions that can be performed on a query.\n * These actions are used to control query state on remote devices.\n */\ntype QueryActions =\n  // Regular query actions\n  | \"ACTION-REFETCH\" // Refetch a query without invalidating it\n  | \"ACTION-INVALIDATE\" // Invalidate a query and trigger a refetch\n  | \"ACTION-RESET\" // Reset a query to its initial state\n  | \"ACTION-REMOVE\" // Remove a query from the cache\n  | \"ACTION-DATA-UPDATE\" // Update a query's data manually\n  // Error handling actions\n  | \"ACTION-TRIGGER-ERROR\" // Manually trigger an error state\n  | \"ACTION-RESTORE-ERROR\" // Restore from an error state\n  // Loading state actions\n  | \"ACTION-TRIGGER-LOADING\" // Manually trigger a loading state\n  | \"ACTION-RESTORE-LOADING\" // Restore from a loading state\n  | \"success\" // Internal success action\n  | \"ACTION-CLEAR-MUTATION-CACHE\" // Clear the mutation cache\n  | \"ACTION-CLEAR-QUERY-CACHE\"; // Clear the query cache\n\n/**\n * Message structure for query actions sent from dashboard to devices\n */\nexport interface QueryActionMessage {\n  queryHash: string; // Unique hash of the query\n  queryKey: QueryKey; // Key array used to identify the query\n  data: unknown; // Data payload (if applicable)\n  action: QueryActions; // Action to perform\n  deviceId: string; // Device ID to target\n}\n\n/**\n * Message structure for requesting initial state from devices\n */\nexport interface QueryRequestInitialStateMessage {\n  targetDeviceId: string; // Device ID to request state from ('All' || device)\n}\n\n/**\n * Message structure for online manager actions sent from dashboard to devices\n */\nexport interface OnlineManagerMessage {\n  action: \"ACTION-ONLINE-MANAGER-ONLINE\" | \"ACTION-ONLINE-MANAGER-OFFLINE\";\n  targetDeviceId: string; // Device ID to target ('All' || device)\n}\n\n// --- Constants ---\nconst LOG_PREFIX = \"[DASHBOARD]\";\nconst INVALID_DEVICE_IDS = [\"No devices available\", \"Please select a device\"];\n\n// Logger metadata types\ninterface BaseLogMetadata extends Partial<User> {\n  queriesCount?: number;\n  mutationsCount?: number;\n  status?: \"ONLINE\" | \"OFFLINE\";\n  connectionStatus?: \"CONNECTED\" | \"DISCONNECTED\";\n  socketExists?: boolean;\n  action?: QueryActions;\n  queryHash?: string;\n  queryKey?: QueryKey;\n  validationError?: string;\n  previousDevice?: {\n    deviceName: string;\n    deviceId: string;\n  };\n  newDevice?: {\n    deviceName: string;\n    deviceId: string;\n    platform?: string;\n  };\n  // Additional fields for better debugging\n  currentSelectedDeviceId?: string;\n  currentSelectedDeviceName?: string;\n  eventType?: string;\n}\n\n// --- Helper Functions ---\n\n/** Checks if the provided device is valid for communication. */\nconst isValidDevice = (device: User | null): boolean => {\n  return !!device && !INVALID_DEVICE_IDS.includes(device.deviceId);\n};\n\n/** Hydrates the query client with state received from a device */\nconst hydrateState = (\n  queryClient: QueryClient,\n  message: SyncMessage,\n  deviceName: string\n) => {\n  const metadata: BaseLogMetadata = {\n    deviceName,\n    deviceId: message.persistentDeviceId,\n    queriesCount: message.state.queries.length,\n    mutationsCount: message.state.mutations.length,\n  };\n  logger.info(\n    `${LOG_PREFIX} Hydrating state from device ${deviceName} `,\n    metadata\n  );\n\n  Hydrate(queryClient, message.state, {\n    defaultOptions: {\n      queries: {\n        staleTime: Infinity,\n        // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      } as any,\n    },\n  });\n};\n\n/** Sends a request for initial state to the target device. */\nconst requestInitialState = (socket: Socket, targetDevice: User) => {\n  const deviceLogger = createDeviceLogger(targetDevice);\n  deviceLogger.info(\n    `${LOG_PREFIX} Requesting initial state from device: ${targetDevice.deviceName} (${targetDevice.deviceId})`\n  );\n\n  const message: QueryRequestInitialStateMessage = {\n    targetDeviceId: targetDevice.deviceId,\n  };\n  socket.emit(\"request-initial-state\", message);\n};\n\n/** Sends a query action message to the target device. */\nconst sendQueryAction = (\n  socket: Socket,\n  targetDevice: User,\n  action: QueryActions,\n  query: { queryHash: string; queryKey: QueryKey; state: { data: unknown } }\n) => {\n  const deviceLogger = createDeviceLogger(targetDevice);\n  // Note: Since deviceLogger already has device context, we don't pass metadata as second param\n  deviceLogger.info(\n    `${LOG_PREFIX} Forwarding query action '${action}' to device: ${targetDevice.deviceName}`\n  );\n\n  const message: QueryActionMessage = {\n    action,\n    deviceId: targetDevice.deviceId,\n    queryHash: query.queryHash,\n    queryKey: query.queryKey,\n    data: query.state.data,\n  };\n  socket.emit(\"query-action\", message);\n};\n\n/** Sends an online status update message to the target device. */\nconst sendOnlineStatus = (\n  socket: Socket,\n  targetDevice: User,\n  isOnline: boolean\n) => {\n  const deviceLogger = createDeviceLogger(targetDevice);\n  const statusText = isOnline ? \"ONLINE\" : \"OFFLINE\";\n\n  deviceLogger.info(\n    `${LOG_PREFIX} Sending ${statusText} status update to device: ${targetDevice.deviceName}`\n  );\n\n  const message: OnlineManagerMessage = {\n    action: isOnline\n      ? \"ACTION-ONLINE-MANAGER-ONLINE\"\n      : \"ACTION-ONLINE-MANAGER-OFFLINE\",\n    targetDeviceId: targetDevice.deviceId,\n  };\n  socket.emit(\"online-manager\", message);\n\n  deviceLogger.debug(\n    `${LOG_PREFIX} Online status message sent successfully to ${targetDevice.deviceName}`\n  );\n};\n\ninterface Props {\n  targetDevice: User; // Currently selected device persistent ID\n  allDevices: User[];\n}\n/**\n * Hook used by the dashboard to sync with and control device queries\n *\n * Handles:\n * - Requesting initial query state from devices\n * - Forwarding query actions to devices\n * - Processing query state updates from devices\n * - Tracking connected devices\n */\nexport function useSyncQueriesWeb({ targetDevice, allDevices }: Props) {\n  const { socket } = useConnectedUsers();\n  const isConnected = !!socket && socket.connected;\n\n  const queryClient = useQueryClient();\n  const selectedDeviceRef = useRef(targetDevice);\n\n  // --- Callbacks ---\n\n  // Callback to handle incoming query sync messages\n  const handleQuerySync = useCallback(\n    (message: SyncMessage) => {\n      // Function that returns device from all devices based off deviceId\n      function getDeviceFromDeviceId(deviceId: string) {\n        return allDevices.find((device) => device.deviceId === deviceId);\n      }\n\n      const device = getDeviceFromDeviceId(message.persistentDeviceId);\n      const deviceName = device?.deviceName || \"Unknown Device\";\n\n      const metadata: BaseLogMetadata = {\n        deviceName,\n        deviceId: message.persistentDeviceId,\n        platform: device?.platform,\n        queriesCount: message.state.queries.length,\n        mutationsCount: message.state.mutations.length,\n      };\n      logger.info(\n        `${LOG_PREFIX} Received query sync from device: ${deviceName} `,\n        metadata\n      );\n\n      if (message.type !== \"dehydrated-state\") {\n        logger.warn(\n          `${LOG_PREFIX} Received unexpected message type: ${message.type} from ${deviceName}`,\n          metadata\n        );\n        return;\n      }\n\n      const currentSelectedDevice = selectedDeviceRef.current;\n      const isFromSelectedDevice =\n        currentSelectedDevice.deviceId === \"All\" ||\n        message.persistentDeviceId === currentSelectedDevice.deviceId;\n\n      if (isFromSelectedDevice) {\n        if (message.isOnlineManagerOnline !== onlineManager.isOnline()) {\n          const onlineMetadata: BaseLogMetadata = {\n            status: message.isOnlineManagerOnline ? \"ONLINE\" : \"OFFLINE\",\n            deviceName,\n            deviceId: message.persistentDeviceId,\n            platform: device?.platform,\n          };\n          const statusText = message.isOnlineManagerOnline\n            ? \"ONLINE\"\n            : \"OFFLINE\";\n          logger.info(\n            `${LOG_PREFIX} Syncing online status with device ${deviceName}: Setting dashboard to ${statusText}`,\n            onlineMetadata\n          );\n          onlineManager.setOnline(message.isOnlineManagerOnline);\n        }\n\n        hydrateState(queryClient, message, deviceName);\n      } else {\n        const metadata: BaseLogMetadata = {\n          deviceId: message.persistentDeviceId,\n          deviceName,\n          platform: device?.platform,\n          currentSelectedDeviceId: currentSelectedDevice.deviceId,\n          currentSelectedDeviceName: currentSelectedDevice.deviceName,\n        };\n        logger.debug(\n          `${LOG_PREFIX} Ignoring sync from non-selected device: ${deviceName} (selected is ${currentSelectedDevice.deviceName})`,\n          metadata\n        );\n      }\n    },\n    [queryClient, allDevices]\n  );\n\n  // --- Effects ---\n\n  // Effect to handle device selection changes\n  useEffect(() => {\n    const previousDevice = selectedDeviceRef.current;\n    selectedDeviceRef.current = targetDevice; // Update the ref immediately\n\n    if (!isConnected || !socket) {\n      const metadata: BaseLogMetadata = {\n        status: isConnected ? \"ONLINE\" : \"OFFLINE\",\n        deviceName: targetDevice.deviceName,\n        deviceId: targetDevice.deviceId,\n        platform: targetDevice.platform,\n        connectionStatus: isConnected ? \"CONNECTED\" : \"DISCONNECTED\",\n        socketExists: !!socket,\n      };\n      logger.warn(\n        `${LOG_PREFIX} Device selection changed to ${targetDevice.deviceName} but connection unavailable`,\n        metadata\n      );\n      return;\n    }\n\n    // Only log/clear/request if the device ID actually changed\n    if (previousDevice.deviceId === targetDevice.deviceId) {\n      return;\n    }\n\n    const metadata: BaseLogMetadata = {\n      previousDevice: {\n        deviceName: previousDevice.deviceName,\n        deviceId: previousDevice.deviceId,\n      },\n      newDevice: {\n        deviceName: targetDevice.deviceName,\n        deviceId: targetDevice.deviceId,\n        platform: targetDevice.platform,\n      },\n    };\n    logger.info(\n      `${LOG_PREFIX} Device selection changed from '${previousDevice.deviceName}' to '${targetDevice.deviceName}'`,\n      metadata\n    );\n\n    if (isValidDevice(targetDevice)) {\n      const stateMetadata: BaseLogMetadata = {\n        deviceName: targetDevice.deviceName,\n        deviceId: targetDevice.deviceId,\n        platform: targetDevice.platform,\n      };\n      logger.info(\n        `${LOG_PREFIX} Clearing dashboard query cache and requesting fresh state from '${targetDevice.deviceName}'`,\n        stateMetadata\n      );\n      queryClient.clear();\n      requestInitialState(socket, targetDevice);\n    } else {\n      const errorMetadata: BaseLogMetadata = {\n        deviceName: targetDevice.deviceName,\n        deviceId: targetDevice.deviceId,\n        platform: targetDevice.platform,\n        validationError: \"Invalid device ID\",\n      };\n      logger.warn(\n        `${LOG_PREFIX} Invalid device selection '${targetDevice.deviceName}' - skipping state request`,\n        errorMetadata\n      );\n    }\n  }, [targetDevice, isConnected, socket, queryClient]);\n\n  // Effect to set up the main query-sync listener\n  useEffect(() => {\n    if (!isConnected || !socket) {\n      const metadata: BaseLogMetadata = {\n        status: \"OFFLINE\",\n        connectionStatus: isConnected ? \"CONNECTED\" : \"DISCONNECTED\",\n        socketExists: !!socket,\n      };\n      logger.warn(\n        `${LOG_PREFIX} Cannot setup query-sync listeners - no socket connection available`,\n        metadata\n      );\n      return;\n    }\n\n    logger.info(\n      `${LOG_PREFIX} Setting up query-sync event listener for incoming device updates`\n    );\n    socket.on(\"query-sync\", handleQuerySync);\n\n    return () => {\n      logger.info(`${LOG_PREFIX} Cleaning up query-sync event listener`);\n      socket.off(\"query-sync\", handleQuerySync);\n    };\n  }, [isConnected, socket, handleQuerySync]);\n\n  // Effect to handle online manager status synchronization\n  useEffect(() => {\n    if (!isConnected || !socket) return;\n\n    const onlineManagerUnsubscribe = onlineManager.subscribe(\n      (isOnline: boolean) => {\n        const currentSelectedDevice = selectedDeviceRef.current;\n        const statusText = isOnline ? \"ONLINE\" : \"OFFLINE\";\n        const metadata: BaseLogMetadata = {\n          status: statusText,\n          deviceName: currentSelectedDevice?.deviceName,\n          deviceId: currentSelectedDevice?.deviceId,\n        };\n        logger.info(\n          `${LOG_PREFIX} Dashboard online status changed to ${statusText} - syncing with device`,\n          metadata\n        );\n\n        if (isValidDevice(currentSelectedDevice)) {\n          sendOnlineStatus(socket, currentSelectedDevice, isOnline);\n        } else {\n          const errorMetadata: BaseLogMetadata = {\n            status: statusText,\n            deviceId: currentSelectedDevice?.deviceId,\n            deviceName: currentSelectedDevice?.deviceName,\n            validationError: \"No valid device selected\",\n          };\n          logger.warn(\n            `${LOG_PREFIX} Cannot sync ${statusText} status - no valid device selected`,\n            errorMetadata\n          );\n        }\n      }\n    );\n\n    return () => {\n      logger.info(`${LOG_PREFIX} Cleaning up online manager subscription`);\n      onlineManagerUnsubscribe();\n    };\n  }, [isConnected, socket]); // Depends on connection status and socket instance\n\n  // Effect to handle query cache changes for manual updates\n  useEffect(() => {\n    if (!isConnected || !socket) return;\n\n    const queryCache = queryClient.getQueryCache();\n    logger.info(\n      `${LOG_PREFIX} Setting up query cache listener for manual data updates`\n    );\n\n    const querySubscription = queryCache.subscribe((event) => {\n      const currentSelectedDevice = selectedDeviceRef.current;\n\n      if (!isValidDevice(currentSelectedDevice)) {\n        const logMetadata: BaseLogMetadata = {\n          eventType: event.type,\n          deviceId: currentSelectedDevice?.deviceId,\n          deviceName: currentSelectedDevice?.deviceName,\n        };\n        logger.debug(\n          `${LOG_PREFIX} Query cache event ignored - no valid device selected`,\n          logMetadata\n        );\n        return;\n      }\n\n      // Process query cache events\n      if (event.type === \"updated\") {\n        const actionType = event.action.type as QueryActions;\n        const queryLogMetadata: BaseLogMetadata = {\n          queryHash: event.query.queryHash,\n          queryKey: event.query.queryKey,\n          deviceName: currentSelectedDevice.deviceName,\n          deviceId: currentSelectedDevice.deviceId,\n          action: actionType,\n        };\n\n        // Handle manual data updates specifically\n        if (actionType === \"success\") {\n          // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n          // @ts-expect-error This 'manual' property might exist based on usage pattern\n          if (event.action.manual) {\n            logger.debug(\n              `${LOG_PREFIX} Forwarding manual data update to device: ${currentSelectedDevice.deviceName}`,\n              queryLogMetadata\n            );\n            sendQueryAction(\n              socket,\n              currentSelectedDevice,\n              \"ACTION-DATA-UPDATE\",\n              event.query\n            );\n          }\n        }\n      }\n    });\n\n    return () => {\n      logger.info(`${LOG_PREFIX} Cleaning up query cache subscription`);\n      querySubscription();\n    };\n  }, [isConnected, socket, queryClient]);\n\n  // Memoize the action handler\n  const handleQueryAction = useCallback(\n    (socket: Socket, targetDevice: User, action: string, query: any) => {\n      if (!isValidDevice(targetDevice)) {\n        logger.debug(\n          `${LOG_PREFIX} Query action ignored - no valid device selected`,\n          {\n            deviceId: targetDevice?.deviceId,\n            deviceName: targetDevice?.deviceName,\n          }\n        );\n        return;\n      }\n\n      const queryLogMetadata = {\n        queryHash: query.queryHash,\n        queryKey: query.queryKey,\n        deviceName: targetDevice.deviceName,\n        deviceId: targetDevice.deviceId,\n        action,\n      };\n\n      logger.debug(\n        `${LOG_PREFIX} Forwarding ${action} to device: ${targetDevice.deviceName}`,\n        queryLogMetadata\n      );\n\n      sendQueryAction(socket, targetDevice, action as QueryActions, query);\n    },\n    []\n  );\n\n  // Debug log for dependencies\n  useEffect(() => {\n    console.log(\"🔍 SyncQueriesWeb Dependencies:\", {\n      isConnected,\n      hasSocket: !!socket,\n      targetDevice,\n      handleQueryAction: !!handleQueryAction,\n    });\n  }, [isConnected, socket, targetDevice, handleQueryAction]);\n\n  // Use the dev tools event handler for explicit actions\n  useDevToolsEventHandler({\n    isConnected,\n    socket,\n    selectedDevice: targetDevice,\n    sendQueryAction: handleQueryAction,\n  });\n\n  // Return connection status (or potentially other state if needed later)\n  return { isConnected };\n}\n"
  },
  {
    "path": "src/components/external-dash/utils/devToolsEvents.ts",
    "content": "import { logger } from \"./logger\";\n\nexport type DevToolsActionType =\n  | \"REFETCH\"\n  | \"INVALIDATE\"\n  | \"RESET\"\n  | \"REMOVE\"\n  | \"TRIGGER_ERROR\"\n  | \"RESTORE_ERROR\"\n  | \"TRIGGER_LOADING\"\n  | \"RESTORE_LOADING\"\n  | \"CLEAR_MUTATION_CACHE\"\n  | \"CLEAR_QUERY_CACHE\";\n\ninterface DevToolsEventDetail {\n  type: DevToolsActionType;\n  queryHash?: string | undefined;\n  metadata?: Record<string, unknown> | undefined;\n}\n\nexport const DEV_TOOLS_EVENT = \"@tanstack/query-devtools-event\";\n\n// For the external dev tools to use in their onClick handlers\nexport const sendDevToolsEvent = ({\n  type,\n  queryHash,\n  metadata,\n}: DevToolsEventDetail) => {\n  logger.debug(\n    `📤 Sending dev tools event - Type: ${type}, QueryHash: ${queryHash}`\n  );\n  const event = new CustomEvent<DevToolsEventDetail>(DEV_TOOLS_EVENT, {\n    detail: {\n      type,\n      queryHash: queryHash ?? undefined,\n      metadata: metadata ?? undefined,\n    },\n    bubbles: true,\n    cancelable: true,\n  });\n  window.dispatchEvent(event);\n  logger.debug(\"✅ Event dispatched successfully\");\n};\n\ntype DevToolsEventCallback = (\n  type: DevToolsActionType,\n  queryHash: string | undefined,\n  metadata: Record<string, unknown> | undefined\n) => void;\n\n// For our app to listen to events\nexport const onDevToolsEvent = (callback: DevToolsEventCallback) => {\n  logger.debug(\"🎧 Setting up dev tools event listener for \" + DEV_TOOLS_EVENT);\n\n  const handler = (event: CustomEvent<DevToolsEventDetail>) => {\n    const { type, queryHash, metadata } = event.detail;\n    logger.debug(\n      `📥 Received dev tools event - Type: ${type}, QueryHash: ${queryHash}`\n    );\n    callback(type, queryHash ?? undefined, metadata ?? undefined);\n  };\n\n  window.addEventListener(DEV_TOOLS_EVENT, handler as EventListener);\n\n  return () => {\n    logger.debug(\"🛑 Removing dev tools event listener\");\n    window.removeEventListener(DEV_TOOLS_EVENT, handler as EventListener);\n  };\n};\n"
  },
  {
    "path": "src/components/external-dash/utils/logStore.ts",
    "content": "import { create } from \"zustand\";\n\nexport type LogLevel = \"info\" | \"error\" | \"warn\" | \"debug\";\n\nexport interface LogEntry {\n  id: string;\n  timestamp: Date;\n  message: string;\n  level: LogLevel;\n  deviceId?: string;\n  deviceName?: string;\n  platform?: string;\n}\n\ninterface LogState {\n  logs: LogEntry[];\n  addLog: (entry: Omit<LogEntry, \"id\" | \"timestamp\">) => void;\n  clearLogs: () => void;\n  maxLogs: number;\n  setMaxLogs: (max: number) => void;\n  isEnabled: boolean;\n  setIsEnabled: (enabled: boolean) => void;\n  isVisible: boolean;\n  setIsVisible: (visible: boolean) => void;\n}\n\nexport const useLogStore = create<LogState>((set) => ({\n  logs: [] as LogEntry[],\n  maxLogs: 500, // Default max logs to store\n  isEnabled: true, // Default enabled\n  isVisible: false, // Default hidden\n\n  addLog: (entry: Omit<LogEntry, \"id\" | \"timestamp\">) =>\n    set((state: LogState) => {\n      // If logging is disabled, ignore the log\n      if (!state.isEnabled) return state;\n\n      const newLog: LogEntry = {\n        id: crypto.randomUUID(),\n        timestamp: new Date(),\n        ...entry,\n      };\n\n      // Add new log and trim if exceeding max\n      const newLogs = [newLog, ...state.logs];\n      if (newLogs.length > state.maxLogs) {\n        newLogs.length = state.maxLogs;\n      }\n\n      return { logs: newLogs };\n    }),\n\n  clearLogs: () => set({ logs: [] }),\n\n  setMaxLogs: (max: number) =>\n    set((state: LogState) => {\n      // Trim logs if needed when reducing max\n      const logs = state.logs.slice(0, max);\n      return { maxLogs: max, logs };\n    }),\n\n  setIsEnabled: (enabled: boolean) => set({ isEnabled: enabled }),\n  setIsVisible: (visible: boolean) => set({ isVisible: visible }),\n}));\n\n// Helper log functions\nexport const logInfo = (\n  message: string,\n  deviceInfo?: { deviceId?: string; deviceName?: string; platform?: string }\n) => {\n  useLogStore.getState().addLog({\n    message,\n    level: \"info\",\n    ...deviceInfo,\n  });\n};\n\nexport const logError = (\n  message: string,\n  deviceInfo?: { deviceId?: string; deviceName?: string; platform?: string }\n) => {\n  useLogStore.getState().addLog({\n    message,\n    level: \"error\",\n    ...deviceInfo,\n  });\n};\n\nexport const logWarn = (\n  message: string,\n  deviceInfo?: { deviceId?: string; deviceName?: string; platform?: string }\n) => {\n  useLogStore.getState().addLog({\n    message,\n    level: \"warn\",\n    ...deviceInfo,\n  });\n};\n\nexport const logDebug = (\n  message: string,\n  deviceInfo?: { deviceId?: string; deviceName?: string; platform?: string }\n) => {\n  useLogStore.getState().addLog({\n    message,\n    level: \"debug\",\n    ...deviceInfo,\n  });\n};\n"
  },
  {
    "path": "src/components/external-dash/utils/logger.ts",
    "content": "import { logInfo, logError, logWarn, logDebug } from \"./logStore\";\nimport { User } from \"../types/User\";\n\n/**\n * A logger utility that both logs to the console and to our LogStore for display in the UI\n */\nexport const logger = {\n  /**\n   * Log informational message\n   */\n  info: (message: string, deviceInfo?: Partial<User>) => {\n    console.info(message);\n    logInfo(message, deviceInfo);\n  },\n\n  /**\n   * Log warning message\n   */\n  warn: (message: string, deviceInfo?: Partial<User>) => {\n    console.warn(message);\n    logWarn(message, deviceInfo);\n  },\n\n  /**\n   * Log error message\n   */\n  error: (message: string, deviceInfo?: Partial<User>) => {\n    console.error(message);\n    logError(message, deviceInfo);\n  },\n\n  /**\n   * Log debug message\n   */\n  debug: (message: string, deviceInfo?: Partial<User>) => {\n    console.debug(message);\n    logDebug(message, deviceInfo);\n  },\n\n  /**\n   * Regular log - maps to info level\n   */\n  log: (message: string, deviceInfo?: Partial<User>) => {\n    console.log(message);\n    logInfo(message, deviceInfo);\n  },\n};\n\n/**\n * Create a logger instance pre-configured with a specific device\n */\nexport const createDeviceLogger = (device: User) => {\n  const deviceInfo = {\n    deviceId: device.deviceId,\n    deviceName: device.deviceName,\n    platform: device.platform,\n  };\n\n  return {\n    info: (message: string) => logger.info(message, deviceInfo),\n    warn: (message: string) => logger.warn(message, deviceInfo),\n    error: (message: string) => logger.error(message, deviceInfo),\n    debug: (message: string) => logger.debug(message, deviceInfo),\n    log: (message: string) => logger.log(message, deviceInfo),\n  };\n};\n"
  },
  {
    "path": "src/components/external-dash/utils/platformUtils.tsx",
    "content": "import React from \"react\";\n\n// --- 1. Extracted SVG Icon Components (SRP, KISS) ---\n\ninterface IconProps {\n  className?: string;\n  // Allow other SVG props like fill, viewBox etc., if needed later\n  [key: string]: any; // Allow passing down arbitrary props\n}\n\nconst defaultIconProps = {\n  viewBox: \"0 0 24 24\",\n  fill: \"currentColor\",\n  className: \"w-3 h-3\", // Default size, can be overridden by props\n};\n\nconst IosIcon: React.FC<IconProps> = (props) => (\n  <svg {...defaultIconProps} {...props}>\n    <path d=\"M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.81-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z\" />\n  </svg>\n);\n\nconst AndroidIcon: React.FC<IconProps> = (props) => (\n  <svg {...defaultIconProps} {...props}>\n    <path d=\"M16.61 15.15c-.46 0-.84-.37-.84-.83s.37-.83.84-.83.83.37.83.83-.37.83-.83.83m-9.22 0c-.46 0-.83-.37-.83-.83s.37-.83.83-.83.84.37.84.83-.37.83-.84.83m9.42-5.89l1.67-2.89c.09-.17.03-.38-.13-.47-.17-.09-.38-.03-.47.13l-1.69 2.93A9.973 9.973 0 0012 7.75c-1.89 0-3.63.52-5.19 1.37L5.12 6.19c-.09-.17-.3-.22-.47-.13-.17.09-.22.3-.13.47l1.67 2.89C3.44 11.15 1.62 14.56 1.62 18h20.76c0-3.44-1.82-6.85-4.57-8.74z\" />\n  </svg>\n);\n\nconst WebIcon: React.FC<IconProps> = (props) => (\n  <svg {...defaultIconProps} {...props}>\n    <path d=\"M16.36 14c.08-.66.14-1.32.14-2 0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2m-5.15 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95a8.03 8.03 0 01-4.33 3.56M14.34 14H9.66c-.1-.66-.16-1.32-.16-2 0-.68.06-1.35.16-2h4.68c.09.65.16 1.32.16 2 0 .68-.07 1.34-.16 2M12 19.96c-.83-1.2-1.5-2.53-1.91-3.96h3.82c-.41 1.43-1.08 2.76-1.91 3.96M8 8H5.08A7.923 7.923 0 019.4 4.44C8.8 5.55 8.35 6.75 8 8m-2.92 8H8c.35 1.25.8 2.45 1.4 3.56A8.008 8.008 0 015.08 16m-.82-2C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2 0 .68.06 1.34.14 2M12 4.03c.83 1.2 1.5 2.54 1.91 3.97h-3.82c.41-1.43 1.08-2.77 1.91-3.97M18.92 8h-2.95a15.65 15.65 0 00-1.38-3.56c1.84.63 3.37 1.9 4.33 3.56M12 2C6.47 2 2 6.5 2 12a10 10 0 0010 10 10 10 0 0010-10A10 10 0 0012 2z\" />\n  </svg>\n);\n\n// Updated Android TV Icon - Represents a modern Smart TV screen\nconst AndroidTvIcon: React.FC<IconProps> = (props) => (\n  <svg {...defaultIconProps} {...props}>\n    <path d=\"M21 17H3V5h18m0-2H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h5v2h8v-2h5c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z\" />\n  </svg>\n);\n\n// Updated Apple TV Icon - Using the Apple logo\nconst AppleTvIcon: React.FC<IconProps> = (props) => (\n  <svg {...defaultIconProps} {...props} viewBox=\"0 0 24 24\">\n    {/* Custom viewBox for Apple logo */}\n    <path d=\"M18.71,19.5c-0.83,1.24-1.71,2.45-3.05,2.47c-1.34,0.03-1.77-0.79-3.29-0.79c-1.53,0-2,0.77-3.27,0.82 C7.79,22.55,6.79,21.28,5.95,20C4.25,17,2.94,12.45,4.7,9.39c0.87-1.52,2.43-2.48,4.12-2.51c1.28-0.02,2.5,0.87,3.29,0.87 c0.78,0,2.26-1.07,3.81-0.91c0.65,0.03,2.47,0.26,3.64,1.98c-0.09,0.06-2.17,1.28-2.15,3.81C17.44,15.67,20.06,16.68,20.09,16.69 C20.06,16.76,19.65,18.06,18.71,19.5z M13,3.5c0.73-0.83,1.94-1.46,2.94-1.5c0.13,1.17-0.34,2.35-1.04,3.19 C14.21,5.94,13.07,6.6,11.95,6.5C11.8,5.35,12.41,4.15,13,3.5z\" />\n  </svg>\n);\n\n// Default Icon (Generic Mobile Device)\nconst DefaultDeviceIcon: React.FC<IconProps> = (props) => (\n  <svg {...defaultIconProps} {...props}>\n    <path d=\"M17.25 18H6.75V4h10.5M14 21h-4v-1h4m2-19H8C6.34 1 5 2.34 5 4v16c0 1.66 1.34 3 3 3h8c1.66 0 3-1.34 3-3V4c0-1.66-1.34-3-3-3z\" />\n  </svg>\n);\n\n// --- 2. Central Platform Configuration (DRY, Convention over Config) ---\n\ninterface PlatformConfig {\n  displayName: string;\n  bgColor: string;\n  textColor: string;\n  IconComponent: React.FC<IconProps>;\n}\n\nconst platformConfiguration: Record<string, PlatformConfig> = {\n  ios: {\n    displayName: \"iOS\",\n    bgColor: \"bg-blue-900/30 text-blue-200\",\n    textColor: \"text-blue-300\",\n    IconComponent: IosIcon,\n  },\n  android: {\n    displayName: \"Android\",\n    bgColor: \"bg-green-900/30 text-green-200\",\n    textColor: \"text-green-300\",\n    IconComponent: AndroidIcon,\n  },\n  web: {\n    displayName: \"Web\",\n    bgColor: \"bg-cyan-900/30 text-cyan-200\",\n    textColor: \"text-cyan-300\",\n    IconComponent: WebIcon,\n  },\n  tv: {\n    displayName: \"Android TV\",\n    bgColor: \"bg-green-900/30 text-green-200\",\n    textColor: \"text-green-300\",\n    IconComponent: AndroidTvIcon,\n  },\n  tvos: {\n    displayName: \"Apple TV\",\n    bgColor: \"bg-purple-900/30 text-purple-200\",\n    textColor: \"text-purple-300\",\n    IconComponent: AppleTvIcon,\n  },\n  // Default configuration for unknown platforms\n  default: {\n    displayName: \"Device\",\n    bgColor: \"bg-[#1D1D1F]/60 text-[#F5F5F7]\",\n    textColor: \"text-[#F5F5F7]\",\n    IconComponent: DefaultDeviceIcon,\n  },\n};\n\n// Helper to get platform config safely\nconst getPlatformConfig = (\n  platform: string | undefined | null\n): PlatformConfig => {\n  const normalizedPlatform = platform?.toLowerCase() || \"\";\n  return (\n    platformConfiguration[normalizedPlatform] || platformConfiguration.default\n  );\n};\n\n// --- 3. Refactored PlatformIcon Component (Composition, KISS) ---\n\nexport const PlatformIcon: React.FC<{\n  platform: string;\n  className?: string;\n}> = ({\n  platform,\n  className, // Allow overriding className\n  ...rest // Pass any other props down\n}) => {\n  const config = getPlatformConfig(platform);\n  const { IconComponent } = config;\n\n  // Combine default className with passed className if provided\n  const finalClassName = className ?? defaultIconProps.className;\n\n  return <IconComponent className={finalClassName} {...rest} />;\n};\n\n/**\n * Gets the combined background and text color CSS classes for a platform.\n */\nexport const getPlatformColorClasses = (platform: string): string => {\n  const config = getPlatformConfig(platform);\n  return `${config.bgColor}`;\n};\n\n/**\n * Gets the text color CSS class for a platform.\n */\nexport const getPlatformTextColor = (platform: string): string => {\n  const config = getPlatformConfig(platform);\n  return config.textColor;\n};\n\n/**\n * Gets the background color CSS class for a platform.\n */\nexport const getPlatformBgColor = (platform: string): string => {\n  const config = getPlatformConfig(platform);\n  return config.bgColor;\n};\n\n/**\n * Gets the display-friendly name for a platform.\n */\nexport const getDisplayPlatform = (platform: string): string => {\n  const config = getPlatformConfig(platform);\n  // Return original platform string if it wasn't found and we used default,\n  // unless the original platform string was empty/null.\n  if (config === platformConfiguration.default && platform) {\n    return platform;\n  }\n  return config.displayName;\n};\n"
  },
  {
    "path": "src/components/external-dash/utils/storageQueryKeys.ts",
    "content": "/**\n * Centralized storage query keys for all storage hooks\n * This ensures consistency across MMKV, AsyncStorage, and SecureStorage hooks\n * and allows easy modification of the base storage key in one place\n */\nexport const storageQueryKeys = {\n  /**\n   * Base storage key - change this to update all storage-related queries\n   */\n  base: () => [\"#storage\"] as const,\n\n  /**\n   * MMKV storage query keys\n   */\n  mmkv: {\n    root: () => [...storageQueryKeys.base(), \"mmkv\"] as const,\n    key: (key: string) => [...storageQueryKeys.mmkv.root(), key] as const,\n    all: () => [...storageQueryKeys.mmkv.root(), \"all\"] as const,\n  },\n\n  /**\n   * AsyncStorage query keys\n   */\n  async: {\n    root: () => [...storageQueryKeys.base(), \"async\"] as const,\n    key: (key: string) => [...storageQueryKeys.async.root(), key] as const,\n    all: () => [...storageQueryKeys.async.root(), \"all\"] as const,\n  },\n\n  /**\n   * SecureStorage query keys\n   */\n  secure: {\n    root: () => [...storageQueryKeys.base(), \"secure\"] as const,\n    key: (key: string) => [...storageQueryKeys.secure.root(), key] as const,\n    all: () => [...storageQueryKeys.secure.root(), \"all\"] as const,\n  },\n} as const;\n\n/**\n * Storage types that can be enabled/disabled\n */\nexport type StorageType = \"mmkv\" | \"async\" | \"secure\";\n\n/**\n * Check if a query key matches any of the storage patterns\n */\nexport function isStorageQuery(queryKey: readonly unknown[]): boolean {\n  if (!Array.isArray(queryKey) || queryKey.length === 0) {\n    return false;\n  }\n\n  return queryKey[0] === \"#storage\";\n}\n\n/**\n * Get the storage type from a query key\n */\nexport function getStorageType(\n  queryKey: readonly unknown[]\n): StorageType | null {\n  if (!isStorageQuery(queryKey) || queryKey.length < 2) {\n    return null;\n  }\n\n  const storageType = queryKey[1];\n  if (\n    storageType === \"mmkv\" ||\n    storageType === \"async\" ||\n    storageType === \"secure\"\n  ) {\n    return storageType;\n  }\n\n  return null;\n}\n\n/**\n * Check if a storage type should be filtered out based on enabled storage types\n */\nexport function shouldFilterStorageQuery(\n  queryKey: readonly unknown[],\n  enabledStorageTypes: Set<StorageType>\n): boolean {\n  const storageType = getStorageType(queryKey);\n  if (storageType === null) {\n    return false; // Not a storage query, don't filter\n  }\n\n  return !enabledStorageTypes.has(storageType);\n}\n"
  },
  {
    "path": "src/components/external-dash/utils/storageStore.ts",
    "content": "import { create } from \"zustand\";\nimport { persist } from \"zustand/middleware\";\nimport { StorageType } from \"./storageQueryKeys\";\n\ninterface StorageStore {\n  enabledStorageTypes: Set<StorageType>;\n  setStorageTypeEnabled: (storageType: StorageType, enabled: boolean) => void;\n  isStorageTypeEnabled: (storageType: StorageType) => boolean;\n  clearAllStorageTypes: () => void;\n  enableAllStorageTypes: () => void;\n}\n\nexport const useStorageStore = create<StorageStore>()(\n  persist(\n    (set, get) => ({\n      // By default, all storage types are enabled\n      enabledStorageTypes: new Set<StorageType>([\"mmkv\", \"async\", \"secure\"]),\n\n      setStorageTypeEnabled: (storageType: StorageType, enabled: boolean) => {\n        set((state) => {\n          const newEnabledTypes = new Set(state.enabledStorageTypes);\n          if (enabled) {\n            newEnabledTypes.add(storageType);\n          } else {\n            newEnabledTypes.delete(storageType);\n          }\n          return { enabledStorageTypes: newEnabledTypes };\n        });\n      },\n\n      isStorageTypeEnabled: (storageType: StorageType) => {\n        return get().enabledStorageTypes.has(storageType);\n      },\n\n      clearAllStorageTypes: () => {\n        set({ enabledStorageTypes: new Set() });\n      },\n\n      enableAllStorageTypes: () => {\n        set({ enabledStorageTypes: new Set([\"mmkv\", \"async\", \"secure\"]) });\n      },\n    }),\n    {\n      name: \"storage-preferences\",\n      // Transform Set to Array for storage\n      partialize: (state) => ({\n        ...state,\n        enabledStorageTypes: Array.from(state.enabledStorageTypes),\n      }),\n      // Transform Array back to Set when loading\n      onRehydrateStorage: () => (state) => {\n        if (\n          state &&\n          \"enabledStorageTypes\" in state &&\n          Array.isArray(state.enabledStorageTypes)\n        ) {\n          state.enabledStorageTypes = new Set(\n            state.enabledStorageTypes as StorageType[]\n          );\n        }\n      },\n    }\n  )\n);\n"
  },
  {
    "path": "src/config.ts",
    "content": "/**\n * Application configuration settings\n */\n\n// Server configuration\nexport const SERVER_PORT = 42831;\n\n// Socket.IO configuration\nexport const SOCKET_CONFIG = {\n  cors: {\n    origin: \"*\",\n    methods: [\"GET\", \"POST\"],\n  },\n  // Fix typing issue with transports\n  transports: [\"websocket\", \"polling\"] as any, // Type assertion to avoid TypeScript error\n  pingTimeout: 30000,\n  pingInterval: 25000,\n};\n\n// Client configuration\nexport const CLIENT_URL = `http://localhost:${SERVER_PORT}`;\n"
  },
  {
    "path": "src/index.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n/* SF Pro Display font for Apple-like UI */\n@font-face {\n  font-family: \"SF Pro Display\";\n  src: local(\"SF Pro Display\"),\n    url(\"https://applesocial.s3.amazonaws.com/assets/styles/fonts/sanfrancisco/sanfranciscodisplay-regular-webfont.woff\")\n      format(\"woff\");\n  font-weight: 400;\n  font-style: normal;\n}\n\n@font-face {\n  font-family: \"SF Pro Display\";\n  src: local(\"SF Pro Display Medium\"),\n    url(\"https://applesocial.s3.amazonaws.com/assets/styles/fonts/sanfrancisco/sanfranciscodisplay-medium-webfont.woff\")\n      format(\"woff\");\n  font-weight: 500;\n  font-style: normal;\n}\n\n@font-face {\n  font-family: \"SF Pro Display\";\n  src: local(\"SF Pro Display Semibold\"),\n    url(\"https://applesocial.s3.amazonaws.com/assets/styles/fonts/sanfrancisco/sanfranciscodisplay-semibold-webfont.woff\")\n      format(\"woff\");\n  font-weight: 600;\n  font-style: normal;\n}\n\n/* Custom styles for the dark theme */\nbody {\n  font-family: \"SF Pro Display\", -apple-system, BlinkMacSystemFont, \"Segoe UI\",\n    Roboto, Helvetica, Arial, sans-serif;\n  margin: 0;\n  padding: 0;\n  background-color: #0a0a0c; /* Dark Apple-like bg */\n  color: #f5f5f7; /* Apple-like text color */\n  height: 100vh;\n  width: 100vw;\n  overflow: hidden;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\n#root {\n  height: 100vh;\n  width: 100vw;\n  display: flex;\n  flex-direction: column;\n  overflow: hidden; /* Prevent overall page scrolling */\n}\n\n/* Fix for scrollable elements */\n.overflow-y-auto {\n  -webkit-overflow-scrolling: touch; /* Smooth scrolling on iOS */\n  scrollbar-width: thin; /* Thin scrollbars on Firefox */\n  scrollbar-color: rgba(156, 163, 175, 0.3) rgba(31, 41, 55, 0.5); /* Custom scrollbar colors for Firefox */\n}\n\n/* Webkit scrollbar styles */\n.overflow-y-auto::-webkit-scrollbar {\n  width: 6px;\n}\n\n.overflow-y-auto::-webkit-scrollbar-track {\n  background: rgba(31, 41, 55, 0.5);\n  border-radius: 3px;\n}\n\n.overflow-y-auto::-webkit-scrollbar-thumb {\n  background-color: rgba(156, 163, 175, 0.3);\n  border-radius: 3px;\n}\n\n/* Custom animations */\n@keyframes fadeIn {\n  0% {\n    opacity: 0;\n  }\n  100% {\n    opacity: 1;\n  }\n}\n\n@keyframes gradient {\n  0% {\n    background-position: 0% 50%;\n  }\n  50% {\n    background-position: 100% 50%;\n  }\n  100% {\n    background-position: 0% 50%;\n  }\n}\n\n@keyframes slideUpFade {\n  0% {\n    transform: translateY(1rem);\n    opacity: 0;\n  }\n  100% {\n    transform: translateY(0);\n    opacity: 1;\n  }\n}\n\n@keyframes shimmer {\n  0% {\n    background-position: -500px 0;\n  }\n  100% {\n    background-position: 500px 0;\n  }\n}\n\n/* Animation utility classes */\n.animate-fadeIn {\n  animation: fadeIn 0.5s ease-out forwards;\n}\n\n.animate-gradient {\n  animation: gradient 8s ease infinite;\n  background-size: 200% 200%;\n}\n\n.animate-slideUpFade {\n  animation: slideUpFade 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards;\n}\n\n.animate-shimmer {\n  animation: shimmer 2.5s infinite linear;\n  background: linear-gradient(\n    90deg,\n    rgba(255, 255, 255, 0),\n    rgba(255, 255, 255, 0.05),\n    rgba(255, 255, 255, 0)\n  );\n  background-size: 500px 100%;\n}\n\n/* Apple-like transition default */\n.transition-apple {\n  transition-property: all;\n  transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1);\n  transition-duration: 500ms;\n}\n\n/* Font class */\n.font-sf-pro {\n  font-family: \"SF Pro Display\", -apple-system, BlinkMacSystemFont, \"Segoe UI\",\n    Roboto, Helvetica, Arial, sans-serif;\n}\n"
  },
  {
    "path": "src/main.ts",
    "content": "import { app, BrowserWindow, dialog } from \"electron\";\nimport path from \"node:path\";\nimport started from \"electron-squirrel-startup\";\nimport { startServer } from \"./server\";\nimport { Server } from \"socket.io\";\nimport { Server as HttpServer } from \"http\";\nimport { setupAutoUpdater } from \"./auto-updater\";\n\n// Handle creating/removing shortcuts on Windows when installing/uninstalling.\nif (started) {\n  app.quit();\n}\n\n// For Socket.IO server\nlet socketServer: { io: Server; server: HttpServer } | null = null;\n\nconst createWindow = () => {\n  // Create the browser window.\n  const mainWindow = new BrowserWindow({\n    width: 900,\n    height: 700,\n    webPreferences: {\n      preload: path.join(__dirname, \"preload.js\"),\n    },\n  });\n\n  // and load the index.html of the app.\n  if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {\n    mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL);\n  } else {\n    mainWindow.loadFile(\n      path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`)\n    );\n  }\n\n  // Open DevTools only in development mode\n  if (!app.isPackaged) {\n    mainWindow.webContents.openDevTools();\n  }\n};\n\n// This method will be called when Electron has finished\n// initialization and is ready to create browser windows.\n// Some APIs can only be used after this event occurs.\napp.on(\"ready\", () => {\n  try {\n    // Start the Socket.IO server\n    socketServer = startServer();\n    console.log(\"Simple Socket.IO server started\");\n\n    // Setup auto-updater\n    setupAutoUpdater();\n\n    // Create the application window\n    createWindow();\n  } catch (error) {\n    console.error(\"Failed to start application:\", error);\n\n    // Show error dialog\n    dialog.showErrorBox(\n      \"Application Error\",\n      `Failed to start the application: ${error.message}\\n\\nThe application will now exit.`\n    );\n\n    // Exit the app with error code\n    app.exit(1);\n  }\n});\n\n// Quit when all windows are closed, except on macOS. There, it's common\n// for applications and their menu bar to stay active until the user quits\n// explicitly with Cmd + Q.\napp.on(\"window-all-closed\", () => {\n  if (process.platform !== \"darwin\") {\n    app.quit();\n  }\n});\n\napp.on(\"activate\", () => {\n  // On OS X it's common to re-create a window in the app when the\n  // dock icon is clicked and there are no other windows open.\n  if (BrowserWindow.getAllWindows().length === 0) {\n    createWindow();\n  }\n});\n\n// In this file you can include the rest of your app's specific main process\n// code. You can also put them in separate files and import them here.\n\n// Close server when app quits\napp.on(\"quit\", () => {\n  if (socketServer && socketServer.server) {\n    socketServer.server.close();\n    console.log(\"Socket.IO server closed\");\n  }\n});\n"
  },
  {
    "path": "src/preload.ts",
    "content": "// See the Electron documentation for details on how to use preload scripts:\n// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts\n"
  },
  {
    "path": "src/react-query-external-sync/README.md",
    "content": "# React Query External Sync\n\nA powerful debugging tool for React Query state in any React-based application. Whether you're building for mobile, web, desktop, TV, or VR - this package has you covered. It works seamlessly across all platforms where React runs, with zero configuration to disable in production.\n\nPairs perfectly with [React Native DevTools](https://github.com/LovesWorking/rn-better-dev-tools) for a complete development experience.\n\n![React Query External Sync Demo](https://github.com/user-attachments/assets/39e5c417-be4d-46af-8138-3589d73fce9f)\n\n## ✨ Features\n\n- 🔄 Real-time React Query state synchronization\n- 📱 Works with any React-based framework (React, React Native, Expo, Next.js, etc.)\n- 🖥️ Platform-agnostic: Web, iOS, Android, macOS, Windows, Linux, tvOS, VR - you name it!\n- 🔌 Socket.IO integration for reliable communication\n- 📊 Query status, data, and error monitoring\n- ⚡️ Simple integration with minimal setup\n- 🧩 Perfect companion to React Native DevTools\n- 🛑 Zero-config production safety - automatically disabled in production builds\n\n## 📦 Installation\n\n```bash\n# Using npm\nnpm install --save-dev react-query-external-sync socket.io-client\n\n# Using yarn\nyarn add -D react-query-external-sync socket.io-client\n\n# Using pnpm\npnpm add -D react-query-external-sync socket.io-client\n```\n\n## 🚀 Quick Start\n\nAdd the hook to your application where you set up your React Query context:\n\n```jsx\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport { useSyncQueriesExternal } from \"react-query-external-sync\";\n// Import Platform for React Native or use other platform detection for web/desktop\nimport { Platform } from \"react-native\";\n\n// Create your query client\nconst queryClient = new QueryClient();\n\nfunction App() {\n  return (\n    <QueryClientProvider client={queryClient}>\n      <AppContent />\n    </QueryClientProvider>\n  );\n}\n\nfunction AppContent() {\n  // Set up the sync hook - automatically disabled in production!\n  useSyncQueriesExternal({\n    queryClient,\n    socketURL: \"http://localhost:42831\", // Default port for React Native DevTools\n    deviceName: Platform?.OS || \"web\", // Platform detection\n    platform: Platform?.OS || \"web\", // Use appropriate platform identifier\n    deviceId: Platform?.OS || \"web\", // Use a PERSISTENT identifier (see note below)\n    extraDeviceInfo: {\n      // Optional additional info about your device\n      appVersion: \"1.0.0\",\n      // Add any relevant platform info\n    },\n    envVariables: {\n      // Optional environment variables from your app\n      NODE_ENV: process.env.NODE_ENV,\n      API_URL: process.env.API_URL,\n      // Add any relevant environment variables\n    },\n    enableLogs: false,\n  });\n\n  // Your app content\n  return <YourApp />;\n}\n```\n\n## 🔒 Production Safety\n\nThis package is automatically disabled in production builds.\n\n```jsx\n// The package handles this internally:\nif (process.env.NODE_ENV !== \"production\") {\n  useSyncQueries = require(\"./new-sync/useSyncQueries\").useSyncQueries;\n} else {\n  // In production, this becomes a no-op function\n  useSyncQueries = () => ({\n    isConnected: false,\n    connect: () => {},\n    disconnect: () => {},\n    socket: null,\n    users: [],\n  });\n}\n```\n\n## 💡 Usage with DevTools\n\nFor the best experience, use this package with the [React Native DevTools](https://github.com/LovesWorking/rn-better-dev-tools) application:\n\n1. Download and launch the DevTools application\n2. Integrate this package in your React application\n3. Start your application\n4. DevTools will automatically detect and connect to your running application\n\n> **Note**: For optimal connection, launch DevTools before starting your application.\n\n## ⚙️ Configuration Options\n\nThe `useSyncQueriesExternal` hook accepts the following options:\n\n| Option            | Type        | Required | Description                                                             |\n| ----------------- | ----------- | -------- | ----------------------------------------------------------------------- |\n| `queryClient`     | QueryClient | Yes      | Your React Query client instance                                        |\n| `socketURL`       | string      | Yes      | URL of the socket server (e.g., 'http://localhost:42831')               |\n| `deviceName`      | string      | Yes      | Human-readable name for your device                                     |\n| `platform`        | string      | Yes      | Platform identifier ('ios', 'android', 'web', 'macos', 'windows', etc.) |\n| `deviceId`        | string      | Yes      | Unique identifier for your device                                       |\n| `extraDeviceInfo` | object      | No       | Additional device metadata to display in DevTools                       |\n| `envVariables`    | object      | No       | Environment variables from your app to display in DevTools              |\n| `enableLogs`      | boolean     | No       | Enable console logging for debugging (default: false)                   |\n\n## 🐛 Troubleshooting\n\n### Quick Checklist\n\n1. **DevTools Connection**\n\n   - Look for \"Connected\" status in the top-left corner of the DevTools app\n   - If it shows \"Disconnected\", restart the DevTools app\n\n2. **No Devices Appearing**\n\n   - Verify the Socket.IO client is installed (`npm list socket.io-client`)\n   - Ensure the hook is properly set up in your app\n   - Check that `socketURL` matches the DevTools port (default: 42831)\n   - Restart both your app and the DevTools\n\n3. **Data Not Syncing**\n   - Confirm you're passing the correct `queryClient` instance\n   - Set `enableLogs: true` to see connection information\n\nThat's it! If you're still having issues, visit the [GitHub repository](https://github.com/LovesWorking/react-query-external-sync/issues) for support.\n\n## ⚠️ Important Note About Device IDs\n\nThe `deviceId` parameter must be **persistent** across app restarts and re-renders. Using a value that changes (like `Date.now()`) will cause each render to be treated as a new device.\n\n**Recommended approaches:**\n\n```jsx\n// Simple approach for single devices\ndeviceId: Platform.OS, // Works if you only have one device per platform\n\n// Better approach for multiple simulators/devices of same type\n// Using AsyncStorage, MMKV, or another storage solution\nconst [deviceId, setDeviceId] = useState(Platform.OS);\n\nuseEffect(() => {\n  const loadOrCreateDeviceId = async () => {\n    // Try to load existing ID\n    const storedId = await AsyncStorage.getItem('deviceId');\n\n    if (storedId) {\n      setDeviceId(storedId);\n    } else {\n      // First launch - generate and store a persistent ID\n      const newId = `${Platform.OS}-${Date.now()}`;\n      await AsyncStorage.setItem('deviceId', newId);\n      setDeviceId(newId);\n    }\n  };\n\n  loadOrCreateDeviceId();\n}, []);\n```\n\n## 📄 License\n\nMIT\n\n---\n\nMade with ❤️ by [LovesWorking](https://github.com/LovesWorking)\n"
  },
  {
    "path": "src/react-query-external-sync/User.ts",
    "content": "export interface User {\n  id: string;\n  deviceName: string;\n  deviceId?: string; // Optional for backward compatibility\n  platform?: string; // Device platform (iOS, Android, Web)\n  extraDeviceInfo?: string; // json string of additional device information as key-value pairs\n  envVariables?: string; // json string of environment variables from the mobile app\n}\n"
  },
  {
    "path": "src/react-query-external-sync/hydration.ts",
    "content": "import type {\n  DefaultError,\n  Mutation,\n  MutationOptions,\n  Query,\n  QueryClient,\n  QueryFunction,\n  QueryOptions,\n} from \"@tanstack/react-query\";\n\nimport {\n  DehydratedMutation,\n  DehydratedQuery,\n  DehydratedState,\n  ObserverState,\n} from \"./types\";\ntype TransformerFn = (data: unknown) => unknown;\n\nexport function Dehydrate(client: QueryClient): DehydratedState {\n  const mutations = client\n    .getMutationCache()\n    .getAll()\n    .flatMap((mutation) => [dehydrateMutation(mutation)]);\n\n  const queries = client\n    .getQueryCache()\n    .getAll()\n    .flatMap((query) => [dehydrateQuery(query)]);\n\n  return { mutations, queries };\n}\nexport interface DehydrateOptions {\n  serializeData?: TransformerFn;\n  shouldDehydrateMutation?: (mutation: Mutation) => boolean;\n  shouldDehydrateQuery?: (query: Query) => boolean;\n  shouldRedactErrors?: (error: unknown) => boolean;\n}\n\nexport interface HydrateOptions {\n  defaultOptions?: {\n    deserializeData?: TransformerFn;\n    queries?: QueryOptions;\n    mutations?: MutationOptions<unknown, DefaultError, unknown, unknown>;\n  };\n}\n\nfunction dehydrateMutation(mutation: Mutation): DehydratedMutation {\n  return {\n    mutationId: mutation.mutationId,\n    mutationKey: mutation.options.mutationKey,\n    state: mutation.state,\n    ...(mutation.options.scope && { scope: mutation.options.scope }),\n    ...(mutation.meta && { meta: mutation.meta }),\n  };\n}\n\nfunction dehydrateQuery(query: Query): DehydratedQuery {\n  // Extract observer states\n  const observerStates: ObserverState[] = query.observers.map((observer) => ({\n    queryHash: query.queryHash,\n    options: observer.options,\n    // Remove queryFn from observer options to prevent not being able to capture fetch action\n    queryFn: undefined as unknown as QueryFunction,\n  }));\n\n  return {\n    state: {\n      ...query.state,\n      ...(query.state.data !== undefined && {\n        data: query.state.data,\n      }),\n    },\n    queryKey: query.queryKey,\n    queryHash: query.queryHash,\n    ...(query.meta && { meta: query.meta }),\n    observers: observerStates,\n  };\n}\n"
  },
  {
    "path": "src/react-query-external-sync/index.ts",
    "content": "// Export the main hook\nexport { useMySocket as useQuerySyncSocket } from \"./useMySocket\";\n"
  },
  {
    "path": "src/react-query-external-sync/platformUtils.ts",
    "content": "/**\n * Platform and storage utilities that work across different environments (React Native, web, etc.)\n */\n\n// Types\n/**\n * Valid platform operating systems that can be used with React Native\n * @see https://reactnative.dev/docs/platform\n */\nexport type PlatformOS =\n  | \"ios\" // iOS devices (iPhone, iPad, iPod)\n  | \"android\" // Android devices\n  | \"web\" // Web browsers\n  | \"windows\" // Windows desktop/UWP\n  | \"macos\" // macOS desktop\n  | \"native\" // Generic native platform\n  | \"tv\" // Generic TV platform (Android TV, etc)\n  | \"tvos\" // Apple TV\n  | \"visionos\" // Apple Vision Pro / visionOS\n  | \"maccatalyst\"; // iOS apps running on macOS via Mac Catalyst\n\n// Storage interface similar to AsyncStorage\nexport interface StorageInterface {\n  getItem: (key: string) => Promise<string | null>;\n  setItem: (key: string, value: string) => Promise<void>;\n  removeItem: (key: string) => Promise<void>;\n}\n\nlet customStorageImplementation: StorageInterface | null = null;\n\n// Try to detect if we're in a React Native environment\nexport const isReactNative = (): boolean => {\n  try {\n    return (\n      typeof navigator !== \"undefined\" && navigator.product === \"ReactNative\"\n    );\n  } catch (e) {\n    return false;\n  }\n};\n\n/**\n * Get platform-specific URL for socket connection\n * On Android emulator, we need to replace localhost with 10.0.2.2\n */\nexport const getPlatformSpecificURL = (\n  baseUrl: string,\n  platform: PlatformOS\n): string => {\n  try {\n    const url = new URL(baseUrl);\n\n    // For Android emulator, replace hostname with 10.0.2.2\n    if (\n      platform === \"android\" &&\n      (url.hostname === \"localhost\" || url.hostname === \"127.0.0.1\")\n    ) {\n      url.hostname = \"10.0.2.2\";\n      return url.toString();\n    }\n\n    // For other platforms, use as provided\n    return baseUrl;\n  } catch (e) {\n    console.warn(\"Error getting platform-specific URL:\", e);\n    return baseUrl;\n  }\n};\n\n// Storage implementation\nexport const getStorage = (): StorageInterface => {\n  // Return user-defined storage if available\n  if (customStorageImplementation) {\n    return customStorageImplementation;\n  }\n\n  // Try to use React Native AsyncStorage if available\n  if (isReactNative()) {\n    try {\n      // Dynamic import to avoid bundling issues\n      const AsyncStorage =\n        // eslint-disable-next-line @typescript-eslint/no-var-requires\n        require(\"@react-native-async-storage/async-storage\").default;\n      return AsyncStorage;\n    } catch (e) {\n      console.warn(\"Failed to import AsyncStorage from react-native:\", e);\n    }\n  }\n\n  // Fallback to browser localStorage with an async wrapper\n  if (typeof localStorage !== \"undefined\") {\n    return {\n      getItem: async (key: string): Promise<string | null> => {\n        return localStorage.getItem(key);\n      },\n      setItem: async (key: string, value: string): Promise<void> => {\n        localStorage.setItem(key, value);\n      },\n      removeItem: async (key: string): Promise<void> => {\n        localStorage.removeItem(key);\n      },\n    };\n  }\n\n  // Memory fallback if nothing else is available\n  console.warn(\"No persistent storage available, using in-memory storage\");\n  const memoryStorage: Record<string, string> = {};\n  return {\n    getItem: async (key: string): Promise<string | null> => {\n      return memoryStorage[key] || null;\n    },\n    setItem: async (key: string, value: string): Promise<void> => {\n      memoryStorage[key] = value;\n    },\n    removeItem: async (key: string): Promise<void> => {\n      delete memoryStorage[key];\n    },\n  };\n};\n\n/**\n * Set a custom storage implementation\n * Use this if you need to provide your own storage solution\n */\nexport const setCustomStorage = (storage: StorageInterface | null): void => {\n  customStorageImplementation = storage;\n};\n"
  },
  {
    "path": "src/react-query-external-sync/types.ts",
    "content": "import {\n  DefaultError,\n  MutationKey,\n  MutationMeta,\n  MutationScope,\n  MutationState,\n  QueryKey,\n  QueryMeta,\n  QueryObserverOptions,\n  QueryState,\n} from \"@tanstack/react-query\";\n// Define a simplified version of DehydratedState that both versions can work with\nexport interface SimpleDehydratedState {\n  mutations: unknown[];\n  queries: unknown[];\n}\n\nexport interface SyncMessage {\n  type: \"dehydrated-state\";\n  state: DehydratedState;\n  isOnlineManagerOnline: boolean;\n  persistentDeviceId: string;\n}\n\nexport interface DehydratedState {\n  mutations: DehydratedMutation[];\n  queries: DehydratedQuery[];\n}\n\nexport interface DehydratedMutation {\n  mutationId: number;\n  mutationKey?: MutationKey;\n  state: MutationState;\n  meta?: MutationMeta;\n  scope?: MutationScope;\n}\nexport interface DehydratedQuery {\n  queryHash: string;\n  queryKey: QueryKey;\n  state: QueryState;\n  promise?: Promise<unknown>;\n  meta?: QueryMeta;\n  observers: ObserverState[];\n  gcTime?: number;\n}\nexport interface ObserverState<\n  TQueryFnData = unknown,\n  TError = DefaultError,\n  TData = TQueryFnData,\n  TQueryData = TQueryFnData,\n  TQueryKey extends QueryKey = QueryKey\n> {\n  queryHash: string;\n  options: QueryObserverOptions<\n    TQueryFnData,\n    TError,\n    TData,\n    TQueryData,\n    TQueryKey\n  >;\n}\n\nexport interface User {\n  id: string;\n  deviceName: string;\n  deviceId: string; // Persisted device ID\n  platform?: string; // Device platform (iOS, Android, Web)\n  isConnected?: boolean; // Whether the device is currently connected\n  extraDeviceInfo?: string; // json string of additional device information as key-value pairs\n  envVariables?: string; // json string of environment variables from the mobile app\n}\n"
  },
  {
    "path": "src/react-query-external-sync/useMySocket.ts",
    "content": "import { useEffect, useRef, useState } from \"react\";\nimport { io as socketIO, Socket } from \"socket.io-client\";\n\nimport { getPlatformSpecificURL, PlatformOS } from \"./platformUtils\";\nimport { log } from \"./utils/logger\";\n\ninterface Props {\n  deviceName: string; // Unique name to identify the device\n  socketURL: string; // Base URL of the socket server (may be modified based on platform)\n  persistentDeviceId: string | null; // Persistent device ID\n  extraDeviceInfo?: Record<string, string>; // Additional device information as key-value pairs\n  envVariables?: Record<string, string>; // Environment variables from the mobile app\n  platform: PlatformOS; // Platform identifier\n  /**\n   * Enable/disable logging for debugging purposes\n   * @default false\n   */\n  enableLogs?: boolean;\n}\n\n/**\n * Create a singleton socket instance that persists across component renders\n * This way multiple components can share the same socket connection\n */\nlet globalSocketInstance: Socket | null = null;\nlet currentSocketURL = \"\";\n\n/**\n * Hook that handles socket connection for device-dashboard communication\n *\n * Features:\n * - Singleton pattern for socket connection\n * - Platform-specific URL handling for iOS/Android/Web\n * - Device name identification\n * - Connection state tracking\n * - User list management\n */\nexport function useMySocket({\n  deviceName,\n  socketURL,\n  persistentDeviceId,\n  extraDeviceInfo,\n  envVariables,\n  platform,\n  enableLogs = false,\n}: Props) {\n  const socketRef = useRef<Socket | null>(null);\n  const [socket, setSocket] = useState<Socket | null>(null);\n  const [isConnected, setIsConnected] = useState(false);\n  const initialized = useRef(false);\n\n  // For logging clarity\n  const logPrefix = `[${deviceName}]`;\n\n  // Define event handlers at function root level to satisfy linter\n  const onConnect = () => {\n    log(`${logPrefix} Socket connected successfully`, enableLogs);\n    setIsConnected(true);\n  };\n\n  const onDisconnect = (reason: string) => {\n    log(`${logPrefix} Socket disconnected. Reason: ${reason}`, enableLogs);\n    setIsConnected(false);\n  };\n\n  const onConnectError = (error: Error) => {\n    log(\n      `${logPrefix} Socket connection error: ${error.message}`,\n      enableLogs,\n      \"error\"\n    );\n  };\n\n  const onConnectTimeout = () => {\n    log(`${logPrefix} Socket connection timeout`, enableLogs, \"error\");\n  };\n\n  // Main socket initialization - runs only once\n  useEffect(() => {\n    // Wait until we have a persistent device ID\n    if (!persistentDeviceId) {\n      return;\n    }\n\n    // Only initialize socket once to prevent multiple connections\n    if (initialized.current) {\n      return;\n    }\n\n    initialized.current = true;\n\n    // Get the platform-specific URL\n    const platformUrl = getPlatformSpecificURL(socketURL, platform);\n    currentSocketURL = platformUrl;\n\n    log(\n      `${logPrefix} Platform: ${platform}, using URL: ${platformUrl}`,\n      enableLogs\n    );\n\n    try {\n      // Use existing global socket or create a new one\n      if (!globalSocketInstance) {\n        log(\n          `${logPrefix} Creating new socket instance to ${platformUrl}`,\n          enableLogs\n        );\n        globalSocketInstance = socketIO(platformUrl, {\n          autoConnect: true,\n          query: {\n            deviceName,\n            deviceId: persistentDeviceId,\n            platform,\n            extraDeviceInfo: JSON.stringify(extraDeviceInfo),\n            envVariables: JSON.stringify(envVariables),\n          },\n          reconnection: false,\n          transports: [\"websocket\"], // Prefer websocket transport for React Native\n        });\n      } else {\n        log(\n          `${logPrefix} Reusing existing socket instance to ${platformUrl}`,\n          enableLogs\n        );\n      }\n\n      socketRef.current = globalSocketInstance;\n      setSocket(socketRef.current);\n\n      // Setup error event listener\n      socketRef.current.on(\"connect_error\", onConnectError);\n      socketRef.current.on(\"connect_timeout\", onConnectTimeout);\n\n      // Check initial connection state\n      if (socketRef.current.connected) {\n        setIsConnected(true);\n        log(`${logPrefix} Socket already connected on init`, enableLogs);\n      }\n\n      // Set up event handlers\n      socketRef.current.on(\"connect\", onConnect);\n      socketRef.current.on(\"disconnect\", onDisconnect);\n\n      // Clean up event listeners on unmount but don't disconnect\n      return () => {\n        if (socketRef.current) {\n          log(`${logPrefix} Cleaning up socket event listeners`, enableLogs);\n          socketRef.current.off(\"connect\", onConnect);\n          socketRef.current.off(\"disconnect\", onDisconnect);\n          socketRef.current.off(\"connect_error\", onConnectError);\n          socketRef.current.off(\"connect_timeout\", onConnectTimeout);\n          // Don't disconnect socket on component unmount\n          // We want it to remain connected for the app's lifetime\n        }\n      };\n    } catch (error) {\n      log(\n        `${logPrefix} Failed to initialize socket: ${error}`,\n        enableLogs,\n        \"error\"\n      );\n    }\n  }, [persistentDeviceId]);\n\n  // Update the socket query parameters when deviceName changes\n  useEffect(() => {\n    if (\n      socketRef.current &&\n      socketRef.current.io.opts.query &&\n      persistentDeviceId\n    ) {\n      log(`${logPrefix} Updating device name in socket connection`, enableLogs);\n      socketRef.current.io.opts.query = {\n        ...socketRef.current.io.opts.query,\n        deviceName,\n        deviceId: persistentDeviceId,\n        platform,\n      };\n    }\n  }, [deviceName, logPrefix, persistentDeviceId, platform, enableLogs]);\n\n  // Update the socket URL when socketURL changes\n  useEffect(() => {\n    // Get platform-specific URL for the new socketURL\n    const platformUrl = getPlatformSpecificURL(socketURL, platform);\n\n    // Compare with last known URL to avoid direct property access\n    if (\n      socketRef.current &&\n      currentSocketURL !== platformUrl &&\n      persistentDeviceId\n    ) {\n      log(\n        `${logPrefix} Socket URL changed from ${currentSocketURL} to ${platformUrl}`,\n        enableLogs\n      );\n\n      try {\n        // Only recreate socket if URL actually changed\n        socketRef.current.disconnect();\n        currentSocketURL = platformUrl;\n\n        log(\n          `${logPrefix} Creating new socket connection to ${platformUrl}`,\n          enableLogs\n        );\n        globalSocketInstance = socketIO(platformUrl, {\n          autoConnect: true,\n          query: {\n            deviceName,\n            deviceId: persistentDeviceId,\n            platform,\n            extraDeviceInfo: JSON.stringify(extraDeviceInfo),\n            envVariables: JSON.stringify(envVariables),\n          },\n          reconnection: false,\n          transports: [\"websocket\"], // Prefer websocket transport for React Native\n        });\n\n        socketRef.current = globalSocketInstance;\n        setSocket(socketRef.current);\n      } catch (error) {\n        log(\n          `${logPrefix} Failed to update socket connection: ${error}`,\n          enableLogs,\n          \"error\"\n        );\n      }\n    }\n  }, [\n    socketURL,\n    deviceName,\n    logPrefix,\n    persistentDeviceId,\n    platform,\n    enableLogs,\n  ]);\n\n  /**\n   * Manually connect to the socket server\n   */\n  function connect() {\n    if (socketRef.current && !socketRef.current.connected) {\n      log(`${logPrefix} Manually connecting to socket server`, enableLogs);\n      socketRef.current.connect();\n    }\n  }\n\n  /**\n   * Manually disconnect from the socket server\n   */\n  function disconnect() {\n    if (socketRef.current && socketRef.current.connected) {\n      log(`${logPrefix} Manually disconnecting from socket server`, enableLogs);\n      socketRef.current.disconnect();\n    }\n  }\n\n  return {\n    socket,\n    connect,\n    disconnect,\n    isConnected,\n  };\n}\n"
  },
  {
    "path": "src/react-query-external-sync/useSyncQueries.ts",
    "content": "import { useEffect, useRef } from \"react\";\nimport type { QueryKey } from \"@tanstack/query-core\";\nimport { onlineManager, QueryClient } from \"@tanstack/react-query\";\n\nimport { Dehydrate } from \"./hydration\";\nimport { SyncMessage } from \"./types\";\nimport { useMySocket } from \"./useMySocket\";\nimport { PlatformOS } from \"./platformUtils\";\nimport { log } from \"./utils/logger\";\n\n/**\n * Query actions that can be performed on a query.\n * These actions are used to synchronize query state between devices and the dashboard.\n */\ntype QueryActions =\n  // Regular query actions\n  | \"ACTION-REFETCH\" // Refetch a query without invalidating it\n  | \"ACTION-INVALIDATE\" // Invalidate a query and trigger a refetch\n  | \"ACTION-RESET\" // Reset a query to its initial state\n  | \"ACTION-REMOVE\" // Remove a query from the cache\n  | \"ACTION-DATA-UPDATE\" // Update a query's data manually\n  // Error handling actions\n  | \"ACTION-TRIGGER-ERROR\" // Manually trigger an error state\n  | \"ACTION-RESTORE-ERROR\" // Restore from an error state\n  // Loading state actions\n  | \"ACTION-TRIGGER-LOADING\" // Manually trigger a loading state\n  | \"ACTION-RESTORE-LOADING\" // Restore from a loading state\n  // Online status actions\n  | \"ACTION-ONLINE-MANAGER-ONLINE\" // Set online manager to online\n  | \"ACTION-ONLINE-MANAGER-OFFLINE\" // Set online manager to offline\n  // Internal action\n  | \"success\" // Internal success action\n  | \"ACTION-CLEAR-MUTATION-CACHE\" // Clear the mutation cache\n  | \"ACTION-CLEAR-QUERY-CACHE\"; // Clear the query cache\n\n/**\n * Message structure for query actions between dashboard and devices\n */\ninterface QueryActionMessage {\n  queryHash: string; // Unique hash of the query\n  queryKey: QueryKey; // Key array used to identify the query\n  data: unknown; // Data payload (if applicable)\n  action: QueryActions; // Action to perform\n  deviceId: string; // Device to target\n}\n\n/**\n * Message structure for online manager actions from dashboard to devices\n */\ninterface OnlineManagerMessage {\n  action: \"ACTION-ONLINE-MANAGER-ONLINE\" | \"ACTION-ONLINE-MANAGER-OFFLINE\";\n  targetDeviceId: string; // Device ID to target ('All' || device)\n}\n\n/**\n * Determines if a message should be processed by the current device\n */\ninterface ShouldProcessMessageProps {\n  targetDeviceId: string;\n  currentDeviceId: string;\n}\nfunction shouldProcessMessage({\n  targetDeviceId,\n  currentDeviceId,\n}: ShouldProcessMessageProps): boolean {\n  return targetDeviceId === currentDeviceId || targetDeviceId === \"All\";\n}\n\n/**\n * Verifies if the React Query version is compatible with dev tools\n */\nfunction checkVersion(queryClient: QueryClient) {\n  // Basic version check\n  const version = (\n    queryClient as unknown as {\n      getDefaultOptions?: () => { queries?: { version?: unknown } };\n    }\n  ).getDefaultOptions?.()?.queries?.version;\n  if (\n    version &&\n    !version.toString().startsWith(\"4\") &&\n    !version.toString().startsWith(\"5\")\n  ) {\n    log(\n      \"This version of React Query has not been tested with the dev tools plugin. Some features might not work as expected.\",\n      true,\n      \"warn\"\n    );\n  }\n}\n\ninterface useSyncQueriesExternalProps {\n  queryClient: QueryClient;\n  deviceName: string;\n  /**\n   * A unique identifier for this device that persists across app restarts.\n   * This is crucial for proper device tracking, especially if you have multiple devices of the same type.\n   * If you only have one iOS and one Android device, you can use 'ios' and 'android'.\n   * For multiple devices of the same type, ensure this ID is unique and persistent.\n   */\n  deviceId: string;\n  extraDeviceInfo?: Record<string, string>; // Additional device information as key-value pairs\n  envVariables?: Record<string, string>; // Environment variables from the mobile app\n  socketURL: string;\n  platform: PlatformOS; // Required platform\n  /**\n   * Enable/disable logging for debugging purposes\n   * @default false\n   */\n  enableLogs?: boolean;\n}\n\n/**\n * Hook used by mobile devices to sync query state with the external dashboard\n *\n * Handles:\n * - Connection to the socket server\n * - Responding to dashboard requests\n * - Processing query actions from the dashboard\n * - Sending query state updates to the dashboard\n */\nexport function useSyncQueriesExternal({\n  queryClient,\n  deviceName,\n  deviceId,\n  extraDeviceInfo,\n  envVariables,\n  socketURL,\n  platform,\n  enableLogs = false,\n}: useSyncQueriesExternalProps) {\n  // ==========================================================\n  // Validate deviceId\n  // ==========================================================\n  if (!deviceId?.trim()) {\n    throw new Error(\n      `[${deviceName}] deviceId is required and must not be empty. This ID must persist across app restarts, especially if you have multiple devices of the same type. If you only have one iOS and one Android device, you can use 'ios' and 'android'.`\n    );\n  }\n\n  // ==========================================================\n  // Persistent device ID - used to identify this device\n  // across app restarts\n  // ==========================================================\n  const logPrefix = `[${deviceName}]`;\n  // ==========================================================\n  // Socket connection - Handles connection to the socket server and\n  // event listeners for the socket server\n  // ==========================================================\n  const { connect, disconnect, isConnected, socket } = useMySocket({\n    deviceName,\n    socketURL,\n    persistentDeviceId: deviceId,\n    extraDeviceInfo,\n    envVariables,\n    platform,\n    enableLogs,\n  });\n\n  // Use a ref to track previous connection state to avoid duplicate logs\n  const prevConnectedRef = useRef(false);\n\n  useEffect(() => {\n    checkVersion(queryClient);\n\n    // Only log connection state changes to reduce noise\n    if (prevConnectedRef.current !== isConnected) {\n      if (!isConnected) {\n        log(`${logPrefix} Not connected to external dashboard`, enableLogs);\n      } else {\n        log(`${deviceName} Connected to external dashboard`, enableLogs);\n      }\n      prevConnectedRef.current = isConnected;\n    }\n\n    // Don't proceed with setting up event handlers if not connected\n    if (!isConnected || !socket) {\n      return;\n    }\n\n    // ==========================================================\n    // Event Handlers\n    // ==========================================================\n\n    // ==========================================================\n    // Handle initial state requests from dashboard\n    // ==========================================================\n    const initialStateSubscription = socket.on(\"request-initial-state\", () => {\n      if (!deviceId) {\n        log(`${logPrefix} No persistent device ID found`, enableLogs, \"warn\");\n        return;\n      }\n      log(`${logPrefix} Dashboard is requesting initial state`, enableLogs);\n      const dehydratedState = Dehydrate(queryClient as unknown as QueryClient);\n      const syncMessage: SyncMessage = {\n        type: \"dehydrated-state\",\n        state: dehydratedState,\n        isOnlineManagerOnline: onlineManager.isOnline(),\n        persistentDeviceId: deviceId,\n      };\n      socket.emit(\"query-sync\", syncMessage);\n      log(\n        `[${deviceName}] Sent initial state to dashboard (${dehydratedState.queries.length} queries)`,\n        enableLogs\n      );\n    });\n\n    // ==========================================================\n    // Online manager handler - Handle device internet connection state changes\n    // ==========================================================\n    const onlineManagerSubscription = socket.on(\n      \"online-manager\",\n      (message: OnlineManagerMessage) => {\n        const { action, targetDeviceId } = message;\n        if (!deviceId) {\n          log(`${logPrefix} No persistent device ID found`, enableLogs, \"warn\");\n          return;\n        }\n        // Only process if this message targets the current device\n        if (\n          !shouldProcessMessage({\n            targetDeviceId: targetDeviceId,\n            currentDeviceId: deviceId,\n          })\n        ) {\n          return;\n        }\n\n        log(\n          `[${deviceName}] Received online-manager action: ${action}`,\n          enableLogs\n        );\n\n        switch (action) {\n          case \"ACTION-ONLINE-MANAGER-ONLINE\": {\n            log(`${logPrefix} Set online state: ONLINE`, enableLogs);\n            onlineManager.setOnline(true);\n            break;\n          }\n          case \"ACTION-ONLINE-MANAGER-OFFLINE\": {\n            log(`${logPrefix} Set online state: OFFLINE`, enableLogs);\n            onlineManager.setOnline(false);\n            break;\n          }\n        }\n      }\n    );\n\n    // ==========================================================\n    // Query Actions handler - Process actions from the dashboard\n    // ==========================================================\n    const queryActionSubscription = socket.on(\n      \"query-action\",\n      (message: QueryActionMessage) => {\n        const { queryHash, queryKey, data, action, deviceId } = message;\n        if (!deviceId) {\n          log(\n            `[${deviceName}] No persistent device ID found`,\n            enableLogs,\n            \"warn\"\n          );\n          return;\n        }\n        // Skip if not targeted at this device\n        if (\n          !shouldProcessMessage({\n            targetDeviceId: deviceId,\n            currentDeviceId: deviceId,\n          })\n        ) {\n          return;\n        }\n\n        log(\n          `${logPrefix} Received query action: ${action} for query ${queryHash}`,\n          enableLogs\n        );\n\n        const activeQuery = queryClient.getQueryCache().get(queryHash);\n        if (!activeQuery) {\n          log(\n            `${logPrefix} Query with hash ${queryHash} not found`,\n            enableLogs,\n            \"warn\"\n          );\n          return;\n        }\n\n        switch (action) {\n          case \"ACTION-DATA-UPDATE\": {\n            log(`${logPrefix} Updating data for query:`, enableLogs);\n            queryClient.setQueryData(queryKey, data, {\n              updatedAt: Date.now(),\n            });\n            break;\n          }\n\n          case \"ACTION-TRIGGER-ERROR\": {\n            log(`${logPrefix} Triggering error state for query:`, enableLogs);\n            const error = new Error(\"Unknown error from devtools\");\n\n            const __previousQueryOptions = activeQuery.options;\n            activeQuery.setState({\n              status: \"error\",\n              error,\n              fetchMeta: {\n                ...activeQuery.state.fetchMeta,\n                // @ts-expect-error This does exist\n                __previousQueryOptions,\n              },\n            });\n            break;\n          }\n          case \"ACTION-RESTORE-ERROR\": {\n            log(\n              `${logPrefix} Restoring from error state for query:`,\n              enableLogs\n            );\n            queryClient.resetQueries(activeQuery);\n            break;\n          }\n          case \"ACTION-TRIGGER-LOADING\": {\n            if (!activeQuery) return;\n            log(`${logPrefix} Triggering loading state for query:`, enableLogs);\n            const __previousQueryOptions = activeQuery.options;\n            // Trigger a fetch in order to trigger suspense as well.\n            activeQuery.fetch({\n              ...__previousQueryOptions,\n              queryFn: () => {\n                return new Promise(() => {\n                  // Never resolve - simulates perpetual loading\n                });\n              },\n              gcTime: -1,\n            });\n            activeQuery.setState({\n              data: undefined,\n              status: \"pending\",\n              fetchMeta: {\n                ...activeQuery.state.fetchMeta,\n                // @ts-expect-error This does exist\n                __previousQueryOptions,\n              },\n            });\n            break;\n          }\n          case \"ACTION-RESTORE-LOADING\": {\n            log(\n              `${logPrefix} Restoring from loading state for query:`,\n              enableLogs\n            );\n            const previousState = activeQuery.state;\n            const previousOptions = activeQuery.state.fetchMeta\n              ? (\n                  activeQuery.state.fetchMeta as unknown as {\n                    __previousQueryOptions: unknown;\n                  }\n                ).__previousQueryOptions\n              : null;\n\n            activeQuery.cancel({ silent: true });\n            activeQuery.setState({\n              ...previousState,\n              fetchStatus: \"idle\",\n              fetchMeta: null,\n            });\n\n            if (previousOptions) {\n              activeQuery.fetch(previousOptions);\n            }\n            break;\n          }\n          case \"ACTION-RESET\": {\n            log(`${logPrefix} Resetting query:`, enableLogs);\n            queryClient.resetQueries(activeQuery);\n            break;\n          }\n          case \"ACTION-REMOVE\": {\n            log(`${logPrefix} Removing query:`, enableLogs);\n            queryClient.removeQueries(activeQuery);\n            break;\n          }\n          case \"ACTION-REFETCH\": {\n            log(`${logPrefix} Refetching query:`, enableLogs);\n            const promise = activeQuery.fetch();\n            promise.catch((error) => {\n              // Log fetch errors but don't propagate them\n              log(\n                `[${deviceName}] Refetch error for ${queryHash}:`,\n                enableLogs,\n                \"error\"\n              );\n            });\n            break;\n          }\n          case \"ACTION-INVALIDATE\": {\n            log(`${logPrefix} Invalidating query:`, enableLogs);\n            queryClient.invalidateQueries(activeQuery);\n            break;\n          }\n          case \"ACTION-ONLINE-MANAGER-ONLINE\": {\n            log(`${logPrefix} Setting online state: ONLINE`, enableLogs);\n            onlineManager.setOnline(true);\n            break;\n          }\n          case \"ACTION-ONLINE-MANAGER-OFFLINE\": {\n            log(`${logPrefix} Setting online state: OFFLINE`, enableLogs);\n            onlineManager.setOnline(false);\n            break;\n          }\n        }\n      }\n    );\n\n    // ==========================================================\n    // Subscribe to query changes and sync to dashboard\n    // ==========================================================\n    const unsubscribe = queryClient.getQueryCache().subscribe(() => {\n      if (!deviceId) {\n        log(`${logPrefix} No persistent device ID found`, enableLogs, \"warn\");\n        return;\n      }\n      // Dehydrate the current state\n      const dehydratedState = Dehydrate(queryClient as unknown as QueryClient);\n\n      // Create sync message\n      const syncMessage: SyncMessage = {\n        type: \"dehydrated-state\",\n        state: dehydratedState,\n        isOnlineManagerOnline: onlineManager.isOnline(),\n        persistentDeviceId: deviceId,\n      };\n\n      // Send message to dashboard\n      socket.emit(\"query-sync\", syncMessage);\n    });\n\n    // ==========================================================\n    // Cleanup function to unsubscribe from all events\n    // ==========================================================\n    return () => {\n      log(`${logPrefix} Cleaning up event listeners`, enableLogs);\n      queryActionSubscription?.off();\n      initialStateSubscription?.off();\n      onlineManagerSubscription?.off();\n      unsubscribe();\n    };\n  }, [queryClient, socket, deviceName, isConnected, deviceId, enableLogs]);\n\n  return { connect, disconnect, isConnected, socket };\n}\n"
  },
  {
    "path": "src/react-query-external-sync/utils/logger.ts",
    "content": "/**\n * Log types supported by the logger\n */\nexport type LogType = \"log\" | \"warn\" | \"error\";\n\n/**\n * Helper function for controlled logging\n * Only shows logs when enableLogs is true\n * Always shows warnings and errors regardless of enableLogs setting\n */\nexport function log(\n  message: string,\n  enableLogs: boolean,\n  type: LogType = \"log\"\n): void {\n  if (!enableLogs && type === \"log\") return;\n  switch (type) {\n    case \"warn\":\n      console.warn(message);\n      break;\n    case \"error\":\n      console.error(message);\n      break;\n    default:\n      console.log(message);\n  }\n}\n"
  },
  {
    "path": "src/renderer.ts",
    "content": "/**\n * This file will automatically be loaded by vite and run in the \"renderer\" context.\n * To learn more about the differences between the \"main\" and the \"renderer\" context in\n * Electron, visit:\n *\n * https://electronjs.org/docs/tutorial/process-model\n *\n * By default, Node.js integration in this file is disabled. When enabling Node.js integration\n * in a renderer process, please be aware of potential security implications. You can read\n * more about security risks here:\n *\n * https://electronjs.org/docs/tutorial/security\n *\n * To enable Node.js integration in this file, open up `main.ts` and enable the `nodeIntegration`\n * flag:\n *\n * ```\n *  // Create the browser window.\n *  mainWindow = new BrowserWindow({\n *    width: 800,\n *    height: 600,\n *    webPreferences: {\n *      nodeIntegration: true\n *    }\n *  });\n * ```\n */\n\nimport './index.css';\n\nconsole.log('👋 This message is being logged by \"renderer.ts\", included via Vite');\n"
  },
  {
    "path": "src/renderer.tsx",
    "content": "import React from \"react\";\nimport { createRoot } from \"react-dom/client\";\nimport { App } from \"./components/App\";\nimport \"./index.css\";\n\n// Initialize React\nconst container = document.getElementById(\"app\");\nif (!container) {\n  throw new Error(\"Failed to find the root element\");\n}\nconst root = createRoot(container);\nroot.render(<App />);\n\nconsole.log(\"👋 React renderer is running!\");\n"
  },
  {
    "path": "src/server/socketHandle.ts",
    "content": "import { Socket, Server as SocketIOServer } from \"socket.io\";\n// Replace the problematic import with a direct type\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype DefaultEventsMap = Record<string, (...args: any[]) => void>;\nimport { User } from \"../components/external-dash/types/User\";\nimport { SyncMessage } from \"../components/external-dash/shared/types\";\nimport {\n  OnlineManagerMessage,\n  QueryActionMessage,\n  QueryRequestInitialStateMessage,\n} from \"../components/external-dash/useSyncQueriesWeb\";\n\ninterface Props {\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  io: SocketIOServer<DefaultEventsMap, DefaultEventsMap, DefaultEventsMap, any>;\n}\n\n/**\n * Global state for connected users and dashboard clients\n */\nlet users = [] as User[]; // Connected users\nconst allDevices = [] as User[]; // All devices that have ever connected (history)\nlet dashboardClients = [] as string[]; // Dashboard client IDs\n// Map to track socketId to deviceId for reconnection handling\nconst deviceIdMap = new Map<string, string>();\n// Map to track active socket connections by deviceId\nconst activeConnectionsMap = new Map<string, string>();\n\n/**\n * Server-side socket handler that manages:\n * - Communication between devices and dashboard\n * - User connection tracking\n * - Query state synchronization\n * - Action forwarding\n */\nexport default function socketHandle({ io }: Props) {\n  const LOG_PREFIX = \"[SERVER]\";\n\n  /**\n   * Handle client disconnection\n   */\n  function handleClose(id: string) {\n    // Find user before removing for logging\n    const user = users.find((user) => user.id === id);\n\n    // Get the device ID from our map if it exists\n    const deviceId = deviceIdMap.get(id);\n\n    // Only remove from users array if there's no persistent device ID\n    // or if there are no other connections with the same device ID\n    if (\n      !deviceId ||\n      !users.some((u) => u.id !== id && u.deviceId === deviceId)\n    ) {\n      users = users.filter((user: User) => user.id !== id);\n\n      console.log(\n        `${LOG_PREFIX} Client disconnected - ID: ${id}, Name: ${\n          user?.deviceName || \"Unknown\"\n        } - Removing user record`\n      );\n\n      // Update the connection status in allDevices array\n      if (deviceId) {\n        const deviceIndex = allDevices.findIndex(\n          (u) => u.deviceId === deviceId\n        );\n        if (deviceIndex !== -1) {\n          allDevices[deviceIndex].isConnected = false;\n          // Remove the active connection mapping\n          activeConnectionsMap.delete(deviceId);\n        }\n      }\n    } else {\n      console.log(\n        `${LOG_PREFIX} Client disconnected - ID: ${id}, Name: ${\n          user?.deviceName || \"Unknown\"\n        } - Keeping user record for same device with ID: ${deviceId}`\n      );\n    }\n\n    // Remove from deviceIdMap\n    deviceIdMap.delete(id);\n\n    // Remove from dashboard clients if it exists\n    const wasDashboard = dashboardClients.includes(id);\n    dashboardClients = dashboardClients.filter((clientId) => clientId !== id);\n\n    console.log(\n      `${LOG_PREFIX} Client disconnected - ID: ${id}, Name: ${\n        user?.deviceName || \"Unknown\"\n      }, Was Dashboard: ${wasDashboard}`\n    );\n\n    // Also send the complete device history list (excluding dashboard)\n    io.emit(\n      \"all-devices-update\",\n      allDevices.filter((device) => device.deviceName !== \"Dashboard\")\n    );\n    console.log(\n      `${LOG_PREFIX} Updated users list: ${users\n        .map((u) => u.deviceName)\n        .join(\", \")}`\n    );\n  }\n\n  // ==========================================================\n  // Add a new user to the connected users list with unique naming\n  // ==========================================================\n  function addNewUser({\n    id,\n    deviceName,\n    deviceId,\n    platform,\n    extraDeviceInfo,\n    envVariables,\n  }: User) {\n    // Check if we're reconnecting an existing device by deviceId\n    if (deviceId) {\n      // Store the mapping for future reference\n      deviceIdMap.set(id, deviceId);\n      // Track active connection\n      activeConnectionsMap.set(deviceId, id);\n\n      // Check if we already have a user with this deviceId\n      const existingUserIndex = users.findIndex(\n        (user) => user.deviceId === deviceId\n      );\n\n      // Check if this device is in our history\n      const existingDeviceIndex = allDevices.findIndex(\n        (device) => device.deviceId === deviceId\n      );\n      // If the user exists, update the user with the new socket id and potentially new info\n      if (existingUserIndex !== -1) {\n        console.log(\n          `${LOG_PREFIX} Reconnecting existing device - ID: ${id}, Name: ${deviceName}, DeviceId: ${deviceId}, Platform: ${\n            platform || \"Unknown\"\n          }, ExtraDeviceInfo: ${extraDeviceInfo}`\n        );\n\n        // Update the existing user with the new socket id and potentially new info\n        users[existingUserIndex].id = id;\n        if (platform) {\n          users[existingUserIndex].platform = platform;\n        }\n        if (extraDeviceInfo) {\n          users[existingUserIndex].extraDeviceInfo = extraDeviceInfo;\n        }\n        if (envVariables) {\n          users[existingUserIndex].envVariables = envVariables;\n        }\n\n        // Add isConnected status\n        users[existingUserIndex].isConnected = true;\n\n        // Update in history list too if it exists\n        if (existingDeviceIndex !== -1) {\n          allDevices[existingDeviceIndex].id = id;\n          allDevices[existingDeviceIndex].isConnected = true;\n          if (platform) {\n            allDevices[existingDeviceIndex].platform = platform;\n          }\n          if (extraDeviceInfo) {\n            allDevices[existingDeviceIndex].extraDeviceInfo = extraDeviceInfo;\n          }\n          if (envVariables) {\n            allDevices[existingDeviceIndex].envVariables = envVariables;\n          }\n        } else if (deviceName !== \"Dashboard\") {\n          // Add to history if not yet present and not a dashboard\n          allDevices.push({\n            id,\n            deviceName,\n            deviceId,\n            platform,\n            isConnected: true,\n            extraDeviceInfo,\n            envVariables,\n          });\n        }\n\n        // Notify all clients of updated device list\n        io.emit(\n          \"all-devices-update\",\n          allDevices.filter((device) => device.deviceName !== \"Dashboard\")\n        );\n        console.log(\n          `${LOG_PREFIX} Updated users list: ${users\n            .map((u) => u.deviceName)\n            .join(\", \")}`\n        );\n\n        return;\n      }\n    }\n\n    // Handle duplicate device names by adding incrementing numbers\n    const extractNumber = (name: string) => {\n      const match = name.match(/(\\d+)$/);\n      return match ? parseInt(match[1], 10) : 1;\n    };\n\n    let highestNumber = 0;\n    users.forEach((user) => {\n      if (user.deviceName.startsWith(deviceName)) {\n        const number = extractNumber(user.deviceName);\n        if (number >= highestNumber) {\n          highestNumber = number;\n        }\n      }\n    });\n\n    // Add a number suffix if this is a duplicate name\n    if (highestNumber > 0) {\n      deviceName = `${deviceName} #${highestNumber + 1}`;\n    }\n\n    // Add user to the list if ID is valid\n    if (id) {\n      // Create user object with connection status\n      const newUser = {\n        id: id,\n        deviceName: deviceName,\n        deviceId: deviceId,\n        platform: platform,\n        isConnected: true,\n        extraDeviceInfo: extraDeviceInfo,\n        envVariables: envVariables,\n      };\n\n      users.push(newUser);\n\n      // Add to history if it's not a dashboard\n      if (deviceName !== \"Dashboard\" && deviceId) {\n        // Check if already in history\n        const existingDeviceIndex = allDevices.findIndex(\n          (device) => device.deviceId === deviceId\n        );\n\n        if (existingDeviceIndex !== -1) {\n          // Update existing record with all fields including extraDeviceInfo\n          allDevices[existingDeviceIndex] = { ...newUser };\n        } else {\n          // Add new device to history with all fields\n          allDevices.push({ ...newUser });\n        }\n      }\n\n      console.log(\n        `${LOG_PREFIX} New user connected - ID: ${id}, Name: ${deviceName}${\n          deviceId ? `, DeviceId: ${deviceId}` : \"\"\n        }${platform ? `, Platform: ${platform}` : \"\"}`\n      );\n\n      // Check if this is a dashboard client\n      if (deviceName === \"Dashboard\" && id) {\n        dashboardClients.push(id);\n        console.log(`${LOG_PREFIX} Registered dashboard client with ID: ${id}`);\n      }\n\n      // Notify all clients of updated device list\n      io.emit(\n        \"all-devices-update\",\n        allDevices.filter((device) => device.deviceName !== \"Dashboard\")\n      );\n      console.log(\n        `${LOG_PREFIX} Updated device list: ${users\n          .map((u) => u.deviceName)\n          .join(\", \")}`\n      );\n    }\n  }\n  // Function that returns device from all devies based off deviceId\n  function getDeviceFromDeviceId(deviceId: string) {\n    return allDevices.find((device) => device.deviceId === deviceId);\n  }\n  // ==========================================================\n  // Handle generic user messages (for debugging purposes)\n  // ==========================================================\n  function handleUserMessage(message: string, deviceName: string) {\n    console.log(`${LOG_PREFIX} Message from ${deviceName}: ${message}`);\n    io.emit(\"message\", `Device ${deviceName} sent message: ${message}`);\n  }\n\n  // ==========================================================\n  // Forward query sync messages only to dashboard clients\n  // ==========================================================\n  function forwardQuerySyncToDashboards(message: SyncMessage) {\n    const device = getDeviceFromDeviceId(message.persistentDeviceId);\n    if (dashboardClients.length === 0) {\n      console.log(\n        `${LOG_PREFIX} No dashboard clients connected to forward query sync to`\n      );\n      return;\n    }\n\n    console.log(\n      `${LOG_PREFIX} Forwarding query sync from ${device?.deviceName} to ${dashboardClients.length} dashboard(s)`\n    );\n\n    // First try to send directly to each dashboard client (more reliable)\n    let sentSuccessfully = false;\n\n    // Log the current socket connections for debugging\n    const socketIds = Array.from(io.sockets.sockets.keys());\n    console.log(\n      `${LOG_PREFIX} Current socket connections: ${socketIds.join(\", \")}`\n    );\n\n    // Check if dashboard clients are still valid\n    dashboardClients = dashboardClients.filter((id) =>\n      io.sockets.sockets.has(id)\n    );\n\n    // First attempt: try to directly send to each dashboard client\n    for (const dashboardId of dashboardClients) {\n      try {\n        const socket = io.sockets.sockets.get(dashboardId);\n        if (socket) {\n          console.log(\n            `${LOG_PREFIX} Sending directly to dashboard ${dashboardId}`\n          );\n          socket.emit(\"query-sync\", message);\n          sentSuccessfully = true;\n          console.log(\n            `${LOG_PREFIX} Successfully sent to dashboard ${dashboardId}`\n          );\n        } else {\n          console.log(\n            `${LOG_PREFIX} Dashboard socket ${dashboardId} not found`\n          );\n        }\n      } catch (error) {\n        console.error(\n          `${LOG_PREFIX} Error sending to dashboard ${dashboardId}:`,\n          error\n        );\n      }\n    }\n\n    // Fallback: If direct sending failed, try broadcasting to all clients\n    // The dashboard clients will filter based on their selected device\n    if (!sentSuccessfully) {\n      console.log(\n        `${LOG_PREFIX} Fallback: Broadcasting query sync to all clients`\n      );\n      io.emit(\"query-sync\", message);\n    }\n\n    // Also log detailed message information for debugging\n    console.log(`${LOG_PREFIX} Message details:`, {\n      deviceName: device?.deviceName,\n      type: message.type,\n      persistentDeviceId: message.persistentDeviceId,\n      queriesCount: message.state.queries.length,\n      mutationsCount: message.state.mutations.length,\n    });\n  }\n\n  /**\n   * Helper function to find target user(s) and execute an action on them\n   * @param targetDevice The target device name or \"All\"\n   * @param action Function to execute on each target user\n   * @param actionName Description of the action for logging\n   */\n  function withTargetUsers(\n    targetDeviceId: string,\n    action: (user: User) => void,\n    actionName: string\n  ) {\n    const deviceName = getDeviceFromDeviceId(targetDeviceId)?.deviceName;\n    // Skip if the targetDevice indicates there are no devices available\n    if (targetDeviceId === \"No devices available\") {\n      console.log(\n        `${LOG_PREFIX} Skipping ${actionName} - No devices available`\n      );\n      return;\n    }\n\n    if (targetDeviceId === \"All\") {\n      console.log(`${LOG_PREFIX} Broadcasting ${actionName} to all devices`);\n      // Broadcast to all non-dashboard clients\n      const deviceUsers = users.filter(\n        (user) => user.deviceName !== \"Dashboard\"\n      );\n      console.log(\n        `${LOG_PREFIX} Sending to ${deviceUsers.length} devices: ${deviceUsers\n          .map((u) => u.deviceName)\n          .join(\", \")}`\n      );\n\n      deviceUsers.forEach(action);\n    } else {\n      // Find the target device and send only to that device\n      const targetUser = users.find((user) => user.deviceId === targetDeviceId);\n\n      if (targetUser) {\n        console.log(\n          `${LOG_PREFIX} Sending to target device ${targetUser.deviceName} (${targetUser.deviceId}) targetDeviceId: ${targetDeviceId}`\n        );\n        action(targetUser);\n      } else {\n        console.log(\n          `${LOG_PREFIX} Target device not found - DeviceId: ${targetDeviceId}, DeviceName: ${deviceName}`\n        );\n      }\n    }\n  }\n\n  // ==========================================================\n  // Main socket connection handler\n  // ==========================================================\n  io.on(\"connection\", (socket: Socket) => {\n    // Get the query parameters from the handshake\n    const { deviceName, deviceId, platform, extraDeviceInfo, envVariables } =\n      socket.handshake.query as {\n        deviceName: string | undefined;\n        deviceId: string | undefined;\n        platform: string | undefined;\n        extraDeviceInfo: string | undefined;\n        envVariables: string | undefined;\n      };\n    console.log(\n      `${LOG_PREFIX} New connection - ID: ${socket.id}, Name: ${\n        deviceName || \"Unknown Device\"\n      }${deviceId ? `, DeviceId: ${deviceId}` : \"\"}${\n        platform ? `, Platform: ${platform}` : \"\"\n      }${extraDeviceInfo ? `, ExtraDeviceInfo: ${extraDeviceInfo}` : \"\"}${\n        envVariables ? `, EnvVariables: ${envVariables}` : \"\"\n      }`\n    );\n\n    // ==========================================================\n    // Add new user\n    // ==========================================================\n    addNewUser({\n      id: socket.id,\n      deviceName: deviceName || \"Unknown Device Name\",\n      deviceId: deviceId,\n      platform: platform,\n      extraDeviceInfo: extraDeviceInfo,\n      envVariables: envVariables,\n    });\n\n    // ==========================================================\n    // Event Handlers\n    // ==========================================================\n\n    // ==========================================================\n    // Handle the disconnect event\n    // ==========================================================\n    socket.on(\"disconnect\", () => handleClose(socket.id));\n\n    // ==========================================================\n    // Handle debug messages\n    // ==========================================================\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    socket.on(\"message\", (msg: any) => {\n      handleUserMessage(msg, deviceName || \"Unknown\");\n    });\n\n    // ==========================================================\n    // Listen for query-sync messages from devices and forward only to dashboard clients\n    // ==========================================================\n    socket.on(\"query-sync\", (message: SyncMessage) => {\n      const device = getDeviceFromDeviceId(message.persistentDeviceId);\n      console.log(\n        `${LOG_PREFIX} Query sync received from: ${device?.deviceName}, Queries: ${message.state.queries.length}, Mutations: ${message.state.mutations.length}`\n      );\n      // Only forward to dashboard clients, not back to devices\n      forwardQuerySyncToDashboards(message);\n    });\n\n    // ==========================================================\n    // Handle query action messages from the dashboard to the devices\n    // ==========================================================\n    socket.on(\"query-action\", (message: QueryActionMessage) => {\n      const deviceName = getDeviceFromDeviceId(message.deviceId)?.deviceName;\n      // Check if message exists before accessing properties\n      if (!message) {\n        console.error(`${LOG_PREFIX} Error: query-action message is undefined`);\n        return;\n      }\n\n      console.log(\n        `${LOG_PREFIX} Query action from dashboard - Action: ${message.action}, Target: ${deviceName}`\n      );\n\n      withTargetUsers(\n        message.deviceId,\n        (user) => io.to(user.id).emit(\"query-action\", message),\n        \"query action\"\n      );\n    });\n\n    // ==========================================================\n    // Handle dashboard requesting initial state from devices\n    // ==========================================================\n    socket.on(\n      \"request-initial-state\",\n      (message: QueryRequestInitialStateMessage) => {\n        const deviceName = getDeviceFromDeviceId(\n          message.targetDeviceId\n        )?.deviceName;\n        // Check if message exists before accessing properties\n        if (!message) {\n          console.error(\n            `${LOG_PREFIX} Error: request-initial-state message is undefined`\n          );\n          return;\n        }\n\n        console.log(\n          `${LOG_PREFIX} Request initial state - Target: ${deviceName} (${message.targetDeviceId})`\n        );\n\n        withTargetUsers(\n          message.targetDeviceId,\n          (user) =>\n            io\n              .to(user.id)\n              .emit(\"request-initial-state\", { type: \"request-initial-state\" }),\n          \"initial state request\"\n        );\n      }\n    );\n\n    // ==========================================================\n    // Handle online manager messages from the dashboard to the devices\n    // ==========================================================\n    socket.on(\"online-manager\", (message: OnlineManagerMessage) => {\n      const deviceName = getDeviceFromDeviceId(\n        message.targetDeviceId\n      )?.deviceName;\n      console.log(\n        `${LOG_PREFIX} Online manager message from dashboard - Action: ${message.action}, Target: ${deviceName} (${message.targetDeviceId})`\n      );\n\n      withTargetUsers(\n        message.targetDeviceId,\n        (user) => io.to(user.id).emit(\"online-manager\", message),\n        \"online manager message\"\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "src/server.ts",
    "content": "import express, { Request, Response } from \"express\";\nimport http from \"http\";\nimport { Server } from \"socket.io\";\nimport path from \"path\";\nimport { SERVER_PORT, SOCKET_CONFIG } from \"./config\";\nimport socketHandle from \"./server/socketHandle\";\n// Import Electron modules for error messages\nimport { dialog, app as electronApp } from \"electron\";\n\n// Create Express app\nconst app = express();\nconst server = http.createServer(app);\n\n// Configure Socket.IO server using config\nconst io = new Server(server, SOCKET_CONFIG);\nsocketHandle({ io });\n\n// Serve static files if needed\napp.use(express.static(path.join(__dirname, \"../build\")));\n\n// Handle root route\napp.get(\"/\", (req: Request, res: Response) => {\n  res.send(`Socket.IO server is running on port ${SERVER_PORT}`);\n});\n\n// Start server\nexport const startServer = () => {\n  try {\n    const serverInstance = server.listen(SERVER_PORT, () => {\n      console.log(`Socket.IO server running on port ${SERVER_PORT}`);\n    });\n\n    // Add error handler for server\n    serverInstance.on(\"error\", (error: NodeJS.ErrnoException) => {\n      if (error.code === \"EADDRINUSE\") {\n        console.error(\n          `Port ${SERVER_PORT} is already in use. Please close the application using this port or change the port in config.ts.`\n        );\n\n        // Close server resources to avoid hanging\n        try {\n          io.close();\n          serverInstance.close();\n        } catch (closeError) {\n          console.error(\"Error closing server resources:\", closeError);\n        }\n\n        // If running in Electron, show a dialog\n        try {\n          dialog.showErrorBox(\n            \"Port Already in Use\",\n            `Cannot start server: Port ${SERVER_PORT} is already in use.\\n\\nPlease close any other instances of this application.`\n          );\n\n          // Optionally exit the application\n          electronApp.exit(1);\n        } catch (dialogError) {\n          // If dialog module can't be loaded (not in Electron context), just log to console\n          console.error(\"Failed to show error dialog:\", dialogError);\n        }\n      } else {\n        console.error(\"Server error:\", error);\n      }\n    });\n\n    return { io, server: serverInstance };\n  } catch (error) {\n    console.error(\"Error starting server:\", error);\n    throw error;\n  }\n};\n\n// Export io instance for use in other files\nexport { io };\n"
  },
  {
    "path": "src/types.d.ts",
    "content": "declare module \"electron-squirrel-startup\" {\n  const value: boolean;\n  export default value;\n}\n\n// Vite environment variables\ndeclare const MAIN_WINDOW_VITE_DEV_SERVER_URL: string | undefined;\ndeclare const MAIN_WINDOW_VITE_NAME: string;\n"
  },
  {
    "path": "tailwind.config.js",
    "content": "/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n  content: [\"./index.html\", \"./src/**/*.{js,ts,jsx,tsx}\"],\n  theme: {\n    extend: {\n      fontFamily: {\n        \"sf-pro\": [\n          \"SF Pro Display\",\n          \"SF Pro\",\n          \"-apple-system\",\n          \"BlinkMacSystemFont\",\n          \"system-ui\",\n          \"sans-serif\",\n        ],\n        \"sf-mono\": [\n          \"SF Mono\",\n          \"SFMono-Regular\",\n          \"Menlo\",\n          \"Monaco\",\n          \"Consolas\",\n          \"Liberation Mono\",\n          \"Courier New\",\n          \"monospace\",\n        ],\n      },\n      colors: {\n        \"apple-dark\": {\n          900: \"#0A0A0C\", // Darkest\n          800: \"#1A1A1C\", // Card background\n          700: \"#2D2D2F\", // Border\n          600: \"#3D3D3F\", // Toggle background\n          500: \"#4D4D4F\", // Hover border\n          400: \"#6D6D6F\", // Muted svg\n          300: \"#8E8E93\", // Muted text\n          200: \"#A1A1A6\", // Secondary text\n          100: \"#F5F5F7\", // Primary text\n        },\n      },\n      boxShadow: {\n        \"apple-sm\":\n          \"0 0.125rem 0.25rem rgba(0, 0, 0, 0.15), 0 0 0.0625rem rgba(0, 0, 0, 0.1)\",\n        apple: \"0 0.5rem 1rem rgba(0, 0, 0, 0.2)\",\n        \"apple-md\": \"0 0.75rem 1.5rem rgba(0, 0, 0, 0.25)\",\n        \"apple-lg\": \"0 1rem 2rem rgba(0, 0, 0, 0.3)\",\n        \"apple-xl\": \"0 1.5rem 3rem rgba(0, 0, 0, 0.35)\",\n        \"apple-glow\": \"0 0 1rem rgba(59, 130, 246, 0.3)\",\n      },\n      animation: {\n        fadeIn: \"fadeIn 0.5s ease-out forwards\",\n        gradient: \"gradient 8s ease infinite\",\n        slideUpFade: \"slideUpFade 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards\",\n        shimmer: \"shimmer 2s infinite linear\",\n        pulse: \"pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite\",\n        fadeInDelay: \"fadeIn 0.5s ease-out 0.2s forwards\",\n        float: \"float 6s ease-in-out infinite\",\n        slideLeft: \"slideLeft 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards\",\n        slideRight: \"slideRight 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards\",\n        scaleIn: \"scaleIn 0.35s cubic-bezier(0.34, 1.56, 0.64, 1) forwards\",\n        scaleOut: \"scaleOut 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards\",\n        blurIn: \"blurIn 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards\",\n        blurOut: \"blurOut 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards\",\n      },\n      keyframes: {\n        fadeIn: {\n          \"0%\": { opacity: 0 },\n          \"100%\": { opacity: 1 },\n        },\n        gradient: {\n          \"0%, 100%\": {\n            backgroundPosition: \"0% 50%\",\n          },\n          \"50%\": {\n            backgroundPosition: \"100% 50%\",\n          },\n        },\n        slideUpFade: {\n          \"0%\": {\n            opacity: 0,\n            transform: \"translateY(1rem)\",\n          },\n          \"100%\": {\n            opacity: 1,\n            transform: \"translateY(0)\",\n          },\n        },\n        shimmer: {\n          \"0%\": {\n            backgroundPosition: \"-1000px 0\",\n          },\n          \"100%\": {\n            backgroundPosition: \"1000px 0\",\n          },\n        },\n        pulse: {\n          \"0%, 100%\": {\n            opacity: 0.8,\n          },\n          \"50%\": {\n            opacity: 0.2,\n          },\n        },\n        float: {\n          \"0%, 100%\": {\n            transform: \"translateY(0)\",\n          },\n          \"50%\": {\n            transform: \"translateY(-10px)\",\n          },\n        },\n        slideLeft: {\n          \"0%\": {\n            opacity: 0,\n            transform: \"translateX(1rem)\",\n          },\n          \"100%\": {\n            opacity: 1,\n            transform: \"translateX(0)\",\n          },\n        },\n        slideRight: {\n          \"0%\": {\n            opacity: 0,\n            transform: \"translateX(-1rem)\",\n          },\n          \"100%\": {\n            opacity: 1,\n            transform: \"translateX(0)\",\n          },\n        },\n        scaleIn: {\n          \"0%\": {\n            opacity: 0,\n            transform: \"scale(0.95)\",\n          },\n          \"100%\": {\n            opacity: 1,\n            transform: \"scale(1)\",\n          },\n        },\n        scaleOut: {\n          \"0%\": {\n            opacity: 1,\n            transform: \"scale(1)\",\n          },\n          \"100%\": {\n            opacity: 0,\n            transform: \"scale(0.95)\",\n          },\n        },\n        blurIn: {\n          \"0%\": {\n            opacity: 0,\n            transform: \"translateY(0.5rem)\",\n          },\n          \"100%\": {\n            opacity: 1,\n            transform: \"translateY(0)\",\n          },\n        },\n        blurOut: {\n          \"0%\": {\n            opacity: 1,\n            transform: \"translateY(0)\",\n          },\n          \"100%\": {\n            opacity: 0,\n            transform: \"translateY(0.5rem)\",\n          },\n        },\n      },\n      transitionTimingFunction: {\n        apple: \"cubic-bezier(0.16, 1, 0.3, 1)\",\n      },\n      borderRadius: {\n        \"4xl\": \"2rem\",\n      },\n    },\n  },\n  plugins: [],\n};\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"commonjs\",\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"esModuleInterop\": true,\n    \"noImplicitAny\": true,\n    \"sourceMap\": true,\n    \"baseUrl\": \".\",\n    \"outDir\": \"dist\",\n    \"moduleResolution\": \"node16\",\n    \"resolveJsonModule\": true,\n    \"jsx\": \"react-jsx\",\n    \"lib\": [\"ESNext\", \"DOM\"]\n  },\n  \"include\": [\"src/**/*\"]\n}\n"
  },
  {
    "path": "vite.main.config.ts",
    "content": "import { defineConfig } from 'vite';\n\n// https://vitejs.dev/config\nexport default defineConfig({});\n"
  },
  {
    "path": "vite.preload.config.ts",
    "content": "import { defineConfig } from 'vite';\n\n// https://vitejs.dev/config\nexport default defineConfig({});\n"
  },
  {
    "path": "vite.renderer.config.ts",
    "content": "import { defineConfig } from \"vite\";\nimport react from \"@vitejs/plugin-react\";\n\n// https://vitejs.dev/config\nexport default defineConfig({\n  plugins: [react()],\n});\n"
  }
]