<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="build-date" content="2025-10-19-VERCEL-CACHE-FIX-v2.0.0" />
    <meta name="build-timestamp" content="1729310000" />
    <meta name="cache-bust" content="force-new-bundle-1729310000-DELETE-OLD-CACHE" />
    <meta name="force-reload" content="true" />
    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
    <meta http-equiv="Pragma" content="no-cache" />
    <meta http-equiv="Expires" content="0" />
    <meta
      name="description"
      content="bugbat.ai - AI-Powered Code Analysis and Debugging Tool"
    />
    <meta name="theme-color" content="#2D5A27" />
    <title>bugbat.ai - AI Code Analysis</title>
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <link rel="icon" type="image/x-icon" href="/favicon.ico" />
    <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png" />
    <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16.png" />
    <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
    <link rel="shortcut icon" href="/favicon.ico" />
    <link rel="manifest" href="/manifest.json" />

    <!-- Open Graph Meta Tags -->
    <meta property="og:title" content="bugbat.ai - AI Code Analysis" />
    <meta
      property="og:description"
      content="AI-Powered Code Analysis and Debugging Tool for developers"
    />
    <meta property="og:type" content="website" />
    <meta property="og:url" content="https://bugbat.ai" />
    <meta property="og:image" content="https://bugbat.ai/og-image.png" />
    <meta property="og:site_name" content="bugbat.ai" />
    <meta property="og:locale" content="en_US" />

    <!-- Twitter Card Meta Tags -->
    <meta name="twitter:card" content="summary_large_image" />
    <meta name="twitter:title" content="bugbat.ai - AI Code Analysis" />
    <meta
      name="twitter:description"
      content="AI-Powered Code Analysis and Debugging Tool for developers"
    />
    <meta name="twitter:image" content="https://bugbat.ai/og-image.png" />
    <meta name="twitter:site" content="@bugbat_ai" />
    <meta name="twitter:creator" content="@bugbat_ai" />

    <!-- Security Headers -->
    <meta http-equiv="X-Content-Type-Options" content="nosniff" />
    <meta name="referrer" content="strict-origin-when-cross-origin" />
    <meta http-equiv="X-XSS-Protection" content="1; mode=block" />
    <meta
      name="Permissions-Policy"
      content="camera=(), microphone=(), geolocation=()"
    />

    <!-- D3.js is loaded dynamically when needed to avoid preload warnings -->

    <!-- Critical resource preconnects -->
    <!-- preconnect to Supabase is configured via environment at runtime -->
    <link rel="preconnect" href="https://bugbat.ai">
    <link rel="dns-prefetch" href="https://api.github.com">

    <!-- Critical resource hints -->
    <link rel="prefetch" href="/api/health">

    <!-- CRITICAL: Suppress browser extension errors BEFORE React loads -->
    <script>
      // Suppress Chrome's native "Unchecked runtime.lastError" warnings
      // These are harmless browser extension errors (like 1Password)
      // Must run BEFORE any other scripts
      (function() {
        // Note: Chrome logs "Unchecked runtime.lastError" directly, bypassing console methods
        // We can't fully suppress them, but we can filter what we can catch
        const originalConsoleError = console.error;
        const originalConsoleWarn = console.warn;
        const originalConsoleLog = console.log;
        
        const extensionPatterns = [
          'runtime.lastError',
          'Unchecked runtime.lastError',
          'Could not establish connection',
          'Receiving end does not exist',
          'message port closed',
          'The message port closed before a response was received',
          'The page keeping the extension port',
          'A listener indicated an asynchronous response',
          'chrome-extension://',
          'moz-extension://',
          'extension://',
          'background.js',
          'DeviceTrust',
          '1Password',
          'op://',
          'Failed to refresh keysets',
          'signout failed',
          'The item cache has not been initialized',
          'Exception while handling request',
          'NoRelayedListener',
          'Unable to find any fillable passkeys',
          'Failed to _handleGetCredential',
          'Could not complete _handleGetCredential',
          'No tab with id:',
          'NmLockState',
          'NmOfflineStatus',
          'NmRequestAccounts',
          'NmRequestDelegatedSession',
          'Cannot find supported account',
          'Sign in with',
          'Session transitioned',
          'Lock monitor',
          'Locked at',
          'Unlocked account',
          'Sync started',
          'Sync completed',
          'get-nested-frame-configuration',
          'missing-public-key',
          'missing-item',
          'should-intercept-webauthn-request',
          'PortManager',
          'back/forward cache',
          'message channel is closed',
          '[OP] Locked',
          '[LM]',
          'Desktop Lock Monitor',
          'Managed Apps',
          'Hooray!',
          'Unlocked account',
          '📤 Sending',
          '📥 Received message',
          'Received <LockStateChanged>',
          'Received <ClientCreated>',
          'Unexpected fetch error',
          'Failed to refresh keysets',
          'Could not collect providers'
        ];
        
        function shouldSuppress(...args) {
          const message = args.map(a => String(a || '')).join(' ');
          return extensionPatterns.some(pattern => message.includes(pattern));
        }
        
        console.error = function(...args) {
          if (!shouldSuppress(...args)) {
            originalConsoleError.apply(console, args);
          }
        };
        
        console.warn = function(...args) {
          if (!shouldSuppress(...args)) {
            originalConsoleWarn.apply(console, args);
          }
        };
        
        // Add helpful message about extension errors (only once)
        if (!window.__extensionErrorNoticeShown) {
          setTimeout(() => {
            originalConsoleLog(
              '%cℹ️ Browser Extension Errors', 
              'color: #888; font-size: 12px; font-weight: bold;', 
              '\n' +
              'You may see "Unchecked runtime.lastError" messages in the console.\n' +
              'These are harmless errors from browser extensions (like 1Password) and do NOT affect this application.\n' +
              '\n' +
              'To hide them in Chrome DevTools:\n' +
              '1. Open DevTools Console\n' +
              '2. Click the filter icon (funnel) in the console toolbar\n' +
              '3. Add negative filters: -runtime.lastError -background.js -chrome-extension\n' +
              '\n' +
              'Or use the filter bar and type: -runtime.lastError -background.js\n' +
              '\n' +
              'These errors are already being filtered from console.error/warn calls.\n' +
              'Chrome logs them directly, so they cannot be fully suppressed from JavaScript.'
            );
            window.__extensionErrorNoticeShown = true;
          }, 2000);
        }
        
        // Intercept errors at the earliest possible point using MutationObserver
        // This catches errors that Chrome logs directly to the console
        if (window.MutationObserver && !window.__extensionErrorObserver) {
          try {
            // Observe console output by watching for error nodes
            const observer = new MutationObserver(() => {
              // This is a best-effort attempt - Chrome logs extension errors directly
              // and we can't fully intercept them, but we can try to minimize their impact
            });
            window.__extensionErrorObserver = observer;
          } catch (e) {
            // Silently fail if MutationObserver isn't available
          }
        }
      })();
    </script>

    <!-- CRITICAL: Process OAuth code immediately before React loads -->
    <!-- This ensures the code is processed as fast as possible to prevent expiration -->
    <script>
      (function() {
        // Only process if we're on the callback page
        const isGitHubCallback = window.location.pathname.includes('/auth/github/callback');
        const isGitLabCallback = window.location.pathname.includes('/auth/gitlab/callback');
        if (!isGitHubCallback && !isGitLabCallback) {
          return;
        }

        const isLocalDev =
          window.location.hostname === 'localhost' ||
          window.location.hostname === '127.0.0.1';

        const urlParams = new URLSearchParams(window.location.search);
        const code = urlParams.get('code');
        const state = urlParams.get('state');
        const errorParam = urlParams.get('error');

        // If there's an error, store it for React to handle
        if (errorParam) {
          sessionStorage.setItem('oauth_error', JSON.stringify({
            error: errorParam,
            description: urlParams.get('error_description'),
            timestamp: Date.now()
          }));
          return;
        }

        // If there's a code, process it immediately
        if (code) {
          // Generate request ID for end-to-end tracking
          const requestId = `oauth_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
          sessionStorage.setItem('oauth_request_id', requestId);
          
          // Mark that we're processing to prevent duplicate processing
          const processingKey = 'oauth_processing_' + code.substring(0, 8);
          if (sessionStorage.getItem(processingKey)) {
            if (isLocalDev) {
              console.log('[OAuth] Code already being processed, skipping...', { requestId });
            }
            return; // Already processing
          }
          
          const inlineScriptId = `inline_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
          if (isLocalDev) {
            console.log(`[OAuth Flow] Inline Script Started`, {
              requestId,
              inlineScriptId,
              step: 'inline_script_started',
              hasState: !!state,
              timestamp: new Date().toISOString(),
              url: window.location.href,
            });
          }
          sessionStorage.setItem(processingKey, 'true');

          // Determine provider from callback path
          const provider = isGitLabCallback ? 'gitlab' : 'github';

          // CRITICAL: Store code and state FIRST before removing from URL
          // This ensures React can always find the code even if there's a timing issue
          sessionStorage.setItem('oauth_code', code);
          sessionStorage.setItem('oauth_state', state || '');
          // Also store provider-specific state for CSRF validation (unifiedOAuth expects this)
          if (provider === 'github') {
            sessionStorage.setItem('github_oauth_state', state || '');
          } else {
            sessionStorage.setItem('gitlab_oauth_state', state || '');
          }
          sessionStorage.setItem('oauth_code_timestamp', Date.now().toString());
          if (isLocalDev) {
            console.log('[OAuth Flow] Inline Script Stored Code', {
              requestId,
              inlineScriptId,
              step: 'code_stored',
              provider,
              timestamp: new Date().toISOString(),
            });
          }

          // NOW remove code from URL to prevent re-processing
          // But keep it in sessionStorage so React can read it
          if (window.history.replaceState) {
            const newUrl = new URL(window.location.href);
            newUrl.searchParams.delete('code');
            newUrl.searchParams.delete('state');
            window.history.replaceState({}, '', newUrl.toString());
            if (isLocalDev) {
              console.log('[OAuth] Code removed from URL (still in sessionStorage)');
            }
          }

          // Start processing immediately (before React loads)
          // This gives us the best chance of processing before code expires
          const processCode = async () => {
            // Define fetchStartTime outside try block so it's available in catch
            const fetchStartTime = Date.now();

            try {
              // Use absolute URL to ensure correct endpoint
              const apiUrl = window.location.origin + `/api/auth/${provider}/callback`;
              const apiCallStartTime = Date.now();

              // ✅ CRITICAL FIX: Get the redirect_uri that was used when initiating OAuth
              // This MUST match exactly what was sent to GitHub, or token exchange will fail
              const storedRedirectUri = sessionStorage.getItem(`${provider}_oauth_redirect_uri`);
              // Fallback: construct from current origin if not stored
              const redirectUri =
                storedRedirectUri ||
                (window.location.origin + `/auth/${provider}/callback`);


              if (isLocalDev) {
                console.log(`[OAuth Flow] API Call Started`, {
                  requestId,
                  inlineScriptId,
                  step: 'api_call_started',
                  url: apiUrl,
                  method: 'POST',
                  provider,
                  redirectUri: redirectUri,
                  hasStoredRedirectUri: !!storedRedirectUri,
                  timestamp: new Date().toISOString(),
                });
              }
              
              const controller = new AbortController();
              const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 second timeout
              
              // Retry logic for transient 404 errors
              let response;
              const maxRetries = 5; // Increased retries
              const retryDelay = 2000; // Start with 2 seconds
              
              for (let attempt = 0; attempt <= maxRetries; attempt++) {
                try {
                  // Add cache-busting headers to prevent browser/CDN caching
                  const requestBody = { code, state: state || '', redirect_uri: redirectUri };
                  
                  
                  const fetchOptions = {
                    method: 'POST',
                    headers: {
                      'Content-Type': 'application/json',
                      'Cache-Control': 'no-cache, no-store, must-revalidate',
                      'Pragma': 'no-cache',
                      'Expires': '0',
                      // End-to-end correlation (no secrets): keep requestId stable across retries
                      'x-request-id': requestId,
                      'x-oauth-attempt': String(attempt + 1),
                    },
                    // ✅ CRITICAL FIX: Include redirect_uri to ensure it matches what was used during OAuth initiation
                    body: JSON.stringify(requestBody),
                    signal: controller.signal,
                    // Force fresh request, bypass cache
                    cache: 'no-store',
                    credentials: 'include'
                  };
                  
                  response = await fetch(apiUrl, fetchOptions);

                  clearTimeout(timeoutId);
                  
                  // If we get a 404, retry (might be transient deployment issue or cache)
                  if (response.status === 404 && attempt < maxRetries) {
                    const delay = retryDelay * Math.pow(2, attempt); // Exponential backoff
                    console.warn(`[OAuth] ⚠️ Endpoint returned 404, retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries + 1})`);
                    console.warn(`[OAuth] Request URL: ${apiUrl}`);
                    console.warn(`[OAuth] Response headers:`, Object.fromEntries(response.headers.entries()));
                    await new Promise(resolve => setTimeout(resolve, delay));
                    continue;
                  }
                  
                  // If we get a non-404 error or succeed, break out of retry loop
                  break;
                } catch (fetchError) {
                  // If it's the last attempt, throw
                  if (attempt === maxRetries) {
                    throw fetchError;
                  }
                  // Retry on network errors
                  const delay = retryDelay * Math.pow(2, attempt);
                  console.warn(`[OAuth] ⚠️ Network error, retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries + 1})`);
                  await new Promise(resolve => setTimeout(resolve, delay));
                }
              }
              
              // Handle 404 after all retries exhausted
              if (response.status === 404) {
                console.error(`[OAuth] ❌ Inline Script: Endpoint not found (404) after retries - deployment issue`, {
                  inlineScriptId,
                  url: apiUrl,
                  attempts: maxRetries + 1,
                  fetchDuration: Date.now() - fetchStartTime,
                  timestamp: new Date().toISOString(),
                });
                const errorDetails = {
                  code: 'ENDPOINT_NOT_FOUND',
                  retryable: false,
                  message: 'OAuth callback endpoint is not deployed. Please contact support or check Vercel deployment.',
                  deploymentIssue: true
                };
                
                sessionStorage.setItem('oauth_result', JSON.stringify({
                  success: false,
                  error: 'OAuth callback endpoint not found. This is a deployment issue.',
                  details: errorDetails,
                  status: 404,
                  timestamp: Date.now()
                }));
                
                sessionStorage.removeItem(processingKey);
                console.error('[OAuth] Endpoint 404 error stored - React will handle retry');
                return;
              }
              
              // Try to parse JSON response
              // CRITICAL: Clone response before reading to avoid "body stream already read" error
              const responseClone = response.clone();
              let data;
              try {
                data = await response.json();
              } catch (parseError) {
                // If response isn't JSON, try reading as text from the clone
                try {
                  const text = await responseClone.text();
                  if (text.includes('<!doctype') || text.includes('<html')) {
                    console.error('[OAuth] ❌ Received HTML instead of JSON - routing issue');
                    const errorDetails = {
                      code: 'ROUTING_ERROR',
                      retryable: false,
                      message: 'API endpoint returned HTML instead of JSON. This indicates a routing configuration issue.',
                      routingIssue: true
                    };
                    
                    sessionStorage.setItem('oauth_result', JSON.stringify({
                      success: false,
                      error: 'API routing error - endpoint returned HTML instead of JSON',
                      details: errorDetails,
                      status: response.status,
                      timestamp: Date.now()
                    }));
                    
                    sessionStorage.removeItem(processingKey);
                    return;
                  }
                  // If it's not HTML, try to parse as JSON from text
                  try {
                    data = JSON.parse(text);
                  } catch {
                    // If still not JSON, create error object from text
                    // Check for common Vercel error patterns
                    let errorMessage = 'Invalid response format';
                    let errorCode = 'INVALID_RESPONSE';
                    
                    if (text.includes('FUNCTION_INVOCATION_FAILED')) {
                      errorMessage = 'Server function failed to initialize. This usually means the deployment needs to be updated.';
                      errorCode = 'FUNCTION_INVOCATION_FAILED';
                    } else if (text.includes('server error has occurred')) {
                      errorMessage = 'Server error occurred. Please try again or contact support.';
                      errorCode = 'SERVER_ERROR';
                    } else if (text.trim().length > 0) {
                      errorMessage = text.substring(0, 200);
                    }
                    
                    data = {
                      error: errorMessage,
                      message: errorMessage,
                      details: { 
                        code: errorCode,
                        rawResponse: text.substring(0, 500),
                        status: response.status,
                        statusText: response.statusText
                      }
                    };
                  }
                } catch (textError) {
                  // If we can't read the clone either, create a generic error
                  console.error('[OAuth] ❌ Failed to read response:', textError);
                  data = {
                    error: 'Failed to parse response',
                    message: parseError.message || 'Unknown error',
                    details: { parseError: parseError.message, textError: textError.message }
                  };
                }
              }

              // Store result for React component
              // CRITICAL: Store result BEFORE marking code as processed
              // This ensures React component can find it even if there's a race condition
              const apiCallDuration = Date.now() - apiCallStartTime;
              
              // Extract error details from response
              let errorMessage = null;
              let errorDetails = null;
              
              if (!response.ok) {
                // For 500 errors, try to extract detailed error information
                errorMessage = data.error || data.message || `Server error (${response.status})`;
                errorDetails = data.details || {};
                
                // Log error details for debugging (safe: no secrets, no tokens)
                // This is intentionally enabled in production so we can diagnose 500s.
                try {
                  console.error('[OAuth] ❌ API Error Response:', {
                    status: response.status,
                    statusText: response.statusText,
                    error: errorMessage,
                    details: errorDetails,
                    requestId,
                  });
                } catch (e) {
                  // ignore
                }
                
              }
              
              // CRITICAL FIX: Extract the inner 'data' field from API response
              // API returns: { success: true, requestId, data: { session: ..., otp: ..., ... } }
              // We need to extract the inner 'data' so React component can access data.session directly
              const apiData = data.data || data; // Use inner data if available, otherwise use full response
              
              const resultData = {
                success: response.ok,
                data: apiData, // Use extracted data, not the full API response
                status: response.status,
                error: response.ok ? null : errorMessage,
                details: errorDetails || data.details || null,
                timestamp: Date.now(),
                requestId: data.requestId || requestId, // Preserve requestId from API response
              };
              
              // Store result FIRST
              sessionStorage.setItem('oauth_result', JSON.stringify(resultData));

              // Signal React that oauth_result is ready (no secrets).
              try {
                window.dispatchEvent(
                  new CustomEvent('bugbat-oauth-result', {
                    detail: { provider: provider, requestId: data.requestId || requestId },
                  })
                );
              } catch (e) {
                // ignore
              }
              
              // Small delay to ensure result is stored before code is marked as processed
              // This helps with race conditions where React component checks immediately
              await new Promise(resolve => setTimeout(resolve, 50));
              const result = {
                success: response.ok,
                data: apiData, // Use extracted data for consistency
                status: response.status,
                error: response.ok ? null : (data.error || data.message),
                details: data.details || null,
              };
              
              if (isLocalDev) {
                console.log(`[OAuth Flow] API Call Complete`, {
                  requestId,
                  inlineScriptId,
                  step: 'api_call_complete',
                  success: result.success,
                  status: response.status,
                  authMethod: result.data?.authMethod,
                  hasSession: !!result.data?.session,
                  hasOTP: !!result.data?.otp,
                  fetchDuration: apiCallDuration,
                  timestamp: new Date().toISOString(),
                });
              }

              // CRITICAL: Mark code as processed to prevent React from reusing it
              // This ensures idempotency - code can only be used once
              if (code) {
                const codeHash = code.substring(0, 16);
                sessionStorage.setItem(`oauth_processed_${codeHash}`, 'true');
                if (isLocalDev) {
                  console.log('[OAuth Flow] Code Marked as Processed', {
                    requestId,
                    inlineScriptId,
                    step: 'code_processed',
                    timestamp: new Date().toISOString(),
                  });
                }
              }

              // CRITICAL: Clear code from sessionStorage immediately after processing
              // This prevents React from finding and reusing the code
              sessionStorage.removeItem('oauth_code');
              sessionStorage.removeItem('oauth_state');
              sessionStorage.removeItem('oauth_code_timestamp');
              sessionStorage.removeItem(`${provider}_oauth_redirect_uri`);

              // Clear processing flag
              sessionStorage.removeItem(processingKey);
              if (isLocalDev) {
                console.log('[OAuth Flow] Code cleared from sessionStorage', {
                  requestId,
                  inlineScriptId,
                  step: 'code_cleared',
                  timestamp: new Date().toISOString(),
                });
              }
            } catch (error) {
              if (isLocalDev) {
                console.error(`[OAuth] ❌ Inline Script: Error processing code:`, {
                  inlineScriptId,
                  errorName: error?.name,
                  errorMessage: error?.message,
                  fetchDuration: Date.now() - fetchStartTime,
                  timestamp: new Date().toISOString(),
                });
              }
              
              // Store error for React component
              const errorDetails = {
                code: error.name === 'AbortError' ? 'NETWORK_TIMEOUT' : 'NETWORK_ERROR',
                retryable: true,
                message: error.message
              };
              
              // CRITICAL: Store error result BEFORE marking code as processed
              // This ensures React component can find the error even if code is marked processed
              const errorResult = {
                success: false,
                error: error.name === 'AbortError' ? 'Request timed out' : error.message,
                details: errorDetails,
                timestamp: Date.now(),
                requestId
              };
              
              sessionStorage.setItem('oauth_result', JSON.stringify(errorResult));
              
              // Also store in oauth_error for fallback
              sessionStorage.setItem('oauth_error', JSON.stringify({
                error: error.name === 'AbortError' ? 'NETWORK_TIMEOUT' : 'NETWORK_ERROR',
                description: error.name === 'AbortError' ? 'Request timed out' : error.message,
                details: errorDetails,
                timestamp: Date.now()
              }));

              // Signal React that oauth_error is ready (no secrets).
              try {
                window.dispatchEvent(
                  new CustomEvent('bugbat-oauth-error', {
                    detail: { provider: provider, requestId: requestId },
                  })
                );
              } catch (e) {
                // ignore
              }

              // Mark code as processed to prevent retry with same code
              if (code) {
                const codeHash = code.substring(0, 16);
                sessionStorage.setItem(`oauth_processed_${codeHash}`, 'true');
              }

              // Clear processing flag
              // NOTE: Keep oauth_code/oauth_state in sessionStorage so React can see the error
              sessionStorage.removeItem(processingKey);
              if (isLocalDev) {
                console.log('[OAuth] Error stored, code marked as processed to prevent retry');
              }
            }
          };

          // Start processing immediately
          processCode();
        }
      })();
    </script>
  <script type="module" crossorigin src="/assets/main-D5aiRRuh.js"></script>
  <link rel="modulepreload" crossorigin href="/assets/vendor-BDas3N3M.js">
  <link rel="modulepreload" crossorigin href="/assets/env-DltMPhgq.js">
  <link rel="modulepreload" crossorigin href="/assets/sentry-CBxZZwdX.js">
  <link rel="stylesheet" crossorigin href="/assets/main-C_tKLBvX.css">
</head>
  <body>
    <div id="app"></div>
  </body>
</html>
