[
  {
    "path": ".eslintrc.json",
    "content": "{\n  \"env\": {\n    \"browser\": true,\n    \"es2021\": true,\n    \"node\": true\n  },\n  \"extends\": [\n    \"eslint:recommended\",\n    \"plugin:@typescript-eslint/recommended\",\n    \"plugin:react/recommended\",\n    \"plugin:react-hooks/recommended\"\n  ],\n  \"parser\": \"@typescript-eslint/parser\",\n  \"parserOptions\": {\n    \"ecmaFeatures\": {\n      \"jsx\": true\n    },\n    \"ecmaVersion\": 12,\n    \"sourceType\": \"module\"\n  },\n  \"plugins\": [\n    \"react\",\n    \"@typescript-eslint\"\n  ],\n  \"rules\": {\n    \"react/react-in-jsx-scope\": \"off\",\n    \"@typescript-eslint/explicit-module-boundary-types\": \"off\",\n    \"@typescript-eslint/no-explicit-any\": \"warn\",\n    \"@typescript-eslint/no-unused-vars\": [\"warn\", { \"argsIgnorePattern\": \"^_\" }]\n  },\n  \"settings\": {\n    \"react\": {\n      \"version\": \"detect\"\n    }\n  }\n} "
  },
  {
    "path": ".gitignore",
    "content": "# OSX\n.DS_Store\nThumbs.db\n\n# Xcode\nbuild/\n*.pbxuser\n!default.pbxuser\n*.mode1v3\n!default.mode1v3\n*.mode2v3\n!default.mode2v3\n*.perspectivev3\n!default.perspectivev3\nxcuserdata\n*.xccheckout\n*.moved-aside\nDerivedData\n*.hmap\n*.ipa\n*.xcuserstate\nios/.xcode.env.local\n\n# Android/IntelliJ\nbuild/\n.idea\n.gradle\nlocal.properties\n*.iml\n*.hprof\n.cxx/\n*.keystore\n!debug.keystore\n\n# node.js\nnode_modules/\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# fastlane\n**/fastlane/report.xml\n**/fastlane/Preview.html\n**/fastlane/screenshots\n**/fastlane/test_output\n\n# Bundle artifact\n*.jsbundle\n\n# Ruby / CocoaPods\n/ios/Pods/\n/vendor/bundle/\n\n# Temporary files created by Metro to check the health of the file watcher\n.metro-health-check*\n\n# testing\n/coverage\n\n# Production build\n/dist\n/build\n\n# TypeScript\n*.tsbuildinfo\n\n# Environment variables\n.env\n.env.*\n!.env.example\n\n# IDE\n.vscode/\n.idea/\n*.swp\n*.swo\n\n# Logs\nlogs\n*.log\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# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variable files\n.env\n.env.development.local\n.env.test.local\n.env.production.local\n.env.local\n\n# Expo\n.expo/\nweb-build/\ndist/\n\n# Dependencies\n.pnp/\n.pnp.js "
  },
  {
    "path": ".npmignore",
    "content": "# Source\nsrc/\ntests/\n__tests__/\n*.test.ts\n*.test.tsx\n*.spec.ts\n*.spec.tsx\n\n# Development\n.git/\n.github/\n.gitignore\n.eslintrc.json\n.prettierrc\njest.config.js\ntsconfig.json\ntsup.config.ts\n*.config.js\n*.config.ts\n\n# IDE\n.idea/\n.vscode/\n*.swp\n*.swo\n*.sublime-*\n\n# OS\n.DS_Store\nThumbs.db\ndesktop.ini\n\n# Logs\nlogs/\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n\n# Environment\n.env\n.env.*\n!.env.example\n\n# Build\ncoverage/\n.nyc_output/\ndist/\nbuild/\n*.tgz\n\n# Dependencies\nnode_modules/\n.pnp/\n.pnp.js\n\n# TypeScript\n*.tsbuildinfo "
  },
  {
    "path": "App.tsx",
    "content": "import React from 'react';\nimport { View, StyleSheet } from 'react-native';\nimport { initOptic } from './src';\nimport { Overlay } from './src/overlay/Overlay';\nimport { RenderTest } from './src/components/RenderTest';\nimport { OpticProvider } from './src/providers/OpticProvider';\nimport { initRenderTracking } from './src/metrics/globalRenderTracking';\n\n// Initialize Optic with all metrics enabled\ninitOptic({\n  enabled: true,\n  network: true,\n  startup: true,\n  reRenders: true,\n  traces: true\n});\n\n// Initialize re-render tracking\ninitRenderTracking();\n\nexport default function App() {\n  return (\n    <OpticProvider>\n      <View style={styles.container}>\n        <RenderTest />\n        <Overlay />\n      </View>\n    </OpticProvider>\n  );\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    backgroundColor: '#fff',\n  },\n}); "
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to optic-react-native\n\nWe love your input! We want to make contributing to optic-react-native as easy and transparent as possible, whether it's:\n\n- Reporting a bug\n- Discussing the current state of the code\n- Submitting a fix\n- Proposing new features\n- Becoming a maintainer\n\n## We Develop with Github\nWe use GitHub to host code, to track issues and feature requests, as well as accept pull requests.\n\n## We Use [Github Flow](https://guides.github.com/introduction/flow/index.html)\nPull requests are the best way to propose changes to the codebase. We actively welcome your pull requests:\n\n1. Fork the repo and create your branch from `main`.\n2. If you've added code that should be tested, add tests.\n3. If you've changed APIs, update the documentation.\n4. Ensure the test suite passes.\n5. Make sure your code lints.\n6. Issue that pull request!\n\n## Any contributions you make will be under the MIT Software License\nIn short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern.\n\n## Report bugs using Github's [issue tracker](https://github.comoptic-react-native/issues)\nWe use GitHub issues to track public bugs. Report a bug by [opening a new issue](https://github.comoptic-react-native/issues/new); it's that easy!\n\n## Write bug reports with detail, background, and sample code\n\n**Great Bug Reports** tend to have:\n\n- A quick summary and/or background\n- Steps to reproduce\n  - Be specific!\n  - Give sample code if you can.\n- What you expected would happen\n- What actually happens\n- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work)\n\n## Development Process\n\n1. Clone the repository\n2. Install dependencies:\n   ```bash\n   npm install\n   ```\n3. Make your changes\n4. Build the project:\n   ```bash\n   npm run build\n   ```\n5. Test your changes\n6. Submit a pull request\n\n## License\nBy contributing, you agree that your contributions will be licensed under its MIT License. "
  },
  {
    "path": "README.md",
    "content": "# optic-react-native\n\nA lightweight performance monitoring tool for React Native applications. Track startup time, network requests, FPS, and custom traces in real-time.\n\n![npm version](https://img.shields.io/npm/v/optic-react-native)\n\n## Features\n\n- App startup time measurement\n- Network request tracking\n- Custom interaction tracing\n- Draggable overlay display\n- Send metrics to custom API\n\n## Demo\n\n<img src=\"https://github.com/user-attachments/assets/d7cc525f-5621-4107-9cce-ef3f8a0dac0f\" width=\"350\" alt=\"Optic Performance Monitor Screenshot\" />\n\n## Installation\n\n```bash\nnpm install optic-react-native\n# or\nyarn add optic-react-native\n```\n\n## Quick Start\n\n1. Initialize Optic in your app's entry point:\n\n```typescript\nimport { initOptic } from 'optic-react-native';\n\ninitOptic();\n```\n\n2. Add the overlay component to your app:\n\n```typescript\nimport { OpticProvider } from 'optic-react-native';\n\nconst App = () => (\n  <>  \n    <OpticProvider>\n    <YourAppContent />\n    <OpticProvider />\n  </>\n);\n```\n\n## Custom Metrics\n\n### Tracking Custom Interactions\n\nUse the tracing API to measure specific interactions in your app:\n\n```typescript\nimport { startTrace, endTrace } from 'optic-react-native';\n\nconst handleButtonPress = async () => {\n  startTrace('ButtonPress');\n  \n  try {\n    await someAsyncOperation();\n  } finally {\n    endTrace('ButtonPress', 'ButtonComponent');\n  }\n};\n```\n\n### Tracking Re-renders\n\nMonitor component re-renders using the `useRenderMonitor` hook:\n\n```typescript\nimport { useRenderMonitor } from 'optic-react-native;\n\nconst MyComponent = () => {\n  useRenderMonitor('MyComponent');\n  // ... your component code\n};\n```\n\n## Configuration\n\n```typescript\ninterface InitOpticOptions {\n  enabled?: boolean;     // Enable/disable all metrics (default: true)\n  startup?: boolean;     // Track startup time (default: true)\n  network?: boolean;     // Track network requests (default: true)\n  reRenders?: boolean;   // Track component re-renders (default: true)\n  traces?: boolean;      // Enable custom tracing (default: true)\n  onMetricsLogged?: (metrics: any) => void; // Callback for metrics updates\n}\n```\n\n## Contributing\n\nWe welcome contributions! Here's how you can help:\n\n1. Fork the repository\n2. Create your feature branch (`git checkout -b feature/amazing-feature`)\n3. Commit your changes (`git commit -m 'Add amazing feature'`)\n4. Push to the branch (`git push origin feature/amazing-feature`)\n5. Open a Pull Request\n\n### Development Setup\n\n1. Clone the repository\n2. Install dependencies: `yarn install`\n3. Run tests: `yarn test`\n4. Build the package: `yarn build`\n\n### Code Style\n\n- Follow the existing code style\n- Write tests for new features\n- Update documentation as needed\n- Keep commits atomic and well-described\n\n## License\n\nMIT © Optic\n\nCopyright (c) 2024 Optic\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE. \n\n## Optic React Native\n\n### Initialization Options\n\n#### `enabled`\n- **Type:** `boolean`\n- **Default:** `true`\n- **Description:** If set to `false`, disables all metrics and hides the overlay. Useful for turning off performance tracking in production or for specific builds.\n\n#### `onMetricsLogged`\n- **Type:** `(metrics: any) => void`\n- **Description:** Callback function that is called whenever metrics are updated. You can use this to log metrics, send them to an API, or perform custom analytics.\n\n#### Example Usage\n\n```js\nimport { initOptic } from 'optic-react-native';\n\ninitOptic({\n  enabled: true, // Set to false to disable metrics and overlay\n  onMetricsLogged: (metrics) => {\n    // Log metrics or send to your API\n    fetch('https://your-api.com/metrics', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify(metrics),\n    });\n  },\n  // ...other options\n});\n```\n\n---\n\nFor more details on all options, see the API documentation section. \n\n## Testing Re-render Tracking with `useRenderMonitor`\n\nTo test and visualize component re-renders in the overlay, use the `useRenderMonitor` hook in your components. This will increment the re-render count for the component in the metrics overlay.\n\n### Example Usage\n\n```tsx\nimport React from 'react';\nimport { useRenderMonitor } from 'optic-react-native';\n\nexport const TestComponent = () => {\n  useRenderMonitor('Home');\n  const [count, setCount] = React.useState(0);\n\n  return (\n    <View>\n      <Text>Render count: {count}</Text>\n      <Button title=\"Re-render\" onPress={() => setCount(count + 1)} />\n    </View>\n  );\n};\n```\n\n- The name you pass to `useRenderMonitor` will appear in the \"Re-renders\" section of the overlay.\n- Each time the component re-renders, the count will increment in real time.\n\nAdd this component to your app and interact with it to see re-render tracking in action in the overlay.\n\n--- \n"
  },
  {
    "path": "app.json",
    "content": "{\n  \"expo\": {\n    \"name\": \"optic-react-native\",\n    \"slug\": \"useoptic-react-native\",\n    \"version\": \"1.0.0\",\n    \"orientation\": \"portrait\",\n    \"icon\": \"./assets/icon.png\",\n    \"userInterfaceStyle\": \"light\",\n    \"splash\": {\n      \"image\": \"./assets/splash.png\",\n      \"resizeMode\": \"contain\",\n      \"backgroundColor\": \"#ffffff\"\n    },\n    \"assetBundlePatterns\": [\n      \"**/*\"\n    ],\n    \"ios\": {\n      \"supportsTablet\": true,\n      \"bundleIdentifier\": \"com.useoptic.app\"\n    },\n    \"android\": {\n      \"adaptiveIcon\": {\n        \"foregroundImage\": \"./assets/adaptive-icon.png\",\n        \"backgroundColor\": \"#ffffff\"\n      },\n      \"package\": \"com.useoptic.app\"\n    },\n    \"web\": {\n      \"favicon\": \"./assets/favicon.png\"\n    },\n    \"plugins\": [\n      \"expo-device\"\n    ]\n  }\n} "
  },
  {
    "path": "babel.config.js",
    "content": "module.exports = function (api) {\n  api.cache(true);\n  return {\n    presets: ['babel-preset-expo'],\n    plugins: [\n      // Add any other plugins you need\n    ],\n  };\n}; "
  },
  {
    "path": "metro.config.js",
    "content": "const { getDefaultConfig } = require('@react-native/metro-config');\n\nmodule.exports = (async () => {\n  const defaultConfig = await getDefaultConfig(__dirname);\n  const { assetExts } = defaultConfig.resolver;\n\n  return {\n    ...defaultConfig,\n    resolver: {\n      ...defaultConfig.resolver,\n      assetExts: [...assetExts, 'png'],\n    },\n  };\n})(); "
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"optic-react-native\",\n  \"author\": \"Adnan Sahinovic\",\n  \"version\": \"1.0.0\",\n  \"main\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"scripts\": {\n    \"build\": \"tsup src/index.ts --dts --format esm,cjs --out-dir dist\",\n    \"dev\": \"tsup --watch\",\n    \"prepare\": \"tsup\",\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  },\n  \"keywords\": [\"react-native\", \"react\", \"optic\", \"metrics\", \"performance\", \"debugging\"],\n  \"license\": \"MIT\",\n  \"description\": \"React Native library for Optic\",\n  \"dependencies\": {\n    \"@react-navigation/native\": \"^7.1.9\",\n    \"expo-router\": \"^5.0.7\",\n    \"react\": \"*\",\n    \"react-native\": \"*\",\n    \"zustand\": \"^5.0.4\"\n  },\n  \"devDependencies\": {\n    \"@types/react\": \"^19.1.3\",\n    \"@types/react-native\": \"^0.72.8\",\n    \"react-native-safe-area-context\": \"^4.9.0\",\n    \"tsup\": \"^8.4.0\",\n    \"typescript\": \"^5.8.3\"\n  },\n  \"peerDependencies\": {\n    \"react\": \"*\",\n    \"react-native\": \"*\",\n    \"react-native-safe-area-context\": \">=4.0.0\"\n  }\n}\n"
  },
  {
    "path": "src/components/OpticProvider.tsx",
    "content": "import React from 'react';\nimport { View } from 'react-native';\nimport { useMetricsStore } from '../store/metricsStore';\nimport { Overlay } from '../overlay/Overlay';\n\n// Define the types we need since we don't want to add @react-navigation/native as a dependency\ninterface NavigationState {\n  routes: Array<{\n    name: string;\n    [key: string]: any;\n  }>;\n  index: number;\n}\n\ninterface NavigationContainerProps {\n  onStateChange?: (state: NavigationState | undefined) => void;\n  [key: string]: any;\n}\n\ninterface OpticProviderProps {\n  children: React.ReactNode;\n}\n\nexport function OpticProvider({ children }: OpticProviderProps) {\n  const setCurrentScreen = useMetricsStore((state) => state.setCurrentScreen);\n\n  // Function to handle navigation state changes\n  const handleNavigationStateChange = (state: NavigationState | undefined) => {\n    if (state?.routes && state.routes.length > 0) {\n      const currentRoute = state.routes[state.index];\n      if (currentRoute?.name) {\n        setCurrentScreen(currentRoute.name);\n      }\n    }\n  };\n\n  // Clone children and add onStateChange prop to NavigationContainer\n  const childrenWithNavigationTracking = React.Children.map(children, (child) => {\n    if (React.isValidElement(child)) {\n      // Check if the child is a NavigationContainer by checking its displayName\n      const displayName = (child.type as any)?.displayName || (child.type as any)?.name;\n      if (displayName === 'NavigationContainer') {\n        return React.cloneElement(child, {\n          onStateChange: handleNavigationStateChange,\n        } as Partial<NavigationContainerProps>);\n      }\n    }\n    return child;\n  });\n\n  return (\n    <View style={{ flex: 1 }}>\n      {childrenWithNavigationTracking}\n      <Overlay />\n    </View>\n  );\n} "
  },
  {
    "path": "src/components/PerformanceOverlay.tsx",
    "content": "import React from 'react';\nimport { View, Text, StyleSheet } from 'react-native';\nimport { useMetricsStore } from '../store/metricsStore';\n\nexport function PerformanceOverlay() {\n  const { traces, fps, currentScreen } = useMetricsStore();\n\n  return (\n    <View style={styles.container}>\n      <Text style={styles.title}>Performance Metrics</Text>\n      <Text style={styles.metric}>FPS: {fps ? `${fps.toFixed(1)}` : 'N/A'}</Text>\n      <Text style={styles.metric}>Screen: {currentScreen || 'N/A'}</Text>\n      <Text style={styles.metric}>Traces: {traces.length}</Text>\n    </View>\n  );\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    position: 'absolute',\n    top: 0,\n    right: 0,\n    backgroundColor: 'rgba(0, 0, 0, 0.8)',\n    padding: 10,\n    borderRadius: 5,\n  },\n  title: {\n    color: '#fff',\n    fontSize: 16,\n    fontWeight: 'bold',\n    marginBottom: 5,\n  },\n  metric: {\n    color: '#fff',\n    fontSize: 14,\n    marginBottom: 3,\n  },\n}); "
  },
  {
    "path": "src/core/initOptic.ts",
    "content": "import { initRenderTracking } from '../metrics/globalRenderTracking';\nimport { initNetworkTracking } from '../metrics/network';\nimport { useMetricsStore } from '../store/metricsStore';\nimport { trackStartupTime } from '../metrics/startup';\nimport { setOpticEnabled } from '../store/metricsStore';\nimport React from 'react';\n\nexport interface InitOpticOptions {\n  enabled?: boolean;\n  onMetricsLogged?: (metrics: any) => void;\n  network?: boolean;\n  startup?: boolean;\n  reRenders?: boolean;\n  traces?: boolean;\n}\n\nexport interface OpticConfig {\n  enabled: boolean;\n  onMetricsLogged?: (metrics: any) => void;\n  network: boolean;\n  startup: boolean;\n  reRenders: boolean;\n  traces: boolean;\n}\n\n// Create a wrapper component that automatically tracks screen names\nfunction withScreenTracking<P extends object>(WrappedComponent: React.ComponentType<P>) {\n  const displayName = WrappedComponent.displayName || WrappedComponent.name || 'Unknown';\n  const screenName = displayName.replace(/Screen$/, '');\n\n  function WithScreenTracking(props: P) {\n    const setCurrentScreen = useMetricsStore((state) => state.setCurrentScreen);\n    \n    React.useEffect(() => {\n      setCurrentScreen(screenName);\n      return () => setCurrentScreen(null);\n    }, [setCurrentScreen]);\n\n    return React.createElement(WrappedComponent, props);\n  }\n\n  WithScreenTracking.displayName = `WithScreenTracking(${displayName})`;\n  return WithScreenTracking;\n}\n\n// Function to check if a component is likely a screen\nfunction isScreenComponent(component: any): boolean {\n  const name = component.displayName || component.name || '';\n  return name.endsWith('Screen') || name.endsWith('Page') || name.endsWith('View');\n}\n\n// Store to keep track of wrapped components\nconst wrappedComponents = new WeakMap();\n\n// Function to wrap a component if it's a screen\nfunction wrapIfScreen<P extends object>(Component: React.ComponentType<P>): React.ComponentType<P> {\n  if (!isScreenComponent(Component)) {\n    return Component;\n  }\n\n  // Check if already wrapped\n  if (wrappedComponents.has(Component)) {\n    return wrappedComponents.get(Component);\n  }\n\n  // Wrap the component\n  const wrapped = withScreenTracking(Component);\n  wrappedComponents.set(Component, wrapped);\n  return wrapped;\n}\n\nexport function initOptic(options: InitOpticOptions = {}) {\n  const {\n    enabled = true,\n    onMetricsLogged,\n    network = true,\n    startup = true,\n    reRenders = true,\n    traces = true,\n  } = options;\n\n  const config: OpticConfig = {\n    enabled,\n    onMetricsLogged,\n    network,\n    startup,\n    reRenders,\n    traces,\n  };\n\n  setOpticEnabled(enabled);\n  if (!enabled) {\n    // Do not initialize anything if disabled\n    return;\n  }\n\n  // Initialize render tracking if enabled\n  if (reRenders) {\n    initRenderTracking();\n  }\n\n  // Initialize network tracking if enabled\n  if (network) {\n    initNetworkTracking();\n  }\n\n  // Track startup time if enabled\n  if (startup) {\n    trackStartupTime();\n  }\n\n  // Initialize metrics store\n  useMetricsStore.getState();\n\n  // Subscribe to metrics changes and call the callback\n  if (onMetricsLogged) {\n    const unsubscribe = useMetricsStore.subscribe((metrics) => {\n      onMetricsLogged(metrics);\n    });\n    // Optionally return unsubscribe so the user can clean up\n    return {\n      config,\n      unsubscribe,\n    };\n  }\n\n  return config;\n}\n"
  },
  {
    "path": "src/hoc/withScreenTracking.tsx",
    "content": "import React, { useEffect } from 'react';\nimport { useNavigation } from '@react-navigation/native';\nimport { useMetricsStore } from '../store/metricsStore';\n\nexport function withScreenTracking<P extends object>(\n  WrappedComponent: React.ComponentType<P>,\n  screenName?: string\n) {\n  return function WithScreenTracking(props: P) {\n    const navigation = useNavigation();\n    const setCurrentScreen = useMetricsStore((state) => state.setCurrentScreen);\n\n    useEffect(() => {\n      // Get screen name from navigation state or use provided name\n      const route = navigation.getState().routes[navigation.getState().index];\n      const currentScreenName = screenName || route.name;\n      \n      setCurrentScreen(currentScreenName);\n      \n      return () => {\n        // Clear screen name when component unmounts\n        setCurrentScreen(null);\n      };\n    }, [navigation, screenName, setCurrentScreen]);\n\n    return <WrappedComponent {...props} />;\n  };\n} "
  },
  {
    "path": "src/hooks/useAutoScreenName.ts",
    "content": "import { useEffect } from 'react';\nimport { useMetricsStore } from '../store/metricsStore';\n\n/**\n * Automatically tracks the current screen name based on the component's name.\n * Just add this hook to your screen components without any parameters:\n * \n * @example\n * function HomeScreen() {\n *   useAutoScreenName(); // That's it! It will automatically use \"HomeScreen\" as the name\n *   return <View>...</View>;\n * }\n */\nexport function useAutoScreenName() {\n  const setCurrentScreen = useMetricsStore((state) => state.setCurrentScreen);\n  \n  useEffect(() => {\n    // Get the component name from the stack trace\n    const stack = new Error().stack || '';\n    const match = stack.match(/at\\s+(\\w+)\\s+\\(/);\n    const componentName = match ? match[1] : 'Unknown';\n    \n    // Remove \"Screen\" suffix if present\n    const screenName = componentName.replace(/Screen$/, '');\n    \n    setCurrentScreen(screenName);\n    return () => setCurrentScreen(null);\n  }, [setCurrentScreen]);\n} "
  },
  {
    "path": "src/hooks/useScreenName.ts",
    "content": "import { useEffect } from 'react';\nimport { useMetricsStore } from '../store/metricsStore';\n\n/**\n * A simple hook to track screen names in your React Native app.\n * Just add this hook to your screen components:\n * \n * @example\n * function HomeScreen() {\n *   useScreenName('Home');\n *   return <View>...</View>;\n * }\n */\nexport function useScreenName(screenName: string) {\n  const setCurrentScreen = useMetricsStore((state) => state.setCurrentScreen);\n\n  useEffect(() => {\n    setCurrentScreen(screenName);\n    return () => setCurrentScreen(null);\n  }, [screenName, setCurrentScreen]);\n} "
  },
  {
    "path": "src/index.ts",
    "content": "export { initOptic } from './core/initOptic';\nexport { OpticProvider } from './providers/OpticProvider';\nexport { useMetricsStore } from './store/metricsStore';\nexport { useRenderMonitor } from './metrics/reRenders';\nexport { startTrace, endTrace } from './metrics/trace';\nexport type { InitOpticOptions } from './core/initOptic';\n"
  },
  {
    "path": "src/index.tsx",
    "content": "import { OpticProvider } from './components/OpticProvider';\nimport { initOptic } from './core/initOptic';\nimport { Overlay } from './overlay/Overlay';\nimport { useRenderMonitor } from './metrics/reRenders';\nimport { useScreenMetrics } from './metrics/screen';\n\nexport {\n  initOptic,\n  Overlay,\n  useRenderMonitor,\n  useScreenMetrics,\n  OpticProvider\n}; "
  },
  {
    "path": "src/metrics/fps.ts",
    "content": "import { useMetricsStore } from '../store/metricsStore';\n\nexport interface FPSMetrics {\n  fps: number;\n  timestamp: number;\n}\n\nexport class FPSManager {\n  private frameCount: number = 0;\n  private lastTime: number = 0;\n  private animationFrameId: number | null = null;\n  private readonly updateInterval: number = 1000; // Update FPS every second\n\n  constructor() {\n    this.lastTime = performance.now();\n  }\n\n  private updateFPS = () => {\n    const currentTime = performance.now();\n    const elapsed = currentTime - this.lastTime;\n\n    if (elapsed >= this.updateInterval) {\n      const fps = Math.round((this.frameCount * 1000) / elapsed);\n      const metricsStore = useMetricsStore.getState();\n      const currentScreen = metricsStore.currentScreen;\n\n      if (currentScreen) {\n        metricsStore.setFPS(fps, currentScreen);\n      }\n\n      this.frameCount = 0;\n      this.lastTime = currentTime;\n    }\n\n    this.frameCount++;\n    this.animationFrameId = requestAnimationFrame(this.updateFPS);\n  };\n\n  public startTracking = () => {\n    if (!this.animationFrameId) {\n      this.lastTime = performance.now();\n      this.frameCount = 0;\n      this.animationFrameId = requestAnimationFrame(this.updateFPS);\n    }\n  };\n\n  public stopTracking = () => {\n    if (this.animationFrameId) {\n      cancelAnimationFrame(this.animationFrameId);\n      this.animationFrameId = null;\n    }\n  };\n}\n\nexport const getFPSColor = (fps: number): string => {\n  if (fps >= 55) return '#4CAF50'; // Good (green)\n  if (fps >= 30) return '#FFC107'; // Warning (yellow)\n  return '#F44336'; // Poor (red)\n}; "
  },
  {
    "path": "src/metrics/globalRenderTracking.ts",
    "content": "import * as React from 'react';\nimport { useMetricsStore } from '../store/metricsStore';\n\ndeclare global {\n  var __OPTIC_ROOT_COMPONENT__: React.ComponentType<any> | undefined;\n  var __OPTIC_RENDER_TRACKING_ENABLED__: boolean;\n}\n\n// Store to keep track of component renders\nconst renderCounts: Record<string, number> = {};\n\n// Create a wrapper component that tracks renders\nconst withRenderTracking = (WrappedComponent: React.ComponentType<any>) => {\n  const RenderTrackingWrapper: React.FC<any> = (props) => {\n    const componentName = WrappedComponent.displayName || WrappedComponent.name || 'Unknown';\n    const incrementReRender = useMetricsStore((state) => state.incrementReRender);\n    \n    React.useEffect(() => {\n      if (global.__OPTIC_RENDER_TRACKING_ENABLED__) {\n        const reRenderInfo = {\n          componentName,\n          timestamp: Date.now(),\n          changedProps: props,\n          renderCount: (renderCounts[componentName] || 0) + 1\n        };\n        incrementReRender(componentName, reRenderInfo);\n        renderCounts[componentName] = (renderCounts[componentName] || 0) + 1;\n      }\n    });\n\n    return React.createElement(WrappedComponent, props);\n  };\n\n  return RenderTrackingWrapper;\n};\n\n// Function to wrap any component with render tracking\nexport function wrapWithRenderTracking<T extends React.ComponentType<any>>(\n  component: T\n): T {\n  if (!component) return component;\n  \n  // Skip if already wrapped\n  if ((component as any).__OPTIC_WRAPPED__) return component;\n  \n  const wrapped = withRenderTracking(component);\n  (wrapped as any).__OPTIC_WRAPPED__ = true;\n  return wrapped as T;\n}\n\n// Function to enable/disable render tracking\nexport function setRenderTrackingEnabled(enabled: boolean) {\n  global.__OPTIC_RENDER_TRACKING_ENABLED__ = enabled;\n}\n\n// Function to wrap the root component\nexport function setupGlobalRenderTracking() {\n  // Get the root component\n  const rootComponent = global.__OPTIC_ROOT_COMPONENT__;\n  if (!rootComponent) {\n    return;\n  }\n\n  // Wrap the root component with render tracking\n  const wrappedRoot = wrapWithRenderTracking(rootComponent);\n  global.__OPTIC_ROOT_COMPONENT__ = wrappedRoot;\n}\n\n// Function to set the root component\nexport function setRootComponent(component: React.ComponentType<any>) {\n  if (!component) return;\n  \n  global.__OPTIC_ROOT_COMPONENT__ = component;\n  \n  // If render tracking is enabled, wrap the component\n  if (global.__OPTIC_RENDER_TRACKING_ENABLED__) {\n    setupGlobalRenderTracking();\n  }\n}\n\n// Initialize render tracking\nexport function initRenderTracking() {\n  // Set initial state\n  global.__OPTIC_RENDER_TRACKING_ENABLED__ = true;\n  \n  // Wrap the root component if it exists\n  if (global.__OPTIC_ROOT_COMPONENT__) {\n    setupGlobalRenderTracking();\n  }\n} "
  },
  {
    "path": "src/metrics/network.ts",
    "content": "import { useMetricsStore } from '../store/metricsStore';\n\n// Network performance thresholds (in milliseconds)\nconst NETWORK_THRESHOLDS = {\n  GOOD: 200,\n  WARNING: 500,\n  CRITICAL: 1000,\n};\n\nlet originalFetch: typeof fetch | null = null;\nlet pendingRequests = new Map<string, { startTime: number; url: string; method: string }>();\n\nconst formatDuration = (duration: number): string => {\n  if (duration >= 1000) {\n    return `${(duration / 1000).toFixed(1)}s`;\n  }\n  return `${duration}ms`;\n};\n\nexport const initNetworkTracking = () => {\n  if (originalFetch !== null) return; // Already initialized\n\n  try {\n    originalFetch = global.fetch;\n    global.fetch = async function (input: RequestInfo | URL, init?: RequestInit) {\n      const startTime = Date.now();\n      const url = input instanceof Request ? input.url : input.toString();\n      const method = input instanceof Request ? input.method : (init?.method || 'GET');\n\n      // Store the request start time\n      pendingRequests.set(url, { startTime, url, method });\n\n      try {\n        const response = await originalFetch!(input, init);\n        const responseTime = Date.now();\n        const responseDuration = responseTime - startTime;\n        \n        // Clone the response to ensure we can read the body\n        const clonedResponse = response.clone();\n        \n        // Create a new response that will track when the body is read\n        const newResponse = new Response(response.body, {\n          status: response.status,\n          statusText: response.statusText,\n          headers: response.headers,\n        });\n\n        // Override the json and text methods to track completion\n        const originalJson = newResponse.json;\n        const originalText = newResponse.text;\n\n        newResponse.json = async function() {\n          try {\n            // First try to read the cloned response to ensure it's valid JSON\n            await clonedResponse.json();\n            \n            // If we get here, the JSON is valid, so read the actual response\n            const data = await originalJson.call(this);\n            const endTime = Date.now();\n            const totalDuration = endTime - startTime;\n\n            const metricsStore = useMetricsStore.getState();\n            const currentScreen = metricsStore.currentScreen;\n\n            const networkRequest = {\n              url,\n              method,\n              duration: totalDuration,\n              responseDuration,\n              status: response.status,\n              screen: currentScreen,\n              timestamp: endTime,\n              startTime,\n              endTime,\n            };\n\n            metricsStore.addNetworkRequest(networkRequest);\n            pendingRequests.delete(url);\n\n            return data;\n          } catch (error) {\n            const endTime = Date.now();\n            const totalDuration = endTime - startTime;\n\n            const metricsStore = useMetricsStore.getState();\n            const currentScreen = metricsStore.currentScreen;\n\n            const networkRequest = {\n              url,\n              method,\n              duration: totalDuration,\n              responseDuration,\n              status: response.status,\n              screen: currentScreen,\n              timestamp: endTime,\n              startTime,\n              endTime,\n              error: error instanceof Error ? error.message : 'Unknown error',\n            };\n\n            metricsStore.addNetworkRequest(networkRequest);\n            pendingRequests.delete(url);\n\n            throw error;\n          }\n        };\n\n        newResponse.text = async function() {\n          try {\n            const data = await originalText.call(this);\n            const endTime = Date.now();\n            const totalDuration = endTime - startTime;\n\n            const metricsStore = useMetricsStore.getState();\n            const currentScreen = metricsStore.currentScreen;\n\n            const networkRequest = {\n              url,\n              method,\n              duration: totalDuration,\n              responseDuration,\n              status: response.status,\n              screen: currentScreen,\n              timestamp: endTime,\n              startTime,\n              endTime,\n            };\n\n            metricsStore.addNetworkRequest(networkRequest);\n            pendingRequests.delete(url);\n\n            return data;\n          } catch (error) {\n            const endTime = Date.now();\n            const totalDuration = endTime - startTime;\n\n            const metricsStore = useMetricsStore.getState();\n            const currentScreen = metricsStore.currentScreen;\n\n            const networkRequest = {\n              url,\n              method,\n              duration: totalDuration,\n              responseDuration,\n              status: response.status,\n              screen: currentScreen,\n              timestamp: endTime,\n              startTime,\n              endTime,\n              error: error instanceof Error ? error.message : 'Unknown error',\n            };\n\n            metricsStore.addNetworkRequest(networkRequest);\n            pendingRequests.delete(url);\n\n            throw error;\n          }\n        };\n\n        return newResponse;\n      } catch (error) {\n        const endTime = Date.now();\n        const totalDuration = endTime - startTime;\n\n        const metricsStore = useMetricsStore.getState();\n        const currentScreen = metricsStore.currentScreen;\n\n        const networkRequest = {\n          url,\n          method,\n          duration: totalDuration,\n          status: 0,\n          screen: currentScreen,\n          timestamp: endTime,\n          startTime,\n          endTime,\n          error: error instanceof Error ? error.message : 'Unknown error',\n        };\n\n        metricsStore.addNetworkRequest(networkRequest);\n        pendingRequests.delete(url);\n\n        throw error;\n      }\n    };\n  } catch (error) {\n    if (originalFetch) {\n      global.fetch = originalFetch;\n      originalFetch = null;\n    }\n  }\n};\n\nexport const stopNetworkTracking = () => {\n  if (originalFetch === null) return;\n\n  global.fetch = originalFetch;\n  originalFetch = null;\n  pendingRequests.clear();\n};\n\nexport const getNetworkColor = (duration: number | null | undefined): string => {\n  if (duration === null || duration === undefined) return '#666666';\n  if (duration <= NETWORK_THRESHOLDS.GOOD) return '#4CAF50';\n  if (duration <= NETWORK_THRESHOLDS.WARNING) return '#FFC107';\n  return '#F44336';\n};\n\nexport const getLatestNetworkRequest = () => {\n  const metricsStore = useMetricsStore.getState();\n  const currentScreen = metricsStore.currentScreen;\n  const networkRequests = metricsStore.networkRequests;\n  const screenNetworkRequests = networkRequests.filter(req => req.screen === currentScreen);\n  return screenNetworkRequests[screenNetworkRequests.length - 1];\n}; "
  },
  {
    "path": "src/metrics/reRenders.ts",
    "content": "import React, { useEffect, useRef } from 'react';\nimport { useMetricsStore } from '../store/metricsStore';\n\ninterface ReRenderInfo {\n  componentName: string;\n  timestamp: number;\n  changedProps: Record<string, { from: any; to: any }>;\n  renderCount: number;\n  stackTrace?: string;\n}\n\n/**\n * Hook to monitor and log prop changes for a component.\n * @param componentName Name of the component\n * @param props Component props\n * @param options Additional options for tracking\n */\nexport function useRenderMonitor<T extends Record<string, any>>(\n  componentName: string,\n  props: T,\n  options: {\n    debug?: boolean;\n    ignoreProps?: string[];\n    trackStack?: boolean;\n  } = {}\n) {\n  if (!React) return;\n\n  const { ignoreProps = [], trackStack = false } = options;\n  const prevProps = useRef<T | null>(null);\n  const renderCount = useRef(0);\n  const incrementReRender = useMetricsStore((state) => state.incrementReRender);\n  const currentScreen = useMetricsStore((state) => state.currentScreen);\n\n  useEffect(() => {\n    prevProps.current = null;\n    renderCount.current = 0;\n  }, [currentScreen]);\n\n  useEffect(() => {\n    if (prevProps.current) {\n      const changedProps: Record<string, { from: any; to: any }> = {};\n      \n      for (const key of Object.keys(props)) {\n        if (!ignoreProps.includes(key) && prevProps.current[key] !== props[key]) {\n          changedProps[key] = {\n            from: prevProps.current[key],\n            to: props[key],\n          };\n        }\n      }\n\n      if (Object.keys(changedProps).length > 0) {\n        renderCount.current++;\n        const reRenderInfo: ReRenderInfo = {\n          componentName,\n          timestamp: Date.now(),\n          changedProps,\n          renderCount: renderCount.current,\n        };\n\n        if (trackStack) {\n          reRenderInfo.stackTrace = new Error().stack;\n        }\n\n        incrementReRender(componentName);\n      }\n    }\n    prevProps.current = props;\n  });\n}\n\nlet renderTrackingSetup = false;\n\n/**\n * Sets up global render tracking with configuration options.\n * @param options Configuration options for render tracking\n */\nexport function setupRenderTracking(options: {\n  debug?: boolean;\n  trackStack?: boolean;\n} = {}) {\n  if (!renderTrackingSetup) {\n    renderTrackingSetup = true;\n  }\n}\n"
  },
  {
    "path": "src/metrics/screen.ts",
    "content": "import { useEffect, useRef, useCallback } from 'react';\nimport { useMetricsStore } from '../store/metricsStore';\n\n/**\n * Hook to track screen performance metrics.\n * @param screenName Name of the current screen\n */\nexport function useScreenMetrics(screenName: string) {\n  const setCurrentScreen = useMetricsStore((state) => state.setCurrentScreen);\n  const screens = useMetricsStore((state) => state.screens);\n  const prevScreenRef = useRef<string | null>(null);\n  const mountedRef = useRef(true);\n\n  // Memoize the screen change handler\n  const handleScreenChange = useCallback(() => {\n    const isNewScreen = prevScreenRef.current !== screenName;\n    if (isNewScreen) {\n      prevScreenRef.current = screenName;\n      setCurrentScreen(screenName);\n    }\n  }, [screenName, setCurrentScreen]);\n\n  // Handle screen changes\n  useEffect(() => {\n    handleScreenChange();\n  }, [handleScreenChange]);\n\n  // Handle cleanup\n  useEffect(() => {\n    mountedRef.current = true;\n\n    return () => {\n      mountedRef.current = false;\n    };\n  }, [screenName]);\n} "
  },
  {
    "path": "src/metrics/startup.ts",
    "content": "export {};\n\nimport { useMetricsStore } from '../store/metricsStore';\n\n// Global app start time (should be set as early as possible in the app entrypoint)\ndeclare global {\n  var __OPTIC_APP_START_TIME__: number | undefined;\n  var __OPTIC_STARTUP_CAPTURED__: boolean;\n}\n\nif (global.__OPTIC_APP_START_TIME__ === undefined) {\n  global.__OPTIC_APP_START_TIME__ = Date.now();\n}\n\nif (global.__OPTIC_STARTUP_CAPTURED__ === undefined) {\n  global.__OPTIC_STARTUP_CAPTURED__ = false;\n}\n\n/**\n * Measures time since global app start and logs it to the console.\n * Only measures once and stores the result.\n */\nexport function trackStartupTime() {\n  // Only measure startup time once\n  if (global.__OPTIC_STARTUP_CAPTURED__) {\n    return;\n  }\n\n  const start = global.__OPTIC_APP_START_TIME__ || Date.now();\n  \n  // Use requestAnimationFrame to ensure we measure after initial render\n  requestAnimationFrame(() => {\n    if (!global.__OPTIC_STARTUP_CAPTURED__) {\n      const duration = Date.now() - start;\n      \n      // Mark as captured before setting the time to prevent race conditions\n      global.__OPTIC_STARTUP_CAPTURED__ = true;\n      \n      useMetricsStore.getState().setStartupTime(duration);\n    }\n  });\n}\n"
  },
  {
    "path": "src/metrics/trace.ts",
    "content": "import { useMetricsStore } from '../store/metricsStore';\n\ninterface Trace {\n  interactionName: string;\n  componentName: string;\n  duration: number;\n  timestamp: number;\n}\n\nclass TraceManager {\n  private activeTraces: Map<string, number> = new Map();\n  private traces: Trace[] = [];\n  private readonly MAX_TRACES = 10;\n\n  /**\n   * Start tracing an interaction\n   * @param interactionName Name of the interaction (e.g., 'OpenModal')\n   */\n  startTrace(interactionName: string) {\n    if (!__DEV__) return;\n    this.activeTraces.set(interactionName, Date.now());\n  }\n\n  /**\n   * End tracing and record the duration\n   * @param interactionName Name of the interaction\n   * @param componentName Name of the component that rendered\n   */\n  endTrace(interactionName: string, componentName: string) {\n    if (!__DEV__) return;\n\n    const startTime = this.activeTraces.get(interactionName);\n    if (!startTime) return;\n\n    const duration = Date.now() - startTime;\n    const trace: Trace = {\n      interactionName,\n      componentName,\n      duration,\n      timestamp: Date.now()\n    };\n\n    this.traces.unshift(trace);\n    if (this.traces.length > this.MAX_TRACES) {\n      this.traces.pop();\n    }\n\n    useMetricsStore.getState().setTrace(trace);\n    this.activeTraces.delete(interactionName);\n  }\n\n  /**\n   * Get all traces\n   */\n  getTraces(): Trace[] {\n    return [...this.traces];\n  }\n\n  /**\n   * Clear all traces\n   */\n  clearTraces() {\n    this.traces = [];\n    this.activeTraces.clear();\n  }\n}\n\nexport const traceManager = new TraceManager();\n\n// Export the public API\nexport const startTrace = traceManager.startTrace.bind(traceManager);\nexport const endTrace = traceManager.endTrace.bind(traceManager); "
  },
  {
    "path": "src/overlay/Overlay.tsx",
    "content": "import React, { useRef, useState } from 'react';\nimport { View, Text, StyleSheet, PanResponder, Animated, Dimensions, TouchableOpacity, Clipboard, Image, Platform, Linking, ScrollView } from 'react-native';\nimport { useMetricsStore } from '../store/metricsStore';\nimport { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';\nimport { getFPSColor } from '../metrics/fps';\nimport { getNetworkColor, getLatestNetworkRequest } from '../metrics/network';\nimport { opticEnabled } from '../store/metricsStore';\n\nconst minimizeImageUrl = 'https://img.icons8.com/material-rounded/24/ffffff/minus.png';\nconst maximizeImageUrl = 'https://img.icons8.com/ios-filled/50/ffffff/full-screen.png';\n\nconst { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window');\n\nconst METRICS_THRESHOLDS = {\n  STARTUP: {\n    good: 1000, // 1 second\n    warning: 2000, // 2 seconds\n  },\n  TRACE: {\n    good: 50, // 50ms\n    warning: 200, // 200ms\n  },\n  FPS: {\n    good: 55, // 55+ FPS is good\n    warning: 30, // 30+ FPS is acceptable\n  },\n};\n\nconst getMetricColor = (metric: 'STARTUP' | 'TRACE' | 'FPS', value: number) => {\n  const thresholds = METRICS_THRESHOLDS[metric];\n  if (metric === 'FPS') {\n    if (value >= thresholds.good) return '#4CAF50';\n    if (value >= thresholds.warning) return '#FFC107';\n    return '#F44336';\n  }\n  if (value <= thresholds.good) return '#4CAF50';\n  if (value <= thresholds.warning) return '#FFC107';\n  return '#F44336';\n};\n\nconst getStatusColor = (status: number): string => {\n  if (status >= 200 && status < 300) return '#4CAF50'; // Green for success\n  if (status >= 400) return '#F44336'; // Red for client/server errors\n  return '#FFC107'; // Yellow for other status codes\n};\n\nexport const Overlay: React.FC = () => {\n  if (!opticEnabled) return null;\n\n  const insets = useSafeAreaInsets();\n  const currentScreen = useMetricsStore((state) => state.currentScreen);\n  const screens = useMetricsStore((state) => state.screens);\n  const startupTime = useMetricsStore((state) => state.startupTime);\n  const networkRequests = useMetricsStore((state) => state.networkRequests);\n  const traces = useMetricsStore((state) => state.traces);\n  const [isMinimized, setIsMinimized] = useState(false);\n  const [isNetworkExpanded, setIsNetworkExpanded] = useState(false);\n  const [isTracesExpanded, setIsTracesExpanded] = useState(false);\n  const [isCollapsed, setIsCollapsed] = useState(false);\n  const [expanded, setExpanded] = useState(false);\n  const [expandedTrace, setExpandedTrace] = useState(false);\n\n  const pan = useRef(new Animated.ValueXY()).current;\n  const [position, setPosition] = useState({ \n    x: (SCREEN_WIDTH - 300) / 2,\n    y: insets.top + 20\n  });\n\n  const panResponder = useRef(\n    PanResponder.create({\n      onStartShouldSetPanResponder: () => true,\n      onPanResponderMove: (_, gesture) => {\n        const newX = position.x + gesture.dx;\n        const newY = position.y + gesture.dy;\n\n        // Keep within screen bounds with padding\n        const boundedX = Math.max(10, Math.min(newX, SCREEN_WIDTH - 290));\n        const boundedY = Math.max(insets.top + 10, Math.min(newY, SCREEN_HEIGHT - 200));\n\n        // Update position directly without animation\n        setPosition({ x: boundedX, y: boundedY });\n      },\n      onPanResponderRelease: () => {\n        // Reset the pan value without animation\n        pan.setValue({ x: 0, y: 0 });\n      },\n    })\n  ).current;\n\n  const currentScreenMetrics = currentScreen ? screens[currentScreen] : null;\n  const latestRequest = getLatestNetworkRequest();\n  const latestTrace = traces[traces.length - 1];\n\n  const handleCopyMetrics = () => {\n    try {\n      const metrics = {\n        currentScreen: currentScreen || 'No Screen',\n        startupTime: startupTime ? `${startupTime.toFixed(2)}ms` : 'N/A',\n        fps: currentScreenMetrics?.fps ? `${currentScreenMetrics.fps.toFixed(1)} FPS` : 'N/A',\n        latestNetworkRequest: latestRequest ? {\n          url: latestRequest.url,\n          duration: `${latestRequest.duration.toFixed(2)}ms`,\n          status: latestRequest.status\n        } : 'N/A',\n        latestTrace: latestTrace ? {\n          interactionName: latestTrace.interactionName,\n          componentName: latestTrace.componentName,\n          duration: `${latestTrace.duration.toFixed(2)}ms`,\n        } : 'N/A',\n      };\n      \n      // Use Clipboard API instead of console.log\n      if (Platform.OS === 'ios' || Platform.OS === 'android') {\n        Clipboard.setString(JSON.stringify(metrics, null, 2));\n      }\n    } catch (error) {\n      console.error('Error copying metrics:', error);\n    }\n  };\n\n  const handleOpenWebsite = () => {\n    Linking.openURL('https://useoptic.dev');\n  };\n\n  const renderCollapsedView = () => (\n    <View style={styles.collapsedContainer}>\n      <View style={styles.collapsedMetrics}>\n        <Text style={styles.collapsedMetric}>\n          🚀 {startupTime !== null ? `${startupTime.toFixed(1)}ms` : '...'}\n        </Text>\n        <Text style={styles.collapsedMetric}>\n          🎮 {currentScreenMetrics?.fps !== null && currentScreenMetrics?.fps !== undefined ? `${currentScreenMetrics.fps.toFixed(1)}` : '...'}\n        </Text>\n      </View>\n    </View>\n  );\n\n  if (!currentScreen) return null;\n\n  return (\n    <SafeAreaView style={styles.safeArea} pointerEvents=\"box-none\">\n      <Animated.View\n        style={[\n          styles.overlay,\n          isCollapsed ? styles.collapsedOverlay : null,\n          {\n            left: position.x,\n            top: position.y,\n          },\n        ]}\n        {...panResponder.panHandlers}\n      >\n        <TouchableOpacity \n          style={styles.dragHandle} \n          onPress={() => setIsCollapsed(!isCollapsed)}\n        />\n        \n        {isCollapsed ? (\n          renderCollapsedView()\n        ) : (\n          <>\n            <View style={styles.header}>\n              <View style={styles.headerTop}>\n                <Text style={styles.text}>Performance Metrics</Text>\n                <View style={styles.headerButtons}>\n                  <TouchableOpacity\n                    style={[styles.iconButton]}\n                    onPress={() => setIsMinimized(!isMinimized)}\n                  >\n                    <Image\n                      source={{ uri: isMinimized ? maximizeImageUrl : minimizeImageUrl }}\n                      style={styles.icon}\n                    />\n                  </TouchableOpacity>\n                </View>\n              </View>\n              <View style={styles.screenNameContainer}>\n                <Text style={styles.screenName}>\n                  {currentScreen || 'No Screen'}\n                </Text>\n              </View>\n            </View>\n            \n            {!isMinimized && (\n              <ScrollView style={styles.content}>\n                <View style={styles.section}>\n                  <Text style={styles.sectionTitle}>Performance Metrics</Text>\n                  {startupTime && (\n                    <Text style={[styles.metric, { color: getMetricColor('STARTUP', startupTime) }]}>Startup: {startupTime.toFixed(2)}ms</Text>\n                  )}\n                  {currentScreenMetrics?.fps && (\n                    <Text style={[styles.metric, { color: getMetricColor('FPS', currentScreenMetrics.fps) }]}>FPS: {currentScreenMetrics.fps.toFixed(1)}</Text>\n                  )}\n                </View>\n\n                {latestRequest && (\n                  <View style={styles.section}>\n                    <TouchableOpacity\n                      style={styles.sectionHeader}\n                      onPress={() => setIsNetworkExpanded(!isNetworkExpanded)}\n                    >\n                      <Text style={styles.sectionTitle}>Network Request</Text>\n                      <Text style={styles.expandIcon}>{isNetworkExpanded ? '▼' : '▶'}</Text>\n                    </TouchableOpacity>\n                    <View style={styles.networkInfo}>\n                      <Text style={[styles.metric, { color: getNetworkColor(latestRequest.duration) }]}>→ {Math.round(latestRequest.duration).toFixed(1)}ms</Text>\n                      {isNetworkExpanded && (\n                        <View style={styles.expandedNetworkInfo}>\n                          <View style={styles.statusContainer}>\n                            <Text style={[styles.statusCode, { color: getStatusColor(latestRequest.status) }]}>\n                              {latestRequest.status} {latestRequest.status >= 500 ? '🔴' : latestRequest.status >= 400 ? '🟠' : '🟢'}\n                            </Text>\n                          </View>\n                          <View style={styles.urlContainer}>\n                            <Text style={styles.networkUrl} numberOfLines={1} ellipsizeMode=\"middle\">{latestRequest.url}</Text>\n                          </View>\n                        </View>\n                      )}\n                    </View>\n                  </View>\n                )}\n\n                {traces.length > 0 && (\n                  <View style={styles.section}>\n                    <TouchableOpacity\n                      style={styles.sectionHeader}\n                      onPress={() => setIsTracesExpanded(!isTracesExpanded)}\n                    >\n                      <Text style={styles.sectionTitle}>Recent Traces</Text>\n                      <Text style={styles.expandIcon}>{isTracesExpanded ? '▼' : '▶'}</Text>\n                    </TouchableOpacity>\n                    {isTracesExpanded && traces.slice(-3).reverse().map((trace, idx) => (\n                      <View key={idx} style={styles.traceRow}>\n                        <Text style={styles.traceScreen}>{trace.interactionName} → {trace.componentName}</Text>\n                        <Text style={[styles.traceDuration, { color: getMetricColor('TRACE', trace.duration) }]}>{trace.duration.toFixed(1)}ms</Text>\n                      </View>\n                    ))}\n                  </View>\n                )}\n\n                <TouchableOpacity style={styles.copyButton} onPress={handleCopyMetrics}>\n                  <Text style={styles.copyButtonText}>Copy Metrics</Text>\n                </TouchableOpacity>\n              </ScrollView>\n            )}\n            <View style={styles.poweredByContainer}>\n              <TouchableOpacity onPress={handleOpenWebsite}>\n                <Text style={styles.poweredByText}>Powered by Optic</Text>\n              </TouchableOpacity>\n            </View>\n          </>\n        )}\n      </Animated.View>\n    </SafeAreaView>\n  );\n};\n\nconst styles = StyleSheet.create({\n  safeArea: {\n    position: 'absolute',\n    top: 0,\n    left: 0,\n    right: 0,\n    bottom: 0,\n    pointerEvents: 'box-none',\n  },\n  overlay: {\n    position: 'absolute',\n    backgroundColor: 'rgba(18, 18, 23, 0.98)',\n    paddingVertical: 8,\n    paddingHorizontal: 16,\n    borderRadius: 16,\n    zIndex: 9999,\n    elevation: 20,\n    width: 320,\n    shadowColor: '#000',\n    shadowOffset: {\n      width: 0,\n      height: 8,\n    },\n    shadowOpacity: 0.4,\n    shadowRadius: 8,\n  },\n  collapsedOverlay: {\n    width: 'auto',\n    paddingVertical: 6,\n    paddingHorizontal: 12,\n  },\n  collapsedContainer: {\n    flexDirection: 'row',\n    alignItems: 'center',\n  },\n  collapsedMetrics: {\n    flexDirection: 'row',\n    gap: 16,\n  },\n  collapsedMetric: {\n    color: '#fff',\n    fontSize: 14,\n    fontWeight: '600',\n  },\n  dragHandle: {\n    width: 40,\n    height: 4,\n    backgroundColor: 'rgba(255, 255, 255, 0.2)',\n    borderRadius: 4,\n    alignSelf: 'center',\n    marginBottom: 6,\n  },\n  header: {\n    marginBottom: 8,\n    borderBottomWidth: 1,\n    borderBottomColor: 'rgba(255, 255, 255, 0.15)',\n    paddingBottom: 6,\n  },\n  headerTop: {\n    flexDirection: 'row',\n    justifyContent: 'space-between',\n    alignItems: 'center',\n  },\n  headerButtons: {\n    flexDirection: 'row',\n    gap: 8,\n  },\n  iconButton: {\n    padding: 6,\n    borderRadius: 10,\n    backgroundColor: 'rgba(255, 255, 255, 0.15)',\n    width: 32,\n    height: 32,\n    justifyContent: 'center',\n    alignItems: 'center',\n  },\n  icon: {\n    width: 18,\n    height: 18,\n    resizeMode: 'contain',\n  },\n  text: {\n    color: '#fff',\n    fontWeight: '700',\n    fontSize: 16,\n    letterSpacing: 0.3,\n  },\n  screenNameContainer: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    marginTop: 6,\n    backgroundColor: 'rgba(255, 255, 255, 0.12)',\n    paddingHorizontal: 10,\n    paddingVertical: 4,\n    borderRadius: 8,\n  },\n  screenName: {\n    color: '#fff',\n    fontSize: 13,\n    fontWeight: '600',\n    fontStyle: 'italic',\n  },\n  content: {\n    marginTop: 6,\n  },\n  section: {\n    marginBottom: 12,\n    backgroundColor: 'rgba(255, 255, 255, 0.05)',\n    borderRadius: 12,\n    padding: 10,\n  },\n  sectionHeader: {\n    flexDirection: 'row',\n    justifyContent: 'space-between',\n    alignItems: 'center',\n    marginBottom: 6,\n  },\n  sectionTitle: {\n    color: '#fff',\n    fontSize: 14,\n    fontWeight: 'bold',\n    letterSpacing: 0.2,\n  },\n  metric: {\n    color: '#fff',\n    fontSize: 13,\n    marginBottom: 3,\n    fontWeight: '500',\n  },\n  traceDetails: {\n    marginTop: 4,\n    padding: 8,\n    backgroundColor: 'rgba(255, 255, 255, 0.08)',\n    borderRadius: 8,\n  },\n  traceText: {\n    color: '#fff',\n    fontSize: 12,\n    marginBottom: 2,\n  },\n  copyButton: {\n    backgroundColor: 'rgba(33, 150, 243, 0.15)',\n    padding: 8,\n    borderRadius: 8,\n    alignItems: 'center',\n    marginTop: 8,\n    borderWidth: 1,\n    borderColor: 'rgba(33, 150, 243, 0.3)',\n  },\n  copyButtonText: {\n    color: '#2196F3',\n    fontSize: 12,\n    fontWeight: '600',\n    letterSpacing: 0.3,\n  },\n  poweredByContainer: {\n    alignSelf: 'flex-end',\n    marginTop: 6,\n    marginBottom: -2,\n    backgroundColor: 'rgba(0, 0, 0, 0.4)',\n    borderRadius: 8,\n    paddingHorizontal: 10,\n    paddingVertical: 4,\n  },\n  poweredByText: {\n    color: '#fff',\n    fontSize: 11,\n    fontWeight: '600',\n    opacity: 0.8,\n    letterSpacing: 0.3,\n    textDecorationLine: 'underline',\n  },\n  expandIcon: {\n    color: '#fff',\n    fontSize: 12,\n    fontWeight: 'bold',\n  },\n  networkInfo: {\n    marginTop: 4,\n  },\n  expandedNetworkInfo: {\n    marginTop: 6,\n    padding: 8,\n    backgroundColor: 'rgba(255, 255, 255, 0.08)',\n    borderRadius: 8,\n  },\n  statusContainer: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    marginBottom: 4,\n  },\n  statusCode: {\n    color: '#fff',\n    fontSize: 13,\n    fontWeight: 'bold',\n  },\n  urlContainer: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    marginLeft: 8,\n  },\n  networkUrl: {\n    color: '#fff',\n    fontSize: 12,\n    marginBottom: 2,\n  },\n  traceRow: {\n    flexDirection: 'row',\n    justifyContent: 'space-between',\n    alignItems: 'center',\n    marginBottom: 4,\n    paddingVertical: 4,\n    paddingHorizontal: 8,\n    backgroundColor: 'rgba(255, 255, 255, 0.05)',\n    borderRadius: 6,\n  },\n  traceScreen: {\n    color: '#fff',\n    fontSize: 12,\n    fontWeight: 'bold',\n  },\n  traceDuration: {\n    color: '#fff',\n    fontSize: 12,\n    fontWeight: '500',\n  },\n});\n"
  },
  {
    "path": "src/providers/OpticProvider.tsx",
    "content": "import React, { useEffect, useRef } from 'react';\nimport { useMetricsStore } from '../store/metricsStore';\nimport { Overlay } from '../overlay/Overlay';\nimport { useNavigation, useRoute, useNavigationContainerRef } from '@react-navigation/native';\nimport { usePathname, useSegments } from 'expo-router';\nimport { initRenderTracking } from '../metrics/globalRenderTracking';\nimport { FPSManager } from '../metrics/fps';\n\ninterface OpticProviderProps {\n  children: React.ReactNode;\n  /**\n   * Enable or disable specific metrics\n   */\n  metrics?: {\n    enabled?: boolean;\n    startup?: boolean;\n    reRenders?: boolean;\n    fps?: boolean;\n    network?: boolean;\n    traces?: boolean;\n  };\n  /**\n   * Show or hide the performance overlay\n   */\n  showOverlay?: boolean;\n}\n\nconst defaultMetrics = {\n  enabled: true,\n  startup: true,\n  reRenders: true,\n  fps: true,\n  network: true,\n  traces: true,\n};\n\nexport const OpticProvider: React.FC<OpticProviderProps> = ({ \n  children,\n  metrics = defaultMetrics,\n  showOverlay = true\n}) => {\n  const { setCurrentScreen } = useMetricsStore();\n  const currentScreen = useMetricsStore((state) => state.currentScreen);\n  const pathname = usePathname();\n  const segments = useSegments();\n  const navigationRef = useNavigationContainerRef();\n  const fpsManager = React.useRef<FPSManager | null>(null);\n\n  // Navigation hooks\n  const navigation = useNavigation();\n  const route = useRoute();\n\n  // Initialize re-render tracking if enabled\n  useEffect(() => {\n    if (metrics.reRenders) {\n      initRenderTracking();\n    }\n  }, [metrics.reRenders]);\n\n  useEffect(() => {\n    if (metrics.enabled && metrics.fps) {\n      fpsManager.current = new FPSManager();\n      fpsManager.current.startTracking();\n    }\n\n    return () => {\n      if (fpsManager.current) {\n        fpsManager.current.stopTracking();\n      }\n    };\n  }, [metrics.enabled, metrics.fps]);\n\n  // Function to get the current screen name\n  const getCurrentScreenName = () => {\n    // Try to get screen name from Expo Router first\n    if (pathname) {\n      return pathname;\n    }\n\n    // Fallback to React Navigation\n    if (navigationRef.current) {\n      const currentRoute = navigationRef.current.getCurrentRoute();\n      if (currentRoute?.name) {\n        return currentRoute.name;\n      }\n    }\n\n    // If no screen name is found, use the first segment or default to 'index'\n    return segments[0] || 'index';\n  };\n\n  // Handle screen changes and initial route\n  useEffect(() => {\n    const screenName = getCurrentScreenName();\n    \n    // Always set the current screen, even if it's the same\n    // This ensures we capture the initial route\n    setCurrentScreen(screenName);\n  }, [pathname, segments, navigationRef.current]);\n\n  return (\n    <>\n      {children}\n      {showOverlay && <Overlay />}\n    </>\n  );\n}; "
  },
  {
    "path": "src/store/metricsStore.ts",
    "content": "import { create } from 'zustand';\nimport { InitOpticOptions } from '../core/initOptic';\n\nexport interface NetworkRequest {\n  url: string;\n  method: string;\n  duration: number;\n  status: number;\n  [key: string]: any; // for any extra fields\n}\n\nexport interface Trace {\n  interactionName: string;\n  componentName: string;\n  duration: number;\n  timestamp: number;\n}\n\nexport interface MetricsState {\n  currentScreen: string | null;\n  screens: Record<string, { \n    reRenderCounts: Record<string, number>;\n    fps: number | null;\n  }>;\n  networkRequests: NetworkRequest[];\n  traces: Trace[];\n  startupTime: number | null;\n  setCurrentScreen: (screenName: string | null) => void;\n  incrementReRender: (componentName: string) => void;\n  setStartupTime: (time: number) => void;\n  setFPS: (fps: number, screenName: string) => void;\n  addNetworkRequest: (request: NetworkRequest) => void;\n  setTrace: (trace: Trace) => void;\n}\n\nexport const useMetricsStore = create<MetricsState>((set, get) => ({\n  currentScreen: null,\n  screens: {},\n  networkRequests: [],\n  traces: [],\n  startupTime: null,\n\n  setCurrentScreen: (screenName) => {\n    set((state) => {\n      // Initialize screen metrics if they don't exist\n      if (screenName && !state.screens[screenName]) {\n        return {\n          currentScreen: screenName,\n          screens: {\n            ...state.screens,\n            [screenName]: {\n              reRenderCounts: {},\n              fps: null,\n            },\n          },\n        };\n      }\n      return { currentScreen: screenName };\n    });\n  },\n\n  incrementReRender: (componentName) => {\n    const state = get();\n    if (!state.currentScreen) return;\n    \n    const currentScreen = state.screens[state.currentScreen];\n    const currentCount = currentScreen.reRenderCounts[componentName] || 0;\n    \n    set((state) => ({\n      screens: {\n        ...state.screens,\n        [state.currentScreen!]: {\n          ...currentScreen,\n          reRenderCounts: {\n            ...currentScreen.reRenderCounts,\n            [componentName]: currentCount + 1,\n          },\n        },\n      },\n    }));\n  },\n\n  setStartupTime: (time) => {\n    set({ startupTime: time });\n  },\n\n  setFPS: (fps, screenName) => {\n    set((state) => ({\n      screens: {\n        ...state.screens,\n        [screenName]: {\n          ...state.screens[screenName],\n          fps,\n        },\n      },\n    }));\n  },\n\n  addNetworkRequest: (request) => {\n    set((state) => ({\n      networkRequests: [...state.networkRequests, request].slice(-50), // Keep last 50 requests\n    }));\n  },\n\n  setTrace: (trace) => {\n    set((state) => ({\n      traces: [...state.traces, trace].slice(-10), // Keep last 10 traces\n    }));\n  },\n}));\n\nexport let opticEnabled = true;\n\nexport function setOpticEnabled(value: boolean) {\n  opticEnabled = value;\n}\n\nexport function initOptic(options: InitOpticOptions = {}) {\n  const { enabled = true, onMetricsLogged } = options;\n  opticEnabled = enabled;\n  if (!enabled) {\n    return;\n  }\n  // ...rest of your logic...\n}"
  },
  {
    "path": "src/types/global.d.ts",
    "content": "declare module '*.png' {\n  const value: any;\n  export default value;\n} "
  },
  {
    "path": "src/utils/logger.ts",
    "content": "const isDevelopment = process.env.NODE_ENV === 'development';\n\nexport const logger = {\n  debug: (...args: any[]) => {\n    if (isDevelopment) {\n      console.log('[useoptic]', ...args);\n    }\n  },\n  warn: (...args: any[]) => {\n    if (isDevelopment) {\n      console.warn('[useoptic]', ...args);\n    }\n  },\n  error: (...args: any[]) => {\n      console.error('[useoptic]', ...args);\n  }\n}; "
  },
  {
    "path": "tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n      \"target\": \"ES2017\",\n      \"module\": \"ESNext\",\n      \"lib\": [\"ES2017\", \"DOM\"],\n      \"jsx\": \"react\",\n      \"declaration\": true,\n      \"declarationMap\": true,\n      \"sourceMap\": true,\n      \"outDir\": \"dist\",\n      \"rootDir\": \"src\",\n      \"strict\": true,\n      \"moduleResolution\": \"node\",\n      \"allowSyntheticDefaultImports\": true,\n      \"esModuleInterop\": true,\n      \"skipLibCheck\": true,\n      \"forceConsistentCasingInFileNames\": true\n    },\n    \"include\": [\"src\"],\n    \"exclude\": [\"node_modules\", \"dist\"]\n  }"
  },
  {
    "path": "tsup.config.ts",
    "content": "import { defineConfig } from 'tsup';\n\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  dts: true,\n  format: ['esm', 'cjs'],\n  outDir: 'dist',\n  sourcemap: true,\n  clean: true,\n  external: ['react', 'react-native'],\n  esbuildOptions(options) {\n    options.jsx = 'automatic';\n  },\n});"
  }
]