devlog-ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +673 -0
- package/dist/index.cjs +1721 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +796 -0
- package/dist/index.js +3529 -0
- package/dist/index.js.map +1 -0
- package/dist/noop.cjs +2 -0
- package/dist/noop.cjs.map +1 -0
- package/dist/noop.d.ts +682 -0
- package/dist/noop.js +132 -0
- package/dist/noop.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/core/types.ts","../src/core/diff.ts","../src/core/logger.ts","../src/ui/styles.ts","../src/ui/log-entry.ts","../src/channel/broadcast.ts","../src/ui/popout.ts","../src/ui/filter.ts","../src/ui/overlay.ts","../src/core/error-capture.ts","../src/core/persistence.ts","../src/core/network-capture.ts","../src/ui/timeline.ts","../src/index.ts"],"sourcesContent":["/**\n * Core type definitions for DevLogger\n *\n * These types define the contract for all logging operations.\n * They are stable and should not change without major version bump.\n */\n\n/**\n * Available log levels in order of severity\n */\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\n/**\n * Source location of a log entry\n */\nexport interface Source {\n /** File path or name */\n file: string;\n /** Line number (1-based) */\n line: number;\n /** Column number (1-based, optional) */\n column?: number;\n /** Function or method name (optional) */\n function?: string;\n}\n\n/**\n * Context/tags for log correlation\n */\nexport type LogContext = Record<string, string | number | boolean>;\n\n/**\n * Span status for grouped logs\n */\nexport type SpanStatus = 'running' | 'success' | 'error';\n\n/**\n * A single log event with all metadata\n */\nexport interface LogEvent {\n /** Unique identifier for this log entry */\n id: string;\n /** Unix timestamp in milliseconds */\n timestamp: number;\n /** Log severity level */\n level: LogLevel;\n /** Primary log message */\n message: string;\n /** Additional data passed to the log call */\n data: unknown[];\n /** Source code location */\n source: Source;\n /** Browser session identifier */\n sessionId: string;\n /** Optional context/tags for filtering */\n context?: LogContext;\n /** Span ID if this log belongs to a span */\n spanId?: string;\n}\n\n/**\n * A span (grouped logs) event\n */\nexport interface SpanEvent {\n /** Unique span identifier */\n id: string;\n /** Span name/label */\n name: string;\n /** Start timestamp */\n startTime: number;\n /** End timestamp (set when span ends) */\n endTime?: number;\n /** Duration in milliseconds */\n duration?: number;\n /** Current status */\n status: SpanStatus;\n /** Parent span ID for nesting */\n parentId?: string;\n /** Context inherited by all logs in this span */\n context?: LogContext;\n /** Source where span was created */\n source: Source;\n /** Session ID */\n sessionId: string;\n}\n\n/**\n * Logger configuration options\n */\nexport interface LoggerConfig {\n /** Maximum number of logs to keep in memory (default: 1000) */\n maxLogs?: number;\n /** Persist logs to sessionStorage (default: false) */\n persist?: boolean;\n /** Minimum log level to capture (default: 'debug') */\n minLevel?: LogLevel;\n /** Enable/disable logging entirely (default: true) */\n enabled?: boolean;\n}\n\n/**\n * Subscriber callback for new log events\n */\nexport type LogSubscriber = (event: LogEvent) => void;\n\n/**\n * Subscriber callback for span events\n */\nexport type SpanSubscriber = (event: SpanEvent) => void;\n\n/**\n * Function to unsubscribe from log events\n */\nexport type Unsubscribe = () => void;\n\n/**\n * Log level numeric values for comparison\n */\nexport const LOG_LEVEL_VALUES: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n};\n\n/**\n * Diff change type\n */\nexport type DiffChangeType = 'added' | 'removed' | 'changed' | 'unchanged';\n\n/**\n * A single diff entry for object comparison\n */\nexport interface DiffEntry {\n /** Path to the property (e.g., \"user.profile.name\") */\n path: string;\n /** Type of change */\n type: DiffChangeType;\n /** Old value (for removed/changed) */\n oldValue?: unknown;\n /** New value (for added/changed) */\n newValue?: unknown;\n}\n\n/**\n * Diff result for object comparison\n */\nexport interface DiffResult {\n /** All changes found */\n changes: DiffEntry[];\n /** Summary counts */\n summary: {\n added: number;\n removed: number;\n changed: number;\n unchanged: number;\n };\n}\n","/**\n * Object Diff Utility\n *\n * Compares two objects and returns a detailed diff.\n * Handles nested objects, arrays, and primitive values.\n */\n\nimport type { DiffEntry, DiffResult } from './types';\n\n/**\n * Check if value is a plain object\n */\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\n/**\n * Check if two values are deeply equal\n */\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (typeof a !== typeof b) return false;\n if (a === null || b === null) return a === b;\n\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((item, i) => deepEqual(item, b[i]));\n }\n\n if (isPlainObject(a) && isPlainObject(b)) {\n const keysA = Object.keys(a);\n const keysB = Object.keys(b);\n if (keysA.length !== keysB.length) return false;\n return keysA.every((key) => deepEqual(a[key], b[key]));\n }\n\n return false;\n}\n\n/**\n * Format a value for display (handles special types)\n */\nexport function formatValue(value: unknown): string {\n if (value === undefined) return 'undefined';\n if (value === null) return 'null';\n if (typeof value === 'string') return `\"${value}\"`;\n if (typeof value === 'number' || typeof value === 'boolean') return String(value);\n if (Array.isArray(value)) {\n if (value.length === 0) return '[]';\n if (value.length <= 3) return `[${value.map(formatValue).join(', ')}]`;\n return `[${value.length} items]`;\n }\n if (isPlainObject(value)) {\n const keys = Object.keys(value);\n if (keys.length === 0) return '{}';\n if (keys.length <= 2) {\n return `{${keys.map((k) => `${k}: ${formatValue(value[k])}`).join(', ')}}`;\n }\n return `{${keys.length} keys}`;\n }\n return String(value);\n}\n\n/**\n * Compute diff between two objects\n */\nexport function computeDiff(\n oldObj: unknown,\n newObj: unknown,\n path: string = '',\n includeUnchanged: boolean = false\n): DiffEntry[] {\n const entries: DiffEntry[] = [];\n\n // Handle non-object cases\n if (!isPlainObject(oldObj) && !isPlainObject(newObj)) {\n if (!deepEqual(oldObj, newObj)) {\n entries.push({\n path: path || '(root)',\n type: 'changed',\n oldValue: oldObj,\n newValue: newObj,\n });\n } else if (includeUnchanged) {\n entries.push({\n path: path || '(root)',\n type: 'unchanged',\n oldValue: oldObj,\n newValue: newObj,\n });\n }\n return entries;\n }\n\n // Handle one side being non-object\n if (!isPlainObject(oldObj)) {\n entries.push({\n path: path || '(root)',\n type: 'changed',\n oldValue: oldObj,\n newValue: newObj,\n });\n return entries;\n }\n if (!isPlainObject(newObj)) {\n entries.push({\n path: path || '(root)',\n type: 'changed',\n oldValue: oldObj,\n newValue: newObj,\n });\n return entries;\n }\n\n // Both are objects - compare keys\n const allKeys = new Set([...Object.keys(oldObj), ...Object.keys(newObj)]);\n\n for (const key of allKeys) {\n const fullPath = path ? `${path}.${key}` : key;\n const oldVal = oldObj[key];\n const newVal = newObj[key];\n\n const oldHas = key in oldObj;\n const newHas = key in newObj;\n\n if (!oldHas && newHas) {\n // Added\n entries.push({\n path: fullPath,\n type: 'added',\n newValue: newVal,\n });\n } else if (oldHas && !newHas) {\n // Removed\n entries.push({\n path: fullPath,\n type: 'removed',\n oldValue: oldVal,\n });\n } else if (isPlainObject(oldVal) && isPlainObject(newVal)) {\n // Recurse into nested objects\n entries.push(...computeDiff(oldVal, newVal, fullPath, includeUnchanged));\n } else if (Array.isArray(oldVal) && Array.isArray(newVal)) {\n // Compare arrays\n if (!deepEqual(oldVal, newVal)) {\n entries.push({\n path: fullPath,\n type: 'changed',\n oldValue: oldVal,\n newValue: newVal,\n });\n } else if (includeUnchanged) {\n entries.push({\n path: fullPath,\n type: 'unchanged',\n oldValue: oldVal,\n newValue: newVal,\n });\n }\n } else if (!deepEqual(oldVal, newVal)) {\n // Changed primitive\n entries.push({\n path: fullPath,\n type: 'changed',\n oldValue: oldVal,\n newValue: newVal,\n });\n } else if (includeUnchanged) {\n // Unchanged\n entries.push({\n path: fullPath,\n type: 'unchanged',\n oldValue: oldVal,\n newValue: newVal,\n });\n }\n }\n\n return entries;\n}\n\n/**\n * Create a full diff result with summary\n */\nexport function createDiffResult(oldObj: unknown, newObj: unknown): DiffResult {\n const changes = computeDiff(oldObj, newObj, '', false);\n\n const summary = {\n added: 0,\n removed: 0,\n changed: 0,\n unchanged: 0,\n };\n\n for (const change of changes) {\n summary[change.type]++;\n }\n\n return { changes, summary };\n}\n\n/**\n * Check if there are any changes\n */\nexport function hasChanges(diff: DiffResult): boolean {\n return diff.summary.added > 0 || diff.summary.removed > 0 || diff.summary.changed > 0;\n}\n","/**\n * Core Logger - Phase 1 Implementation\n *\n * Single Source of Truth for all logging operations.\n * UI-agnostic, crash-resistant, zero external dependencies.\n */\n\nimport type {\n LogEvent,\n LogLevel,\n LoggerConfig,\n LogSubscriber,\n SpanSubscriber,\n Unsubscribe,\n Source,\n LogContext,\n SpanEvent,\n SpanStatus,\n DiffResult,\n} from './types';\nimport { LOG_LEVEL_VALUES } from './types';\nimport { createDiffResult } from './diff';\n\n/**\n * Export format options\n */\nexport interface ExportOptions {\n /** Export format: 'json' or 'text' */\n format?: 'json' | 'text';\n /** Include only logs from the last N milliseconds */\n lastMs?: number;\n /** Include only logs matching these levels */\n levels?: LogLevel[];\n /** Include only logs matching this search */\n search?: string;\n /** Pretty print JSON (default: true) */\n pretty?: boolean;\n}\n\n/**\n * Check environment variables to determine if logger should be enabled\n * Supports various build tools: Vite, Webpack/CRA, generic\n */\nfunction checkEnvEnabled(): boolean {\n try {\n // Check for explicit disable flag (highest priority)\n // Vite: import.meta.env.VITE_DEVLOGGER_ENABLED\n // CRA/Webpack: process.env.REACT_APP_DEVLOGGER_ENABLED\n // Generic: process.env.DEVLOGGER_ENABLED\n\n // @ts-expect-error - Vite env\n if (typeof import.meta !== 'undefined' && import.meta.env) {\n // @ts-expect-error - Vite env\n const viteEnabled = import.meta.env.VITE_DEVLOGGER_ENABLED;\n if (viteEnabled === 'false' || viteEnabled === '0') return false;\n if (viteEnabled === 'true' || viteEnabled === '1') return true;\n\n // Check Vite mode\n // @ts-expect-error - Vite env\n if (import.meta.env.PROD === true) return false;\n // @ts-expect-error - Vite env\n if (import.meta.env.DEV === true) return true;\n }\n\n // Check process.env (Node.js / Webpack / CRA)\n // Use globalThis to avoid TypeScript errors\n const proc = (globalThis as unknown as { process?: { env?: Record<string, string | undefined> } }).process;\n if (proc?.env) {\n // Explicit flag\n const envEnabled = proc.env.DEVLOGGER_ENABLED || proc.env.REACT_APP_DEVLOGGER_ENABLED;\n if (envEnabled === 'false' || envEnabled === '0') return false;\n if (envEnabled === 'true' || envEnabled === '1') return true;\n\n // NODE_ENV check\n if (proc.env.NODE_ENV === 'production') return false;\n }\n\n // Default: enabled (for development)\n return true;\n } catch {\n // If env check fails, default to enabled\n return true;\n }\n}\n\n/**\n * Default configuration values\n */\nconst DEFAULT_CONFIG: Required<LoggerConfig> = {\n maxLogs: 1000,\n persist: false,\n minLevel: 'debug',\n enabled: checkEnvEnabled(),\n};\n\n/**\n * Generate a unique session ID for this browser session\n */\nfunction generateSessionId(): string {\n // Check for existing session ID in sessionStorage\n const STORAGE_KEY = 'devlogger_session_id';\n try {\n const existing = sessionStorage.getItem(STORAGE_KEY);\n if (existing) {\n return existing;\n }\n const newId = `session_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;\n sessionStorage.setItem(STORAGE_KEY, newId);\n return newId;\n } catch {\n // sessionStorage not available, generate ephemeral ID\n return `session_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;\n }\n}\n\n/**\n * Generate a unique log ID\n */\nfunction generateLogId(): string {\n return `log_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;\n}\n\n/**\n * Capture source location from stack trace\n * (Basic implementation - will be enhanced in Phase 2)\n */\nfunction captureSource(): Source {\n try {\n const stack = new Error().stack;\n if (!stack) {\n return { file: 'unknown', line: 0 };\n }\n\n const lines = stack.split('\\n');\n // Find the first stack frame that's not from this file\n // Skip: Error, captureSource, log, info/warn/error/debug\n for (let i = 4; i < lines.length; i++) {\n const line = lines[i];\n if (!line) continue;\n\n // Skip internal logger frames\n if (line.includes('logger.ts') || line.includes('logger.js')) {\n continue;\n }\n\n // Parse stack frame - handles various formats:\n // Chrome/Edge: \" at functionName (file:line:col)\"\n // Firefox: \"functionName@file:line:col\"\n // Safari: \"functionName@file:line:col\"\n\n // Try Chrome/Edge format first\n const chromeMatch = line.match(/at\\s+(?:(.+?)\\s+)?\\(?(.+?):(\\d+):(\\d+)\\)?/);\n if (chromeMatch) {\n return {\n function: chromeMatch[1] || undefined,\n file: cleanFilePath(chromeMatch[2] || 'unknown'),\n line: parseInt(chromeMatch[3] || '0', 10),\n column: parseInt(chromeMatch[4] || '0', 10),\n };\n }\n\n // Try Firefox/Safari format\n const firefoxMatch = line.match(/(.+)?@(.+?):(\\d+):(\\d+)/);\n if (firefoxMatch) {\n return {\n function: firefoxMatch[1] || undefined,\n file: cleanFilePath(firefoxMatch[2] || 'unknown'),\n line: parseInt(firefoxMatch[3] || '0', 10),\n column: parseInt(firefoxMatch[4] || '0', 10),\n };\n }\n }\n\n return { file: 'unknown', line: 0 };\n } catch {\n return { file: 'unknown', line: 0 };\n }\n}\n\n/**\n * Clean up file path for display\n */\nfunction cleanFilePath(path: string): string {\n // Remove webpack/vite internal prefixes\n let cleaned = path\n .replace(/^webpack:\\/\\/[^/]*\\//, '')\n .replace(/^\\/@fs/, '')\n .replace(/^file:\\/\\//, '')\n .replace(/\\?.*$/, ''); // Remove query strings\n\n // Extract just the filename with one parent dir for context\n const parts = cleaned.split('/');\n if (parts.length > 2) {\n cleaned = parts.slice(-2).join('/');\n }\n\n return cleaned;\n}\n\n/**\n * Generate a unique span ID\n */\nfunction generateSpanId(): string {\n return `span_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;\n}\n\n/**\n * Log Span class - represents a grouped set of related logs\n */\nclass LogSpan {\n private logger: LoggerCore;\n private _event: SpanEvent;\n private _ended = false;\n\n constructor(\n logger: LoggerCore,\n name: string,\n context?: LogContext,\n parentId?: string\n ) {\n this.logger = logger;\n this._event = {\n id: generateSpanId(),\n name,\n startTime: Date.now(),\n status: 'running',\n parentId,\n context,\n source: captureSource(),\n sessionId: logger.getSessionId(),\n };\n logger['notifySpan'](this._event);\n }\n\n /** Get span ID */\n get id(): string {\n return this._event.id;\n }\n\n /** Get span event data */\n get event(): Readonly<SpanEvent> {\n return this._event;\n }\n\n /** Check if span has ended */\n get ended(): boolean {\n return this._ended;\n }\n\n /** Log debug within this span */\n debug(message: string, ...data: unknown[]): void {\n if (!this._ended) {\n this.logger['logWithSpan']('debug', message, data, this._event.id, this._event.context);\n }\n }\n\n /** Log info within this span */\n info(message: string, ...data: unknown[]): void {\n if (!this._ended) {\n this.logger['logWithSpan']('info', message, data, this._event.id, this._event.context);\n }\n }\n\n /** Log warn within this span */\n warn(message: string, ...data: unknown[]): void {\n if (!this._ended) {\n this.logger['logWithSpan']('warn', message, data, this._event.id, this._event.context);\n }\n }\n\n /** Log error within this span */\n error(message: string, ...data: unknown[]): void {\n if (!this._ended) {\n this.logger['logWithSpan']('error', message, data, this._event.id, this._event.context);\n }\n }\n\n /** Create a child span */\n span(name: string, context?: LogContext): LogSpan {\n const mergedContext = { ...this._event.context, ...context };\n return new LogSpan(this.logger, name, mergedContext, this._event.id);\n }\n\n /** End the span successfully */\n end(): void {\n this.finish('success');\n }\n\n /** End the span with error status */\n fail(error?: Error | string): void {\n if (error) {\n this.error(typeof error === 'string' ? error : error.message, error);\n }\n this.finish('error');\n }\n\n /** Internal finish method */\n private finish(status: SpanStatus): void {\n if (this._ended) return;\n this._ended = true;\n this._event.endTime = Date.now();\n this._event.duration = this._event.endTime - this._event.startTime;\n this._event.status = status;\n this.logger['notifySpan'](this._event);\n }\n}\n\n/**\n * Context-bound logger - logs with specific context attached\n */\nclass ContextLogger {\n private logger: LoggerCore;\n private context: LogContext;\n\n constructor(logger: LoggerCore, context: LogContext) {\n this.logger = logger;\n this.context = context;\n }\n\n debug(message: string, ...data: unknown[]): void {\n this.logger['logWithContext']('debug', message, data, this.context);\n }\n\n info(message: string, ...data: unknown[]): void {\n this.logger['logWithContext']('info', message, data, this.context);\n }\n\n warn(message: string, ...data: unknown[]): void {\n this.logger['logWithContext']('warn', message, data, this.context);\n }\n\n error(message: string, ...data: unknown[]): void {\n this.logger['logWithContext']('error', message, data, this.context);\n }\n\n /** Create a span with this context */\n span(name: string, extraContext?: LogContext): LogSpan {\n return this.logger.span(name, { ...this.context, ...extraContext });\n }\n\n /** Create a new context logger with merged context */\n withContext(extraContext: LogContext): ContextLogger {\n return new ContextLogger(this.logger, { ...this.context, ...extraContext });\n }\n}\n\n/**\n * Core Logger Class\n *\n * Lifecycle of a log:\n * 1. Log is created (info/warn/error/debug called)\n * 2. Log is enriched (timestamp, source, sessionId added)\n * 3. Log is stored (in-memory, with rotation)\n * 4. Log is distributed (subscribers notified)\n * 5. Log is displayed (by UI subscribers)\n */\nclass LoggerCore {\n private logs: LogEvent[] = [];\n private spans: Map<string, SpanEvent> = new Map();\n private subscribers: Set<LogSubscriber> = new Set();\n private spanSubscribers: Set<SpanSubscriber> = new Set();\n private config: Required<LoggerConfig> = { ...DEFAULT_CONFIG };\n private sessionId: string;\n private globalContext: LogContext = {};\n\n constructor() {\n this.sessionId = generateSessionId();\n }\n\n /**\n * Core logging method - all public methods delegate to this\n */\n private log(level: LogLevel, message: string, data: unknown[], context?: LogContext, spanId?: string): void {\n // Zero-Throw Policy: wrap everything in try-catch\n try {\n // Check if logging is enabled\n if (!this.config.enabled) {\n return;\n }\n\n // Check minimum log level\n if (LOG_LEVEL_VALUES[level] < LOG_LEVEL_VALUES[this.config.minLevel]) {\n return;\n }\n\n // Merge global context with provided context\n const mergedContext = Object.keys(this.globalContext).length > 0 || context\n ? { ...this.globalContext, ...context }\n : undefined;\n\n // Step 1 & 2: Create and enrich log event\n const event: LogEvent = {\n id: generateLogId(),\n timestamp: Date.now(),\n level,\n message: String(message),\n data: this.safeCloneData(data),\n source: captureSource(),\n sessionId: this.sessionId,\n context: mergedContext,\n spanId,\n };\n\n // Step 3: Store with rotation\n this.store(event);\n\n // Step 4: Distribute to subscribers\n this.notify(event);\n } catch (e) {\n // Silent fail - never throw from logger\n // Optionally log to console for debugging the debugger\n if (typeof console !== 'undefined' && console.warn) {\n console.warn('[DevLogger] Internal error:', e);\n }\n }\n }\n\n /**\n * Log with context (used by ContextLogger)\n */\n private logWithContext(level: LogLevel, message: string, data: unknown[], context: LogContext): void {\n this.log(level, message, data, context);\n }\n\n /**\n * Log with span (used by LogSpan)\n */\n private logWithSpan(level: LogLevel, message: string, data: unknown[], spanId: string, context?: LogContext): void {\n this.log(level, message, data, context, spanId);\n }\n\n /**\n * Notify span subscribers\n */\n private notifySpan(span: SpanEvent): void {\n this.spans.set(span.id, span);\n for (const subscriber of this.spanSubscribers) {\n try {\n subscriber(span);\n } catch {\n // Don't let subscriber errors break logging\n }\n }\n }\n\n /**\n * Safely clone data to prevent mutations and handle special cases\n */\n private safeCloneData(data: unknown[]): unknown[] {\n return data.map((item) => this.safeClone(item));\n }\n\n /**\n * Deep clone with circular reference handling\n */\n private safeClone(value: unknown, seen = new WeakSet()): unknown {\n // Primitives\n if (value === null || value === undefined) {\n return value;\n }\n\n if (typeof value !== 'object') {\n return value;\n }\n\n // Handle Error objects specially\n if (value instanceof Error) {\n return {\n __type: 'Error',\n name: value.name,\n message: value.message,\n stack: value.stack,\n };\n }\n\n // Handle Date\n if (value instanceof Date) {\n return { __type: 'Date', value: value.toISOString() };\n }\n\n // Handle RegExp\n if (value instanceof RegExp) {\n return { __type: 'RegExp', value: value.toString() };\n }\n\n // Circular reference check\n if (seen.has(value as object)) {\n return '[Circular Reference]';\n }\n seen.add(value as object);\n\n // Handle Arrays\n if (Array.isArray(value)) {\n return value.map((item) => this.safeClone(item, seen));\n }\n\n // Handle plain objects\n try {\n const cloned: Record<string, unknown> = {};\n for (const key of Object.keys(value as object)) {\n cloned[key] = this.safeClone((value as Record<string, unknown>)[key], seen);\n }\n return cloned;\n } catch {\n return '[Uncloneable Object]';\n }\n }\n\n /**\n * Store log with FIFO rotation\n */\n private store(event: LogEvent): void {\n this.logs.push(event);\n\n // FIFO rotation - remove oldest logs if over limit\n while (this.logs.length > this.config.maxLogs) {\n this.logs.shift();\n }\n }\n\n /**\n * Notify all subscribers of new log\n */\n private notify(event: LogEvent): void {\n for (const subscriber of this.subscribers) {\n try {\n subscriber(event);\n } catch {\n // Don't let subscriber errors break logging\n }\n }\n }\n\n // ========== Public API ==========\n\n /**\n * Log an info message\n */\n info(message: string, ...data: unknown[]): void {\n this.log('info', message, data);\n }\n\n /**\n * Log a warning message\n */\n warn(message: string, ...data: unknown[]): void {\n this.log('warn', message, data);\n }\n\n /**\n * Log an error message\n */\n error(message: string, ...data: unknown[]): void {\n this.log('error', message, data);\n }\n\n /**\n * Log a debug message\n */\n debug(message: string, ...data: unknown[]): void {\n this.log('debug', message, data);\n }\n\n /**\n * Update logger configuration\n */\n configure(config: Partial<LoggerConfig>): void {\n try {\n this.config = { ...this.config, ...config };\n } catch {\n // Silent fail\n }\n }\n\n /**\n * Clear all stored logs and spans\n */\n clear(): void {\n try {\n this.logs = [];\n this.spans.clear();\n } catch {\n // Silent fail\n }\n }\n\n /**\n * Import logs (used for rehydration from persistence)\n * Imported logs are added at the beginning, preserving order\n */\n importLogs(logs: LogEvent[]): void {\n try {\n if (!Array.isArray(logs) || logs.length === 0) {\n return;\n }\n\n // Filter out logs that already exist (by id)\n const existingIds = new Set(this.logs.map((l) => l.id));\n const newLogs = logs.filter((l) => !existingIds.has(l.id));\n\n // Prepend imported logs\n this.logs = [...newLogs, ...this.logs];\n\n // Apply rotation\n while (this.logs.length > this.config.maxLogs) {\n this.logs.shift();\n }\n } catch {\n // Silent fail\n }\n }\n\n /**\n * Get all stored logs (readonly)\n */\n getLogs(): readonly LogEvent[] {\n return this.logs;\n }\n\n /**\n * Subscribe to new log events\n */\n subscribe(fn: LogSubscriber): Unsubscribe {\n try {\n this.subscribers.add(fn);\n return () => {\n this.subscribers.delete(fn);\n };\n } catch {\n return () => {};\n }\n }\n\n /**\n * Get current session ID\n */\n getSessionId(): string {\n return this.sessionId;\n }\n\n /**\n * Get current configuration\n */\n getConfig(): Readonly<Required<LoggerConfig>> {\n return { ...this.config };\n }\n\n /**\n * Check if logging is currently enabled\n */\n isEnabled(): boolean {\n return this.config.enabled;\n }\n\n // ========== Span API ==========\n\n /**\n * Create a new span for grouping related logs\n */\n span(name: string, context?: LogContext): LogSpan {\n try {\n return new LogSpan(this, name, context);\n } catch {\n // Return a no-op span on failure\n return new LogSpan(this, name, context);\n }\n }\n\n /**\n * Get all spans\n */\n getSpans(): readonly SpanEvent[] {\n return Array.from(this.spans.values());\n }\n\n /**\n * Get a specific span by ID\n */\n getSpan(spanId: string): SpanEvent | undefined {\n return this.spans.get(spanId);\n }\n\n /**\n * Get logs belonging to a specific span\n */\n getSpanLogs(spanId: string): readonly LogEvent[] {\n return this.logs.filter((log) => log.spanId === spanId);\n }\n\n /**\n * Subscribe to span events\n */\n subscribeSpans(fn: SpanSubscriber): Unsubscribe {\n try {\n this.spanSubscribers.add(fn);\n return () => {\n this.spanSubscribers.delete(fn);\n };\n } catch {\n return () => {};\n }\n }\n\n // ========== Context API ==========\n\n /**\n * Set global context that will be attached to all logs\n */\n setGlobalContext(context: LogContext): void {\n try {\n this.globalContext = { ...context };\n } catch {\n // Silent fail\n }\n }\n\n /**\n * Update global context (merge with existing)\n */\n updateGlobalContext(context: LogContext): void {\n try {\n this.globalContext = { ...this.globalContext, ...context };\n } catch {\n // Silent fail\n }\n }\n\n /**\n * Get current global context\n */\n getGlobalContext(): Readonly<LogContext> {\n return { ...this.globalContext };\n }\n\n /**\n * Clear global context\n */\n clearGlobalContext(): void {\n this.globalContext = {};\n }\n\n /**\n * Create a context-bound logger\n */\n withContext(context: LogContext): ContextLogger {\n return new ContextLogger(this, context);\n }\n\n // ========== Export API ==========\n\n /**\n * Export logs in specified format\n */\n exportLogs(options: ExportOptions = {}): string {\n try {\n const {\n format = 'json',\n lastMs,\n levels,\n search,\n pretty = true,\n } = options;\n\n let filteredLogs = [...this.logs];\n\n // Filter by time\n if (lastMs !== undefined && lastMs > 0) {\n const cutoff = Date.now() - lastMs;\n filteredLogs = filteredLogs.filter((log) => log.timestamp >= cutoff);\n }\n\n // Filter by levels\n if (levels && levels.length > 0) {\n const levelSet = new Set(levels);\n filteredLogs = filteredLogs.filter((log) => levelSet.has(log.level));\n }\n\n // Filter by search\n if (search) {\n const searchLower = search.toLowerCase();\n filteredLogs = filteredLogs.filter(\n (log) =>\n log.message.toLowerCase().includes(searchLower) ||\n JSON.stringify(log.data).toLowerCase().includes(searchLower)\n );\n }\n\n if (format === 'text') {\n return this.formatLogsAsText(filteredLogs);\n }\n\n return pretty\n ? JSON.stringify(filteredLogs, null, 2)\n : JSON.stringify(filteredLogs);\n } catch {\n return '[]';\n }\n }\n\n /**\n * Format logs as human-readable text\n */\n private formatLogsAsText(logs: LogEvent[]): string {\n return logs\n .map((log) => {\n const time = new Date(log.timestamp).toISOString();\n const level = log.level.toUpperCase().padEnd(5);\n const source = `${log.source.file}:${log.source.line}`;\n const context = log.context ? ` [${Object.entries(log.context).map(([k, v]) => `${k}=${v}`).join(', ')}]` : '';\n const span = log.spanId ? ` (span: ${log.spanId})` : '';\n const data = log.data.length > 0 ? `\\n Data: ${JSON.stringify(log.data)}` : '';\n return `[${time}] ${level} ${log.message}${context}${span}\\n Source: ${source}${data}`;\n })\n .join('\\n\\n');\n }\n\n /**\n * Copy logs to clipboard\n */\n async copyLogs(options: ExportOptions = {}): Promise<boolean> {\n try {\n const exported = this.exportLogs(options);\n await navigator.clipboard.writeText(exported);\n return true;\n } catch {\n return false;\n }\n }\n\n // ========== Diff API ==========\n\n /**\n * Log a visual diff between two objects\n */\n diff(message: string, oldObj: unknown, newObj: unknown, level: LogLevel = 'info'): DiffResult {\n try {\n const diffResult = createDiffResult(oldObj, newObj);\n\n // Log with the diff data attached\n this.log(level, message, [\n {\n __type: 'Diff',\n diff: diffResult,\n oldObj: this.safeClone(oldObj),\n newObj: this.safeClone(newObj),\n },\n ]);\n\n return diffResult;\n } catch {\n // Return empty diff on error\n return {\n changes: [],\n summary: { added: 0, removed: 0, changed: 0, unchanged: 0 },\n };\n }\n }\n\n /**\n * Compute diff without logging (utility method)\n */\n computeDiff(oldObj: unknown, newObj: unknown): DiffResult {\n try {\n return createDiffResult(oldObj, newObj);\n } catch {\n return {\n changes: [],\n summary: { added: 0, removed: 0, changed: 0, unchanged: 0 },\n };\n }\n }\n}\n\n// Singleton export\nexport const logger = new LoggerCore();\n\n// Export LogSpan type for external use\nexport type { LogSpan, ContextLogger };\n","/**\n * UI Styles for DevLogger\n *\n * CSS-in-JS for Shadow DOM isolation.\n * Design philosophy: Clean, readable, non-intrusive.\n */\n\nexport const COLORS = {\n // Background\n bgPrimary: '#1e1e1e',\n bgSecondary: '#252526',\n bgHover: '#2a2a2a',\n bgHeader: '#333333',\n\n // Text\n textPrimary: '#cccccc',\n textSecondary: '#858585',\n textMuted: '#6e6e6e',\n\n // Log Levels\n levelDebug: '#6e6e6e',\n levelInfo: '#3794ff',\n levelWarn: '#cca700',\n levelError: '#f14c4c',\n\n // Accents\n border: '#3c3c3c',\n scrollbar: '#4a4a4a',\n scrollbarHover: '#5a5a5a',\n\n // Interactive\n buttonBg: '#0e639c',\n buttonHover: '#1177bb',\n} as const;\n\nexport const STYLES = `\n :host {\n --bg-primary: ${COLORS.bgPrimary};\n --bg-secondary: ${COLORS.bgSecondary};\n --bg-hover: ${COLORS.bgHover};\n --bg-header: ${COLORS.bgHeader};\n --text-primary: ${COLORS.textPrimary};\n --text-secondary: ${COLORS.textSecondary};\n --text-muted: ${COLORS.textMuted};\n --border: ${COLORS.border};\n\n all: initial;\n font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;\n font-size: 12px;\n line-height: 1.4;\n color: var(--text-primary);\n }\n\n * {\n box-sizing: border-box;\n }\n\n /* Container */\n .devlogger-container {\n position: fixed;\n top: 0;\n right: 0;\n width: 420px;\n height: 100vh;\n background: var(--bg-primary);\n border-left: 1px solid var(--border);\n display: flex;\n flex-direction: column;\n z-index: 99999;\n box-shadow: -2px 0 8px rgba(0, 0, 0, 0.3);\n }\n\n .devlogger-container.hidden {\n display: none;\n }\n\n /* Header */\n .devlogger-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 12px;\n background: var(--bg-header);\n border-bottom: 1px solid var(--border);\n flex-shrink: 0;\n }\n\n .devlogger-title {\n font-weight: 600;\n font-size: 13px;\n color: var(--text-primary);\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .devlogger-badge {\n background: ${COLORS.levelInfo};\n color: white;\n padding: 2px 6px;\n border-radius: 10px;\n font-size: 10px;\n font-weight: 500;\n }\n\n .devlogger-actions {\n display: flex;\n gap: 4px;\n }\n\n .devlogger-btn {\n background: transparent;\n border: none;\n color: var(--text-secondary);\n cursor: pointer;\n padding: 4px 8px;\n border-radius: 4px;\n font-size: 12px;\n transition: all 0.15s ease;\n }\n\n .devlogger-btn:hover {\n background: var(--bg-hover);\n color: var(--text-primary);\n }\n\n .devlogger-btn-primary {\n background: ${COLORS.buttonBg};\n color: white;\n }\n\n .devlogger-btn-primary:hover {\n background: ${COLORS.buttonHover};\n color: white;\n }\n\n /* Log List */\n .devlogger-logs {\n flex: 1;\n overflow-y: auto;\n overflow-x: hidden;\n }\n\n .devlogger-logs::-webkit-scrollbar {\n width: 8px;\n }\n\n .devlogger-logs::-webkit-scrollbar-track {\n background: var(--bg-primary);\n }\n\n .devlogger-logs::-webkit-scrollbar-thumb {\n background: ${COLORS.scrollbar};\n border-radius: 4px;\n }\n\n .devlogger-logs::-webkit-scrollbar-thumb:hover {\n background: ${COLORS.scrollbarHover};\n }\n\n .devlogger-empty {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: var(--text-muted);\n font-style: italic;\n }\n\n /* Log Entry */\n .log-entry {\n border-bottom: 1px solid var(--border);\n padding: 8px 12px;\n transition: background 0.1s ease;\n }\n\n .log-entry:hover {\n background: var(--bg-hover);\n }\n\n .log-entry-header {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 4px;\n }\n\n .log-level {\n font-weight: 600;\n font-size: 10px;\n text-transform: uppercase;\n padding: 2px 6px;\n border-radius: 3px;\n min-width: 45px;\n text-align: center;\n }\n\n .log-level-debug {\n background: rgba(110, 110, 110, 0.2);\n color: ${COLORS.levelDebug};\n }\n\n .log-level-info {\n background: rgba(55, 148, 255, 0.15);\n color: ${COLORS.levelInfo};\n }\n\n .log-level-warn {\n background: rgba(204, 167, 0, 0.15);\n color: ${COLORS.levelWarn};\n }\n\n .log-level-error {\n background: rgba(241, 76, 76, 0.15);\n color: ${COLORS.levelError};\n }\n\n .log-time {\n color: var(--text-muted);\n font-size: 11px;\n }\n\n .log-source {\n color: var(--text-secondary);\n font-size: 11px;\n margin-left: auto;\n max-width: 150px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .log-source:hover {\n color: ${COLORS.levelInfo};\n }\n\n .log-context {\n font-size: 10px;\n color: ${COLORS.levelInfo};\n background: rgba(55, 148, 255, 0.1);\n padding: 2px 6px;\n border-radius: 3px;\n max-width: 150px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .log-entry-in-span {\n border-left: 2px solid ${COLORS.levelInfo};\n padding-left: 10px;\n }\n\n .log-message {\n color: var(--text-primary);\n word-break: break-word;\n margin-bottom: 4px;\n }\n\n /* Data Display */\n .log-data {\n margin-top: 6px;\n }\n\n .log-data-toggle {\n background: transparent;\n border: none;\n color: var(--text-secondary);\n cursor: pointer;\n padding: 2px 0;\n font-size: 11px;\n display: flex;\n align-items: center;\n gap: 4px;\n }\n\n .log-data-toggle:hover {\n color: var(--text-primary);\n }\n\n .log-data-toggle::before {\n content: '▶';\n font-size: 8px;\n transition: transform 0.15s ease;\n }\n\n .log-data-toggle.expanded::before {\n transform: rotate(90deg);\n }\n\n .log-data-content {\n display: none;\n margin-top: 6px;\n padding: 8px;\n background: var(--bg-secondary);\n border-radius: 4px;\n font-size: 11px;\n overflow-x: auto;\n white-space: pre-wrap;\n word-break: break-all;\n }\n\n .log-data-content.visible {\n display: block;\n }\n\n /* Toggle Button (floating) */\n .devlogger-toggle {\n position: fixed;\n bottom: 20px;\n right: 20px;\n width: 48px;\n height: 48px;\n border-radius: 50%;\n background: ${COLORS.buttonBg};\n color: white;\n border: none;\n cursor: pointer;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);\n font-size: 20px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.2s ease;\n z-index: 99998;\n }\n\n .devlogger-toggle:hover {\n background: ${COLORS.buttonHover};\n transform: scale(1.05);\n }\n\n .devlogger-toggle.hidden {\n display: none;\n }\n\n /* Footer / Status */\n .devlogger-footer {\n padding: 6px 12px;\n background: var(--bg-header);\n border-top: 1px solid var(--border);\n font-size: 11px;\n color: var(--text-muted);\n display: flex;\n justify-content: space-between;\n flex-shrink: 0;\n }\n\n .devlogger-shortcut {\n opacity: 0.7;\n }\n\n /* Filter Bar */\n .filter-bar {\n padding: 8px 12px;\n background: var(--bg-secondary);\n border-bottom: 1px solid var(--border);\n flex-shrink: 0;\n }\n\n .filter-bar.filter-active {\n background: rgba(55, 148, 255, 0.08);\n border-bottom-color: ${COLORS.levelInfo};\n }\n\n .filter-row {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .filter-levels {\n display: flex;\n gap: 2px;\n }\n\n .filter-level-btn {\n width: 24px;\n height: 24px;\n border: 1px solid var(--border);\n border-radius: 4px;\n background: transparent;\n color: var(--text-muted);\n cursor: pointer;\n font-size: 10px;\n font-weight: 600;\n transition: all 0.15s ease;\n }\n\n .filter-level-btn:hover {\n background: var(--bg-hover);\n color: var(--text-primary);\n }\n\n .filter-level-btn.active {\n color: white;\n }\n\n .filter-level-btn.active[data-level=\"debug\"] {\n background: ${COLORS.levelDebug};\n border-color: ${COLORS.levelDebug};\n }\n\n .filter-level-btn.active[data-level=\"info\"] {\n background: ${COLORS.levelInfo};\n border-color: ${COLORS.levelInfo};\n }\n\n .filter-level-btn.active[data-level=\"warn\"] {\n background: ${COLORS.levelWarn};\n border-color: ${COLORS.levelWarn};\n }\n\n .filter-level-btn.active[data-level=\"error\"] {\n background: ${COLORS.levelError};\n border-color: ${COLORS.levelError};\n }\n\n .filter-search {\n flex: 1;\n }\n\n .filter-file {\n width: 120px;\n }\n\n .filter-input {\n width: 100%;\n padding: 4px 8px;\n border: 1px solid var(--border);\n border-radius: 4px;\n background: var(--bg-primary);\n color: var(--text-primary);\n font-size: 11px;\n font-family: inherit;\n outline: none;\n transition: border-color 0.15s ease;\n }\n\n .filter-input::placeholder {\n color: var(--text-muted);\n }\n\n .filter-input:focus {\n border-color: ${COLORS.levelInfo};\n }\n\n .filter-clear-btn {\n width: 24px;\n height: 24px;\n border: none;\n border-radius: 4px;\n background: rgba(241, 76, 76, 0.2);\n color: ${COLORS.levelError};\n cursor: pointer;\n font-size: 12px;\n transition: all 0.15s ease;\n }\n\n .filter-clear-btn:hover {\n background: ${COLORS.levelError};\n color: white;\n }\n\n .filter-status {\n margin-top: 6px;\n font-size: 10px;\n color: ${COLORS.levelInfo};\n }\n\n /* No results state */\n .devlogger-no-results {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: var(--text-muted);\n gap: 8px;\n }\n\n .devlogger-no-results-icon {\n font-size: 24px;\n opacity: 0.5;\n }\n\n .devlogger-no-results-text {\n font-style: italic;\n }\n\n /* Diff Display */\n .diff-container {\n font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;\n font-size: 11px;\n }\n\n .diff-summary {\n display: flex;\n gap: 8px;\n margin-bottom: 8px;\n padding-bottom: 8px;\n border-bottom: 1px solid var(--border);\n }\n\n .diff-count {\n padding: 2px 6px;\n border-radius: 3px;\n font-weight: 600;\n }\n\n .diff-count.diff-added {\n background: rgba(80, 200, 120, 0.2);\n color: #50c878;\n }\n\n .diff-count.diff-removed {\n background: rgba(241, 76, 76, 0.2);\n color: ${COLORS.levelError};\n }\n\n .diff-count.diff-changed {\n background: rgba(204, 167, 0, 0.2);\n color: ${COLORS.levelWarn};\n }\n\n .diff-count.diff-unchanged {\n background: var(--bg-hover);\n color: var(--text-muted);\n }\n\n .diff-changes {\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n\n .diff-entry {\n padding: 2px 6px;\n border-radius: 3px;\n display: flex;\n gap: 6px;\n }\n\n .diff-entry.diff-added {\n background: rgba(80, 200, 120, 0.1);\n color: #50c878;\n }\n\n .diff-entry.diff-removed {\n background: rgba(241, 76, 76, 0.1);\n color: ${COLORS.levelError};\n }\n\n .diff-entry.diff-changed {\n background: rgba(204, 167, 0, 0.1);\n color: ${COLORS.levelWarn};\n }\n\n .diff-icon {\n font-weight: bold;\n width: 12px;\n flex-shrink: 0;\n }\n\n .diff-path {\n color: var(--text-secondary);\n flex-shrink: 0;\n }\n\n .diff-value {\n word-break: break-all;\n }\n\n .log-data-diff .log-data-toggle {\n color: ${COLORS.levelInfo};\n }\n`;\n","/**\n * Log Entry Renderer\n *\n * Renders a single log entry with:\n * - Level badge (color-coded)\n * - Timestamp\n * - Source location\n * - Message\n * - Expandable data\n */\n\nimport type { LogEvent, DiffEntry, DiffResult } from '../core/types';\n\n/**\n * Format timestamp for display (HH:MM:SS.mmm)\n */\nfunction formatTime(timestamp: number): string {\n const date = new Date(timestamp);\n const hours = date.getHours().toString().padStart(2, '0');\n const minutes = date.getMinutes().toString().padStart(2, '0');\n const seconds = date.getSeconds().toString().padStart(2, '0');\n const ms = date.getMilliseconds().toString().padStart(3, '0');\n return `${hours}:${minutes}:${seconds}.${ms}`;\n}\n\n/**\n * Format source location for display\n */\nfunction formatSource(source: LogEvent['source']): string {\n if (source.file === 'unknown') {\n return 'unknown';\n }\n return `${source.file}:${source.line}`;\n}\n\n/**\n * Check if data item is a diff result\n */\nfunction isDiffData(item: unknown): item is { __type: 'Diff'; diff: DiffResult; oldObj: unknown; newObj: unknown } {\n return (\n item !== null &&\n typeof item === 'object' &&\n (item as Record<string, unknown>).__type === 'Diff' &&\n (item as Record<string, unknown>).diff !== undefined\n );\n}\n\n/**\n * Format a value for diff display\n */\nfunction formatDiffValue(value: unknown): string {\n if (value === undefined) return 'undefined';\n if (value === null) return 'null';\n if (typeof value === 'string') return `\"${value}\"`;\n if (typeof value === 'number' || typeof value === 'boolean') return String(value);\n if (Array.isArray(value)) {\n if (value.length === 0) return '[]';\n if (value.length <= 3) return `[${value.map(formatDiffValue).join(', ')}]`;\n return `[${value.length} items]`;\n }\n if (typeof value === 'object') {\n const keys = Object.keys(value);\n if (keys.length === 0) return '{}';\n if (keys.length <= 2) {\n return `{${keys.map((k) => `${k}: ${formatDiffValue((value as Record<string, unknown>)[k])}`).join(', ')}}`;\n }\n return `{${keys.length} keys}`;\n }\n return String(value);\n}\n\n/**\n * Format diff entry for display\n */\nfunction formatDiffEntry(entry: DiffEntry): string {\n const typeClass = `diff-${entry.type}`;\n const icon = entry.type === 'added' ? '+' : entry.type === 'removed' ? '-' : entry.type === 'changed' ? '~' : ' ';\n\n let valueStr = '';\n if (entry.type === 'added') {\n valueStr = formatDiffValue(entry.newValue);\n } else if (entry.type === 'removed') {\n valueStr = formatDiffValue(entry.oldValue);\n } else if (entry.type === 'changed') {\n valueStr = `${formatDiffValue(entry.oldValue)} → ${formatDiffValue(entry.newValue)}`;\n }\n\n return `<div class=\"diff-entry ${typeClass}\"><span class=\"diff-icon\">${icon}</span> <span class=\"diff-path\">${entry.path}</span>: <span class=\"diff-value\">${valueStr}</span></div>`;\n}\n\n/**\n * Format diff result for display\n */\nfunction formatDiff(diffData: { __type: 'Diff'; diff: DiffResult; oldObj: unknown; newObj: unknown }): string {\n const { diff } = diffData;\n const { summary, changes } = diff;\n\n let html = '<div class=\"diff-container\">';\n\n // Summary\n html += '<div class=\"diff-summary\">';\n if (summary.added > 0) html += `<span class=\"diff-count diff-added\">+${summary.added}</span>`;\n if (summary.removed > 0) html += `<span class=\"diff-count diff-removed\">-${summary.removed}</span>`;\n if (summary.changed > 0) html += `<span class=\"diff-count diff-changed\">~${summary.changed}</span>`;\n if (summary.added === 0 && summary.removed === 0 && summary.changed === 0) {\n html += '<span class=\"diff-count diff-unchanged\">No changes</span>';\n }\n html += '</div>';\n\n // Changes\n if (changes.length > 0) {\n html += '<div class=\"diff-changes\">';\n for (const entry of changes) {\n html += formatDiffEntry(entry);\n }\n html += '</div>';\n }\n\n html += '</div>';\n return html;\n}\n\n/**\n * Safely stringify data for display\n */\nfunction formatData(data: unknown[]): string {\n if (data.length === 0) {\n return '';\n }\n\n try {\n return data\n .map((item) => {\n if (typeof item === 'string') {\n return item;\n }\n return JSON.stringify(item, null, 2);\n })\n .join('\\n');\n } catch {\n return '[Unable to display data]';\n }\n}\n\n/**\n * Check if data contains a diff\n */\nfunction containsDiff(data: unknown[]): boolean {\n return data.some(isDiffData);\n}\n\n/**\n * Escape HTML to prevent XSS\n */\nfunction escapeHtml(str: string): string {\n const div = document.createElement('div');\n div.textContent = str;\n return div.innerHTML;\n}\n\n/**\n * Format context tags for display\n */\nfunction formatContext(context: Record<string, string | number | boolean> | undefined): string {\n if (!context || Object.keys(context).length === 0) {\n return '';\n }\n return Object.entries(context)\n .map(([k, v]) => `${k}=${v}`)\n .join(' · ');\n}\n\n/**\n * Create a log entry DOM element\n */\nexport function createLogEntry(log: LogEvent): HTMLElement {\n const entry = document.createElement('div');\n entry.className = `log-entry${log.spanId ? ' log-entry-in-span' : ''}`;\n entry.dataset.id = log.id;\n if (log.spanId) {\n entry.dataset.spanId = log.spanId;\n }\n\n const hasData = log.data.length > 0;\n const hasDiff = containsDiff(log.data);\n const hasContext = log.context && Object.keys(log.context).length > 0;\n const dataId = `data-${log.id}`;\n\n // Render diff data specially\n const renderData = (): string => {\n if (!hasData) return '';\n\n if (hasDiff) {\n const diffItem = log.data.find(isDiffData);\n if (diffItem) {\n return `\n <div class=\"log-data log-data-diff\">\n <button class=\"log-data-toggle\" data-target=\"${dataId}\">\n diff\n </button>\n <div class=\"log-data-content\" id=\"${dataId}\">${formatDiff(diffItem)}</div>\n </div>\n `;\n }\n }\n\n return `\n <div class=\"log-data\">\n <button class=\"log-data-toggle\" data-target=\"${dataId}\">\n ${log.data.length} item${log.data.length > 1 ? 's' : ''}\n </button>\n <pre class=\"log-data-content\" id=\"${dataId}\">${escapeHtml(formatData(log.data))}</pre>\n </div>\n `;\n };\n\n entry.innerHTML = `\n <div class=\"log-entry-header\">\n <span class=\"log-level log-level-${log.level}\">${log.level}</span>\n <span class=\"log-time\">${formatTime(log.timestamp)}</span>\n ${hasContext ? `<span class=\"log-context\" title=\"Context\">${escapeHtml(formatContext(log.context))}</span>` : ''}\n <span class=\"log-source\" title=\"${escapeHtml(formatSource(log.source))}\">${escapeHtml(formatSource(log.source))}</span>\n </div>\n <div class=\"log-message\">${escapeHtml(log.message)}</div>\n ${renderData()}\n `;\n\n // Add click handler for data toggle\n if (hasData) {\n const toggle = entry.querySelector('.log-data-toggle');\n const content = entry.querySelector(`#${dataId}`);\n\n if (toggle && content) {\n toggle.addEventListener('click', () => {\n toggle.classList.toggle('expanded');\n content.classList.toggle('visible');\n });\n }\n }\n\n return entry;\n}\n\n/**\n * Create the empty state element\n */\nexport function createEmptyState(): HTMLElement {\n const empty = document.createElement('div');\n empty.className = 'devlogger-empty';\n empty.textContent = 'No logs yet...';\n return empty;\n}\n","/**\n * BroadcastChannel Communication Layer\n *\n * Enables real-time synchronization between:\n * - Main app overlay\n * - Pop-out window\n * - Multiple tabs (future)\n */\n\nimport type { LogEvent } from '../core/types';\n\n/** Channel name for DevLogger communication */\nconst CHANNEL_NAME = 'devlogger-sync';\n\n/** Message types for channel communication */\nexport type MessageType =\n | 'NEW_LOG'\n | 'CLEAR_LOGS'\n | 'SYNC_REQUEST'\n | 'SYNC_RESPONSE'\n | 'PING'\n | 'PONG';\n\n/** Message structure for channel communication */\nexport interface ChannelMessage {\n type: MessageType;\n payload?: unknown;\n senderId: string;\n timestamp: number;\n}\n\n/** Callback for message handlers */\nexport type MessageHandler = (message: ChannelMessage) => void;\n\n/**\n * Generate a unique sender ID for this window/tab\n */\nfunction generateSenderId(): string {\n return `sender_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;\n}\n\n/**\n * BroadcastChannel wrapper with automatic reconnection\n * and error handling\n */\nclass LoggerChannel {\n private channel: BroadcastChannel | null = null;\n private handlers: Set<MessageHandler> = new Set();\n private senderId: string;\n private isConnected: boolean = false;\n\n constructor() {\n this.senderId = generateSenderId();\n this.connect();\n }\n\n /**\n * Connect to the broadcast channel\n */\n private connect(): void {\n try {\n if (typeof BroadcastChannel === 'undefined') {\n console.warn('[DevLogger] BroadcastChannel not supported');\n return;\n }\n\n this.channel = new BroadcastChannel(CHANNEL_NAME);\n this.channel.onmessage = (event: MessageEvent<ChannelMessage>) => {\n this.handleMessage(event.data);\n };\n this.channel.onmessageerror = () => {\n console.warn('[DevLogger] Channel message error');\n };\n this.isConnected = true;\n } catch (e) {\n console.warn('[DevLogger] Failed to connect to channel:', e);\n this.isConnected = false;\n }\n }\n\n /**\n * Handle incoming messages\n */\n private handleMessage(message: ChannelMessage): void {\n // Ignore own messages\n if (message.senderId === this.senderId) {\n return;\n }\n\n // Notify all handlers\n for (const handler of this.handlers) {\n try {\n handler(message);\n } catch {\n // Silent fail - don't let handler errors break the channel\n }\n }\n }\n\n /**\n * Send a message to all connected windows/tabs\n */\n send(type: MessageType, payload?: unknown): void {\n try {\n if (!this.channel || !this.isConnected) {\n return;\n }\n\n const message: ChannelMessage = {\n type,\n payload,\n senderId: this.senderId,\n timestamp: Date.now(),\n };\n\n this.channel.postMessage(message);\n } catch {\n // Silent fail\n }\n }\n\n /**\n * Send a new log to all connected windows\n */\n sendLog(log: LogEvent): void {\n this.send('NEW_LOG', log);\n }\n\n /**\n * Request all logs from main window\n */\n requestSync(): void {\n this.send('SYNC_REQUEST');\n }\n\n /**\n * Send all logs as sync response\n */\n sendSyncResponse(logs: readonly LogEvent[]): void {\n this.send('SYNC_RESPONSE', logs);\n }\n\n /**\n * Notify all windows to clear logs\n */\n sendClear(): void {\n this.send('CLEAR_LOGS');\n }\n\n /**\n * Subscribe to channel messages\n */\n subscribe(handler: MessageHandler): () => void {\n this.handlers.add(handler);\n return () => {\n this.handlers.delete(handler);\n };\n }\n\n /**\n * Check if channel is connected\n */\n isActive(): boolean {\n return this.isConnected;\n }\n\n /**\n * Get this window's sender ID\n */\n getSenderId(): string {\n return this.senderId;\n }\n\n /**\n * Close the channel\n */\n close(): void {\n try {\n if (this.channel) {\n this.channel.close();\n this.channel = null;\n }\n this.handlers.clear();\n this.isConnected = false;\n } catch {\n // Silent fail\n }\n }\n}\n\n// Singleton instance\nexport const channel = new LoggerChannel();\n","/**\n * Pop-out Window Manager\n *\n * Handles:\n * - Opening pop-out window\n * - Synchronizing logs via BroadcastChannel\n * - Maintaining connection when main app crashes\n */\n\nimport { channel, type ChannelMessage } from \"../channel/broadcast\";\nimport { logger } from \"../core/logger\";\n\n/** Pop-out window dimensions */\nconst POPOUT_WIDTH = 500;\nconst POPOUT_HEIGHT = 700;\n\n/** Pop-out window reference */\nlet popoutWindow: Window | null = null;\n\n/** Check if we're running inside the pop-out window */\nexport function isPopoutWindow(): boolean {\n try {\n return window.name === \"devlogger-popout\";\n } catch {\n return false;\n }\n}\n\n/**\n * Open the pop-out window\n */\nexport function openPopout(): Window | null {\n try {\n // Check if already open\n if (popoutWindow && !popoutWindow.closed) {\n popoutWindow.focus();\n return popoutWindow;\n }\n\n // Calculate position (center of screen)\n const left = Math.max(0, (screen.width - POPOUT_WIDTH) / 2);\n const top = Math.max(0, (screen.height - POPOUT_HEIGHT) / 2);\n\n // Generate pop-out HTML content\n const popoutHtml = generatePopoutHtml();\n\n // Open new window\n popoutWindow = window.open(\n \"\",\n \"devlogger-popout\",\n `width=${POPOUT_WIDTH},height=${POPOUT_HEIGHT},left=${left},top=${top},resizable=yes,scrollbars=yes`\n );\n\n if (!popoutWindow) {\n console.warn(\"[DevLogger] Pop-out blocked by browser\");\n return null;\n }\n\n // Write content to window\n popoutWindow.document.open();\n popoutWindow.document.write(popoutHtml);\n popoutWindow.document.close();\n\n // Send initial sync after window loads\n popoutWindow.addEventListener(\"load\", () => {\n // Give the pop-out time to set up its channel listener\n setTimeout(() => {\n channel.sendSyncResponse(logger.getLogs());\n }, 100);\n });\n\n return popoutWindow;\n } catch (e) {\n console.warn(\"[DevLogger] Failed to open pop-out:\", e);\n return null;\n }\n}\n\n/**\n * Close the pop-out window\n */\nexport function closePopout(): void {\n try {\n if (popoutWindow && !popoutWindow.closed) {\n popoutWindow.close();\n }\n popoutWindow = null;\n } catch {\n // Silent fail\n }\n}\n\n/**\n * Check if pop-out is open\n */\nexport function isPopoutOpen(): boolean {\n return popoutWindow !== null && !popoutWindow.closed;\n}\n\n/**\n * Set up main window as log broadcaster\n */\nexport function setupMainWindowBroadcast(): void {\n // Subscribe to logger and broadcast new logs\n logger.subscribe((log) => {\n channel.sendLog(log);\n });\n\n // Handle sync requests from pop-out\n channel.subscribe((message: ChannelMessage) => {\n if (message.type === \"SYNC_REQUEST\") {\n channel.sendSyncResponse(logger.getLogs());\n }\n });\n}\n\n/**\n * Generate the HTML content for the pop-out window\n */\nfunction generatePopoutHtml(): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>DevLogger - Pop-out</title>\n <style>\n :root {\n --bg-primary: #1e1e1e;\n --bg-secondary: #252526;\n --bg-hover: #2a2a2a;\n --bg-header: #333333;\n --text-primary: #cccccc;\n --text-secondary: #858585;\n --text-muted: #6e6e6e;\n --border: #3c3c3c;\n --level-debug: #6e6e6e;\n --level-info: #3794ff;\n --level-warn: #cca700;\n --level-error: #f14c4c;\n --button-bg: #0e639c;\n --button-hover: #1177bb;\n }\n\n * {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n }\n\n body {\n font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;\n font-size: 12px;\n line-height: 1.4;\n background: var(--bg-primary);\n color: var(--text-primary);\n height: 100vh;\n display: flex;\n flex-direction: column;\n }\n\n .header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 16px;\n background: var(--bg-header);\n border-bottom: 1px solid var(--border);\n }\n\n .title {\n font-weight: 600;\n font-size: 14px;\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .badge {\n background: var(--level-info);\n color: white;\n padding: 2px 8px;\n border-radius: 10px;\n font-size: 11px;\n }\n\n .status {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 11px;\n color: var(--text-muted);\n }\n\n .status-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #4caf50;\n }\n\n .status-dot.disconnected {\n background: var(--level-error);\n }\n\n .actions {\n display: flex;\n gap: 8px;\n }\n\n .btn {\n background: transparent;\n border: 1px solid var(--border);\n color: var(--text-secondary);\n padding: 6px 12px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 12px;\n transition: all 0.15s ease;\n }\n\n .btn:hover {\n background: var(--bg-hover);\n color: var(--text-primary);\n }\n\n .btn-copy {\n font-size: 10px;\n padding: 4px 8px;\n }\n\n .btn-copy.success {\n background: #4caf50;\n border-color: #4caf50;\n color: white;\n }\n\n .btn-copy.error {\n background: var(--level-error);\n border-color: var(--level-error);\n color: white;\n }\n\n .btn-timeline {\n font-size: 10px;\n padding: 4px 8px;\n }\n\n .btn-timeline.active {\n background: var(--level-info);\n border-color: var(--level-info);\n color: white;\n }\n\n /* Timeline Container */\n .timeline-container {\n position: relative;\n background: var(--bg-secondary);\n border-bottom: 1px solid var(--border);\n padding: 8px;\n }\n\n #timeline-canvas {\n width: 100%;\n height: 120px;\n background: var(--bg-primary);\n border-radius: 4px;\n }\n\n .timeline-tooltip {\n position: absolute;\n background: var(--bg-header);\n border: 1px solid var(--border);\n border-radius: 4px;\n padding: 6px 10px;\n font-size: 11px;\n pointer-events: none;\n display: none;\n z-index: 100;\n max-width: 250px;\n }\n\n .timeline-controls {\n display: flex;\n justify-content: center;\n gap: 4px;\n margin-top: 6px;\n }\n\n .timeline-btn {\n background: transparent;\n border: 1px solid var(--border);\n color: var(--text-muted);\n padding: 2px 8px;\n border-radius: 3px;\n cursor: pointer;\n font-size: 10px;\n }\n\n .timeline-btn:hover {\n background: var(--bg-hover);\n color: var(--text-primary);\n }\n\n .timeline-btn.active {\n background: var(--button-bg);\n border-color: var(--button-bg);\n color: white;\n }\n\n /* Filter Bar Styles */\n .filter-bar {\n padding: 8px 16px;\n background: var(--bg-secondary);\n border-bottom: 1px solid var(--border);\n }\n\n .filter-bar.filter-active {\n border-bottom-color: var(--level-info);\n }\n\n .filter-row {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .filter-levels {\n display: flex;\n gap: 4px;\n }\n\n .filter-level-btn {\n width: 24px;\n height: 24px;\n border: 1px solid var(--border);\n border-radius: 4px;\n background: transparent;\n color: var(--text-muted);\n cursor: pointer;\n font-size: 11px;\n font-weight: 600;\n transition: all 0.15s ease;\n }\n\n .filter-level-btn:hover {\n background: var(--bg-hover);\n color: var(--text-primary);\n }\n\n .filter-level-btn.active {\n background: var(--bg-hover);\n color: var(--text-primary);\n border-color: var(--text-secondary);\n }\n\n .filter-level-btn[data-level=\"debug\"].active { border-color: var(--level-debug); color: var(--level-debug); }\n .filter-level-btn[data-level=\"info\"].active { border-color: var(--level-info); color: var(--level-info); }\n .filter-level-btn[data-level=\"warn\"].active { border-color: var(--level-warn); color: var(--level-warn); }\n .filter-level-btn[data-level=\"error\"].active { border-color: var(--level-error); color: var(--level-error); }\n\n .filter-input {\n flex: 1;\n min-width: 80px;\n padding: 4px 8px;\n border: 1px solid var(--border);\n border-radius: 4px;\n background: var(--bg-primary);\n color: var(--text-primary);\n font-size: 12px;\n font-family: inherit;\n }\n\n .filter-input:focus {\n outline: none;\n border-color: var(--level-info);\n }\n\n .filter-input::placeholder {\n color: var(--text-muted);\n }\n\n .filter-clear-btn {\n width: 24px;\n height: 24px;\n border: 1px solid var(--border);\n border-radius: 4px;\n background: transparent;\n color: var(--text-muted);\n cursor: pointer;\n font-size: 14px;\n line-height: 1;\n transition: all 0.15s ease;\n }\n\n .filter-clear-btn:hover {\n background: var(--level-error);\n border-color: var(--level-error);\n color: white;\n }\n\n .filter-status {\n font-size: 11px;\n color: var(--text-muted);\n margin-top: 6px;\n }\n\n .logs {\n flex: 1;\n overflow-y: auto;\n overflow-x: hidden;\n }\n\n .logs::-webkit-scrollbar {\n width: 8px;\n }\n\n .logs::-webkit-scrollbar-track {\n background: var(--bg-primary);\n }\n\n .logs::-webkit-scrollbar-thumb {\n background: #4a4a4a;\n border-radius: 4px;\n }\n\n .empty {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: var(--text-muted);\n font-style: italic;\n }\n\n .log-entry {\n border-bottom: 1px solid var(--border);\n padding: 10px 16px;\n }\n\n .log-entry:hover {\n background: var(--bg-hover);\n }\n\n .log-header {\n display: flex;\n align-items: center;\n gap: 10px;\n margin-bottom: 4px;\n }\n\n .log-level {\n font-weight: 600;\n font-size: 10px;\n text-transform: uppercase;\n padding: 2px 6px;\n border-radius: 3px;\n min-width: 45px;\n text-align: center;\n }\n\n .log-level-debug { background: rgba(110, 110, 110, 0.2); color: var(--level-debug); }\n .log-level-info { background: rgba(55, 148, 255, 0.15); color: var(--level-info); }\n .log-level-warn { background: rgba(204, 167, 0, 0.15); color: var(--level-warn); }\n .log-level-error { background: rgba(241, 76, 76, 0.15); color: var(--level-error); }\n\n .log-time {\n color: var(--text-muted);\n font-size: 11px;\n }\n\n .log-source {\n color: var(--text-secondary);\n font-size: 11px;\n margin-left: auto;\n }\n\n .log-message {\n color: var(--text-primary);\n word-break: break-word;\n }\n\n .log-data {\n margin-top: 6px;\n }\n\n .log-data-toggle {\n background: transparent;\n border: none;\n color: var(--text-secondary);\n cursor: pointer;\n padding: 2px 0;\n font-size: 11px;\n display: flex;\n align-items: center;\n gap: 4px;\n }\n\n .log-data-toggle:hover {\n color: var(--text-primary);\n }\n\n .log-data-toggle::before {\n content: '▶';\n font-size: 8px;\n transition: transform 0.15s ease;\n }\n\n .log-data-toggle.expanded::before {\n transform: rotate(90deg);\n }\n\n .log-data-content {\n display: none;\n margin-top: 6px;\n padding: 8px;\n background: var(--bg-secondary);\n border-radius: 4px;\n font-size: 11px;\n overflow-x: auto;\n white-space: pre-wrap;\n word-break: break-all;\n }\n\n .log-data-content.visible {\n display: block;\n }\n\n .footer {\n padding: 8px 16px;\n background: var(--bg-header);\n border-top: 1px solid var(--border);\n font-size: 11px;\n color: var(--text-muted);\n display: flex;\n justify-content: space-between;\n }\n </style>\n</head>\n<body>\n <div class=\"header\">\n <div class=\"title\">\n DevLogger\n <span class=\"badge\" id=\"log-count\">0</span>\n </div>\n <div class=\"status\">\n <span class=\"status-dot\" id=\"status-dot\"></span>\n <span id=\"status-text\">Connected</span>\n </div>\n <div class=\"actions\">\n <button class=\"btn btn-copy\" id=\"btn-copy-json\" title=\"Copy as JSON\">JSON</button>\n <button class=\"btn btn-copy\" id=\"btn-copy-text\" title=\"Copy as Text\">TXT</button>\n <button class=\"btn btn-timeline\" id=\"btn-timeline\" title=\"Toggle Timeline\">Timeline</button>\n <button class=\"btn\" id=\"btn-clear\">Clear</button>\n </div>\n </div>\n\n <div class=\"timeline-container\" id=\"timeline-container\" style=\"display: none;\">\n <canvas id=\"timeline-canvas\"></canvas>\n <div class=\"timeline-tooltip\" id=\"timeline-tooltip\"></div>\n <div class=\"timeline-controls\">\n <button class=\"timeline-btn\" data-window=\"10000\">10s</button>\n <button class=\"timeline-btn active\" data-window=\"30000\">30s</button>\n <button class=\"timeline-btn\" data-window=\"60000\">60s</button>\n </div>\n </div>\n\n <div class=\"filter-bar\" id=\"filter-bar\">\n <div class=\"filter-row\">\n <div class=\"filter-levels\">\n <button class=\"filter-level-btn active\" data-level=\"debug\" title=\"Debug\">D</button>\n <button class=\"filter-level-btn active\" data-level=\"info\" title=\"Info\">I</button>\n <button class=\"filter-level-btn active\" data-level=\"warn\" title=\"Warning\">W</button>\n <button class=\"filter-level-btn active\" data-level=\"error\" title=\"Error\">E</button>\n </div>\n <input type=\"text\" class=\"filter-input\" id=\"filter-search\" placeholder=\"Search logs...\" data-filter=\"search\">\n <input type=\"text\" class=\"filter-input\" id=\"filter-file\" placeholder=\"Filter by file...\" data-filter=\"file\" style=\"max-width: 120px;\">\n </div>\n <div class=\"filter-status\" id=\"filter-status\" style=\"display: none;\"></div>\n </div>\n\n <div class=\"logs\" id=\"logs\">\n <div class=\"empty\">Waiting for logs...</div>\n </div>\n\n <div class=\"footer\">\n <span id=\"footer-count\">0 logs</span>\n <span>Pop-out Window</span>\n </div>\n\n <script>\n // Pop-out window script\n const CHANNEL_NAME = 'devlogger-sync';\n let logs = [];\n let channel = null;\n let isConnected = false;\n\n // Filter state\n const filter = {\n levels: new Set(['debug', 'info', 'warn', 'error']),\n search: '',\n file: ''\n };\n\n // DOM elements\n const logsContainer = document.getElementById('logs');\n const logCountBadge = document.getElementById('log-count');\n const footerCount = document.getElementById('footer-count');\n const statusDot = document.getElementById('status-dot');\n const statusText = document.getElementById('status-text');\n const btnClear = document.getElementById('btn-clear');\n const btnCopyJson = document.getElementById('btn-copy-json');\n const btnCopyText = document.getElementById('btn-copy-text');\n const btnTimeline = document.getElementById('btn-timeline');\n const timelineContainer = document.getElementById('timeline-container');\n const timelineCanvas = document.getElementById('timeline-canvas');\n const timelineTooltip = document.getElementById('timeline-tooltip');\n const filterBar = document.getElementById('filter-bar');\n\n // Timeline state\n let timelineVisible = false;\n let timelineWindow = 30000; // 30 seconds default\n let timelineInterval = null;\n const LEVEL_COLORS = {\n debug: '#6e6e6e',\n info: '#3794ff',\n warn: '#cca700',\n error: '#f14c4c'\n };\n const filterSearch = document.getElementById('filter-search');\n const filterFile = document.getElementById('filter-file');\n const filterStatus = document.getElementById('filter-status');\n\n // Connect to broadcast channel\n function connect() {\n try {\n channel = new BroadcastChannel(CHANNEL_NAME);\n channel.onmessage = handleMessage;\n isConnected = true;\n updateStatus(true);\n\n // Request sync from main window\n channel.postMessage({\n type: 'SYNC_REQUEST',\n senderId: 'popout',\n timestamp: Date.now()\n });\n } catch (e) {\n console.error('Failed to connect:', e);\n updateStatus(false);\n }\n }\n\n // Handle incoming messages\n function handleMessage(event) {\n const message = event.data;\n\n switch (message.type) {\n case 'NEW_LOG':\n addLog(message.payload);\n break;\n case 'SYNC_RESPONSE':\n syncLogs(message.payload);\n break;\n case 'CLEAR_LOGS':\n clearLogs();\n break;\n }\n }\n\n // Add a single log\n function addLog(log) {\n logs.push(log);\n // Only render if log matches current filter\n if (matchesFilter(log)) {\n // Remove empty state if present\n const empty = logsContainer.querySelector('.empty');\n if (empty) empty.remove();\n renderLog(log);\n scrollToBottom();\n }\n updateCounts();\n updateFilterUI();\n }\n\n // Sync all logs\n function syncLogs(newLogs) {\n logs = newLogs || [];\n renderAllLogs();\n updateCounts();\n }\n\n // Clear all logs\n function clearLogs() {\n logs = [];\n renderAllLogs();\n updateCounts();\n }\n\n // Check if a log matches the current filter\n function matchesFilter(log) {\n // Level filter\n if (filter.levels.size > 0 && !filter.levels.has(log.level)) {\n return false;\n }\n\n // File filter\n if (filter.file && !log.source.file.toLowerCase().includes(filter.file.toLowerCase())) {\n return false;\n }\n\n // Text search\n if (filter.search) {\n const searchLower = filter.search.toLowerCase();\n const messageMatch = log.message.toLowerCase().includes(searchLower);\n const dataMatch = JSON.stringify(log.data).toLowerCase().includes(searchLower);\n if (!messageMatch && !dataMatch) {\n return false;\n }\n }\n\n return true;\n }\n\n // Get filtered logs\n function getFilteredLogs() {\n return logs.filter(log => matchesFilter(log));\n }\n\n // Check if any filter is active\n function isFilterActive() {\n return filter.levels.size !== 4 || filter.search !== '' || filter.file !== '';\n }\n\n // Update filter UI\n function updateFilterUI() {\n const active = isFilterActive();\n const filteredLogs = getFilteredLogs();\n\n filterBar.classList.toggle('filter-active', active);\n\n if (active) {\n filterStatus.style.display = 'block';\n filterStatus.textContent = 'Showing ' + filteredLogs.length + ' of ' + logs.length + ' logs';\n } else {\n filterStatus.style.display = 'none';\n }\n\n // Update counts\n logCountBadge.textContent = filteredLogs.length;\n if (active) {\n footerCount.textContent = filteredLogs.length + ' of ' + logs.length + ' logs';\n } else {\n footerCount.textContent = logs.length + ' logs';\n }\n }\n\n // Render all logs\n function renderAllLogs() {\n const filteredLogs = getFilteredLogs();\n\n if (logs.length === 0) {\n logsContainer.innerHTML = '<div class=\"empty\">No logs yet...</div>';\n updateFilterUI();\n return;\n }\n\n if (filteredLogs.length === 0) {\n logsContainer.innerHTML = '<div class=\"empty\">No logs match your filter</div>';\n updateFilterUI();\n return;\n }\n\n logsContainer.innerHTML = '';\n filteredLogs.forEach(log => renderLog(log));\n scrollToBottom();\n updateFilterUI();\n }\n\n // Render a single log entry\n function renderLog(log) {\n // Remove empty state if present\n const empty = logsContainer.querySelector('.empty');\n if (empty) empty.remove();\n\n const entry = document.createElement('div');\n entry.className = 'log-entry';\n\n const time = formatTime(log.timestamp);\n const source = formatSource(log.source);\n const hasData = log.data && log.data.length > 0;\n const dataId = 'data-' + log.id;\n\n entry.innerHTML = \\`\n <div class=\"log-header\">\n <span class=\"log-level log-level-\\${log.level}\">\\${log.level}</span>\n <span class=\"log-time\">\\${time}</span>\n <span class=\"log-source\">\\${escapeHtml(source)}</span>\n </div>\n <div class=\"log-message\">\\${escapeHtml(log.message)}</div>\n \\${hasData ? \\`\n <div class=\"log-data\">\n <button class=\"log-data-toggle\" data-target=\"\\${dataId}\">\n \\${log.data.length} item\\${log.data.length > 1 ? 's' : ''}\n </button>\n <pre class=\"log-data-content\" id=\"\\${dataId}\">\\${escapeHtml(formatData(log.data))}</pre>\n </div>\n \\` : ''}\n \\`;\n\n // Add toggle handler\n if (hasData) {\n const toggle = entry.querySelector('.log-data-toggle');\n const content = entry.querySelector('#' + dataId);\n toggle.addEventListener('click', () => {\n toggle.classList.toggle('expanded');\n content.classList.toggle('visible');\n });\n }\n\n logsContainer.appendChild(entry);\n }\n\n // Format timestamp\n function formatTime(timestamp) {\n const date = new Date(timestamp);\n return date.toTimeString().split(' ')[0] + '.' +\n date.getMilliseconds().toString().padStart(3, '0');\n }\n\n // Format source\n function formatSource(source) {\n if (!source || source.file === 'unknown') return 'unknown';\n return source.file + ':' + source.line;\n }\n\n // Format data\n function formatData(data) {\n try {\n return data.map(item =>\n typeof item === 'string' ? item : JSON.stringify(item, null, 2)\n ).join('\\\\n');\n } catch {\n return '[Unable to display]';\n }\n }\n\n // Escape HTML\n function escapeHtml(str) {\n const div = document.createElement('div');\n div.textContent = str;\n return div.innerHTML;\n }\n\n // Update counts\n function updateCounts() {\n const count = logs.length;\n logCountBadge.textContent = count;\n footerCount.textContent = count + ' logs';\n }\n\n // Update connection status\n function updateStatus(connected) {\n isConnected = connected;\n statusDot.className = 'status-dot' + (connected ? '' : ' disconnected');\n statusText.textContent = connected ? 'Connected' : 'Disconnected';\n }\n\n // Scroll to bottom\n function scrollToBottom() {\n logsContainer.scrollTop = logsContainer.scrollHeight;\n }\n\n // Clear button handler\n btnClear.addEventListener('click', () => {\n if (channel) {\n channel.postMessage({\n type: 'CLEAR_LOGS',\n senderId: 'popout',\n timestamp: Date.now()\n });\n }\n clearLogs();\n });\n\n // Export logs as JSON\n function exportLogsJson() {\n return JSON.stringify(logs, null, 2);\n }\n\n // Export logs as text\n function exportLogsText() {\n return logs.map(log => {\n const time = new Date(log.timestamp).toISOString();\n const level = log.level.toUpperCase().padEnd(5);\n const source = log.source.file + ':' + log.source.line;\n const context = log.context ? ' [' + Object.entries(log.context).map(([k, v]) => k + '=' + v).join(', ') + ']' : '';\n const span = log.spanId ? ' (span: ' + log.spanId + ')' : '';\n const data = log.data.length > 0 ? '\\\\n Data: ' + JSON.stringify(log.data) : '';\n return '[' + time + '] ' + level + ' ' + log.message + context + span + '\\\\n Source: ' + source + data;\n }).join('\\\\n\\\\n');\n }\n\n // Show copy feedback\n function showCopyFeedback(button, success) {\n const originalText = button.textContent;\n button.textContent = success ? '✓' : '✗';\n button.classList.add(success ? 'success' : 'error');\n setTimeout(() => {\n button.textContent = originalText;\n button.classList.remove('success', 'error');\n }, 1500);\n }\n\n // Copy JSON handler\n btnCopyJson.addEventListener('click', () => {\n navigator.clipboard.writeText(exportLogsJson()).then(() => {\n showCopyFeedback(btnCopyJson, true);\n }).catch(() => {\n showCopyFeedback(btnCopyJson, false);\n });\n });\n\n // Copy Text handler\n btnCopyText.addEventListener('click', () => {\n navigator.clipboard.writeText(exportLogsText()).then(() => {\n showCopyFeedback(btnCopyText, true);\n }).catch(() => {\n showCopyFeedback(btnCopyText, false);\n });\n });\n\n // Check connection periodically\n setInterval(() => {\n if (channel) {\n try {\n channel.postMessage({\n type: 'PING',\n senderId: 'popout',\n timestamp: Date.now()\n });\n } catch {\n updateStatus(false);\n }\n }\n }, 5000);\n\n // Filter: Level buttons\n document.querySelectorAll('.filter-level-btn').forEach(btn => {\n btn.addEventListener('click', (e) => {\n const level = e.currentTarget.dataset.level;\n if (filter.levels.has(level)) {\n filter.levels.delete(level);\n e.currentTarget.classList.remove('active');\n } else {\n filter.levels.add(level);\n e.currentTarget.classList.add('active');\n }\n renderAllLogs();\n });\n });\n\n // Filter: Search input\n filterSearch.addEventListener('input', (e) => {\n filter.search = e.target.value;\n renderAllLogs();\n });\n\n // Filter: File input\n filterFile.addEventListener('input', (e) => {\n filter.file = e.target.value;\n renderAllLogs();\n });\n\n // Timeline: Toggle button\n btnTimeline.addEventListener('click', () => {\n timelineVisible = !timelineVisible;\n timelineContainer.style.display = timelineVisible ? 'block' : 'none';\n btnTimeline.classList.toggle('active', timelineVisible);\n if (timelineVisible) {\n resizeCanvas();\n drawTimeline();\n startTimelineRefresh();\n } else {\n stopTimelineRefresh();\n }\n });\n\n // Timeline: Time window buttons\n document.querySelectorAll('.timeline-btn[data-window]').forEach(btn => {\n btn.addEventListener('click', (e) => {\n timelineWindow = parseInt(e.currentTarget.dataset.window);\n document.querySelectorAll('.timeline-btn[data-window]').forEach(b => b.classList.remove('active'));\n e.currentTarget.classList.add('active');\n drawTimeline();\n });\n });\n\n // Timeline: Resize canvas for proper DPI\n function resizeCanvas() {\n const rect = timelineCanvas.getBoundingClientRect();\n const dpr = window.devicePixelRatio || 1;\n timelineCanvas.width = rect.width * dpr;\n timelineCanvas.height = rect.height * dpr;\n const ctx = timelineCanvas.getContext('2d');\n ctx.scale(dpr, dpr);\n }\n\n // Timeline: Draw the timeline visualization\n function drawTimeline() {\n const ctx = timelineCanvas.getContext('2d');\n const rect = timelineCanvas.getBoundingClientRect();\n const width = rect.width;\n const height = rect.height;\n\n // Clear canvas\n ctx.fillStyle = '#1e1e1e';\n ctx.fillRect(0, 0, width, height);\n\n // Calculate time bounds\n const now = Date.now();\n const startTime = now - timelineWindow;\n\n // Filter logs within time window\n const visibleLogs = logs.filter(log => log.timestamp >= startTime && log.timestamp <= now);\n\n // Draw time grid\n ctx.strokeStyle = '#333';\n ctx.lineWidth = 1;\n const gridLines = 5;\n for (let i = 0; i <= gridLines; i++) {\n const x = (i / gridLines) * width;\n ctx.beginPath();\n ctx.moveTo(x, 0);\n ctx.lineTo(x, height);\n ctx.stroke();\n\n // Draw time labels\n const time = new Date(startTime + (i / gridLines) * timelineWindow);\n ctx.fillStyle = '#666';\n ctx.font = '10px monospace';\n ctx.fillText(time.toTimeString().split(' ')[0], x + 3, height - 5);\n }\n\n // Draw log markers\n const markerHeight = 16;\n const bottomOffset = 20;\n\n visibleLogs.forEach(log => {\n const x = ((log.timestamp - startTime) / timelineWindow) * width;\n const y = height - bottomOffset - markerHeight / 2;\n const color = LEVEL_COLORS[log.level] || LEVEL_COLORS.debug;\n\n // Draw marker\n ctx.beginPath();\n ctx.arc(x, y, 4, 0, Math.PI * 2);\n ctx.fillStyle = color;\n ctx.fill();\n\n // Store bounds for hover\n log._timelineBounds = { x, y, r: 8 };\n });\n\n // Draw level legend\n ctx.font = '10px monospace';\n let legendX = 10;\n Object.entries(LEVEL_COLORS).forEach(([level, color]) => {\n ctx.fillStyle = color;\n ctx.beginPath();\n ctx.arc(legendX, 12, 4, 0, Math.PI * 2);\n ctx.fill();\n ctx.fillStyle = '#888';\n ctx.fillText(level, legendX + 8, 15);\n legendX += ctx.measureText(level).width + 20;\n });\n }\n\n // Timeline: Mouse move for tooltips\n timelineCanvas.addEventListener('mousemove', (e) => {\n const rect = timelineCanvas.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const y = e.clientY - rect.top;\n\n const hoveredLog = logs.find(log => {\n if (!log._timelineBounds) return false;\n const b = log._timelineBounds;\n const dx = x - b.x;\n const dy = y - b.y;\n return Math.sqrt(dx * dx + dy * dy) <= b.r;\n });\n\n if (hoveredLog) {\n const time = new Date(hoveredLog.timestamp).toTimeString().split(' ')[0];\n timelineTooltip.innerHTML =\n '<strong>[' + hoveredLog.level.toUpperCase() + ']</strong> ' +\n escapeHtml(hoveredLog.message.substring(0, 100)) +\n (hoveredLog.message.length > 100 ? '...' : '') +\n '<br><small>' + time + '</small>';\n timelineTooltip.style.display = 'block';\n timelineTooltip.style.left = Math.min(e.clientX - rect.left + 10, rect.width - 260) + 'px';\n timelineTooltip.style.top = (e.clientY - rect.top - 50) + 'px';\n } else {\n timelineTooltip.style.display = 'none';\n }\n });\n\n // Timeline: Hide tooltip on mouse leave\n timelineCanvas.addEventListener('mouseleave', () => {\n timelineTooltip.style.display = 'none';\n });\n\n // Timeline: Start auto-refresh\n function startTimelineRefresh() {\n if (timelineInterval) return;\n timelineInterval = setInterval(() => {\n if (timelineVisible) drawTimeline();\n }, 500);\n }\n\n // Timeline: Stop auto-refresh\n function stopTimelineRefresh() {\n if (timelineInterval) {\n clearInterval(timelineInterval);\n timelineInterval = null;\n }\n }\n\n // Handle window resize for timeline\n window.addEventListener('resize', () => {\n if (timelineVisible) {\n resizeCanvas();\n drawTimeline();\n }\n });\n\n // Initialize\n connect();\n </script>\n</body>\n</html>`;\n}\n","/**\n * Filter System for DevLogger\n *\n * Key principle: Logs remain complete, UI decides visibility.\n * No data loss through filtering.\n */\n\nimport type { LogEvent, LogLevel } from '../core/types';\n\n/**\n * Filter state interface\n */\nexport interface FilterState {\n /** Active log levels (empty = all) */\n levels: Set<LogLevel>;\n /** Text search query */\n search: string;\n /** File filter (partial match) */\n file: string;\n}\n\n/**\n * Create default filter state (show all)\n */\nexport function createDefaultFilterState(): FilterState {\n return {\n levels: new Set(['debug', 'info', 'warn', 'error']),\n search: '',\n file: '',\n };\n}\n\n/**\n * Check if a log matches the current filter\n */\nexport function matchesFilter(log: LogEvent, filter: FilterState): boolean {\n // Level filter\n if (filter.levels.size > 0 && !filter.levels.has(log.level)) {\n return false;\n }\n\n // File filter\n if (filter.file && !log.source.file.toLowerCase().includes(filter.file.toLowerCase())) {\n return false;\n }\n\n // Text search (searches message and data)\n if (filter.search) {\n const searchLower = filter.search.toLowerCase();\n const messageMatch = log.message.toLowerCase().includes(searchLower);\n const dataMatch = JSON.stringify(log.data).toLowerCase().includes(searchLower);\n\n if (!messageMatch && !dataMatch) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Filter logs based on current filter state\n */\nexport function filterLogs(logs: readonly LogEvent[], filter: FilterState): LogEvent[] {\n return logs.filter((log) => matchesFilter(log, filter));\n}\n\n/**\n * Check if any filter is active\n */\nexport function isFilterActive(filter: FilterState): boolean {\n const allLevels = filter.levels.size === 4; // All 4 levels selected\n return !allLevels || filter.search !== '' || filter.file !== '';\n}\n\n/**\n * Get unique files from logs\n */\nexport function getUniqueFiles(logs: readonly LogEvent[]): string[] {\n const files = new Set<string>();\n for (const log of logs) {\n if (log.source.file && log.source.file !== 'unknown') {\n files.add(log.source.file);\n }\n }\n return Array.from(files).sort();\n}\n\n/**\n * Create filter bar HTML\n */\nexport function createFilterBarHtml(filter: FilterState, logCount: number, filteredCount: number): string {\n const isActive = isFilterActive(filter);\n\n return `\n <div class=\"filter-bar ${isActive ? 'filter-active' : ''}\">\n <div class=\"filter-row\">\n <div class=\"filter-levels\">\n <button class=\"filter-level-btn ${filter.levels.has('debug') ? 'active' : ''}\" data-level=\"debug\" title=\"Debug\">D</button>\n <button class=\"filter-level-btn ${filter.levels.has('info') ? 'active' : ''}\" data-level=\"info\" title=\"Info\">I</button>\n <button class=\"filter-level-btn ${filter.levels.has('warn') ? 'active' : ''}\" data-level=\"warn\" title=\"Warning\">W</button>\n <button class=\"filter-level-btn ${filter.levels.has('error') ? 'active' : ''}\" data-level=\"error\" title=\"Error\">E</button>\n </div>\n <div class=\"filter-search\">\n <input type=\"text\" class=\"filter-input\" placeholder=\"Search logs...\" value=\"${escapeHtml(filter.search)}\" data-filter=\"search\">\n </div>\n <div class=\"filter-file\">\n <input type=\"text\" class=\"filter-input filter-file-input\" placeholder=\"Filter by file...\" value=\"${escapeHtml(filter.file)}\" data-filter=\"file\">\n </div>\n ${isActive ? `<button class=\"filter-clear-btn\" title=\"Clear filters\">✕</button>` : ''}\n </div>\n ${isActive ? `<div class=\"filter-status\">Showing ${filteredCount} of ${logCount} logs</div>` : ''}\n </div>\n `;\n}\n\n/**\n * Escape HTML for safe insertion\n */\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n","/**\n * Debug UI Overlay - Phase 4, 5, 6 Implementation\n *\n * Shadow DOM based overlay that displays logs in real-time.\n * Features:\n * - Toggle button (floating)\n * - Keyboard shortcut (Ctrl+Shift+L)\n * - Auto-scroll for new logs\n * - Level-coded log entries\n * - Expandable data\n * - Pop-out window with BroadcastChannel sync\n * - Filter & Search (Level, Text, File)\n */\n\nimport { logger } from '../core/logger';\nimport type { LogEvent, LogLevel, Unsubscribe } from '../core/types';\nimport { STYLES } from './styles';\nimport { createLogEntry, createEmptyState } from './log-entry';\nimport { channel, type ChannelMessage } from '../channel/broadcast';\nimport { openPopout, closePopout, isPopoutOpen } from './popout';\nimport {\n type FilterState,\n createDefaultFilterState,\n filterLogs,\n isFilterActive,\n createFilterBarHtml,\n} from './filter';\n\n/** Keyboard shortcut for toggle */\nconst SHORTCUT = { key: 'l', ctrlKey: true, shiftKey: true };\n\n/**\n * Show feedback after copy action\n */\nfunction showCopyFeedback(button: HTMLButtonElement, success: boolean): void {\n const originalText = button.textContent;\n button.textContent = success ? '✓' : '✗';\n button.disabled = true;\n setTimeout(() => {\n button.textContent = originalText;\n button.disabled = false;\n }, 1000);\n}\n\n/** DevLogger UI State */\ninterface UIState {\n initialized: boolean;\n visible: boolean;\n host: HTMLElement | null;\n shadow: ShadowRoot | null;\n container: HTMLElement | null;\n logsList: HTMLElement | null;\n filterBar: HTMLElement | null;\n toggleBtn: HTMLElement | null;\n badge: HTMLElement | null;\n unsubscribe: Unsubscribe | null;\n channelUnsubscribe: (() => void) | null;\n filter: FilterState;\n}\n\nconst state: UIState = {\n initialized: false,\n visible: false,\n host: null,\n shadow: null,\n container: null,\n logsList: null,\n filterBar: null,\n toggleBtn: null,\n badge: null,\n unsubscribe: null,\n channelUnsubscribe: null,\n filter: createDefaultFilterState(),\n};\n\n/**\n * Create the overlay DOM structure\n */\nfunction createOverlayDOM(shadow: ShadowRoot): void {\n // Add styles\n const style = document.createElement('style');\n style.textContent = STYLES;\n shadow.appendChild(style);\n\n // Toggle button (floating)\n const toggleBtn = document.createElement('button');\n toggleBtn.className = 'devlogger-toggle';\n toggleBtn.innerHTML = '📋';\n toggleBtn.title = 'Toggle DevLogger (Ctrl+Shift+L)';\n toggleBtn.addEventListener('click', () => DevLoggerUI.toggle());\n shadow.appendChild(toggleBtn);\n state.toggleBtn = toggleBtn;\n\n // Main container\n const container = document.createElement('div');\n container.className = 'devlogger-container hidden';\n\n const logs = logger.getLogs();\n const filteredLogs = filterLogs(logs, state.filter);\n\n container.innerHTML = `\n <div class=\"devlogger-header\">\n <div class=\"devlogger-title\">\n DevLogger\n <span class=\"devlogger-badge\">${filteredLogs.length}</span>\n </div>\n <div class=\"devlogger-actions\">\n <button class=\"devlogger-btn\" data-action=\"copy-json\" title=\"Copy as JSON\">JSON</button>\n <button class=\"devlogger-btn\" data-action=\"copy-text\" title=\"Copy as Text\">TXT</button>\n <button class=\"devlogger-btn\" data-action=\"clear\" title=\"Clear logs\">Clear</button>\n <button class=\"devlogger-btn devlogger-btn-primary\" data-action=\"popout\" title=\"Open in new window\">Pop-out</button>\n <button class=\"devlogger-btn\" data-action=\"close\" title=\"Close (Ctrl+Shift+L)\">✕</button>\n </div>\n </div>\n <div class=\"filter-bar-container\"></div>\n <div class=\"devlogger-logs\"></div>\n <div class=\"devlogger-footer\">\n <span class=\"devlogger-log-count\">${logs.length} logs</span>\n <span class=\"devlogger-shortcut\">Ctrl+Shift+L to toggle</span>\n </div>\n `;\n\n // Get references\n state.badge = container.querySelector('.devlogger-badge');\n state.logsList = container.querySelector('.devlogger-logs');\n state.filterBar = container.querySelector('.filter-bar-container');\n\n // Add button handlers\n container.querySelectorAll('[data-action]').forEach((btn) => {\n btn.addEventListener('click', (e) => {\n const action = (e.currentTarget as HTMLElement).dataset.action;\n const button = e.currentTarget as HTMLButtonElement;\n switch (action) {\n case 'clear':\n logger.clear();\n channel.sendClear();\n renderLogs();\n break;\n case 'popout':\n DevLoggerUI.popout();\n break;\n case 'close':\n DevLoggerUI.close();\n break;\n case 'copy-json':\n void logger.copyLogs({ format: 'json' }).then((success) => {\n showCopyFeedback(button, success);\n });\n break;\n case 'copy-text':\n void logger.copyLogs({ format: 'text' }).then((success) => {\n showCopyFeedback(button, success);\n });\n break;\n }\n });\n });\n\n shadow.appendChild(container);\n state.container = container;\n\n // Render filter bar and logs\n renderFilterBar();\n renderLogs();\n}\n\n/**\n * Render the filter bar\n */\nfunction renderFilterBar(): void {\n if (!state.filterBar) return;\n\n const logs = logger.getLogs();\n const filteredLogs = filterLogs(logs, state.filter);\n\n state.filterBar.innerHTML = createFilterBarHtml(state.filter, logs.length, filteredLogs.length);\n\n // Add event listeners for filter controls\n setupFilterListeners();\n}\n\n/**\n * Set up filter event listeners\n */\nfunction setupFilterListeners(): void {\n if (!state.filterBar) return;\n\n // Level buttons\n state.filterBar.querySelectorAll('.filter-level-btn').forEach((btn) => {\n btn.addEventListener('click', (e) => {\n const level = (e.currentTarget as HTMLElement).dataset.level as LogLevel;\n if (state.filter.levels.has(level)) {\n state.filter.levels.delete(level);\n } else {\n state.filter.levels.add(level);\n }\n renderFilterBar();\n renderLogs();\n });\n });\n\n // Search input - don't re-render the filter bar to keep focus\n const searchInput = state.filterBar.querySelector('[data-filter=\"search\"]') as HTMLInputElement;\n if (searchInput) {\n searchInput.addEventListener('input', (e) => {\n state.filter.search = (e.target as HTMLInputElement).value;\n updateFilterStatus();\n renderLogs();\n });\n }\n\n // File input - don't re-render the filter bar to keep focus\n const fileInput = state.filterBar.querySelector('[data-filter=\"file\"]') as HTMLInputElement;\n if (fileInput) {\n fileInput.addEventListener('input', (e) => {\n state.filter.file = (e.target as HTMLInputElement).value;\n updateFilterStatus();\n renderLogs();\n });\n }\n\n // Clear button\n const clearBtn = state.filterBar.querySelector('.filter-clear-btn');\n if (clearBtn) {\n clearBtn.addEventListener('click', () => {\n state.filter = createDefaultFilterState();\n renderFilterBar();\n renderLogs();\n });\n }\n}\n\n/**\n * Render all logs to the list (with filtering)\n */\nfunction renderLogs(): void {\n if (!state.logsList) return;\n\n const logs = logger.getLogs();\n const filteredLogs = filterLogs(logs, state.filter);\n\n state.logsList.innerHTML = '';\n\n if (logs.length === 0) {\n state.logsList.appendChild(createEmptyState());\n } else if (filteredLogs.length === 0) {\n // No results after filtering\n const noResults = document.createElement('div');\n noResults.className = 'devlogger-no-results';\n noResults.innerHTML = `\n <span class=\"devlogger-no-results-icon\">🔍</span>\n <span class=\"devlogger-no-results-text\">No logs match your filter</span>\n `;\n state.logsList.appendChild(noResults);\n } else {\n const fragment = document.createDocumentFragment();\n for (const log of filteredLogs) {\n fragment.appendChild(createLogEntry(log));\n }\n state.logsList.appendChild(fragment);\n }\n\n updateBadge(filteredLogs.length, logs.length);\n scrollToBottom();\n}\n\n/**\n * Add a single log entry (optimized for streaming)\n */\nfunction addLogEntry(log: LogEvent): void {\n if (!state.logsList) return;\n\n const logs = logger.getLogs();\n const filteredLogs = filterLogs(logs, state.filter);\n\n // Check if this log matches the filter\n const matchesCurrentFilter = filteredLogs.some((l) => l.id === log.id);\n\n if (matchesCurrentFilter) {\n // Remove empty state or no-results state if present\n const empty = state.logsList.querySelector('.devlogger-empty, .devlogger-no-results');\n if (empty) {\n empty.remove();\n }\n\n state.logsList.appendChild(createLogEntry(log));\n }\n\n updateBadge(filteredLogs.length, logs.length);\n updateFilterStatus();\n scrollToBottom();\n}\n\n/**\n * Update the log count badge\n */\nfunction updateBadge(filteredCount: number, totalCount: number): void {\n if (state.badge) {\n state.badge.textContent = String(filteredCount);\n }\n\n // Update footer count\n const footerCount = state.container?.querySelector('.devlogger-log-count');\n if (footerCount) {\n if (isFilterActive(state.filter)) {\n footerCount.textContent = `${filteredCount} of ${totalCount} logs`;\n } else {\n footerCount.textContent = `${totalCount} logs`;\n }\n }\n}\n\n/**\n * Update filter status display (without re-rendering inputs)\n */\nfunction updateFilterStatus(): void {\n if (!state.filterBar) return;\n\n const logs = logger.getLogs();\n const filteredLogs = filterLogs(logs, state.filter);\n const active = isFilterActive(state.filter);\n\n // Update filter-active class on the filter-bar\n const filterBar = state.filterBar.querySelector('.filter-bar');\n if (filterBar) {\n filterBar.classList.toggle('filter-active', active);\n }\n\n // Update or create status element\n let statusEl = state.filterBar.querySelector('.filter-status');\n if (active) {\n if (!statusEl) {\n statusEl = document.createElement('div');\n statusEl.className = 'filter-status';\n filterBar?.appendChild(statusEl);\n }\n statusEl.textContent = `Showing ${filteredLogs.length} of ${logs.length} logs`;\n\n // Add clear button if not present\n let clearBtn = state.filterBar.querySelector('.filter-clear-btn');\n if (!clearBtn) {\n clearBtn = document.createElement('button');\n clearBtn.className = 'filter-clear-btn';\n clearBtn.setAttribute('title', 'Clear filters');\n clearBtn.textContent = '✕';\n clearBtn.addEventListener('click', () => {\n state.filter = createDefaultFilterState();\n renderFilterBar();\n renderLogs();\n });\n const filterRow = state.filterBar.querySelector('.filter-row');\n filterRow?.appendChild(clearBtn);\n }\n } else {\n // Remove status and clear button when filter is inactive\n statusEl?.remove();\n state.filterBar.querySelector('.filter-clear-btn')?.remove();\n }\n}\n\n/**\n * Auto-scroll to bottom\n */\nfunction scrollToBottom(): void {\n if (state.logsList && state.visible) {\n state.logsList.scrollTop = state.logsList.scrollHeight;\n }\n}\n\n/**\n * Handle keyboard shortcuts\n */\nfunction handleKeydown(e: KeyboardEvent): void {\n if (\n e.key.toLowerCase() === SHORTCUT.key &&\n e.ctrlKey === SHORTCUT.ctrlKey &&\n e.shiftKey === SHORTCUT.shiftKey\n ) {\n e.preventDefault();\n DevLoggerUI.toggle();\n }\n}\n\n/**\n * Handle messages from pop-out window\n */\nfunction handleChannelMessage(message: ChannelMessage): void {\n switch (message.type) {\n case 'CLEAR_LOGS':\n logger.clear();\n renderLogs();\n break;\n case 'SYNC_REQUEST':\n channel.sendSyncResponse(logger.getLogs());\n break;\n }\n}\n\n/**\n * DevLogger UI Public API\n */\nexport const DevLoggerUI = {\n /**\n * Initialize the overlay UI\n */\n init(): void {\n try {\n if (state.initialized) {\n return;\n }\n\n // Skip initialization if logger is disabled (e.g., in production)\n if (!logger.isEnabled()) {\n return;\n }\n\n if (typeof document === 'undefined') {\n return;\n }\n\n const host = document.createElement('div');\n host.id = 'devlogger-host';\n document.body.appendChild(host);\n state.host = host;\n\n const shadow = host.attachShadow({ mode: 'open' });\n state.shadow = shadow;\n\n createOverlayDOM(shadow);\n\n state.unsubscribe = logger.subscribe((log) => {\n addLogEntry(log);\n channel.sendLog(log);\n });\n\n state.channelUnsubscribe = channel.subscribe(handleChannelMessage);\n\n document.addEventListener('keydown', handleKeydown);\n\n state.initialized = true;\n } catch (e) {\n console.warn('[DevLogger UI] Init error:', e);\n }\n },\n\n /**\n * Open the overlay panel\n */\n open(): void {\n try {\n if (!state.initialized) {\n this.init();\n }\n\n if (state.container) {\n state.container.classList.remove('hidden');\n state.visible = true;\n scrollToBottom();\n }\n } catch {\n // Silent fail\n }\n },\n\n /**\n * Close the overlay panel\n */\n close(): void {\n try {\n if (state.container) {\n state.container.classList.add('hidden');\n state.visible = false;\n }\n } catch {\n // Silent fail\n }\n },\n\n /**\n * Toggle the overlay panel\n */\n toggle(): void {\n if (state.visible) {\n this.close();\n } else {\n this.open();\n }\n },\n\n /**\n * Open logs in a separate window\n */\n popout(): void {\n try {\n if (!state.initialized) {\n this.init();\n }\n openPopout();\n } catch (e) {\n console.warn('[DevLogger] Failed to open pop-out:', e);\n }\n },\n\n /**\n * Close the pop-out window\n */\n closePopout(): void {\n closePopout();\n },\n\n /**\n * Check if pop-out window is open\n */\n isPopoutOpen(): boolean {\n return isPopoutOpen();\n },\n\n /**\n * Set filter state\n */\n setFilter(filter: Partial<FilterState>): void {\n try {\n if (filter.levels !== undefined) {\n state.filter.levels = filter.levels;\n }\n if (filter.search !== undefined) {\n state.filter.search = filter.search;\n }\n if (filter.file !== undefined) {\n state.filter.file = filter.file;\n }\n renderFilterBar();\n renderLogs();\n } catch {\n // Silent fail\n }\n },\n\n /**\n * Get current filter state\n */\n getFilter(): FilterState {\n return { ...state.filter, levels: new Set(state.filter.levels) };\n },\n\n /**\n * Clear all filters\n */\n clearFilter(): void {\n try {\n state.filter = createDefaultFilterState();\n renderFilterBar();\n renderLogs();\n } catch {\n // Silent fail\n }\n },\n\n /**\n * Destroy the UI and clean up\n */\n destroy(): void {\n try {\n closePopout();\n\n if (state.unsubscribe) {\n state.unsubscribe();\n state.unsubscribe = null;\n }\n\n if (state.channelUnsubscribe) {\n state.channelUnsubscribe();\n state.channelUnsubscribe = null;\n }\n\n document.removeEventListener('keydown', handleKeydown);\n\n if (state.host) {\n state.host.remove();\n state.host = null;\n }\n\n state.initialized = false;\n state.visible = false;\n state.shadow = null;\n state.container = null;\n state.logsList = null;\n state.filterBar = null;\n state.toggleBtn = null;\n state.badge = null;\n state.filter = createDefaultFilterState();\n } catch {\n // Silent fail\n }\n },\n\n /**\n * Check if the UI is currently visible\n */\n isVisible(): boolean {\n return state.visible;\n },\n\n /**\n * Check if the UI has been initialized\n */\n isInitialized(): boolean {\n return state.initialized;\n },\n};\n","/**\n * Global Error Capture Module\n *\n * Automatically captures uncaught errors and unhandled promise rejections\n * and logs them through the DevLogger system.\n *\n * Features:\n * - window.onerror for synchronous errors\n * - window.onunhandledrejection for promise rejections\n * - Preserves existing handlers (chaining)\n * - Can be enabled/disabled at runtime\n * - Zero-throw policy maintained\n */\n\nimport { logger } from './logger';\n\n/** Configuration for error capture */\nexport interface ErrorCaptureConfig {\n /** Capture synchronous errors via window.onerror */\n captureErrors?: boolean;\n /** Capture unhandled promise rejections */\n captureRejections?: boolean;\n /** Prefix for error messages */\n errorPrefix?: string;\n /** Prefix for rejection messages */\n rejectionPrefix?: string;\n}\n\nconst DEFAULT_CONFIG: Required<ErrorCaptureConfig> = {\n captureErrors: true,\n captureRejections: true,\n errorPrefix: '[Uncaught Error]',\n rejectionPrefix: '[Unhandled Rejection]',\n};\n\n/** Internal state */\nlet isInstalled = false;\nlet config: Required<ErrorCaptureConfig> = { ...DEFAULT_CONFIG };\n\n// Store original handlers to restore later and chain calls\nlet originalOnError: OnErrorEventHandler = null;\n\n// Bound handler reference for removal\nlet boundRejectionHandler: ((event: PromiseRejectionEvent) => void) | null = null;\n\n/**\n * Handle uncaught errors\n */\nfunction handleError(\n event: Event | string,\n source?: string,\n lineno?: number,\n colno?: number,\n error?: Error\n): boolean {\n try {\n // Build error info\n const errorObj = error || (event instanceof ErrorEvent ? event.error : null);\n const message = errorObj?.message || String(event);\n const stack = errorObj?.stack;\n\n // Log through DevLogger\n logger.error(`${config.errorPrefix} ${message}`, {\n source: source || 'unknown',\n line: lineno || 0,\n column: colno || 0,\n stack,\n originalError: errorObj\n ? {\n name: errorObj.name,\n message: errorObj.message,\n stack: errorObj.stack,\n }\n : undefined,\n });\n } catch {\n // Zero-throw policy - silently fail\n }\n\n // Chain to original handler if it exists\n if (originalOnError) {\n try {\n return originalOnError(event, source, lineno, colno, error) ?? false;\n } catch {\n // Ignore errors in original handler\n }\n }\n\n // Return false to allow default browser handling\n return false;\n}\n\n/**\n * Handle unhandled promise rejections\n */\nfunction handleRejection(event: PromiseRejectionEvent): void {\n try {\n const reason = event.reason;\n let message: string;\n let errorInfo: Record<string, unknown> = {};\n\n if (reason instanceof Error) {\n message = reason.message;\n errorInfo = {\n name: reason.name,\n message: reason.message,\n stack: reason.stack,\n };\n } else if (typeof reason === 'string') {\n message = reason;\n } else {\n message = 'Unknown rejection reason';\n errorInfo = { reason };\n }\n\n // Log through DevLogger\n logger.error(`${config.rejectionPrefix} ${message}`, errorInfo);\n } catch {\n // Zero-throw policy - silently fail\n }\n}\n\n/**\n * Install global error handlers\n */\nfunction install(options: ErrorCaptureConfig = {}): void {\n if (isInstalled) {\n // Update config if already installed\n config = { ...config, ...options };\n return;\n }\n\n try {\n // Check if we're in a browser environment\n if (typeof window === 'undefined') {\n return;\n }\n\n config = { ...DEFAULT_CONFIG, ...options };\n\n // Store original error handler\n originalOnError = window.onerror;\n\n // Install error handler\n if (config.captureErrors) {\n window.onerror = handleError;\n }\n\n // Install rejection handler using addEventListener for proper event dispatch\n if (config.captureRejections) {\n boundRejectionHandler = handleRejection;\n window.addEventListener('unhandledrejection', boundRejectionHandler);\n }\n\n isInstalled = true;\n } catch {\n // Zero-throw policy - silently fail\n }\n}\n\n/**\n * Uninstall global error handlers and restore originals\n */\nfunction uninstall(): void {\n if (!isInstalled) {\n return;\n }\n\n try {\n if (typeof window === 'undefined') {\n return;\n }\n\n // Restore original error handler\n window.onerror = originalOnError;\n\n // Remove rejection handler\n if (boundRejectionHandler) {\n window.removeEventListener('unhandledrejection', boundRejectionHandler);\n }\n\n // Reset state\n originalOnError = null;\n boundRejectionHandler = null;\n isInstalled = false;\n } catch {\n // Zero-throw policy - silently fail\n }\n}\n\n/**\n * Check if error capture is installed\n */\nfunction isActive(): boolean {\n return isInstalled;\n}\n\n/**\n * Get current configuration\n */\nfunction getConfig(): Readonly<Required<ErrorCaptureConfig>> {\n return { ...config };\n}\n\n/**\n * Error Capture Public API\n */\nexport const ErrorCapture = {\n /**\n * Install global error handlers\n *\n * @example\n * ```typescript\n * // Install with defaults\n * ErrorCapture.install();\n *\n * // Install with custom config\n * ErrorCapture.install({\n * captureErrors: true,\n * captureRejections: true,\n * errorPrefix: '[ERROR]',\n * });\n * ```\n */\n install,\n\n /**\n * Uninstall global error handlers and restore originals\n */\n uninstall,\n\n /**\n * Check if error capture is currently active\n */\n isActive,\n\n /**\n * Get current configuration\n */\n getConfig,\n};\n","/**\n * Log Persistence Module\n *\n * Provides early persistence of logs to survive page crashes.\n * Uses sessionStorage for session-scoped persistence.\n *\n * Features:\n * - Immediate persistence after each log (debounced for performance)\n * - Crash rehydration on page load\n * - Storage quota handling\n * - Zero-throw policy maintained\n */\n\nimport type { LogEvent, Unsubscribe } from './types';\nimport { logger } from './logger';\n\n/** Storage key for persisted logs */\nconst STORAGE_KEY = 'devlogger_persisted_logs';\n\n/** Storage key for crash detection */\nconst CRASH_FLAG_KEY = 'devlogger_session_active';\n\n/** Configuration for persistence */\nexport interface PersistenceConfig {\n /** Storage type: 'session' (sessionStorage) or 'local' (localStorage) */\n storage?: 'session' | 'local';\n /** Maximum logs to persist (default: 500) */\n maxPersisted?: number;\n /** Debounce delay in ms for batching writes (default: 100) */\n debounceMs?: number;\n}\n\nconst DEFAULT_CONFIG: Required<PersistenceConfig> = {\n storage: 'session',\n maxPersisted: 500,\n debounceMs: 100,\n};\n\n/** Internal state */\nlet isEnabled = false;\nlet config: Required<PersistenceConfig> = { ...DEFAULT_CONFIG };\nlet pendingLogs: LogEvent[] = [];\nlet debounceTimer: ReturnType<typeof setTimeout> | null = null;\nlet wasUncleanShutdown = false;\nlet unsubscribeFromLogger: Unsubscribe | null = null;\n\n/**\n * Get the appropriate storage object\n */\nfunction getStorage(): Storage | null {\n try {\n if (typeof window === 'undefined') {\n return null;\n }\n return config.storage === 'local' ? localStorage : sessionStorage;\n } catch {\n return null;\n }\n}\n\n/**\n * Check if there was a crash (unclean shutdown)\n */\nfunction detectCrash(): boolean {\n try {\n const storage = getStorage();\n if (!storage) return false;\n\n // If the flag exists from a previous session, it was an unclean shutdown\n const flag = storage.getItem(CRASH_FLAG_KEY);\n return flag === 'active';\n } catch {\n return false;\n }\n}\n\n/**\n * Set the active session flag\n */\nfunction setActiveFlag(): void {\n try {\n const storage = getStorage();\n if (!storage) return;\n storage.setItem(CRASH_FLAG_KEY, 'active');\n } catch {\n // Ignore storage errors\n }\n}\n\n/**\n * Clear the active session flag (clean shutdown)\n */\nfunction clearActiveFlag(): void {\n try {\n const storage = getStorage();\n if (!storage) return;\n storage.removeItem(CRASH_FLAG_KEY);\n } catch {\n // Ignore storage errors\n }\n}\n\n/**\n * Persist logs to storage\n */\nfunction persistLogs(logs: LogEvent[]): void {\n try {\n const storage = getStorage();\n if (!storage) return;\n\n // Trim to max persisted\n const toStore = logs.slice(-config.maxPersisted);\n\n // Serialize and store\n const serialized = JSON.stringify(toStore);\n storage.setItem(STORAGE_KEY, serialized);\n } catch (e) {\n // Handle quota exceeded\n if (e instanceof DOMException && e.name === 'QuotaExceededError') {\n // Try with fewer logs\n try {\n const storage = getStorage();\n if (!storage) return;\n const reduced = logs.slice(-Math.floor(config.maxPersisted / 2));\n storage.setItem(STORAGE_KEY, JSON.stringify(reduced));\n } catch {\n // Give up silently\n }\n }\n }\n}\n\n/**\n * Load persisted logs from storage\n */\nfunction loadPersistedLogs(): LogEvent[] {\n try {\n const storage = getStorage();\n if (!storage) return [];\n\n const serialized = storage.getItem(STORAGE_KEY);\n if (!serialized) return [];\n\n const logs = JSON.parse(serialized);\n if (!Array.isArray(logs)) return [];\n\n // Validate basic structure\n return logs.filter(\n (log): log is LogEvent =>\n log &&\n typeof log.id === 'string' &&\n typeof log.timestamp === 'number' &&\n typeof log.level === 'string' &&\n typeof log.message === 'string'\n );\n } catch {\n return [];\n }\n}\n\n/**\n * Clear persisted logs\n */\nfunction clearPersistedLogs(): void {\n try {\n const storage = getStorage();\n if (!storage) return;\n storage.removeItem(STORAGE_KEY);\n } catch {\n // Ignore errors\n }\n}\n\n/**\n * Debounced persist function\n */\nfunction schedulePersist(allLogs: LogEvent[]): void {\n // Store reference to latest logs\n pendingLogs = allLogs;\n\n // Clear existing timer\n if (debounceTimer) {\n clearTimeout(debounceTimer);\n }\n\n // Schedule persist\n debounceTimer = setTimeout(() => {\n persistLogs(pendingLogs);\n debounceTimer = null;\n }, config.debounceMs);\n}\n\n/**\n * Handle page unload - ensure logs are persisted\n */\nfunction handleBeforeUnload(): void {\n try {\n // Cancel debounce and persist immediately\n if (debounceTimer) {\n clearTimeout(debounceTimer);\n debounceTimer = null;\n }\n\n if (pendingLogs.length > 0) {\n persistLogs(pendingLogs);\n }\n\n // Clear active flag for clean shutdown\n clearActiveFlag();\n } catch {\n // Ignore errors during unload\n }\n}\n\n/**\n * Handle new log event from subscription\n */\nfunction handleNewLog(): void {\n if (!isEnabled) return;\n // Get all current logs from logger and schedule persist\n const allLogs = logger.getLogs();\n schedulePersist([...allLogs]);\n}\n\n/**\n * Enable persistence\n */\nfunction enable(options: PersistenceConfig = {}): void {\n if (isEnabled) {\n // Update config\n config = { ...config, ...options };\n return;\n }\n\n try {\n if (typeof window === 'undefined') {\n return;\n }\n\n config = { ...DEFAULT_CONFIG, ...options };\n\n // Check for crash before setting new flag\n wasUncleanShutdown = detectCrash();\n\n // Set active flag\n setActiveFlag();\n\n // Subscribe to logger to track new logs\n unsubscribeFromLogger = logger.subscribe(handleNewLog);\n\n // Register unload handler\n window.addEventListener('beforeunload', handleBeforeUnload);\n window.addEventListener('pagehide', handleBeforeUnload);\n\n isEnabled = true;\n } catch {\n // Silent fail\n }\n}\n\n/**\n * Disable persistence\n */\nfunction disable(): void {\n if (!isEnabled) return;\n\n try {\n if (typeof window === 'undefined') {\n return;\n }\n\n // Cancel pending persist\n if (debounceTimer) {\n clearTimeout(debounceTimer);\n debounceTimer = null;\n }\n\n // Unsubscribe from logger\n if (unsubscribeFromLogger) {\n unsubscribeFromLogger();\n unsubscribeFromLogger = null;\n }\n\n // Remove handlers\n window.removeEventListener('beforeunload', handleBeforeUnload);\n window.removeEventListener('pagehide', handleBeforeUnload);\n\n // Clear active flag\n clearActiveFlag();\n\n isEnabled = false;\n } catch {\n // Silent fail\n }\n}\n\n/**\n * Check if last session crashed\n */\nfunction hadCrash(): boolean {\n return wasUncleanShutdown;\n}\n\n/**\n * Get persisted logs (for rehydration)\n */\nfunction getPersistedLogs(): LogEvent[] {\n return loadPersistedLogs();\n}\n\n/**\n * Rehydrate logs from storage into the logger\n * Returns number of logs rehydrated\n */\nfunction rehydrate(): number {\n try {\n const logs = loadPersistedLogs();\n if (logs.length === 0) {\n return 0;\n }\n\n // Import logs into the logger\n logger.importLogs(logs);\n return logs.length;\n } catch {\n return 0;\n }\n}\n\n/**\n * Clear all persisted data\n */\nfunction clear(): void {\n clearPersistedLogs();\n pendingLogs = [];\n wasUncleanShutdown = false;\n}\n\n/**\n * Check if persistence is enabled\n */\nfunction isActive(): boolean {\n return isEnabled;\n}\n\n/**\n * Get current configuration\n */\nfunction getConfig(): Readonly<Required<PersistenceConfig>> {\n return { ...config };\n}\n\n/**\n * Log Persistence Public API\n */\nexport const LogPersistence = {\n /**\n * Enable log persistence\n *\n * @example\n * ```typescript\n * LogPersistence.enable();\n *\n * // With options\n * LogPersistence.enable({\n * storage: 'session',\n * maxPersisted: 500,\n * debounceMs: 100\n * });\n * ```\n */\n enable,\n\n /**\n * Disable log persistence\n */\n disable,\n\n /**\n * Check if persistence is enabled\n */\n isActive,\n\n /**\n * Check if the last session had a crash (unclean shutdown)\n */\n hadCrash,\n\n /**\n * Get persisted logs from previous session (without importing)\n */\n getPersistedLogs,\n\n /**\n * Rehydrate logs from storage into the logger\n * Call this at app startup to restore logs from previous session\n *\n * @returns Number of logs rehydrated\n *\n * @example\n * ```typescript\n * // At app start\n * LogPersistence.enable();\n * const count = LogPersistence.rehydrate();\n * if (LogPersistence.hadCrash()) {\n * console.log(`Recovered ${count} logs from crash`);\n * }\n * ```\n */\n rehydrate,\n\n /**\n * Clear all persisted logs\n */\n clear,\n\n /**\n * Get current configuration\n */\n getConfig,\n};\n","/**\n * Network Capture - Fetch/XHR Hook\n *\n * Automatically creates spans for network requests.\n * Captures request/response data and correlates with logs.\n */\n\nimport { logger } from './logger';\nimport type { LogContext } from './types';\n\n/**\n * Network capture configuration\n */\nexport interface NetworkCaptureConfig {\n /** Capture fetch requests (default: true) */\n captureFetch?: boolean;\n /** Capture XHR requests (default: true) */\n captureXHR?: boolean;\n /** Include request headers (default: false, may contain sensitive data) */\n includeHeaders?: boolean;\n /** Include request body (default: false, may be large) */\n includeBody?: boolean;\n /** Include response body (default: false, may be large) */\n includeResponse?: boolean;\n /** Max response body length to capture (default: 1000) */\n maxResponseLength?: number;\n /** URL patterns to ignore (e.g., analytics, hot reload) */\n ignorePatterns?: (string | RegExp)[];\n /** Custom context to add to all network logs */\n context?: LogContext;\n}\n\nconst DEFAULT_CONFIG: Required<NetworkCaptureConfig> = {\n captureFetch: true,\n captureXHR: true,\n includeHeaders: false,\n includeBody: false,\n includeResponse: false,\n maxResponseLength: 1000,\n ignorePatterns: [],\n context: {},\n};\n\n// Store original implementations\nlet originalFetch: typeof globalThis.fetch | null = null;\nlet originalXHROpen: typeof XMLHttpRequest.prototype.open | null = null;\nlet originalXHRSend: typeof XMLHttpRequest.prototype.send | null = null;\n\nlet isActive = false;\nlet config: Required<NetworkCaptureConfig> = { ...DEFAULT_CONFIG };\n\n/**\n * Check if URL should be ignored\n */\nfunction shouldIgnore(url: string): boolean {\n for (const pattern of config.ignorePatterns) {\n if (typeof pattern === 'string') {\n if (url.includes(pattern)) return true;\n } else if (pattern.test(url)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Extract useful URL info\n */\nfunction parseUrl(url: string): { host: string; path: string; full: string } {\n try {\n const parsed = new URL(url, window.location.origin);\n return {\n host: parsed.host,\n path: parsed.pathname + parsed.search,\n full: parsed.href,\n };\n } catch {\n return { host: 'unknown', path: url, full: url };\n }\n}\n\n/**\n * Truncate string if too long\n */\nfunction truncate(str: string, maxLen: number): string {\n if (str.length <= maxLen) return str;\n return str.slice(0, maxLen) + '... (truncated)';\n}\n\n/**\n * Safe JSON parse\n */\nfunction safeParseJSON(str: string): unknown {\n try {\n return JSON.parse(str);\n } catch {\n return str;\n }\n}\n\n/**\n * Create patched fetch function\n */\nfunction createPatchedFetch(): typeof globalThis.fetch {\n return async function patchedFetch(\n input: RequestInfo | URL,\n init?: RequestInit\n ): Promise<Response> {\n const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;\n\n if (shouldIgnore(url)) {\n return originalFetch!(input, init);\n }\n\n const { host, path } = parseUrl(url);\n const method = init?.method || 'GET';\n const spanName = `${method} ${path}`;\n\n const span = logger.span(spanName, {\n ...config.context,\n type: 'fetch',\n method,\n host,\n });\n\n span.info(`Request started: ${url}`);\n\n if (config.includeHeaders && init?.headers) {\n span.debug('Request headers', init.headers);\n }\n\n if (config.includeBody && init?.body) {\n try {\n const body = typeof init.body === 'string' ? safeParseJSON(init.body) : init.body;\n span.debug('Request body', body);\n } catch {\n span.debug('Request body', '[Unable to parse]');\n }\n }\n\n const startTime = performance.now();\n\n try {\n const response = await originalFetch!(input, init);\n const duration = Math.round(performance.now() - startTime);\n\n if (response.ok) {\n span.info(`Response: ${response.status} ${response.statusText} (${duration}ms)`);\n\n if (config.includeResponse) {\n try {\n const clone = response.clone();\n const text = await clone.text();\n const body = safeParseJSON(truncate(text, config.maxResponseLength));\n span.debug('Response body', body);\n } catch {\n span.debug('Response body', '[Unable to read]');\n }\n }\n\n span.end();\n } else {\n span.warn(`Response: ${response.status} ${response.statusText} (${duration}ms)`);\n span.fail();\n }\n\n return response;\n } catch (error) {\n const duration = Math.round(performance.now() - startTime);\n span.error(`Request failed after ${duration}ms`, error);\n span.fail();\n throw error;\n }\n };\n}\n\n/**\n * Patch XMLHttpRequest\n */\nfunction patchXHR(): void {\n originalXHROpen = XMLHttpRequest.prototype.open;\n originalXHRSend = XMLHttpRequest.prototype.send;\n\n XMLHttpRequest.prototype.open = function (\n method: string,\n url: string | URL,\n async: boolean = true,\n username?: string | null,\n password?: string | null\n ) {\n const urlStr = typeof url === 'string' ? url : url.href;\n\n // Store request info for later\n (this as XMLHttpRequest & { __networkCapture?: unknown }).__networkCapture = {\n method,\n url: urlStr,\n ignored: shouldIgnore(urlStr),\n };\n\n return originalXHROpen!.call(this, method, url, async, username, password);\n };\n\n XMLHttpRequest.prototype.send = function (body?: Document | XMLHttpRequestBodyInit | null) {\n const captureInfo = (this as XMLHttpRequest & { __networkCapture?: { method: string; url: string; ignored: boolean } }).__networkCapture;\n\n if (!captureInfo || captureInfo.ignored) {\n return originalXHRSend!.call(this, body);\n }\n\n const { method, url } = captureInfo;\n const { host, path } = parseUrl(url);\n const spanName = `${method} ${path}`;\n\n const span = logger.span(spanName, {\n ...config.context,\n type: 'xhr',\n method,\n host,\n });\n\n span.info(`XHR Request started: ${url}`);\n\n if (config.includeBody && body) {\n try {\n const bodyData = typeof body === 'string' ? safeParseJSON(body) : body;\n span.debug('Request body', bodyData);\n } catch {\n span.debug('Request body', '[Unable to parse]');\n }\n }\n\n const startTime = performance.now();\n\n this.addEventListener('load', () => {\n const duration = Math.round(performance.now() - startTime);\n\n if (this.status >= 200 && this.status < 400) {\n span.info(`Response: ${this.status} ${this.statusText} (${duration}ms)`);\n\n if (config.includeResponse && this.responseText) {\n const body = safeParseJSON(truncate(this.responseText, config.maxResponseLength));\n span.debug('Response body', body);\n }\n\n span.end();\n } else {\n span.warn(`Response: ${this.status} ${this.statusText} (${duration}ms)`);\n span.fail();\n }\n });\n\n this.addEventListener('error', () => {\n const duration = Math.round(performance.now() - startTime);\n span.error(`XHR Request failed after ${duration}ms`);\n span.fail();\n });\n\n this.addEventListener('abort', () => {\n const duration = Math.round(performance.now() - startTime);\n span.warn(`XHR Request aborted after ${duration}ms`);\n span.fail();\n });\n\n this.addEventListener('timeout', () => {\n const duration = Math.round(performance.now() - startTime);\n span.error(`XHR Request timeout after ${duration}ms`);\n span.fail();\n });\n\n return originalXHRSend!.call(this, body);\n };\n}\n\n/**\n * Restore original XHR\n */\nfunction restoreXHR(): void {\n if (originalXHROpen) {\n XMLHttpRequest.prototype.open = originalXHROpen;\n originalXHROpen = null;\n }\n if (originalXHRSend) {\n XMLHttpRequest.prototype.send = originalXHRSend;\n originalXHRSend = null;\n }\n}\n\n/**\n * Network Capture API\n */\nexport const NetworkCapture = {\n /**\n * Install network capture hooks\n */\n install(userConfig: NetworkCaptureConfig = {}): void {\n try {\n if (isActive) {\n return;\n }\n\n config = { ...DEFAULT_CONFIG, ...userConfig };\n\n if (config.captureFetch && typeof globalThis.fetch === 'function') {\n originalFetch = globalThis.fetch;\n globalThis.fetch = createPatchedFetch();\n }\n\n if (config.captureXHR && typeof XMLHttpRequest !== 'undefined') {\n patchXHR();\n }\n\n isActive = true;\n logger.debug('[NetworkCapture] Installed', {\n fetch: config.captureFetch,\n xhr: config.captureXHR,\n });\n } catch (e) {\n console.warn('[NetworkCapture] Install error:', e);\n }\n },\n\n /**\n * Uninstall network capture hooks\n */\n uninstall(): void {\n try {\n if (!isActive) {\n return;\n }\n\n if (originalFetch) {\n globalThis.fetch = originalFetch;\n originalFetch = null;\n }\n\n restoreXHR();\n\n isActive = false;\n logger.debug('[NetworkCapture] Uninstalled');\n } catch (e) {\n console.warn('[NetworkCapture] Uninstall error:', e);\n }\n },\n\n /**\n * Check if capture is active\n */\n isActive(): boolean {\n return isActive;\n },\n\n /**\n * Get current configuration\n */\n getConfig(): Readonly<Required<NetworkCaptureConfig>> {\n return { ...config };\n },\n\n /**\n * Update ignore patterns\n */\n addIgnorePattern(pattern: string | RegExp): void {\n config.ignorePatterns.push(pattern);\n },\n};\n","/**\n * Timeline / Flame-View Component\n *\n * Lightweight timeline visualization for logs and spans.\n * Features:\n * - Logs displayed on time axis\n * - Spans visualized as bars\n * - Zoom to last X seconds\n * - Hover for details\n */\n\nimport { logger } from '../core/logger';\nimport type { LogEvent, SpanEvent, LogLevel } from '../core/types';\n\n/**\n * Timeline configuration\n */\nexport interface TimelineConfig {\n /** Container element or selector */\n container: HTMLElement | string;\n /** Time window in milliseconds (default: 60000 = 1 minute) */\n timeWindow?: number;\n /** Auto-refresh interval in ms (default: 1000) */\n refreshInterval?: number;\n /** Show span bars (default: true) */\n showSpans?: boolean;\n /** Show log markers (default: true) */\n showLogs?: boolean;\n /** Height of timeline in pixels (default: 200) */\n height?: number;\n}\n\nconst LEVEL_COLORS: Record<LogLevel, string> = {\n debug: '#6e6e6e',\n info: '#3794ff',\n warn: '#cca700',\n error: '#f14c4c',\n};\n\nconst SPAN_STATUS_COLORS = {\n running: '#3794ff',\n success: '#4caf50',\n error: '#f14c4c',\n};\n\n/**\n * Timeline class\n */\nexport class Timeline {\n private container: HTMLElement;\n private config: Required<TimelineConfig>;\n private canvas: HTMLCanvasElement | null = null;\n private ctx: CanvasRenderingContext2D | null = null;\n private intervalId: number | null = null;\n private tooltip: HTMLElement | null = null;\n // Bounds maps to avoid mutating readonly objects\n private spanBounds: Map<string, { x: number; y: number; w: number; h: number }> = new Map();\n private logBounds: Map<string, { x: number; y: number; r: number }> = new Map();\n\n constructor(userConfig: TimelineConfig) {\n const containerEl =\n typeof userConfig.container === 'string'\n ? document.querySelector<HTMLElement>(userConfig.container)\n : userConfig.container;\n\n if (!containerEl) {\n throw new Error('Timeline: Container not found');\n }\n\n this.container = containerEl;\n this.config = {\n container: containerEl,\n timeWindow: userConfig.timeWindow ?? 60000,\n refreshInterval: userConfig.refreshInterval ?? 1000,\n showSpans: userConfig.showSpans ?? true,\n showLogs: userConfig.showLogs ?? true,\n height: userConfig.height ?? 200,\n };\n\n this.init();\n }\n\n private init(): void {\n // Create container structure\n this.container.innerHTML = `\n <div class=\"devlogger-timeline\" style=\"\n position: relative;\n background: #1e1e1e;\n border: 1px solid #3c3c3c;\n border-radius: 4px;\n overflow: hidden;\n \">\n <div class=\"timeline-header\" style=\"\n display: flex;\n justify-content: space-between;\n padding: 8px 12px;\n background: #252526;\n border-bottom: 1px solid #3c3c3c;\n font-family: 'SF Mono', monospace;\n font-size: 12px;\n color: #ccc;\n \">\n <span>Timeline</span>\n <div class=\"timeline-controls\">\n <button data-window=\"10000\" style=\"\n background: transparent;\n border: 1px solid #3c3c3c;\n color: #858585;\n padding: 2px 8px;\n border-radius: 3px;\n cursor: pointer;\n margin-left: 4px;\n font-size: 11px;\n \">10s</button>\n <button data-window=\"30000\" style=\"\n background: transparent;\n border: 1px solid #3c3c3c;\n color: #858585;\n padding: 2px 8px;\n border-radius: 3px;\n cursor: pointer;\n margin-left: 4px;\n font-size: 11px;\n \">30s</button>\n <button data-window=\"60000\" style=\"\n background: #0e639c;\n border: 1px solid #0e639c;\n color: white;\n padding: 2px 8px;\n border-radius: 3px;\n cursor: pointer;\n margin-left: 4px;\n font-size: 11px;\n \">1m</button>\n <button data-window=\"300000\" style=\"\n background: transparent;\n border: 1px solid #3c3c3c;\n color: #858585;\n padding: 2px 8px;\n border-radius: 3px;\n cursor: pointer;\n margin-left: 4px;\n font-size: 11px;\n \">5m</button>\n </div>\n </div>\n <div class=\"timeline-canvas-container\" style=\"position: relative;\">\n <canvas class=\"timeline-canvas\"></canvas>\n </div>\n <div class=\"timeline-tooltip\" style=\"\n position: absolute;\n display: none;\n background: #333;\n border: 1px solid #555;\n padding: 8px;\n border-radius: 4px;\n font-family: 'SF Mono', monospace;\n font-size: 11px;\n color: #ccc;\n max-width: 300px;\n z-index: 1000;\n pointer-events: none;\n \"></div>\n </div>\n `;\n\n // Get elements\n this.canvas = this.container.querySelector('.timeline-canvas');\n this.tooltip = this.container.querySelector('.timeline-tooltip');\n\n if (this.canvas) {\n this.ctx = this.canvas.getContext('2d');\n this.resizeCanvas();\n }\n\n // Set up controls\n this.container.querySelectorAll('[data-window]').forEach((btn) => {\n btn.addEventListener('click', (e) => {\n const target = e.currentTarget as HTMLButtonElement;\n const window = parseInt(target.dataset.window || '60000', 10);\n this.setTimeWindow(window);\n\n // Update button styles\n this.container.querySelectorAll('[data-window]').forEach((b) => {\n const el = b as HTMLButtonElement;\n if (el === target) {\n el.style.background = '#0e639c';\n el.style.borderColor = '#0e639c';\n el.style.color = 'white';\n } else {\n el.style.background = 'transparent';\n el.style.borderColor = '#3c3c3c';\n el.style.color = '#858585';\n }\n });\n });\n });\n\n // Set up mouse events for tooltip\n if (this.canvas) {\n this.canvas.addEventListener('mousemove', this.handleMouseMove.bind(this));\n this.canvas.addEventListener('mouseleave', this.handleMouseLeave.bind(this));\n }\n\n // Start auto-refresh\n this.startRefresh();\n }\n\n private resizeCanvas(): void {\n if (!this.canvas) return;\n const rect = this.container.getBoundingClientRect();\n this.canvas.width = rect.width - 2; // Account for border\n this.canvas.height = this.config.height;\n this.render();\n }\n\n private startRefresh(): void {\n if (this.intervalId) return;\n this.intervalId = window.setInterval(() => this.render(), this.config.refreshInterval);\n this.render();\n }\n\n private stopRefresh(): void {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n this.intervalId = null;\n }\n }\n\n setTimeWindow(ms: number): void {\n this.config.timeWindow = ms;\n this.render();\n }\n\n private render(): void {\n if (!this.canvas || !this.ctx) return;\n\n const ctx = this.ctx;\n const width = this.canvas.width;\n const height = this.canvas.height;\n const now = Date.now();\n const startTime = now - this.config.timeWindow;\n\n // Clear canvas\n ctx.fillStyle = '#1e1e1e';\n ctx.fillRect(0, 0, width, height);\n\n // Clear bounds maps\n this.spanBounds.clear();\n this.logBounds.clear();\n\n // Draw time grid\n this.drawTimeGrid(ctx, width, height, startTime, now);\n\n // Get data\n const logs = logger.getLogs().filter((l) => l.timestamp >= startTime);\n const spans = logger.getSpans().filter((s) => s.startTime >= startTime || (s.endTime && s.endTime >= startTime));\n\n // Draw spans\n if (this.config.showSpans) {\n this.drawSpans(ctx, spans, width, height, startTime, now);\n }\n\n // Draw log markers\n if (this.config.showLogs) {\n this.drawLogs(ctx, logs, width, height, startTime, now);\n }\n }\n\n private drawTimeGrid(\n ctx: CanvasRenderingContext2D,\n width: number,\n height: number,\n startTime: number,\n endTime: number\n ): void {\n const duration = endTime - startTime;\n const gridLines = 6;\n\n ctx.strokeStyle = '#333';\n ctx.lineWidth = 1;\n ctx.font = '10px SF Mono, monospace';\n ctx.fillStyle = '#666';\n\n for (let i = 0; i <= gridLines; i++) {\n const x = (i / gridLines) * width;\n const time = startTime + (duration * i) / gridLines;\n\n ctx.beginPath();\n ctx.moveTo(x, 0);\n ctx.lineTo(x, height);\n ctx.stroke();\n\n // Time label\n const date = new Date(time);\n const label = `${date.getMinutes().toString().padStart(2, '0')}:${date.getSeconds().toString().padStart(2, '0')}`;\n ctx.fillText(label, x + 2, height - 4);\n }\n }\n\n private drawSpans(\n ctx: CanvasRenderingContext2D,\n spans: readonly SpanEvent[],\n width: number,\n _height: number,\n startTime: number,\n endTime: number\n ): void {\n const duration = endTime - startTime;\n const spanHeight = 20;\n const spanMargin = 4;\n const topOffset = 20;\n\n // Group spans by parent for nesting\n const rootSpans = spans.filter((s) => !s.parentId);\n let yOffset = topOffset;\n\n const drawSpan = (span: SpanEvent, y: number, _depth: number = 0): number => {\n const x1 = ((span.startTime - startTime) / duration) * width;\n const x2 = ((span.endTime || endTime) - startTime) / duration * width;\n const barWidth = Math.max(x2 - x1, 2);\n\n const color = SPAN_STATUS_COLORS[span.status];\n\n // Draw span bar\n ctx.fillStyle = color + '40'; // Semi-transparent\n ctx.fillRect(x1, y, barWidth, spanHeight);\n\n ctx.strokeStyle = color;\n ctx.lineWidth = 1;\n ctx.strokeRect(x1, y, barWidth, spanHeight);\n\n // Draw span name\n ctx.fillStyle = '#ccc';\n ctx.font = '10px SF Mono, monospace';\n const label = span.duration ? `${span.name} (${span.duration}ms)` : span.name;\n ctx.fillText(label, x1 + 4, y + 14, barWidth - 8);\n\n // Store for hit testing (in map to avoid mutating readonly object)\n this.spanBounds.set(span.id, {\n x: x1,\n y,\n w: barWidth,\n h: spanHeight,\n });\n\n return y + spanHeight + spanMargin;\n };\n\n for (const span of rootSpans) {\n yOffset = drawSpan(span, yOffset);\n }\n }\n\n private drawLogs(\n ctx: CanvasRenderingContext2D,\n logs: readonly LogEvent[],\n width: number,\n height: number,\n startTime: number,\n endTime: number\n ): void {\n const duration = endTime - startTime;\n const markerHeight = 8;\n const bottomOffset = 30;\n\n for (const log of logs) {\n const x = ((log.timestamp - startTime) / duration) * width;\n const color = LEVEL_COLORS[log.level];\n\n // Draw marker\n ctx.fillStyle = color;\n ctx.beginPath();\n ctx.moveTo(x, height - bottomOffset);\n ctx.lineTo(x - markerHeight / 2, height - bottomOffset - markerHeight);\n ctx.lineTo(x + markerHeight / 2, height - bottomOffset - markerHeight);\n ctx.closePath();\n ctx.fill();\n\n // Store for hit testing (in map to avoid mutating readonly object)\n this.logBounds.set(log.id, {\n x,\n y: height - bottomOffset - markerHeight / 2,\n r: markerHeight,\n });\n }\n }\n\n private handleMouseMove(e: MouseEvent): void {\n if (!this.canvas || !this.tooltip) return;\n\n const rect = this.canvas.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const y = e.clientY - rect.top;\n\n // Check spans\n for (const span of logger.getSpans()) {\n const bounds = this.spanBounds.get(span.id);\n if (bounds && x >= bounds.x && x <= bounds.x + bounds.w && y >= bounds.y && y <= bounds.y + bounds.h) {\n this.showTooltip(e, `\n <strong>${span.name}</strong><br>\n Status: ${span.status}<br>\n ${span.duration !== undefined ? `Duration: ${span.duration}ms` : 'Running...'}<br>\n ${span.context ? `Context: ${JSON.stringify(span.context)}` : ''}\n `);\n return;\n }\n }\n\n // Check logs\n for (const log of logger.getLogs()) {\n const bounds = this.logBounds.get(log.id);\n if (bounds) {\n const dist = Math.sqrt((x - bounds.x) ** 2 + (y - bounds.y) ** 2);\n if (dist <= bounds.r) {\n this.showTooltip(e, `\n <strong>[${log.level.toUpperCase()}]</strong> ${log.message}<br>\n <small>${new Date(log.timestamp).toISOString()}</small>\n `);\n return;\n }\n }\n }\n\n this.hideTooltip();\n }\n\n private handleMouseLeave(): void {\n this.hideTooltip();\n }\n\n private showTooltip(e: MouseEvent, html: string): void {\n if (!this.tooltip) return;\n this.tooltip.innerHTML = html;\n this.tooltip.style.display = 'block';\n this.tooltip.style.left = `${e.offsetX + 10}px`;\n this.tooltip.style.top = `${e.offsetY + 10}px`;\n }\n\n private hideTooltip(): void {\n if (!this.tooltip) return;\n this.tooltip.style.display = 'none';\n }\n\n /**\n * Destroy the timeline\n */\n destroy(): void {\n this.stopRefresh();\n this.container.innerHTML = '';\n }\n}\n\n/**\n * Create a timeline instance\n */\nexport function createTimeline(config: TimelineConfig): Timeline {\n return new Timeline(config);\n}\n","/**\n * DevLogger - Browser-based Dev Logger with UI\n *\n * A lightweight, framework-agnostic dev logger with a beautiful debug UI.\n * Zero dependencies, zero production overhead (via tree-shaking).\n *\n * @example\n * ```typescript\n * import { logger, DevLoggerUI } from 'devlogger';\n *\n * DevLoggerUI.init();\n * logger.info('App started');\n * logger.debug('Config loaded', { theme: 'dark' });\n * ```\n *\n * @packageDocumentation\n */\n\n// ============================================================================\n// Core Logger API\n// ============================================================================\n\n/**\n * The logger singleton instance.\n *\n * @example\n * ```typescript\n * logger.info('User logged in', { userId: 123 });\n * logger.warn('Cache miss');\n * logger.error('API failed', new Error('Timeout'));\n * logger.debug('Rendering component', props);\n *\n * // With context\n * logger.withContext({ requestId: '123' }).info('Request started');\n *\n * // With spans\n * const span = logger.span('Load user');\n * span.info('Fetching...');\n * span.end();\n * ```\n */\nexport { logger } from './core/logger';\n\n/**\n * Export options for log export functionality.\n */\nexport type { ExportOptions, LogSpan, ContextLogger } from './core/logger';\n\n/**\n * Core type definitions for log events and configuration.\n */\nexport type {\n /** A single log event with all metadata */\n LogEvent,\n /** Log severity level: 'debug' | 'info' | 'warn' | 'error' */\n LogLevel,\n /** Logger configuration options */\n LoggerConfig,\n /** Source code location (file, line, column, function) */\n Source,\n /** Callback function for log subscriptions */\n LogSubscriber,\n /** Callback function for span events */\n SpanSubscriber,\n /** Function to unsubscribe from log events */\n Unsubscribe,\n /** Context/tags for log correlation */\n LogContext,\n /** Span event data */\n SpanEvent,\n /** Span status: 'running' | 'success' | 'error' */\n SpanStatus,\n} from './core/types';\n\n// ============================================================================\n// Debug UI API\n// ============================================================================\n\n/**\n * The DevLogger UI overlay controller.\n *\n * @example\n * ```typescript\n * // Initialize at app start\n * DevLoggerUI.init();\n *\n * // Toggle with code or Ctrl+Shift+L\n * DevLoggerUI.toggle();\n *\n * // Open in separate window\n * DevLoggerUI.popout();\n * ```\n */\nexport { DevLoggerUI } from './ui/overlay';\n\n/**\n * Filter state type for programmatic filter control.\n */\nexport type { FilterState } from './ui/filter';\n\n// ============================================================================\n// Error Capture API\n// ============================================================================\n\n/**\n * Global error capture for uncaught errors and unhandled rejections.\n *\n * @example\n * ```typescript\n * // Install at app start to auto-capture errors\n * ErrorCapture.install();\n *\n * // Later, uninstall if needed\n * ErrorCapture.uninstall();\n * ```\n */\nexport { ErrorCapture } from './core/error-capture';\n\n/**\n * Configuration type for error capture.\n */\nexport type { ErrorCaptureConfig } from './core/error-capture';\n\n// ============================================================================\n// Persistence API\n// ============================================================================\n\n/**\n * Log persistence for crash recovery.\n *\n * @example\n * ```typescript\n * // Enable persistence at app start\n * LogPersistence.enable();\n *\n * // Rehydrate logs from previous session\n * const count = LogPersistence.rehydrate();\n * if (LogPersistence.hadCrash()) {\n * logger.warn(`Recovered ${count} logs from crash`);\n * }\n * ```\n */\nexport { LogPersistence } from './core/persistence';\n\n/**\n * Configuration type for log persistence.\n */\nexport type { PersistenceConfig } from './core/persistence';\n\n// ============================================================================\n// Network Capture API\n// ============================================================================\n\n/**\n * Network capture for automatic request/response tracking.\n *\n * @example\n * ```typescript\n * // Install at app start to auto-capture network requests\n * NetworkCapture.install();\n *\n * // With configuration\n * NetworkCapture.install({\n * includeHeaders: true,\n * ignorePatterns: ['/analytics', /\\.hot-update\\./],\n * });\n *\n * // Later, uninstall if needed\n * NetworkCapture.uninstall();\n * ```\n */\nexport { NetworkCapture } from './core/network-capture';\n\n/**\n * Configuration type for network capture.\n */\nexport type { NetworkCaptureConfig } from './core/network-capture';\n\n// ============================================================================\n// Timeline API\n// ============================================================================\n\n/**\n * Timeline visualization for logs and spans.\n *\n * @example\n * ```typescript\n * // Create a timeline in a container\n * const timeline = createTimeline({\n * container: '#timeline-container',\n * timeWindow: 60000, // 1 minute\n * showSpans: true,\n * showLogs: true,\n * });\n *\n * // Change time window\n * timeline.setTimeWindow(30000); // 30 seconds\n *\n * // Cleanup\n * timeline.destroy();\n * ```\n */\nexport { Timeline, createTimeline } from './ui/timeline';\n\n/**\n * Configuration type for timeline.\n */\nexport type { TimelineConfig } from './ui/timeline';\n\n// ============================================================================\n// Diff Utilities\n// ============================================================================\n\n/**\n * Diff utility functions for object comparison.\n *\n * @example\n * ```typescript\n * // Log a visual diff\n * logger.diff('Config changed', oldConfig, newConfig);\n *\n * // Compute diff without logging\n * const result = logger.computeDiff(oldObj, newObj);\n * console.log(result.summary); // { added: 2, removed: 1, changed: 3, unchanged: 5 }\n * ```\n */\nexport { computeDiff, createDiffResult, hasChanges, formatValue } from './core/diff';\n\n/**\n * Diff-related type definitions.\n */\nexport type { DiffEntry, DiffResult, DiffChangeType } from './core/types';\n\n// ============================================================================\n// Package Info\n// ============================================================================\n\n/** Current package version */\nexport const VERSION = '0.1.0';\n"],"names":["LOG_LEVEL_VALUES","isPlainObject","value","deepEqual","a","b","item","i","keysA","keysB","key","formatValue","keys","k","computeDiff","oldObj","newObj","path","includeUnchanged","entries","allKeys","fullPath","oldVal","newVal","oldHas","newHas","createDiffResult","changes","summary","change","hasChanges","diff","checkEnvEnabled","_documentCurrentScript","__vite_import_meta_env__","proc","envEnabled","DEFAULT_CONFIG","generateSessionId","STORAGE_KEY","existing","newId","generateLogId","captureSource","stack","lines","line","chromeMatch","cleanFilePath","firefoxMatch","cleaned","parts","generateSpanId","LogSpan","logger","name","context","parentId","__publicField","message","data","mergedContext","error","status","ContextLogger","extraContext","LoggerCore","level","spanId","event","e","span","subscriber","seen","cloned","config","logs","existingIds","l","newLogs","fn","log","options","format","lastMs","levels","search","pretty","filteredLogs","cutoff","levelSet","searchLower","time","source","v","exported","diffResult","COLORS","STYLES","formatTime","timestamp","date","hours","minutes","seconds","ms","formatSource","isDiffData","formatDiffValue","formatDiffEntry","entry","typeClass","icon","valueStr","formatDiff","diffData","html","formatData","containsDiff","escapeHtml","str","div","formatContext","createLogEntry","hasData","hasDiff","hasContext","dataId","renderData","diffItem","toggle","content","createEmptyState","empty","CHANNEL_NAME","generateSenderId","LoggerChannel","handler","type","payload","channel","POPOUT_WIDTH","POPOUT_HEIGHT","popoutWindow","openPopout","left","top","popoutHtml","generatePopoutHtml","closePopout","isPopoutOpen","createDefaultFilterState","matchesFilter","filter","messageMatch","dataMatch","filterLogs","isFilterActive","createFilterBarHtml","logCount","filteredCount","isActive","SHORTCUT","showCopyFeedback","button","success","originalText","state","createOverlayDOM","shadow","style","toggleBtn","DevLoggerUI","container","btn","action","renderLogs","renderFilterBar","setupFilterListeners","searchInput","updateFilterStatus","fileInput","clearBtn","noResults","fragment","updateBadge","scrollToBottom","addLogEntry","totalCount","footerCount","active","filterBar","statusEl","handleKeydown","handleChannelMessage","host","isInstalled","originalOnError","boundRejectionHandler","handleError","lineno","colno","errorObj","handleRejection","reason","errorInfo","install","uninstall","getConfig","ErrorCapture","CRASH_FLAG_KEY","isEnabled","pendingLogs","debounceTimer","wasUncleanShutdown","unsubscribeFromLogger","getStorage","detectCrash","storage","setActiveFlag","clearActiveFlag","persistLogs","toStore","serialized","reduced","loadPersistedLogs","clearPersistedLogs","schedulePersist","allLogs","handleBeforeUnload","handleNewLog","enable","disable","hadCrash","getPersistedLogs","rehydrate","clear","LogPersistence","originalFetch","originalXHROpen","originalXHRSend","shouldIgnore","url","pattern","parseUrl","parsed","truncate","maxLen","safeParseJSON","createPatchedFetch","input","init","method","spanName","body","startTime","response","duration","text","patchXHR","async","username","password","urlStr","captureInfo","bodyData","restoreXHR","NetworkCapture","userConfig","LEVEL_COLORS","SPAN_STATUS_COLORS","Timeline","containerEl","target","window","el","rect","ctx","width","height","now","spans","s","endTime","gridLines","x","label","_height","spanHeight","spanMargin","topOffset","rootSpans","yOffset","drawSpan","y","_depth","x1","x2","barWidth","color","markerHeight","bottomOffset","bounds","createTimeline","VERSION"],"mappings":"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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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"}
|