{"version":3,"sources":["../src/store.ts","../src/types.ts","../src/utils.ts"],"sourcesContent":["/**\n * @file store.ts\n * @description Zustand store for the API Visual Debugger.\n * Tracks both request state and the active debugger configuration.\n *\n * As of the SSR bridge update this store holds two parallel request lists:\n *   - `requests`    — Client-side requests captured directly in the browser.\n *   - `ssrRequests` — SSR requests polled from /api/dev/api-debugger every second.\n *\n * The overlay combines them via a `mergedRequests` selector, sorted newest-first.\n *\n * Replaces: src/store/useApiDebuggerStore.ts\n */\n\nimport { create } from 'zustand';\nimport { persist, type PersistStorage } from 'zustand/middleware';\n\nimport { ApiRequest, DebuggerConfig, DEFAULT_CONFIG } from './types';\n\n/** sessionStorage key used when `clearOnReload=false`. */\nconst SESSION_STORAGE_KEY = '__api-debugger-store';\n\n/**\n * Dynamic `PersistStorage` that decides — on each call — whether to persist\n * to sessionStorage or short-circuit to a no-op based on the currently\n * published `clearOnReload` flag on `globalThis`.\n *\n * Why not `createJSONStorage`?\n *   `createJSONStorage` wraps `setItem` with a `JSON.stringify(value)` call\n *   **before** it reaches the backing store. That stringify — a full\n *   serialization of a cap-sized `requests` array where each entry carries\n *   a multi-KB raw stack string — is the exact per-`set()` cost we're\n *   trying to eliminate. Implementing `PersistStorage` directly lets us\n *   skip the stringify entirely when it isn't needed.\n *\n * Why dynamic instead of a one-shot factory choice?\n *   The `persist` middleware evaluates its `storage` option once at store\n *   creation time — which is at module import, **before**\n *   `createDebugInterceptor` publishes the resolved `clearOnReload` flag\n *   onto `globalThis`. A dynamic adapter checks the flag on every I/O so\n *   the user's config always wins.\n */\ntype PersistedShape = { requests: ApiRequest[] };\n\nfunction isPersistDisabled(): boolean {\n  const clearOnReload =\n    (globalThis as any).__apiDebuggerClearOnReload ??\n    DEFAULT_CONFIG.clearOnReload;\n  return clearOnReload === true;\n}\n\nconst DYNAMIC_PERSIST_STORAGE: PersistStorage<PersistedShape> = {\n  getItem: (name) => {\n    if (isPersistDisabled()) return null;\n    if (typeof window === 'undefined') return null;\n    try {\n      const raw = window.sessionStorage.getItem(name);\n      return raw ? (JSON.parse(raw) as { state: PersistedShape; version?: number }) : null;\n    } catch {\n      return null;\n    }\n  },\n  setItem: (name, value) => {\n    if (isPersistDisabled()) return;\n    if (typeof window === 'undefined') return;\n    try {\n      window.sessionStorage.setItem(name, JSON.stringify(value));\n    } catch {\n      // Ignore storage errors (quota, private mode, etc.).\n    }\n  },\n  removeItem: (name) => {\n    if (typeof window === 'undefined') return;\n    try {\n      window.sessionStorage.removeItem(name);\n    } catch {\n      // Ignore.\n    }\n  },\n};\n\n// ─── State & Action Types ─────────────────────────────────────────────────────\n\ntype ApiDebuggerState = {\n  /** Live Client-side request log, newest first. Capped at config.maxRequests. */\n  requests: ApiRequest[];\n\n  /**\n   * SSR requests polled from /api/dev/api-debugger.\n   * Replaced wholesale on each successful poll tick.\n   */\n  ssrRequests: ApiRequest[];\n\n  /** Active configuration. Seeded by createDebugInterceptor() on mount. */\n  config: DebuggerConfig;\n};\n\ntype ApiDebuggerActions = {\n  /**\n   * Adds a new Client-side request entry to the log.\n   * Respects the maxRequests cap from config.\n   */\n  addRequest: (request: ApiRequest) => void;\n\n  /**\n   * Patches an existing Client-side request by ID (used in onResponse to update\n   * status, statusCode, and duration).\n   */\n  updateRequest: (\n    id: string,\n    patch: Partial<Pick<ApiRequest, 'status' | 'statusCode' | 'duration'>>,\n  ) => void;\n\n  /**\n   * Applies a batch of adds and updates in a single `set()` call.\n   *\n   * This is the hot-path entry point used by the interceptor's\n   * microtask-coalesced flush. It replaces N discrete `addRequest` /\n   * `updateRequest` calls (each of which would trigger its own subscriber\n   * notification and `persist` serialization) with a single write, which\n   * is what keeps the store cheap under bursts of concurrent openapi-fetch\n   * calls.\n   */\n  applyBatch: (\n    adds: ApiRequest[],\n    updates: Array<{\n      id: string;\n      patch: Partial<Pick<ApiRequest, 'status' | 'statusCode' | 'duration'>>;\n    }>,\n  ) => void;\n\n  /**\n   * Replaces the SSR request list with a fresh snapshot from the server.\n   * Called by the polling effect in <ApiDebuggerOverlay />.\n   */\n  setSsrRequests: (requests: ApiRequest[]) => void;\n\n  /**\n   * Clears all recorded Client-side requests.\n   * The overlay is responsible for also sending DELETE /api/dev/api-debugger\n   * to clear the server-side cache and reset ssrRequests.\n   */\n  clearRequests: () => void;\n\n  /**\n   * Merges a partial config patch into the active configuration.\n   * Called by createDebugInterceptor() to seed initial config from call-site options.\n   */\n  setConfig: (patch: Partial<DebuggerConfig>) => void;\n};\n\n// ─── Store ───────────────────────────────────────────────────────────────────\n\n/**\n * The store is always wrapped in the `persist` middleware, but rehydration is\n * gated on `config.clearOnReload`:\n *   - `clearOnReload: true`  (default) — persisted state is dropped on every\n *     fresh page load, so the debugger starts empty.\n *   - `clearOnReload: false` — persisted `requests` are restored from\n *     sessionStorage so they survive reloads within the same tab.\n *\n * The active `clearOnReload` value is read from a globalThis handshake set\n * by `createDebugInterceptor` before the store is first hydrated. This avoids\n * a circular dependency (interceptor → store → interceptor) at module load.\n */\nexport const useApiDebuggerStore = create<\n  ApiDebuggerState & ApiDebuggerActions\n>()(\n  persist(\n    (set) => ({\n      requests: [],\n      ssrRequests: [],\n      config: { ...DEFAULT_CONFIG },\n\n      addRequest: (request) =>\n        set((state) => {\n          const cap = state.config.maxRequests;\n          return {\n            requests: [request, ...state.requests].slice(0, cap),\n          };\n        }),\n\n      updateRequest: (id, patch) =>\n        set((state) => {\n          // Fast path: findIndex + slice/splice avoids allocating a new\n          // object for every element the way `.map()` does. On a full\n          // 100-entry buffer this is ~50× less garbage per response tick.\n          const idx = state.requests.findIndex((r) => r.id === id);\n          if (idx === -1) return state;\n          const next = state.requests.slice();\n          next[idx] = { ...next[idx], ...patch };\n          return { requests: next };\n        }),\n\n      applyBatch: (adds, updates) =>\n        set((state) => {\n          if (adds.length === 0 && updates.length === 0) return state;\n\n          let next = state.requests;\n          let mutated = false;\n\n          if (adds.length > 0) {\n            // Prepend newest-first: reverse the batch so within-batch order\n            // is preserved (later push → older within the burst).\n            const cap = state.config.maxRequests;\n            next = adds.concat(next);\n            if (next.length > cap) next = next.slice(0, cap);\n            mutated = true;\n          }\n\n          if (updates.length > 0) {\n            // Build an index map once; O(N + U) instead of O(N * U).\n            const indexById = new Map<string, number>();\n            for (let i = 0; i < next.length; i++) indexById.set(next[i].id, i);\n\n            let cloned = mutated ? next : null;\n            for (const { id, patch } of updates) {\n              const idx = indexById.get(id);\n              if (idx === undefined) continue;\n              if (!cloned) cloned = next.slice();\n              cloned[idx] = { ...cloned[idx], ...patch };\n            }\n            if (cloned) {\n              next = cloned;\n              mutated = true;\n            }\n          }\n\n          return mutated ? { requests: next } : state;\n        }),\n\n      setSsrRequests: (requests) =>\n        set((state) => {\n          // Skip state update if the incoming snapshot is structurally identical\n          // to the current one. Without this guard, the 1s polling loop would\n          // hand the store a new array reference every tick, forcing every\n          // subscribed component (the entire overlay) to re-render — even when\n          // no SSR activity has occurred.\n          //\n          // Cheap field-level comparison instead of `JSON.stringify`:\n          // each SSR entry carries a multi-KB `stackTrace` string, and\n          // running `stringify` on both arrays every 1000ms produces\n          // sustained GC pressure. The only fields the overlay actually\n          // renders reactively are id/status/statusCode/duration — the\n          // stack trace is immutable per-id — so comparing just those\n          // is sufficient to detect meaningful changes.\n          const prev = state.ssrRequests;\n          if (prev.length === requests.length) {\n            let identical = true;\n            for (let i = 0; i < prev.length; i++) {\n              const a = prev[i];\n              const b = requests[i];\n              if (\n                a.id !== b.id ||\n                a.status !== b.status ||\n                a.statusCode !== b.statusCode ||\n                a.duration !== b.duration\n              ) {\n                identical = false;\n                break;\n              }\n            }\n            if (identical) return state;\n          }\n          return { ssrRequests: requests };\n        }),\n\n      clearRequests: () => set({ requests: [], ssrRequests: [] }),\n\n      setConfig: (patch) =>\n        set((state) => ({\n          config: { ...state.config, ...patch },\n        })),\n    }),\n    {\n      name: SESSION_STORAGE_KEY,\n      // ⚠️ PERFORMANCE: `persist` calls `storage.setItem` **synchronously**\n      // on every `set()` — which under bursts of concurrent openapi-fetch\n      // requests means N × `JSON.stringify` of the whole `requests` array\n      // (each entry carries a multi-KB raw stack string). `sessionStorage`\n      // is main-thread-blocking.\n      //\n      // The debugger's default is `clearOnReload: true`, in which case\n      // the `merge` fn below unconditionally throws the persisted blob\n      // away on next mount. So when the flag is true, persistence is\n      // pure overhead — we swap in a no-op Storage that skips all\n      // serialization entirely. Persistence is only performed when the\n      // caller explicitly opts in with `clearOnReload: false`.\n      storage: DYNAMIC_PERSIST_STORAGE,\n      // Only persist the client-side `requests` list — SSR requests come from\n      // the live server cache, and `config` is seeded fresh each mount.\n      partialize: (state) => ({ requests: state.requests }),\n      // ⚠️ Hydration race fix:\n      // `persist` normally hydrates synchronously during `create()` — which\n      // happens at module import time, before the host app has a chance to\n      // call `createDebugInterceptor(config)`. At that moment\n      // `globalThis.__apiDebuggerClearOnReload` is still undefined, so both\n      // the storage adapter and the `merge` fn fall back to\n      // `DEFAULT_CONFIG.clearOnReload` (true) and wipe sessionStorage —\n      // making `clearOnReload: false` silently no-op.\n      //\n      // With `skipHydration: true`, hydration is deferred until\n      // `createDebugInterceptor` explicitly calls\n      // `useApiDebuggerStore.persist.rehydrate()` *after* publishing the\n      // resolved config to globalThis, so the user's flag always wins.\n      skipHydration: true,\n      merge: (persistedState, currentState) => {\n        const clearOnReload =\n          (globalThis as any).__apiDebuggerClearOnReload ??\n          DEFAULT_CONFIG.clearOnReload;\n        if (clearOnReload) {\n          // Wipe the persisted blob so it doesn't linger between sessions.\n          try {\n            if (typeof window !== 'undefined') {\n              window.sessionStorage.removeItem(SESSION_STORAGE_KEY);\n            }\n          } catch {\n            // Ignore storage errors (e.g. private-mode quota).\n          }\n          return currentState;\n        }\n        return {\n          ...currentState,\n          ...(persistedState as Partial<ApiDebuggerState>),\n        };\n      },\n    },\n  ),\n);\n\n// ─── Convenience selector types (re-exported for consumers) ───────────────────\nexport type { ApiRequest, DebuggerConfig } from './types';\nexport type { ApiRequestStatus, ApiEnvironment, StackFrame } from './types';\n","/**\n * @file types.ts\n * @description Shared type definitions for the API Visual Debugger module.\n * These are the public-facing types that would be exported in an NPM package.\n */\n\n// ─── Request State Types ──────────────────────────────────────────────────────\n\nexport type ApiRequestStatus = 'pending' | 'success' | 'error';\nexport type ApiEnvironment = 'SSR' | 'Client';\n\nexport interface StackFrame {\n  fn: string;\n  file: string;\n  line: string;\n  column: string;\n}\n\nexport interface ApiRequest {\n  /** Unique identifier for this request, used to correlate onRequest → onResponse. */\n  id: string;\n  url: string;\n  method: string;\n  status: ApiRequestStatus;\n  /** HTTP status code received from the server, null while pending. */\n  statusCode: number | null;\n  /** Unix timestamp (ms) when the request was initiated. */\n  startedAt: number;\n  /** Round-trip duration in milliseconds, null while pending. */\n  duration: number | null;\n  environment: ApiEnvironment;\n  /** Raw Error.stack string captured at the call site. */\n  stackTrace: string;\n  /**\n   * Parsed and filtered StackFrame objects derived from stackTrace.\n   * Populated lazily on the client (parsed by the overlay on demand) —\n   * may be `undefined` for entries just added by the interceptor.\n   */\n  stackFrames?: StackFrame[];\n  /**\n   * The extracted trigger function name from the call stack.\n   * Populated lazily on the client — may be `undefined` for entries just\n   * added by the interceptor.\n   */\n  trigger?: string;\n}\n\n// ─── Configuration Type ───────────────────────────────────────────────────────\n\nexport interface DebuggerConfig {\n  /**\n   * Explicitly enable or disable the debugger. When omitted, the resolved\n   * default falls back to `process.env.NODE_ENV === 'development'`.\n   */\n  enabled?: boolean;\n\n  /**\n   * Position of the floating badge and panel on screen.\n   * @default 'bottom-right'\n   */\n  position: 'bottom-left' | 'bottom-right';\n\n  /**\n   * Optional list of URL filters. Each entry can be:\n   * - A **string**: treated as a substring match against the full request URL.\n   * - A **RegExp**: tested against the full request URL.\n   *\n   * If the array is empty or undefined, **all** requests are logged.\n   *\n   * @example ['vrid', 'checkout', /^\\/v1\\/products/]\n   */\n  urlFilters: Array<string | RegExp>;\n\n  /**\n   * Maximum number of requests to retain in the store.\n   * Oldest requests are discarded when the cap is reached.\n   * @default 100\n   */\n  maxRequests: number;\n\n  /**\n   * When `true`, emits a collapsed console group with a native async stack\n   * trace for every intercepted request. Leverages Chrome's async stack\n   * stitching so you can trace the call back to its originating component.\n   * @default false\n   */\n  logToConsole: boolean;\n\n  /**\n   * When `true`, clears all recorded requests (both Client-side Zustand state\n   * and the SSR server-side cache) on initial client mount — i.e. on every\n   * full page reload the debugger starts with a clean slate.\n   *\n   * When `false`, the client `requests` array is persisted to `sessionStorage`\n   * so it survives soft navigations and reloads within the same tab session.\n   * (SSR requests are not persisted; they always come from the live server\n   * cache polled every second.)\n   *\n   * @default true\n   */\n  clearOnReload: boolean;\n}\n\n/** Resolved defaults for DebuggerConfig — all fields required. */\nexport const DEFAULT_CONFIG: DebuggerConfig = {\n  enabled: process.env.NODE_ENV === 'development',\n  position: 'bottom-right',\n  urlFilters: [],\n  maxRequests: 100,\n  logToConsole: false,\n  clearOnReload: true,\n};\n","/**\n * @file utils.ts\n * @description Stack trace utilities and URL filter matching for the API Visual Debugger.\n * Moved from src/utils/parseStackTrace.ts and extended with filter logic.\n */\n\nimport type { DebuggerConfig, StackFrame } from './types';\n\n// ─── Stack Trace Utilities ────────────────────────────────────────────────────\n\n/**\n * Patterns to filter out non-project frames from a stack trace.\n * These are internal Next.js / webpack / Node internals that add noise.\n */\nconst NOISE_PATTERNS: RegExp[] = [\n  // Blanket /node_modules/ is intentionally avoided here.\n  // Next.js Turbopack bundles client-side code into paths that contain\n  // 'next/static/chunks/node_modules', so a blanket rule would incorrectly\n  // drop valid project frames. Instead we target the specific noisy packages.\n  /node_modules\\/react\\//,\n  /node_modules\\/react-dom\\//,\n  /node_modules\\/scheduler\\//,\n  /node_modules\\/next\\//,\n  /node_modules\\/openapi-fetch\\//,\n  /webpack-internal/,\n  /\\(node:/,\n  /<anonymous>/,\n  /at eval/,\n  /next\\/dist/,\n  /react-dom/,\n  /react\\/cjs/,\n  /at Object\\.fetch/,\n  /parseStackTrace/,   // exclude this utility itself\n  /debugInterceptor/,  // exclude the interceptor bootstrap frames\n  /createDebugInterceptor/, // exclude the factory wrapper\n  /useApiDebuggerStore/,\n  /api-debugger\\/store/, // exclude the new module store\n  /api-debugger\\/utils/, // exclude the new module utils\n  /\\[root-of-the-server\\]/, // filter Turbopack internal async-boundary wrapper frames\n  // ── Proxy / Request constructor noise ─────────────────────────────────────\n  /Proxy\\.(|Request)/,   // frames produced by the Request constructor Proxy\n  // ── openapi-fetch internals ────────────────────────────────────────────────\n  /coreFetch/,           // openapi-fetch core fetch wrapper\n  /fetchMethod/,         // openapi-fetch HTTP-method dispatcher\n  // ── openapi-fetch HTTP-method shorthand frames ─────────────────────────────\n  /at GET/,\n  /at POST/,\n  /at PUT/,\n  /at DELETE/,\n  /at PATCH/,\n  /captureStackTrace/,\n  /onRequest/,\n];\n\n/**\n * Captures the current call stack as a raw string.\n * Call this synchronously — before any await — so the originating component\n * frame is still visible in the stack.\n *\n * Temporarily raises Error.stackTraceLimit to 30 (V8 only) so that frames\n * deep inside Next.js / Turbopack async boundaries are not truncated. The\n * original limit is always restored, even if an error is thrown.\n *\n * ⚠️ PERFORMANCE: This is called synchronously on the request hot path. V8\n * must symbolicate every frame it captures, so keeping the limit modest is\n * important. Previously this was 100, which produced multi-KB stack strings\n * and serialized into visible main-thread stalls when many openapi-fetch\n * calls fired concurrently.\n */\nexport function captureStackTrace(): string {\n  // Error.stackTraceLimit is a V8 extension; guard for non-V8 runtimes.\n  const originalLimit = (Error as { stackTraceLimit?: number }).stackTraceLimit;\n  try {\n    if (originalLimit !== undefined) {\n      (Error as { stackTraceLimit?: number }).stackTraceLimit = 30;\n    }\n    return new Error().stack ?? '';\n  } finally {\n    if (originalLimit !== undefined) {\n      (Error as { stackTraceLimit?: number }).stackTraceLimit = originalLimit;\n    }\n  }\n}\n\n/**\n * Parses a raw Error.stack string into clean StackFrame objects,\n * filtering out noisy internal frames so only project call sites remain.\n */\nexport function parseStackTrace(rawStack: string): StackFrame[] {\n  const lines = rawStack.split('\\n').slice(1); // drop the \"Error\" header line\n\n  return lines\n    .map((line) => line.trim())\n    .filter(\n      (line) =>\n        line.startsWith('at ') && !NOISE_PATTERNS.some((p) => p.test(line)),\n    )\n    .map((line) => {\n      // Format 1:  at FunctionName (file:line:col)\n      const withParens = /^at (.+?) \\((.+):(\\d+):(\\d+)\\)$/.exec(line);\n      if (withParens) {\n        return {\n          fn: withParens[1],\n          file: withParens[2],\n          line: withParens[3],\n          column: withParens[4],\n        };\n      }\n\n      // Format 2:  at file:line:col\n      const noParens = /^at (.+):(\\d+):(\\d+)$/.exec(line);\n      if (noParens) {\n        return {\n          fn: '(anonymous)',\n          file: noParens[1],\n          line: noParens[2],\n          column: noParens[3],\n        };\n      }\n\n      return null;\n    })\n    .filter((frame): frame is StackFrame => frame !== null)\n    .slice(0, 10); // keep at most 10 useful frames\n}\n\n/**\n * Formats a parsed StackFrame array back into a readable string,\n * shortening file paths to be relative to `src/`.\n */\nexport function formatStackFrames(frames: StackFrame[]): string {\n  return frames\n    .map(({ fn, file, line, column }) => {\n      const shortFile = file.replace(/.*\\/src\\//, 'src/').replace(/\\?.*$/, '');\n      return `  at ${fn} (${shortFile}:${line}:${column})`;\n    })\n    .join('\\n');\n}\n\n// ─── URL Filter Matching ──────────────────────────────────────────────────────\n\n/**\n * Evaluates whether a given URL should be logged based on the configured filters.\n *\n * @returns `true` if the URL should be logged (i.e., passes the filter).\n *\n * Rules:\n * - If `filters` is empty or undefined → always log (pass-through).\n * - If any filter matches → log.\n * - String filters are substring-matched (case-sensitive).\n * - RegExp filters are tested against the full URL string.\n */\nexport function matchesUrlFilters(\n  url: string,\n  filters: DebuggerConfig['urlFilters'],\n): boolean {\n  // Empty filter list → log everything\n  if (!filters || filters.length === 0) return true;\n\n  return filters.some((filter) => {\n    if (typeof filter === 'string') {\n      return url.includes(filter);\n    }\n    // RegExp\n    return filter.test(url);\n  });\n}\n\n// ─── Trigger Extraction ───────────────────────────────────────────────────────\n\n/**\n * Internal substrings used to identify and skip noise frames when extracting\n * the trigger function. Mirrors the intent of NOISE_PATTERNS but operates on\n * raw string matching for speed.\n */\nconst TRIGGER_SKIP_SUBSTRINGS: string[] = [\n  'api-debugger',\n  'szs-next-api-debugger',\n  'openapi-fetch',\n  'captureStackTrace',\n  'onRequest',\n  'coreFetch',\n  'node_modules_next',\n  'next/dist',\n  'react-dom',\n  'scheduler',\n  '@tanstack',\n  // ── Added for SSR: React internals & Turbopack/Next.js RSC markers ────────\n  'node_modules/react/',\n  'react/cjs',\n  'react.production',\n  'react.development',\n  '[project]',\n  '[app-router]',\n  '[root-of-the-server]',\n  '[turbopack]',\n  '(rsc)/',\n  'node:internal',\n  'node:async_hooks',\n  'processTicksAndRejections',\n];\n\n/**\n * Function names that look like infrastructure / HTTP-method entry points and\n * should not be surfaced as the \"trigger\".\n */\nconst TRIGGER_SKIP_NAMES: Set<string> = new Set([\n  'Object.construct',\n  'Object.GET',\n  'Object.POST',\n  'Object.PUT',\n  'Object.PATCH',\n  'Object.DELETE',\n  'Object.HEAD',\n  'Object.OPTIONS',\n  'GET',\n  'POST',\n  'PUT',\n  'PATCH',\n  'DELETE',\n  'HEAD',\n  'OPTIONS',\n  'async',\n  'eval',\n  'Module.default',\n  'AsyncFunction',\n  '<anonymous>',\n  'Promise.all',\n]);\n\n/**\n * Regex extracting a function name from a V8 stack frame.\n * Handles the optional `async ` qualifier V8 inserts on awaited frames, and\n * stops at the first whitespace or opening parenthesis.\n *\n * Examples matched:\n *   \"    at async RootLayout (.../layout.tsx:170:...)\"  → \"RootLayout\"\n *   \"    at Object.GET (.../openapi-fetch/...)\"          → \"Object.GET\"\n *   \"    at getVrIdProfile (.../auth.ts:11:...)\"         → \"getVrIdProfile\"\n */\nconst FRAME_FN_REGEX = /at (?:async )?([^\\s(]+)/;\n\n/**\n * Directories that typically identify project (user-land) code paths.\n * Used by the file-basename fallback below.\n */\nconst PROJECT_PATH_REGEX =\n  /\\/((?:app|src|features|components|hooks|providers|pages|lib|api)\\/[^)\\s:]+)/;\n\n/**\n * Walks a raw Error.stack string and returns the first function name that\n * belongs to user-land code — skipping all internal Next.js / library frames.\n *\n * When no meaningful function name survives (common on SSR where Turbopack\n * strips names), falls back to the basename of the first project file\n * encountered in the stack (e.g. `layout.tsx`, `growthBookServer.ts`) so the\n * overlay always shows something useful instead of `Unknown`.\n *\n * @returns The extracted trigger label, or `'Unknown'` if nothing usable found.\n */\nexport function extractTrigger(rawStack: string): string {\n  const lines = rawStack.split('\\n');\n\n  // ── Pass 1: look for a named user-land function ─────────────────────────────\n  for (const line of lines) {\n    if (!line.includes('at ')) continue;\n    if (TRIGGER_SKIP_SUBSTRINGS.some((s) => line.includes(s))) continue;\n\n    const match = FRAME_FN_REGEX.exec(line);\n    if (!match) continue;\n\n    const name = match[1];\n    if (!name || name.length < 2) continue;\n    if (TRIGGER_SKIP_NAMES.has(name)) continue;\n\n    return name;\n  }\n\n  // ── Pass 2: fallback — first project file basename ──────────────────────────\n  for (const line of lines) {\n    if (!line.includes('at ')) continue;\n    if (TRIGGER_SKIP_SUBSTRINGS.some((s) => line.includes(s))) continue;\n\n    const fileMatch = PROJECT_PATH_REGEX.exec(line);\n    if (!fileMatch) continue;\n\n    const segments = fileMatch[1].split('/');\n    const basename = segments[segments.length - 1];\n    if (basename) return basename.replace(/\\?.*$/, '');\n  }\n\n  return 'Unknown';\n}\n"],"mappings":"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","names":[]}