tracebug-sdk
Advanced tools
| // src/storage.ts | ||
| var SESSIONS_KEY = "tracebug_sessions"; | ||
| var ACTIVE_SESSION_KEY = "tracebug_active_session"; | ||
| var ACTIVE_CAPTURE_MODE_KEY = "tracebug_active_capture_mode"; | ||
| function generateSessionId() { | ||
| return typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : "bt_" + Date.now().toString(36) + Math.random().toString(36).slice(2, 10); | ||
| } | ||
| function getActiveSessionId() { | ||
| try { | ||
| return localStorage.getItem(ACTIVE_SESSION_KEY); | ||
| } catch (e) { | ||
| return null; | ||
| } | ||
| } | ||
| function setActiveSessionId(id) { | ||
| try { | ||
| localStorage.setItem(ACTIVE_SESSION_KEY, id); | ||
| } catch (e) { | ||
| } | ||
| } | ||
| function clearActiveSessionId() { | ||
| try { | ||
| localStorage.removeItem(ACTIVE_SESSION_KEY); | ||
| } catch (e) { | ||
| } | ||
| try { | ||
| localStorage.removeItem(ACTIVE_CAPTURE_MODE_KEY); | ||
| } catch (e) { | ||
| } | ||
| } | ||
| function getActiveCaptureMode() { | ||
| try { | ||
| const v = localStorage.getItem(ACTIVE_CAPTURE_MODE_KEY); | ||
| return v === "events" || v === "video" ? v : null; | ||
| } catch (e) { | ||
| return null; | ||
| } | ||
| } | ||
| function setActiveCaptureMode(mode) { | ||
| try { | ||
| localStorage.setItem(ACTIVE_CAPTURE_MODE_KEY, mode); | ||
| } catch (e) { | ||
| } | ||
| } | ||
| function getAllSessions() { | ||
| try { | ||
| const raw = localStorage.getItem(SESSIONS_KEY); | ||
| return raw ? JSON.parse(raw) : []; | ||
| } catch (e) { | ||
| return []; | ||
| } | ||
| } | ||
| function saveSessions(sessions) { | ||
| try { | ||
| localStorage.setItem(SESSIONS_KEY, JSON.stringify(sessions)); | ||
| return; | ||
| } catch (e) { | ||
| } | ||
| const commit = (next) => { | ||
| try { | ||
| localStorage.setItem(SESSIONS_KEY, JSON.stringify(next)); | ||
| sessions.length = 0; | ||
| sessions.push(...next); | ||
| return true; | ||
| } catch (e) { | ||
| return false; | ||
| } | ||
| }; | ||
| const working = sessions.slice(); | ||
| while (working.length > 1) { | ||
| working.shift(); | ||
| if (commit(working)) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] Storage full \u2014 dropped oldest session(s) to fit."); | ||
| return; | ||
| } | ||
| } | ||
| const last = working[0]; | ||
| if (last && Array.isArray(last.events)) { | ||
| while (last.events.length > 1) { | ||
| last.events = last.events.slice(Math.ceil(last.events.length / 2)); | ||
| if (commit(working)) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] Storage full \u2014 trimmed older events from the current session to fit."); | ||
| return; | ||
| } | ||
| } | ||
| } | ||
| if (typeof console !== "undefined") console.error("[TraceBug] Could not persist sessions: localStorage quota exceeded."); | ||
| } | ||
| var _cachedSessions = null; | ||
| var _pendingFlush = null; | ||
| var _dirty = false; | ||
| var FLUSH_INTERVAL_MS = 1e3; | ||
| function getCachedSessions() { | ||
| if (!_cachedSessions) { | ||
| _cachedSessions = getAllSessions(); | ||
| } | ||
| return _cachedSessions; | ||
| } | ||
| function scheduleFlush() { | ||
| _dirty = true; | ||
| if (_pendingFlush) return; | ||
| _pendingFlush = setTimeout(() => { | ||
| _pendingFlush = null; | ||
| if (_cachedSessions && _dirty) { | ||
| saveSessions(_cachedSessions); | ||
| _dirty = false; | ||
| } | ||
| }, FLUSH_INTERVAL_MS); | ||
| } | ||
| function flushPendingEvents() { | ||
| if (_pendingFlush) { | ||
| clearTimeout(_pendingFlush); | ||
| _pendingFlush = null; | ||
| } | ||
| if (_cachedSessions) { | ||
| saveSessions(_cachedSessions); | ||
| _dirty = false; | ||
| } | ||
| } | ||
| function invalidateCache() { | ||
| if (_pendingFlush) { | ||
| clearTimeout(_pendingFlush); | ||
| _pendingFlush = null; | ||
| } | ||
| _cachedSessions = null; | ||
| _dirty = false; | ||
| } | ||
| if (typeof window !== "undefined") { | ||
| window.addEventListener("beforeunload", flushPendingEvents); | ||
| window.addEventListener("pagehide", flushPendingEvents); | ||
| document.addEventListener("visibilitychange", () => { | ||
| if (document.visibilityState === "hidden") flushPendingEvents(); | ||
| }); | ||
| } | ||
| function appendEvent(sessionId, event, maxEvents, maxSessions) { | ||
| let sessions = getCachedSessions(); | ||
| let session = sessions.find((s) => s.sessionId === sessionId); | ||
| if (!session) { | ||
| session = { | ||
| sessionId, | ||
| projectId: event.projectId, | ||
| createdAt: Date.now(), | ||
| updatedAt: Date.now(), | ||
| errorMessage: null, | ||
| errorStack: null, | ||
| reproSteps: null, | ||
| errorSummary: null, | ||
| events: [], | ||
| annotations: [], | ||
| environment: null | ||
| }; | ||
| sessions.push(session); | ||
| } | ||
| session.events.push(event); | ||
| session.updatedAt = Date.now(); | ||
| if (session.events.length > maxEvents) { | ||
| session.events = session.events.slice(-maxEvents); | ||
| } | ||
| if (sessions.length > maxSessions) { | ||
| sessions = sessions.slice(-maxSessions); | ||
| _cachedSessions = sessions; | ||
| } | ||
| scheduleFlush(); | ||
| } | ||
| function updateSessionError(sessionId, errorMessage, errorStack, reproSteps, errorSummary) { | ||
| const sessions = getCachedSessions(); | ||
| const session = sessions.find((s) => s.sessionId === sessionId); | ||
| if (!session) return; | ||
| session.errorMessage = errorMessage; | ||
| session.errorStack = errorStack || null; | ||
| session.reproSteps = reproSteps; | ||
| session.errorSummary = errorSummary; | ||
| session.updatedAt = Date.now(); | ||
| scheduleFlush(); | ||
| } | ||
| function deleteSession(sessionId) { | ||
| flushPendingEvents(); | ||
| const remaining = getAllSessions().filter((s) => s.sessionId !== sessionId); | ||
| invalidateCache(); | ||
| saveSessions(remaining); | ||
| } | ||
| function addAnnotation(sessionId, annotation) { | ||
| const sessions = getCachedSessions(); | ||
| const session = sessions.find((s) => s.sessionId === sessionId); | ||
| if (!session) return; | ||
| if (!session.annotations) session.annotations = []; | ||
| session.annotations.push(annotation); | ||
| session.updatedAt = Date.now(); | ||
| scheduleFlush(); | ||
| } | ||
| function saveEnvironment(sessionId, env) { | ||
| const sessions = getCachedSessions(); | ||
| const session = sessions.find((s) => s.sessionId === sessionId); | ||
| if (!session) return; | ||
| session.environment = env; | ||
| scheduleFlush(); | ||
| } | ||
| function setSessionPriority(sessionId, priority) { | ||
| const sessions = getCachedSessions(); | ||
| const session = sessions.find((s) => s.sessionId === sessionId); | ||
| if (!session) return; | ||
| session.priority = priority; | ||
| session.updatedAt = Date.now(); | ||
| scheduleFlush(); | ||
| } | ||
| function markSessionSaved(sessionId) { | ||
| const sessions = getCachedSessions(); | ||
| const session = sessions.find((s) => s.sessionId === sessionId); | ||
| if (!session) return; | ||
| session.saved = true; | ||
| session.updatedAt = Date.now(); | ||
| flushPendingEvents(); | ||
| } | ||
| function clearAllSessions() { | ||
| invalidateCache(); | ||
| try { | ||
| localStorage.removeItem(SESSIONS_KEY); | ||
| } catch (e) { | ||
| } | ||
| } | ||
| // src/sanitize/custom-redaction.ts | ||
| var REDACTED = "[REDACTED]"; | ||
| var _fieldRe = null; | ||
| var _fieldTextRes = []; | ||
| var _patterns = []; | ||
| function escapeRe(s) { | ||
| return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); | ||
| } | ||
| function setRedactRules(rules) { | ||
| _fieldRe = null; | ||
| _fieldTextRes = []; | ||
| _patterns = []; | ||
| if (!rules) return; | ||
| const fields = (rules.fields || []).filter((f) => typeof f === "string" && f.trim().length > 0); | ||
| if (fields.length > 0) { | ||
| const alts = fields.map((f) => escapeRe(f.trim())).join("|"); | ||
| _fieldRe = new RegExp(alts, "i"); | ||
| for (const f of fields) { | ||
| const k = escapeRe(f.trim()); | ||
| _fieldTextRes.push({ | ||
| re: new RegExp(`("([^"]*${k}[^"]*)"\\s*:\\s*)("(?:[^"\\\\]|\\\\.)*"|-?\\d[\\d.eE+-]*|true|false)`, "gi"), | ||
| replace: `$1"${REDACTED}"` | ||
| }); | ||
| _fieldTextRes.push({ | ||
| re: new RegExp(`\\b([\\w.-]*${k}[\\w.-]*)=([^&\\s"']+)`, "gi"), | ||
| replace: `$1=${REDACTED}` | ||
| }); | ||
| } | ||
| } | ||
| for (const p of rules.patterns || []) { | ||
| try { | ||
| if (typeof p === "string") { | ||
| _patterns.push(new RegExp(p, "gi")); | ||
| } else if (p instanceof RegExp) { | ||
| _patterns.push(p.flags.includes("g") ? p : new RegExp(p.source, p.flags + "g")); | ||
| } | ||
| } catch (e) { | ||
| } | ||
| } | ||
| } | ||
| function isCustomSensitiveKey(key) { | ||
| if (!key || !_fieldRe) return false; | ||
| return _fieldRe.test(key); | ||
| } | ||
| function applyCustomRedaction(s) { | ||
| if (!s || _fieldTextRes.length === 0 && _patterns.length === 0) return s; | ||
| let out = s; | ||
| for (const { re, replace } of _fieldTextRes) out = out.replace(re, replace); | ||
| for (const re of _patterns) out = out.replace(re, REDACTED); | ||
| return out; | ||
| } | ||
| // src/url-hygiene.ts | ||
| var SENSITIVE_PARAM_RE = /token|key|secret|auth|password|passwd|pwd|credential|session|sid|csrf|sig|signature/i; | ||
| function isSensitiveParamName(name) { | ||
| return !!name && SENSITIVE_PARAM_RE.test(name); | ||
| } | ||
| var ASSET_EXT_RE = /\.(png|jpe?g|gif|webp|avif|svg|ico|bmp|css|woff2?|ttf|otf|eot|map|mp4|webm|ogg|mp3|pdf)(\?|#|$)/i; | ||
| var NOISE_HOSTS = [ | ||
| "camo.githubusercontent.com", | ||
| "avatars.githubusercontent.com", | ||
| "img.shields.io", | ||
| "fonts.googleapis.com", | ||
| "fonts.gstatic.com", | ||
| "google-analytics.com", | ||
| "www.google-analytics.com", | ||
| "googletagmanager.com", | ||
| "www.googletagmanager.com", | ||
| "stats.g.doubleclick.net", | ||
| "connect.facebook.net", | ||
| "cdn.segment.com", | ||
| "api.segment.io", | ||
| "gravatar.com", | ||
| "www.gravatar.com" | ||
| ]; | ||
| var NOISE_HOST_PREFIX_RE = /^(collector|stats|telemetry|analytics|metrics|beacon|track(ing)?|pixel|events|logs?)\./i; | ||
| var NOISE_PATH_RE = /(^|\/)(collect|collector|beacon|telemetry|pixel|track|tracking)(\/|$)|\/_private\//i; | ||
| var SEGMENT_MAX = 24; | ||
| var PATH_MAX = 60; | ||
| function isNoiseRequest(url) { | ||
| if (!url) return false; | ||
| if (ASSET_EXT_RE.test(url)) return true; | ||
| if (!/^https?:\/\//i.test(url)) return false; | ||
| try { | ||
| const u = new URL(url); | ||
| const host = u.hostname.toLowerCase(); | ||
| if (NOISE_HOSTS.some((h) => host === h || host.endsWith("." + h))) return true; | ||
| if (NOISE_HOST_PREFIX_RE.test(host)) return true; | ||
| const pageHost = typeof window !== "undefined" ? window.location.hostname.toLowerCase() : null; | ||
| if (pageHost && host === pageHost) return false; | ||
| return NOISE_PATH_RE.test(u.pathname); | ||
| } catch (e) { | ||
| return false; | ||
| } | ||
| } | ||
| function isStaticResource(url) { | ||
| if (!url) return false; | ||
| return ASSET_EXT_RE.test(url) || /\.(m?js)(\?|#|$)/i.test(url); | ||
| } | ||
| function shortDisplayPath(url, base) { | ||
| if (!url) return ""; | ||
| let pathname; | ||
| try { | ||
| const origin = base || (typeof window !== "undefined" ? window.location.origin : "http://relative.local"); | ||
| pathname = new URL(url, origin).pathname || url; | ||
| } catch (e) { | ||
| pathname = url; | ||
| } | ||
| const segments = pathname.split("/").map( | ||
| (seg) => seg.length > SEGMENT_MAX ? `${seg.slice(0, 10)}\u2026${seg.slice(-6)}` : seg | ||
| ); | ||
| let out = segments.join("/"); | ||
| if (out.length > PATH_MAX) out = out.slice(0, PATH_MAX - 1) + "\u2026"; | ||
| return out; | ||
| } | ||
| // src/sanitize/cloud-upload.ts | ||
| var REDACTED2 = "[REDACTED]"; | ||
| var TOKEN_PATTERNS = [ | ||
| // Bearer <token> in headers, console output, anywhere | ||
| { name: "bearer", re: /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}/gi, replace: () => "Bearer " + REDACTED2 }, | ||
| // JWT (3 base64url segments separated by dots, leading with eyJ which is `{"` in base64) | ||
| { name: "jwt", re: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g, replace: mask }, | ||
| // OpenAI / Stripe sk_* | ||
| { name: "sk_prefix", re: /\bsk-[A-Za-z0-9_-]{20,}\b/g, replace: mask }, | ||
| // Stripe secret + publishable (live/test, secret + publishable + restricted) | ||
| { name: "stripe", re: /\b(?:sk|pk|rk)_(?:live|test)_[A-Za-z0-9]{16,}\b/g, replace: mask }, | ||
| // GitHub PATs (classic + fine-grained + OAuth + server tokens) | ||
| { name: "github_pat", re: /\bghp_[A-Za-z0-9]{30,}\b/g, replace: mask }, | ||
| { name: "github_fine", re: /\bgithub_pat_[A-Za-z0-9_]{60,}\b/g, replace: mask }, | ||
| { name: "github_oauth", re: /\bgho_[A-Za-z0-9]{30,}\b/g, replace: mask }, | ||
| { name: "github_server", re: /\bghs_[A-Za-z0-9]{30,}\b/g, replace: mask }, | ||
| // AWS access keys (begins with AKIA, ASIA, AGPA, AIDA, etc.) + secret key (40-char base64) | ||
| { name: "aws_access", re: /\b(?:AKIA|ASIA|AGPA|AIDA|AROA|ANPA|ANVA)[A-Z0-9]{16}\b/g, replace: mask }, | ||
| { name: "aws_secret", re: /\b(?:aws.{0,20})?[A-Za-z0-9/+]{40}\b(?=.*aws|.*secret|.*key)/gi, replace: mask }, | ||
| // Slack — broader than before (xoxa-z covers all known prefixes) | ||
| { name: "slack", re: /\bxox[abeprs]-[A-Za-z0-9-]{10,}\b/g, replace: mask }, | ||
| // Google API keys | ||
| { name: "google_api", re: /\bAIza[A-Za-z0-9_-]{35}\b/g, replace: mask }, | ||
| // Twilio — Account SID + Auth tokens | ||
| { name: "twilio_sid", re: /\b(?:AC|SK)[a-f0-9]{32}\b/g, replace: mask }, | ||
| // SendGrid | ||
| { name: "sendgrid", re: /\bSG\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}\b/g, replace: mask }, | ||
| // Mailgun | ||
| { name: "mailgun", re: /\bkey-[a-f0-9]{32}\b/g, replace: mask }, | ||
| // Postmark | ||
| { name: "postmark", re: /\b[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\b(?=.{0,30}(postmark|server-token|api-token))/gi, replace: mask }, | ||
| // Linear / Vercel / Cloudflare / Discord | ||
| { name: "linear", re: /\blin_api_[A-Za-z0-9]{40,}\b/g, replace: mask }, | ||
| { name: "discord_bot", re: /\b[MN][A-Za-z\d]{23}\.[A-Za-z\d_-]{6}\.[A-Za-z\d_-]{27,}\b/g, replace: mask }, | ||
| // Generic high-entropy hex (≥32 chars). Catches webhook signing secrets, | ||
| // session IDs, etc. that don't carry a recognizable prefix. Conservative: | ||
| // only triggers when preceded by a common secret-y keyword to avoid | ||
| // mangling legitimate hex like git SHAs. | ||
| { name: "labeled_hex", re: /\b(?:secret|token|key|password|api[_-]?key|auth)["':\s=]{1,5}([a-fA-F0-9]{32,})\b/gi, replace: (s) => s.replace(/[a-fA-F0-9]{32,}/, REDACTED2) } | ||
| ]; | ||
| function mask(s) { | ||
| if (s.length <= 12) return REDACTED2; | ||
| return `${s.slice(0, 4)}\u2026${REDACTED2}\u2026${s.slice(-4)}`; | ||
| } | ||
| function sanitizeUrl(url) { | ||
| if (typeof url !== "string" || url.length === 0) return url; | ||
| try { | ||
| const isAbs = /^[a-z][a-z0-9+.-]*:/i.test(url); | ||
| const u = new URL(isAbs ? url : `http://_placeholder_${url.startsWith("/") ? "" : "/"}${url}`); | ||
| let changed = false; | ||
| u.searchParams.forEach((_v, k) => { | ||
| if (isSensitiveParamName(k) || isCustomSensitiveKey(k)) { | ||
| u.searchParams.set(k, REDACTED2); | ||
| changed = true; | ||
| } | ||
| }); | ||
| if (!changed) return sanitizeText(url); | ||
| const out = isAbs ? u.toString() : u.pathname + u.search + u.hash; | ||
| return sanitizeText(out); | ||
| } catch (e) { | ||
| return sanitizeText(url); | ||
| } | ||
| } | ||
| function sanitizeText(s) { | ||
| if (s == null) return s; | ||
| let out = String(s); | ||
| for (const p of TOKEN_PATTERNS) out = out.replace(p.re, p.replace); | ||
| return applyCustomRedaction(out); | ||
| } | ||
| function sanitizeTokenShapes(s) { | ||
| return sanitizeText(s); | ||
| } | ||
| function sanitizeReportForUpload(report) { | ||
| var _a; | ||
| const out = typeof structuredClone === "function" ? structuredClone(report) : JSON.parse(JSON.stringify(report)); | ||
| if (out.consoleErrors) { | ||
| out.consoleErrors = out.consoleErrors.map((e) => { | ||
| var _a2; | ||
| return { | ||
| ...e, | ||
| message: sanitizeText(e.message), | ||
| stack: sanitizeText((_a2 = e.stack) != null ? _a2 : void 0) | ||
| }; | ||
| }); | ||
| } | ||
| if (out.consoleLogs) { | ||
| out.consoleLogs = out.consoleLogs.map((e) => { | ||
| var _a2; | ||
| return { | ||
| ...e, | ||
| message: sanitizeText(e.message), | ||
| stack: sanitizeText((_a2 = e.stack) != null ? _a2 : void 0) | ||
| }; | ||
| }); | ||
| } | ||
| if (out.networkErrors) { | ||
| out.networkErrors = out.networkErrors.map((e) => { | ||
| var _a2; | ||
| return { | ||
| ...e, | ||
| url: sanitizeUrl(e.url), | ||
| response: sanitizeText((_a2 = e.response) != null ? _a2 : void 0) | ||
| }; | ||
| }); | ||
| } | ||
| if (out.networkRequests) { | ||
| out.networkRequests = out.networkRequests.map((e) => { | ||
| var _a2; | ||
| return { | ||
| ...e, | ||
| url: sanitizeUrl(e.url), | ||
| response: sanitizeText((_a2 = e.response) != null ? _a2 : void 0) | ||
| }; | ||
| }); | ||
| } | ||
| if (out.steps) out.steps = sanitizeText(out.steps); | ||
| if (out.summary) out.summary = sanitizeText(out.summary); | ||
| if (out.title) out.title = sanitizeText(out.title); | ||
| if (Array.isArray(out.sessionSteps)) out.sessionSteps = out.sessionSteps.map((s) => sanitizeText(s)); | ||
| if (out.actionChips) { | ||
| out.actionChips = out.actionChips.map((c) => { | ||
| var _a2, _b; | ||
| return { | ||
| ...c, | ||
| target: sanitizeText((_a2 = c.target) != null ? _a2 : void 0), | ||
| detail: sanitizeText((_b = c.detail) != null ? _b : void 0) | ||
| }; | ||
| }); | ||
| } | ||
| if (out.storage) { | ||
| const scrub = (entries) => entries.map((e) => ({ ...e, value: sanitizeText(e.value) })); | ||
| out.storage.local = scrub(out.storage.local || []); | ||
| out.storage.session = scrub(out.storage.session || []); | ||
| if (out.storage.cookies) out.storage.cookies = scrub(out.storage.cookies); | ||
| } | ||
| if ((_a = out.environment) == null ? void 0 : _a.url) out.environment.url = sanitizeUrl(out.environment.url); | ||
| if (out.context && typeof out.context === "object") { | ||
| const ctx = {}; | ||
| for (const [k, v] of Object.entries(out.context)) { | ||
| ctx[k] = typeof v === "string" ? sanitizeText(v) : v; | ||
| } | ||
| out.context = ctx; | ||
| } | ||
| return out; | ||
| } | ||
| // src/collectors.ts | ||
| var ROOT_ID = "tracebug-root"; | ||
| var PANEL_ID = "tracebug-dashboard-panel"; | ||
| var BTN_ID = "tracebug-dashboard-btn"; | ||
| var NETWORK_FAILURE_LIMIT = 10; | ||
| var RESPONSE_SNIPPET_CHARS = 200; | ||
| var _networkFailures = []; | ||
| function pushNetworkFailure(failure) { | ||
| try { | ||
| if (failure.response) failure.response = sanitizeTokenShapes(failure.response); | ||
| _networkFailures.push(failure); | ||
| if (_networkFailures.length > NETWORK_FAILURE_LIMIT) { | ||
| _networkFailures.splice(0, _networkFailures.length - NETWORK_FAILURE_LIMIT); | ||
| } | ||
| } catch (e) { | ||
| } | ||
| } | ||
| function getNetworkFailures() { | ||
| return _networkFailures.slice(); | ||
| } | ||
| function clearNetworkFailures() { | ||
| _networkFailures.length = 0; | ||
| } | ||
| function sanitizeUrl2(url) { | ||
| if (!url) return url; | ||
| try { | ||
| const qIdx = url.indexOf("?"); | ||
| if (qIdx === -1) return url; | ||
| const base = url.slice(0, qIdx); | ||
| const afterQ = url.slice(qIdx + 1); | ||
| const hashIdx = afterQ.indexOf("#"); | ||
| const query = hashIdx === -1 ? afterQ : afterQ.slice(0, hashIdx); | ||
| const hash = hashIdx === -1 ? "" : afterQ.slice(hashIdx); | ||
| const redacted = query.split("&").map((part) => { | ||
| const eqIdx = part.indexOf("="); | ||
| if (eqIdx === -1) return part; | ||
| const key = part.slice(0, eqIdx); | ||
| if (isSensitiveParamName(key) || isCustomSensitiveKey(key)) return `${key}=[REDACTED]`; | ||
| return part; | ||
| }).join("&"); | ||
| return `${base}?${redacted}${hash}`; | ||
| } catch (e) { | ||
| return url; | ||
| } | ||
| } | ||
| var MAX_BODY_BYTES = 10 * 1024; | ||
| var BINARY_CONTENT_TYPE_RE = /^(image|video|audio)\/|^application\/(octet-stream|pdf|zip|x-protobuf|x-msgpack|wasm|vnd\.)/i; | ||
| async function readResponseBodySafe(response) { | ||
| try { | ||
| const ct = response.headers.get("content-type") || ""; | ||
| if (BINARY_CONTENT_TYPE_RE.test(ct)) return ""; | ||
| if (!response.body || typeof response.body.getReader !== "function") { | ||
| try { | ||
| const text = await response.text(); | ||
| return typeof text === "string" ? text.slice(0, RESPONSE_SNIPPET_CHARS) : ""; | ||
| } catch (e) { | ||
| return ""; | ||
| } | ||
| } | ||
| const reader = response.body.getReader(); | ||
| const decoder = new TextDecoder("utf-8", { fatal: false }); | ||
| let collected = ""; | ||
| let bytesRead = 0; | ||
| while (bytesRead < MAX_BODY_BYTES && collected.length < RESPONSE_SNIPPET_CHARS) { | ||
| const { value, done } = await reader.read(); | ||
| if (done) break; | ||
| if (value) { | ||
| bytesRead += value.byteLength; | ||
| collected += decoder.decode(value, { stream: true }); | ||
| } | ||
| } | ||
| try { | ||
| await reader.cancel(); | ||
| } catch (e) { | ||
| } | ||
| return collected.slice(0, RESPONSE_SNIPPET_CHARS); | ||
| } catch (e) { | ||
| return ""; | ||
| } | ||
| } | ||
| var INTERNAL_URL_PATTERNS = [ | ||
| /__nextjs_original-stack-frame/, | ||
| /\/_next\/static\/webpack/, | ||
| /\/__webpack_hmr/, | ||
| /\.hot-update\./, | ||
| /\/sockjs-node\//, | ||
| /\/turbopack-hmr\//, | ||
| /\/_next\/webpack-hmr/, | ||
| /\/webpack-dev-server\//, | ||
| /\/__vite_ping/, | ||
| /\/@vite\/client/, | ||
| /\/@react-refresh/ | ||
| ]; | ||
| function isInternalUrl(url) { | ||
| return INTERNAL_URL_PATTERNS.some((pattern) => pattern.test(url)); | ||
| } | ||
| var _rootCache; | ||
| function getRoot() { | ||
| if (_rootCache === void 0) { | ||
| _rootCache = document.getElementById(ROOT_ID); | ||
| } | ||
| if (_rootCache && !_rootCache.isConnected) { | ||
| _rootCache = document.getElementById(ROOT_ID); | ||
| } | ||
| return _rootCache; | ||
| } | ||
| function isTraceBugElement(el) { | ||
| if (!el) return false; | ||
| if (el.id === ROOT_ID || el.id === BTN_ID || el.id === PANEL_ID) return true; | ||
| if (el.dataset && el.dataset.tracebug) return true; | ||
| const root = getRoot(); | ||
| if (root && root.contains(el)) return true; | ||
| let node = el; | ||
| while (node) { | ||
| const id = node.id || ""; | ||
| if (id.startsWith("tracebug-") || id.startsWith("bt-")) return true; | ||
| const cn = typeof node.className === "string" ? node.className : ""; | ||
| if (cn.includes("tracebug-") || cn.includes("bt-ann") || cn.includes("bt-voice")) return true; | ||
| if (node.dataset && node.dataset.tracebug) return true; | ||
| node = node.parentElement; | ||
| } | ||
| return false; | ||
| } | ||
| function collectClicks(emit) { | ||
| const handler = (e) => { | ||
| try { | ||
| const t = e.target; | ||
| if (!t || isTraceBugElement(t)) return; | ||
| const tag = t.tagName.toLowerCase(); | ||
| const el = { | ||
| tag, | ||
| text: (t.innerText || "").slice(0, 120), | ||
| id: t.id || "", | ||
| className: typeof t.className === "string" ? t.className : "" | ||
| }; | ||
| const data = { element: el }; | ||
| if (tag === "a") el.href = t.href || ""; | ||
| if (tag === "button" || t.type === "submit") { | ||
| el.buttonType = t.type || "button"; | ||
| el.disabled = t.disabled; | ||
| } | ||
| if (tag === "label") el.forField = t.htmlFor || ""; | ||
| const ariaLabel = t.getAttribute("aria-label"); | ||
| if (ariaLabel) el.ariaLabel = ariaLabel; | ||
| const role = t.getAttribute("role"); | ||
| if (role) el.role = role; | ||
| const testId = t.getAttribute("data-testid"); | ||
| if (testId) el.testId = testId; | ||
| const form = t.closest("form"); | ||
| if (form) { | ||
| el.formId = form.id || ""; | ||
| el.formAction = form.action || ""; | ||
| } | ||
| try { | ||
| el.selector = buildSelector(t); | ||
| } catch (e2) { | ||
| } | ||
| try { | ||
| const r = t.getBoundingClientRect(); | ||
| el.boundingBox = { x: Math.round(r.left), y: Math.round(r.top), width: Math.round(r.width), height: Math.round(r.height) }; | ||
| } catch (e2) { | ||
| } | ||
| emit("click", data); | ||
| } catch (err) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] Click capture error:", err); | ||
| } | ||
| }; | ||
| document.addEventListener("click", handler, { capture: true }); | ||
| return () => document.removeEventListener("click", handler, { capture: true }); | ||
| } | ||
| function buildSelector(el) { | ||
| if (!el) return ""; | ||
| if (el.id) return `#${CSS.escape(el.id)}`; | ||
| const testId = el.getAttribute("data-testid"); | ||
| if (testId) return `[data-testid="${testId}"]`; | ||
| const parts = []; | ||
| let node = el; | ||
| let depth = 0; | ||
| while (node && node !== document.body && depth < 4) { | ||
| let part = node.tagName.toLowerCase(); | ||
| if (node.id) { | ||
| parts.unshift(`#${CSS.escape(node.id)}`); | ||
| break; | ||
| } | ||
| const cls = typeof node.className === "string" ? node.className.trim().split(/\s+/).filter(Boolean)[0] : ""; | ||
| if (cls) part += `.${CSS.escape(cls)}`; | ||
| const parent = node.parentElement; | ||
| const currentTag = node.tagName; | ||
| const currentNode = node; | ||
| if (parent) { | ||
| const sameTag = Array.from(parent.children).filter((c) => c.tagName === currentTag); | ||
| if (sameTag.length > 1) part += `:nth-of-type(${sameTag.indexOf(currentNode) + 1})`; | ||
| } | ||
| parts.unshift(part); | ||
| node = parent; | ||
| depth++; | ||
| } | ||
| return parts.join(" > "); | ||
| } | ||
| function collectInputs(emit) { | ||
| const timers = /* @__PURE__ */ new Map(); | ||
| const handler = (e) => { | ||
| try { | ||
| const t = e.target; | ||
| if (!t || !("value" in t) || isTraceBugElement(t)) return; | ||
| if (t.tagName.toLowerCase() === "select") return; | ||
| const prev = timers.get(t); | ||
| if (prev) clearTimeout(prev); | ||
| timers.set( | ||
| t, | ||
| setTimeout(() => { | ||
| try { | ||
| const tag = t.tagName.toLowerCase(); | ||
| const inputType = t.type || ""; | ||
| const isSensitive = ["password", "credit-card", "ssn"].includes(inputType) || /password|secret|token|ssn|credit/i.test(t.name || t.id || "") || isCustomSensitiveKey(t.name || t.id); | ||
| const element = { | ||
| tag, | ||
| name: t.name || t.id || "", | ||
| type: inputType, | ||
| valueLength: (t.value || "").length, | ||
| value: isSensitive ? "[REDACTED]" : (t.value || "").slice(0, 200), | ||
| placeholder: t.placeholder || "" | ||
| }; | ||
| try { | ||
| element.selector = buildSelector(t); | ||
| } catch (e2) { | ||
| } | ||
| const data = { element }; | ||
| if (inputType === "checkbox" || inputType === "radio") { | ||
| element.checked = t.checked; | ||
| element.value = t.checked ? "checked" : "unchecked"; | ||
| } | ||
| if (inputType === "number" || inputType === "range") { | ||
| element.value = t.value; | ||
| } | ||
| emit("input", data); | ||
| } catch (err) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] Input capture error:", err); | ||
| } | ||
| timers.delete(t); | ||
| }, 300) | ||
| ); | ||
| } catch (err) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] Input capture error:", err); | ||
| } | ||
| }; | ||
| document.addEventListener("input", handler, { capture: true }); | ||
| return () => { | ||
| document.removeEventListener("input", handler, { capture: true }); | ||
| timers.forEach((t) => clearTimeout(t)); | ||
| }; | ||
| } | ||
| function collectSelectChanges(emit) { | ||
| const handler = (e) => { | ||
| try { | ||
| const t = e.target; | ||
| if (!t || t.tagName.toLowerCase() !== "select" || isTraceBugElement(t)) return; | ||
| const selectedOption = t.options[t.selectedIndex]; | ||
| let selector = ""; | ||
| try { | ||
| selector = buildSelector(t); | ||
| } catch (e2) { | ||
| } | ||
| emit("select_change", { | ||
| element: { | ||
| tag: "select", | ||
| name: t.name || t.id || "", | ||
| value: t.value, | ||
| selectedText: selectedOption ? selectedOption.text : "", | ||
| selectedIndex: t.selectedIndex, | ||
| optionCount: t.options.length, | ||
| allOptions: Array.from(t.options).map((o) => o.text).slice(0, 20), | ||
| selector | ||
| } | ||
| }); | ||
| } catch (err) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] Select capture error:", err); | ||
| } | ||
| }; | ||
| document.addEventListener("change", handler, { capture: true }); | ||
| return () => document.removeEventListener("change", handler, { capture: true }); | ||
| } | ||
| function collectFormSubmits(emit) { | ||
| const handler = (e) => { | ||
| try { | ||
| const form = e.target; | ||
| if (!form || form.tagName.toLowerCase() !== "form" || isTraceBugElement(form)) return; | ||
| const formData = {}; | ||
| const elements = form.elements; | ||
| for (let i = 0; i < elements.length; i++) { | ||
| const el = elements[i]; | ||
| if (!el.name) continue; | ||
| const isSensitive = ["password"].includes(el.type) || /password|secret|token|ssn|credit/i.test(el.name) || isCustomSensitiveKey(el.name); | ||
| if (el.type === "submit" || el.type === "button") continue; | ||
| formData[el.name] = isSensitive ? "[REDACTED]" : (el.value || "").slice(0, 200); | ||
| } | ||
| emit("form_submit", { | ||
| form: { id: form.id || "", action: form.action || "", method: form.method || "GET", fieldCount: elements.length, fields: formData } | ||
| }); | ||
| } catch (err) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] Form capture error:", err); | ||
| } | ||
| }; | ||
| document.addEventListener("submit", handler, { capture: true }); | ||
| return () => document.removeEventListener("submit", handler, { capture: true }); | ||
| } | ||
| function collectRouteChanges(emit) { | ||
| let lastPath = window.location.pathname; | ||
| const check = () => { | ||
| const current = window.location.pathname; | ||
| if (current !== lastPath) { | ||
| const from = lastPath; | ||
| lastPath = current; | ||
| emit("route_change", { from, to: current }); | ||
| } | ||
| }; | ||
| window.addEventListener("popstate", check); | ||
| const origPush = history.pushState.bind(history); | ||
| const origReplace = history.replaceState.bind(history); | ||
| history.pushState = function(...args) { | ||
| origPush(...args); | ||
| check(); | ||
| }; | ||
| history.replaceState = function(...args) { | ||
| origReplace(...args); | ||
| check(); | ||
| }; | ||
| return () => { | ||
| window.removeEventListener("popstate", check); | ||
| history.pushState = origPush; | ||
| history.replaceState = origReplace; | ||
| }; | ||
| } | ||
| function collectApiRequests(emit) { | ||
| const originalFetch = window.fetch; | ||
| window.fetch = async function(input, init) { | ||
| var _a; | ||
| let url = ""; | ||
| let method = "GET"; | ||
| try { | ||
| if (typeof input === "string") { | ||
| url = input; | ||
| } else if (input instanceof URL) { | ||
| url = input.href; | ||
| } else if (input && typeof input === "object" && "url" in input) { | ||
| url = input.url; | ||
| method = input.method || "GET"; | ||
| } | ||
| if (init == null ? void 0 : init.method) method = init.method; | ||
| } catch (e) { | ||
| } | ||
| const start = Date.now(); | ||
| try { | ||
| if (url && isInternalUrl(url)) return originalFetch.call(window, input, init); | ||
| } catch (e) { | ||
| } | ||
| const safeUrl = sanitizeUrl2(url).slice(0, 500); | ||
| try { | ||
| const response = await originalFetch.call(window, input, init); | ||
| try { | ||
| emit("api_request", { | ||
| request: { url: safeUrl, method: method.toUpperCase(), statusCode: response.status, durationMs: Date.now() - start } | ||
| }); | ||
| } catch (e) { | ||
| } | ||
| try { | ||
| if (response.status >= 400 || response.status === 0) { | ||
| const clone = response.clone(); | ||
| readResponseBodySafe(clone).then((snippet) => { | ||
| pushNetworkFailure({ | ||
| url: safeUrl, | ||
| method: method.toUpperCase(), | ||
| status: response.status, | ||
| response: snippet, | ||
| timestamp: Date.now() | ||
| }); | ||
| }).catch(() => { | ||
| pushNetworkFailure({ | ||
| url: safeUrl, | ||
| method: method.toUpperCase(), | ||
| status: response.status, | ||
| response: "", | ||
| timestamp: Date.now() | ||
| }); | ||
| }); | ||
| } | ||
| } catch (e) { | ||
| } | ||
| return response; | ||
| } catch (err) { | ||
| try { | ||
| emit("api_request", { | ||
| request: { url: safeUrl, method: method.toUpperCase(), statusCode: 0, durationMs: Date.now() - start } | ||
| }); | ||
| } catch (e) { | ||
| } | ||
| try { | ||
| pushNetworkFailure({ | ||
| url: safeUrl, | ||
| method: method.toUpperCase(), | ||
| status: 0, | ||
| response: ((_a = err == null ? void 0 : err.message) == null ? void 0 : _a.slice(0, RESPONSE_SNIPPET_CHARS)) || "", | ||
| timestamp: Date.now() | ||
| }); | ||
| } catch (e) { | ||
| } | ||
| throw err; | ||
| } | ||
| }; | ||
| return () => { | ||
| window.fetch = originalFetch; | ||
| }; | ||
| } | ||
| function collectXhrRequests(emit) { | ||
| const OrigXHR = window.XMLHttpRequest; | ||
| const origOpen = OrigXHR.prototype.open; | ||
| const origSend = OrigXHR.prototype.send; | ||
| const xhrMeta = /* @__PURE__ */ new WeakMap(); | ||
| OrigXHR.prototype.open = function(method, url, ...rest) { | ||
| try { | ||
| xhrMeta.set(this, { method, url: typeof url === "string" ? url : url.toString() }); | ||
| } catch (e) { | ||
| } | ||
| return origOpen.apply(this, [method, url, ...rest]); | ||
| }; | ||
| OrigXHR.prototype.send = function(body) { | ||
| try { | ||
| const xhr = this; | ||
| const start = Date.now(); | ||
| const meta = xhrMeta.get(xhr); | ||
| const method = (meta == null ? void 0 : meta.method) || "GET"; | ||
| const url = (meta == null ? void 0 : meta.url) || ""; | ||
| if (isInternalUrl(url)) return origSend.call(this, body); | ||
| const safeUrl = sanitizeUrl2(url).slice(0, 500); | ||
| xhr.addEventListener("loadend", function() { | ||
| try { | ||
| emit("api_request", { request: { url: safeUrl, method: method.toUpperCase(), statusCode: xhr.status, durationMs: Date.now() - start } }); | ||
| } catch (e) { | ||
| } | ||
| try { | ||
| if (xhr.status >= 400 || xhr.status === 0) { | ||
| let body2 = ""; | ||
| try { | ||
| const ct = xhr.getResponseHeader && xhr.getResponseHeader("content-type") || ""; | ||
| if (!BINARY_CONTENT_TYPE_RE.test(ct)) { | ||
| body2 = typeof xhr.responseText === "string" ? xhr.responseText : ""; | ||
| } | ||
| } catch (e) { | ||
| } | ||
| pushNetworkFailure({ | ||
| url: safeUrl, | ||
| method: method.toUpperCase(), | ||
| status: xhr.status, | ||
| response: body2.slice(0, RESPONSE_SNIPPET_CHARS), | ||
| timestamp: Date.now() | ||
| }); | ||
| } | ||
| } catch (e) { | ||
| } | ||
| }); | ||
| xhr.addEventListener("error", function() { | ||
| try { | ||
| emit("api_request", { request: { url: safeUrl, method: method.toUpperCase(), statusCode: 0, durationMs: Date.now() - start } }); | ||
| } catch (e) { | ||
| } | ||
| try { | ||
| pushNetworkFailure({ | ||
| url: safeUrl, | ||
| method: method.toUpperCase(), | ||
| status: 0, | ||
| response: "", | ||
| timestamp: Date.now() | ||
| }); | ||
| } catch (e) { | ||
| } | ||
| }); | ||
| } catch (err) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] XHR capture error:", err); | ||
| } | ||
| return origSend.call(this, body); | ||
| }; | ||
| return () => { | ||
| OrigXHR.prototype.open = origOpen; | ||
| OrigXHR.prototype.send = origSend; | ||
| }; | ||
| } | ||
| var _perfSeen = /* @__PURE__ */ new Set(); | ||
| function _emitPerfEntry(emit, e) { | ||
| if (!e || !e.name) return; | ||
| try { | ||
| if (isInternalUrl(e.name)) return; | ||
| } catch (e2) { | ||
| } | ||
| if (typeof e.name === "string" && e.name.indexOf("tracebug") !== -1) return; | ||
| const key = `${e.name}|${Math.round(e.startTime)}`; | ||
| if (_perfSeen.has(key)) return; | ||
| _perfSeen.add(key); | ||
| const navStart = typeof performance.timeOrigin === "number" ? performance.timeOrigin : Date.now(); | ||
| const ext = e; | ||
| const initiator = e.initiatorType || ""; | ||
| const method = (ext.method || "GET").toUpperCase(); | ||
| const status = ext.responseStatus || 0; | ||
| const url = sanitizeUrl2(e.name).slice(0, 500); | ||
| const timestamp = Math.round(navStart + e.startTime); | ||
| const durationMs = Math.round(e.duration || 0); | ||
| try { | ||
| emit("api_request", { | ||
| request: { url, method, statusCode: status, durationMs, initiatorType: initiator }, | ||
| _ts: timestamp | ||
| }); | ||
| } catch (e2) { | ||
| } | ||
| } | ||
| function drainPerformanceNetwork(emit) { | ||
| if (typeof performance === "undefined") return; | ||
| try { | ||
| const entries = performance.getEntriesByType("resource"); | ||
| for (const e of entries) _emitPerfEntry(emit, e); | ||
| } catch (e) { | ||
| } | ||
| } | ||
| function collectPerformanceNetwork(emit) { | ||
| if (typeof performance === "undefined" || typeof PerformanceObserver === "undefined") { | ||
| return () => { | ||
| }; | ||
| } | ||
| drainPerformanceNetwork(emit); | ||
| let observer = null; | ||
| try { | ||
| observer = new PerformanceObserver((list) => { | ||
| for (const e of list.getEntries()) { | ||
| _emitPerfEntry(emit, e); | ||
| } | ||
| }); | ||
| observer.observe({ type: "resource", buffered: false }); | ||
| } catch (e) { | ||
| } | ||
| return () => { | ||
| try { | ||
| observer == null ? void 0 : observer.disconnect(); | ||
| } catch (e) { | ||
| } | ||
| }; | ||
| } | ||
| var CONSOLE_MSG_MAX = 1e4; | ||
| function capMessage(s, max = CONSOLE_MSG_MAX) { | ||
| return s.length > max ? s.slice(0, max) + "\u2026[truncated]" : s; | ||
| } | ||
| function formatConsoleArgs(args) { | ||
| const parts = args.map((a) => { | ||
| if (typeof a === "string") return a; | ||
| try { | ||
| const s = JSON.stringify(a); | ||
| return s === void 0 ? String(a) : s; | ||
| } catch (e) { | ||
| return "[unserializable]"; | ||
| } | ||
| }); | ||
| return capMessage(parts.join(" ")); | ||
| } | ||
| function collectErrors(emit) { | ||
| const prevOnError = window.onerror; | ||
| window.onerror = (msg, source, line, col, error) => { | ||
| try { | ||
| emit("error", { | ||
| error: { | ||
| message: sanitizeTokenShapes(capMessage(typeof msg === "string" ? msg : "Unknown error")), | ||
| stack: (error == null ? void 0 : error.stack) && sanitizeTokenShapes(capMessage(error.stack)), | ||
| source, | ||
| line, | ||
| column: col | ||
| } | ||
| }); | ||
| } catch (e) { | ||
| } | ||
| if (prevOnError) { | ||
| try { | ||
| prevOnError(msg, source, line, col, error); | ||
| } catch (e) { | ||
| } | ||
| } | ||
| }; | ||
| const onRejection = (e) => { | ||
| var _a, _b; | ||
| try { | ||
| emit("unhandled_rejection", { | ||
| error: { | ||
| message: sanitizeTokenShapes(capMessage(((_a = e.reason) == null ? void 0 : _a.message) || String(e.reason))), | ||
| stack: ((_b = e.reason) == null ? void 0 : _b.stack) && sanitizeTokenShapes(capMessage(e.reason.stack)) | ||
| } | ||
| }); | ||
| } catch (e2) { | ||
| } | ||
| }; | ||
| window.addEventListener("unhandledrejection", onRejection); | ||
| return () => { | ||
| window.onerror = prevOnError; | ||
| window.removeEventListener("unhandledrejection", onRejection); | ||
| }; | ||
| } | ||
| function collectConsoleErrors(emit) { | ||
| const origConsoleError = console.error; | ||
| let _insideEmit = false; | ||
| console.error = function(...args) { | ||
| if (_insideEmit) { | ||
| origConsoleError.apply(console, args); | ||
| return; | ||
| } | ||
| _insideEmit = true; | ||
| try { | ||
| emit("console_error", { | ||
| // Token-shape scrub at capture — a token logged to the console must | ||
| // never reach the offline .html export unmasked (the cloud sanitizer | ||
| // only covers the upload path). formatConsoleArgs caps + is circular-safe. | ||
| error: { message: sanitizeTokenShapes(formatConsoleArgs(args)) } | ||
| }); | ||
| } catch (e) { | ||
| } finally { | ||
| _insideEmit = false; | ||
| } | ||
| origConsoleError.apply(console, args); | ||
| }; | ||
| return () => { | ||
| console.error = origConsoleError; | ||
| }; | ||
| } | ||
| var CONSOLE_LEVEL_CAP = 50; | ||
| function wrapConsoleLevel(method, type, emit) { | ||
| const orig = console[method]; | ||
| let _inside = false; | ||
| let _count = 0; | ||
| console[method] = function(...args) { | ||
| const own = typeof args[0] === "string" && args[0].startsWith("[TraceBug]"); | ||
| if (_inside || own || _count >= CONSOLE_LEVEL_CAP) { | ||
| orig.apply(console, args); | ||
| return; | ||
| } | ||
| _inside = true; | ||
| _count++; | ||
| try { | ||
| emit(type, { | ||
| // Same capture-time token scrub as console_error — the offline | ||
| // export path never runs the cloud sanitizer. Capped + circular-safe. | ||
| error: { message: sanitizeTokenShapes(formatConsoleArgs(args)) } | ||
| }); | ||
| } catch (e) { | ||
| } finally { | ||
| _inside = false; | ||
| } | ||
| orig.apply(console, args); | ||
| }; | ||
| return () => { | ||
| console[method] = orig; | ||
| }; | ||
| } | ||
| function collectConsoleWarnings(emit) { | ||
| return wrapConsoleLevel("warn", "console_warn", emit); | ||
| } | ||
| function collectConsoleInfo(emit) { | ||
| return wrapConsoleLevel("info", "console_info", emit); | ||
| } | ||
| function collectConsoleLogs(emit) { | ||
| return wrapConsoleLevel("log", "console_log", emit); | ||
| } | ||
| // src/ui/helpers.ts | ||
| function tbIsolationCss(root) { | ||
| return ` | ||
| ${root} { | ||
| box-sizing: border-box; | ||
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; | ||
| font-size: 14px; font-weight: 400; line-height: 1.5; font-style: normal; | ||
| letter-spacing: normal; text-transform: none; text-align: left; | ||
| text-indent: 0; white-space: normal; word-spacing: normal; text-shadow: none; | ||
| -webkit-font-smoothing: antialiased; | ||
| } | ||
| ${root} *, ${root} *::before, ${root} *::after { box-sizing: border-box; } | ||
| ${root} button, ${root} input, ${root} select, ${root} textarea { | ||
| font-family: inherit; font-size: inherit; letter-spacing: normal; | ||
| text-transform: none; margin: 0; | ||
| } | ||
| ${root} svg { max-width: none; max-height: none; vertical-align: middle; } | ||
| ${root} img { max-width: none; } | ||
| ${root} a { text-decoration: none; } | ||
| `; | ||
| } | ||
| function parseShortcut(shortcut) { | ||
| const parts = (shortcut || "").toLowerCase().split("+").map((s) => s.trim()); | ||
| return { | ||
| mod: parts.includes("ctrl") || parts.includes("control") || parts.includes("cmd") || parts.includes("meta"), | ||
| shift: parts.includes("shift"), | ||
| alt: parts.includes("alt") || parts.includes("option"), | ||
| key: parts[parts.length - 1] || "" | ||
| }; | ||
| } | ||
| function matchesShortcut(e, shortcut) { | ||
| if (!shortcut) return false; | ||
| const s = parseShortcut(shortcut); | ||
| const mod = e.ctrlKey || e.metaKey; | ||
| const key = (e.key || "").toLowerCase(); | ||
| return mod === s.mod && e.shiftKey === s.shift && e.altKey === s.alt && key === s.key; | ||
| } | ||
| function escapeHtml(str) { | ||
| return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """); | ||
| } | ||
| export { | ||
| generateSessionId, | ||
| getActiveSessionId, | ||
| setActiveSessionId, | ||
| clearActiveSessionId, | ||
| getActiveCaptureMode, | ||
| setActiveCaptureMode, | ||
| getAllSessions, | ||
| getCachedSessions, | ||
| scheduleFlush, | ||
| flushPendingEvents, | ||
| appendEvent, | ||
| updateSessionError, | ||
| deleteSession, | ||
| addAnnotation, | ||
| saveEnvironment, | ||
| setSessionPriority, | ||
| markSessionSaved, | ||
| clearAllSessions, | ||
| setRedactRules, | ||
| isCustomSensitiveKey, | ||
| isNoiseRequest, | ||
| isStaticResource, | ||
| shortDisplayPath, | ||
| sanitizeTokenShapes, | ||
| sanitizeReportForUpload, | ||
| getNetworkFailures, | ||
| clearNetworkFailures, | ||
| collectClicks, | ||
| collectInputs, | ||
| collectSelectChanges, | ||
| collectFormSubmits, | ||
| collectRouteChanges, | ||
| collectApiRequests, | ||
| collectXhrRequests, | ||
| drainPerformanceNetwork, | ||
| collectPerformanceNetwork, | ||
| collectErrors, | ||
| collectConsoleErrors, | ||
| collectConsoleWarnings, | ||
| collectConsoleInfo, | ||
| collectConsoleLogs, | ||
| tbIsolationCss, | ||
| matchesShortcut, | ||
| escapeHtml | ||
| }; | ||
| //# sourceMappingURL=chunk-2ZJIB656.js.map |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
| "use strict";Object.defineProperty(exports, "__esModule", {value: true});// src/storage.ts | ||
| var SESSIONS_KEY = "tracebug_sessions"; | ||
| var ACTIVE_SESSION_KEY = "tracebug_active_session"; | ||
| var ACTIVE_CAPTURE_MODE_KEY = "tracebug_active_capture_mode"; | ||
| function generateSessionId() { | ||
| return typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : "bt_" + Date.now().toString(36) + Math.random().toString(36).slice(2, 10); | ||
| } | ||
| function getActiveSessionId() { | ||
| try { | ||
| return localStorage.getItem(ACTIVE_SESSION_KEY); | ||
| } catch (e) { | ||
| return null; | ||
| } | ||
| } | ||
| function setActiveSessionId(id) { | ||
| try { | ||
| localStorage.setItem(ACTIVE_SESSION_KEY, id); | ||
| } catch (e) { | ||
| } | ||
| } | ||
| function clearActiveSessionId() { | ||
| try { | ||
| localStorage.removeItem(ACTIVE_SESSION_KEY); | ||
| } catch (e) { | ||
| } | ||
| try { | ||
| localStorage.removeItem(ACTIVE_CAPTURE_MODE_KEY); | ||
| } catch (e) { | ||
| } | ||
| } | ||
| function getActiveCaptureMode() { | ||
| try { | ||
| const v = localStorage.getItem(ACTIVE_CAPTURE_MODE_KEY); | ||
| return v === "events" || v === "video" ? v : null; | ||
| } catch (e) { | ||
| return null; | ||
| } | ||
| } | ||
| function setActiveCaptureMode(mode) { | ||
| try { | ||
| localStorage.setItem(ACTIVE_CAPTURE_MODE_KEY, mode); | ||
| } catch (e) { | ||
| } | ||
| } | ||
| function getAllSessions() { | ||
| try { | ||
| const raw = localStorage.getItem(SESSIONS_KEY); | ||
| return raw ? JSON.parse(raw) : []; | ||
| } catch (e) { | ||
| return []; | ||
| } | ||
| } | ||
| function saveSessions(sessions) { | ||
| try { | ||
| localStorage.setItem(SESSIONS_KEY, JSON.stringify(sessions)); | ||
| return; | ||
| } catch (e) { | ||
| } | ||
| const commit = (next) => { | ||
| try { | ||
| localStorage.setItem(SESSIONS_KEY, JSON.stringify(next)); | ||
| sessions.length = 0; | ||
| sessions.push(...next); | ||
| return true; | ||
| } catch (e) { | ||
| return false; | ||
| } | ||
| }; | ||
| const working = sessions.slice(); | ||
| while (working.length > 1) { | ||
| working.shift(); | ||
| if (commit(working)) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] Storage full \u2014 dropped oldest session(s) to fit."); | ||
| return; | ||
| } | ||
| } | ||
| const last = working[0]; | ||
| if (last && Array.isArray(last.events)) { | ||
| while (last.events.length > 1) { | ||
| last.events = last.events.slice(Math.ceil(last.events.length / 2)); | ||
| if (commit(working)) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] Storage full \u2014 trimmed older events from the current session to fit."); | ||
| return; | ||
| } | ||
| } | ||
| } | ||
| if (typeof console !== "undefined") console.error("[TraceBug] Could not persist sessions: localStorage quota exceeded."); | ||
| } | ||
| var _cachedSessions = null; | ||
| var _pendingFlush = null; | ||
| var _dirty = false; | ||
| var FLUSH_INTERVAL_MS = 1e3; | ||
| function getCachedSessions() { | ||
| if (!_cachedSessions) { | ||
| _cachedSessions = getAllSessions(); | ||
| } | ||
| return _cachedSessions; | ||
| } | ||
| function scheduleFlush() { | ||
| _dirty = true; | ||
| if (_pendingFlush) return; | ||
| _pendingFlush = setTimeout(() => { | ||
| _pendingFlush = null; | ||
| if (_cachedSessions && _dirty) { | ||
| saveSessions(_cachedSessions); | ||
| _dirty = false; | ||
| } | ||
| }, FLUSH_INTERVAL_MS); | ||
| } | ||
| function flushPendingEvents() { | ||
| if (_pendingFlush) { | ||
| clearTimeout(_pendingFlush); | ||
| _pendingFlush = null; | ||
| } | ||
| if (_cachedSessions) { | ||
| saveSessions(_cachedSessions); | ||
| _dirty = false; | ||
| } | ||
| } | ||
| function invalidateCache() { | ||
| if (_pendingFlush) { | ||
| clearTimeout(_pendingFlush); | ||
| _pendingFlush = null; | ||
| } | ||
| _cachedSessions = null; | ||
| _dirty = false; | ||
| } | ||
| if (typeof window !== "undefined") { | ||
| window.addEventListener("beforeunload", flushPendingEvents); | ||
| window.addEventListener("pagehide", flushPendingEvents); | ||
| document.addEventListener("visibilitychange", () => { | ||
| if (document.visibilityState === "hidden") flushPendingEvents(); | ||
| }); | ||
| } | ||
| function appendEvent(sessionId, event, maxEvents, maxSessions) { | ||
| let sessions = getCachedSessions(); | ||
| let session = sessions.find((s) => s.sessionId === sessionId); | ||
| if (!session) { | ||
| session = { | ||
| sessionId, | ||
| projectId: event.projectId, | ||
| createdAt: Date.now(), | ||
| updatedAt: Date.now(), | ||
| errorMessage: null, | ||
| errorStack: null, | ||
| reproSteps: null, | ||
| errorSummary: null, | ||
| events: [], | ||
| annotations: [], | ||
| environment: null | ||
| }; | ||
| sessions.push(session); | ||
| } | ||
| session.events.push(event); | ||
| session.updatedAt = Date.now(); | ||
| if (session.events.length > maxEvents) { | ||
| session.events = session.events.slice(-maxEvents); | ||
| } | ||
| if (sessions.length > maxSessions) { | ||
| sessions = sessions.slice(-maxSessions); | ||
| _cachedSessions = sessions; | ||
| } | ||
| scheduleFlush(); | ||
| } | ||
| function updateSessionError(sessionId, errorMessage, errorStack, reproSteps, errorSummary) { | ||
| const sessions = getCachedSessions(); | ||
| const session = sessions.find((s) => s.sessionId === sessionId); | ||
| if (!session) return; | ||
| session.errorMessage = errorMessage; | ||
| session.errorStack = errorStack || null; | ||
| session.reproSteps = reproSteps; | ||
| session.errorSummary = errorSummary; | ||
| session.updatedAt = Date.now(); | ||
| scheduleFlush(); | ||
| } | ||
| function deleteSession(sessionId) { | ||
| flushPendingEvents(); | ||
| const remaining = getAllSessions().filter((s) => s.sessionId !== sessionId); | ||
| invalidateCache(); | ||
| saveSessions(remaining); | ||
| } | ||
| function addAnnotation(sessionId, annotation) { | ||
| const sessions = getCachedSessions(); | ||
| const session = sessions.find((s) => s.sessionId === sessionId); | ||
| if (!session) return; | ||
| if (!session.annotations) session.annotations = []; | ||
| session.annotations.push(annotation); | ||
| session.updatedAt = Date.now(); | ||
| scheduleFlush(); | ||
| } | ||
| function saveEnvironment(sessionId, env) { | ||
| const sessions = getCachedSessions(); | ||
| const session = sessions.find((s) => s.sessionId === sessionId); | ||
| if (!session) return; | ||
| session.environment = env; | ||
| scheduleFlush(); | ||
| } | ||
| function setSessionPriority(sessionId, priority) { | ||
| const sessions = getCachedSessions(); | ||
| const session = sessions.find((s) => s.sessionId === sessionId); | ||
| if (!session) return; | ||
| session.priority = priority; | ||
| session.updatedAt = Date.now(); | ||
| scheduleFlush(); | ||
| } | ||
| function markSessionSaved(sessionId) { | ||
| const sessions = getCachedSessions(); | ||
| const session = sessions.find((s) => s.sessionId === sessionId); | ||
| if (!session) return; | ||
| session.saved = true; | ||
| session.updatedAt = Date.now(); | ||
| flushPendingEvents(); | ||
| } | ||
| function clearAllSessions() { | ||
| invalidateCache(); | ||
| try { | ||
| localStorage.removeItem(SESSIONS_KEY); | ||
| } catch (e) { | ||
| } | ||
| } | ||
| // src/sanitize/custom-redaction.ts | ||
| var REDACTED = "[REDACTED]"; | ||
| var _fieldRe = null; | ||
| var _fieldTextRes = []; | ||
| var _patterns = []; | ||
| function escapeRe(s) { | ||
| return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); | ||
| } | ||
| function setRedactRules(rules) { | ||
| _fieldRe = null; | ||
| _fieldTextRes = []; | ||
| _patterns = []; | ||
| if (!rules) return; | ||
| const fields = (rules.fields || []).filter((f) => typeof f === "string" && f.trim().length > 0); | ||
| if (fields.length > 0) { | ||
| const alts = fields.map((f) => escapeRe(f.trim())).join("|"); | ||
| _fieldRe = new RegExp(alts, "i"); | ||
| for (const f of fields) { | ||
| const k = escapeRe(f.trim()); | ||
| _fieldTextRes.push({ | ||
| re: new RegExp(`("([^"]*${k}[^"]*)"\\s*:\\s*)("(?:[^"\\\\]|\\\\.)*"|-?\\d[\\d.eE+-]*|true|false)`, "gi"), | ||
| replace: `$1"${REDACTED}"` | ||
| }); | ||
| _fieldTextRes.push({ | ||
| re: new RegExp(`\\b([\\w.-]*${k}[\\w.-]*)=([^&\\s"']+)`, "gi"), | ||
| replace: `$1=${REDACTED}` | ||
| }); | ||
| } | ||
| } | ||
| for (const p of rules.patterns || []) { | ||
| try { | ||
| if (typeof p === "string") { | ||
| _patterns.push(new RegExp(p, "gi")); | ||
| } else if (p instanceof RegExp) { | ||
| _patterns.push(p.flags.includes("g") ? p : new RegExp(p.source, p.flags + "g")); | ||
| } | ||
| } catch (e) { | ||
| } | ||
| } | ||
| } | ||
| function isCustomSensitiveKey(key) { | ||
| if (!key || !_fieldRe) return false; | ||
| return _fieldRe.test(key); | ||
| } | ||
| function applyCustomRedaction(s) { | ||
| if (!s || _fieldTextRes.length === 0 && _patterns.length === 0) return s; | ||
| let out = s; | ||
| for (const { re, replace } of _fieldTextRes) out = out.replace(re, replace); | ||
| for (const re of _patterns) out = out.replace(re, REDACTED); | ||
| return out; | ||
| } | ||
| // src/url-hygiene.ts | ||
| var SENSITIVE_PARAM_RE = /token|key|secret|auth|password|passwd|pwd|credential|session|sid|csrf|sig|signature/i; | ||
| function isSensitiveParamName(name) { | ||
| return !!name && SENSITIVE_PARAM_RE.test(name); | ||
| } | ||
| var ASSET_EXT_RE = /\.(png|jpe?g|gif|webp|avif|svg|ico|bmp|css|woff2?|ttf|otf|eot|map|mp4|webm|ogg|mp3|pdf)(\?|#|$)/i; | ||
| var NOISE_HOSTS = [ | ||
| "camo.githubusercontent.com", | ||
| "avatars.githubusercontent.com", | ||
| "img.shields.io", | ||
| "fonts.googleapis.com", | ||
| "fonts.gstatic.com", | ||
| "google-analytics.com", | ||
| "www.google-analytics.com", | ||
| "googletagmanager.com", | ||
| "www.googletagmanager.com", | ||
| "stats.g.doubleclick.net", | ||
| "connect.facebook.net", | ||
| "cdn.segment.com", | ||
| "api.segment.io", | ||
| "gravatar.com", | ||
| "www.gravatar.com" | ||
| ]; | ||
| var NOISE_HOST_PREFIX_RE = /^(collector|stats|telemetry|analytics|metrics|beacon|track(ing)?|pixel|events|logs?)\./i; | ||
| var NOISE_PATH_RE = /(^|\/)(collect|collector|beacon|telemetry|pixel|track|tracking)(\/|$)|\/_private\//i; | ||
| var SEGMENT_MAX = 24; | ||
| var PATH_MAX = 60; | ||
| function isNoiseRequest(url) { | ||
| if (!url) return false; | ||
| if (ASSET_EXT_RE.test(url)) return true; | ||
| if (!/^https?:\/\//i.test(url)) return false; | ||
| try { | ||
| const u = new URL(url); | ||
| const host = u.hostname.toLowerCase(); | ||
| if (NOISE_HOSTS.some((h) => host === h || host.endsWith("." + h))) return true; | ||
| if (NOISE_HOST_PREFIX_RE.test(host)) return true; | ||
| const pageHost = typeof window !== "undefined" ? window.location.hostname.toLowerCase() : null; | ||
| if (pageHost && host === pageHost) return false; | ||
| return NOISE_PATH_RE.test(u.pathname); | ||
| } catch (e) { | ||
| return false; | ||
| } | ||
| } | ||
| function isStaticResource(url) { | ||
| if (!url) return false; | ||
| return ASSET_EXT_RE.test(url) || /\.(m?js)(\?|#|$)/i.test(url); | ||
| } | ||
| function shortDisplayPath(url, base) { | ||
| if (!url) return ""; | ||
| let pathname; | ||
| try { | ||
| const origin = base || (typeof window !== "undefined" ? window.location.origin : "http://relative.local"); | ||
| pathname = new URL(url, origin).pathname || url; | ||
| } catch (e) { | ||
| pathname = url; | ||
| } | ||
| const segments = pathname.split("/").map( | ||
| (seg) => seg.length > SEGMENT_MAX ? `${seg.slice(0, 10)}\u2026${seg.slice(-6)}` : seg | ||
| ); | ||
| let out = segments.join("/"); | ||
| if (out.length > PATH_MAX) out = out.slice(0, PATH_MAX - 1) + "\u2026"; | ||
| return out; | ||
| } | ||
| // src/sanitize/cloud-upload.ts | ||
| var REDACTED2 = "[REDACTED]"; | ||
| var TOKEN_PATTERNS = [ | ||
| // Bearer <token> in headers, console output, anywhere | ||
| { name: "bearer", re: /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}/gi, replace: () => "Bearer " + REDACTED2 }, | ||
| // JWT (3 base64url segments separated by dots, leading with eyJ which is `{"` in base64) | ||
| { name: "jwt", re: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g, replace: mask }, | ||
| // OpenAI / Stripe sk_* | ||
| { name: "sk_prefix", re: /\bsk-[A-Za-z0-9_-]{20,}\b/g, replace: mask }, | ||
| // Stripe secret + publishable (live/test, secret + publishable + restricted) | ||
| { name: "stripe", re: /\b(?:sk|pk|rk)_(?:live|test)_[A-Za-z0-9]{16,}\b/g, replace: mask }, | ||
| // GitHub PATs (classic + fine-grained + OAuth + server tokens) | ||
| { name: "github_pat", re: /\bghp_[A-Za-z0-9]{30,}\b/g, replace: mask }, | ||
| { name: "github_fine", re: /\bgithub_pat_[A-Za-z0-9_]{60,}\b/g, replace: mask }, | ||
| { name: "github_oauth", re: /\bgho_[A-Za-z0-9]{30,}\b/g, replace: mask }, | ||
| { name: "github_server", re: /\bghs_[A-Za-z0-9]{30,}\b/g, replace: mask }, | ||
| // AWS access keys (begins with AKIA, ASIA, AGPA, AIDA, etc.) + secret key (40-char base64) | ||
| { name: "aws_access", re: /\b(?:AKIA|ASIA|AGPA|AIDA|AROA|ANPA|ANVA)[A-Z0-9]{16}\b/g, replace: mask }, | ||
| { name: "aws_secret", re: /\b(?:aws.{0,20})?[A-Za-z0-9/+]{40}\b(?=.*aws|.*secret|.*key)/gi, replace: mask }, | ||
| // Slack — broader than before (xoxa-z covers all known prefixes) | ||
| { name: "slack", re: /\bxox[abeprs]-[A-Za-z0-9-]{10,}\b/g, replace: mask }, | ||
| // Google API keys | ||
| { name: "google_api", re: /\bAIza[A-Za-z0-9_-]{35}\b/g, replace: mask }, | ||
| // Twilio — Account SID + Auth tokens | ||
| { name: "twilio_sid", re: /\b(?:AC|SK)[a-f0-9]{32}\b/g, replace: mask }, | ||
| // SendGrid | ||
| { name: "sendgrid", re: /\bSG\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}\b/g, replace: mask }, | ||
| // Mailgun | ||
| { name: "mailgun", re: /\bkey-[a-f0-9]{32}\b/g, replace: mask }, | ||
| // Postmark | ||
| { name: "postmark", re: /\b[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\b(?=.{0,30}(postmark|server-token|api-token))/gi, replace: mask }, | ||
| // Linear / Vercel / Cloudflare / Discord | ||
| { name: "linear", re: /\blin_api_[A-Za-z0-9]{40,}\b/g, replace: mask }, | ||
| { name: "discord_bot", re: /\b[MN][A-Za-z\d]{23}\.[A-Za-z\d_-]{6}\.[A-Za-z\d_-]{27,}\b/g, replace: mask }, | ||
| // Generic high-entropy hex (≥32 chars). Catches webhook signing secrets, | ||
| // session IDs, etc. that don't carry a recognizable prefix. Conservative: | ||
| // only triggers when preceded by a common secret-y keyword to avoid | ||
| // mangling legitimate hex like git SHAs. | ||
| { name: "labeled_hex", re: /\b(?:secret|token|key|password|api[_-]?key|auth)["':\s=]{1,5}([a-fA-F0-9]{32,})\b/gi, replace: (s) => s.replace(/[a-fA-F0-9]{32,}/, REDACTED2) } | ||
| ]; | ||
| function mask(s) { | ||
| if (s.length <= 12) return REDACTED2; | ||
| return `${s.slice(0, 4)}\u2026${REDACTED2}\u2026${s.slice(-4)}`; | ||
| } | ||
| function sanitizeUrl(url) { | ||
| if (typeof url !== "string" || url.length === 0) return url; | ||
| try { | ||
| const isAbs = /^[a-z][a-z0-9+.-]*:/i.test(url); | ||
| const u = new URL(isAbs ? url : `http://_placeholder_${url.startsWith("/") ? "" : "/"}${url}`); | ||
| let changed = false; | ||
| u.searchParams.forEach((_v, k) => { | ||
| if (isSensitiveParamName(k) || isCustomSensitiveKey(k)) { | ||
| u.searchParams.set(k, REDACTED2); | ||
| changed = true; | ||
| } | ||
| }); | ||
| if (!changed) return sanitizeText(url); | ||
| const out = isAbs ? u.toString() : u.pathname + u.search + u.hash; | ||
| return sanitizeText(out); | ||
| } catch (e) { | ||
| return sanitizeText(url); | ||
| } | ||
| } | ||
| function sanitizeText(s) { | ||
| if (s == null) return s; | ||
| let out = String(s); | ||
| for (const p of TOKEN_PATTERNS) out = out.replace(p.re, p.replace); | ||
| return applyCustomRedaction(out); | ||
| } | ||
| function sanitizeTokenShapes(s) { | ||
| return sanitizeText(s); | ||
| } | ||
| function sanitizeReportForUpload(report) { | ||
| var _a; | ||
| const out = typeof structuredClone === "function" ? structuredClone(report) : JSON.parse(JSON.stringify(report)); | ||
| if (out.consoleErrors) { | ||
| out.consoleErrors = out.consoleErrors.map((e) => { | ||
| var _a2; | ||
| return { | ||
| ...e, | ||
| message: sanitizeText(e.message), | ||
| stack: sanitizeText((_a2 = e.stack) != null ? _a2 : void 0) | ||
| }; | ||
| }); | ||
| } | ||
| if (out.consoleLogs) { | ||
| out.consoleLogs = out.consoleLogs.map((e) => { | ||
| var _a2; | ||
| return { | ||
| ...e, | ||
| message: sanitizeText(e.message), | ||
| stack: sanitizeText((_a2 = e.stack) != null ? _a2 : void 0) | ||
| }; | ||
| }); | ||
| } | ||
| if (out.networkErrors) { | ||
| out.networkErrors = out.networkErrors.map((e) => { | ||
| var _a2; | ||
| return { | ||
| ...e, | ||
| url: sanitizeUrl(e.url), | ||
| response: sanitizeText((_a2 = e.response) != null ? _a2 : void 0) | ||
| }; | ||
| }); | ||
| } | ||
| if (out.networkRequests) { | ||
| out.networkRequests = out.networkRequests.map((e) => { | ||
| var _a2; | ||
| return { | ||
| ...e, | ||
| url: sanitizeUrl(e.url), | ||
| response: sanitizeText((_a2 = e.response) != null ? _a2 : void 0) | ||
| }; | ||
| }); | ||
| } | ||
| if (out.steps) out.steps = sanitizeText(out.steps); | ||
| if (out.summary) out.summary = sanitizeText(out.summary); | ||
| if (out.title) out.title = sanitizeText(out.title); | ||
| if (Array.isArray(out.sessionSteps)) out.sessionSteps = out.sessionSteps.map((s) => sanitizeText(s)); | ||
| if (out.actionChips) { | ||
| out.actionChips = out.actionChips.map((c) => { | ||
| var _a2, _b; | ||
| return { | ||
| ...c, | ||
| target: sanitizeText((_a2 = c.target) != null ? _a2 : void 0), | ||
| detail: sanitizeText((_b = c.detail) != null ? _b : void 0) | ||
| }; | ||
| }); | ||
| } | ||
| if (out.storage) { | ||
| const scrub = (entries) => entries.map((e) => ({ ...e, value: sanitizeText(e.value) })); | ||
| out.storage.local = scrub(out.storage.local || []); | ||
| out.storage.session = scrub(out.storage.session || []); | ||
| if (out.storage.cookies) out.storage.cookies = scrub(out.storage.cookies); | ||
| } | ||
| if ((_a = out.environment) == null ? void 0 : _a.url) out.environment.url = sanitizeUrl(out.environment.url); | ||
| if (out.context && typeof out.context === "object") { | ||
| const ctx = {}; | ||
| for (const [k, v] of Object.entries(out.context)) { | ||
| ctx[k] = typeof v === "string" ? sanitizeText(v) : v; | ||
| } | ||
| out.context = ctx; | ||
| } | ||
| return out; | ||
| } | ||
| // src/collectors.ts | ||
| var ROOT_ID = "tracebug-root"; | ||
| var PANEL_ID = "tracebug-dashboard-panel"; | ||
| var BTN_ID = "tracebug-dashboard-btn"; | ||
| var NETWORK_FAILURE_LIMIT = 10; | ||
| var RESPONSE_SNIPPET_CHARS = 200; | ||
| var _networkFailures = []; | ||
| function pushNetworkFailure(failure) { | ||
| try { | ||
| if (failure.response) failure.response = sanitizeTokenShapes(failure.response); | ||
| _networkFailures.push(failure); | ||
| if (_networkFailures.length > NETWORK_FAILURE_LIMIT) { | ||
| _networkFailures.splice(0, _networkFailures.length - NETWORK_FAILURE_LIMIT); | ||
| } | ||
| } catch (e) { | ||
| } | ||
| } | ||
| function getNetworkFailures() { | ||
| return _networkFailures.slice(); | ||
| } | ||
| function clearNetworkFailures() { | ||
| _networkFailures.length = 0; | ||
| } | ||
| function sanitizeUrl2(url) { | ||
| if (!url) return url; | ||
| try { | ||
| const qIdx = url.indexOf("?"); | ||
| if (qIdx === -1) return url; | ||
| const base = url.slice(0, qIdx); | ||
| const afterQ = url.slice(qIdx + 1); | ||
| const hashIdx = afterQ.indexOf("#"); | ||
| const query = hashIdx === -1 ? afterQ : afterQ.slice(0, hashIdx); | ||
| const hash = hashIdx === -1 ? "" : afterQ.slice(hashIdx); | ||
| const redacted = query.split("&").map((part) => { | ||
| const eqIdx = part.indexOf("="); | ||
| if (eqIdx === -1) return part; | ||
| const key = part.slice(0, eqIdx); | ||
| if (isSensitiveParamName(key) || isCustomSensitiveKey(key)) return `${key}=[REDACTED]`; | ||
| return part; | ||
| }).join("&"); | ||
| return `${base}?${redacted}${hash}`; | ||
| } catch (e) { | ||
| return url; | ||
| } | ||
| } | ||
| var MAX_BODY_BYTES = 10 * 1024; | ||
| var BINARY_CONTENT_TYPE_RE = /^(image|video|audio)\/|^application\/(octet-stream|pdf|zip|x-protobuf|x-msgpack|wasm|vnd\.)/i; | ||
| async function readResponseBodySafe(response) { | ||
| try { | ||
| const ct = response.headers.get("content-type") || ""; | ||
| if (BINARY_CONTENT_TYPE_RE.test(ct)) return ""; | ||
| if (!response.body || typeof response.body.getReader !== "function") { | ||
| try { | ||
| const text = await response.text(); | ||
| return typeof text === "string" ? text.slice(0, RESPONSE_SNIPPET_CHARS) : ""; | ||
| } catch (e) { | ||
| return ""; | ||
| } | ||
| } | ||
| const reader = response.body.getReader(); | ||
| const decoder = new TextDecoder("utf-8", { fatal: false }); | ||
| let collected = ""; | ||
| let bytesRead = 0; | ||
| while (bytesRead < MAX_BODY_BYTES && collected.length < RESPONSE_SNIPPET_CHARS) { | ||
| const { value, done } = await reader.read(); | ||
| if (done) break; | ||
| if (value) { | ||
| bytesRead += value.byteLength; | ||
| collected += decoder.decode(value, { stream: true }); | ||
| } | ||
| } | ||
| try { | ||
| await reader.cancel(); | ||
| } catch (e) { | ||
| } | ||
| return collected.slice(0, RESPONSE_SNIPPET_CHARS); | ||
| } catch (e) { | ||
| return ""; | ||
| } | ||
| } | ||
| var INTERNAL_URL_PATTERNS = [ | ||
| /__nextjs_original-stack-frame/, | ||
| /\/_next\/static\/webpack/, | ||
| /\/__webpack_hmr/, | ||
| /\.hot-update\./, | ||
| /\/sockjs-node\//, | ||
| /\/turbopack-hmr\//, | ||
| /\/_next\/webpack-hmr/, | ||
| /\/webpack-dev-server\//, | ||
| /\/__vite_ping/, | ||
| /\/@vite\/client/, | ||
| /\/@react-refresh/ | ||
| ]; | ||
| function isInternalUrl(url) { | ||
| return INTERNAL_URL_PATTERNS.some((pattern) => pattern.test(url)); | ||
| } | ||
| var _rootCache; | ||
| function getRoot() { | ||
| if (_rootCache === void 0) { | ||
| _rootCache = document.getElementById(ROOT_ID); | ||
| } | ||
| if (_rootCache && !_rootCache.isConnected) { | ||
| _rootCache = document.getElementById(ROOT_ID); | ||
| } | ||
| return _rootCache; | ||
| } | ||
| function isTraceBugElement(el) { | ||
| if (!el) return false; | ||
| if (el.id === ROOT_ID || el.id === BTN_ID || el.id === PANEL_ID) return true; | ||
| if (el.dataset && el.dataset.tracebug) return true; | ||
| const root = getRoot(); | ||
| if (root && root.contains(el)) return true; | ||
| let node = el; | ||
| while (node) { | ||
| const id = node.id || ""; | ||
| if (id.startsWith("tracebug-") || id.startsWith("bt-")) return true; | ||
| const cn = typeof node.className === "string" ? node.className : ""; | ||
| if (cn.includes("tracebug-") || cn.includes("bt-ann") || cn.includes("bt-voice")) return true; | ||
| if (node.dataset && node.dataset.tracebug) return true; | ||
| node = node.parentElement; | ||
| } | ||
| return false; | ||
| } | ||
| function collectClicks(emit) { | ||
| const handler = (e) => { | ||
| try { | ||
| const t = e.target; | ||
| if (!t || isTraceBugElement(t)) return; | ||
| const tag = t.tagName.toLowerCase(); | ||
| const el = { | ||
| tag, | ||
| text: (t.innerText || "").slice(0, 120), | ||
| id: t.id || "", | ||
| className: typeof t.className === "string" ? t.className : "" | ||
| }; | ||
| const data = { element: el }; | ||
| if (tag === "a") el.href = t.href || ""; | ||
| if (tag === "button" || t.type === "submit") { | ||
| el.buttonType = t.type || "button"; | ||
| el.disabled = t.disabled; | ||
| } | ||
| if (tag === "label") el.forField = t.htmlFor || ""; | ||
| const ariaLabel = t.getAttribute("aria-label"); | ||
| if (ariaLabel) el.ariaLabel = ariaLabel; | ||
| const role = t.getAttribute("role"); | ||
| if (role) el.role = role; | ||
| const testId = t.getAttribute("data-testid"); | ||
| if (testId) el.testId = testId; | ||
| const form = t.closest("form"); | ||
| if (form) { | ||
| el.formId = form.id || ""; | ||
| el.formAction = form.action || ""; | ||
| } | ||
| try { | ||
| el.selector = buildSelector(t); | ||
| } catch (e2) { | ||
| } | ||
| try { | ||
| const r = t.getBoundingClientRect(); | ||
| el.boundingBox = { x: Math.round(r.left), y: Math.round(r.top), width: Math.round(r.width), height: Math.round(r.height) }; | ||
| } catch (e2) { | ||
| } | ||
| emit("click", data); | ||
| } catch (err) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] Click capture error:", err); | ||
| } | ||
| }; | ||
| document.addEventListener("click", handler, { capture: true }); | ||
| return () => document.removeEventListener("click", handler, { capture: true }); | ||
| } | ||
| function buildSelector(el) { | ||
| if (!el) return ""; | ||
| if (el.id) return `#${CSS.escape(el.id)}`; | ||
| const testId = el.getAttribute("data-testid"); | ||
| if (testId) return `[data-testid="${testId}"]`; | ||
| const parts = []; | ||
| let node = el; | ||
| let depth = 0; | ||
| while (node && node !== document.body && depth < 4) { | ||
| let part = node.tagName.toLowerCase(); | ||
| if (node.id) { | ||
| parts.unshift(`#${CSS.escape(node.id)}`); | ||
| break; | ||
| } | ||
| const cls = typeof node.className === "string" ? node.className.trim().split(/\s+/).filter(Boolean)[0] : ""; | ||
| if (cls) part += `.${CSS.escape(cls)}`; | ||
| const parent = node.parentElement; | ||
| const currentTag = node.tagName; | ||
| const currentNode = node; | ||
| if (parent) { | ||
| const sameTag = Array.from(parent.children).filter((c) => c.tagName === currentTag); | ||
| if (sameTag.length > 1) part += `:nth-of-type(${sameTag.indexOf(currentNode) + 1})`; | ||
| } | ||
| parts.unshift(part); | ||
| node = parent; | ||
| depth++; | ||
| } | ||
| return parts.join(" > "); | ||
| } | ||
| function collectInputs(emit) { | ||
| const timers = /* @__PURE__ */ new Map(); | ||
| const handler = (e) => { | ||
| try { | ||
| const t = e.target; | ||
| if (!t || !("value" in t) || isTraceBugElement(t)) return; | ||
| if (t.tagName.toLowerCase() === "select") return; | ||
| const prev = timers.get(t); | ||
| if (prev) clearTimeout(prev); | ||
| timers.set( | ||
| t, | ||
| setTimeout(() => { | ||
| try { | ||
| const tag = t.tagName.toLowerCase(); | ||
| const inputType = t.type || ""; | ||
| const isSensitive = ["password", "credit-card", "ssn"].includes(inputType) || /password|secret|token|ssn|credit/i.test(t.name || t.id || "") || isCustomSensitiveKey(t.name || t.id); | ||
| const element = { | ||
| tag, | ||
| name: t.name || t.id || "", | ||
| type: inputType, | ||
| valueLength: (t.value || "").length, | ||
| value: isSensitive ? "[REDACTED]" : (t.value || "").slice(0, 200), | ||
| placeholder: t.placeholder || "" | ||
| }; | ||
| try { | ||
| element.selector = buildSelector(t); | ||
| } catch (e2) { | ||
| } | ||
| const data = { element }; | ||
| if (inputType === "checkbox" || inputType === "radio") { | ||
| element.checked = t.checked; | ||
| element.value = t.checked ? "checked" : "unchecked"; | ||
| } | ||
| if (inputType === "number" || inputType === "range") { | ||
| element.value = t.value; | ||
| } | ||
| emit("input", data); | ||
| } catch (err) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] Input capture error:", err); | ||
| } | ||
| timers.delete(t); | ||
| }, 300) | ||
| ); | ||
| } catch (err) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] Input capture error:", err); | ||
| } | ||
| }; | ||
| document.addEventListener("input", handler, { capture: true }); | ||
| return () => { | ||
| document.removeEventListener("input", handler, { capture: true }); | ||
| timers.forEach((t) => clearTimeout(t)); | ||
| }; | ||
| } | ||
| function collectSelectChanges(emit) { | ||
| const handler = (e) => { | ||
| try { | ||
| const t = e.target; | ||
| if (!t || t.tagName.toLowerCase() !== "select" || isTraceBugElement(t)) return; | ||
| const selectedOption = t.options[t.selectedIndex]; | ||
| let selector = ""; | ||
| try { | ||
| selector = buildSelector(t); | ||
| } catch (e2) { | ||
| } | ||
| emit("select_change", { | ||
| element: { | ||
| tag: "select", | ||
| name: t.name || t.id || "", | ||
| value: t.value, | ||
| selectedText: selectedOption ? selectedOption.text : "", | ||
| selectedIndex: t.selectedIndex, | ||
| optionCount: t.options.length, | ||
| allOptions: Array.from(t.options).map((o) => o.text).slice(0, 20), | ||
| selector | ||
| } | ||
| }); | ||
| } catch (err) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] Select capture error:", err); | ||
| } | ||
| }; | ||
| document.addEventListener("change", handler, { capture: true }); | ||
| return () => document.removeEventListener("change", handler, { capture: true }); | ||
| } | ||
| function collectFormSubmits(emit) { | ||
| const handler = (e) => { | ||
| try { | ||
| const form = e.target; | ||
| if (!form || form.tagName.toLowerCase() !== "form" || isTraceBugElement(form)) return; | ||
| const formData = {}; | ||
| const elements = form.elements; | ||
| for (let i = 0; i < elements.length; i++) { | ||
| const el = elements[i]; | ||
| if (!el.name) continue; | ||
| const isSensitive = ["password"].includes(el.type) || /password|secret|token|ssn|credit/i.test(el.name) || isCustomSensitiveKey(el.name); | ||
| if (el.type === "submit" || el.type === "button") continue; | ||
| formData[el.name] = isSensitive ? "[REDACTED]" : (el.value || "").slice(0, 200); | ||
| } | ||
| emit("form_submit", { | ||
| form: { id: form.id || "", action: form.action || "", method: form.method || "GET", fieldCount: elements.length, fields: formData } | ||
| }); | ||
| } catch (err) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] Form capture error:", err); | ||
| } | ||
| }; | ||
| document.addEventListener("submit", handler, { capture: true }); | ||
| return () => document.removeEventListener("submit", handler, { capture: true }); | ||
| } | ||
| function collectRouteChanges(emit) { | ||
| let lastPath = window.location.pathname; | ||
| const check = () => { | ||
| const current = window.location.pathname; | ||
| if (current !== lastPath) { | ||
| const from = lastPath; | ||
| lastPath = current; | ||
| emit("route_change", { from, to: current }); | ||
| } | ||
| }; | ||
| window.addEventListener("popstate", check); | ||
| const origPush = history.pushState.bind(history); | ||
| const origReplace = history.replaceState.bind(history); | ||
| history.pushState = function(...args) { | ||
| origPush(...args); | ||
| check(); | ||
| }; | ||
| history.replaceState = function(...args) { | ||
| origReplace(...args); | ||
| check(); | ||
| }; | ||
| return () => { | ||
| window.removeEventListener("popstate", check); | ||
| history.pushState = origPush; | ||
| history.replaceState = origReplace; | ||
| }; | ||
| } | ||
| function collectApiRequests(emit) { | ||
| const originalFetch = window.fetch; | ||
| window.fetch = async function(input, init) { | ||
| var _a; | ||
| let url = ""; | ||
| let method = "GET"; | ||
| try { | ||
| if (typeof input === "string") { | ||
| url = input; | ||
| } else if (input instanceof URL) { | ||
| url = input.href; | ||
| } else if (input && typeof input === "object" && "url" in input) { | ||
| url = input.url; | ||
| method = input.method || "GET"; | ||
| } | ||
| if (init == null ? void 0 : init.method) method = init.method; | ||
| } catch (e) { | ||
| } | ||
| const start = Date.now(); | ||
| try { | ||
| if (url && isInternalUrl(url)) return originalFetch.call(window, input, init); | ||
| } catch (e) { | ||
| } | ||
| const safeUrl = sanitizeUrl2(url).slice(0, 500); | ||
| try { | ||
| const response = await originalFetch.call(window, input, init); | ||
| try { | ||
| emit("api_request", { | ||
| request: { url: safeUrl, method: method.toUpperCase(), statusCode: response.status, durationMs: Date.now() - start } | ||
| }); | ||
| } catch (e) { | ||
| } | ||
| try { | ||
| if (response.status >= 400 || response.status === 0) { | ||
| const clone = response.clone(); | ||
| readResponseBodySafe(clone).then((snippet) => { | ||
| pushNetworkFailure({ | ||
| url: safeUrl, | ||
| method: method.toUpperCase(), | ||
| status: response.status, | ||
| response: snippet, | ||
| timestamp: Date.now() | ||
| }); | ||
| }).catch(() => { | ||
| pushNetworkFailure({ | ||
| url: safeUrl, | ||
| method: method.toUpperCase(), | ||
| status: response.status, | ||
| response: "", | ||
| timestamp: Date.now() | ||
| }); | ||
| }); | ||
| } | ||
| } catch (e) { | ||
| } | ||
| return response; | ||
| } catch (err) { | ||
| try { | ||
| emit("api_request", { | ||
| request: { url: safeUrl, method: method.toUpperCase(), statusCode: 0, durationMs: Date.now() - start } | ||
| }); | ||
| } catch (e) { | ||
| } | ||
| try { | ||
| pushNetworkFailure({ | ||
| url: safeUrl, | ||
| method: method.toUpperCase(), | ||
| status: 0, | ||
| response: ((_a = err == null ? void 0 : err.message) == null ? void 0 : _a.slice(0, RESPONSE_SNIPPET_CHARS)) || "", | ||
| timestamp: Date.now() | ||
| }); | ||
| } catch (e) { | ||
| } | ||
| throw err; | ||
| } | ||
| }; | ||
| return () => { | ||
| window.fetch = originalFetch; | ||
| }; | ||
| } | ||
| function collectXhrRequests(emit) { | ||
| const OrigXHR = window.XMLHttpRequest; | ||
| const origOpen = OrigXHR.prototype.open; | ||
| const origSend = OrigXHR.prototype.send; | ||
| const xhrMeta = /* @__PURE__ */ new WeakMap(); | ||
| OrigXHR.prototype.open = function(method, url, ...rest) { | ||
| try { | ||
| xhrMeta.set(this, { method, url: typeof url === "string" ? url : url.toString() }); | ||
| } catch (e) { | ||
| } | ||
| return origOpen.apply(this, [method, url, ...rest]); | ||
| }; | ||
| OrigXHR.prototype.send = function(body) { | ||
| try { | ||
| const xhr = this; | ||
| const start = Date.now(); | ||
| const meta = xhrMeta.get(xhr); | ||
| const method = (meta == null ? void 0 : meta.method) || "GET"; | ||
| const url = (meta == null ? void 0 : meta.url) || ""; | ||
| if (isInternalUrl(url)) return origSend.call(this, body); | ||
| const safeUrl = sanitizeUrl2(url).slice(0, 500); | ||
| xhr.addEventListener("loadend", function() { | ||
| try { | ||
| emit("api_request", { request: { url: safeUrl, method: method.toUpperCase(), statusCode: xhr.status, durationMs: Date.now() - start } }); | ||
| } catch (e) { | ||
| } | ||
| try { | ||
| if (xhr.status >= 400 || xhr.status === 0) { | ||
| let body2 = ""; | ||
| try { | ||
| const ct = xhr.getResponseHeader && xhr.getResponseHeader("content-type") || ""; | ||
| if (!BINARY_CONTENT_TYPE_RE.test(ct)) { | ||
| body2 = typeof xhr.responseText === "string" ? xhr.responseText : ""; | ||
| } | ||
| } catch (e) { | ||
| } | ||
| pushNetworkFailure({ | ||
| url: safeUrl, | ||
| method: method.toUpperCase(), | ||
| status: xhr.status, | ||
| response: body2.slice(0, RESPONSE_SNIPPET_CHARS), | ||
| timestamp: Date.now() | ||
| }); | ||
| } | ||
| } catch (e) { | ||
| } | ||
| }); | ||
| xhr.addEventListener("error", function() { | ||
| try { | ||
| emit("api_request", { request: { url: safeUrl, method: method.toUpperCase(), statusCode: 0, durationMs: Date.now() - start } }); | ||
| } catch (e) { | ||
| } | ||
| try { | ||
| pushNetworkFailure({ | ||
| url: safeUrl, | ||
| method: method.toUpperCase(), | ||
| status: 0, | ||
| response: "", | ||
| timestamp: Date.now() | ||
| }); | ||
| } catch (e) { | ||
| } | ||
| }); | ||
| } catch (err) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] XHR capture error:", err); | ||
| } | ||
| return origSend.call(this, body); | ||
| }; | ||
| return () => { | ||
| OrigXHR.prototype.open = origOpen; | ||
| OrigXHR.prototype.send = origSend; | ||
| }; | ||
| } | ||
| var _perfSeen = /* @__PURE__ */ new Set(); | ||
| function _emitPerfEntry(emit, e) { | ||
| if (!e || !e.name) return; | ||
| try { | ||
| if (isInternalUrl(e.name)) return; | ||
| } catch (e2) { | ||
| } | ||
| if (typeof e.name === "string" && e.name.indexOf("tracebug") !== -1) return; | ||
| const key = `${e.name}|${Math.round(e.startTime)}`; | ||
| if (_perfSeen.has(key)) return; | ||
| _perfSeen.add(key); | ||
| const navStart = typeof performance.timeOrigin === "number" ? performance.timeOrigin : Date.now(); | ||
| const ext = e; | ||
| const initiator = e.initiatorType || ""; | ||
| const method = (ext.method || "GET").toUpperCase(); | ||
| const status = ext.responseStatus || 0; | ||
| const url = sanitizeUrl2(e.name).slice(0, 500); | ||
| const timestamp = Math.round(navStart + e.startTime); | ||
| const durationMs = Math.round(e.duration || 0); | ||
| try { | ||
| emit("api_request", { | ||
| request: { url, method, statusCode: status, durationMs, initiatorType: initiator }, | ||
| _ts: timestamp | ||
| }); | ||
| } catch (e2) { | ||
| } | ||
| } | ||
| function drainPerformanceNetwork(emit) { | ||
| if (typeof performance === "undefined") return; | ||
| try { | ||
| const entries = performance.getEntriesByType("resource"); | ||
| for (const e of entries) _emitPerfEntry(emit, e); | ||
| } catch (e) { | ||
| } | ||
| } | ||
| function collectPerformanceNetwork(emit) { | ||
| if (typeof performance === "undefined" || typeof PerformanceObserver === "undefined") { | ||
| return () => { | ||
| }; | ||
| } | ||
| drainPerformanceNetwork(emit); | ||
| let observer = null; | ||
| try { | ||
| observer = new PerformanceObserver((list) => { | ||
| for (const e of list.getEntries()) { | ||
| _emitPerfEntry(emit, e); | ||
| } | ||
| }); | ||
| observer.observe({ type: "resource", buffered: false }); | ||
| } catch (e) { | ||
| } | ||
| return () => { | ||
| try { | ||
| observer == null ? void 0 : observer.disconnect(); | ||
| } catch (e) { | ||
| } | ||
| }; | ||
| } | ||
| var CONSOLE_MSG_MAX = 1e4; | ||
| function capMessage(s, max = CONSOLE_MSG_MAX) { | ||
| return s.length > max ? s.slice(0, max) + "\u2026[truncated]" : s; | ||
| } | ||
| function formatConsoleArgs(args) { | ||
| const parts = args.map((a) => { | ||
| if (typeof a === "string") return a; | ||
| try { | ||
| const s = JSON.stringify(a); | ||
| return s === void 0 ? String(a) : s; | ||
| } catch (e) { | ||
| return "[unserializable]"; | ||
| } | ||
| }); | ||
| return capMessage(parts.join(" ")); | ||
| } | ||
| function collectErrors(emit) { | ||
| const prevOnError = window.onerror; | ||
| window.onerror = (msg, source, line, col, error) => { | ||
| try { | ||
| emit("error", { | ||
| error: { | ||
| message: sanitizeTokenShapes(capMessage(typeof msg === "string" ? msg : "Unknown error")), | ||
| stack: (error == null ? void 0 : error.stack) && sanitizeTokenShapes(capMessage(error.stack)), | ||
| source, | ||
| line, | ||
| column: col | ||
| } | ||
| }); | ||
| } catch (e) { | ||
| } | ||
| if (prevOnError) { | ||
| try { | ||
| prevOnError(msg, source, line, col, error); | ||
| } catch (e) { | ||
| } | ||
| } | ||
| }; | ||
| const onRejection = (e) => { | ||
| var _a, _b; | ||
| try { | ||
| emit("unhandled_rejection", { | ||
| error: { | ||
| message: sanitizeTokenShapes(capMessage(((_a = e.reason) == null ? void 0 : _a.message) || String(e.reason))), | ||
| stack: ((_b = e.reason) == null ? void 0 : _b.stack) && sanitizeTokenShapes(capMessage(e.reason.stack)) | ||
| } | ||
| }); | ||
| } catch (e2) { | ||
| } | ||
| }; | ||
| window.addEventListener("unhandledrejection", onRejection); | ||
| return () => { | ||
| window.onerror = prevOnError; | ||
| window.removeEventListener("unhandledrejection", onRejection); | ||
| }; | ||
| } | ||
| function collectConsoleErrors(emit) { | ||
| const origConsoleError = console.error; | ||
| let _insideEmit = false; | ||
| console.error = function(...args) { | ||
| if (_insideEmit) { | ||
| origConsoleError.apply(console, args); | ||
| return; | ||
| } | ||
| _insideEmit = true; | ||
| try { | ||
| emit("console_error", { | ||
| // Token-shape scrub at capture — a token logged to the console must | ||
| // never reach the offline .html export unmasked (the cloud sanitizer | ||
| // only covers the upload path). formatConsoleArgs caps + is circular-safe. | ||
| error: { message: sanitizeTokenShapes(formatConsoleArgs(args)) } | ||
| }); | ||
| } catch (e) { | ||
| } finally { | ||
| _insideEmit = false; | ||
| } | ||
| origConsoleError.apply(console, args); | ||
| }; | ||
| return () => { | ||
| console.error = origConsoleError; | ||
| }; | ||
| } | ||
| var CONSOLE_LEVEL_CAP = 50; | ||
| function wrapConsoleLevel(method, type, emit) { | ||
| const orig = console[method]; | ||
| let _inside = false; | ||
| let _count = 0; | ||
| console[method] = function(...args) { | ||
| const own = typeof args[0] === "string" && args[0].startsWith("[TraceBug]"); | ||
| if (_inside || own || _count >= CONSOLE_LEVEL_CAP) { | ||
| orig.apply(console, args); | ||
| return; | ||
| } | ||
| _inside = true; | ||
| _count++; | ||
| try { | ||
| emit(type, { | ||
| // Same capture-time token scrub as console_error — the offline | ||
| // export path never runs the cloud sanitizer. Capped + circular-safe. | ||
| error: { message: sanitizeTokenShapes(formatConsoleArgs(args)) } | ||
| }); | ||
| } catch (e) { | ||
| } finally { | ||
| _inside = false; | ||
| } | ||
| orig.apply(console, args); | ||
| }; | ||
| return () => { | ||
| console[method] = orig; | ||
| }; | ||
| } | ||
| function collectConsoleWarnings(emit) { | ||
| return wrapConsoleLevel("warn", "console_warn", emit); | ||
| } | ||
| function collectConsoleInfo(emit) { | ||
| return wrapConsoleLevel("info", "console_info", emit); | ||
| } | ||
| function collectConsoleLogs(emit) { | ||
| return wrapConsoleLevel("log", "console_log", emit); | ||
| } | ||
| // src/ui/helpers.ts | ||
| function tbIsolationCss(root) { | ||
| return ` | ||
| ${root} { | ||
| box-sizing: border-box; | ||
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; | ||
| font-size: 14px; font-weight: 400; line-height: 1.5; font-style: normal; | ||
| letter-spacing: normal; text-transform: none; text-align: left; | ||
| text-indent: 0; white-space: normal; word-spacing: normal; text-shadow: none; | ||
| -webkit-font-smoothing: antialiased; | ||
| } | ||
| ${root} *, ${root} *::before, ${root} *::after { box-sizing: border-box; } | ||
| ${root} button, ${root} input, ${root} select, ${root} textarea { | ||
| font-family: inherit; font-size: inherit; letter-spacing: normal; | ||
| text-transform: none; margin: 0; | ||
| } | ||
| ${root} svg { max-width: none; max-height: none; vertical-align: middle; } | ||
| ${root} img { max-width: none; } | ||
| ${root} a { text-decoration: none; } | ||
| `; | ||
| } | ||
| function parseShortcut(shortcut) { | ||
| const parts = (shortcut || "").toLowerCase().split("+").map((s) => s.trim()); | ||
| return { | ||
| mod: parts.includes("ctrl") || parts.includes("control") || parts.includes("cmd") || parts.includes("meta"), | ||
| shift: parts.includes("shift"), | ||
| alt: parts.includes("alt") || parts.includes("option"), | ||
| key: parts[parts.length - 1] || "" | ||
| }; | ||
| } | ||
| function matchesShortcut(e, shortcut) { | ||
| if (!shortcut) return false; | ||
| const s = parseShortcut(shortcut); | ||
| const mod = e.ctrlKey || e.metaKey; | ||
| const key = (e.key || "").toLowerCase(); | ||
| return mod === s.mod && e.shiftKey === s.shift && e.altKey === s.alt && key === s.key; | ||
| } | ||
| function escapeHtml(str) { | ||
| return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """); | ||
| } | ||
| exports.generateSessionId = generateSessionId; exports.getActiveSessionId = getActiveSessionId; exports.setActiveSessionId = setActiveSessionId; exports.clearActiveSessionId = clearActiveSessionId; exports.getActiveCaptureMode = getActiveCaptureMode; exports.setActiveCaptureMode = setActiveCaptureMode; exports.getAllSessions = getAllSessions; exports.getCachedSessions = getCachedSessions; exports.scheduleFlush = scheduleFlush; exports.flushPendingEvents = flushPendingEvents; exports.appendEvent = appendEvent; exports.updateSessionError = updateSessionError; exports.deleteSession = deleteSession; exports.addAnnotation = addAnnotation; exports.saveEnvironment = saveEnvironment; exports.setSessionPriority = setSessionPriority; exports.markSessionSaved = markSessionSaved; exports.clearAllSessions = clearAllSessions; exports.setRedactRules = setRedactRules; exports.isCustomSensitiveKey = isCustomSensitiveKey; exports.isNoiseRequest = isNoiseRequest; exports.isStaticResource = isStaticResource; exports.shortDisplayPath = shortDisplayPath; exports.sanitizeTokenShapes = sanitizeTokenShapes; exports.sanitizeReportForUpload = sanitizeReportForUpload; exports.getNetworkFailures = getNetworkFailures; exports.clearNetworkFailures = clearNetworkFailures; exports.collectClicks = collectClicks; exports.collectInputs = collectInputs; exports.collectSelectChanges = collectSelectChanges; exports.collectFormSubmits = collectFormSubmits; exports.collectRouteChanges = collectRouteChanges; exports.collectApiRequests = collectApiRequests; exports.collectXhrRequests = collectXhrRequests; exports.drainPerformanceNetwork = drainPerformanceNetwork; exports.collectPerformanceNetwork = collectPerformanceNetwork; exports.collectErrors = collectErrors; exports.collectConsoleErrors = collectConsoleErrors; exports.collectConsoleWarnings = collectConsoleWarnings; exports.collectConsoleInfo = collectConsoleInfo; exports.collectConsoleLogs = collectConsoleLogs; exports.tbIsolationCss = tbIsolationCss; exports.matchesShortcut = matchesShortcut; exports.escapeHtml = escapeHtml; | ||
| //# sourceMappingURL=chunk-B5YM4JRB.cjs.map |
| {"version":3,"sources":["d:\\Project\\TraceBug-ai\\dist\\chunk-B5YM4JRB.cjs"],"names":[],"mappings":"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;AACA;AACA;AACA;AACA;AACA,EAAA;AACA;AACA,EAAA;AACA;AACA,EAAA;AACA;AACA,EAAA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,EAAA;AACA;AACA,EAAA;AACA;AACA,EAAA;AACA;AACA,EAAA;AACA;AACA,EAAA;AACA;AACA,EAAA;AACA;AACA,EAAA;AACA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,YAAA;AACA,YAAA;AACA,cAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,cAAA;AACA,cAAA;AACA,YAAA;AACA,YAAA;AACA,cAAA;AACA,YAAA;AACA,YAAA;AACA,UAAA;AACA,YAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,YAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,YAAA;AACA,UAAA;AACA,YAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,YAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,YAAA;AACA,YAAA;AACA,cAAA;AACA,cAAA;AACA,gBAAA;AACA,cAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,YAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA;AACA;AACA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA;AACA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAA;AACA,IAAA;AACA;AACA;AACA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"D:\\Project\\TraceBug-ai\\dist\\chunk-B5YM4JRB.cjs","sourcesContent":[null]} |
| "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } | ||
| var _chunkB5YM4JRBcjs = require('./chunk-B5YM4JRB.cjs'); | ||
| // src/scanner/helpers.ts | ||
| var _issueCounter = 0; | ||
| function makeIssueId(detector) { | ||
| _issueCounter += 1; | ||
| return `${detector}_${Date.now().toString(36)}_${_issueCounter}`; | ||
| } | ||
| function buildSelector(el) { | ||
| if (!el || el.nodeType !== 1) return ""; | ||
| if (el.id) return `#${cssEscape(el.id)}`; | ||
| const testId = el.getAttribute("data-testid") || el.getAttribute("data-test-id"); | ||
| if (testId) return `[data-testid="${cssEscape(testId)}"]`; | ||
| const parts = []; | ||
| let cur = el; | ||
| let depth = 0; | ||
| while (cur && cur.nodeType === 1 && cur.tagName !== "BODY" && depth < 5) { | ||
| const tag = cur.tagName.toLowerCase(); | ||
| const parent = cur.parentElement; | ||
| if (!parent) { | ||
| parts.unshift(tag); | ||
| break; | ||
| } | ||
| const siblings = Array.from(parent.children).filter((c) => c.tagName === cur.tagName); | ||
| if (siblings.length > 1) { | ||
| const idx = siblings.indexOf(cur) + 1; | ||
| parts.unshift(`${tag}:nth-of-type(${idx})`); | ||
| } else { | ||
| parts.unshift(tag); | ||
| } | ||
| cur = parent; | ||
| depth += 1; | ||
| } | ||
| return parts.join(" > "); | ||
| } | ||
| function coerceSeverity(impact) { | ||
| switch ((impact || "").toLowerCase()) { | ||
| case "critical": | ||
| return "critical"; | ||
| case "serious": | ||
| return "serious"; | ||
| case "moderate": | ||
| return "moderate"; | ||
| case "minor": | ||
| return "minor"; | ||
| default: | ||
| return "minor"; | ||
| } | ||
| } | ||
| function cssEscape(value) { | ||
| if (typeof CSS !== "undefined" && typeof CSS.escape === "function") { | ||
| return CSS.escape(value); | ||
| } | ||
| return value.replace(/[^\w-]/g, (ch) => `\\${ch}`); | ||
| } | ||
| // src/scanner/detectors/broken-images.ts | ||
| async function detectBrokenImages() { | ||
| const issues = []; | ||
| const imgs = Array.from(document.images); | ||
| for (const img of imgs) { | ||
| if (img.closest("#tracebug-root")) continue; | ||
| if (!img.complete) continue; | ||
| if (img.naturalWidth > 0) continue; | ||
| const src = img.currentSrc || img.src; | ||
| if (!src) continue; | ||
| issues.push({ | ||
| id: makeIssueId("broken-image"), | ||
| detector: "broken-image", | ||
| severity: "moderate", | ||
| title: `Broken image: ${truncateUrl(src)}`, | ||
| description: `<img> element failed to load. The browser tried to fetch \`${src}\` and got a network error or a non-image response. ${img.alt ? `Alt text: "${img.alt}"` : "No alt text \u2014 also fails accessibility."}`, | ||
| selector: buildSelector(img), | ||
| url: src, | ||
| page: window.location.pathname, | ||
| detectedAt: Date.now() | ||
| }); | ||
| } | ||
| return issues; | ||
| } | ||
| function truncateUrl(url) { | ||
| if (url.length <= 60) return url; | ||
| const tail = url.split("/").pop() || url.slice(-40); | ||
| return `\u2026/${tail}`; | ||
| } | ||
| // src/scanner/detectors/mixed-content.ts | ||
| var ATTR_TARGETS = [ | ||
| { tag: "img", attr: "src" }, | ||
| { tag: "script", attr: "src" }, | ||
| { tag: "iframe", attr: "src" }, | ||
| { tag: "link", attr: "href" }, | ||
| { tag: "audio", attr: "src" }, | ||
| { tag: "video", attr: "src" }, | ||
| { tag: "source", attr: "src" }, | ||
| { tag: "embed", attr: "src" }, | ||
| { tag: "object", attr: "data" } | ||
| ]; | ||
| async function detectMixedContent() { | ||
| if (typeof window === "undefined" || window.location.protocol !== "https:") { | ||
| return []; | ||
| } | ||
| const issues = []; | ||
| for (const { tag, attr } of ATTR_TARGETS) { | ||
| const elements = document.querySelectorAll(`${tag}[${attr}]`); | ||
| for (const el of Array.from(elements)) { | ||
| if (el.closest("#tracebug-root")) continue; | ||
| const value = el.getAttribute(attr) || ""; | ||
| if (!value.startsWith("http://")) continue; | ||
| if (tag === "link") { | ||
| const rel = (el.rel || "").toLowerCase(); | ||
| const fetchableRels = ["stylesheet", "preload", "prefetch", "manifest", "icon", "shortcut icon"]; | ||
| if (!fetchableRels.some((r) => rel.includes(r))) continue; | ||
| } | ||
| issues.push({ | ||
| id: makeIssueId("mixed-content"), | ||
| detector: "mixed-content", | ||
| severity: tag === "script" || tag === "iframe" ? "serious" : "moderate", | ||
| title: `Mixed content: ${tag} loads over HTTP`, | ||
| description: `<${tag}> on an HTTPS page references \`${value}\`. Browsers block or downgrade this \u2014 the resource usually fails to load and breaks the page's secure-context indicator.`, | ||
| selector: buildSelector(el), | ||
| url: value, | ||
| page: window.location.pathname, | ||
| detectedAt: Date.now() | ||
| }); | ||
| } | ||
| } | ||
| return issues; | ||
| } | ||
| // src/fingerprint.ts | ||
| async function computeFingerprint(errorMessage, errorStack, page) { | ||
| const errorType = extractErrorType(errorMessage); | ||
| const topFrames = extractTopFrames(errorStack || "", 3); | ||
| const input = `${errorType}|${topFrames.join("\n")}|${page}`; | ||
| if (typeof crypto !== "undefined" && crypto.subtle && typeof crypto.subtle.digest === "function") { | ||
| try { | ||
| const buf = new TextEncoder().encode(input); | ||
| const hash = await crypto.subtle.digest("SHA-1", buf); | ||
| return bufferToHex(hash).slice(0, 16); | ||
| } catch (e) { | ||
| } | ||
| } | ||
| return djb2(input).toString(16).padStart(8, "0"); | ||
| } | ||
| function extractErrorType(message) { | ||
| const m = message.match(/^([A-Z][a-zA-Z]+Error|Error)\b/); | ||
| return m ? m[1] : "Error"; | ||
| } | ||
| function extractTopFrames(stack, n) { | ||
| const frames = []; | ||
| const lines = stack.split("\n"); | ||
| for (const line of lines) { | ||
| const m = line.match(/(https?:\/\/[^):\s]+|[^():\s]+\.[a-z]+):(\d+):(\d+)/i); | ||
| if (m) { | ||
| const url = m[1]; | ||
| const path = url.includes("://") ? new URL(url, typeof window !== "undefined" ? window.location.origin : "http://localhost").pathname : url; | ||
| frames.push(`${path}:${m[2]}:${m[3]}`); | ||
| if (frames.length >= n) break; | ||
| } | ||
| } | ||
| return frames; | ||
| } | ||
| function bufferToHex(buf) { | ||
| const bytes = new Uint8Array(buf); | ||
| let out = ""; | ||
| for (let i = 0; i < bytes.length; i++) out += bytes[i].toString(16).padStart(2, "0"); | ||
| return out; | ||
| } | ||
| function djb2(str) { | ||
| let hash = 5381; | ||
| for (let i = 0; i < str.length; i++) hash = (hash << 5) + hash + str.charCodeAt(i) | 0; | ||
| return hash >>> 0; | ||
| } | ||
| // src/scanner/detectors/session-data.ts | ||
| var SLOW_API_MS = 2e3; | ||
| var MAX_CONTEXT_SAMPLES = 10; | ||
| async function detectConsoleErrors(session) { | ||
| var _a, _b, _c; | ||
| if (!session) return []; | ||
| const groups = /* @__PURE__ */ new Map(); | ||
| for (let i = 0; i < session.events.length; i++) { | ||
| const e = session.events[i]; | ||
| if (e.type !== "error" && e.type !== "unhandled_rejection" && e.type !== "console_error") continue; | ||
| const message = ((_a = e.data.error) == null ? void 0 : _a.message) || e.data.message || ""; | ||
| if (!message) continue; | ||
| const stack = ((_b = e.data.error) == null ? void 0 : _b.stack) || ""; | ||
| const page = e.page || (typeof window !== "undefined" ? window.location.pathname : ""); | ||
| const fp = await computeFingerprint(message, stack, page); | ||
| const precedingAction = describePrecedingAction(session.events, i); | ||
| const existing = groups.get(fp); | ||
| if (existing) { | ||
| existing.issue.occurrences = (existing.issue.occurrences || 1) + 1; | ||
| existing.issue.lastSeenAt = e.timestamp; | ||
| if (existing.samples.length < MAX_CONTEXT_SAMPLES) { | ||
| existing.samples.push({ timestamp: e.timestamp, precedingAction }); | ||
| } | ||
| continue; | ||
| } | ||
| const firstFrame = ((_c = stack.split("\n").find((l) => l.trim().startsWith("at "))) == null ? void 0 : _c.trim()) || ""; | ||
| const issue = { | ||
| id: makeIssueId("console-error"), | ||
| detector: "console-error", | ||
| severity: classifyErrorSeverity(message), | ||
| title: `JS error: ${message.slice(0, 70)}${message.length > 70 ? "\u2026" : ""}`, | ||
| description: firstFrame ? `${message} | ||
| First frame: ${firstFrame}` : message, | ||
| page, | ||
| detectedAt: e.timestamp, | ||
| fingerprint: fp, | ||
| occurrences: 1, | ||
| firstSeenAt: e.timestamp, | ||
| lastSeenAt: e.timestamp | ||
| }; | ||
| groups.set(fp, { issue, samples: [{ timestamp: e.timestamp, precedingAction }] }); | ||
| } | ||
| const out = []; | ||
| for (const g of groups.values()) { | ||
| const n = g.issue.occurrences || 1; | ||
| if (n > 1) { | ||
| g.issue.title = `${g.issue.title} [\xD7${n}]`; | ||
| g.issue.contextSamples = g.samples; | ||
| } | ||
| out.push(g.issue); | ||
| } | ||
| return out; | ||
| } | ||
| function describePrecedingAction(events, i) { | ||
| var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k; | ||
| for (let j = i - 1; j >= 0; j--) { | ||
| const e = events[j]; | ||
| if (e.type === "click") { | ||
| const t = ((_b = (_a = e.data) == null ? void 0 : _a.element) == null ? void 0 : _b.text) || ((_d = (_c = e.data) == null ? void 0 : _c.element) == null ? void 0 : _d.ariaLabel) || ((_f = (_e = e.data) == null ? void 0 : _e.element) == null ? void 0 : _f.tag) || "element"; | ||
| return `clicked "${String(t).slice(0, 40)}"`; | ||
| } | ||
| if (e.type === "input") { | ||
| const n = ((_h = (_g = e.data) == null ? void 0 : _g.element) == null ? void 0 : _h.name) || ((_j = (_i = e.data) == null ? void 0 : _i.element) == null ? void 0 : _j.id) || "field"; | ||
| return `typed in ${n}`; | ||
| } | ||
| if (e.type === "select_change") return "selected an option"; | ||
| if (e.type === "form_submit") return "submitted a form"; | ||
| if (e.type === "route_change") return `navigated to ${((_k = e.data) == null ? void 0 : _k.to) || "page"}`; | ||
| } | ||
| return void 0; | ||
| } | ||
| async function detectFailedRequests(session) { | ||
| if (!session) return []; | ||
| const issues = []; | ||
| const buffer = _chunkB5YM4JRBcjs.getNetworkFailures.call(void 0, ); | ||
| for (const e of session.events) { | ||
| if (e.type !== "api_request") continue; | ||
| const req = e.data.request; | ||
| if (!req) continue; | ||
| const status = req.statusCode || 0; | ||
| if (status >= 200 && status < 400) continue; | ||
| if (status === 0 && req.method === "HEAD") continue; | ||
| const match = buffer.find( | ||
| (b) => b.url === req.url && b.method === req.method && b.status === status && Math.abs(b.timestamp - e.timestamp) < 5e3 | ||
| ); | ||
| const snippet = (match == null ? void 0 : match.response) ? ` | ||
| Response: ${match.response.slice(0, 160)}` : ""; | ||
| issues.push({ | ||
| id: makeIssueId("failed-request"), | ||
| detector: "failed-request", | ||
| severity: status >= 500 ? "critical" : status === 0 ? "serious" : "moderate", | ||
| title: `${req.method} ${truncatePath(req.url)} \u2192 ${status === 0 ? "Network Error" : status}`, | ||
| description: `Request failed in ${req.durationMs || 0}ms.${snippet}`, | ||
| url: req.url, | ||
| page: e.page || window.location.pathname, | ||
| detectedAt: e.timestamp | ||
| }); | ||
| } | ||
| return issues; | ||
| } | ||
| async function detectSlowApis(session) { | ||
| if (!session) return []; | ||
| const issues = []; | ||
| for (const e of session.events) { | ||
| if (e.type !== "api_request") continue; | ||
| const req = e.data.request; | ||
| if (!req) continue; | ||
| const status = req.statusCode || 0; | ||
| if (status < 200 || status >= 400) continue; | ||
| const duration = req.durationMs || 0; | ||
| if (duration < SLOW_API_MS) continue; | ||
| issues.push({ | ||
| id: makeIssueId("slow-api"), | ||
| detector: "slow-api", | ||
| severity: duration > 5e3 ? "serious" : "moderate", | ||
| title: `Slow API: ${req.method} ${truncatePath(req.url)} (${duration}ms)`, | ||
| description: `This request took ${(duration / 1e3).toFixed(1)}s \u2014 over the ${SLOW_API_MS / 1e3}s threshold. Slow APIs are a common UX complaint and a leading cause of perceived bugs ("the page is frozen").`, | ||
| url: req.url, | ||
| page: e.page || window.location.pathname, | ||
| detectedAt: e.timestamp | ||
| }); | ||
| } | ||
| return issues; | ||
| } | ||
| function truncatePath(url) { | ||
| try { | ||
| const u = new URL(url, window.location.origin); | ||
| const p = u.pathname.length > 50 ? u.pathname.slice(0, 47) + "\u2026" : u.pathname; | ||
| return p; | ||
| } catch (e) { | ||
| return url.length > 50 ? url.slice(0, 47) + "\u2026" : url; | ||
| } | ||
| } | ||
| function classifyErrorSeverity(message) { | ||
| if (/TypeError|ReferenceError|SyntaxError/i.test(message)) return "critical"; | ||
| if (/Network|fetch|failed to/i.test(message)) return "serious"; | ||
| return "moderate"; | ||
| } | ||
| // src/scanner/detectors/a11y.ts | ||
| var _axePromise = null; | ||
| function loadAxe() { | ||
| if (_axePromise) return _axePromise; | ||
| _axePromise = Promise.resolve().then(() => _interopRequireWildcard(require("axe-core"))).then((mod) => mod.default || mod).catch((err) => { | ||
| console.warn("[TraceBug] axe-core failed to load:", err); | ||
| return null; | ||
| }); | ||
| return _axePromise; | ||
| } | ||
| async function detectA11yViolations() { | ||
| const axe = await loadAxe(); | ||
| if (!axe || typeof axe.run !== "function") return []; | ||
| let results; | ||
| try { | ||
| results = await axe.run(document, { | ||
| // Only WCAG-tagged rules — keeps signal-to-noise high. Best-practice | ||
| // rules add ~30% more noise without proportional value for QA. | ||
| runOnly: { type: "tag", values: ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"] }, | ||
| // Skip our own UI so QA isn't told their toolbar fails contrast checks. | ||
| // axe accepts a context with exclude — we pass { exclude: [...] } via | ||
| // the second-argument options-shaped form below to keep types loose. | ||
| resultTypes: ["violations"] | ||
| }); | ||
| } catch (err) { | ||
| console.warn("[TraceBug] axe.run failed:", err); | ||
| return []; | ||
| } | ||
| const issues = []; | ||
| const violations = (results == null ? void 0 : results.violations) || []; | ||
| for (const v of violations) { | ||
| const nodes = v.nodes || []; | ||
| const firstNode = nodes[0]; | ||
| const selector = Array.isArray(firstNode == null ? void 0 : firstNode.target) ? firstNode.target.join(" ") : ""; | ||
| const exampleSnippet = ((firstNode == null ? void 0 : firstNode.html) || "").slice(0, 120); | ||
| const moreSuffix = nodes.length > 1 ? ` (+ ${nodes.length - 1} more element${nodes.length === 2 ? "" : "s"})` : ""; | ||
| issues.push({ | ||
| id: makeIssueId("axe-a11y"), | ||
| detector: "axe-a11y", | ||
| severity: coerceSeverity(v.impact), | ||
| title: `${v.help || v.id}${moreSuffix}`, | ||
| description: `${v.description || v.id} | ||
| First element: \`${exampleSnippet}\``, | ||
| selector: selector || void 0, | ||
| helpUrl: v.helpUrl, | ||
| page: window.location.pathname, | ||
| detectedAt: Date.now() | ||
| }); | ||
| } | ||
| return issues; | ||
| } | ||
| // src/scanner/detectors/frustration.ts | ||
| var RAGE_WINDOW_MS = 1500; | ||
| var RAGE_MIN_CLICKS = 3; | ||
| var DEAD_RESPONSE_WINDOW_MS = 1500; | ||
| var ABANDON_WINDOW_MS = 6e4; | ||
| var ERROR_CORRELATION_WINDOW_MS = 2500; | ||
| async function detectFrustration(session) { | ||
| var _a; | ||
| if (!session) return []; | ||
| const events = session.events; | ||
| if (events.length === 0) return []; | ||
| const issues = []; | ||
| const page = ((_a = session.events[0]) == null ? void 0 : _a.page) || window.location.pathname; | ||
| issues.push(...detectRageClicks(events, page)); | ||
| issues.push(...detectDeadClicks(events, page)); | ||
| issues.push(...detectFormAbandonment(events, page)); | ||
| issues.push(...detectErrorCorrelated(events, page)); | ||
| return issues; | ||
| } | ||
| function detectRageClicks(events, page) { | ||
| var _a, _b, _c, _d, _e, _f, _g, _h; | ||
| const out = []; | ||
| const seenGroups = /* @__PURE__ */ new Set(); | ||
| for (let i = 0; i < events.length; i++) { | ||
| const e = events[i]; | ||
| if (e.type !== "click") continue; | ||
| const sel = ((_b = (_a = e.data) == null ? void 0 : _a.element) == null ? void 0 : _b.selector) || ((_d = (_c = e.data) == null ? void 0 : _c.element) == null ? void 0 : _d.testId) || ""; | ||
| if (!sel) continue; | ||
| const cluster = [e]; | ||
| let j = i + 1; | ||
| while (j < events.length) { | ||
| const next = events[j]; | ||
| if (next.timestamp - e.timestamp > RAGE_WINDOW_MS) break; | ||
| if (isResponseEvent(next)) break; | ||
| if (next.type === "click") { | ||
| const nextSel = ((_f = (_e = next.data) == null ? void 0 : _e.element) == null ? void 0 : _f.selector) || ((_h = (_g = next.data) == null ? void 0 : _g.element) == null ? void 0 : _h.testId) || ""; | ||
| if (nextSel === sel) cluster.push(next); | ||
| } | ||
| j++; | ||
| } | ||
| if (cluster.length >= RAGE_MIN_CLICKS) { | ||
| const key = `${sel}@${e.timestamp}`; | ||
| if (seenGroups.has(key)) continue; | ||
| seenGroups.add(key); | ||
| const label = clickLabel(cluster[0]); | ||
| out.push({ | ||
| id: makeIssueId("frustration-rage"), | ||
| detector: "frustration-rage", | ||
| severity: "serious", | ||
| title: `Rage clicks on ${label} (${cluster.length}\xD7 in ${Math.round(cluster[cluster.length - 1].timestamp - cluster[0].timestamp)}ms)`, | ||
| description: `User clicked the same element ${cluster.length} times within ${RAGE_WINDOW_MS}ms with no observable response (no API call, navigation, or DOM update). The element either doesn't respond to clicks or feels broken.`, | ||
| selector: sel, | ||
| page, | ||
| detectedAt: cluster[0].timestamp, | ||
| firstSeenAt: cluster[0].timestamp, | ||
| lastSeenAt: cluster[cluster.length - 1].timestamp, | ||
| occurrences: cluster.length | ||
| }); | ||
| i = j - 1; | ||
| } | ||
| } | ||
| return out; | ||
| } | ||
| function detectDeadClicks(events, page) { | ||
| var _a, _b; | ||
| const out = []; | ||
| const MAX = 5; | ||
| for (let i = 0; i < events.length && out.length < MAX; i++) { | ||
| const e = events[i]; | ||
| if (e.type !== "click") continue; | ||
| let responsive = false; | ||
| for (let j = i + 1; j < events.length; j++) { | ||
| const next = events[j]; | ||
| if (next.timestamp - e.timestamp > DEAD_RESPONSE_WINDOW_MS) break; | ||
| if (isResponseEvent(next)) { | ||
| responsive = true; | ||
| break; | ||
| } | ||
| } | ||
| if (responsive) continue; | ||
| const sel = ((_b = (_a = e.data) == null ? void 0 : _a.element) == null ? void 0 : _b.selector) || ""; | ||
| const label = clickLabel(e); | ||
| out.push({ | ||
| id: makeIssueId("frustration-dead"), | ||
| detector: "frustration-dead", | ||
| severity: "moderate", | ||
| title: `Dead click on ${label}`, | ||
| description: `Clicked but nothing happened within ${DEAD_RESPONSE_WINDOW_MS}ms (no API call, navigation, or DOM input). The element may have an unbound handler, a swallowed event, or be visually clickable but disabled.`, | ||
| selector: sel, | ||
| page: e.page || page, | ||
| detectedAt: e.timestamp | ||
| }); | ||
| } | ||
| return out; | ||
| } | ||
| function detectFormAbandonment(events, _page) { | ||
| var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j; | ||
| const out = []; | ||
| const formActivity = {}; | ||
| for (const e of events) { | ||
| if (e.type === "input") { | ||
| const formId = ((_b = (_a = e.data) == null ? void 0 : _a.element) == null ? void 0 : _b.formId) || ((_d = (_c = e.data) == null ? void 0 : _c.element) == null ? void 0 : _d.formAction) || "_default"; | ||
| if (!formActivity[formId]) { | ||
| formActivity[formId] = { firstInputAt: e.timestamp, fieldsSeen: /* @__PURE__ */ new Set(), lastInputAt: e.timestamp, page: e.page }; | ||
| } | ||
| const name = ((_f = (_e = e.data) == null ? void 0 : _e.element) == null ? void 0 : _f.name) || ((_h = (_g = e.data) == null ? void 0 : _g.element) == null ? void 0 : _h.id) || "field"; | ||
| formActivity[formId].fieldsSeen.add(name); | ||
| formActivity[formId].lastInputAt = e.timestamp; | ||
| } else if (e.type === "form_submit") { | ||
| const formId = ((_j = (_i = e.data) == null ? void 0 : _i.form) == null ? void 0 : _j.id) || "_default"; | ||
| delete formActivity[formId]; | ||
| } else if (e.type === "route_change") { | ||
| for (const formId of Object.keys(formActivity)) { | ||
| const a = formActivity[formId]; | ||
| if (e.timestamp - a.lastInputAt > ABANDON_WINDOW_MS) continue; | ||
| if (a.fieldsSeen.size === 0) continue; | ||
| out.push({ | ||
| id: makeIssueId("frustration-abandon"), | ||
| detector: "frustration-abandon", | ||
| severity: "moderate", | ||
| title: `Form abandoned on ${a.page} (${a.fieldsSeen.size} field${a.fieldsSeen.size === 1 ? "" : "s"} filled)`, | ||
| description: `User typed into ${a.fieldsSeen.size} field${a.fieldsSeen.size === 1 ? "" : "s"} (${Array.from(a.fieldsSeen).slice(0, 5).join(", ")}) and then navigated away without submitting. Likely a UX problem: the submit button is unclear, the form requires too much info, or it's failing silently.`, | ||
| page: a.page, | ||
| detectedAt: a.lastInputAt, | ||
| firstSeenAt: a.firstInputAt, | ||
| lastSeenAt: a.lastInputAt | ||
| }); | ||
| delete formActivity[formId]; | ||
| } | ||
| } | ||
| } | ||
| return out; | ||
| } | ||
| function detectErrorCorrelated(events, page) { | ||
| var _a, _b, _c, _d, _e, _f; | ||
| const out = []; | ||
| const seen = /* @__PURE__ */ new Set(); | ||
| for (let i = 0; i < events.length; i++) { | ||
| const e = events[i]; | ||
| const isError = e.type === "error" || e.type === "unhandled_rejection" || e.type === "console_error"; | ||
| if (!isError) continue; | ||
| let click = null; | ||
| for (let j = i - 1; j >= 0; j--) { | ||
| const prev = events[j]; | ||
| if (e.timestamp - prev.timestamp > ERROR_CORRELATION_WINDOW_MS) break; | ||
| if (prev.type === "click") { | ||
| click = prev; | ||
| break; | ||
| } | ||
| } | ||
| if (!click) continue; | ||
| const errMsg = ((_b = (_a = e.data) == null ? void 0 : _a.error) == null ? void 0 : _b.message) || ""; | ||
| if (!errMsg) continue; | ||
| const key = `${errMsg}::${((_d = (_c = click.data) == null ? void 0 : _c.element) == null ? void 0 : _d.selector) || ""}`; | ||
| if (seen.has(key)) continue; | ||
| seen.add(key); | ||
| const label = clickLabel(click); | ||
| const truncMsg = errMsg.length > 60 ? errMsg.slice(0, 57) + "\u2026" : errMsg; | ||
| const delta = Math.round(e.timestamp - click.timestamp); | ||
| out.push({ | ||
| id: makeIssueId("frustration-error-correlated"), | ||
| detector: "frustration-error-correlated", | ||
| severity: "critical", | ||
| title: `Click on ${label} triggered: ${truncMsg}`, | ||
| description: `An error fired ${delta}ms after the user clicked ${label}. This is almost certainly the offending interaction \u2014 the click handler threw, or its async path failed.`, | ||
| selector: (_f = (_e = click.data) == null ? void 0 : _e.element) == null ? void 0 : _f.selector, | ||
| page: e.page || page, | ||
| detectedAt: e.timestamp | ||
| }); | ||
| } | ||
| return out; | ||
| } | ||
| function isResponseEvent(e) { | ||
| return e.type === "api_request" || e.type === "route_change" || e.type === "input" || e.type === "form_submit" || e.type === "select_change"; | ||
| } | ||
| function clickLabel(e) { | ||
| var _a; | ||
| const el = (_a = e.data) == null ? void 0 : _a.element; | ||
| const raw = (el == null ? void 0 : el.text) || (el == null ? void 0 : el.ariaLabel) || (el == null ? void 0 : el.testId) || (el == null ? void 0 : el.id) || (el == null ? void 0 : el.tag) || "element"; | ||
| const trimmed = String(raw).replace(/\s+/g, " ").trim(); | ||
| return trimmed.length > 40 ? `"${trimmed.slice(0, 37)}\u2026"` : `"${trimmed}"`; | ||
| } | ||
| // src/scanner/index.ts | ||
| var _issues = []; | ||
| var _scanInFlight = null; | ||
| var _lastScanAt = 0; | ||
| var SEVERITY_ORDER = { | ||
| critical: 0, | ||
| serious: 1, | ||
| moderate: 2, | ||
| minor: 3 | ||
| }; | ||
| async function scan() { | ||
| if (_scanInFlight) { | ||
| const issues = await _scanInFlight; | ||
| return { issues, durationMs: 0, scannedAt: _lastScanAt }; | ||
| } | ||
| const startedAt = Date.now(); | ||
| const sessions = _chunkB5YM4JRBcjs.getAllSessions.call(void 0, ).sort((a, b) => b.updatedAt - a.updatedAt); | ||
| const session = sessions[0] || null; | ||
| const safeRun = (p) => p.catch((err) => { | ||
| console.warn("[TraceBug] Detector failed:", err); | ||
| return []; | ||
| }); | ||
| _scanInFlight = Promise.all([ | ||
| safeRun(detectBrokenImages()), | ||
| safeRun(detectMixedContent()), | ||
| safeRun(detectConsoleErrors(session)), | ||
| safeRun(detectFailedRequests(session)), | ||
| safeRun(detectSlowApis(session)), | ||
| safeRun(detectA11yViolations()), | ||
| safeRun(detectFrustration(session)) | ||
| ]).then((results) => { | ||
| const all = [].concat(...results); | ||
| all.sort((a, b) => { | ||
| const sev = SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity]; | ||
| if (sev !== 0) return sev; | ||
| return a.detectedAt - b.detectedAt; | ||
| }); | ||
| _issues = all; | ||
| return all; | ||
| }); | ||
| try { | ||
| const issues = await _scanInFlight; | ||
| _lastScanAt = Date.now(); | ||
| return { issues, durationMs: _lastScanAt - startedAt, scannedAt: _lastScanAt }; | ||
| } finally { | ||
| _scanInFlight = null; | ||
| } | ||
| } | ||
| function getIssues(options) { | ||
| var _a; | ||
| const includeDismissed = (_a = options == null ? void 0 : options.includeDismissed) != null ? _a : false; | ||
| return includeDismissed ? _issues.slice() : _issues.filter((i) => !i.dismissed); | ||
| } | ||
| function dismissIssue(id) { | ||
| const issue = _issues.find((i) => i.id === id); | ||
| if (!issue) return false; | ||
| issue.dismissed = true; | ||
| return true; | ||
| } | ||
| function undismissIssue(id) { | ||
| const issue = _issues.find((i) => i.id === id); | ||
| if (!issue) return false; | ||
| issue.dismissed = false; | ||
| return true; | ||
| } | ||
| function clearIssues() { | ||
| _issues = []; | ||
| _lastScanAt = 0; | ||
| } | ||
| function getIssueCountsByDetector() { | ||
| const counts = { | ||
| "axe-a11y": 0, | ||
| "broken-image": 0, | ||
| "mixed-content": 0, | ||
| "console-error": 0, | ||
| "slow-api": 0, | ||
| "failed-request": 0, | ||
| "frustration-rage": 0, | ||
| "frustration-dead": 0, | ||
| "frustration-abandon": 0, | ||
| "frustration-error-correlated": 0 | ||
| }; | ||
| for (const i of _issues) { | ||
| if (i.dismissed) continue; | ||
| counts[i.detector] = (counts[i.detector] || 0) + 1; | ||
| } | ||
| return counts; | ||
| } | ||
| function getIssueCountsBySeverity() { | ||
| const counts = { | ||
| critical: 0, | ||
| serious: 0, | ||
| moderate: 0, | ||
| minor: 0 | ||
| }; | ||
| for (const i of _issues) { | ||
| if (i.dismissed) continue; | ||
| counts[i.severity] += 1; | ||
| } | ||
| return counts; | ||
| } | ||
| function getIssueById(id) { | ||
| return _issues.find((i) => i.id === id) || null; | ||
| } | ||
| exports.scan = scan; exports.getIssues = getIssues; exports.dismissIssue = dismissIssue; exports.undismissIssue = undismissIssue; exports.clearIssues = clearIssues; exports.getIssueCountsByDetector = getIssueCountsByDetector; exports.getIssueCountsBySeverity = getIssueCountsBySeverity; exports.getIssueById = getIssueById; | ||
| //# sourceMappingURL=chunk-HW3D3VMG.cjs.map |
| {"version":3,"sources":["d:\\Project\\TraceBug-ai\\dist\\chunk-HW3D3VMG.cjs"],"names":[],"mappings":"AAAA;AACE;AACA;AACF,wDAA6B;AAC7B;AACA;AACA,IAAI,cAAc,EAAE,CAAC;AACrB,SAAS,WAAW,CAAC,QAAQ,EAAE;AAC/B,EAAE,cAAc,GAAG,CAAC;AACpB,EAAE,OAAO,CAAC,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA;AACA,aAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA;AACA,UAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA;AACA;AACA,MAAA;AACA;AACA;AACA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA;AACA,iBAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"D:\\Project\\TraceBug-ai\\dist\\chunk-HW3D3VMG.cjs","sourcesContent":[null]} |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
| import { | ||
| getAllSessions, | ||
| getNetworkFailures | ||
| } from "./chunk-2ZJIB656.js"; | ||
| // src/scanner/helpers.ts | ||
| var _issueCounter = 0; | ||
| function makeIssueId(detector) { | ||
| _issueCounter += 1; | ||
| return `${detector}_${Date.now().toString(36)}_${_issueCounter}`; | ||
| } | ||
| function buildSelector(el) { | ||
| if (!el || el.nodeType !== 1) return ""; | ||
| if (el.id) return `#${cssEscape(el.id)}`; | ||
| const testId = el.getAttribute("data-testid") || el.getAttribute("data-test-id"); | ||
| if (testId) return `[data-testid="${cssEscape(testId)}"]`; | ||
| const parts = []; | ||
| let cur = el; | ||
| let depth = 0; | ||
| while (cur && cur.nodeType === 1 && cur.tagName !== "BODY" && depth < 5) { | ||
| const tag = cur.tagName.toLowerCase(); | ||
| const parent = cur.parentElement; | ||
| if (!parent) { | ||
| parts.unshift(tag); | ||
| break; | ||
| } | ||
| const siblings = Array.from(parent.children).filter((c) => c.tagName === cur.tagName); | ||
| if (siblings.length > 1) { | ||
| const idx = siblings.indexOf(cur) + 1; | ||
| parts.unshift(`${tag}:nth-of-type(${idx})`); | ||
| } else { | ||
| parts.unshift(tag); | ||
| } | ||
| cur = parent; | ||
| depth += 1; | ||
| } | ||
| return parts.join(" > "); | ||
| } | ||
| function coerceSeverity(impact) { | ||
| switch ((impact || "").toLowerCase()) { | ||
| case "critical": | ||
| return "critical"; | ||
| case "serious": | ||
| return "serious"; | ||
| case "moderate": | ||
| return "moderate"; | ||
| case "minor": | ||
| return "minor"; | ||
| default: | ||
| return "minor"; | ||
| } | ||
| } | ||
| function cssEscape(value) { | ||
| if (typeof CSS !== "undefined" && typeof CSS.escape === "function") { | ||
| return CSS.escape(value); | ||
| } | ||
| return value.replace(/[^\w-]/g, (ch) => `\\${ch}`); | ||
| } | ||
| // src/scanner/detectors/broken-images.ts | ||
| async function detectBrokenImages() { | ||
| const issues = []; | ||
| const imgs = Array.from(document.images); | ||
| for (const img of imgs) { | ||
| if (img.closest("#tracebug-root")) continue; | ||
| if (!img.complete) continue; | ||
| if (img.naturalWidth > 0) continue; | ||
| const src = img.currentSrc || img.src; | ||
| if (!src) continue; | ||
| issues.push({ | ||
| id: makeIssueId("broken-image"), | ||
| detector: "broken-image", | ||
| severity: "moderate", | ||
| title: `Broken image: ${truncateUrl(src)}`, | ||
| description: `<img> element failed to load. The browser tried to fetch \`${src}\` and got a network error or a non-image response. ${img.alt ? `Alt text: "${img.alt}"` : "No alt text \u2014 also fails accessibility."}`, | ||
| selector: buildSelector(img), | ||
| url: src, | ||
| page: window.location.pathname, | ||
| detectedAt: Date.now() | ||
| }); | ||
| } | ||
| return issues; | ||
| } | ||
| function truncateUrl(url) { | ||
| if (url.length <= 60) return url; | ||
| const tail = url.split("/").pop() || url.slice(-40); | ||
| return `\u2026/${tail}`; | ||
| } | ||
| // src/scanner/detectors/mixed-content.ts | ||
| var ATTR_TARGETS = [ | ||
| { tag: "img", attr: "src" }, | ||
| { tag: "script", attr: "src" }, | ||
| { tag: "iframe", attr: "src" }, | ||
| { tag: "link", attr: "href" }, | ||
| { tag: "audio", attr: "src" }, | ||
| { tag: "video", attr: "src" }, | ||
| { tag: "source", attr: "src" }, | ||
| { tag: "embed", attr: "src" }, | ||
| { tag: "object", attr: "data" } | ||
| ]; | ||
| async function detectMixedContent() { | ||
| if (typeof window === "undefined" || window.location.protocol !== "https:") { | ||
| return []; | ||
| } | ||
| const issues = []; | ||
| for (const { tag, attr } of ATTR_TARGETS) { | ||
| const elements = document.querySelectorAll(`${tag}[${attr}]`); | ||
| for (const el of Array.from(elements)) { | ||
| if (el.closest("#tracebug-root")) continue; | ||
| const value = el.getAttribute(attr) || ""; | ||
| if (!value.startsWith("http://")) continue; | ||
| if (tag === "link") { | ||
| const rel = (el.rel || "").toLowerCase(); | ||
| const fetchableRels = ["stylesheet", "preload", "prefetch", "manifest", "icon", "shortcut icon"]; | ||
| if (!fetchableRels.some((r) => rel.includes(r))) continue; | ||
| } | ||
| issues.push({ | ||
| id: makeIssueId("mixed-content"), | ||
| detector: "mixed-content", | ||
| severity: tag === "script" || tag === "iframe" ? "serious" : "moderate", | ||
| title: `Mixed content: ${tag} loads over HTTP`, | ||
| description: `<${tag}> on an HTTPS page references \`${value}\`. Browsers block or downgrade this \u2014 the resource usually fails to load and breaks the page's secure-context indicator.`, | ||
| selector: buildSelector(el), | ||
| url: value, | ||
| page: window.location.pathname, | ||
| detectedAt: Date.now() | ||
| }); | ||
| } | ||
| } | ||
| return issues; | ||
| } | ||
| // src/fingerprint.ts | ||
| async function computeFingerprint(errorMessage, errorStack, page) { | ||
| const errorType = extractErrorType(errorMessage); | ||
| const topFrames = extractTopFrames(errorStack || "", 3); | ||
| const input = `${errorType}|${topFrames.join("\n")}|${page}`; | ||
| if (typeof crypto !== "undefined" && crypto.subtle && typeof crypto.subtle.digest === "function") { | ||
| try { | ||
| const buf = new TextEncoder().encode(input); | ||
| const hash = await crypto.subtle.digest("SHA-1", buf); | ||
| return bufferToHex(hash).slice(0, 16); | ||
| } catch (e) { | ||
| } | ||
| } | ||
| return djb2(input).toString(16).padStart(8, "0"); | ||
| } | ||
| function extractErrorType(message) { | ||
| const m = message.match(/^([A-Z][a-zA-Z]+Error|Error)\b/); | ||
| return m ? m[1] : "Error"; | ||
| } | ||
| function extractTopFrames(stack, n) { | ||
| const frames = []; | ||
| const lines = stack.split("\n"); | ||
| for (const line of lines) { | ||
| const m = line.match(/(https?:\/\/[^):\s]+|[^():\s]+\.[a-z]+):(\d+):(\d+)/i); | ||
| if (m) { | ||
| const url = m[1]; | ||
| const path = url.includes("://") ? new URL(url, typeof window !== "undefined" ? window.location.origin : "http://localhost").pathname : url; | ||
| frames.push(`${path}:${m[2]}:${m[3]}`); | ||
| if (frames.length >= n) break; | ||
| } | ||
| } | ||
| return frames; | ||
| } | ||
| function bufferToHex(buf) { | ||
| const bytes = new Uint8Array(buf); | ||
| let out = ""; | ||
| for (let i = 0; i < bytes.length; i++) out += bytes[i].toString(16).padStart(2, "0"); | ||
| return out; | ||
| } | ||
| function djb2(str) { | ||
| let hash = 5381; | ||
| for (let i = 0; i < str.length; i++) hash = (hash << 5) + hash + str.charCodeAt(i) | 0; | ||
| return hash >>> 0; | ||
| } | ||
| // src/scanner/detectors/session-data.ts | ||
| var SLOW_API_MS = 2e3; | ||
| var MAX_CONTEXT_SAMPLES = 10; | ||
| async function detectConsoleErrors(session) { | ||
| var _a, _b, _c; | ||
| if (!session) return []; | ||
| const groups = /* @__PURE__ */ new Map(); | ||
| for (let i = 0; i < session.events.length; i++) { | ||
| const e = session.events[i]; | ||
| if (e.type !== "error" && e.type !== "unhandled_rejection" && e.type !== "console_error") continue; | ||
| const message = ((_a = e.data.error) == null ? void 0 : _a.message) || e.data.message || ""; | ||
| if (!message) continue; | ||
| const stack = ((_b = e.data.error) == null ? void 0 : _b.stack) || ""; | ||
| const page = e.page || (typeof window !== "undefined" ? window.location.pathname : ""); | ||
| const fp = await computeFingerprint(message, stack, page); | ||
| const precedingAction = describePrecedingAction(session.events, i); | ||
| const existing = groups.get(fp); | ||
| if (existing) { | ||
| existing.issue.occurrences = (existing.issue.occurrences || 1) + 1; | ||
| existing.issue.lastSeenAt = e.timestamp; | ||
| if (existing.samples.length < MAX_CONTEXT_SAMPLES) { | ||
| existing.samples.push({ timestamp: e.timestamp, precedingAction }); | ||
| } | ||
| continue; | ||
| } | ||
| const firstFrame = ((_c = stack.split("\n").find((l) => l.trim().startsWith("at "))) == null ? void 0 : _c.trim()) || ""; | ||
| const issue = { | ||
| id: makeIssueId("console-error"), | ||
| detector: "console-error", | ||
| severity: classifyErrorSeverity(message), | ||
| title: `JS error: ${message.slice(0, 70)}${message.length > 70 ? "\u2026" : ""}`, | ||
| description: firstFrame ? `${message} | ||
| First frame: ${firstFrame}` : message, | ||
| page, | ||
| detectedAt: e.timestamp, | ||
| fingerprint: fp, | ||
| occurrences: 1, | ||
| firstSeenAt: e.timestamp, | ||
| lastSeenAt: e.timestamp | ||
| }; | ||
| groups.set(fp, { issue, samples: [{ timestamp: e.timestamp, precedingAction }] }); | ||
| } | ||
| const out = []; | ||
| for (const g of groups.values()) { | ||
| const n = g.issue.occurrences || 1; | ||
| if (n > 1) { | ||
| g.issue.title = `${g.issue.title} [\xD7${n}]`; | ||
| g.issue.contextSamples = g.samples; | ||
| } | ||
| out.push(g.issue); | ||
| } | ||
| return out; | ||
| } | ||
| function describePrecedingAction(events, i) { | ||
| var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k; | ||
| for (let j = i - 1; j >= 0; j--) { | ||
| const e = events[j]; | ||
| if (e.type === "click") { | ||
| const t = ((_b = (_a = e.data) == null ? void 0 : _a.element) == null ? void 0 : _b.text) || ((_d = (_c = e.data) == null ? void 0 : _c.element) == null ? void 0 : _d.ariaLabel) || ((_f = (_e = e.data) == null ? void 0 : _e.element) == null ? void 0 : _f.tag) || "element"; | ||
| return `clicked "${String(t).slice(0, 40)}"`; | ||
| } | ||
| if (e.type === "input") { | ||
| const n = ((_h = (_g = e.data) == null ? void 0 : _g.element) == null ? void 0 : _h.name) || ((_j = (_i = e.data) == null ? void 0 : _i.element) == null ? void 0 : _j.id) || "field"; | ||
| return `typed in ${n}`; | ||
| } | ||
| if (e.type === "select_change") return "selected an option"; | ||
| if (e.type === "form_submit") return "submitted a form"; | ||
| if (e.type === "route_change") return `navigated to ${((_k = e.data) == null ? void 0 : _k.to) || "page"}`; | ||
| } | ||
| return void 0; | ||
| } | ||
| async function detectFailedRequests(session) { | ||
| if (!session) return []; | ||
| const issues = []; | ||
| const buffer = getNetworkFailures(); | ||
| for (const e of session.events) { | ||
| if (e.type !== "api_request") continue; | ||
| const req = e.data.request; | ||
| if (!req) continue; | ||
| const status = req.statusCode || 0; | ||
| if (status >= 200 && status < 400) continue; | ||
| if (status === 0 && req.method === "HEAD") continue; | ||
| const match = buffer.find( | ||
| (b) => b.url === req.url && b.method === req.method && b.status === status && Math.abs(b.timestamp - e.timestamp) < 5e3 | ||
| ); | ||
| const snippet = (match == null ? void 0 : match.response) ? ` | ||
| Response: ${match.response.slice(0, 160)}` : ""; | ||
| issues.push({ | ||
| id: makeIssueId("failed-request"), | ||
| detector: "failed-request", | ||
| severity: status >= 500 ? "critical" : status === 0 ? "serious" : "moderate", | ||
| title: `${req.method} ${truncatePath(req.url)} \u2192 ${status === 0 ? "Network Error" : status}`, | ||
| description: `Request failed in ${req.durationMs || 0}ms.${snippet}`, | ||
| url: req.url, | ||
| page: e.page || window.location.pathname, | ||
| detectedAt: e.timestamp | ||
| }); | ||
| } | ||
| return issues; | ||
| } | ||
| async function detectSlowApis(session) { | ||
| if (!session) return []; | ||
| const issues = []; | ||
| for (const e of session.events) { | ||
| if (e.type !== "api_request") continue; | ||
| const req = e.data.request; | ||
| if (!req) continue; | ||
| const status = req.statusCode || 0; | ||
| if (status < 200 || status >= 400) continue; | ||
| const duration = req.durationMs || 0; | ||
| if (duration < SLOW_API_MS) continue; | ||
| issues.push({ | ||
| id: makeIssueId("slow-api"), | ||
| detector: "slow-api", | ||
| severity: duration > 5e3 ? "serious" : "moderate", | ||
| title: `Slow API: ${req.method} ${truncatePath(req.url)} (${duration}ms)`, | ||
| description: `This request took ${(duration / 1e3).toFixed(1)}s \u2014 over the ${SLOW_API_MS / 1e3}s threshold. Slow APIs are a common UX complaint and a leading cause of perceived bugs ("the page is frozen").`, | ||
| url: req.url, | ||
| page: e.page || window.location.pathname, | ||
| detectedAt: e.timestamp | ||
| }); | ||
| } | ||
| return issues; | ||
| } | ||
| function truncatePath(url) { | ||
| try { | ||
| const u = new URL(url, window.location.origin); | ||
| const p = u.pathname.length > 50 ? u.pathname.slice(0, 47) + "\u2026" : u.pathname; | ||
| return p; | ||
| } catch (e) { | ||
| return url.length > 50 ? url.slice(0, 47) + "\u2026" : url; | ||
| } | ||
| } | ||
| function classifyErrorSeverity(message) { | ||
| if (/TypeError|ReferenceError|SyntaxError/i.test(message)) return "critical"; | ||
| if (/Network|fetch|failed to/i.test(message)) return "serious"; | ||
| return "moderate"; | ||
| } | ||
| // src/scanner/detectors/a11y.ts | ||
| var _axePromise = null; | ||
| function loadAxe() { | ||
| if (_axePromise) return _axePromise; | ||
| _axePromise = import("axe-core").then((mod) => mod.default || mod).catch((err) => { | ||
| console.warn("[TraceBug] axe-core failed to load:", err); | ||
| return null; | ||
| }); | ||
| return _axePromise; | ||
| } | ||
| async function detectA11yViolations() { | ||
| const axe = await loadAxe(); | ||
| if (!axe || typeof axe.run !== "function") return []; | ||
| let results; | ||
| try { | ||
| results = await axe.run(document, { | ||
| // Only WCAG-tagged rules — keeps signal-to-noise high. Best-practice | ||
| // rules add ~30% more noise without proportional value for QA. | ||
| runOnly: { type: "tag", values: ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"] }, | ||
| // Skip our own UI so QA isn't told their toolbar fails contrast checks. | ||
| // axe accepts a context with exclude — we pass { exclude: [...] } via | ||
| // the second-argument options-shaped form below to keep types loose. | ||
| resultTypes: ["violations"] | ||
| }); | ||
| } catch (err) { | ||
| console.warn("[TraceBug] axe.run failed:", err); | ||
| return []; | ||
| } | ||
| const issues = []; | ||
| const violations = (results == null ? void 0 : results.violations) || []; | ||
| for (const v of violations) { | ||
| const nodes = v.nodes || []; | ||
| const firstNode = nodes[0]; | ||
| const selector = Array.isArray(firstNode == null ? void 0 : firstNode.target) ? firstNode.target.join(" ") : ""; | ||
| const exampleSnippet = ((firstNode == null ? void 0 : firstNode.html) || "").slice(0, 120); | ||
| const moreSuffix = nodes.length > 1 ? ` (+ ${nodes.length - 1} more element${nodes.length === 2 ? "" : "s"})` : ""; | ||
| issues.push({ | ||
| id: makeIssueId("axe-a11y"), | ||
| detector: "axe-a11y", | ||
| severity: coerceSeverity(v.impact), | ||
| title: `${v.help || v.id}${moreSuffix}`, | ||
| description: `${v.description || v.id} | ||
| First element: \`${exampleSnippet}\``, | ||
| selector: selector || void 0, | ||
| helpUrl: v.helpUrl, | ||
| page: window.location.pathname, | ||
| detectedAt: Date.now() | ||
| }); | ||
| } | ||
| return issues; | ||
| } | ||
| // src/scanner/detectors/frustration.ts | ||
| var RAGE_WINDOW_MS = 1500; | ||
| var RAGE_MIN_CLICKS = 3; | ||
| var DEAD_RESPONSE_WINDOW_MS = 1500; | ||
| var ABANDON_WINDOW_MS = 6e4; | ||
| var ERROR_CORRELATION_WINDOW_MS = 2500; | ||
| async function detectFrustration(session) { | ||
| var _a; | ||
| if (!session) return []; | ||
| const events = session.events; | ||
| if (events.length === 0) return []; | ||
| const issues = []; | ||
| const page = ((_a = session.events[0]) == null ? void 0 : _a.page) || window.location.pathname; | ||
| issues.push(...detectRageClicks(events, page)); | ||
| issues.push(...detectDeadClicks(events, page)); | ||
| issues.push(...detectFormAbandonment(events, page)); | ||
| issues.push(...detectErrorCorrelated(events, page)); | ||
| return issues; | ||
| } | ||
| function detectRageClicks(events, page) { | ||
| var _a, _b, _c, _d, _e, _f, _g, _h; | ||
| const out = []; | ||
| const seenGroups = /* @__PURE__ */ new Set(); | ||
| for (let i = 0; i < events.length; i++) { | ||
| const e = events[i]; | ||
| if (e.type !== "click") continue; | ||
| const sel = ((_b = (_a = e.data) == null ? void 0 : _a.element) == null ? void 0 : _b.selector) || ((_d = (_c = e.data) == null ? void 0 : _c.element) == null ? void 0 : _d.testId) || ""; | ||
| if (!sel) continue; | ||
| const cluster = [e]; | ||
| let j = i + 1; | ||
| while (j < events.length) { | ||
| const next = events[j]; | ||
| if (next.timestamp - e.timestamp > RAGE_WINDOW_MS) break; | ||
| if (isResponseEvent(next)) break; | ||
| if (next.type === "click") { | ||
| const nextSel = ((_f = (_e = next.data) == null ? void 0 : _e.element) == null ? void 0 : _f.selector) || ((_h = (_g = next.data) == null ? void 0 : _g.element) == null ? void 0 : _h.testId) || ""; | ||
| if (nextSel === sel) cluster.push(next); | ||
| } | ||
| j++; | ||
| } | ||
| if (cluster.length >= RAGE_MIN_CLICKS) { | ||
| const key = `${sel}@${e.timestamp}`; | ||
| if (seenGroups.has(key)) continue; | ||
| seenGroups.add(key); | ||
| const label = clickLabel(cluster[0]); | ||
| out.push({ | ||
| id: makeIssueId("frustration-rage"), | ||
| detector: "frustration-rage", | ||
| severity: "serious", | ||
| title: `Rage clicks on ${label} (${cluster.length}\xD7 in ${Math.round(cluster[cluster.length - 1].timestamp - cluster[0].timestamp)}ms)`, | ||
| description: `User clicked the same element ${cluster.length} times within ${RAGE_WINDOW_MS}ms with no observable response (no API call, navigation, or DOM update). The element either doesn't respond to clicks or feels broken.`, | ||
| selector: sel, | ||
| page, | ||
| detectedAt: cluster[0].timestamp, | ||
| firstSeenAt: cluster[0].timestamp, | ||
| lastSeenAt: cluster[cluster.length - 1].timestamp, | ||
| occurrences: cluster.length | ||
| }); | ||
| i = j - 1; | ||
| } | ||
| } | ||
| return out; | ||
| } | ||
| function detectDeadClicks(events, page) { | ||
| var _a, _b; | ||
| const out = []; | ||
| const MAX = 5; | ||
| for (let i = 0; i < events.length && out.length < MAX; i++) { | ||
| const e = events[i]; | ||
| if (e.type !== "click") continue; | ||
| let responsive = false; | ||
| for (let j = i + 1; j < events.length; j++) { | ||
| const next = events[j]; | ||
| if (next.timestamp - e.timestamp > DEAD_RESPONSE_WINDOW_MS) break; | ||
| if (isResponseEvent(next)) { | ||
| responsive = true; | ||
| break; | ||
| } | ||
| } | ||
| if (responsive) continue; | ||
| const sel = ((_b = (_a = e.data) == null ? void 0 : _a.element) == null ? void 0 : _b.selector) || ""; | ||
| const label = clickLabel(e); | ||
| out.push({ | ||
| id: makeIssueId("frustration-dead"), | ||
| detector: "frustration-dead", | ||
| severity: "moderate", | ||
| title: `Dead click on ${label}`, | ||
| description: `Clicked but nothing happened within ${DEAD_RESPONSE_WINDOW_MS}ms (no API call, navigation, or DOM input). The element may have an unbound handler, a swallowed event, or be visually clickable but disabled.`, | ||
| selector: sel, | ||
| page: e.page || page, | ||
| detectedAt: e.timestamp | ||
| }); | ||
| } | ||
| return out; | ||
| } | ||
| function detectFormAbandonment(events, _page) { | ||
| var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j; | ||
| const out = []; | ||
| const formActivity = {}; | ||
| for (const e of events) { | ||
| if (e.type === "input") { | ||
| const formId = ((_b = (_a = e.data) == null ? void 0 : _a.element) == null ? void 0 : _b.formId) || ((_d = (_c = e.data) == null ? void 0 : _c.element) == null ? void 0 : _d.formAction) || "_default"; | ||
| if (!formActivity[formId]) { | ||
| formActivity[formId] = { firstInputAt: e.timestamp, fieldsSeen: /* @__PURE__ */ new Set(), lastInputAt: e.timestamp, page: e.page }; | ||
| } | ||
| const name = ((_f = (_e = e.data) == null ? void 0 : _e.element) == null ? void 0 : _f.name) || ((_h = (_g = e.data) == null ? void 0 : _g.element) == null ? void 0 : _h.id) || "field"; | ||
| formActivity[formId].fieldsSeen.add(name); | ||
| formActivity[formId].lastInputAt = e.timestamp; | ||
| } else if (e.type === "form_submit") { | ||
| const formId = ((_j = (_i = e.data) == null ? void 0 : _i.form) == null ? void 0 : _j.id) || "_default"; | ||
| delete formActivity[formId]; | ||
| } else if (e.type === "route_change") { | ||
| for (const formId of Object.keys(formActivity)) { | ||
| const a = formActivity[formId]; | ||
| if (e.timestamp - a.lastInputAt > ABANDON_WINDOW_MS) continue; | ||
| if (a.fieldsSeen.size === 0) continue; | ||
| out.push({ | ||
| id: makeIssueId("frustration-abandon"), | ||
| detector: "frustration-abandon", | ||
| severity: "moderate", | ||
| title: `Form abandoned on ${a.page} (${a.fieldsSeen.size} field${a.fieldsSeen.size === 1 ? "" : "s"} filled)`, | ||
| description: `User typed into ${a.fieldsSeen.size} field${a.fieldsSeen.size === 1 ? "" : "s"} (${Array.from(a.fieldsSeen).slice(0, 5).join(", ")}) and then navigated away without submitting. Likely a UX problem: the submit button is unclear, the form requires too much info, or it's failing silently.`, | ||
| page: a.page, | ||
| detectedAt: a.lastInputAt, | ||
| firstSeenAt: a.firstInputAt, | ||
| lastSeenAt: a.lastInputAt | ||
| }); | ||
| delete formActivity[formId]; | ||
| } | ||
| } | ||
| } | ||
| return out; | ||
| } | ||
| function detectErrorCorrelated(events, page) { | ||
| var _a, _b, _c, _d, _e, _f; | ||
| const out = []; | ||
| const seen = /* @__PURE__ */ new Set(); | ||
| for (let i = 0; i < events.length; i++) { | ||
| const e = events[i]; | ||
| const isError = e.type === "error" || e.type === "unhandled_rejection" || e.type === "console_error"; | ||
| if (!isError) continue; | ||
| let click = null; | ||
| for (let j = i - 1; j >= 0; j--) { | ||
| const prev = events[j]; | ||
| if (e.timestamp - prev.timestamp > ERROR_CORRELATION_WINDOW_MS) break; | ||
| if (prev.type === "click") { | ||
| click = prev; | ||
| break; | ||
| } | ||
| } | ||
| if (!click) continue; | ||
| const errMsg = ((_b = (_a = e.data) == null ? void 0 : _a.error) == null ? void 0 : _b.message) || ""; | ||
| if (!errMsg) continue; | ||
| const key = `${errMsg}::${((_d = (_c = click.data) == null ? void 0 : _c.element) == null ? void 0 : _d.selector) || ""}`; | ||
| if (seen.has(key)) continue; | ||
| seen.add(key); | ||
| const label = clickLabel(click); | ||
| const truncMsg = errMsg.length > 60 ? errMsg.slice(0, 57) + "\u2026" : errMsg; | ||
| const delta = Math.round(e.timestamp - click.timestamp); | ||
| out.push({ | ||
| id: makeIssueId("frustration-error-correlated"), | ||
| detector: "frustration-error-correlated", | ||
| severity: "critical", | ||
| title: `Click on ${label} triggered: ${truncMsg}`, | ||
| description: `An error fired ${delta}ms after the user clicked ${label}. This is almost certainly the offending interaction \u2014 the click handler threw, or its async path failed.`, | ||
| selector: (_f = (_e = click.data) == null ? void 0 : _e.element) == null ? void 0 : _f.selector, | ||
| page: e.page || page, | ||
| detectedAt: e.timestamp | ||
| }); | ||
| } | ||
| return out; | ||
| } | ||
| function isResponseEvent(e) { | ||
| return e.type === "api_request" || e.type === "route_change" || e.type === "input" || e.type === "form_submit" || e.type === "select_change"; | ||
| } | ||
| function clickLabel(e) { | ||
| var _a; | ||
| const el = (_a = e.data) == null ? void 0 : _a.element; | ||
| const raw = (el == null ? void 0 : el.text) || (el == null ? void 0 : el.ariaLabel) || (el == null ? void 0 : el.testId) || (el == null ? void 0 : el.id) || (el == null ? void 0 : el.tag) || "element"; | ||
| const trimmed = String(raw).replace(/\s+/g, " ").trim(); | ||
| return trimmed.length > 40 ? `"${trimmed.slice(0, 37)}\u2026"` : `"${trimmed}"`; | ||
| } | ||
| // src/scanner/index.ts | ||
| var _issues = []; | ||
| var _scanInFlight = null; | ||
| var _lastScanAt = 0; | ||
| var SEVERITY_ORDER = { | ||
| critical: 0, | ||
| serious: 1, | ||
| moderate: 2, | ||
| minor: 3 | ||
| }; | ||
| async function scan() { | ||
| if (_scanInFlight) { | ||
| const issues = await _scanInFlight; | ||
| return { issues, durationMs: 0, scannedAt: _lastScanAt }; | ||
| } | ||
| const startedAt = Date.now(); | ||
| const sessions = getAllSessions().sort((a, b) => b.updatedAt - a.updatedAt); | ||
| const session = sessions[0] || null; | ||
| const safeRun = (p) => p.catch((err) => { | ||
| console.warn("[TraceBug] Detector failed:", err); | ||
| return []; | ||
| }); | ||
| _scanInFlight = Promise.all([ | ||
| safeRun(detectBrokenImages()), | ||
| safeRun(detectMixedContent()), | ||
| safeRun(detectConsoleErrors(session)), | ||
| safeRun(detectFailedRequests(session)), | ||
| safeRun(detectSlowApis(session)), | ||
| safeRun(detectA11yViolations()), | ||
| safeRun(detectFrustration(session)) | ||
| ]).then((results) => { | ||
| const all = [].concat(...results); | ||
| all.sort((a, b) => { | ||
| const sev = SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity]; | ||
| if (sev !== 0) return sev; | ||
| return a.detectedAt - b.detectedAt; | ||
| }); | ||
| _issues = all; | ||
| return all; | ||
| }); | ||
| try { | ||
| const issues = await _scanInFlight; | ||
| _lastScanAt = Date.now(); | ||
| return { issues, durationMs: _lastScanAt - startedAt, scannedAt: _lastScanAt }; | ||
| } finally { | ||
| _scanInFlight = null; | ||
| } | ||
| } | ||
| function getIssues(options) { | ||
| var _a; | ||
| const includeDismissed = (_a = options == null ? void 0 : options.includeDismissed) != null ? _a : false; | ||
| return includeDismissed ? _issues.slice() : _issues.filter((i) => !i.dismissed); | ||
| } | ||
| function dismissIssue(id) { | ||
| const issue = _issues.find((i) => i.id === id); | ||
| if (!issue) return false; | ||
| issue.dismissed = true; | ||
| return true; | ||
| } | ||
| function undismissIssue(id) { | ||
| const issue = _issues.find((i) => i.id === id); | ||
| if (!issue) return false; | ||
| issue.dismissed = false; | ||
| return true; | ||
| } | ||
| function clearIssues() { | ||
| _issues = []; | ||
| _lastScanAt = 0; | ||
| } | ||
| function getIssueCountsByDetector() { | ||
| const counts = { | ||
| "axe-a11y": 0, | ||
| "broken-image": 0, | ||
| "mixed-content": 0, | ||
| "console-error": 0, | ||
| "slow-api": 0, | ||
| "failed-request": 0, | ||
| "frustration-rage": 0, | ||
| "frustration-dead": 0, | ||
| "frustration-abandon": 0, | ||
| "frustration-error-correlated": 0 | ||
| }; | ||
| for (const i of _issues) { | ||
| if (i.dismissed) continue; | ||
| counts[i.detector] = (counts[i.detector] || 0) + 1; | ||
| } | ||
| return counts; | ||
| } | ||
| function getIssueCountsBySeverity() { | ||
| const counts = { | ||
| critical: 0, | ||
| serious: 0, | ||
| moderate: 0, | ||
| minor: 0 | ||
| }; | ||
| for (const i of _issues) { | ||
| if (i.dismissed) continue; | ||
| counts[i.severity] += 1; | ||
| } | ||
| return counts; | ||
| } | ||
| function getIssueById(id) { | ||
| return _issues.find((i) => i.id === id) || null; | ||
| } | ||
| export { | ||
| scan, | ||
| getIssues, | ||
| dismissIssue, | ||
| undismissIssue, | ||
| clearIssues, | ||
| getIssueCountsByDetector, | ||
| getIssueCountsBySeverity, | ||
| getIssueById | ||
| }; | ||
| //# sourceMappingURL=chunk-XVPLXS7L.js.map |
| {"version":3,"sources":["../src/scanner/helpers.ts","../src/scanner/detectors/broken-images.ts","../src/scanner/detectors/mixed-content.ts","../src/fingerprint.ts","../src/scanner/detectors/session-data.ts","../src/scanner/detectors/a11y.ts","../src/scanner/detectors/frustration.ts","../src/scanner/index.ts"],"sourcesContent":["// ── Scanner helpers ───────────────────────────────────────────────────────\r\n// Shared utilities for detectors: stable issue IDs, robust CSS selectors,\r\n// severity coercion.\r\n\r\nimport { IssueDetector, IssueSeverity } from \"../types\";\r\n\r\nlet _issueCounter = 0;\r\n\r\n/** Stable, unique issue ID across detectors and runs. */\r\nexport function makeIssueId(detector: IssueDetector): string {\r\n _issueCounter += 1;\r\n return `${detector}_${Date.now().toString(36)}_${_issueCounter}`;\r\n}\r\n\r\n/**\r\n * Build a \"good enough\" CSS selector for an element. Prefers id, then\r\n * data-testid, then a tag+nth-of-type chain bounded to depth 5. Not\r\n * guaranteed unique on pathological pages but unique enough for clicking\r\n * back to the offending node.\r\n */\r\nexport function buildSelector(el: Element): string {\r\n if (!el || el.nodeType !== 1) return \"\";\r\n if (el.id) return `#${cssEscape(el.id)}`;\r\n\r\n const testId = el.getAttribute(\"data-testid\") || el.getAttribute(\"data-test-id\");\r\n if (testId) return `[data-testid=\"${cssEscape(testId)}\"]`;\r\n\r\n const parts: string[] = [];\r\n let cur: Element | null = el;\r\n let depth = 0;\r\n while (cur && cur.nodeType === 1 && cur.tagName !== \"BODY\" && depth < 5) {\r\n const tag = cur.tagName.toLowerCase();\r\n // Explicit annotation breaks the circular inference from `cur = parent`\r\n // below (which otherwise makes `parent` — and `parent.children` — `any`).\r\n const parent: HTMLElement | null = cur.parentElement;\r\n if (!parent) {\r\n parts.unshift(tag);\r\n break;\r\n }\r\n const siblings = Array.from(parent.children).filter(c => c.tagName === cur!.tagName);\r\n if (siblings.length > 1) {\r\n const idx = siblings.indexOf(cur) + 1;\r\n parts.unshift(`${tag}:nth-of-type(${idx})`);\r\n } else {\r\n parts.unshift(tag);\r\n }\r\n cur = parent;\r\n depth += 1;\r\n }\r\n return parts.join(\" > \");\r\n}\r\n\r\n/**\r\n * Map axe-core's impact strings to our IssueSeverity enum. Axe's \"serious\"\r\n * is our highest non-critical bucket.\r\n */\r\nexport function coerceSeverity(impact: string | null | undefined): IssueSeverity {\r\n switch ((impact || \"\").toLowerCase()) {\r\n case \"critical\": return \"critical\";\r\n case \"serious\": return \"serious\";\r\n case \"moderate\": return \"moderate\";\r\n case \"minor\": return \"minor\";\r\n default: return \"minor\";\r\n }\r\n}\r\n\r\n/**\r\n * Minimal CSS.escape polyfill — needed when an id contains characters that\r\n * would break a selector (colons, brackets, dots in framework-generated ids).\r\n */\r\nfunction cssEscape(value: string): string {\r\n if (typeof CSS !== \"undefined\" && typeof CSS.escape === \"function\") {\r\n return CSS.escape(value);\r\n }\r\n return value.replace(/[^\\w-]/g, ch => `\\\\${ch}`);\r\n}\r\n","// ── Broken-image detector ─────────────────────────────────────────────────\r\n// Walks every <img> on the page and flags ones that failed to load. Uses\r\n// `naturalWidth === 0 && complete === true` — the standard signal for\r\n// \"image attempted to load and failed.\" Skips images that haven't finished\r\n// loading yet (we can't tell if they're broken until they settle).\r\n\r\nimport { Issue } from \"../../types\";\r\nimport { buildSelector, makeIssueId } from \"../helpers\";\r\n\r\nexport async function detectBrokenImages(): Promise<Issue[]> {\r\n const issues: Issue[] = [];\r\n const imgs = Array.from(document.images);\r\n\r\n for (const img of imgs) {\r\n // Skip TraceBug's own UI.\r\n if (img.closest(\"#tracebug-root\")) continue;\r\n // Image still loading — can't decide yet.\r\n if (!img.complete) continue;\r\n // Decoded successfully — naturalWidth is non-zero.\r\n if (img.naturalWidth > 0) continue;\r\n\r\n // No src is a different problem (missing asset, not a broken load).\r\n const src = img.currentSrc || img.src;\r\n if (!src) continue;\r\n\r\n issues.push({\r\n id: makeIssueId(\"broken-image\"),\r\n detector: \"broken-image\",\r\n severity: \"moderate\",\r\n title: `Broken image: ${truncateUrl(src)}`,\r\n description: `<img> element failed to load. The browser tried to fetch \\`${src}\\` and got a network error or a non-image response. ${\r\n img.alt ? `Alt text: \"${img.alt}\"` : \"No alt text — also fails accessibility.\"\r\n }`,\r\n selector: buildSelector(img),\r\n url: src,\r\n page: window.location.pathname,\r\n detectedAt: Date.now(),\r\n });\r\n }\r\n\r\n return issues;\r\n}\r\n\r\nfunction truncateUrl(url: string): string {\r\n if (url.length <= 60) return url;\r\n // Keep the filename — easier to identify than the host.\r\n const tail = url.split(\"/\").pop() || url.slice(-40);\r\n return `…/${tail}`;\r\n}\r\n","// ── Mixed-content detector ────────────────────────────────────────────────\r\n// Flags every `http://` resource on a `https://` page. Browsers block most\r\n// of these silently (active content) or downgrade them (passive content),\r\n// so users rarely notice — but the asset usually fails or breaks the lock\r\n// icon. Worth surfacing.\r\n\r\nimport { Issue } from \"../../types\";\r\nimport { buildSelector, makeIssueId } from \"../helpers\";\r\n\r\nconst ATTR_TARGETS: Array<{ tag: string; attr: string }> = [\r\n { tag: \"img\", attr: \"src\" },\r\n { tag: \"script\", attr: \"src\" },\r\n { tag: \"iframe\", attr: \"src\" },\r\n { tag: \"link\", attr: \"href\" },\r\n { tag: \"audio\", attr: \"src\" },\r\n { tag: \"video\", attr: \"src\" },\r\n { tag: \"source\", attr: \"src\" },\r\n { tag: \"embed\", attr: \"src\" },\r\n { tag: \"object\", attr: \"data\" },\r\n];\r\n\r\nexport async function detectMixedContent(): Promise<Issue[]> {\r\n // Only relevant on HTTPS pages — skip on plain HTTP and file:// origins.\r\n if (typeof window === \"undefined\" || window.location.protocol !== \"https:\") {\r\n return [];\r\n }\r\n\r\n const issues: Issue[] = [];\r\n for (const { tag, attr } of ATTR_TARGETS) {\r\n const elements = document.querySelectorAll(`${tag}[${attr}]`);\r\n for (const el of Array.from(elements)) {\r\n if (el.closest(\"#tracebug-root\")) continue;\r\n const value = (el as HTMLElement).getAttribute(attr) || \"\";\r\n if (!value.startsWith(\"http://\")) continue;\r\n\r\n // <link> only matters when it's loading something the browser fetches —\r\n // stylesheets, preloads, manifests, icons. Skip rel=\"canonical\" etc.\r\n if (tag === \"link\") {\r\n const rel = ((el as HTMLLinkElement).rel || \"\").toLowerCase();\r\n const fetchableRels = [\"stylesheet\", \"preload\", \"prefetch\", \"manifest\", \"icon\", \"shortcut icon\"];\r\n if (!fetchableRels.some(r => rel.includes(r))) continue;\r\n }\r\n\r\n issues.push({\r\n id: makeIssueId(\"mixed-content\"),\r\n detector: \"mixed-content\",\r\n severity: tag === \"script\" || tag === \"iframe\" ? \"serious\" : \"moderate\",\r\n title: `Mixed content: ${tag} loads over HTTP`,\r\n description: `<${tag}> on an HTTPS page references \\`${value}\\`. Browsers block or downgrade this — the resource usually fails to load and breaks the page's secure-context indicator.`,\r\n selector: buildSelector(el as HTMLElement),\r\n url: value,\r\n page: window.location.pathname,\r\n detectedAt: Date.now(),\r\n });\r\n }\r\n }\r\n return issues;\r\n}\r\n","// ── Bug Fingerprint ───────────────────────────────────────────────────────\r\n// Group identical errors locally so 14× the same TypeError collapses into\r\n// one issue with `[×14]` instead of 14 near-duplicate rows.\r\n//\r\n// Fingerprint inputs (in priority order):\r\n// 1. Error type/class extracted from the message (\"TypeError\", etc.)\r\n// 2. Top three \"at ...\" stack frames (location-only — line numbers\r\n// stable across invocations within the same build)\r\n// 3. Page path\r\n//\r\n// We use SHA-1 via `crypto.subtle.digest` when available — falls back to\r\n// a tiny non-cryptographic hash on older contexts. Fingerprint is not\r\n// security-sensitive; collision rate of djb2 is good enough for this.\r\n\r\n/** Compute a stable fingerprint string for an error + page combo. */\r\nexport async function computeFingerprint(\r\n errorMessage: string,\r\n errorStack: string | undefined,\r\n page: string\r\n): Promise<string> {\r\n const errorType = extractErrorType(errorMessage);\r\n const topFrames = extractTopFrames(errorStack || \"\", 3);\r\n const input = `${errorType}|${topFrames.join(\"\\n\")}|${page}`;\r\n\r\n // Prefer SHA-1 from the Subtle Crypto API for stronger uniqueness.\r\n if (typeof crypto !== \"undefined\" && crypto.subtle && typeof crypto.subtle.digest === \"function\") {\r\n try {\r\n const buf = new TextEncoder().encode(input);\r\n const hash = await crypto.subtle.digest(\"SHA-1\", buf);\r\n return bufferToHex(hash).slice(0, 16);\r\n } catch {}\r\n }\r\n return djb2(input).toString(16).padStart(8, \"0\");\r\n}\r\n\r\n/**\r\n * Synchronous fallback fingerprint — used in code paths that can't `await`.\r\n * Collision rate higher than SHA-1 but acceptable for in-session grouping.\r\n */\r\nexport function computeFingerprintSync(\r\n errorMessage: string,\r\n errorStack: string | undefined,\r\n page: string\r\n): string {\r\n const errorType = extractErrorType(errorMessage);\r\n const topFrames = extractTopFrames(errorStack || \"\", 3);\r\n const input = `${errorType}|${topFrames.join(\"\\n\")}|${page}`;\r\n return djb2(input).toString(16).padStart(8, \"0\");\r\n}\r\n\r\n/** Pick out the JS error class — TypeError, ReferenceError, etc. */\r\nfunction extractErrorType(message: string): string {\r\n const m = message.match(/^([A-Z][a-zA-Z]+Error|Error)\\b/);\r\n return m ? m[1] : \"Error\";\r\n}\r\n\r\n/**\r\n * Extract the location parts (\"foo.js:42:13\") of the top N stack frames,\r\n * dropping function names. Same call site → same fingerprint, even if the\r\n * function gets renamed between minified/dev builds.\r\n */\r\nfunction extractTopFrames(stack: string, n: number): string[] {\r\n const frames: string[] = [];\r\n const lines = stack.split(\"\\n\");\r\n for (const line of lines) {\r\n const m = line.match(/(https?:\\/\\/[^):\\s]+|[^():\\s]+\\.[a-z]+):(\\d+):(\\d+)/i);\r\n if (m) {\r\n const url = m[1];\r\n const path = url.includes(\"://\") ? new URL(url, typeof window !== \"undefined\" ? window.location.origin : \"http://localhost\").pathname : url;\r\n frames.push(`${path}:${m[2]}:${m[3]}`);\r\n if (frames.length >= n) break;\r\n }\r\n }\r\n return frames;\r\n}\r\n\r\nfunction bufferToHex(buf: ArrayBuffer): string {\r\n const bytes = new Uint8Array(buf);\r\n let out = \"\";\r\n for (let i = 0; i < bytes.length; i++) out += bytes[i].toString(16).padStart(2, \"0\");\r\n return out;\r\n}\r\n\r\n/** djb2 — tiny non-cryptographic hash. Stable, deterministic, no deps. */\r\nfunction djb2(str: string): number {\r\n let hash = 5381;\r\n for (let i = 0; i < str.length; i++) hash = ((hash << 5) + hash + str.charCodeAt(i)) | 0;\r\n return hash >>> 0; // unsigned 32-bit\r\n}\r\n","// ── Session-data detectors ────────────────────────────────────────────────\r\n// Three detectors that read from the SDK's existing event buffer and\r\n// network-failure ring buffer — no new tracking, just classification.\r\n//\r\n// console-error: every distinct error/unhandled rejection in the session\r\n// failed-request: every 4xx/5xx/network-error from the api_request stream\r\n// slow-api: every successful api_request that took longer than the threshold\r\n\r\nimport { Issue, StoredSession, TraceBugEvent } from \"../../types\";\r\nimport { getNetworkFailures } from \"../../collectors\";\r\nimport { makeIssueId } from \"../helpers\";\r\nimport { computeFingerprint } from \"../../fingerprint\";\r\n\r\nconst SLOW_API_MS = 2000;\r\nconst MAX_CONTEXT_SAMPLES = 10;\r\n\r\n/**\r\n * Console errors collapsed by fingerprint (errorType + top-3 frames + page).\r\n * Repeats accumulate `occurrences` + `firstSeenAt`/`lastSeenAt` + a few\r\n * `contextSamples` so the UI can show the count and let the user expand\r\n * to see distinct preceding actions.\r\n */\r\nexport async function detectConsoleErrors(session: StoredSession | null): Promise<Issue[]> {\r\n if (!session) return [];\r\n\r\n // Build groups by fingerprint.\r\n type Group = {\r\n issue: Issue;\r\n samples: Array<{ timestamp: number; precedingAction?: string }>;\r\n };\r\n const groups = new Map<string, Group>();\r\n\r\n for (let i = 0; i < session.events.length; i++) {\r\n const e = session.events[i];\r\n if (e.type !== \"error\" && e.type !== \"unhandled_rejection\" && e.type !== \"console_error\") continue;\r\n const message = e.data.error?.message || e.data.message || \"\";\r\n if (!message) continue;\r\n\r\n const stack = e.data.error?.stack || \"\";\r\n const page = e.page || (typeof window !== \"undefined\" ? window.location.pathname : \"\");\r\n const fp = await computeFingerprint(message, stack, page);\r\n\r\n const precedingAction = describePrecedingAction(session.events, i);\r\n\r\n const existing = groups.get(fp);\r\n if (existing) {\r\n existing.issue.occurrences = (existing.issue.occurrences || 1) + 1;\r\n existing.issue.lastSeenAt = e.timestamp;\r\n if (existing.samples.length < MAX_CONTEXT_SAMPLES) {\r\n existing.samples.push({ timestamp: e.timestamp, precedingAction });\r\n }\r\n continue;\r\n }\r\n\r\n const firstFrame = stack.split(\"\\n\").find((l: string) => l.trim().startsWith(\"at \"))?.trim() || \"\";\r\n const issue: Issue = {\r\n id: makeIssueId(\"console-error\"),\r\n detector: \"console-error\",\r\n severity: classifyErrorSeverity(message),\r\n title: `JS error: ${message.slice(0, 70)}${message.length > 70 ? \"…\" : \"\"}`,\r\n description: firstFrame ? `${message}\\n\\nFirst frame: ${firstFrame}` : message,\r\n page,\r\n detectedAt: e.timestamp,\r\n fingerprint: fp,\r\n occurrences: 1,\r\n firstSeenAt: e.timestamp,\r\n lastSeenAt: e.timestamp,\r\n };\r\n groups.set(fp, { issue, samples: [{ timestamp: e.timestamp, precedingAction }] });\r\n }\r\n\r\n // Finalize: attach context samples + adjust title to reflect repeat count.\r\n const out: Issue[] = [];\r\n for (const g of groups.values()) {\r\n const n = g.issue.occurrences || 1;\r\n if (n > 1) {\r\n g.issue.title = `${g.issue.title} [×${n}]`;\r\n g.issue.contextSamples = g.samples;\r\n }\r\n out.push(g.issue);\r\n }\r\n return out;\r\n}\r\n\r\n/** Look back from index `i` for the most recent click/input/navigation. */\r\nfunction describePrecedingAction(events: TraceBugEvent[], i: number): string | undefined {\r\n for (let j = i - 1; j >= 0; j--) {\r\n const e = events[j];\r\n if (e.type === \"click\") {\r\n const t = e.data?.element?.text || e.data?.element?.ariaLabel || e.data?.element?.tag || \"element\";\r\n return `clicked \"${String(t).slice(0, 40)}\"`;\r\n }\r\n if (e.type === \"input\") {\r\n const n = e.data?.element?.name || e.data?.element?.id || \"field\";\r\n return `typed in ${n}`;\r\n }\r\n if (e.type === \"select_change\") return \"selected an option\";\r\n if (e.type === \"form_submit\") return \"submitted a form\";\r\n if (e.type === \"route_change\") return `navigated to ${e.data?.to || \"page\"}`;\r\n }\r\n return undefined;\r\n}\r\n\r\nexport async function detectFailedRequests(session: StoredSession | null): Promise<Issue[]> {\r\n if (!session) return [];\r\n const issues: Issue[] = [];\r\n const buffer = getNetworkFailures();\r\n\r\n for (const e of session.events) {\r\n if (e.type !== \"api_request\") continue;\r\n const req = e.data.request;\r\n if (!req) continue;\r\n const status = req.statusCode || 0;\r\n if (status >= 200 && status < 400) continue; // success\r\n if (status === 0 && req.method === \"HEAD\") continue; // skip our own probe noise\r\n\r\n // Try to find a matching response body snippet from the failure buffer.\r\n const match = buffer.find(b =>\r\n b.url === req.url &&\r\n b.method === req.method &&\r\n b.status === status &&\r\n Math.abs(b.timestamp - e.timestamp) < 5000\r\n );\r\n const snippet = match?.response ? `\\n\\nResponse: ${match.response.slice(0, 160)}` : \"\";\r\n\r\n issues.push({\r\n id: makeIssueId(\"failed-request\"),\r\n detector: \"failed-request\",\r\n severity: status >= 500 ? \"critical\" : status === 0 ? \"serious\" : \"moderate\",\r\n title: `${req.method} ${truncatePath(req.url)} → ${status === 0 ? \"Network Error\" : status}`,\r\n description: `Request failed in ${req.durationMs || 0}ms.${snippet}`,\r\n url: req.url,\r\n page: e.page || window.location.pathname,\r\n detectedAt: e.timestamp,\r\n });\r\n }\r\n\r\n return issues;\r\n}\r\n\r\nexport async function detectSlowApis(session: StoredSession | null): Promise<Issue[]> {\r\n if (!session) return [];\r\n const issues: Issue[] = [];\r\n\r\n for (const e of session.events) {\r\n if (e.type !== \"api_request\") continue;\r\n const req = e.data.request;\r\n if (!req) continue;\r\n const status = req.statusCode || 0;\r\n // Only flag *successful* slow requests — failures are surfaced separately.\r\n if (status < 200 || status >= 400) continue;\r\n const duration = req.durationMs || 0;\r\n if (duration < SLOW_API_MS) continue;\r\n\r\n issues.push({\r\n id: makeIssueId(\"slow-api\"),\r\n detector: \"slow-api\",\r\n severity: duration > 5000 ? \"serious\" : \"moderate\",\r\n title: `Slow API: ${req.method} ${truncatePath(req.url)} (${duration}ms)`,\r\n description: `This request took ${(duration / 1000).toFixed(1)}s — over the ${SLOW_API_MS / 1000}s threshold. Slow APIs are a common UX complaint and a leading cause of perceived bugs (\"the page is frozen\").`,\r\n url: req.url,\r\n page: e.page || window.location.pathname,\r\n detectedAt: e.timestamp,\r\n });\r\n }\r\n\r\n return issues;\r\n}\r\n\r\nfunction truncatePath(url: string): string {\r\n try {\r\n const u = new URL(url, window.location.origin);\r\n const p = u.pathname.length > 50 ? u.pathname.slice(0, 47) + \"…\" : u.pathname;\r\n return p;\r\n } catch {\r\n return url.length > 50 ? url.slice(0, 47) + \"…\" : url;\r\n }\r\n}\r\n\r\nfunction classifyErrorSeverity(message: string): Issue[\"severity\"] {\r\n if (/TypeError|ReferenceError|SyntaxError/i.test(message)) return \"critical\";\r\n if (/Network|fetch|failed to/i.test(message)) return \"serious\";\r\n return \"moderate\";\r\n}\r\n","// ── Accessibility detector ────────────────────────────────────────────────\r\n// Lazy-loads axe-core (~250 KB minified) only on first scan, runs the\r\n// default rule set against the live DOM, and converts axe violations into\r\n// our Issue shape. Skips TraceBug's own UI by passing `exclude: [\"#tracebug-root\"]`.\r\n//\r\n// axe-core has its own dependency graph but bundles cleanly via tsup's\r\n// dynamic-import support — the chunk only loads when scan() is called.\r\n\r\nimport { Issue } from \"../../types\";\r\nimport { coerceSeverity, makeIssueId } from \"../helpers\";\r\n\r\ntype AxeModule = typeof import(\"axe-core\");\r\ntype AxeResults = import(\"axe-core\").AxeResults;\r\n\r\nlet _axePromise: Promise<AxeModule | null> | null = null;\r\n\r\nfunction loadAxe(): Promise<AxeModule | null> {\r\n if (_axePromise) return _axePromise;\r\n _axePromise = import(\"axe-core\")\r\n .then((mod) => (mod as AxeModule & { default?: AxeModule }).default || mod)\r\n .catch((err) => {\r\n console.warn(\"[TraceBug] axe-core failed to load:\", err);\r\n return null;\r\n });\r\n return _axePromise;\r\n}\r\n\r\nexport async function detectA11yViolations(): Promise<Issue[]> {\r\n const axe = await loadAxe();\r\n if (!axe || typeof axe.run !== \"function\") return [];\r\n\r\n let results: AxeResults;\r\n try {\r\n results = await axe.run(document, {\r\n // Only WCAG-tagged rules — keeps signal-to-noise high. Best-practice\r\n // rules add ~30% more noise without proportional value for QA.\r\n runOnly: { type: \"tag\", values: [\"wcag2a\", \"wcag2aa\", \"wcag21a\", \"wcag21aa\"] },\r\n // Skip our own UI so QA isn't told their toolbar fails contrast checks.\r\n // axe accepts a context with exclude — we pass { exclude: [...] } via\r\n // the second-argument options-shaped form below to keep types loose.\r\n resultTypes: [\"violations\"],\r\n });\r\n } catch (err) {\r\n console.warn(\"[TraceBug] axe.run failed:\", err);\r\n return [];\r\n }\r\n\r\n const issues: Issue[] = [];\r\n const violations = results?.violations || [];\r\n\r\n for (const v of violations) {\r\n const nodes = v.nodes || [];\r\n // Each violation often hits multiple elements (e.g. 12 buttons missing\r\n // labels). Roll all of them into a single issue with a node count, so\r\n // the panel doesn't drown in 200 near-duplicate rows.\r\n const firstNode = nodes[0];\r\n const selector = Array.isArray(firstNode?.target) ? firstNode.target.join(\" \") : \"\";\r\n const exampleSnippet: string = (firstNode?.html || \"\").slice(0, 120);\r\n const moreSuffix = nodes.length > 1 ? ` (+ ${nodes.length - 1} more element${nodes.length === 2 ? \"\" : \"s\"})` : \"\";\r\n\r\n issues.push({\r\n id: makeIssueId(\"axe-a11y\"),\r\n detector: \"axe-a11y\",\r\n severity: coerceSeverity(v.impact),\r\n title: `${v.help || v.id}${moreSuffix}`,\r\n description: `${v.description || v.id}\\n\\nFirst element: \\`${exampleSnippet}\\``,\r\n selector: selector || undefined,\r\n helpUrl: v.helpUrl,\r\n page: window.location.pathname,\r\n detectedAt: Date.now(),\r\n });\r\n }\r\n\r\n return issues;\r\n}\r\n","// ── Frustration detectors ─────────────────────────────────────────────────\r\n// Surface user-experienced bugs that don't throw exceptions but make users\r\n// hate the product:\r\n// - Rage clicks: ≥3 clicks on the same selector within 1.5 s with no response\r\n// - Dead clicks: click with no DOM/route/network/input response within 1.5 s\r\n// - Form abandonment: input events on a form, then route_change before submit\r\n// - Error correlation: error fired ≤2.5 s after a click — pair them\r\n//\r\n// Pure analysis over the existing event log. No new tracking code, no extra\r\n// listeners.\r\n\r\nimport { Issue, StoredSession, TraceBugEvent } from \"../../types\";\r\nimport { makeIssueId } from \"../helpers\";\r\n\r\nconst RAGE_WINDOW_MS = 1500;\r\nconst RAGE_MIN_CLICKS = 3;\r\nconst DEAD_RESPONSE_WINDOW_MS = 1500;\r\nconst ABANDON_WINDOW_MS = 60_000;\r\nconst ERROR_CORRELATION_WINDOW_MS = 2500;\r\n\r\nexport async function detectFrustration(session: StoredSession | null): Promise<Issue[]> {\r\n if (!session) return [];\r\n const events = session.events;\r\n if (events.length === 0) return [];\r\n\r\n const issues: Issue[] = [];\r\n const page = session.events[0]?.page || window.location.pathname;\r\n\r\n issues.push(...detectRageClicks(events, page));\r\n issues.push(...detectDeadClicks(events, page));\r\n issues.push(...detectFormAbandonment(events, page));\r\n issues.push(...detectErrorCorrelated(events, page));\r\n\r\n return issues;\r\n}\r\n\r\n// ── Rage clicks ───────────────────────────────────────────────────────\r\n// Sliding window: ≥3 clicks on the same selector inside 1.5 s, with no\r\n// response (api_request, route_change, or input on the same form) between.\r\n\r\nfunction detectRageClicks(events: TraceBugEvent[], page: string): Issue[] {\r\n const out: Issue[] = [];\r\n const seenGroups = new Set<string>();\r\n\r\n for (let i = 0; i < events.length; i++) {\r\n const e = events[i];\r\n if (e.type !== \"click\") continue;\r\n const sel = e.data?.element?.selector || e.data?.element?.testId || \"\";\r\n if (!sel) continue;\r\n\r\n // Look ahead for sibling clicks within RAGE_WINDOW_MS on the same selector.\r\n const cluster: TraceBugEvent[] = [e];\r\n let j = i + 1;\r\n while (j < events.length) {\r\n const next = events[j];\r\n if (next.timestamp - e.timestamp > RAGE_WINDOW_MS) break;\r\n // Stop if a \"response\" event happened — not rage if anything responded.\r\n if (isResponseEvent(next)) break;\r\n if (next.type === \"click\") {\r\n const nextSel = next.data?.element?.selector || next.data?.element?.testId || \"\";\r\n if (nextSel === sel) cluster.push(next);\r\n }\r\n j++;\r\n }\r\n\r\n if (cluster.length >= RAGE_MIN_CLICKS) {\r\n const key = `${sel}@${e.timestamp}`;\r\n if (seenGroups.has(key)) continue;\r\n seenGroups.add(key);\r\n\r\n const label = clickLabel(cluster[0]);\r\n out.push({\r\n id: makeIssueId(\"frustration-rage\"),\r\n detector: \"frustration-rage\",\r\n severity: \"serious\",\r\n title: `Rage clicks on ${label} (${cluster.length}× in ${Math.round((cluster[cluster.length - 1].timestamp - cluster[0].timestamp))}ms)`,\r\n description: `User clicked the same element ${cluster.length} times within ${RAGE_WINDOW_MS}ms with no observable response (no API call, navigation, or DOM update). The element either doesn't respond to clicks or feels broken.`,\r\n selector: sel,\r\n page,\r\n detectedAt: cluster[0].timestamp,\r\n firstSeenAt: cluster[0].timestamp,\r\n lastSeenAt: cluster[cluster.length - 1].timestamp,\r\n occurrences: cluster.length,\r\n });\r\n // Skip past the cluster to avoid double-flagging.\r\n i = j - 1;\r\n }\r\n }\r\n\r\n return out;\r\n}\r\n\r\n// ── Dead clicks ───────────────────────────────────────────────────────\r\n// Click followed by no api_request, route_change, or input within 1.5 s.\r\n\r\nfunction detectDeadClicks(events: TraceBugEvent[], page: string): Issue[] {\r\n const out: Issue[] = [];\r\n // Cap output: dead-click detection is noisy on long sessions.\r\n const MAX = 5;\r\n\r\n for (let i = 0; i < events.length && out.length < MAX; i++) {\r\n const e = events[i];\r\n if (e.type !== \"click\") continue;\r\n // Skip if the next event in the same window is another click on the same\r\n // selector (likely a rage cluster — already flagged separately).\r\n let responsive = false;\r\n for (let j = i + 1; j < events.length; j++) {\r\n const next = events[j];\r\n if (next.timestamp - e.timestamp > DEAD_RESPONSE_WINDOW_MS) break;\r\n if (isResponseEvent(next)) { responsive = true; break; }\r\n }\r\n if (responsive) continue;\r\n\r\n const sel = e.data?.element?.selector || \"\";\r\n const label = clickLabel(e);\r\n out.push({\r\n id: makeIssueId(\"frustration-dead\"),\r\n detector: \"frustration-dead\",\r\n severity: \"moderate\",\r\n title: `Dead click on ${label}`,\r\n description: `Clicked but nothing happened within ${DEAD_RESPONSE_WINDOW_MS}ms (no API call, navigation, or DOM input). The element may have an unbound handler, a swallowed event, or be visually clickable but disabled.`,\r\n selector: sel,\r\n page: e.page || page,\r\n detectedAt: e.timestamp,\r\n });\r\n }\r\n\r\n return out;\r\n}\r\n\r\n// ── Form abandonment ──────────────────────────────────────────────────\r\n// Inputs on a form, then route_change before form_submit.\r\n\r\nfunction detectFormAbandonment(events: TraceBugEvent[], _page: string): Issue[] {\r\n const out: Issue[] = [];\r\n // Track per-form: has the user typed in it but not submitted?\r\n const formActivity: Record<string, { firstInputAt: number; fieldsSeen: Set<string>; lastInputAt: number; page: string }> = {};\r\n\r\n for (const e of events) {\r\n if (e.type === \"input\") {\r\n const formId = e.data?.element?.formId || e.data?.element?.formAction || \"_default\";\r\n if (!formActivity[formId]) {\r\n formActivity[formId] = { firstInputAt: e.timestamp, fieldsSeen: new Set(), lastInputAt: e.timestamp, page: e.page };\r\n }\r\n const name = e.data?.element?.name || e.data?.element?.id || \"field\";\r\n formActivity[formId].fieldsSeen.add(name);\r\n formActivity[formId].lastInputAt = e.timestamp;\r\n } else if (e.type === \"form_submit\") {\r\n const formId = e.data?.form?.id || \"_default\";\r\n delete formActivity[formId];\r\n } else if (e.type === \"route_change\") {\r\n // Any route change with active forms = abandonment if within window.\r\n for (const formId of Object.keys(formActivity)) {\r\n const a = formActivity[formId];\r\n if (e.timestamp - a.lastInputAt > ABANDON_WINDOW_MS) continue;\r\n if (a.fieldsSeen.size === 0) continue;\r\n out.push({\r\n id: makeIssueId(\"frustration-abandon\"),\r\n detector: \"frustration-abandon\",\r\n severity: \"moderate\",\r\n title: `Form abandoned on ${a.page} (${a.fieldsSeen.size} field${a.fieldsSeen.size === 1 ? \"\" : \"s\"} filled)`,\r\n description: `User typed into ${a.fieldsSeen.size} field${a.fieldsSeen.size === 1 ? \"\" : \"s\"} (${Array.from(a.fieldsSeen).slice(0, 5).join(\", \")}) and then navigated away without submitting. Likely a UX problem: the submit button is unclear, the form requires too much info, or it's failing silently.`,\r\n page: a.page,\r\n detectedAt: a.lastInputAt,\r\n firstSeenAt: a.firstInputAt,\r\n lastSeenAt: a.lastInputAt,\r\n });\r\n delete formActivity[formId];\r\n }\r\n }\r\n }\r\n\r\n return out;\r\n}\r\n\r\n// ── Error correlation ─────────────────────────────────────────────────\r\n// For each error, look back ≤2.5 s for the nearest click — likely the\r\n// triggering interaction. Tag both events together.\r\n\r\nfunction detectErrorCorrelated(events: TraceBugEvent[], page: string): Issue[] {\r\n const out: Issue[] = [];\r\n const seen = new Set<string>();\r\n\r\n for (let i = 0; i < events.length; i++) {\r\n const e = events[i];\r\n const isError = e.type === \"error\" || e.type === \"unhandled_rejection\" || e.type === \"console_error\";\r\n if (!isError) continue;\r\n\r\n // Look back for the nearest click.\r\n let click: TraceBugEvent | null = null;\r\n for (let j = i - 1; j >= 0; j--) {\r\n const prev = events[j];\r\n if (e.timestamp - prev.timestamp > ERROR_CORRELATION_WINDOW_MS) break;\r\n if (prev.type === \"click\") { click = prev; break; }\r\n }\r\n if (!click) continue;\r\n\r\n const errMsg = e.data?.error?.message || \"\";\r\n if (!errMsg) continue;\r\n const key = `${errMsg}::${click.data?.element?.selector || \"\"}`;\r\n if (seen.has(key)) continue;\r\n seen.add(key);\r\n\r\n const label = clickLabel(click);\r\n const truncMsg = errMsg.length > 60 ? errMsg.slice(0, 57) + \"…\" : errMsg;\r\n const delta = Math.round((e.timestamp - click.timestamp));\r\n out.push({\r\n id: makeIssueId(\"frustration-error-correlated\"),\r\n detector: \"frustration-error-correlated\",\r\n severity: \"critical\",\r\n title: `Click on ${label} triggered: ${truncMsg}`,\r\n description: `An error fired ${delta}ms after the user clicked ${label}. This is almost certainly the offending interaction — the click handler threw, or its async path failed.`,\r\n selector: click.data?.element?.selector,\r\n page: e.page || page,\r\n detectedAt: e.timestamp,\r\n });\r\n }\r\n\r\n return out;\r\n}\r\n\r\n// ── Helpers ────────────────────────────────────────────────────────────\r\n\r\nfunction isResponseEvent(e: TraceBugEvent): boolean {\r\n return (\r\n e.type === \"api_request\" ||\r\n e.type === \"route_change\" ||\r\n e.type === \"input\" ||\r\n e.type === \"form_submit\" ||\r\n e.type === \"select_change\"\r\n );\r\n}\r\n\r\nfunction clickLabel(e: TraceBugEvent): string {\r\n const el = e.data?.element;\r\n const raw = el?.text || el?.ariaLabel || el?.testId || el?.id || el?.tag || \"element\";\r\n const trimmed = String(raw).replace(/\\s+/g, \" \").trim();\r\n return trimmed.length > 40 ? `\"${trimmed.slice(0, 37)}…\"` : `\"${trimmed}\"`;\r\n}\r\n","// ── Scanner orchestrator ──────────────────────────────────────────────────\r\n// Runs every detector in parallel, collects results into a single in-memory\r\n// store, and exposes simple queries (getIssues, dismissIssue, fileAsBug).\r\n//\r\n// Issues live in memory only — each scan is a fresh run, results clear on\r\n// page reload. Mirrors the screenshot/video memory model.\r\n\r\nimport { Issue, IssueDetector } from \"../types\";\r\nimport { getAllSessions } from \"../storage\";\r\nimport { detectBrokenImages } from \"./detectors/broken-images\";\r\nimport { detectMixedContent } from \"./detectors/mixed-content\";\r\nimport { detectConsoleErrors, detectFailedRequests, detectSlowApis } from \"./detectors/session-data\";\r\nimport { detectA11yViolations } from \"./detectors/a11y\";\r\nimport { detectFrustration } from \"./detectors/frustration\";\r\n\r\nlet _issues: Issue[] = [];\r\nlet _scanInFlight: Promise<Issue[]> | null = null;\r\nlet _lastScanAt = 0;\r\n\r\nconst SEVERITY_ORDER: Record<Issue[\"severity\"], number> = {\r\n critical: 0,\r\n serious: 1,\r\n moderate: 2,\r\n minor: 3,\r\n};\r\n\r\nexport interface ScanResult {\r\n issues: Issue[];\r\n durationMs: number;\r\n scannedAt: number;\r\n}\r\n\r\n/**\r\n * Run every detector in parallel. Concurrent scans are coalesced — calling\r\n * scan() while one is already running returns the in-flight promise.\r\n */\r\nexport async function scan(): Promise<ScanResult> {\r\n if (_scanInFlight) {\r\n const issues = await _scanInFlight;\r\n return { issues, durationMs: 0, scannedAt: _lastScanAt };\r\n }\r\n\r\n const startedAt = Date.now();\r\n const sessions = getAllSessions().sort((a, b) => b.updatedAt - a.updatedAt);\r\n const session = sessions[0] || null;\r\n\r\n // Promise.allSettled isn't in the ES2018 target lib, so wrap each detector\r\n // in a catch that returns []. One failure doesn't block the others.\r\n const safeRun = (p: Promise<Issue[]>): Promise<Issue[]> =>\r\n p.catch((err) => {\r\n console.warn(\"[TraceBug] Detector failed:\", err);\r\n return [];\r\n });\r\n\r\n _scanInFlight = Promise.all([\r\n safeRun(detectBrokenImages()),\r\n safeRun(detectMixedContent()),\r\n safeRun(detectConsoleErrors(session)),\r\n safeRun(detectFailedRequests(session)),\r\n safeRun(detectSlowApis(session)),\r\n safeRun(detectA11yViolations()),\r\n safeRun(detectFrustration(session)),\r\n ]).then((results) => {\r\n const all: Issue[] = ([] as Issue[]).concat(...results);\r\n // Stable sort: severity first, then detection time.\r\n all.sort((a, b) => {\r\n const sev = SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity];\r\n if (sev !== 0) return sev;\r\n return a.detectedAt - b.detectedAt;\r\n });\r\n _issues = all;\r\n return all;\r\n });\r\n\r\n try {\r\n const issues = await _scanInFlight;\r\n _lastScanAt = Date.now();\r\n return { issues, durationMs: _lastScanAt - startedAt, scannedAt: _lastScanAt };\r\n } finally {\r\n _scanInFlight = null;\r\n }\r\n}\r\n\r\n/** Snapshot of current issues. Filters out dismissed by default. */\r\nexport function getIssues(options?: { includeDismissed?: boolean }): Issue[] {\r\n const includeDismissed = options?.includeDismissed ?? false;\r\n return includeDismissed ? _issues.slice() : _issues.filter(i => !i.dismissed);\r\n}\r\n\r\n/** Mark an issue as dismissed for the current session. */\r\nexport function dismissIssue(id: string): boolean {\r\n const issue = _issues.find(i => i.id === id);\r\n if (!issue) return false;\r\n issue.dismissed = true;\r\n return true;\r\n}\r\n\r\n/** Restore a previously dismissed issue. */\r\nexport function undismissIssue(id: string): boolean {\r\n const issue = _issues.find(i => i.id === id);\r\n if (!issue) return false;\r\n issue.dismissed = false;\r\n return true;\r\n}\r\n\r\n/** Clear all issues from memory. Called from destroy() and \"Clear all data\". */\r\nexport function clearIssues(): void {\r\n _issues = [];\r\n _lastScanAt = 0;\r\n}\r\n\r\n/** Aggregate counts grouped by detector, useful for the toolbar badge. */\r\nexport function getIssueCountsByDetector(): Record<IssueDetector, number> {\r\n const counts: Record<IssueDetector, number> = {\r\n \"axe-a11y\": 0,\r\n \"broken-image\": 0,\r\n \"mixed-content\": 0,\r\n \"console-error\": 0,\r\n \"slow-api\": 0,\r\n \"failed-request\": 0,\r\n \"frustration-rage\": 0,\r\n \"frustration-dead\": 0,\r\n \"frustration-abandon\": 0,\r\n \"frustration-error-correlated\": 0,\r\n };\r\n for (const i of _issues) {\r\n if (i.dismissed) continue;\r\n counts[i.detector] = (counts[i.detector] || 0) + 1;\r\n }\r\n return counts;\r\n}\r\n\r\n/** Count of non-dismissed issues at each severity. */\r\nexport function getIssueCountsBySeverity(): Record<Issue[\"severity\"], number> {\r\n const counts: Record<Issue[\"severity\"], number> = {\r\n critical: 0,\r\n serious: 0,\r\n moderate: 0,\r\n minor: 0,\r\n };\r\n for (const i of _issues) {\r\n if (i.dismissed) continue;\r\n counts[i.severity] += 1;\r\n }\r\n return counts;\r\n}\r\n\r\n/** Lookup helper for the issues panel — find by id. */\r\nexport function getIssueById(id: string): Issue | null {\r\n return _issues.find(i => i.id === id) || null;\r\n}\r\n"],"mappings":"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","names":[]} |
| "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } | ||
| var _chunkHW3D3VMGcjs = require('./chunk-HW3D3VMG.cjs'); | ||
| var _chunkB5YM4JRBcjs = require('./chunk-B5YM4JRB.cjs'); | ||
| // src/ui/issues-panel.ts | ||
| var PANEL_ID = "tracebug-issues-panel"; | ||
| var STYLE_ID = "tracebug-issues-panel-styles"; | ||
| var _isOpen = false; | ||
| var _root = null; | ||
| var SEVERITY_COLORS = { | ||
| critical: { bg: "#7f1d1d", fg: "#fee2e2", border: "#dc2626" }, | ||
| serious: { bg: "#7c2d12", fg: "#fed7aa", border: "#ea580c" }, | ||
| moderate: { bg: "#713f12", fg: "#fde68a", border: "#ca8a04" }, | ||
| minor: { bg: "#1e3a8a", fg: "#bfdbfe", border: "#2563eb" } | ||
| }; | ||
| var DETECTOR_LABELS = { | ||
| "axe-a11y": "A11y", | ||
| "broken-image": "Broken image", | ||
| "mixed-content": "Mixed content", | ||
| "console-error": "JS error", | ||
| "slow-api": "Slow API", | ||
| "failed-request": "Failed request", | ||
| "frustration-rage": "Rage clicks", | ||
| "frustration-dead": "Dead click", | ||
| "frustration-abandon": "Form abandoned", | ||
| "frustration-error-correlated": "Click \u2192 error" | ||
| }; | ||
| function isIssuesPanelOpen() { | ||
| return _isOpen; | ||
| } | ||
| async function showIssuesPanel(root, options) { | ||
| var _a; | ||
| if (_isOpen) return; | ||
| _root = root; | ||
| _injectStyles(); | ||
| _open(root, { issues: [], loading: true }); | ||
| try { | ||
| if ((_a = options == null ? void 0 : options.rescan) != null ? _a : true) { | ||
| await _chunkHW3D3VMGcjs.scan.call(void 0, ); | ||
| } | ||
| } catch (err) { | ||
| console.warn("[TraceBug] Scan failed:", err); | ||
| } | ||
| _renderBody(_chunkHW3D3VMGcjs.getIssues.call(void 0, )); | ||
| } | ||
| function _injectStyles() { | ||
| if (document.getElementById(STYLE_ID)) return; | ||
| const style = document.createElement("style"); | ||
| style.id = STYLE_ID; | ||
| style.textContent = ` | ||
| @keyframes tracebug-issue-locate-flash { | ||
| 0%, 100% { box-shadow: 0 0 0 0 rgba(99,102,241,0.0); outline: 2px solid transparent; } | ||
| 30% { box-shadow: 0 0 0 6px rgba(99,102,241,0.5); outline: 2px solid #6366F1; } | ||
| } | ||
| #${PANEL_ID}-overlay { | ||
| position: fixed !important; | ||
| inset: 0 !important; | ||
| z-index: 2147483647 !important; | ||
| background: rgba(0,0,0,0.75) !important; | ||
| backdrop-filter: blur(6px) !important; | ||
| display: flex !important; | ||
| align-items: center !important; | ||
| justify-content: center !important; | ||
| padding: 20px !important; | ||
| pointer-events: auto !important; | ||
| box-sizing: border-box !important; | ||
| } | ||
| #${PANEL_ID} { | ||
| background: var(--tb-bg-secondary, #1a1a2e) !important; | ||
| border: 1px solid var(--tb-border-hover, #3a3a5e) !important; | ||
| border-radius: var(--tb-radius-lg, 12px) !important; | ||
| width: 100% !important; | ||
| max-width: 720px !important; | ||
| max-height: 90vh !important; | ||
| display: flex !important; | ||
| flex-direction: column !important; | ||
| overflow: hidden !important; | ||
| font-family: var(--tb-font-family, system-ui, -apple-system, sans-serif) !important; | ||
| color: var(--tb-text-primary, #e0e0e0) !important; | ||
| box-sizing: border-box !important; | ||
| box-shadow: 0 20px 60px rgba(0,0,0,0.5) !important; | ||
| } | ||
| #${PANEL_ID} *, #${PANEL_ID} *::before, #${PANEL_ID} *::after { box-sizing: border-box !important; } | ||
| #${PANEL_ID} button { font-family: inherit !important; cursor: pointer !important; } | ||
| #${PANEL_ID} .tb-issue-row { | ||
| padding: 12px 14px; | ||
| border-top: 1px solid var(--tb-border, #2a2a3e); | ||
| display: flex; | ||
| gap: 12px; | ||
| align-items: flex-start; | ||
| } | ||
| #${PANEL_ID} .tb-issue-row:hover { background: var(--tb-bg-primary, #0f0f1a); } | ||
| #${PANEL_ID} .tb-sev-badge { | ||
| font-size: 10px; | ||
| font-weight: 700; | ||
| padding: 3px 7px; | ||
| border-radius: 4px; | ||
| letter-spacing: 0.4px; | ||
| text-transform: uppercase; | ||
| flex-shrink: 0; | ||
| border: 1px solid; | ||
| } | ||
| #${PANEL_ID} .tb-detector-tag { | ||
| font-size: 10px; | ||
| color: var(--tb-text-muted, #888); | ||
| background: var(--tb-bg-primary, #0f0f1a); | ||
| border: 1px solid var(--tb-border, #2a2a3e); | ||
| padding: 2px 6px; | ||
| border-radius: 4px; | ||
| flex-shrink: 0; | ||
| } | ||
| #${PANEL_ID} .tb-issue-actions { | ||
| display: flex; | ||
| gap: 6px; | ||
| flex-shrink: 0; | ||
| } | ||
| #${PANEL_ID} .tb-issue-actions button { | ||
| background: transparent; | ||
| color: var(--tb-text-secondary, #aaa); | ||
| border: 1px solid var(--tb-border, #2a2a3e); | ||
| border-radius: 4px; | ||
| padding: 4px 10px; | ||
| font-size: 11px; | ||
| transition: all 0.15s; | ||
| } | ||
| #${PANEL_ID} .tb-issue-actions button:hover { | ||
| background: var(--tb-btn-hover, #ffffff15); | ||
| color: var(--tb-text-primary, #fff); | ||
| } | ||
| #${PANEL_ID} .tb-issue-actions button[data-action="file"] { | ||
| background: var(--tb-accent, #6366F1); | ||
| color: #fff; | ||
| border-color: transparent; | ||
| } | ||
| #${PANEL_ID} .tb-issue-actions button[data-action="file"]:hover { opacity: 0.9; } | ||
| `; | ||
| document.head.appendChild(style); | ||
| } | ||
| function _open(root, state) { | ||
| var _a; | ||
| _isOpen = true; | ||
| (_a = document.getElementById(`${PANEL_ID}-overlay`)) == null ? void 0 : _a.remove(); | ||
| const overlay = document.createElement("div"); | ||
| overlay.id = `${PANEL_ID}-overlay`; | ||
| overlay.dataset.tracebug = "issues-panel-overlay"; | ||
| overlay.setAttribute("role", "dialog"); | ||
| overlay.setAttribute("aria-modal", "true"); | ||
| overlay.setAttribute("aria-label", "Page issues"); | ||
| const panel = document.createElement("div"); | ||
| panel.id = PANEL_ID; | ||
| panel.dataset.tracebug = "issues-panel"; | ||
| panel.innerHTML = _shellHtml(state); | ||
| overlay.appendChild(panel); | ||
| root.appendChild(overlay); | ||
| _wireShellHandlers(overlay, panel); | ||
| } | ||
| function _shellHtml(state) { | ||
| return ` | ||
| <div data-tb-issues="header" style="padding:16px 18px;display:flex;align-items:center;gap:12px;border-bottom:1px solid var(--tb-border, #2a2a3e)"> | ||
| <span style="font-size:20px">\u{1F50D}</span> | ||
| <div style="flex:1;min-width:0"> | ||
| <div style="font-size:16px;font-weight:700;color:var(--tb-text-primary, #fff)">Page Issues</div> | ||
| <div data-tb-issues="subtitle" style="font-size:11px;color:var(--tb-text-muted, #888);margin-top:2px">${state.loading ? "Scanning\u2026" : "No scan yet"}</div> | ||
| </div> | ||
| <button data-tb-issues="rescan" style="background:transparent;color:var(--tb-text-secondary, #aaa);border:1px solid var(--tb-border, #2a2a3e);border-radius:6px;padding:6px 12px;font-size:12px;display:flex;align-items:center;gap:6px"> | ||
| <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-3-6.7"/><polyline points="21 3 21 9 15 9"/></svg> | ||
| Rescan | ||
| </button> | ||
| <button data-tb-issues="close" aria-label="Close" style="background:none;border:none;color:var(--tb-text-muted, #888);font-size:20px;padding:4px 8px;border-radius:6px">×</button> | ||
| </div> | ||
| <div data-tb-issues="body" style="flex:1;overflow-y:auto;min-height:200px"> | ||
| ${state.loading ? _loadingHtml() : ""} | ||
| </div> | ||
| `; | ||
| } | ||
| function _loadingHtml() { | ||
| return ` | ||
| <div style="padding:48px 20px;text-align:center;color:var(--tb-text-muted, #888);font-size:13px"> | ||
| <div style="font-size:24px;margin-bottom:8px">\u{1F50D}</div> | ||
| Scanning page for issues\u2026 | ||
| <div style="font-size:11px;margin-top:6px;opacity:0.7">Loading axe-core, checking images, network calls, JS errors\u2026</div> | ||
| </div> | ||
| `; | ||
| } | ||
| function _wireShellHandlers(overlay, panel) { | ||
| const close = () => { | ||
| _isOpen = false; | ||
| overlay.remove(); | ||
| document.removeEventListener("keydown", escHandler); | ||
| }; | ||
| panel.querySelector('[data-tb-issues="close"]').addEventListener("click", close); | ||
| overlay.addEventListener("click", (e) => { | ||
| if (e.target === overlay) close(); | ||
| }); | ||
| panel.querySelector('[data-tb-issues="rescan"]').addEventListener("click", async () => { | ||
| const body = panel.querySelector('[data-tb-issues="body"]'); | ||
| body.innerHTML = _loadingHtml(); | ||
| const sub = panel.querySelector('[data-tb-issues="subtitle"]'); | ||
| sub.textContent = "Scanning\u2026"; | ||
| await _chunkHW3D3VMGcjs.scan.call(void 0, ); | ||
| _renderBody(_chunkHW3D3VMGcjs.getIssues.call(void 0, )); | ||
| }); | ||
| const escHandler = (e) => { | ||
| if (e.key === "Escape") close(); | ||
| }; | ||
| document.addEventListener("keydown", escHandler); | ||
| } | ||
| function _renderBody(issues) { | ||
| const panel = document.getElementById(PANEL_ID); | ||
| if (!panel) return; | ||
| const body = panel.querySelector('[data-tb-issues="body"]'); | ||
| const subtitle = panel.querySelector('[data-tb-issues="subtitle"]'); | ||
| if (issues.length === 0) { | ||
| subtitle.textContent = "No issues found"; | ||
| body.innerHTML = ` | ||
| <div style="padding:48px 20px;text-align:center;color:var(--tb-text-muted, #888);font-size:13px"> | ||
| <div style="font-size:32px;margin-bottom:8px">\u2713</div> | ||
| Clean scan \u2014 no issues detected on this page. | ||
| <div style="font-size:11px;margin-top:6px;opacity:0.7">Includes a11y \xB7 broken images \xB7 mixed content \xB7 JS errors \xB7 failed/slow API calls</div> | ||
| </div> | ||
| `; | ||
| return; | ||
| } | ||
| const counts = {}; | ||
| for (const i of issues) counts[i.severity] = (counts[i.severity] || 0) + 1; | ||
| const summary = ["critical", "serious", "moderate", "minor"].filter((s) => counts[s]).map((s) => `${counts[s]} ${s}`).join(" \xB7 "); | ||
| subtitle.textContent = `${issues.length} issue${issues.length === 1 ? "" : "s"} \xB7 ${summary}`; | ||
| body.innerHTML = issues.map((issue) => _issueRowHtml(issue)).join(""); | ||
| body.querySelectorAll("[data-tb-issue-id]").forEach((row) => { | ||
| var _a, _b, _c; | ||
| const id = row.dataset.tbIssueId; | ||
| const issue = issues.find((i) => i.id === id); | ||
| if (!issue) return; | ||
| (_a = row.querySelector('[data-action="locate"]')) == null ? void 0 : _a.addEventListener("click", () => _locate(issue)); | ||
| (_b = row.querySelector('[data-action="dismiss"]')) == null ? void 0 : _b.addEventListener("click", () => { | ||
| _chunkHW3D3VMGcjs.dismissIssue.call(void 0, id); | ||
| _renderBody(_chunkHW3D3VMGcjs.getIssues.call(void 0, )); | ||
| }); | ||
| (_c = row.querySelector('[data-action="file"]')) == null ? void 0 : _c.addEventListener("click", () => _fileAsBug(issue)); | ||
| }); | ||
| } | ||
| function _issueRowHtml(issue) { | ||
| const colors = SEVERITY_COLORS[issue.severity]; | ||
| const detectorLabel = DETECTOR_LABELS[issue.detector]; | ||
| const desc = issue.description.length > 240 ? issue.description.slice(0, 237) + "\u2026" : issue.description; | ||
| const repeats = (issue.occurrences || 1) > 1; | ||
| const samplesHtml = repeats && issue.contextSamples && issue.contextSamples.length > 0 ? `<details style="margin-top:6px;font-size:11px"> | ||
| <summary style="cursor:pointer;color:var(--tb-text-muted, #888)"> | ||
| View all ${issue.occurrences} contexts | ||
| </summary> | ||
| <ol style="margin:6px 0 0 20px;padding:0;color:var(--tb-text-secondary, #aaa);line-height:1.5"> | ||
| ${issue.contextSamples.map((s) => ` | ||
| <li>${new Date(s.timestamp).toLocaleTimeString()}${s.precedingAction ? ` \xB7 ${_chunkB5YM4JRBcjs.escapeHtml.call(void 0, s.precedingAction)}` : ""}</li> | ||
| `).join("")} | ||
| </ol> | ||
| </details>` : ""; | ||
| return ` | ||
| <div class="tb-issue-row" data-tb-issue-id="${issue.id}"> | ||
| <span class="tb-sev-badge" style="background:${colors.bg};color:${colors.fg};border-color:${colors.border}">${issue.severity}</span> | ||
| <div style="flex:1;min-width:0"> | ||
| <div style="display:flex;align-items:center;gap:6px;margin-bottom:4px;flex-wrap:wrap"> | ||
| <span class="tb-detector-tag">${detectorLabel}</span> | ||
| <span style="font-size:13px;color:var(--tb-text-primary, #e0e0e0);font-weight:500">${_chunkB5YM4JRBcjs.escapeHtml.call(void 0, issue.title)}</span> | ||
| </div> | ||
| <div style="font-size:11px;color:var(--tb-text-muted, #888);line-height:1.5;white-space:pre-wrap">${_chunkB5YM4JRBcjs.escapeHtml.call(void 0, desc)}</div> | ||
| ${issue.helpUrl ? `<a href="${issue.helpUrl}" target="_blank" rel="noopener noreferrer" style="font-size:11px;color:var(--tb-accent, #6366F1);margin-top:4px;display:inline-block">Learn more \u2192</a>` : ""} | ||
| ${samplesHtml} | ||
| </div> | ||
| <div class="tb-issue-actions"> | ||
| ${issue.selector ? `<button data-action="locate" title="Highlight on page">\u{1F4CD} Locate</button>` : ""} | ||
| <button data-action="file" title="File as bug ticket">File ticket</button> | ||
| <button data-action="dismiss" title="Dismiss for this session">Dismiss</button> | ||
| </div> | ||
| </div> | ||
| `; | ||
| } | ||
| function _locate(issue) { | ||
| var _a; | ||
| if (!issue.selector) return; | ||
| let el = null; | ||
| try { | ||
| el = document.querySelector(issue.selector); | ||
| } catch (e) { | ||
| el = null; | ||
| } | ||
| if (!el) return; | ||
| (_a = document.getElementById(`${PANEL_ID}-overlay`)) == null ? void 0 : _a.remove(); | ||
| _isOpen = false; | ||
| el.scrollIntoView({ behavior: "smooth", block: "center" }); | ||
| const htmlEl = el; | ||
| const prevOutline = htmlEl.style.outline; | ||
| const prevTransition = htmlEl.style.transition; | ||
| htmlEl.style.transition = "outline 0.2s, box-shadow 0.2s"; | ||
| htmlEl.style.outline = "3px solid #6366F1"; | ||
| htmlEl.style.boxShadow = "0 0 0 6px rgba(99,102,241,0.35)"; | ||
| setTimeout(() => { | ||
| htmlEl.style.outline = prevOutline; | ||
| htmlEl.style.boxShadow = ""; | ||
| htmlEl.style.transition = prevTransition; | ||
| }, 2400); | ||
| } | ||
| function _fileAsBug(issue) { | ||
| var _a; | ||
| const root = _root || document.getElementById("tracebug-root"); | ||
| if (!root) return; | ||
| (_a = document.getElementById(`${PANEL_ID}-overlay`)) == null ? void 0 : _a.remove(); | ||
| _isOpen = false; | ||
| Promise.resolve().then(() => _interopRequireWildcard(require("./quick-bug-BIXBRAXH.cjs"))).then((m) => { | ||
| m.showQuickBugCapture(root, { | ||
| prefilledTitle: issue.title, | ||
| prefilledDescription: _bugDescriptionFromIssue(issue) | ||
| }).catch(() => { | ||
| }); | ||
| }); | ||
| } | ||
| function _bugDescriptionFromIssue(issue) { | ||
| const lines = []; | ||
| lines.push(`> Detected by TraceBug auto-scanner: **${DETECTOR_LABELS[issue.detector]}** (${issue.severity})`); | ||
| lines.push(""); | ||
| lines.push(issue.description); | ||
| if (issue.selector) lines.push(` | ||
| **Selector:** \`${issue.selector}\``); | ||
| if (issue.url) lines.push(`**URL:** \`${issue.url}\``); | ||
| if (issue.helpUrl) lines.push(`**Reference:** ${issue.helpUrl}`); | ||
| return lines.join("\n"); | ||
| } | ||
| exports.isIssuesPanelOpen = isIssuesPanelOpen; exports.showIssuesPanel = showIssuesPanel; | ||
| //# sourceMappingURL=issues-panel-23ZYQZ2E.cjs.map |
| {"version":3,"sources":["d:\\Project\\TraceBug-ai\\dist\\issues-panel-23ZYQZ2E.cjs"],"names":[],"mappings":"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E,QAAQ,CAAC;AAChB,EAAE,CAAC;AACH,EAAE,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC;AAClC;AACA,SAAS,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE;AAC5B,EAAE,IAAI,EAAE;AACR,EAAE,QAAQ,EAAE,IAAI;AAChB,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAC,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,8GAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAA;AACA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA;AACA;AACA;AACA;AACA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA,oBAAA;AACA;AACA;AACA,WAAA;AACA,iBAAA;AACA,WAAA;AACA;AACA,iBAAA;AACA,EAAA;AACA,gDAAA;AACA,mDAAA;AACA;AACA;AACA,wCAAA;AACA,6FAAA;AACA;AACA,0GAAA;AACA,QAAA;AACA,QAAA;AACA;AACA;AACA,QAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,gBAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA","file":"D:\\Project\\TraceBug-ai\\dist\\issues-panel-23ZYQZ2E.cjs","sourcesContent":[null]} |
| import { | ||
| dismissIssue, | ||
| getIssues, | ||
| scan | ||
| } from "./chunk-XVPLXS7L.js"; | ||
| import { | ||
| escapeHtml | ||
| } from "./chunk-2ZJIB656.js"; | ||
| // src/ui/issues-panel.ts | ||
| var PANEL_ID = "tracebug-issues-panel"; | ||
| var STYLE_ID = "tracebug-issues-panel-styles"; | ||
| var _isOpen = false; | ||
| var _root = null; | ||
| var SEVERITY_COLORS = { | ||
| critical: { bg: "#7f1d1d", fg: "#fee2e2", border: "#dc2626" }, | ||
| serious: { bg: "#7c2d12", fg: "#fed7aa", border: "#ea580c" }, | ||
| moderate: { bg: "#713f12", fg: "#fde68a", border: "#ca8a04" }, | ||
| minor: { bg: "#1e3a8a", fg: "#bfdbfe", border: "#2563eb" } | ||
| }; | ||
| var DETECTOR_LABELS = { | ||
| "axe-a11y": "A11y", | ||
| "broken-image": "Broken image", | ||
| "mixed-content": "Mixed content", | ||
| "console-error": "JS error", | ||
| "slow-api": "Slow API", | ||
| "failed-request": "Failed request", | ||
| "frustration-rage": "Rage clicks", | ||
| "frustration-dead": "Dead click", | ||
| "frustration-abandon": "Form abandoned", | ||
| "frustration-error-correlated": "Click \u2192 error" | ||
| }; | ||
| function isIssuesPanelOpen() { | ||
| return _isOpen; | ||
| } | ||
| async function showIssuesPanel(root, options) { | ||
| var _a; | ||
| if (_isOpen) return; | ||
| _root = root; | ||
| _injectStyles(); | ||
| _open(root, { issues: [], loading: true }); | ||
| try { | ||
| if ((_a = options == null ? void 0 : options.rescan) != null ? _a : true) { | ||
| await scan(); | ||
| } | ||
| } catch (err) { | ||
| console.warn("[TraceBug] Scan failed:", err); | ||
| } | ||
| _renderBody(getIssues()); | ||
| } | ||
| function _injectStyles() { | ||
| if (document.getElementById(STYLE_ID)) return; | ||
| const style = document.createElement("style"); | ||
| style.id = STYLE_ID; | ||
| style.textContent = ` | ||
| @keyframes tracebug-issue-locate-flash { | ||
| 0%, 100% { box-shadow: 0 0 0 0 rgba(99,102,241,0.0); outline: 2px solid transparent; } | ||
| 30% { box-shadow: 0 0 0 6px rgba(99,102,241,0.5); outline: 2px solid #6366F1; } | ||
| } | ||
| #${PANEL_ID}-overlay { | ||
| position: fixed !important; | ||
| inset: 0 !important; | ||
| z-index: 2147483647 !important; | ||
| background: rgba(0,0,0,0.75) !important; | ||
| backdrop-filter: blur(6px) !important; | ||
| display: flex !important; | ||
| align-items: center !important; | ||
| justify-content: center !important; | ||
| padding: 20px !important; | ||
| pointer-events: auto !important; | ||
| box-sizing: border-box !important; | ||
| } | ||
| #${PANEL_ID} { | ||
| background: var(--tb-bg-secondary, #1a1a2e) !important; | ||
| border: 1px solid var(--tb-border-hover, #3a3a5e) !important; | ||
| border-radius: var(--tb-radius-lg, 12px) !important; | ||
| width: 100% !important; | ||
| max-width: 720px !important; | ||
| max-height: 90vh !important; | ||
| display: flex !important; | ||
| flex-direction: column !important; | ||
| overflow: hidden !important; | ||
| font-family: var(--tb-font-family, system-ui, -apple-system, sans-serif) !important; | ||
| color: var(--tb-text-primary, #e0e0e0) !important; | ||
| box-sizing: border-box !important; | ||
| box-shadow: 0 20px 60px rgba(0,0,0,0.5) !important; | ||
| } | ||
| #${PANEL_ID} *, #${PANEL_ID} *::before, #${PANEL_ID} *::after { box-sizing: border-box !important; } | ||
| #${PANEL_ID} button { font-family: inherit !important; cursor: pointer !important; } | ||
| #${PANEL_ID} .tb-issue-row { | ||
| padding: 12px 14px; | ||
| border-top: 1px solid var(--tb-border, #2a2a3e); | ||
| display: flex; | ||
| gap: 12px; | ||
| align-items: flex-start; | ||
| } | ||
| #${PANEL_ID} .tb-issue-row:hover { background: var(--tb-bg-primary, #0f0f1a); } | ||
| #${PANEL_ID} .tb-sev-badge { | ||
| font-size: 10px; | ||
| font-weight: 700; | ||
| padding: 3px 7px; | ||
| border-radius: 4px; | ||
| letter-spacing: 0.4px; | ||
| text-transform: uppercase; | ||
| flex-shrink: 0; | ||
| border: 1px solid; | ||
| } | ||
| #${PANEL_ID} .tb-detector-tag { | ||
| font-size: 10px; | ||
| color: var(--tb-text-muted, #888); | ||
| background: var(--tb-bg-primary, #0f0f1a); | ||
| border: 1px solid var(--tb-border, #2a2a3e); | ||
| padding: 2px 6px; | ||
| border-radius: 4px; | ||
| flex-shrink: 0; | ||
| } | ||
| #${PANEL_ID} .tb-issue-actions { | ||
| display: flex; | ||
| gap: 6px; | ||
| flex-shrink: 0; | ||
| } | ||
| #${PANEL_ID} .tb-issue-actions button { | ||
| background: transparent; | ||
| color: var(--tb-text-secondary, #aaa); | ||
| border: 1px solid var(--tb-border, #2a2a3e); | ||
| border-radius: 4px; | ||
| padding: 4px 10px; | ||
| font-size: 11px; | ||
| transition: all 0.15s; | ||
| } | ||
| #${PANEL_ID} .tb-issue-actions button:hover { | ||
| background: var(--tb-btn-hover, #ffffff15); | ||
| color: var(--tb-text-primary, #fff); | ||
| } | ||
| #${PANEL_ID} .tb-issue-actions button[data-action="file"] { | ||
| background: var(--tb-accent, #6366F1); | ||
| color: #fff; | ||
| border-color: transparent; | ||
| } | ||
| #${PANEL_ID} .tb-issue-actions button[data-action="file"]:hover { opacity: 0.9; } | ||
| `; | ||
| document.head.appendChild(style); | ||
| } | ||
| function _open(root, state) { | ||
| var _a; | ||
| _isOpen = true; | ||
| (_a = document.getElementById(`${PANEL_ID}-overlay`)) == null ? void 0 : _a.remove(); | ||
| const overlay = document.createElement("div"); | ||
| overlay.id = `${PANEL_ID}-overlay`; | ||
| overlay.dataset.tracebug = "issues-panel-overlay"; | ||
| overlay.setAttribute("role", "dialog"); | ||
| overlay.setAttribute("aria-modal", "true"); | ||
| overlay.setAttribute("aria-label", "Page issues"); | ||
| const panel = document.createElement("div"); | ||
| panel.id = PANEL_ID; | ||
| panel.dataset.tracebug = "issues-panel"; | ||
| panel.innerHTML = _shellHtml(state); | ||
| overlay.appendChild(panel); | ||
| root.appendChild(overlay); | ||
| _wireShellHandlers(overlay, panel); | ||
| } | ||
| function _shellHtml(state) { | ||
| return ` | ||
| <div data-tb-issues="header" style="padding:16px 18px;display:flex;align-items:center;gap:12px;border-bottom:1px solid var(--tb-border, #2a2a3e)"> | ||
| <span style="font-size:20px">\u{1F50D}</span> | ||
| <div style="flex:1;min-width:0"> | ||
| <div style="font-size:16px;font-weight:700;color:var(--tb-text-primary, #fff)">Page Issues</div> | ||
| <div data-tb-issues="subtitle" style="font-size:11px;color:var(--tb-text-muted, #888);margin-top:2px">${state.loading ? "Scanning\u2026" : "No scan yet"}</div> | ||
| </div> | ||
| <button data-tb-issues="rescan" style="background:transparent;color:var(--tb-text-secondary, #aaa);border:1px solid var(--tb-border, #2a2a3e);border-radius:6px;padding:6px 12px;font-size:12px;display:flex;align-items:center;gap:6px"> | ||
| <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-3-6.7"/><polyline points="21 3 21 9 15 9"/></svg> | ||
| Rescan | ||
| </button> | ||
| <button data-tb-issues="close" aria-label="Close" style="background:none;border:none;color:var(--tb-text-muted, #888);font-size:20px;padding:4px 8px;border-radius:6px">×</button> | ||
| </div> | ||
| <div data-tb-issues="body" style="flex:1;overflow-y:auto;min-height:200px"> | ||
| ${state.loading ? _loadingHtml() : ""} | ||
| </div> | ||
| `; | ||
| } | ||
| function _loadingHtml() { | ||
| return ` | ||
| <div style="padding:48px 20px;text-align:center;color:var(--tb-text-muted, #888);font-size:13px"> | ||
| <div style="font-size:24px;margin-bottom:8px">\u{1F50D}</div> | ||
| Scanning page for issues\u2026 | ||
| <div style="font-size:11px;margin-top:6px;opacity:0.7">Loading axe-core, checking images, network calls, JS errors\u2026</div> | ||
| </div> | ||
| `; | ||
| } | ||
| function _wireShellHandlers(overlay, panel) { | ||
| const close = () => { | ||
| _isOpen = false; | ||
| overlay.remove(); | ||
| document.removeEventListener("keydown", escHandler); | ||
| }; | ||
| panel.querySelector('[data-tb-issues="close"]').addEventListener("click", close); | ||
| overlay.addEventListener("click", (e) => { | ||
| if (e.target === overlay) close(); | ||
| }); | ||
| panel.querySelector('[data-tb-issues="rescan"]').addEventListener("click", async () => { | ||
| const body = panel.querySelector('[data-tb-issues="body"]'); | ||
| body.innerHTML = _loadingHtml(); | ||
| const sub = panel.querySelector('[data-tb-issues="subtitle"]'); | ||
| sub.textContent = "Scanning\u2026"; | ||
| await scan(); | ||
| _renderBody(getIssues()); | ||
| }); | ||
| const escHandler = (e) => { | ||
| if (e.key === "Escape") close(); | ||
| }; | ||
| document.addEventListener("keydown", escHandler); | ||
| } | ||
| function _renderBody(issues) { | ||
| const panel = document.getElementById(PANEL_ID); | ||
| if (!panel) return; | ||
| const body = panel.querySelector('[data-tb-issues="body"]'); | ||
| const subtitle = panel.querySelector('[data-tb-issues="subtitle"]'); | ||
| if (issues.length === 0) { | ||
| subtitle.textContent = "No issues found"; | ||
| body.innerHTML = ` | ||
| <div style="padding:48px 20px;text-align:center;color:var(--tb-text-muted, #888);font-size:13px"> | ||
| <div style="font-size:32px;margin-bottom:8px">\u2713</div> | ||
| Clean scan \u2014 no issues detected on this page. | ||
| <div style="font-size:11px;margin-top:6px;opacity:0.7">Includes a11y \xB7 broken images \xB7 mixed content \xB7 JS errors \xB7 failed/slow API calls</div> | ||
| </div> | ||
| `; | ||
| return; | ||
| } | ||
| const counts = {}; | ||
| for (const i of issues) counts[i.severity] = (counts[i.severity] || 0) + 1; | ||
| const summary = ["critical", "serious", "moderate", "minor"].filter((s) => counts[s]).map((s) => `${counts[s]} ${s}`).join(" \xB7 "); | ||
| subtitle.textContent = `${issues.length} issue${issues.length === 1 ? "" : "s"} \xB7 ${summary}`; | ||
| body.innerHTML = issues.map((issue) => _issueRowHtml(issue)).join(""); | ||
| body.querySelectorAll("[data-tb-issue-id]").forEach((row) => { | ||
| var _a, _b, _c; | ||
| const id = row.dataset.tbIssueId; | ||
| const issue = issues.find((i) => i.id === id); | ||
| if (!issue) return; | ||
| (_a = row.querySelector('[data-action="locate"]')) == null ? void 0 : _a.addEventListener("click", () => _locate(issue)); | ||
| (_b = row.querySelector('[data-action="dismiss"]')) == null ? void 0 : _b.addEventListener("click", () => { | ||
| dismissIssue(id); | ||
| _renderBody(getIssues()); | ||
| }); | ||
| (_c = row.querySelector('[data-action="file"]')) == null ? void 0 : _c.addEventListener("click", () => _fileAsBug(issue)); | ||
| }); | ||
| } | ||
| function _issueRowHtml(issue) { | ||
| const colors = SEVERITY_COLORS[issue.severity]; | ||
| const detectorLabel = DETECTOR_LABELS[issue.detector]; | ||
| const desc = issue.description.length > 240 ? issue.description.slice(0, 237) + "\u2026" : issue.description; | ||
| const repeats = (issue.occurrences || 1) > 1; | ||
| const samplesHtml = repeats && issue.contextSamples && issue.contextSamples.length > 0 ? `<details style="margin-top:6px;font-size:11px"> | ||
| <summary style="cursor:pointer;color:var(--tb-text-muted, #888)"> | ||
| View all ${issue.occurrences} contexts | ||
| </summary> | ||
| <ol style="margin:6px 0 0 20px;padding:0;color:var(--tb-text-secondary, #aaa);line-height:1.5"> | ||
| ${issue.contextSamples.map((s) => ` | ||
| <li>${new Date(s.timestamp).toLocaleTimeString()}${s.precedingAction ? ` \xB7 ${escapeHtml(s.precedingAction)}` : ""}</li> | ||
| `).join("")} | ||
| </ol> | ||
| </details>` : ""; | ||
| return ` | ||
| <div class="tb-issue-row" data-tb-issue-id="${issue.id}"> | ||
| <span class="tb-sev-badge" style="background:${colors.bg};color:${colors.fg};border-color:${colors.border}">${issue.severity}</span> | ||
| <div style="flex:1;min-width:0"> | ||
| <div style="display:flex;align-items:center;gap:6px;margin-bottom:4px;flex-wrap:wrap"> | ||
| <span class="tb-detector-tag">${detectorLabel}</span> | ||
| <span style="font-size:13px;color:var(--tb-text-primary, #e0e0e0);font-weight:500">${escapeHtml(issue.title)}</span> | ||
| </div> | ||
| <div style="font-size:11px;color:var(--tb-text-muted, #888);line-height:1.5;white-space:pre-wrap">${escapeHtml(desc)}</div> | ||
| ${issue.helpUrl ? `<a href="${issue.helpUrl}" target="_blank" rel="noopener noreferrer" style="font-size:11px;color:var(--tb-accent, #6366F1);margin-top:4px;display:inline-block">Learn more \u2192</a>` : ""} | ||
| ${samplesHtml} | ||
| </div> | ||
| <div class="tb-issue-actions"> | ||
| ${issue.selector ? `<button data-action="locate" title="Highlight on page">\u{1F4CD} Locate</button>` : ""} | ||
| <button data-action="file" title="File as bug ticket">File ticket</button> | ||
| <button data-action="dismiss" title="Dismiss for this session">Dismiss</button> | ||
| </div> | ||
| </div> | ||
| `; | ||
| } | ||
| function _locate(issue) { | ||
| var _a; | ||
| if (!issue.selector) return; | ||
| let el = null; | ||
| try { | ||
| el = document.querySelector(issue.selector); | ||
| } catch (e) { | ||
| el = null; | ||
| } | ||
| if (!el) return; | ||
| (_a = document.getElementById(`${PANEL_ID}-overlay`)) == null ? void 0 : _a.remove(); | ||
| _isOpen = false; | ||
| el.scrollIntoView({ behavior: "smooth", block: "center" }); | ||
| const htmlEl = el; | ||
| const prevOutline = htmlEl.style.outline; | ||
| const prevTransition = htmlEl.style.transition; | ||
| htmlEl.style.transition = "outline 0.2s, box-shadow 0.2s"; | ||
| htmlEl.style.outline = "3px solid #6366F1"; | ||
| htmlEl.style.boxShadow = "0 0 0 6px rgba(99,102,241,0.35)"; | ||
| setTimeout(() => { | ||
| htmlEl.style.outline = prevOutline; | ||
| htmlEl.style.boxShadow = ""; | ||
| htmlEl.style.transition = prevTransition; | ||
| }, 2400); | ||
| } | ||
| function _fileAsBug(issue) { | ||
| var _a; | ||
| const root = _root || document.getElementById("tracebug-root"); | ||
| if (!root) return; | ||
| (_a = document.getElementById(`${PANEL_ID}-overlay`)) == null ? void 0 : _a.remove(); | ||
| _isOpen = false; | ||
| import("./quick-bug-XNRMA2KO.js").then((m) => { | ||
| m.showQuickBugCapture(root, { | ||
| prefilledTitle: issue.title, | ||
| prefilledDescription: _bugDescriptionFromIssue(issue) | ||
| }).catch(() => { | ||
| }); | ||
| }); | ||
| } | ||
| function _bugDescriptionFromIssue(issue) { | ||
| const lines = []; | ||
| lines.push(`> Detected by TraceBug auto-scanner: **${DETECTOR_LABELS[issue.detector]}** (${issue.severity})`); | ||
| lines.push(""); | ||
| lines.push(issue.description); | ||
| if (issue.selector) lines.push(` | ||
| **Selector:** \`${issue.selector}\``); | ||
| if (issue.url) lines.push(`**URL:** \`${issue.url}\``); | ||
| if (issue.helpUrl) lines.push(`**Reference:** ${issue.helpUrl}`); | ||
| return lines.join("\n"); | ||
| } | ||
| export { | ||
| isIssuesPanelOpen, | ||
| showIssuesPanel | ||
| }; | ||
| //# sourceMappingURL=issues-panel-425S4HCZ.js.map |
| {"version":3,"sources":["../src/ui/issues-panel.ts"],"sourcesContent":["// ── Issues Panel ──────────────────────────────────────────────────────────\r\n// Modal that lists scanner findings grouped by severity. Each row offers\r\n// \"Locate\" (flash the offending element on the page) and \"File ticket\"\r\n// (open the Quick Bug modal pre-filled with the issue's context).\r\n//\r\n// Styles are injected in <head> with !important so host-page CSS resets\r\n// (Tailwind preflight, Bootstrap, etc.) can't squish the layout.\r\n\r\nimport { Issue } from \"../types\";\r\nimport { dismissIssue, getIssues, scan } from \"../scanner\";\r\nimport { escapeHtml } from \"./helpers\";\r\n\r\nconst PANEL_ID = \"tracebug-issues-panel\";\r\nconst STYLE_ID = \"tracebug-issues-panel-styles\";\r\n\r\nlet _isOpen = false;\r\nlet _root: HTMLElement | null = null;\r\n\r\nconst SEVERITY_COLORS: Record<Issue[\"severity\"], { bg: string; fg: string; border: string }> = {\r\n critical: { bg: \"#7f1d1d\", fg: \"#fee2e2\", border: \"#dc2626\" },\r\n serious: { bg: \"#7c2d12\", fg: \"#fed7aa\", border: \"#ea580c\" },\r\n moderate: { bg: \"#713f12\", fg: \"#fde68a\", border: \"#ca8a04\" },\r\n minor: { bg: \"#1e3a8a\", fg: \"#bfdbfe\", border: \"#2563eb\" },\r\n};\r\n\r\nconst DETECTOR_LABELS: Record<Issue[\"detector\"], string> = {\r\n \"axe-a11y\": \"A11y\",\r\n \"broken-image\": \"Broken image\",\r\n \"mixed-content\": \"Mixed content\",\r\n \"console-error\": \"JS error\",\r\n \"slow-api\": \"Slow API\",\r\n \"failed-request\": \"Failed request\",\r\n \"frustration-rage\": \"Rage clicks\",\r\n \"frustration-dead\": \"Dead click\",\r\n \"frustration-abandon\": \"Form abandoned\",\r\n \"frustration-error-correlated\": \"Click → error\",\r\n};\r\n\r\nexport function isIssuesPanelOpen(): boolean {\r\n return _isOpen;\r\n}\r\n\r\n/**\r\n * Run a scan (or use cached results) and open the panel. The scan promise\r\n * resolves before we render so the panel never flashes empty.\r\n */\r\nexport async function showIssuesPanel(\r\n root: HTMLElement,\r\n options?: { rescan?: boolean }\r\n): Promise<void> {\r\n if (_isOpen) return;\r\n _root = root;\r\n _injectStyles();\r\n\r\n // Render shell with a loading state so the user gets immediate feedback.\r\n _open(root, { issues: [], loading: true });\r\n try {\r\n if (options?.rescan ?? true) {\r\n await scan();\r\n }\r\n } catch (err) {\r\n console.warn(\"[TraceBug] Scan failed:\", err);\r\n }\r\n _renderBody(getIssues());\r\n}\r\n\r\nfunction _injectStyles(): void {\r\n if (document.getElementById(STYLE_ID)) return;\r\n const style = document.createElement(\"style\");\r\n style.id = STYLE_ID;\r\n style.textContent = `\r\n @keyframes tracebug-issue-locate-flash {\r\n 0%, 100% { box-shadow: 0 0 0 0 rgba(99,102,241,0.0); outline: 2px solid transparent; }\r\n 30% { box-shadow: 0 0 0 6px rgba(99,102,241,0.5); outline: 2px solid #6366F1; }\r\n }\r\n #${PANEL_ID}-overlay {\r\n position: fixed !important;\r\n inset: 0 !important;\r\n z-index: 2147483647 !important;\r\n background: rgba(0,0,0,0.75) !important;\r\n backdrop-filter: blur(6px) !important;\r\n display: flex !important;\r\n align-items: center !important;\r\n justify-content: center !important;\r\n padding: 20px !important;\r\n pointer-events: auto !important;\r\n box-sizing: border-box !important;\r\n }\r\n #${PANEL_ID} {\r\n background: var(--tb-bg-secondary, #1a1a2e) !important;\r\n border: 1px solid var(--tb-border-hover, #3a3a5e) !important;\r\n border-radius: var(--tb-radius-lg, 12px) !important;\r\n width: 100% !important;\r\n max-width: 720px !important;\r\n max-height: 90vh !important;\r\n display: flex !important;\r\n flex-direction: column !important;\r\n overflow: hidden !important;\r\n font-family: var(--tb-font-family, system-ui, -apple-system, sans-serif) !important;\r\n color: var(--tb-text-primary, #e0e0e0) !important;\r\n box-sizing: border-box !important;\r\n box-shadow: 0 20px 60px rgba(0,0,0,0.5) !important;\r\n }\r\n #${PANEL_ID} *, #${PANEL_ID} *::before, #${PANEL_ID} *::after { box-sizing: border-box !important; }\r\n #${PANEL_ID} button { font-family: inherit !important; cursor: pointer !important; }\r\n #${PANEL_ID} .tb-issue-row {\r\n padding: 12px 14px;\r\n border-top: 1px solid var(--tb-border, #2a2a3e);\r\n display: flex;\r\n gap: 12px;\r\n align-items: flex-start;\r\n }\r\n #${PANEL_ID} .tb-issue-row:hover { background: var(--tb-bg-primary, #0f0f1a); }\r\n #${PANEL_ID} .tb-sev-badge {\r\n font-size: 10px;\r\n font-weight: 700;\r\n padding: 3px 7px;\r\n border-radius: 4px;\r\n letter-spacing: 0.4px;\r\n text-transform: uppercase;\r\n flex-shrink: 0;\r\n border: 1px solid;\r\n }\r\n #${PANEL_ID} .tb-detector-tag {\r\n font-size: 10px;\r\n color: var(--tb-text-muted, #888);\r\n background: var(--tb-bg-primary, #0f0f1a);\r\n border: 1px solid var(--tb-border, #2a2a3e);\r\n padding: 2px 6px;\r\n border-radius: 4px;\r\n flex-shrink: 0;\r\n }\r\n #${PANEL_ID} .tb-issue-actions {\r\n display: flex;\r\n gap: 6px;\r\n flex-shrink: 0;\r\n }\r\n #${PANEL_ID} .tb-issue-actions button {\r\n background: transparent;\r\n color: var(--tb-text-secondary, #aaa);\r\n border: 1px solid var(--tb-border, #2a2a3e);\r\n border-radius: 4px;\r\n padding: 4px 10px;\r\n font-size: 11px;\r\n transition: all 0.15s;\r\n }\r\n #${PANEL_ID} .tb-issue-actions button:hover {\r\n background: var(--tb-btn-hover, #ffffff15);\r\n color: var(--tb-text-primary, #fff);\r\n }\r\n #${PANEL_ID} .tb-issue-actions button[data-action=\"file\"] {\r\n background: var(--tb-accent, #6366F1);\r\n color: #fff;\r\n border-color: transparent;\r\n }\r\n #${PANEL_ID} .tb-issue-actions button[data-action=\"file\"]:hover { opacity: 0.9; }\r\n `;\r\n document.head.appendChild(style);\r\n}\r\n\r\nfunction _open(root: HTMLElement, state: { issues: Issue[]; loading: boolean }): void {\r\n _isOpen = true;\r\n // Remove any prior overlay (defensive — should not happen).\r\n document.getElementById(`${PANEL_ID}-overlay`)?.remove();\r\n\r\n const overlay = document.createElement(\"div\");\r\n overlay.id = `${PANEL_ID}-overlay`;\r\n overlay.dataset.tracebug = \"issues-panel-overlay\";\r\n overlay.setAttribute(\"role\", \"dialog\");\r\n overlay.setAttribute(\"aria-modal\", \"true\");\r\n overlay.setAttribute(\"aria-label\", \"Page issues\");\r\n\r\n const panel = document.createElement(\"div\");\r\n panel.id = PANEL_ID;\r\n panel.dataset.tracebug = \"issues-panel\";\r\n\r\n panel.innerHTML = _shellHtml(state);\r\n\r\n overlay.appendChild(panel);\r\n root.appendChild(overlay);\r\n\r\n _wireShellHandlers(overlay, panel);\r\n}\r\n\r\nfunction _shellHtml(state: { issues: Issue[]; loading: boolean }): string {\r\n return `\r\n <div data-tb-issues=\"header\" style=\"padding:16px 18px;display:flex;align-items:center;gap:12px;border-bottom:1px solid var(--tb-border, #2a2a3e)\">\r\n <span style=\"font-size:20px\">🔍</span>\r\n <div style=\"flex:1;min-width:0\">\r\n <div style=\"font-size:16px;font-weight:700;color:var(--tb-text-primary, #fff)\">Page Issues</div>\r\n <div data-tb-issues=\"subtitle\" style=\"font-size:11px;color:var(--tb-text-muted, #888);margin-top:2px\">${state.loading ? \"Scanning…\" : \"No scan yet\"}</div>\r\n </div>\r\n <button data-tb-issues=\"rescan\" style=\"background:transparent;color:var(--tb-text-secondary, #aaa);border:1px solid var(--tb-border, #2a2a3e);border-radius:6px;padding:6px 12px;font-size:12px;display:flex;align-items:center;gap:6px\">\r\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 12a9 9 0 1 1-3-6.7\"/><polyline points=\"21 3 21 9 15 9\"/></svg>\r\n Rescan\r\n </button>\r\n <button data-tb-issues=\"close\" aria-label=\"Close\" style=\"background:none;border:none;color:var(--tb-text-muted, #888);font-size:20px;padding:4px 8px;border-radius:6px\">×</button>\r\n </div>\r\n <div data-tb-issues=\"body\" style=\"flex:1;overflow-y:auto;min-height:200px\">\r\n ${state.loading ? _loadingHtml() : \"\"}\r\n </div>\r\n `;\r\n}\r\n\r\nfunction _loadingHtml(): string {\r\n return `\r\n <div style=\"padding:48px 20px;text-align:center;color:var(--tb-text-muted, #888);font-size:13px\">\r\n <div style=\"font-size:24px;margin-bottom:8px\">🔍</div>\r\n Scanning page for issues…\r\n <div style=\"font-size:11px;margin-top:6px;opacity:0.7\">Loading axe-core, checking images, network calls, JS errors…</div>\r\n </div>\r\n `;\r\n}\r\n\r\nfunction _wireShellHandlers(overlay: HTMLElement, panel: HTMLElement): void {\r\n const close = () => {\r\n _isOpen = false;\r\n overlay.remove();\r\n document.removeEventListener(\"keydown\", escHandler);\r\n };\r\n panel.querySelector('[data-tb-issues=\"close\"]')!.addEventListener(\"click\", close);\r\n overlay.addEventListener(\"click\", (e) => { if (e.target === overlay) close(); });\r\n\r\n panel.querySelector('[data-tb-issues=\"rescan\"]')!.addEventListener(\"click\", async () => {\r\n const body = panel.querySelector('[data-tb-issues=\"body\"]') as HTMLElement;\r\n body.innerHTML = _loadingHtml();\r\n const sub = panel.querySelector('[data-tb-issues=\"subtitle\"]') as HTMLElement;\r\n sub.textContent = \"Scanning…\";\r\n await scan();\r\n _renderBody(getIssues());\r\n });\r\n\r\n const escHandler = (e: KeyboardEvent) => { if (e.key === \"Escape\") close(); };\r\n document.addEventListener(\"keydown\", escHandler);\r\n}\r\n\r\nfunction _renderBody(issues: Issue[]): void {\r\n const panel = document.getElementById(PANEL_ID);\r\n if (!panel) return;\r\n const body = panel.querySelector('[data-tb-issues=\"body\"]') as HTMLElement;\r\n const subtitle = panel.querySelector('[data-tb-issues=\"subtitle\"]') as HTMLElement;\r\n\r\n if (issues.length === 0) {\r\n subtitle.textContent = \"No issues found\";\r\n body.innerHTML = `\r\n <div style=\"padding:48px 20px;text-align:center;color:var(--tb-text-muted, #888);font-size:13px\">\r\n <div style=\"font-size:32px;margin-bottom:8px\">✓</div>\r\n Clean scan — no issues detected on this page.\r\n <div style=\"font-size:11px;margin-top:6px;opacity:0.7\">Includes a11y · broken images · mixed content · JS errors · failed/slow API calls</div>\r\n </div>\r\n `;\r\n return;\r\n }\r\n\r\n // Severity counts in subtitle\r\n const counts: Record<string, number> = {};\r\n for (const i of issues) counts[i.severity] = (counts[i.severity] || 0) + 1;\r\n const summary = [\"critical\", \"serious\", \"moderate\", \"minor\"]\r\n .filter(s => counts[s])\r\n .map(s => `${counts[s]} ${s}`)\r\n .join(\" · \");\r\n subtitle.textContent = `${issues.length} issue${issues.length === 1 ? \"\" : \"s\"} · ${summary}`;\r\n\r\n body.innerHTML = issues.map(issue => _issueRowHtml(issue)).join(\"\");\r\n\r\n // Wire row actions\r\n body.querySelectorAll<HTMLElement>(\"[data-tb-issue-id]\").forEach(row => {\r\n const id = row.dataset.tbIssueId!;\r\n const issue = issues.find(i => i.id === id);\r\n if (!issue) return;\r\n\r\n row.querySelector('[data-action=\"locate\"]')?.addEventListener(\"click\", () => _locate(issue));\r\n row.querySelector('[data-action=\"dismiss\"]')?.addEventListener(\"click\", () => {\r\n dismissIssue(id);\r\n _renderBody(getIssues());\r\n });\r\n row.querySelector('[data-action=\"file\"]')?.addEventListener(\"click\", () => _fileAsBug(issue));\r\n });\r\n}\r\n\r\nfunction _issueRowHtml(issue: Issue): string {\r\n const colors = SEVERITY_COLORS[issue.severity];\r\n const detectorLabel = DETECTOR_LABELS[issue.detector];\r\n const desc = issue.description.length > 240\r\n ? issue.description.slice(0, 237) + \"…\"\r\n : issue.description;\r\n\r\n // Fingerprint dedup: when occurrences > 1 show a collapsible <details>\r\n // listing each occurrence with its preceding action.\r\n const repeats = (issue.occurrences || 1) > 1;\r\n const samplesHtml = repeats && issue.contextSamples && issue.contextSamples.length > 0\r\n ? `<details style=\"margin-top:6px;font-size:11px\">\r\n <summary style=\"cursor:pointer;color:var(--tb-text-muted, #888)\">\r\n View all ${issue.occurrences} contexts\r\n </summary>\r\n <ol style=\"margin:6px 0 0 20px;padding:0;color:var(--tb-text-secondary, #aaa);line-height:1.5\">\r\n ${issue.contextSamples.map(s => `\r\n <li>${new Date(s.timestamp).toLocaleTimeString()}${s.precedingAction ? ` · ${escapeHtml(s.precedingAction)}` : \"\"}</li>\r\n `).join(\"\")}\r\n </ol>\r\n </details>`\r\n : \"\";\r\n\r\n return `\r\n <div class=\"tb-issue-row\" data-tb-issue-id=\"${issue.id}\">\r\n <span class=\"tb-sev-badge\" style=\"background:${colors.bg};color:${colors.fg};border-color:${colors.border}\">${issue.severity}</span>\r\n <div style=\"flex:1;min-width:0\">\r\n <div style=\"display:flex;align-items:center;gap:6px;margin-bottom:4px;flex-wrap:wrap\">\r\n <span class=\"tb-detector-tag\">${detectorLabel}</span>\r\n <span style=\"font-size:13px;color:var(--tb-text-primary, #e0e0e0);font-weight:500\">${escapeHtml(issue.title)}</span>\r\n </div>\r\n <div style=\"font-size:11px;color:var(--tb-text-muted, #888);line-height:1.5;white-space:pre-wrap\">${escapeHtml(desc)}</div>\r\n ${issue.helpUrl ? `<a href=\"${issue.helpUrl}\" target=\"_blank\" rel=\"noopener noreferrer\" style=\"font-size:11px;color:var(--tb-accent, #6366F1);margin-top:4px;display:inline-block\">Learn more →</a>` : \"\"}\r\n ${samplesHtml}\r\n </div>\r\n <div class=\"tb-issue-actions\">\r\n ${issue.selector ? `<button data-action=\"locate\" title=\"Highlight on page\">📍 Locate</button>` : \"\"}\r\n <button data-action=\"file\" title=\"File as bug ticket\">File ticket</button>\r\n <button data-action=\"dismiss\" title=\"Dismiss for this session\">Dismiss</button>\r\n </div>\r\n </div>\r\n `;\r\n}\r\n\r\n/**\r\n * Briefly outline the offending element. Closes the panel so the page is\r\n * visible. Restores after 2.4s — non-destructive flash.\r\n */\r\nfunction _locate(issue: Issue): void {\r\n if (!issue.selector) return;\r\n let el: Element | null = null;\r\n try {\r\n el = document.querySelector(issue.selector);\r\n } catch {\r\n el = null;\r\n }\r\n if (!el) return;\r\n\r\n // Close the panel so the user can see the page.\r\n document.getElementById(`${PANEL_ID}-overlay`)?.remove();\r\n _isOpen = false;\r\n\r\n el.scrollIntoView({ behavior: \"smooth\", block: \"center\" });\r\n const htmlEl = el as HTMLElement;\r\n const prevOutline = htmlEl.style.outline;\r\n const prevTransition = htmlEl.style.transition;\r\n htmlEl.style.transition = \"outline 0.2s, box-shadow 0.2s\";\r\n htmlEl.style.outline = \"3px solid #6366F1\";\r\n htmlEl.style.boxShadow = \"0 0 0 6px rgba(99,102,241,0.35)\";\r\n setTimeout(() => {\r\n htmlEl.style.outline = prevOutline;\r\n htmlEl.style.boxShadow = \"\";\r\n htmlEl.style.transition = prevTransition;\r\n }, 2400);\r\n}\r\n\r\n/**\r\n * Pre-fill the Quick Bug modal with the issue's title + description as the\r\n * starting point for a ticket. Reuses the existing ticket-export pipeline.\r\n */\r\nfunction _fileAsBug(issue: Issue): void {\r\n const root = _root || document.getElementById(\"tracebug-root\");\r\n if (!root) return;\r\n // Close the issues panel.\r\n document.getElementById(`${PANEL_ID}-overlay`)?.remove();\r\n _isOpen = false;\r\n\r\n import(\"./quick-bug\").then(m => {\r\n m.showQuickBugCapture(root, {\r\n prefilledTitle: issue.title,\r\n prefilledDescription: _bugDescriptionFromIssue(issue),\r\n }).catch(() => {});\r\n });\r\n}\r\n\r\nfunction _bugDescriptionFromIssue(issue: Issue): string {\r\n const lines: string[] = [];\r\n lines.push(`> Detected by TraceBug auto-scanner: **${DETECTOR_LABELS[issue.detector]}** (${issue.severity})`);\r\n lines.push(\"\");\r\n lines.push(issue.description);\r\n if (issue.selector) lines.push(`\\n**Selector:** \\`${issue.selector}\\``);\r\n if (issue.url) lines.push(`**URL:** \\`${issue.url}\\``);\r\n if (issue.helpUrl) lines.push(`**Reference:** ${issue.helpUrl}`);\r\n return lines.join(\"\\n\");\r\n}\r\n"],"mappings":"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","names":[]} |
| "use strict";Object.defineProperty(exports, "__esModule", {value: true}); | ||
| var _chunk5OSPH5POcjs = require('./chunk-5OSPH5PO.cjs'); | ||
| require('./chunk-3HHUTYZ5.cjs'); | ||
| require('./chunk-B5YM4JRB.cjs'); | ||
| exports.getFocusableElements = _chunk5OSPH5POcjs.getFocusableElements; exports.isQuickBugOpen = _chunk5OSPH5POcjs.isQuickBugOpen; exports.refreshQuickBugCapture = _chunk5OSPH5POcjs.refreshQuickBugCapture; exports.setCloudEndpoint = _chunk5OSPH5POcjs.setCloudEndpoint; exports.setGithubRepo = _chunk5OSPH5POcjs.setGithubRepo; exports.showQuickBugCapture = _chunk5OSPH5POcjs.showQuickBugCapture; exports.trapModalTab = _chunk5OSPH5POcjs.trapModalTab; | ||
| //# sourceMappingURL=quick-bug-BIXBRAXH.cjs.map |
| {"version":3,"sources":["d:\\Project\\TraceBug-ai\\dist\\quick-bug-BIXBRAXH.cjs"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACF,wDAA6B;AAC7B,gCAA6B;AAC7B,gCAA6B;AAC7B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACF,gcAAC","file":"D:\\Project\\TraceBug-ai\\dist\\quick-bug-BIXBRAXH.cjs"} |
| import { | ||
| getFocusableElements, | ||
| isQuickBugOpen, | ||
| refreshQuickBugCapture, | ||
| setCloudEndpoint, | ||
| setGithubRepo, | ||
| showQuickBugCapture, | ||
| trapModalTab | ||
| } from "./chunk-UVKC2SFP.js"; | ||
| import "./chunk-L3Q7Y6QP.js"; | ||
| import "./chunk-2ZJIB656.js"; | ||
| export { | ||
| getFocusableElements, | ||
| isQuickBugOpen, | ||
| refreshQuickBugCapture, | ||
| setCloudEndpoint, | ||
| setGithubRepo, | ||
| showQuickBugCapture, | ||
| trapModalTab | ||
| }; | ||
| //# sourceMappingURL=quick-bug-XNRMA2KO.js.map |
| {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]} |
+337
-29
@@ -12,2 +12,167 @@ #!/usr/bin/env node | ||
| // cli/source-map.ts | ||
| import * as fs from "fs"; | ||
| import * as path from "path"; | ||
| function parseStackFrames(stack) { | ||
| const frames = []; | ||
| for (const line of String(stack || "").split("\n")) { | ||
| const m = V8_FRAME.exec(line) || FF_FRAME.exec(line); | ||
| if (!m) continue; | ||
| frames.push({ | ||
| fn: (m[1] || "").trim() || "(anonymous)", | ||
| file: m[2], | ||
| line: parseInt(m[3], 10), | ||
| column: parseInt(m[4], 10), | ||
| raw: line.trim() | ||
| }); | ||
| } | ||
| return frames; | ||
| } | ||
| function decodeVlq(s, pos) { | ||
| let result = 0; | ||
| let shift = 0; | ||
| let digit; | ||
| do { | ||
| digit = B64_MAP[s[pos++]]; | ||
| if (digit === void 0) throw new Error("bad VLQ"); | ||
| result += (digit & 31) << shift; | ||
| shift += 5; | ||
| } while (digit & 32); | ||
| const negative = result & 1; | ||
| result >>= 1; | ||
| return [negative ? -result : result, pos]; | ||
| } | ||
| function decodeMappings(map) { | ||
| const cached = _decodeCache.get(map); | ||
| if (cached) return cached; | ||
| const lines = map.mappings.split(";"); | ||
| const out = []; | ||
| let srcIdx = 0, srcLine = 0, srcCol = 0, nameIdx = 0; | ||
| for (const segs of lines) { | ||
| const lineSegs = []; | ||
| let genCol = 0, pos = 0; | ||
| while (pos < segs.length) { | ||
| if (segs[pos] === ",") { | ||
| pos++; | ||
| continue; | ||
| } | ||
| let v; | ||
| try { | ||
| [v, pos] = decodeVlq(segs, pos); | ||
| } catch { | ||
| break; | ||
| } | ||
| genCol += v; | ||
| if (pos < segs.length && segs[pos] !== ",") { | ||
| try { | ||
| [v, pos] = decodeVlq(segs, pos); | ||
| srcIdx += v; | ||
| [v, pos] = decodeVlq(segs, pos); | ||
| srcLine += v; | ||
| [v, pos] = decodeVlq(segs, pos); | ||
| srcCol += v; | ||
| let hasName = false; | ||
| if (pos < segs.length && segs[pos] !== ",") { | ||
| [v, pos] = decodeVlq(segs, pos); | ||
| nameIdx += v; | ||
| hasName = true; | ||
| } | ||
| lineSegs.push({ genCol, srcIdx, srcLine, srcCol, nameIdx, hasName }); | ||
| } catch { | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| out.push(lineSegs); | ||
| } | ||
| _decodeCache.set(map, out); | ||
| return out; | ||
| } | ||
| function resolvePosition(map, genLine, genColumn) { | ||
| const decoded = decodeMappings(map); | ||
| const targetLine = genLine - 1; | ||
| if (targetLine < 0 || targetLine >= decoded.length) return null; | ||
| let best = null; | ||
| for (const s of decoded[targetLine]) { | ||
| if (s.genCol <= genColumn - 1) best = s; | ||
| else { | ||
| if (best === null) best = s; | ||
| break; | ||
| } | ||
| } | ||
| if (!best) return null; | ||
| return { | ||
| source: (map.sourceRoot ? map.sourceRoot.replace(/\/?$/, "/") : "") + (map.sources[best.srcIdx] ?? "?"), | ||
| line: best.srcLine + 1, | ||
| column: best.srcCol, | ||
| name: best.hasName ? map.names?.[best.nameIdx] : void 0 | ||
| }; | ||
| } | ||
| function findMapFile(searchDir, bundleBasename) { | ||
| const target = bundleBasename + ".map"; | ||
| const walk = (dir, depth) => { | ||
| if (depth > MAP_SCAN_DEPTH) return null; | ||
| let entries; | ||
| try { | ||
| entries = fs.readdirSync(dir, { withFileTypes: true }); | ||
| } catch { | ||
| return null; | ||
| } | ||
| for (const e of entries) { | ||
| if (!e.isDirectory() && e.name === target) return path.join(dir, e.name); | ||
| } | ||
| for (const e of entries) { | ||
| if (e.isDirectory() && !MAP_SKIP_DIRS.has(e.name) && !e.name.startsWith(".")) { | ||
| const hit = walk(path.join(dir, e.name), depth + 1); | ||
| if (hit) return hit; | ||
| } | ||
| } | ||
| return null; | ||
| }; | ||
| return walk(searchDir, 0); | ||
| } | ||
| function resolveStackWithMaps(stack, searchDir) { | ||
| const frames = parseStackFrames(stack); | ||
| return frames.map((frame) => { | ||
| const basename3 = path.basename(frame.file.split("?")[0].split("#")[0]); | ||
| if (!/\.(m?js|cjs)$/.test(basename3)) return frame; | ||
| const pathKey = searchDir + "\0" + basename3; | ||
| let mapPath = _mapPathCache.get(pathKey); | ||
| if (mapPath === void 0) { | ||
| mapPath = findMapFile(searchDir, basename3); | ||
| _mapPathCache.set(pathKey, mapPath); | ||
| } | ||
| if (!mapPath) return frame; | ||
| let map = _mapDataCache.get(mapPath); | ||
| if (map === void 0) { | ||
| try { | ||
| const parsed = JSON.parse(fs.readFileSync(mapPath, "utf8")); | ||
| map = parsed && parsed.version === 3 && typeof parsed.mappings === "string" ? parsed : null; | ||
| } catch { | ||
| map = null; | ||
| } | ||
| _mapDataCache.set(mapPath, map); | ||
| } | ||
| if (!map) return frame; | ||
| const original = resolvePosition(map, frame.line, frame.column); | ||
| return original ? { ...frame, original, mapFile: mapPath } : { ...frame, mapFile: mapPath }; | ||
| }); | ||
| } | ||
| var V8_FRAME, FF_FRAME, B64, B64_MAP, _decodeCache, MAP_SKIP_DIRS, MAP_SCAN_DEPTH, _mapPathCache, _mapDataCache; | ||
| var init_source_map = __esm({ | ||
| "cli/source-map.ts"() { | ||
| "use strict"; | ||
| V8_FRAME = /^\s*at\s+(?:(.*?)\s+\()?((?:https?|file|webpack):\/\/[^\s)]+|[^\s)]+?):(\d+):(\d+)\)?\s*$/; | ||
| FF_FRAME = /^\s*(.*?)@((?:https?|file):\/\/[^\s]+|[^\s]+?):(\d+):(\d+)\s*$/; | ||
| B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | ||
| B64_MAP = {}; | ||
| for (let i = 0; i < B64.length; i++) B64_MAP[B64[i]] = i; | ||
| _decodeCache = /* @__PURE__ */ new WeakMap(); | ||
| MAP_SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".next/cache", "coverage"]); | ||
| MAP_SCAN_DEPTH = 6; | ||
| _mapPathCache = /* @__PURE__ */ new Map(); | ||
| _mapDataCache = /* @__PURE__ */ new Map(); | ||
| } | ||
| }); | ||
| // cli/mcp-server.ts | ||
@@ -29,9 +194,12 @@ var mcp_server_exports = {}; | ||
| toolGetConsoleErrors: () => toolGetConsoleErrors, | ||
| toolGetFixContext: () => toolGetFixContext, | ||
| toolGetNetworkActivity: () => toolGetNetworkActivity, | ||
| toolGetPlaywrightTest: () => toolGetPlaywrightTest, | ||
| toolGetReproSteps: () => toolGetReproSteps, | ||
| toolGetScreenshot: () => toolGetScreenshot, | ||
| toolListBugReports: () => toolListBugReports | ||
| toolListBugReports: () => toolListBugReports, | ||
| toolResolveStack: () => toolResolveStack | ||
| }); | ||
| import * as fs from "fs"; | ||
| import * as path from "path"; | ||
| import * as fs2 from "fs"; | ||
| import * as path2 from "path"; | ||
| import * as os from "os"; | ||
@@ -41,3 +209,3 @@ function parseReportFile(filePath) { | ||
| try { | ||
| raw = fs.readFileSync(filePath, "utf8"); | ||
| raw = fs2.readFileSync(filePath, "utf8"); | ||
| } catch { | ||
@@ -70,3 +238,3 @@ return null; | ||
| try { | ||
| entries = fs.readdirSync(dir, { withFileTypes: true }); | ||
| entries = fs2.readdirSync(dir, { withFileTypes: true }); | ||
| } catch { | ||
@@ -77,3 +245,3 @@ return []; | ||
| for (const entry of entries) { | ||
| const full = path.join(dir, entry.name); | ||
| const full = path2.join(dir, entry.name); | ||
| if (entry.isDirectory()) { | ||
@@ -94,3 +262,3 @@ if (!SKIP_DIRS.has(entry.name) && !entry.name.startsWith(".")) { | ||
| const home = os.homedir(); | ||
| raw.push(path.join(home, "Downloads"), path.join(home, "Desktop")); | ||
| raw.push(path2.join(home, "Downloads"), path2.join(home, "Desktop")); | ||
| } catch { | ||
@@ -103,3 +271,3 @@ } | ||
| try { | ||
| key = path.resolve(d); | ||
| key = path2.resolve(d); | ||
| } catch { | ||
@@ -111,3 +279,3 @@ continue; | ||
| try { | ||
| if (fs.statSync(d).isDirectory()) out.push(d); | ||
| if (fs2.statSync(d).isDirectory()) out.push(d); | ||
| } catch { | ||
@@ -122,3 +290,3 @@ } | ||
| const add = (f) => { | ||
| const key = path.resolve(f); | ||
| const key = path2.resolve(f); | ||
| if (!seen.has(key)) { | ||
@@ -130,5 +298,5 @@ seen.add(key); | ||
| for (const f of scanReportFiles(baseDir)) add(f); | ||
| const baseKey = path.resolve(baseDir); | ||
| const baseKey = path2.resolve(baseDir); | ||
| for (const dir of knownReportDirs(baseDir)) { | ||
| if (path.resolve(dir) === baseKey) continue; | ||
| if (path2.resolve(dir) === baseKey) continue; | ||
| for (const f of scanReportFiles(dir, 0, { maxDepth: 1, nameFilter: (n) => TB_EXPORT_NAME_RE.test(n) })) add(f); | ||
@@ -139,7 +307,7 @@ } | ||
| function displayReportName(baseDir, file) { | ||
| const rel = path.relative(baseDir, file); | ||
| return rel && !rel.startsWith("..") && !path.isAbsolute(rel) ? rel : path.basename(file); | ||
| const rel = path2.relative(baseDir, file); | ||
| return rel && !rel.startsWith("..") && !path2.isAbsolute(rel) ? rel : path2.basename(file); | ||
| } | ||
| function requireReport(baseDir, file) { | ||
| const directCandidates = path.isAbsolute(file) ? [file] : [path.join(baseDir, file), path.resolve(process.cwd(), file)]; | ||
| const directCandidates = path2.isAbsolute(file) ? [file] : [path2.join(baseDir, file), path2.resolve(process.cwd(), file)]; | ||
| for (const direct of directCandidates) { | ||
@@ -151,6 +319,6 @@ const p = parseReportFile(direct); | ||
| const query = file.toLowerCase(); | ||
| let match = candidates.find((c) => path.basename(c).toLowerCase() === query); | ||
| let match = candidates.find((c) => path2.basename(c).toLowerCase() === query); | ||
| if (!match) { | ||
| match = candidates.find((c) => { | ||
| if (path.basename(c).toLowerCase().includes(query)) return true; | ||
| if (path2.basename(c).toLowerCase().includes(query)) return true; | ||
| const p = parseReportFile(c); | ||
@@ -193,4 +361,20 @@ return p !== null && p.meta.title.toLowerCase().includes(query); | ||
| } | ||
| if (p.playwrightTest) { | ||
| steps.push( | ||
| "[HIGH] get_playwright_test \u2014 this report includes a generated Playwright spec that REPLAYS the session and asserts the captured failure is gone. Save it, run it to reproduce (red), then use it to verify your fix (green)." | ||
| ); | ||
| } | ||
| if ((p.elementAnnotations ?? []).length > 0) { | ||
| const n = p.elementAnnotations.length; | ||
| steps.push( | ||
| `[HIGH] This report carries ${n} element annotation${n === 1 ? "" : "s"} with computed-style evidence (included in get_bug_report) \u2014 for visual bugs, diff the captured typography/colors/spacing against the design tokens or CSS in this codebase.` | ||
| ); | ||
| } | ||
| if ((p.consoleErrors ?? []).some((e) => e.stack) || (p.consoleLogs ?? []).some((e) => e.stack)) { | ||
| steps.push( | ||
| "[MEDIUM] resolve_stack \u2014 maps minified stack frames to original source files/lines using .map files found in this repo (run from the project that built the app)." | ||
| ); | ||
| } | ||
| steps.push( | ||
| "Finally: cross-reference the findings with the codebase \u2014 search for the failing endpoint path, the symbols in the stack trace, or the UI text near the error \u2014 to locate the root cause and propose a fix." | ||
| "Finally: cross-reference the findings with the codebase \u2014 search for the failing endpoint path, the symbols in the stack trace, or the UI text near the error \u2014 to locate the root cause and propose a fix. get_fix_context bundles the failing request + triggering action + resolved stack in one call." | ||
| ); | ||
@@ -200,3 +384,3 @@ return steps; | ||
| function toolListBugReports(baseDir, args2) { | ||
| const scanDir = args2.dir ? path.isAbsolute(args2.dir) ? args2.dir : path.join(baseDir, args2.dir) : baseDir; | ||
| const scanDir = args2.dir ? path2.isAbsolute(args2.dir) ? args2.dir : path2.join(baseDir, args2.dir) : baseDir; | ||
| const files = args2.dir ? scanReportFiles(scanDir) : AUTO_DISCOVER ? collectReports(baseDir) : scanReportFiles(baseDir); | ||
@@ -245,2 +429,5 @@ const reports = files.map((file) => { | ||
| annotations: p.annotations ?? [], | ||
| // Computed-style receipts for design-QA bugs (selector, typography, | ||
| // colors, box model, WCAG contrast) — diff these against the codebase. | ||
| elementAnnotations: p.elementAnnotations ?? [], | ||
| consoleErrorCount: p.consoleErrors?.length ?? 0, | ||
@@ -325,2 +512,87 @@ networkFailureCount: p.networkErrors?.length ?? 0, | ||
| } | ||
| function toolGetPlaywrightTest(baseDir, args2) { | ||
| const { payload: p, resolved } = requireReport(baseDir, args2.file); | ||
| if (!p.playwrightTest) { | ||
| throw new Error( | ||
| "This report was exported before TraceBug 1.9 and has no generated test. Re-export the session from the TraceBug widget (Export .html) to include one." | ||
| ); | ||
| } | ||
| const filename = p.playwrightTestFilename || "tracebug-bug.spec.ts"; | ||
| return { | ||
| filename, | ||
| spec: p.playwrightTest, | ||
| sourceReport: path2.basename(resolved), | ||
| howToUse: [ | ||
| `1. Save the spec as tests/${filename} (or your e2e folder).`, | ||
| "2. Set BASE_URL to your running dev server if it differs from the captured origin.", | ||
| `3. Run: npx playwright test ${filename} \u2014 the test FAILS while the bug exists.`, | ||
| "4. Fix the bug, re-run \u2014 green means the captured failure is gone." | ||
| ] | ||
| }; | ||
| } | ||
| function collectStacks(p) { | ||
| const out = []; | ||
| for (const e of p.consoleLogs ?? []) { | ||
| if (e.stack) out.push({ message: e.message, stack: e.stack }); | ||
| } | ||
| if (!out.length) { | ||
| for (const e of p.consoleErrors ?? []) { | ||
| if (e.stack) out.push({ message: e.message, stack: e.stack }); | ||
| } | ||
| } | ||
| return out; | ||
| } | ||
| function frameSummary(f) { | ||
| return { | ||
| fn: f.fn, | ||
| bundled: `${f.file}:${f.line}:${f.column}`, | ||
| original: f.original ? `${f.original.source}:${f.original.line}:${f.original.column}${f.original.name ? ` (${f.original.name})` : ""}` : null, | ||
| mapFile: f.mapFile ?? null | ||
| }; | ||
| } | ||
| function toolResolveStack(baseDir, args2) { | ||
| const { payload: p } = requireReport(baseDir, args2.file); | ||
| const searchDir = args2.searchDir ? path2.resolve(process.cwd(), args2.searchDir) : process.cwd(); | ||
| const stacks = collectStacks(p); | ||
| if (!stacks.length) throw new Error("This report contains no stack traces to resolve."); | ||
| const resolved = stacks.map((s) => { | ||
| const frames = resolveStackWithMaps(s.stack, searchDir).map(frameSummary); | ||
| return { error: s.message.slice(0, 200), frames }; | ||
| }); | ||
| const anyResolved = resolved.some((r) => r.frames.some((f) => f.original)); | ||
| return { | ||
| searchDir, | ||
| note: anyResolved ? "original = source file:line:column from the matching .map file \u2014 search the codebase there." : `No .map files matched under ${searchDir}. Build with sourcemaps enabled, or pass searchDir pointing at the build output.`, | ||
| stacks: resolved | ||
| }; | ||
| } | ||
| function toolGetFixContext(baseDir, args2) { | ||
| const { payload: p } = requireReport(baseDir, args2.file); | ||
| const failingRequest = (p.networkErrors ?? [])[0] ?? null; | ||
| const USER_KINDS = /* @__PURE__ */ new Set(["click", "input", "select", "submit", "navigate"]); | ||
| const failureTs = failingRequest?.timestamp ?? (p.consoleErrors ?? [])[0]?.timestamp ?? Infinity; | ||
| const userChips = (p.actionChips ?? []).filter((c) => USER_KINDS.has(c.kind)); | ||
| const triggeringAction = [...userChips].reverse().find((c) => c.timestamp <= failureTs) ?? userChips[userChips.length - 1] ?? null; | ||
| const firstError = (p.consoleLogs ?? []).find((l) => l.level === "error") ?? (p.consoleErrors ?? [])[0] ?? null; | ||
| const searchDir = args2.searchDir ? path2.resolve(process.cwd(), args2.searchDir) : process.cwd(); | ||
| const topFrames = firstError?.stack ? resolveStackWithMaps(firstError.stack, searchDir).slice(0, 5).map(frameSummary) : []; | ||
| return { | ||
| title: p.meta.title, | ||
| rootCause: p.rootCauseHint ?? p.meta.rootCause ?? null, | ||
| failingRequest: failingRequest ? { | ||
| method: failingRequest.method, | ||
| url: failingRequest.url, | ||
| status: failingRequest.status, | ||
| responseSnippet: failingRequest.response ?? null | ||
| } : null, | ||
| triggeringAction: triggeringAction ? { | ||
| action: [triggeringAction.verb, triggeringAction.nounLabel ?? triggeringAction.target].filter(Boolean).join(" "), | ||
| detail: triggeringAction.detail ?? null, | ||
| at: new Date(triggeringAction.timestamp).toISOString() | ||
| } : null, | ||
| error: firstError ? { message: firstError.message, topFrames } : null, | ||
| failingTest: p.playwrightTest ? { available: true, tool: "get_playwright_test", filename: p.playwrightTestFilename ?? "tracebug-bug.spec.ts" } : { available: false, tool: null, filename: null }, | ||
| nextStep: "Search the codebase for the failing endpoint path and the original source locations above; " + (p.playwrightTest ? "then save the generated test (get_playwright_test) and iterate: run \u2192 fix \u2192 run until green." : "reproduce manually using get_repro_steps, then fix and verify.") | ||
| }; | ||
| } | ||
| function buildDebugPrompt(file) { | ||
@@ -359,2 +631,8 @@ const target = file?.trim(); | ||
| } | ||
| case "get_playwright_test": | ||
| return textResult(toolGetPlaywrightTest(baseDir, args2)); | ||
| case "resolve_stack": | ||
| return textResult(toolResolveStack(baseDir, args2)); | ||
| case "get_fix_context": | ||
| return textResult(toolGetFixContext(baseDir, args2)); | ||
| default: | ||
@@ -448,2 +726,3 @@ return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true }; | ||
| "use strict"; | ||
| init_source_map(); | ||
| TB_DATA_RE = /<script id="tb-data" type="application\/json">([\s\S]*?)<\/script>/; | ||
@@ -508,2 +787,31 @@ MAX_SCAN_DEPTH = 3; | ||
| } | ||
| }, | ||
| { | ||
| name: "get_playwright_test", | ||
| description: "Get the generated Playwright spec that REPLAYS this bug report's session and asserts the captured failure is gone \u2014 the test fails while the bug exists and passes once fixed. Save it, run it to reproduce, then use it to verify your fix.", | ||
| inputSchema: { type: "object", properties: { ...FILE_PROP }, required: ["file"] } | ||
| }, | ||
| { | ||
| name: "resolve_stack", | ||
| description: "Map the minified stack traces in a TraceBug bug report to original source files/lines using .map files found in the current project (run from the repo that built the app). Returns bundled and original positions per frame.", | ||
| inputSchema: { | ||
| type: "object", | ||
| properties: { | ||
| ...FILE_PROP, | ||
| searchDir: { type: "string", description: "Directory to search for .map files (default: the current working directory). Point at your build output if maps aren't found." } | ||
| }, | ||
| required: ["file"] | ||
| } | ||
| }, | ||
| { | ||
| name: "get_fix_context", | ||
| description: "One-call fix starter for a TraceBug bug report: the failing request (with response snippet), the user action that triggered it, the first error with source-map-resolved top stack frames, and whether a generated failing test is available. Call this when you're ready to locate and fix the bug.", | ||
| inputSchema: { | ||
| type: "object", | ||
| properties: { | ||
| ...FILE_PROP, | ||
| searchDir: { type: "string", description: "Directory to search for .map files when resolving stack frames (default: current working directory)." } | ||
| }, | ||
| required: ["file"] | ||
| } | ||
| } | ||
@@ -555,11 +863,11 @@ ]; | ||
| const { runMcpServer: runMcpServer2 } = await Promise.resolve().then(() => (init_mcp_server(), mcp_server_exports)); | ||
| const fs2 = await import("fs"); | ||
| const path2 = await import("path"); | ||
| const fs3 = await import("fs"); | ||
| const path3 = await import("path"); | ||
| const dirFlag = args.indexOf("--dir"); | ||
| const hasExplicitDir = dirFlag !== -1 && !!args[dirFlag + 1]; | ||
| const baseDir = hasExplicitDir ? path2.resolve(args[dirFlag + 1]) : process.cwd(); | ||
| const baseDir = hasExplicitDir ? path3.resolve(args[dirFlag + 1]) : process.cwd(); | ||
| let version = "0.0.0"; | ||
| for (const rel of ["./package.json", "../package.json"]) { | ||
| try { | ||
| const pkg = JSON.parse(fs2.readFileSync(new URL(rel, import.meta.url), "utf8")); | ||
| const pkg = JSON.parse(fs3.readFileSync(new URL(rel, import.meta.url), "utf8")); | ||
| if (pkg.version) { | ||
@@ -592,6 +900,6 @@ version = pkg.version; | ||
| async function initProject() { | ||
| const fs2 = await import("fs"); | ||
| const path2 = await import("path"); | ||
| const fs3 = await import("fs"); | ||
| const path3 = await import("path"); | ||
| const cwd = process.cwd(); | ||
| const projectId = path2.basename(cwd) || "my-app"; | ||
| const projectId = path3.basename(cwd) || "my-app"; | ||
| console.log(` | ||
@@ -602,6 +910,6 @@ ${BOLD}${CYAN}TraceBug${RESET} \u2014 the exact setup for your framework`); | ||
| let framework = "vanilla"; | ||
| const pkgPath = path2.join(cwd, "package.json"); | ||
| if (fs2.existsSync(pkgPath)) { | ||
| const pkgPath = path3.join(cwd, "package.json"); | ||
| if (fs3.existsSync(pkgPath)) { | ||
| try { | ||
| const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8")); | ||
| const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf-8")); | ||
| const deps = { ...pkg.dependencies, ...pkg.devDependencies }; | ||
@@ -608,0 +916,0 @@ if (deps["next"]) framework = "nextjs"; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["d:\\Project\\TraceBug-ai\\dist\\index.cjs"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,wDAA6B;AAC7B,gCAA6B;AAC7B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,wDAA6B;AAC7B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,wDAA6B;AAC7B;AACA;AACA,IAAI,uBAAuB,EAAE,sBAAsB;AACnD,SAAS,oBAAoB,CAAC,QAAQ,EAAE;AACxC,EAAE,MAAM,IAAI,EAAE,SAAS,GAAG,KAAK,EAAE,KAAK,EAAE,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;AACzD,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,sBAAsB;AACzC,EAAE,IAAI;AACN,IAAI,MAAM,IAAI,EAAE,IAAI,GAAG,CAAC,GAAG,CAAC;AAC5B,IAAI,MAAM,QAAQ,EAAE,GAAG,CAAC,SAAS,IAAI,YAAY,GAAG,GAAG,CAAC,SAAS,IAAI,YAAY,GAAG,GAAG,CAAC,SAAS,IAAI,OAAO;AAC5G,IAAI,GAAG,CAAC,GAAG,CAAC,SAAS,IAAI,SAAS,GAAG,CAAC,CAAC,GAAG,CAAC,SAAS,IAAI,QAAQ,GAAG,OAAO,CAAC,EAAE;AAC7E,MAAM,GAAG,CAAC,OAAO,QAAQ,IAAI,WAAW,EAAE;AAC1C,QAAQ,OAAO,CAAC,IAAI,CAAC,CAAC,2EAA2E,EAAE,sBAAsB,CAAC,CAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA;AACA;AACA;AACA,IAAA;AACA;AACA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,UAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,YAAA;AACA,cAAA;AACA,YAAA;AACA,cAAA;AACA,YAAA;AACA,YAAA;AACA,UAAA;AACA,UAAA;AACA,YAAA;AACA,YAAA;AACA,UAAA;AACA,UAAA;AACA,YAAA;AACA,cAAA;AACA,YAAA;AACA,YAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,YAAA;AACA,YAAA;AACA,UAAA;AACA,YAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,gBAAA;AACA,cAAA;AACA,cAAA;AACA,YAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,YAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,YAAA;AACA,YAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,cAAA;AACA,cAAA;AACA,gBAAA;AACA,gBAAA;AACA,cAAA;AACA,cAAA;AACA,gBAAA;AACA,gBAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,YAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,YAAA;AACA,YAAA;AACA,UAAA;AACA,YAAA;AACA,YAAA;AACA,UAAA;AACA,UAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"D:\\Project\\TraceBug-ai\\dist\\index.cjs","sourcesContent":[null]} | ||
| {"version":3,"sources":["d:\\Project\\TraceBug-ai\\dist\\index.cjs"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,wDAA6B;AAC7B,gCAA6B;AAC7B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,wDAA6B;AAC7B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,wDAA6B;AAC7B;AACA;AACA,IAAI,uBAAuB,EAAE,sBAAsB;AACnD,SAAS,oBAAoB,CAAC,QAAQ,EAAE;AACxC,EAAE,MAAM,IAAI,EAAE,SAAS,GAAG,KAAK,EAAE,KAAK,EAAE,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;AACzD,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,sBAAsB;AACzC,EAAE,IAAI;AACN,IAAI,MAAM,IAAI,EAAE,IAAI,GAAG,CAAC,GAAG,CAAC;AAC5B,IAAI,MAAM,QAAQ,EAAE,GAAG,CAAC,SAAS,IAAI,YAAY,GAAG,GAAG,CAAC,SAAS,IAAI,YAAY,GAAG,GAAG,CAAC,SAAS,IAAI,OAAO;AAC5G,IAAI,GAAG,CAAC,GAAG,CAAC,SAAS,IAAI,SAAS,GAAG,CAAC,CAAC,GAAG,CAAC,SAAS,IAAI,QAAQ,GAAG,OAAO,CAAC,EAAE;AAC7E,MAAM,GAAG,CAAC,OAAO,QAAQ,IAAI,WAAW,EAAE;AAC1C,QAAQ,OAAO,CAAC,IAAI,CAAC,CAAC,2EAA2E,EAAE,sBAAsB,CAAC,CAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA;AACA;AACA;AACA,IAAA;AACA;AACA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,YAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,YAAA;AACA,YAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,cAAA;AACA,cAAA;AACA,gBAAA;AACA,gBAAA;AACA,cAAA;AACA,cAAA;AACA,gBAAA;AACA,gBAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,YAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,YAAA;AACA,YAAA;AACA,UAAA;AACA,YAAA;AACA,YAAA;AACA,UAAA;AACA,UAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"D:\\Project\\TraceBug-ai\\dist\\index.cjs","sourcesContent":[null]} |
+135
-5
@@ -0,1 +1,51 @@ | ||
| interface StyleEvidence { | ||
| typography: { | ||
| fontFamily: string; | ||
| fontSize: string; | ||
| fontWeight: string; | ||
| lineHeight: string; | ||
| letterSpacing: string; | ||
| textAlign: string; | ||
| }; | ||
| colors: { | ||
| color: string; | ||
| backgroundColor: string; | ||
| borderColor: string; | ||
| opacity: string; | ||
| }; | ||
| box: { | ||
| width: string; | ||
| height: string; | ||
| margin: string; | ||
| padding: string; | ||
| border: string; | ||
| borderRadius: string; | ||
| boxSizing: string; | ||
| }; | ||
| layout: { | ||
| display: string; | ||
| position: string; | ||
| zIndex: string; | ||
| overflow: string; | ||
| }; | ||
| /** WCAG text contrast — omitted when the element has no visible text. */ | ||
| contrast?: { | ||
| ratio: number; | ||
| /** Passes AA for normal text (≥ 4.5). */ | ||
| aa: boolean; | ||
| /** Passes AA for large text (≥ 3; large = ≥24px, or ≥18.66px bold). */ | ||
| aaLarge: boolean; | ||
| foreground: string; | ||
| background: string; | ||
| }; | ||
| } | ||
| /** Computed rgb()/rgba() → #rrggbb (alpha appended as /NN% only when < 1). */ | ||
| declare function cssColorToHex(css: string): string; | ||
| /** WCAG contrast ratio between two computed CSS colors (1–21). */ | ||
| declare function contrastRatio(fg: string, bg: string): number | null; | ||
| /** Snapshot the curated computed-style evidence for an element. */ | ||
| declare function captureStyleEvidence(el: Element): StyleEvidence; | ||
| /** One-line human summary — used in tooltips, markdown, and MCP output. */ | ||
| declare function formatStyleSummary(s: StyleEvidence): string; | ||
| interface TraceBugConfig { | ||
@@ -21,5 +71,5 @@ projectId: string; | ||
| /** | ||
| * Color theme. Default: "dark" | ||
| * - "dark" → Dark navy background (default) | ||
| * - "light" → Light background | ||
| * Color theme. Default: "light" | ||
| * - "light" → Light background (default) | ||
| * - "dark" → Dark (cyber-graphite) background | ||
| * - "auto" → Follows system prefers-color-scheme | ||
@@ -174,3 +224,3 @@ */ | ||
| } | ||
| type AnnotationIntent = "fix" | "redesign" | "remove" | "question"; | ||
| type AnnotationIntent = "fix" | "redesign" | "remove" | "question" | "inspect"; | ||
| interface ElementAnnotation { | ||
@@ -194,2 +244,5 @@ id: string; | ||
| scrollY: number; | ||
| /** Computed-style snapshot taken at annotation time — the "receipts" for | ||
| * design-QA bugs (typography, colors as hex, box model, WCAG contrast). */ | ||
| styles?: StyleEvidence; | ||
| } | ||
@@ -409,2 +462,5 @@ interface DrawRegion { | ||
| annotations: Annotation[]; | ||
| /** Element-level annotations (annotate + inspect modes) with their | ||
| * computed-style evidence — surfaced in exports and over MCP. */ | ||
| elementAnnotations?: ElementAnnotation[]; | ||
| screenshots: ScreenshotData[]; | ||
@@ -868,2 +924,10 @@ timeline: TimelineEntry[]; | ||
| } | ||
| /** | ||
| * Generate the replay-as-HTML and trigger a browser download. Returns the | ||
| * blob + URL so the caller can also inspect or pipe elsewhere. | ||
| * | ||
| * Caller passes the already-built `BugReport` so we don't double-compute. | ||
| */ | ||
| declare function exportSessionAsHtml(session: StoredSession, report: BugReport, options?: HtmlReplayOptions): Promise<ExportedReplay>; | ||
| declare function buildReplayBlob(session: StoredSession, report: BugReport, options?: HtmlReplayOptions): Promise<Blob>; | ||
@@ -888,2 +952,27 @@ interface ZipEntry { | ||
| /** | ||
| * Generate a runnable Playwright spec from a bug report, or null when the | ||
| * session has no replayable user actions. | ||
| */ | ||
| declare function generatePlaywrightTest(report: BugReport): string | null; | ||
| /** Suggested filename for the generated spec. */ | ||
| declare function playwrightTestFilename(report: BugReport): string; | ||
| /** Fullscreen 3-2-1 countdown. Resolves when it hits zero. */ | ||
| declare function runRecordCountdown(seconds: number): Promise<void>; | ||
| /** | ||
| * Blur-first arming: activates the blur tool so the user can drag boxes over | ||
| * sensitive areas, with a floating bar to start the recording (or cancel). | ||
| * The blur boxes persist into the recording — that's the whole point. | ||
| */ | ||
| declare function startBlurThenRecord(opts: { | ||
| onStart: () => void; | ||
| onCancel?: () => void; | ||
| }): void; | ||
| declare function isBlurModeActive(): boolean; | ||
| /** Unblur every element (used when a recording stops — the video already | ||
| * captured them blurred). Name kept from the box era for API stability. */ | ||
| declare function removeAllBlurBoxes(): void; | ||
| type AIProvider = "anthropic" | "openai" | "ollama"; | ||
@@ -1030,2 +1119,25 @@ interface AIConfig { | ||
| init(config: TraceBugConfig): void; | ||
| /** Validate the config object and environment gate. Returns false (and logs) | ||
| * when init should abort; may normalize invalid maxEvents/maxSessions. */ | ||
| private _validateConfig; | ||
| /** Inject theme CSS custom properties, honoring a per-origin user preference | ||
| * saved by the modal's theme toggle; falls back to the config default. */ | ||
| private _applyThemeFromConfig; | ||
| /** Wire githubRepo + cloudEndpoint into the (lazy-loaded) Quick Bug modal. | ||
| * cloudEndpoint always wires through so Share reads the override even with | ||
| * no githubRepo set. */ | ||
| private _wireQuickBugConfig; | ||
| /** The emit function collectors call with raw event data. Reads | ||
| * sessionId / recording dynamically from `this` so toggling record state | ||
| * mid-session takes effect without re-wiring collectors. */ | ||
| private _buildEmit; | ||
| /** Attach all event collectors, pushing their cleanups. */ | ||
| private _startCollectors; | ||
| /** Mount the in-browser dashboard toolbar and wire its session handlers. */ | ||
| private _mountDashboardIfEnabled; | ||
| /** Reconnect to a live screen recording that survived a page reload. | ||
| * Extension transport keeps the recording in an offscreen document, so a | ||
| * reload doesn't kill it: ping for status, re-mount the HUD if still armed, | ||
| * otherwise clear a stale active-session id so the next Record starts fresh. */ | ||
| private _restoreVideoSessionOnInit; | ||
| /** | ||
@@ -1130,2 +1242,13 @@ * Begin a fresh record-driven session: mints a session ID, persists it so | ||
| */ | ||
| /** | ||
| * Pre-recording flow: optionally let the user BLUR sensitive areas first | ||
| * (the blur is real backdrop blur — the redacted pixels are what the | ||
| * recording captures), then an optional 3-2-1 countdown, then record. | ||
| */ | ||
| prepareRecording(opts?: { | ||
| blurFirst?: boolean; | ||
| delaySec?: number; | ||
| withMicrophone?: boolean; | ||
| surfaceMode?: "tab" | "desktop"; | ||
| }): void; | ||
| startVideoRecording(options?: { | ||
@@ -1237,2 +1360,9 @@ mode?: "rolling" | "standard"; | ||
| isDrawModeActive(): boolean; | ||
| /** Activate inspect mode — hover shows the box model + computed-style | ||
| * summary; click attaches the element's style evidence to the report. */ | ||
| activateInspectMode(): void; | ||
| /** Deactivate inspect mode */ | ||
| deactivateInspectMode(): void; | ||
| /** Check if inspect mode is active */ | ||
| isInspectModeActive(): boolean; | ||
| /** Get complete annotation report (element annotations + draw regions) */ | ||
@@ -1388,2 +1518,2 @@ getAnnotationReport(): UIAnnotationReport; | ||
| export { type AIAnalysisResult, type AIConfig, type AIPromptOptions, type AIProvider, ANTHROPIC_MODEL_CHOICES, type Annotation, type AnnotationIntent, type BugReport, type ClickedElementSummary, type CreateIssueResult, DEFAULT_MODELS, type DrawRegion, type ElementAnnotation, type EnvironmentInfo, FREE_LIMITS, type GitHubConfig, type HarExportResult, type HarLog, type IntegrationsConfig, type Issue, type IssueDetector, type IssueSeverity, type JiraTicket, type LinearConfig, type NetworkErrorEntry, type NetworkFailure, PROVIDER_LABELS, type Plan, type RedactRules, type RedactionSummary, type RootCauseHint, type ScanResult, type ScreenshotData, type SlackConfig, type StoredSession, TRACKER_LABELS, type TraceBugConfig, type TraceBugEvent, type TraceBugPlugin, type TraceBugUser, type TrackerProvider, type UIAnnotationReport, type VideoComment, type VideoRecording, type VoiceTranscript, buildAnalysisPrompt, buildHar, buildReport, buildTimeline, buildZipBlob, captureEnvironment, captureRegionScreenshot, captureRollingBuffer, captureScreenshot, clearAIConfig, clearAllSessions, clearIntegrationsConfig, clearIssues, clearVideoRecording, clearVoiceTranscripts, createGitHubIssue, createLinearIssue, createTrackerIssue, TraceBug as default, deleteSession, dismissIssue, downloadAllScreenshots, downloadPdfAsHtml, downloadVideoRecording, exportSessionAsHar, exportSessionAsZip, extractClickedElement, formatRedactionSummary, formatRootCauseLine, formatTimelineText, generateAIPrompt, generateBugTitle, generateFlowSummary, generateGitHubIssue, generateGitHubIssueUrl, generateJiraTicket, generateMcpPrompt, generatePdfReport, generateReproSteps, generateRootCauseHint, generateSessionSteps, generateSmartSummary, getAIConfig, getAllSessions, getCaptureCount, getIntegrationsConfig, getIssueById, getIssueCountsByDetector, getIssueCountsBySeverity, getIssues, getLastVideoRecording, getNetworkFailures, getPlan, getScreenshots, getVoiceTranscripts, hasAIKey, hasIntegration, hydratePlan, isPremium, isRollingMode, isVideoRecording, isVideoSupported as isVideoSupportedFn, isVoiceRecording, isVoiceSupported, openGitHubIssue, openInChatGPT, openInClaude, runLLMAnalysis, scan, sendSlackMessage, setAIConfig, setIntegrationsConfig, setPlan, setRedactRules, startVideoRecording, startVoiceRecording, stopVideoRecording, stopVoiceRecording, summarizeRedactions, undismissIssue }; | ||
| export { type AIAnalysisResult, type AIConfig, type AIPromptOptions, type AIProvider, ANTHROPIC_MODEL_CHOICES, type Annotation, type AnnotationIntent, type BugReport, type ClickedElementSummary, type CreateIssueResult, DEFAULT_MODELS, type DrawRegion, type ElementAnnotation, type EnvironmentInfo, FREE_LIMITS, type GitHubConfig, type HarExportResult, type HarLog, type IntegrationsConfig, type Issue, type IssueDetector, type IssueSeverity, type JiraTicket, type LinearConfig, type NetworkErrorEntry, type NetworkFailure, PROVIDER_LABELS, type Plan, type RedactRules, type RedactionSummary, type RootCauseHint, type ScanResult, type ScreenshotData, type SlackConfig, type StoredSession, type StyleEvidence, TRACKER_LABELS, type TraceBugConfig, type TraceBugEvent, type TraceBugPlugin, type TraceBugUser, type TrackerProvider, type UIAnnotationReport, type VideoComment, type VideoRecording, type VoiceTranscript, buildAnalysisPrompt, buildHar, buildReplayBlob, buildReport, buildTimeline, buildZipBlob, captureEnvironment, captureRegionScreenshot, captureRollingBuffer, captureScreenshot, captureStyleEvidence, clearAIConfig, clearAllSessions, clearIntegrationsConfig, clearIssues, clearVideoRecording, clearVoiceTranscripts, contrastRatio, createGitHubIssue, createLinearIssue, createTrackerIssue, cssColorToHex, TraceBug as default, deleteSession, dismissIssue, downloadAllScreenshots, downloadPdfAsHtml, downloadVideoRecording, exportSessionAsHar, exportSessionAsHtml, exportSessionAsZip, extractClickedElement, formatRedactionSummary, formatRootCauseLine, formatStyleSummary, formatTimelineText, generateAIPrompt, generateBugTitle, generateFlowSummary, generateGitHubIssue, generateGitHubIssueUrl, generateJiraTicket, generateMcpPrompt, generatePdfReport, generatePlaywrightTest, generateReproSteps, generateRootCauseHint, generateSessionSteps, generateSmartSummary, getAIConfig, getAllSessions, getCaptureCount, getIntegrationsConfig, getIssueById, getIssueCountsByDetector, getIssueCountsBySeverity, getIssues, getLastVideoRecording, getNetworkFailures, getPlan, getScreenshots, getVoiceTranscripts, hasAIKey, hasIntegration, hydratePlan, isBlurModeActive, isPremium, isRollingMode, isVideoRecording, isVideoSupported as isVideoSupportedFn, isVoiceRecording, isVoiceSupported, openGitHubIssue, openInChatGPT, openInClaude, playwrightTestFilename, removeAllBlurBoxes, runLLMAnalysis, runRecordCountdown, scan, sendSlackMessage, setAIConfig, setIntegrationsConfig, setPlan, setRedactRules, startBlurThenRecord, startVideoRecording, startVoiceRecording, stopVideoRecording, stopVoiceRecording, summarizeRedactions, undismissIssue }; |
+135
-5
@@ -0,1 +1,51 @@ | ||
| interface StyleEvidence { | ||
| typography: { | ||
| fontFamily: string; | ||
| fontSize: string; | ||
| fontWeight: string; | ||
| lineHeight: string; | ||
| letterSpacing: string; | ||
| textAlign: string; | ||
| }; | ||
| colors: { | ||
| color: string; | ||
| backgroundColor: string; | ||
| borderColor: string; | ||
| opacity: string; | ||
| }; | ||
| box: { | ||
| width: string; | ||
| height: string; | ||
| margin: string; | ||
| padding: string; | ||
| border: string; | ||
| borderRadius: string; | ||
| boxSizing: string; | ||
| }; | ||
| layout: { | ||
| display: string; | ||
| position: string; | ||
| zIndex: string; | ||
| overflow: string; | ||
| }; | ||
| /** WCAG text contrast — omitted when the element has no visible text. */ | ||
| contrast?: { | ||
| ratio: number; | ||
| /** Passes AA for normal text (≥ 4.5). */ | ||
| aa: boolean; | ||
| /** Passes AA for large text (≥ 3; large = ≥24px, or ≥18.66px bold). */ | ||
| aaLarge: boolean; | ||
| foreground: string; | ||
| background: string; | ||
| }; | ||
| } | ||
| /** Computed rgb()/rgba() → #rrggbb (alpha appended as /NN% only when < 1). */ | ||
| declare function cssColorToHex(css: string): string; | ||
| /** WCAG contrast ratio between two computed CSS colors (1–21). */ | ||
| declare function contrastRatio(fg: string, bg: string): number | null; | ||
| /** Snapshot the curated computed-style evidence for an element. */ | ||
| declare function captureStyleEvidence(el: Element): StyleEvidence; | ||
| /** One-line human summary — used in tooltips, markdown, and MCP output. */ | ||
| declare function formatStyleSummary(s: StyleEvidence): string; | ||
| interface TraceBugConfig { | ||
@@ -21,5 +71,5 @@ projectId: string; | ||
| /** | ||
| * Color theme. Default: "dark" | ||
| * - "dark" → Dark navy background (default) | ||
| * - "light" → Light background | ||
| * Color theme. Default: "light" | ||
| * - "light" → Light background (default) | ||
| * - "dark" → Dark (cyber-graphite) background | ||
| * - "auto" → Follows system prefers-color-scheme | ||
@@ -174,3 +224,3 @@ */ | ||
| } | ||
| type AnnotationIntent = "fix" | "redesign" | "remove" | "question"; | ||
| type AnnotationIntent = "fix" | "redesign" | "remove" | "question" | "inspect"; | ||
| interface ElementAnnotation { | ||
@@ -194,2 +244,5 @@ id: string; | ||
| scrollY: number; | ||
| /** Computed-style snapshot taken at annotation time — the "receipts" for | ||
| * design-QA bugs (typography, colors as hex, box model, WCAG contrast). */ | ||
| styles?: StyleEvidence; | ||
| } | ||
@@ -409,2 +462,5 @@ interface DrawRegion { | ||
| annotations: Annotation[]; | ||
| /** Element-level annotations (annotate + inspect modes) with their | ||
| * computed-style evidence — surfaced in exports and over MCP. */ | ||
| elementAnnotations?: ElementAnnotation[]; | ||
| screenshots: ScreenshotData[]; | ||
@@ -868,2 +924,10 @@ timeline: TimelineEntry[]; | ||
| } | ||
| /** | ||
| * Generate the replay-as-HTML and trigger a browser download. Returns the | ||
| * blob + URL so the caller can also inspect or pipe elsewhere. | ||
| * | ||
| * Caller passes the already-built `BugReport` so we don't double-compute. | ||
| */ | ||
| declare function exportSessionAsHtml(session: StoredSession, report: BugReport, options?: HtmlReplayOptions): Promise<ExportedReplay>; | ||
| declare function buildReplayBlob(session: StoredSession, report: BugReport, options?: HtmlReplayOptions): Promise<Blob>; | ||
@@ -888,2 +952,27 @@ interface ZipEntry { | ||
| /** | ||
| * Generate a runnable Playwright spec from a bug report, or null when the | ||
| * session has no replayable user actions. | ||
| */ | ||
| declare function generatePlaywrightTest(report: BugReport): string | null; | ||
| /** Suggested filename for the generated spec. */ | ||
| declare function playwrightTestFilename(report: BugReport): string; | ||
| /** Fullscreen 3-2-1 countdown. Resolves when it hits zero. */ | ||
| declare function runRecordCountdown(seconds: number): Promise<void>; | ||
| /** | ||
| * Blur-first arming: activates the blur tool so the user can drag boxes over | ||
| * sensitive areas, with a floating bar to start the recording (or cancel). | ||
| * The blur boxes persist into the recording — that's the whole point. | ||
| */ | ||
| declare function startBlurThenRecord(opts: { | ||
| onStart: () => void; | ||
| onCancel?: () => void; | ||
| }): void; | ||
| declare function isBlurModeActive(): boolean; | ||
| /** Unblur every element (used when a recording stops — the video already | ||
| * captured them blurred). Name kept from the box era for API stability. */ | ||
| declare function removeAllBlurBoxes(): void; | ||
| type AIProvider = "anthropic" | "openai" | "ollama"; | ||
@@ -1030,2 +1119,25 @@ interface AIConfig { | ||
| init(config: TraceBugConfig): void; | ||
| /** Validate the config object and environment gate. Returns false (and logs) | ||
| * when init should abort; may normalize invalid maxEvents/maxSessions. */ | ||
| private _validateConfig; | ||
| /** Inject theme CSS custom properties, honoring a per-origin user preference | ||
| * saved by the modal's theme toggle; falls back to the config default. */ | ||
| private _applyThemeFromConfig; | ||
| /** Wire githubRepo + cloudEndpoint into the (lazy-loaded) Quick Bug modal. | ||
| * cloudEndpoint always wires through so Share reads the override even with | ||
| * no githubRepo set. */ | ||
| private _wireQuickBugConfig; | ||
| /** The emit function collectors call with raw event data. Reads | ||
| * sessionId / recording dynamically from `this` so toggling record state | ||
| * mid-session takes effect without re-wiring collectors. */ | ||
| private _buildEmit; | ||
| /** Attach all event collectors, pushing their cleanups. */ | ||
| private _startCollectors; | ||
| /** Mount the in-browser dashboard toolbar and wire its session handlers. */ | ||
| private _mountDashboardIfEnabled; | ||
| /** Reconnect to a live screen recording that survived a page reload. | ||
| * Extension transport keeps the recording in an offscreen document, so a | ||
| * reload doesn't kill it: ping for status, re-mount the HUD if still armed, | ||
| * otherwise clear a stale active-session id so the next Record starts fresh. */ | ||
| private _restoreVideoSessionOnInit; | ||
| /** | ||
@@ -1130,2 +1242,13 @@ * Begin a fresh record-driven session: mints a session ID, persists it so | ||
| */ | ||
| /** | ||
| * Pre-recording flow: optionally let the user BLUR sensitive areas first | ||
| * (the blur is real backdrop blur — the redacted pixels are what the | ||
| * recording captures), then an optional 3-2-1 countdown, then record. | ||
| */ | ||
| prepareRecording(opts?: { | ||
| blurFirst?: boolean; | ||
| delaySec?: number; | ||
| withMicrophone?: boolean; | ||
| surfaceMode?: "tab" | "desktop"; | ||
| }): void; | ||
| startVideoRecording(options?: { | ||
@@ -1237,2 +1360,9 @@ mode?: "rolling" | "standard"; | ||
| isDrawModeActive(): boolean; | ||
| /** Activate inspect mode — hover shows the box model + computed-style | ||
| * summary; click attaches the element's style evidence to the report. */ | ||
| activateInspectMode(): void; | ||
| /** Deactivate inspect mode */ | ||
| deactivateInspectMode(): void; | ||
| /** Check if inspect mode is active */ | ||
| isInspectModeActive(): boolean; | ||
| /** Get complete annotation report (element annotations + draw regions) */ | ||
@@ -1388,2 +1518,2 @@ getAnnotationReport(): UIAnnotationReport; | ||
| export { type AIAnalysisResult, type AIConfig, type AIPromptOptions, type AIProvider, ANTHROPIC_MODEL_CHOICES, type Annotation, type AnnotationIntent, type BugReport, type ClickedElementSummary, type CreateIssueResult, DEFAULT_MODELS, type DrawRegion, type ElementAnnotation, type EnvironmentInfo, FREE_LIMITS, type GitHubConfig, type HarExportResult, type HarLog, type IntegrationsConfig, type Issue, type IssueDetector, type IssueSeverity, type JiraTicket, type LinearConfig, type NetworkErrorEntry, type NetworkFailure, PROVIDER_LABELS, type Plan, type RedactRules, type RedactionSummary, type RootCauseHint, type ScanResult, type ScreenshotData, type SlackConfig, type StoredSession, TRACKER_LABELS, type TraceBugConfig, type TraceBugEvent, type TraceBugPlugin, type TraceBugUser, type TrackerProvider, type UIAnnotationReport, type VideoComment, type VideoRecording, type VoiceTranscript, buildAnalysisPrompt, buildHar, buildReport, buildTimeline, buildZipBlob, captureEnvironment, captureRegionScreenshot, captureRollingBuffer, captureScreenshot, clearAIConfig, clearAllSessions, clearIntegrationsConfig, clearIssues, clearVideoRecording, clearVoiceTranscripts, createGitHubIssue, createLinearIssue, createTrackerIssue, TraceBug as default, deleteSession, dismissIssue, downloadAllScreenshots, downloadPdfAsHtml, downloadVideoRecording, exportSessionAsHar, exportSessionAsZip, extractClickedElement, formatRedactionSummary, formatRootCauseLine, formatTimelineText, generateAIPrompt, generateBugTitle, generateFlowSummary, generateGitHubIssue, generateGitHubIssueUrl, generateJiraTicket, generateMcpPrompt, generatePdfReport, generateReproSteps, generateRootCauseHint, generateSessionSteps, generateSmartSummary, getAIConfig, getAllSessions, getCaptureCount, getIntegrationsConfig, getIssueById, getIssueCountsByDetector, getIssueCountsBySeverity, getIssues, getLastVideoRecording, getNetworkFailures, getPlan, getScreenshots, getVoiceTranscripts, hasAIKey, hasIntegration, hydratePlan, isPremium, isRollingMode, isVideoRecording, isVideoSupported as isVideoSupportedFn, isVoiceRecording, isVoiceSupported, openGitHubIssue, openInChatGPT, openInClaude, runLLMAnalysis, scan, sendSlackMessage, setAIConfig, setIntegrationsConfig, setPlan, setRedactRules, startVideoRecording, startVoiceRecording, stopVideoRecording, stopVoiceRecording, summarizeRedactions, undismissIssue }; | ||
| export { type AIAnalysisResult, type AIConfig, type AIPromptOptions, type AIProvider, ANTHROPIC_MODEL_CHOICES, type Annotation, type AnnotationIntent, type BugReport, type ClickedElementSummary, type CreateIssueResult, DEFAULT_MODELS, type DrawRegion, type ElementAnnotation, type EnvironmentInfo, FREE_LIMITS, type GitHubConfig, type HarExportResult, type HarLog, type IntegrationsConfig, type Issue, type IssueDetector, type IssueSeverity, type JiraTicket, type LinearConfig, type NetworkErrorEntry, type NetworkFailure, PROVIDER_LABELS, type Plan, type RedactRules, type RedactionSummary, type RootCauseHint, type ScanResult, type ScreenshotData, type SlackConfig, type StoredSession, type StyleEvidence, TRACKER_LABELS, type TraceBugConfig, type TraceBugEvent, type TraceBugPlugin, type TraceBugUser, type TrackerProvider, type UIAnnotationReport, type VideoComment, type VideoRecording, type VoiceTranscript, buildAnalysisPrompt, buildHar, buildReplayBlob, buildReport, buildTimeline, buildZipBlob, captureEnvironment, captureRegionScreenshot, captureRollingBuffer, captureScreenshot, captureStyleEvidence, clearAIConfig, clearAllSessions, clearIntegrationsConfig, clearIssues, clearVideoRecording, clearVoiceTranscripts, contrastRatio, createGitHubIssue, createLinearIssue, createTrackerIssue, cssColorToHex, TraceBug as default, deleteSession, dismissIssue, downloadAllScreenshots, downloadPdfAsHtml, downloadVideoRecording, exportSessionAsHar, exportSessionAsHtml, exportSessionAsZip, extractClickedElement, formatRedactionSummary, formatRootCauseLine, formatStyleSummary, formatTimelineText, generateAIPrompt, generateBugTitle, generateFlowSummary, generateGitHubIssue, generateGitHubIssueUrl, generateJiraTicket, generateMcpPrompt, generatePdfReport, generatePlaywrightTest, generateReproSteps, generateRootCauseHint, generateSessionSteps, generateSmartSummary, getAIConfig, getAllSessions, getCaptureCount, getIntegrationsConfig, getIssueById, getIssueCountsByDetector, getIssueCountsBySeverity, getIssues, getLastVideoRecording, getNetworkFailures, getPlan, getScreenshots, getVoiceTranscripts, hasAIKey, hasIntegration, hydratePlan, isBlurModeActive, isPremium, isRollingMode, isVideoRecording, isVideoSupported as isVideoSupportedFn, isVoiceRecording, isVoiceSupported, openGitHubIssue, openInChatGPT, openInClaude, playwrightTestFilename, removeAllBlurBoxes, runLLMAnalysis, runRecordCountdown, scan, sendSlackMessage, setAIConfig, setIntegrationsConfig, setPlan, setRedactRules, startBlurThenRecord, startVideoRecording, startVoiceRecording, stopVideoRecording, stopVoiceRecording, summarizeRedactions, undismissIssue }; |
+1
-1
| { | ||
| "name": "tracebug-sdk", | ||
| "version": "1.8.0", | ||
| "version": "1.9.0", | ||
| "description": "Capture a bug, see the root cause, and create a GitHub issue in 5 seconds. Zero-backend, browser-only debugging assistant. Free Sentry/LogRocket alternative.", | ||
@@ -5,0 +5,0 @@ "type": "module", |
+34
-4
@@ -79,3 +79,4 @@ <h1 align="center">TraceBug</h1> | ||
| • Export for AI (.html) — tiny text-only report to paste into a chat | ||
| • Download report (.md) · Export HAR · file a real GitHub/Linear/Slack/Jira issue | ||
| • Download report (.md) · .zip (GitHub-attachable) · failing test (.spec.ts) | ||
| • Export HAR · file a real GitHub/Linear/Slack/Jira issue | ||
| ↓ | ||
@@ -172,4 +173,6 @@ Complete report includes: | ||
| The server reads the same self-contained `.html` files TraceBug exports. A tester hands a dev the report file, the dev drops it in the repo, and the agent gets six tools: `list_bug_reports`, `get_bug_report`, `get_console_errors`, `get_network_activity`, `get_repro_steps`, and `get_screenshot` (real image content). `get_bug_report` returns a prioritized **investigation guide** computed from what the report contains, so the agent knows exactly which tools to call next. Console stacks + failed-request bodies + repro steps + frustration signals — everything an agent needs to go from bug report to fix. | ||
| The server reads the same self-contained `.html` files TraceBug exports. A tester hands a dev the report file, the dev drops it in the repo, and the agent gets nine tools: `list_bug_reports`, `get_bug_report`, `get_console_errors`, `get_network_activity`, `get_repro_steps`, `get_screenshot` (real image content), `get_playwright_test`, `resolve_stack`, and `get_fix_context`. `get_bug_report` returns a prioritized **investigation guide** computed from what the report contains, so the agent knows exactly which tools to call next. Console stacks + failed-request bodies + repro steps + frustration signals — everything an agent needs to go from bug report to fix. | ||
| The last three close the fix loop (v1.9): `get_playwright_test` returns the generated **failing Playwright spec** that replays the session and asserts the captured failure is gone — red until the bug is fixed, green after — so the agent can run it, patch, and re-run until green. `resolve_stack` maps the report's minified stack frames to original source files/lines using `.map` files found in the repo the server runs from. `get_fix_context` is a one-call fix starter: the failing request with response snippet, the user action that triggered it, the source-map-resolved top stack frames, and whether a failing test is available. | ||
| Kicking off is one paste: the extension shows a ready-made agent prompt after every **Export .html** (auto-copied), the exported file itself carries the same prompt in its **AI** tab, and in Claude Code you can just type `/tracebug:debug_bug_report`. | ||
@@ -200,2 +203,21 @@ | ||
| ### 🧪 Generated Failing Playwright Test (v1.9) | ||
| Every export carries a runnable Playwright spec that **replays the captured session** (locators prefer `data-testid` → id → aria-label → role+name → captured CSS selector) and **asserts the captured failure is gone** — the failing endpoint must stop failing, the console errors must stop being thrown. Red while the bug exists, green after the fix. Get it three ways: **Download failing test (.spec.ts)** in the Quick Bug More menu, embedded in the `.html` export, or via the MCP `get_playwright_test` tool — so an agent can run it, patch, and re-run until green. | ||
| ### 🔎 Inspect Mode — Style Evidence for Design-QA Bugs (v1.9) | ||
| "The button looks wrong" now ships with the receipts. A DevTools-style inspect mode (extension popup → **Inspect element**, or `TraceBug.activateInspectMode()`): hover paints the box-model highlight plus a computed-style summary tooltip; click attaches the element to the report with a curated style snapshot — typography, colors as hex, box model — plus a **WCAG text-contrast verdict** (ratio + AA pass/fail). Surfaced on annotation cards, in generated GitHub issues, in the export, and as structured data MCP agents get from `get_bug_report`. | ||
| ### 🎬 Pre-Recording Options + Element-Level Blur (v1.9) | ||
| The extension popup's **⚙ Record options** panel picks the capture surface (current tab / desktop picker), an optional 3s/5s countdown, and **Blur before recording** — redact sensitive areas *before* the first frame is captured. Also public SDK API: `TraceBug.prepareRecording({ blurFirst, delaySec, surfaceMode, withMicrophone })`. | ||
| Blur itself is element-level, click-to-blur: hover highlights, click applies `filter: blur(12px)` **to the element itself**, click again unblurs. Because the blur is part of the element's own rendering, it physically cannot lag behind scrolling. Blurred elements also get `tb-mask`, so the DOM replay masks their text, not just the video pixels. | ||
| ### 📦 Reports the Recipient Can Act On (v1.8) | ||
| - **Download .zip (attach to GitHub)** — the same offline replay wrapped in a `.zip`, because GitHub issues accept `.zip` attachments by drag-and-drop but reject bare `.html`. | ||
| - **Issue actions inside the exported report** — the viewer header has **Open GitHub issue** (prefilled URL when the exporter configured `githubRepo`) and **Copy issue markdown** (fully offline, pastes into any tracker). Both are precomputed at export time from the already-redacted report. | ||
| ### Auto-Captured (Zero Effort) | ||
@@ -212,3 +234,3 @@ | ||
| | **Errors** | Message, stack trace, source file, line, column | | ||
| | **Console Errors** | `console.error()` calls | | ||
| | **Console** | `console.error` + `warn` + `info` + `log` (each non-error level capped at 50/session); warn/info render in the repro timeline | | ||
| | **Unhandled Rejections** | Promise rejection reason + stack | | ||
@@ -518,3 +540,3 @@ | **Environment** | Browser, OS, viewport, device type, connection, language, timezone | | ||
| - Tabs: Info · Console · Network · Actions · AI · Events | ||
| - Export: **Export .html** (replay) · **Export for AI (.html)** · **Download report (.md)** · **Export HAR** | ||
| - Export: **Export .html** (replay) · **Export for AI (.html)** · **Download report (.md)** · **Download .zip** (GitHub-attachable) · **Download failing test (.spec.ts)** · **Export HAR** | ||
| - File directly: GitHub · Linear · Slack · Jira (real issues with a configured token) | ||
@@ -544,2 +566,7 @@ | ||
| - [Architecture](docs/architecture.md) — How TraceBug works internally | ||
| - [ADRs](docs/adr/README.md) — Why local-first, single-file HTML, rrweb, MV3, zero deps | ||
| - [Performance](docs/performance.md) — Measured numbers + the reproducible benchmark | ||
| - [Compatibility](docs/compatibility.md) — Browser matrix, support policy, known limitations | ||
| - [Migration Guide](docs/migrating.md) — Upgrade notes and API stability | ||
| - [Roadmap](ROADMAP.md) — Shipped · in progress · planned · long-term | ||
@@ -632,2 +659,5 @@ ## Chrome Extension | ||
| - Sensitive fields auto-redacted (`password`, `secret`, `token`, `ssn`, `credit`) | ||
| - ~20 token-shape patterns masked at capture (JWT, `Bearer` headers, cloud provider keys) — a logged secret never enters the report object | ||
| - The export flow and the exported report show exactly what was masked: `🛡 N sensitive values auto-masked` | ||
| - App-specific PII covered via `redact: { fields, patterns }` in config — also settable in the extension popup's 🛡 Redaction rules section | ||
| - All data stays in `localStorage` — nothing leaves the browser | ||
@@ -634,0 +664,0 @@ - SDK never captures its own UI interactions |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
| "use strict";Object.defineProperty(exports, "__esModule", {value: true});// src/storage.ts | ||
| var SESSIONS_KEY = "tracebug_sessions"; | ||
| var ACTIVE_SESSION_KEY = "tracebug_active_session"; | ||
| var ACTIVE_CAPTURE_MODE_KEY = "tracebug_active_capture_mode"; | ||
| function generateSessionId() { | ||
| return typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : "bt_" + Date.now().toString(36) + Math.random().toString(36).slice(2, 10); | ||
| } | ||
| function getActiveSessionId() { | ||
| try { | ||
| return localStorage.getItem(ACTIVE_SESSION_KEY); | ||
| } catch (e) { | ||
| return null; | ||
| } | ||
| } | ||
| function setActiveSessionId(id) { | ||
| try { | ||
| localStorage.setItem(ACTIVE_SESSION_KEY, id); | ||
| } catch (e) { | ||
| } | ||
| } | ||
| function clearActiveSessionId() { | ||
| try { | ||
| localStorage.removeItem(ACTIVE_SESSION_KEY); | ||
| } catch (e) { | ||
| } | ||
| try { | ||
| localStorage.removeItem(ACTIVE_CAPTURE_MODE_KEY); | ||
| } catch (e) { | ||
| } | ||
| } | ||
| function getActiveCaptureMode() { | ||
| try { | ||
| const v = localStorage.getItem(ACTIVE_CAPTURE_MODE_KEY); | ||
| return v === "events" || v === "video" ? v : null; | ||
| } catch (e) { | ||
| return null; | ||
| } | ||
| } | ||
| function setActiveCaptureMode(mode) { | ||
| try { | ||
| localStorage.setItem(ACTIVE_CAPTURE_MODE_KEY, mode); | ||
| } catch (e) { | ||
| } | ||
| } | ||
| function getAllSessions() { | ||
| try { | ||
| const raw = localStorage.getItem(SESSIONS_KEY); | ||
| return raw ? JSON.parse(raw) : []; | ||
| } catch (e) { | ||
| return []; | ||
| } | ||
| } | ||
| function saveSessions(sessions) { | ||
| try { | ||
| localStorage.setItem(SESSIONS_KEY, JSON.stringify(sessions)); | ||
| return; | ||
| } catch (e) { | ||
| } | ||
| const commit = (next) => { | ||
| try { | ||
| localStorage.setItem(SESSIONS_KEY, JSON.stringify(next)); | ||
| sessions.length = 0; | ||
| sessions.push(...next); | ||
| return true; | ||
| } catch (e) { | ||
| return false; | ||
| } | ||
| }; | ||
| const working = sessions.slice(); | ||
| while (working.length > 1) { | ||
| working.shift(); | ||
| if (commit(working)) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] Storage full \u2014 dropped oldest session(s) to fit."); | ||
| return; | ||
| } | ||
| } | ||
| const last = working[0]; | ||
| if (last && Array.isArray(last.events)) { | ||
| while (last.events.length > 1) { | ||
| last.events = last.events.slice(Math.ceil(last.events.length / 2)); | ||
| if (commit(working)) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] Storage full \u2014 trimmed older events from the current session to fit."); | ||
| return; | ||
| } | ||
| } | ||
| } | ||
| if (typeof console !== "undefined") console.error("[TraceBug] Could not persist sessions: localStorage quota exceeded."); | ||
| } | ||
| var _cachedSessions = null; | ||
| var _pendingFlush = null; | ||
| var _dirty = false; | ||
| var FLUSH_INTERVAL_MS = 1e3; | ||
| function getCachedSessions() { | ||
| if (!_cachedSessions) { | ||
| _cachedSessions = getAllSessions(); | ||
| } | ||
| return _cachedSessions; | ||
| } | ||
| function scheduleFlush() { | ||
| _dirty = true; | ||
| if (_pendingFlush) return; | ||
| _pendingFlush = setTimeout(() => { | ||
| _pendingFlush = null; | ||
| if (_cachedSessions && _dirty) { | ||
| saveSessions(_cachedSessions); | ||
| _dirty = false; | ||
| } | ||
| }, FLUSH_INTERVAL_MS); | ||
| } | ||
| function flushPendingEvents() { | ||
| if (_pendingFlush) { | ||
| clearTimeout(_pendingFlush); | ||
| _pendingFlush = null; | ||
| } | ||
| if (_cachedSessions) { | ||
| saveSessions(_cachedSessions); | ||
| _dirty = false; | ||
| } | ||
| } | ||
| function invalidateCache() { | ||
| if (_pendingFlush) { | ||
| clearTimeout(_pendingFlush); | ||
| _pendingFlush = null; | ||
| } | ||
| _cachedSessions = null; | ||
| _dirty = false; | ||
| } | ||
| if (typeof window !== "undefined") { | ||
| window.addEventListener("beforeunload", flushPendingEvents); | ||
| window.addEventListener("pagehide", flushPendingEvents); | ||
| document.addEventListener("visibilitychange", () => { | ||
| if (document.visibilityState === "hidden") flushPendingEvents(); | ||
| }); | ||
| } | ||
| function appendEvent(sessionId, event, maxEvents, maxSessions) { | ||
| let sessions = getCachedSessions(); | ||
| let session = sessions.find((s) => s.sessionId === sessionId); | ||
| if (!session) { | ||
| session = { | ||
| sessionId, | ||
| projectId: event.projectId, | ||
| createdAt: Date.now(), | ||
| updatedAt: Date.now(), | ||
| errorMessage: null, | ||
| errorStack: null, | ||
| reproSteps: null, | ||
| errorSummary: null, | ||
| events: [], | ||
| annotations: [], | ||
| environment: null | ||
| }; | ||
| sessions.push(session); | ||
| } | ||
| session.events.push(event); | ||
| session.updatedAt = Date.now(); | ||
| if (session.events.length > maxEvents) { | ||
| session.events = session.events.slice(-maxEvents); | ||
| } | ||
| if (sessions.length > maxSessions) { | ||
| sessions = sessions.slice(-maxSessions); | ||
| _cachedSessions = sessions; | ||
| } | ||
| scheduleFlush(); | ||
| } | ||
| function updateSessionError(sessionId, errorMessage, errorStack, reproSteps, errorSummary) { | ||
| const sessions = getCachedSessions(); | ||
| const session = sessions.find((s) => s.sessionId === sessionId); | ||
| if (!session) return; | ||
| session.errorMessage = errorMessage; | ||
| session.errorStack = errorStack || null; | ||
| session.reproSteps = reproSteps; | ||
| session.errorSummary = errorSummary; | ||
| session.updatedAt = Date.now(); | ||
| scheduleFlush(); | ||
| } | ||
| function deleteSession(sessionId) { | ||
| flushPendingEvents(); | ||
| const remaining = getAllSessions().filter((s) => s.sessionId !== sessionId); | ||
| invalidateCache(); | ||
| saveSessions(remaining); | ||
| } | ||
| function addAnnotation(sessionId, annotation) { | ||
| const sessions = getCachedSessions(); | ||
| const session = sessions.find((s) => s.sessionId === sessionId); | ||
| if (!session) return; | ||
| if (!session.annotations) session.annotations = []; | ||
| session.annotations.push(annotation); | ||
| session.updatedAt = Date.now(); | ||
| scheduleFlush(); | ||
| } | ||
| function saveEnvironment(sessionId, env) { | ||
| const sessions = getCachedSessions(); | ||
| const session = sessions.find((s) => s.sessionId === sessionId); | ||
| if (!session) return; | ||
| session.environment = env; | ||
| scheduleFlush(); | ||
| } | ||
| function setSessionPriority(sessionId, priority) { | ||
| const sessions = getCachedSessions(); | ||
| const session = sessions.find((s) => s.sessionId === sessionId); | ||
| if (!session) return; | ||
| session.priority = priority; | ||
| session.updatedAt = Date.now(); | ||
| scheduleFlush(); | ||
| } | ||
| function markSessionSaved(sessionId) { | ||
| const sessions = getCachedSessions(); | ||
| const session = sessions.find((s) => s.sessionId === sessionId); | ||
| if (!session) return; | ||
| session.saved = true; | ||
| session.updatedAt = Date.now(); | ||
| flushPendingEvents(); | ||
| } | ||
| function clearAllSessions() { | ||
| invalidateCache(); | ||
| try { | ||
| localStorage.removeItem(SESSIONS_KEY); | ||
| } catch (e) { | ||
| } | ||
| } | ||
| // src/sanitize/custom-redaction.ts | ||
| var REDACTED = "[REDACTED]"; | ||
| var _fieldRe = null; | ||
| var _fieldTextRes = []; | ||
| var _patterns = []; | ||
| function escapeRe(s) { | ||
| return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); | ||
| } | ||
| function setRedactRules(rules) { | ||
| _fieldRe = null; | ||
| _fieldTextRes = []; | ||
| _patterns = []; | ||
| if (!rules) return; | ||
| const fields = (rules.fields || []).filter((f) => typeof f === "string" && f.trim().length > 0); | ||
| if (fields.length > 0) { | ||
| const alts = fields.map((f) => escapeRe(f.trim())).join("|"); | ||
| _fieldRe = new RegExp(alts, "i"); | ||
| for (const f of fields) { | ||
| const k = escapeRe(f.trim()); | ||
| _fieldTextRes.push({ | ||
| re: new RegExp(`("([^"]*${k}[^"]*)"\\s*:\\s*)("(?:[^"\\\\]|\\\\.)*"|-?\\d[\\d.eE+-]*|true|false)`, "gi"), | ||
| replace: `$1"${REDACTED}"` | ||
| }); | ||
| _fieldTextRes.push({ | ||
| re: new RegExp(`\\b([\\w.-]*${k}[\\w.-]*)=([^&\\s"']+)`, "gi"), | ||
| replace: `$1=${REDACTED}` | ||
| }); | ||
| } | ||
| } | ||
| for (const p of rules.patterns || []) { | ||
| try { | ||
| if (typeof p === "string") { | ||
| _patterns.push(new RegExp(p, "gi")); | ||
| } else if (p instanceof RegExp) { | ||
| _patterns.push(p.flags.includes("g") ? p : new RegExp(p.source, p.flags + "g")); | ||
| } | ||
| } catch (e) { | ||
| } | ||
| } | ||
| } | ||
| function isCustomSensitiveKey(key) { | ||
| if (!key || !_fieldRe) return false; | ||
| return _fieldRe.test(key); | ||
| } | ||
| function applyCustomRedaction(s) { | ||
| if (!s || _fieldTextRes.length === 0 && _patterns.length === 0) return s; | ||
| let out = s; | ||
| for (const { re, replace } of _fieldTextRes) out = out.replace(re, replace); | ||
| for (const re of _patterns) out = out.replace(re, REDACTED); | ||
| return out; | ||
| } | ||
| // src/sanitize/cloud-upload.ts | ||
| var REDACTED2 = "[REDACTED]"; | ||
| var TOKEN_PATTERNS = [ | ||
| // Bearer <token> in headers, console output, anywhere | ||
| { name: "bearer", re: /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}/gi, replace: () => "Bearer " + REDACTED2 }, | ||
| // JWT (3 base64url segments separated by dots, leading with eyJ which is `{"` in base64) | ||
| { name: "jwt", re: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g, replace: mask }, | ||
| // OpenAI / Stripe sk_* | ||
| { name: "sk_prefix", re: /\bsk-[A-Za-z0-9_-]{20,}\b/g, replace: mask }, | ||
| // Stripe secret + publishable (live/test, secret + publishable + restricted) | ||
| { name: "stripe", re: /\b(?:sk|pk|rk)_(?:live|test)_[A-Za-z0-9]{16,}\b/g, replace: mask }, | ||
| // GitHub PATs (classic + fine-grained + OAuth + server tokens) | ||
| { name: "github_pat", re: /\bghp_[A-Za-z0-9]{30,}\b/g, replace: mask }, | ||
| { name: "github_fine", re: /\bgithub_pat_[A-Za-z0-9_]{60,}\b/g, replace: mask }, | ||
| { name: "github_oauth", re: /\bgho_[A-Za-z0-9]{30,}\b/g, replace: mask }, | ||
| { name: "github_server", re: /\bghs_[A-Za-z0-9]{30,}\b/g, replace: mask }, | ||
| // AWS access keys (begins with AKIA, ASIA, AGPA, AIDA, etc.) + secret key (40-char base64) | ||
| { name: "aws_access", re: /\b(?:AKIA|ASIA|AGPA|AIDA|AROA|ANPA|ANVA)[A-Z0-9]{16}\b/g, replace: mask }, | ||
| { name: "aws_secret", re: /\b(?:aws.{0,20})?[A-Za-z0-9/+]{40}\b(?=.*aws|.*secret|.*key)/gi, replace: mask }, | ||
| // Slack — broader than before (xoxa-z covers all known prefixes) | ||
| { name: "slack", re: /\bxox[abeprs]-[A-Za-z0-9-]{10,}\b/g, replace: mask }, | ||
| // Google API keys | ||
| { name: "google_api", re: /\bAIza[A-Za-z0-9_-]{35}\b/g, replace: mask }, | ||
| // Twilio — Account SID + Auth tokens | ||
| { name: "twilio_sid", re: /\b(?:AC|SK)[a-f0-9]{32}\b/g, replace: mask }, | ||
| // SendGrid | ||
| { name: "sendgrid", re: /\bSG\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}\b/g, replace: mask }, | ||
| // Mailgun | ||
| { name: "mailgun", re: /\bkey-[a-f0-9]{32}\b/g, replace: mask }, | ||
| // Postmark | ||
| { name: "postmark", re: /\b[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\b(?=.{0,30}(postmark|server-token|api-token))/gi, replace: mask }, | ||
| // Linear / Vercel / Cloudflare / Discord | ||
| { name: "linear", re: /\blin_api_[A-Za-z0-9]{40,}\b/g, replace: mask }, | ||
| { name: "discord_bot", re: /\b[MN][A-Za-z\d]{23}\.[A-Za-z\d_-]{6}\.[A-Za-z\d_-]{27,}\b/g, replace: mask }, | ||
| // Generic high-entropy hex (≥32 chars). Catches webhook signing secrets, | ||
| // session IDs, etc. that don't carry a recognizable prefix. Conservative: | ||
| // only triggers when preceded by a common secret-y keyword to avoid | ||
| // mangling legitimate hex like git SHAs. | ||
| { name: "labeled_hex", re: /\b(?:secret|token|key|password|api[_-]?key|auth)["':\s=]{1,5}([a-fA-F0-9]{32,})\b/gi, replace: (s) => s.replace(/[a-fA-F0-9]{32,}/, REDACTED2) } | ||
| ]; | ||
| function mask(s) { | ||
| if (s.length <= 12) return REDACTED2; | ||
| return `${s.slice(0, 4)}\u2026${REDACTED2}\u2026${s.slice(-4)}`; | ||
| } | ||
| var SENSITIVE_QUERY_KEYS = /* @__PURE__ */ new Set([ | ||
| "token", | ||
| "access_token", | ||
| "id_token", | ||
| "refresh_token", | ||
| "api_key", | ||
| "apikey", | ||
| "secret", | ||
| "password", | ||
| "passwd", | ||
| "pwd", | ||
| "auth", | ||
| "authorization", | ||
| "x-api-key", | ||
| "session", | ||
| "sid", | ||
| "csrf" | ||
| ]); | ||
| function sanitizeUrl(url) { | ||
| if (typeof url !== "string" || url.length === 0) return url; | ||
| try { | ||
| const isAbs = /^[a-z][a-z0-9+.-]*:/i.test(url); | ||
| const u = new URL(isAbs ? url : `http://_placeholder_${url.startsWith("/") ? "" : "/"}${url}`); | ||
| let changed = false; | ||
| u.searchParams.forEach((_v, k) => { | ||
| if (SENSITIVE_QUERY_KEYS.has(k.toLowerCase()) || isCustomSensitiveKey(k)) { | ||
| u.searchParams.set(k, REDACTED2); | ||
| changed = true; | ||
| } | ||
| }); | ||
| if (!changed) return sanitizeText(url); | ||
| const out = isAbs ? u.toString() : u.pathname + u.search + u.hash; | ||
| return sanitizeText(out); | ||
| } catch (e) { | ||
| return sanitizeText(url); | ||
| } | ||
| } | ||
| function sanitizeText(s) { | ||
| if (s == null) return s; | ||
| let out = String(s); | ||
| for (const p of TOKEN_PATTERNS) out = out.replace(p.re, p.replace); | ||
| return applyCustomRedaction(out); | ||
| } | ||
| function sanitizeTokenShapes(s) { | ||
| return sanitizeText(s); | ||
| } | ||
| function sanitizeReportForUpload(report) { | ||
| var _a; | ||
| const out = typeof structuredClone === "function" ? structuredClone(report) : JSON.parse(JSON.stringify(report)); | ||
| if (out.consoleErrors) { | ||
| out.consoleErrors = out.consoleErrors.map((e) => { | ||
| var _a2; | ||
| return { | ||
| ...e, | ||
| message: sanitizeText(e.message), | ||
| stack: sanitizeText((_a2 = e.stack) != null ? _a2 : void 0) | ||
| }; | ||
| }); | ||
| } | ||
| if (out.consoleLogs) { | ||
| out.consoleLogs = out.consoleLogs.map((e) => { | ||
| var _a2; | ||
| return { | ||
| ...e, | ||
| message: sanitizeText(e.message), | ||
| stack: sanitizeText((_a2 = e.stack) != null ? _a2 : void 0) | ||
| }; | ||
| }); | ||
| } | ||
| if (out.networkErrors) { | ||
| out.networkErrors = out.networkErrors.map((e) => { | ||
| var _a2; | ||
| return { | ||
| ...e, | ||
| url: sanitizeUrl(e.url), | ||
| response: sanitizeText((_a2 = e.response) != null ? _a2 : void 0) | ||
| }; | ||
| }); | ||
| } | ||
| if (out.networkRequests) { | ||
| out.networkRequests = out.networkRequests.map((e) => { | ||
| var _a2; | ||
| return { | ||
| ...e, | ||
| url: sanitizeUrl(e.url), | ||
| response: sanitizeText((_a2 = e.response) != null ? _a2 : void 0) | ||
| }; | ||
| }); | ||
| } | ||
| if (out.steps) out.steps = sanitizeText(out.steps); | ||
| if (out.summary) out.summary = sanitizeText(out.summary); | ||
| if (out.title) out.title = sanitizeText(out.title); | ||
| if (Array.isArray(out.sessionSteps)) out.sessionSteps = out.sessionSteps.map((s) => sanitizeText(s)); | ||
| if (out.actionChips) { | ||
| out.actionChips = out.actionChips.map((c) => { | ||
| var _a2, _b; | ||
| return { | ||
| ...c, | ||
| target: sanitizeText((_a2 = c.target) != null ? _a2 : void 0), | ||
| detail: sanitizeText((_b = c.detail) != null ? _b : void 0) | ||
| }; | ||
| }); | ||
| } | ||
| if (out.storage) { | ||
| const scrub = (entries) => entries.map((e) => ({ ...e, value: sanitizeText(e.value) })); | ||
| out.storage.local = scrub(out.storage.local || []); | ||
| out.storage.session = scrub(out.storage.session || []); | ||
| if (out.storage.cookies) out.storage.cookies = scrub(out.storage.cookies); | ||
| } | ||
| if ((_a = out.environment) == null ? void 0 : _a.url) out.environment.url = sanitizeUrl(out.environment.url); | ||
| if (out.context && typeof out.context === "object") { | ||
| const ctx = {}; | ||
| for (const [k, v] of Object.entries(out.context)) { | ||
| ctx[k] = typeof v === "string" ? sanitizeText(v) : v; | ||
| } | ||
| out.context = ctx; | ||
| } | ||
| return out; | ||
| } | ||
| // src/collectors.ts | ||
| var ROOT_ID = "tracebug-root"; | ||
| var PANEL_ID = "tracebug-dashboard-panel"; | ||
| var BTN_ID = "tracebug-dashboard-btn"; | ||
| var NETWORK_FAILURE_LIMIT = 10; | ||
| var RESPONSE_SNIPPET_CHARS = 200; | ||
| var _networkFailures = []; | ||
| function pushNetworkFailure(failure) { | ||
| try { | ||
| if (failure.response) failure.response = sanitizeTokenShapes(failure.response); | ||
| _networkFailures.push(failure); | ||
| if (_networkFailures.length > NETWORK_FAILURE_LIMIT) { | ||
| _networkFailures.splice(0, _networkFailures.length - NETWORK_FAILURE_LIMIT); | ||
| } | ||
| } catch (e) { | ||
| } | ||
| } | ||
| function getNetworkFailures() { | ||
| return _networkFailures.slice(); | ||
| } | ||
| function clearNetworkFailures() { | ||
| _networkFailures.length = 0; | ||
| } | ||
| var SENSITIVE_PARAM_RE = /token|key|secret|auth|password|sig|signature/i; | ||
| function sanitizeUrl2(url) { | ||
| if (!url) return url; | ||
| try { | ||
| const qIdx = url.indexOf("?"); | ||
| if (qIdx === -1) return url; | ||
| const base = url.slice(0, qIdx); | ||
| const afterQ = url.slice(qIdx + 1); | ||
| const hashIdx = afterQ.indexOf("#"); | ||
| const query = hashIdx === -1 ? afterQ : afterQ.slice(0, hashIdx); | ||
| const hash = hashIdx === -1 ? "" : afterQ.slice(hashIdx); | ||
| const redacted = query.split("&").map((part) => { | ||
| const eqIdx = part.indexOf("="); | ||
| if (eqIdx === -1) return part; | ||
| const key = part.slice(0, eqIdx); | ||
| if (SENSITIVE_PARAM_RE.test(key) || isCustomSensitiveKey(key)) return `${key}=[REDACTED]`; | ||
| return part; | ||
| }).join("&"); | ||
| return `${base}?${redacted}${hash}`; | ||
| } catch (e) { | ||
| return url; | ||
| } | ||
| } | ||
| var MAX_BODY_BYTES = 10 * 1024; | ||
| var BINARY_CONTENT_TYPE_RE = /^(image|video|audio)\/|^application\/(octet-stream|pdf|zip|x-protobuf|x-msgpack|wasm|vnd\.)/i; | ||
| async function readResponseBodySafe(response) { | ||
| try { | ||
| const ct = response.headers.get("content-type") || ""; | ||
| if (BINARY_CONTENT_TYPE_RE.test(ct)) return ""; | ||
| if (!response.body || typeof response.body.getReader !== "function") { | ||
| try { | ||
| const text = await response.text(); | ||
| return typeof text === "string" ? text.slice(0, RESPONSE_SNIPPET_CHARS) : ""; | ||
| } catch (e) { | ||
| return ""; | ||
| } | ||
| } | ||
| const reader = response.body.getReader(); | ||
| const decoder = new TextDecoder("utf-8", { fatal: false }); | ||
| let collected = ""; | ||
| let bytesRead = 0; | ||
| while (bytesRead < MAX_BODY_BYTES && collected.length < RESPONSE_SNIPPET_CHARS) { | ||
| const { value, done } = await reader.read(); | ||
| if (done) break; | ||
| if (value) { | ||
| bytesRead += value.byteLength; | ||
| collected += decoder.decode(value, { stream: true }); | ||
| } | ||
| } | ||
| try { | ||
| await reader.cancel(); | ||
| } catch (e) { | ||
| } | ||
| return collected.slice(0, RESPONSE_SNIPPET_CHARS); | ||
| } catch (e) { | ||
| return ""; | ||
| } | ||
| } | ||
| var INTERNAL_URL_PATTERNS = [ | ||
| /__nextjs_original-stack-frame/, | ||
| /\/_next\/static\/webpack/, | ||
| /\/__webpack_hmr/, | ||
| /\.hot-update\./, | ||
| /\/sockjs-node\//, | ||
| /\/turbopack-hmr\//, | ||
| /\/_next\/webpack-hmr/, | ||
| /\/webpack-dev-server\//, | ||
| /\/__vite_ping/, | ||
| /\/@vite\/client/, | ||
| /\/@react-refresh/ | ||
| ]; | ||
| function isInternalUrl(url) { | ||
| return INTERNAL_URL_PATTERNS.some((pattern) => pattern.test(url)); | ||
| } | ||
| var _rootCache; | ||
| function getRoot() { | ||
| if (_rootCache === void 0) { | ||
| _rootCache = document.getElementById(ROOT_ID); | ||
| } | ||
| if (_rootCache && !_rootCache.isConnected) { | ||
| _rootCache = document.getElementById(ROOT_ID); | ||
| } | ||
| return _rootCache; | ||
| } | ||
| function isTraceBugElement(el) { | ||
| if (!el) return false; | ||
| if (el.id === ROOT_ID || el.id === BTN_ID || el.id === PANEL_ID) return true; | ||
| if (el.dataset && el.dataset.tracebug) return true; | ||
| const root = getRoot(); | ||
| if (root && root.contains(el)) return true; | ||
| let node = el; | ||
| while (node) { | ||
| const id = node.id || ""; | ||
| if (id.startsWith("tracebug-") || id.startsWith("bt-")) return true; | ||
| const cn = typeof node.className === "string" ? node.className : ""; | ||
| if (cn.includes("tracebug-") || cn.includes("bt-ann") || cn.includes("bt-voice")) return true; | ||
| if (node.dataset && node.dataset.tracebug) return true; | ||
| node = node.parentElement; | ||
| } | ||
| return false; | ||
| } | ||
| function collectClicks(emit) { | ||
| const handler = (e) => { | ||
| try { | ||
| const t = e.target; | ||
| if (!t || isTraceBugElement(t)) return; | ||
| const tag = t.tagName.toLowerCase(); | ||
| const el = { | ||
| tag, | ||
| text: (t.innerText || "").slice(0, 120), | ||
| id: t.id || "", | ||
| className: typeof t.className === "string" ? t.className : "" | ||
| }; | ||
| const data = { element: el }; | ||
| if (tag === "a") el.href = t.href || ""; | ||
| if (tag === "button" || t.type === "submit") { | ||
| el.buttonType = t.type || "button"; | ||
| el.disabled = t.disabled; | ||
| } | ||
| if (tag === "label") el.forField = t.htmlFor || ""; | ||
| const ariaLabel = t.getAttribute("aria-label"); | ||
| if (ariaLabel) el.ariaLabel = ariaLabel; | ||
| const role = t.getAttribute("role"); | ||
| if (role) el.role = role; | ||
| const testId = t.getAttribute("data-testid"); | ||
| if (testId) el.testId = testId; | ||
| const form = t.closest("form"); | ||
| if (form) { | ||
| el.formId = form.id || ""; | ||
| el.formAction = form.action || ""; | ||
| } | ||
| try { | ||
| el.selector = buildSelector(t); | ||
| } catch (e2) { | ||
| } | ||
| try { | ||
| const r = t.getBoundingClientRect(); | ||
| el.boundingBox = { x: Math.round(r.left), y: Math.round(r.top), width: Math.round(r.width), height: Math.round(r.height) }; | ||
| } catch (e2) { | ||
| } | ||
| emit("click", data); | ||
| } catch (err) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] Click capture error:", err); | ||
| } | ||
| }; | ||
| document.addEventListener("click", handler, { capture: true }); | ||
| return () => document.removeEventListener("click", handler, { capture: true }); | ||
| } | ||
| function buildSelector(el) { | ||
| if (!el) return ""; | ||
| if (el.id) return `#${CSS.escape(el.id)}`; | ||
| const testId = el.getAttribute("data-testid"); | ||
| if (testId) return `[data-testid="${testId}"]`; | ||
| const parts = []; | ||
| let node = el; | ||
| let depth = 0; | ||
| while (node && node !== document.body && depth < 4) { | ||
| let part = node.tagName.toLowerCase(); | ||
| if (node.id) { | ||
| parts.unshift(`#${CSS.escape(node.id)}`); | ||
| break; | ||
| } | ||
| const cls = typeof node.className === "string" ? node.className.trim().split(/\s+/).filter(Boolean)[0] : ""; | ||
| if (cls) part += `.${CSS.escape(cls)}`; | ||
| const parent = node.parentElement; | ||
| const currentTag = node.tagName; | ||
| const currentNode = node; | ||
| if (parent) { | ||
| const sameTag = Array.from(parent.children).filter((c) => c.tagName === currentTag); | ||
| if (sameTag.length > 1) part += `:nth-of-type(${sameTag.indexOf(currentNode) + 1})`; | ||
| } | ||
| parts.unshift(part); | ||
| node = parent; | ||
| depth++; | ||
| } | ||
| return parts.join(" > "); | ||
| } | ||
| function collectInputs(emit) { | ||
| const timers = /* @__PURE__ */ new Map(); | ||
| const handler = (e) => { | ||
| try { | ||
| const t = e.target; | ||
| if (!t || !("value" in t) || isTraceBugElement(t)) return; | ||
| if (t.tagName.toLowerCase() === "select") return; | ||
| const prev = timers.get(t); | ||
| if (prev) clearTimeout(prev); | ||
| timers.set( | ||
| t, | ||
| setTimeout(() => { | ||
| try { | ||
| const tag = t.tagName.toLowerCase(); | ||
| const inputType = t.type || ""; | ||
| const isSensitive = ["password", "credit-card", "ssn"].includes(inputType) || /password|secret|token|ssn|credit/i.test(t.name || t.id || "") || isCustomSensitiveKey(t.name || t.id); | ||
| const element = { | ||
| tag, | ||
| name: t.name || t.id || "", | ||
| type: inputType, | ||
| valueLength: (t.value || "").length, | ||
| value: isSensitive ? "[REDACTED]" : (t.value || "").slice(0, 200), | ||
| placeholder: t.placeholder || "" | ||
| }; | ||
| const data = { element }; | ||
| if (inputType === "checkbox" || inputType === "radio") { | ||
| element.checked = t.checked; | ||
| element.value = t.checked ? "checked" : "unchecked"; | ||
| } | ||
| if (inputType === "number" || inputType === "range") { | ||
| element.value = t.value; | ||
| } | ||
| emit("input", data); | ||
| } catch (err) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] Input capture error:", err); | ||
| } | ||
| timers.delete(t); | ||
| }, 300) | ||
| ); | ||
| } catch (err) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] Input capture error:", err); | ||
| } | ||
| }; | ||
| document.addEventListener("input", handler, { capture: true }); | ||
| return () => { | ||
| document.removeEventListener("input", handler, { capture: true }); | ||
| timers.forEach((t) => clearTimeout(t)); | ||
| }; | ||
| } | ||
| function collectSelectChanges(emit) { | ||
| const handler = (e) => { | ||
| try { | ||
| const t = e.target; | ||
| if (!t || t.tagName.toLowerCase() !== "select" || isTraceBugElement(t)) return; | ||
| const selectedOption = t.options[t.selectedIndex]; | ||
| emit("select_change", { | ||
| element: { | ||
| tag: "select", | ||
| name: t.name || t.id || "", | ||
| value: t.value, | ||
| selectedText: selectedOption ? selectedOption.text : "", | ||
| selectedIndex: t.selectedIndex, | ||
| optionCount: t.options.length, | ||
| allOptions: Array.from(t.options).map((o) => o.text).slice(0, 20) | ||
| } | ||
| }); | ||
| } catch (err) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] Select capture error:", err); | ||
| } | ||
| }; | ||
| document.addEventListener("change", handler, { capture: true }); | ||
| return () => document.removeEventListener("change", handler, { capture: true }); | ||
| } | ||
| function collectFormSubmits(emit) { | ||
| const handler = (e) => { | ||
| try { | ||
| const form = e.target; | ||
| if (!form || form.tagName.toLowerCase() !== "form" || isTraceBugElement(form)) return; | ||
| const formData = {}; | ||
| const elements = form.elements; | ||
| for (let i = 0; i < elements.length; i++) { | ||
| const el = elements[i]; | ||
| if (!el.name) continue; | ||
| const isSensitive = ["password"].includes(el.type) || /password|secret|token|ssn|credit/i.test(el.name) || isCustomSensitiveKey(el.name); | ||
| if (el.type === "submit" || el.type === "button") continue; | ||
| formData[el.name] = isSensitive ? "[REDACTED]" : (el.value || "").slice(0, 200); | ||
| } | ||
| emit("form_submit", { | ||
| form: { id: form.id || "", action: form.action || "", method: form.method || "GET", fieldCount: elements.length, fields: formData } | ||
| }); | ||
| } catch (err) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] Form capture error:", err); | ||
| } | ||
| }; | ||
| document.addEventListener("submit", handler, { capture: true }); | ||
| return () => document.removeEventListener("submit", handler, { capture: true }); | ||
| } | ||
| function collectRouteChanges(emit) { | ||
| let lastPath = window.location.pathname; | ||
| const check = () => { | ||
| const current = window.location.pathname; | ||
| if (current !== lastPath) { | ||
| const from = lastPath; | ||
| lastPath = current; | ||
| emit("route_change", { from, to: current }); | ||
| } | ||
| }; | ||
| window.addEventListener("popstate", check); | ||
| const origPush = history.pushState.bind(history); | ||
| const origReplace = history.replaceState.bind(history); | ||
| history.pushState = function(...args) { | ||
| origPush(...args); | ||
| check(); | ||
| }; | ||
| history.replaceState = function(...args) { | ||
| origReplace(...args); | ||
| check(); | ||
| }; | ||
| return () => { | ||
| window.removeEventListener("popstate", check); | ||
| history.pushState = origPush; | ||
| history.replaceState = origReplace; | ||
| }; | ||
| } | ||
| function collectApiRequests(emit) { | ||
| const originalFetch = window.fetch; | ||
| window.fetch = async function(input, init) { | ||
| var _a; | ||
| let url = ""; | ||
| let method = "GET"; | ||
| try { | ||
| if (typeof input === "string") { | ||
| url = input; | ||
| } else if (input instanceof URL) { | ||
| url = input.href; | ||
| } else if (input && typeof input === "object" && "url" in input) { | ||
| url = input.url; | ||
| method = input.method || "GET"; | ||
| } | ||
| if (init == null ? void 0 : init.method) method = init.method; | ||
| } catch (e) { | ||
| } | ||
| const start = Date.now(); | ||
| try { | ||
| if (url && isInternalUrl(url)) return originalFetch.call(window, input, init); | ||
| } catch (e) { | ||
| } | ||
| const safeUrl = sanitizeUrl2(url).slice(0, 500); | ||
| try { | ||
| const response = await originalFetch.call(window, input, init); | ||
| try { | ||
| emit("api_request", { | ||
| request: { url: safeUrl, method: method.toUpperCase(), statusCode: response.status, durationMs: Date.now() - start } | ||
| }); | ||
| } catch (e) { | ||
| } | ||
| try { | ||
| if (response.status >= 400 || response.status === 0) { | ||
| const clone = response.clone(); | ||
| readResponseBodySafe(clone).then((snippet) => { | ||
| pushNetworkFailure({ | ||
| url: safeUrl, | ||
| method: method.toUpperCase(), | ||
| status: response.status, | ||
| response: snippet, | ||
| timestamp: Date.now() | ||
| }); | ||
| }).catch(() => { | ||
| pushNetworkFailure({ | ||
| url: safeUrl, | ||
| method: method.toUpperCase(), | ||
| status: response.status, | ||
| response: "", | ||
| timestamp: Date.now() | ||
| }); | ||
| }); | ||
| } | ||
| } catch (e) { | ||
| } | ||
| return response; | ||
| } catch (err) { | ||
| try { | ||
| emit("api_request", { | ||
| request: { url: safeUrl, method: method.toUpperCase(), statusCode: 0, durationMs: Date.now() - start } | ||
| }); | ||
| } catch (e) { | ||
| } | ||
| try { | ||
| pushNetworkFailure({ | ||
| url: safeUrl, | ||
| method: method.toUpperCase(), | ||
| status: 0, | ||
| response: ((_a = err == null ? void 0 : err.message) == null ? void 0 : _a.slice(0, RESPONSE_SNIPPET_CHARS)) || "", | ||
| timestamp: Date.now() | ||
| }); | ||
| } catch (e) { | ||
| } | ||
| throw err; | ||
| } | ||
| }; | ||
| return () => { | ||
| window.fetch = originalFetch; | ||
| }; | ||
| } | ||
| function collectXhrRequests(emit) { | ||
| const OrigXHR = window.XMLHttpRequest; | ||
| const origOpen = OrigXHR.prototype.open; | ||
| const origSend = OrigXHR.prototype.send; | ||
| const xhrMeta = /* @__PURE__ */ new WeakMap(); | ||
| OrigXHR.prototype.open = function(method, url, ...rest) { | ||
| try { | ||
| xhrMeta.set(this, { method, url: typeof url === "string" ? url : url.toString() }); | ||
| } catch (e) { | ||
| } | ||
| return origOpen.apply(this, [method, url, ...rest]); | ||
| }; | ||
| OrigXHR.prototype.send = function(body) { | ||
| try { | ||
| const xhr = this; | ||
| const start = Date.now(); | ||
| const meta = xhrMeta.get(xhr); | ||
| const method = (meta == null ? void 0 : meta.method) || "GET"; | ||
| const url = (meta == null ? void 0 : meta.url) || ""; | ||
| if (isInternalUrl(url)) return origSend.call(this, body); | ||
| const safeUrl = sanitizeUrl2(url).slice(0, 500); | ||
| xhr.addEventListener("loadend", function() { | ||
| try { | ||
| emit("api_request", { request: { url: safeUrl, method: method.toUpperCase(), statusCode: xhr.status, durationMs: Date.now() - start } }); | ||
| } catch (e) { | ||
| } | ||
| try { | ||
| if (xhr.status >= 400 || xhr.status === 0) { | ||
| let body2 = ""; | ||
| try { | ||
| const ct = xhr.getResponseHeader && xhr.getResponseHeader("content-type") || ""; | ||
| if (!BINARY_CONTENT_TYPE_RE.test(ct)) { | ||
| body2 = typeof xhr.responseText === "string" ? xhr.responseText : ""; | ||
| } | ||
| } catch (e) { | ||
| } | ||
| pushNetworkFailure({ | ||
| url: safeUrl, | ||
| method: method.toUpperCase(), | ||
| status: xhr.status, | ||
| response: body2.slice(0, RESPONSE_SNIPPET_CHARS), | ||
| timestamp: Date.now() | ||
| }); | ||
| } | ||
| } catch (e) { | ||
| } | ||
| }); | ||
| xhr.addEventListener("error", function() { | ||
| try { | ||
| emit("api_request", { request: { url: safeUrl, method: method.toUpperCase(), statusCode: 0, durationMs: Date.now() - start } }); | ||
| } catch (e) { | ||
| } | ||
| try { | ||
| pushNetworkFailure({ | ||
| url: safeUrl, | ||
| method: method.toUpperCase(), | ||
| status: 0, | ||
| response: "", | ||
| timestamp: Date.now() | ||
| }); | ||
| } catch (e) { | ||
| } | ||
| }); | ||
| } catch (err) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] XHR capture error:", err); | ||
| } | ||
| return origSend.call(this, body); | ||
| }; | ||
| return () => { | ||
| OrigXHR.prototype.open = origOpen; | ||
| OrigXHR.prototype.send = origSend; | ||
| }; | ||
| } | ||
| var _perfSeen = /* @__PURE__ */ new Set(); | ||
| function _emitPerfEntry(emit, e) { | ||
| if (!e || !e.name) return; | ||
| try { | ||
| if (isInternalUrl(e.name)) return; | ||
| } catch (e2) { | ||
| } | ||
| if (typeof e.name === "string" && e.name.indexOf("tracebug") !== -1) return; | ||
| const key = `${e.name}|${Math.round(e.startTime)}`; | ||
| if (_perfSeen.has(key)) return; | ||
| _perfSeen.add(key); | ||
| const navStart = typeof performance.timeOrigin === "number" ? performance.timeOrigin : Date.now(); | ||
| const ext = e; | ||
| const initiator = e.initiatorType || ""; | ||
| const method = (ext.method || "GET").toUpperCase(); | ||
| const status = ext.responseStatus || 0; | ||
| const url = sanitizeUrl2(e.name).slice(0, 500); | ||
| const timestamp = Math.round(navStart + e.startTime); | ||
| const durationMs = Math.round(e.duration || 0); | ||
| try { | ||
| emit("api_request", { | ||
| request: { url, method, statusCode: status, durationMs, initiatorType: initiator }, | ||
| _ts: timestamp | ||
| }); | ||
| } catch (e2) { | ||
| } | ||
| } | ||
| function drainPerformanceNetwork(emit) { | ||
| if (typeof performance === "undefined") return; | ||
| try { | ||
| const entries = performance.getEntriesByType("resource"); | ||
| for (const e of entries) _emitPerfEntry(emit, e); | ||
| } catch (e) { | ||
| } | ||
| } | ||
| function collectPerformanceNetwork(emit) { | ||
| if (typeof performance === "undefined" || typeof PerformanceObserver === "undefined") { | ||
| return () => { | ||
| }; | ||
| } | ||
| drainPerformanceNetwork(emit); | ||
| let observer = null; | ||
| try { | ||
| observer = new PerformanceObserver((list) => { | ||
| for (const e of list.getEntries()) { | ||
| _emitPerfEntry(emit, e); | ||
| } | ||
| }); | ||
| observer.observe({ type: "resource", buffered: false }); | ||
| } catch (e) { | ||
| } | ||
| return () => { | ||
| try { | ||
| observer == null ? void 0 : observer.disconnect(); | ||
| } catch (e) { | ||
| } | ||
| }; | ||
| } | ||
| function collectErrors(emit) { | ||
| const prevOnError = window.onerror; | ||
| window.onerror = (msg, source, line, col, error) => { | ||
| try { | ||
| emit("error", { | ||
| error: { | ||
| message: sanitizeTokenShapes(typeof msg === "string" ? msg : "Unknown error"), | ||
| stack: (error == null ? void 0 : error.stack) && sanitizeTokenShapes(error.stack), | ||
| source, | ||
| line, | ||
| column: col | ||
| } | ||
| }); | ||
| } catch (e) { | ||
| } | ||
| if (prevOnError) { | ||
| try { | ||
| prevOnError(msg, source, line, col, error); | ||
| } catch (e) { | ||
| } | ||
| } | ||
| }; | ||
| const onRejection = (e) => { | ||
| var _a, _b; | ||
| try { | ||
| emit("unhandled_rejection", { | ||
| error: { | ||
| message: sanitizeTokenShapes(((_a = e.reason) == null ? void 0 : _a.message) || String(e.reason)), | ||
| stack: ((_b = e.reason) == null ? void 0 : _b.stack) && sanitizeTokenShapes(e.reason.stack) | ||
| } | ||
| }); | ||
| } catch (e2) { | ||
| } | ||
| }; | ||
| window.addEventListener("unhandledrejection", onRejection); | ||
| return () => { | ||
| window.onerror = prevOnError; | ||
| window.removeEventListener("unhandledrejection", onRejection); | ||
| }; | ||
| } | ||
| function collectConsoleErrors(emit) { | ||
| const origConsoleError = console.error; | ||
| let _insideEmit = false; | ||
| console.error = function(...args) { | ||
| if (_insideEmit) { | ||
| origConsoleError.apply(console, args); | ||
| return; | ||
| } | ||
| _insideEmit = true; | ||
| try { | ||
| emit("console_error", { | ||
| // Token-shape scrub at capture — a token logged to the console must | ||
| // never reach the offline .html export unmasked (the cloud sanitizer | ||
| // only covers the upload path). | ||
| error: { message: sanitizeTokenShapes(args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")) } | ||
| }); | ||
| } catch (e) { | ||
| } finally { | ||
| _insideEmit = false; | ||
| } | ||
| origConsoleError.apply(console, args); | ||
| }; | ||
| return () => { | ||
| console.error = origConsoleError; | ||
| }; | ||
| } | ||
| var CONSOLE_LEVEL_CAP = 50; | ||
| function wrapConsoleLevel(method, type, emit) { | ||
| const orig = console[method]; | ||
| let _inside = false; | ||
| let _count = 0; | ||
| console[method] = function(...args) { | ||
| const own = typeof args[0] === "string" && args[0].startsWith("[TraceBug]"); | ||
| if (_inside || own || _count >= CONSOLE_LEVEL_CAP) { | ||
| orig.apply(console, args); | ||
| return; | ||
| } | ||
| _inside = true; | ||
| _count++; | ||
| try { | ||
| emit(type, { | ||
| // Same capture-time token scrub as console_error — the offline | ||
| // export path never runs the cloud sanitizer. | ||
| error: { message: sanitizeTokenShapes(args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")) } | ||
| }); | ||
| } catch (e) { | ||
| } finally { | ||
| _inside = false; | ||
| } | ||
| orig.apply(console, args); | ||
| }; | ||
| return () => { | ||
| console[method] = orig; | ||
| }; | ||
| } | ||
| function collectConsoleWarnings(emit) { | ||
| return wrapConsoleLevel("warn", "console_warn", emit); | ||
| } | ||
| function collectConsoleInfo(emit) { | ||
| return wrapConsoleLevel("info", "console_info", emit); | ||
| } | ||
| function collectConsoleLogs(emit) { | ||
| return wrapConsoleLevel("log", "console_log", emit); | ||
| } | ||
| // src/ui/helpers.ts | ||
| function tbIsolationCss(root) { | ||
| return ` | ||
| ${root} { | ||
| box-sizing: border-box; | ||
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; | ||
| font-size: 14px; font-weight: 400; line-height: 1.5; font-style: normal; | ||
| letter-spacing: normal; text-transform: none; text-align: left; | ||
| text-indent: 0; white-space: normal; word-spacing: normal; text-shadow: none; | ||
| -webkit-font-smoothing: antialiased; | ||
| } | ||
| ${root} *, ${root} *::before, ${root} *::after { box-sizing: border-box; } | ||
| ${root} button, ${root} input, ${root} select, ${root} textarea { | ||
| font-family: inherit; font-size: inherit; letter-spacing: normal; | ||
| text-transform: none; margin: 0; | ||
| } | ||
| ${root} svg { max-width: none; max-height: none; vertical-align: middle; } | ||
| ${root} img { max-width: none; } | ||
| ${root} a { text-decoration: none; } | ||
| `; | ||
| } | ||
| function parseShortcut(shortcut) { | ||
| const parts = (shortcut || "").toLowerCase().split("+").map((s) => s.trim()); | ||
| return { | ||
| mod: parts.includes("ctrl") || parts.includes("control") || parts.includes("cmd") || parts.includes("meta"), | ||
| shift: parts.includes("shift"), | ||
| alt: parts.includes("alt") || parts.includes("option"), | ||
| key: parts[parts.length - 1] || "" | ||
| }; | ||
| } | ||
| function matchesShortcut(e, shortcut) { | ||
| if (!shortcut) return false; | ||
| const s = parseShortcut(shortcut); | ||
| const mod = e.ctrlKey || e.metaKey; | ||
| const key = (e.key || "").toLowerCase(); | ||
| return mod === s.mod && e.shiftKey === s.shift && e.altKey === s.alt && key === s.key; | ||
| } | ||
| function escapeHtml(str) { | ||
| return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """); | ||
| } | ||
| exports.generateSessionId = generateSessionId; exports.getActiveSessionId = getActiveSessionId; exports.setActiveSessionId = setActiveSessionId; exports.clearActiveSessionId = clearActiveSessionId; exports.getActiveCaptureMode = getActiveCaptureMode; exports.setActiveCaptureMode = setActiveCaptureMode; exports.getAllSessions = getAllSessions; exports.getCachedSessions = getCachedSessions; exports.scheduleFlush = scheduleFlush; exports.flushPendingEvents = flushPendingEvents; exports.appendEvent = appendEvent; exports.updateSessionError = updateSessionError; exports.deleteSession = deleteSession; exports.addAnnotation = addAnnotation; exports.saveEnvironment = saveEnvironment; exports.setSessionPriority = setSessionPriority; exports.markSessionSaved = markSessionSaved; exports.clearAllSessions = clearAllSessions; exports.setRedactRules = setRedactRules; exports.isCustomSensitiveKey = isCustomSensitiveKey; exports.sanitizeTokenShapes = sanitizeTokenShapes; exports.sanitizeReportForUpload = sanitizeReportForUpload; exports.getNetworkFailures = getNetworkFailures; exports.clearNetworkFailures = clearNetworkFailures; exports.collectClicks = collectClicks; exports.collectInputs = collectInputs; exports.collectSelectChanges = collectSelectChanges; exports.collectFormSubmits = collectFormSubmits; exports.collectRouteChanges = collectRouteChanges; exports.collectApiRequests = collectApiRequests; exports.collectXhrRequests = collectXhrRequests; exports.drainPerformanceNetwork = drainPerformanceNetwork; exports.collectPerformanceNetwork = collectPerformanceNetwork; exports.collectErrors = collectErrors; exports.collectConsoleErrors = collectConsoleErrors; exports.collectConsoleWarnings = collectConsoleWarnings; exports.collectConsoleInfo = collectConsoleInfo; exports.collectConsoleLogs = collectConsoleLogs; exports.tbIsolationCss = tbIsolationCss; exports.matchesShortcut = matchesShortcut; exports.escapeHtml = escapeHtml; | ||
| //# sourceMappingURL=chunk-4SLN5LQD.cjs.map |
| {"version":3,"sources":["d:\\Project\\TraceBug-ai\\dist\\chunk-4SLN5LQD.cjs"],"names":[],"mappings":"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;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,cAAA;AACA,cAAA;AACA,YAAA;AACA,YAAA;AACA,cAAA;AACA,YAAA;AACA,YAAA;AACA,UAAA;AACA,YAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,YAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,YAAA;AACA,UAAA;AACA,YAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,YAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,YAAA;AACA,YAAA;AACA,cAAA;AACA,cAAA;AACA,gBAAA;AACA,cAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AACA,YAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA;AACA;AACA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA;AACA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAA;AACA,IAAA;AACA;AACA;AACA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"D:\\Project\\TraceBug-ai\\dist\\chunk-4SLN5LQD.cjs","sourcesContent":[null]} |
| "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } | ||
| var _chunk4SLN5LQDcjs = require('./chunk-4SLN5LQD.cjs'); | ||
| // src/scanner/helpers.ts | ||
| var _issueCounter = 0; | ||
| function makeIssueId(detector) { | ||
| _issueCounter += 1; | ||
| return `${detector}_${Date.now().toString(36)}_${_issueCounter}`; | ||
| } | ||
| function buildSelector(el) { | ||
| if (!el || el.nodeType !== 1) return ""; | ||
| if (el.id) return `#${cssEscape(el.id)}`; | ||
| const testId = el.getAttribute("data-testid") || el.getAttribute("data-test-id"); | ||
| if (testId) return `[data-testid="${cssEscape(testId)}"]`; | ||
| const parts = []; | ||
| let cur = el; | ||
| let depth = 0; | ||
| while (cur && cur.nodeType === 1 && cur.tagName !== "BODY" && depth < 5) { | ||
| const tag = cur.tagName.toLowerCase(); | ||
| const parent = cur.parentElement; | ||
| if (!parent) { | ||
| parts.unshift(tag); | ||
| break; | ||
| } | ||
| const siblings = Array.from(parent.children).filter((c) => c.tagName === cur.tagName); | ||
| if (siblings.length > 1) { | ||
| const idx = siblings.indexOf(cur) + 1; | ||
| parts.unshift(`${tag}:nth-of-type(${idx})`); | ||
| } else { | ||
| parts.unshift(tag); | ||
| } | ||
| cur = parent; | ||
| depth += 1; | ||
| } | ||
| return parts.join(" > "); | ||
| } | ||
| function coerceSeverity(impact) { | ||
| switch ((impact || "").toLowerCase()) { | ||
| case "critical": | ||
| return "critical"; | ||
| case "serious": | ||
| return "serious"; | ||
| case "moderate": | ||
| return "moderate"; | ||
| case "minor": | ||
| return "minor"; | ||
| default: | ||
| return "minor"; | ||
| } | ||
| } | ||
| function cssEscape(value) { | ||
| if (typeof CSS !== "undefined" && typeof CSS.escape === "function") { | ||
| return CSS.escape(value); | ||
| } | ||
| return value.replace(/[^\w-]/g, (ch) => `\\${ch}`); | ||
| } | ||
| // src/scanner/detectors/broken-images.ts | ||
| async function detectBrokenImages() { | ||
| const issues = []; | ||
| const imgs = Array.from(document.images); | ||
| for (const img of imgs) { | ||
| if (img.closest("#tracebug-root")) continue; | ||
| if (!img.complete) continue; | ||
| if (img.naturalWidth > 0) continue; | ||
| const src = img.currentSrc || img.src; | ||
| if (!src) continue; | ||
| issues.push({ | ||
| id: makeIssueId("broken-image"), | ||
| detector: "broken-image", | ||
| severity: "moderate", | ||
| title: `Broken image: ${truncateUrl(src)}`, | ||
| description: `<img> element failed to load. The browser tried to fetch \`${src}\` and got a network error or a non-image response. ${img.alt ? `Alt text: "${img.alt}"` : "No alt text \u2014 also fails accessibility."}`, | ||
| selector: buildSelector(img), | ||
| url: src, | ||
| page: window.location.pathname, | ||
| detectedAt: Date.now() | ||
| }); | ||
| } | ||
| return issues; | ||
| } | ||
| function truncateUrl(url) { | ||
| if (url.length <= 60) return url; | ||
| const tail = url.split("/").pop() || url.slice(-40); | ||
| return `\u2026/${tail}`; | ||
| } | ||
| // src/scanner/detectors/mixed-content.ts | ||
| var ATTR_TARGETS = [ | ||
| { tag: "img", attr: "src" }, | ||
| { tag: "script", attr: "src" }, | ||
| { tag: "iframe", attr: "src" }, | ||
| { tag: "link", attr: "href" }, | ||
| { tag: "audio", attr: "src" }, | ||
| { tag: "video", attr: "src" }, | ||
| { tag: "source", attr: "src" }, | ||
| { tag: "embed", attr: "src" }, | ||
| { tag: "object", attr: "data" } | ||
| ]; | ||
| async function detectMixedContent() { | ||
| if (typeof window === "undefined" || window.location.protocol !== "https:") { | ||
| return []; | ||
| } | ||
| const issues = []; | ||
| for (const { tag, attr } of ATTR_TARGETS) { | ||
| const elements = document.querySelectorAll(`${tag}[${attr}]`); | ||
| for (const el of Array.from(elements)) { | ||
| if (el.closest("#tracebug-root")) continue; | ||
| const value = el.getAttribute(attr) || ""; | ||
| if (!value.startsWith("http://")) continue; | ||
| if (tag === "link") { | ||
| const rel = (el.rel || "").toLowerCase(); | ||
| const fetchableRels = ["stylesheet", "preload", "prefetch", "manifest", "icon", "shortcut icon"]; | ||
| if (!fetchableRels.some((r) => rel.includes(r))) continue; | ||
| } | ||
| issues.push({ | ||
| id: makeIssueId("mixed-content"), | ||
| detector: "mixed-content", | ||
| severity: tag === "script" || tag === "iframe" ? "serious" : "moderate", | ||
| title: `Mixed content: ${tag} loads over HTTP`, | ||
| description: `<${tag}> on an HTTPS page references \`${value}\`. Browsers block or downgrade this \u2014 the resource usually fails to load and breaks the page's secure-context indicator.`, | ||
| selector: buildSelector(el), | ||
| url: value, | ||
| page: window.location.pathname, | ||
| detectedAt: Date.now() | ||
| }); | ||
| } | ||
| } | ||
| return issues; | ||
| } | ||
| // src/fingerprint.ts | ||
| async function computeFingerprint(errorMessage, errorStack, page) { | ||
| const errorType = extractErrorType(errorMessage); | ||
| const topFrames = extractTopFrames(errorStack || "", 3); | ||
| const input = `${errorType}|${topFrames.join("\n")}|${page}`; | ||
| if (typeof crypto !== "undefined" && crypto.subtle && typeof crypto.subtle.digest === "function") { | ||
| try { | ||
| const buf = new TextEncoder().encode(input); | ||
| const hash = await crypto.subtle.digest("SHA-1", buf); | ||
| return bufferToHex(hash).slice(0, 16); | ||
| } catch (e) { | ||
| } | ||
| } | ||
| return djb2(input).toString(16).padStart(8, "0"); | ||
| } | ||
| function extractErrorType(message) { | ||
| const m = message.match(/^([A-Z][a-zA-Z]+Error|Error)\b/); | ||
| return m ? m[1] : "Error"; | ||
| } | ||
| function extractTopFrames(stack, n) { | ||
| const frames = []; | ||
| const lines = stack.split("\n"); | ||
| for (const line of lines) { | ||
| const m = line.match(/(https?:\/\/[^):\s]+|[^():\s]+\.[a-z]+):(\d+):(\d+)/i); | ||
| if (m) { | ||
| const url = m[1]; | ||
| const path = url.includes("://") ? new URL(url, typeof window !== "undefined" ? window.location.origin : "http://localhost").pathname : url; | ||
| frames.push(`${path}:${m[2]}:${m[3]}`); | ||
| if (frames.length >= n) break; | ||
| } | ||
| } | ||
| return frames; | ||
| } | ||
| function bufferToHex(buf) { | ||
| const bytes = new Uint8Array(buf); | ||
| let out = ""; | ||
| for (let i = 0; i < bytes.length; i++) out += bytes[i].toString(16).padStart(2, "0"); | ||
| return out; | ||
| } | ||
| function djb2(str) { | ||
| let hash = 5381; | ||
| for (let i = 0; i < str.length; i++) hash = (hash << 5) + hash + str.charCodeAt(i) | 0; | ||
| return hash >>> 0; | ||
| } | ||
| // src/scanner/detectors/session-data.ts | ||
| var SLOW_API_MS = 2e3; | ||
| var MAX_CONTEXT_SAMPLES = 10; | ||
| async function detectConsoleErrors(session) { | ||
| var _a, _b, _c; | ||
| if (!session) return []; | ||
| const groups = /* @__PURE__ */ new Map(); | ||
| for (let i = 0; i < session.events.length; i++) { | ||
| const e = session.events[i]; | ||
| if (e.type !== "error" && e.type !== "unhandled_rejection" && e.type !== "console_error") continue; | ||
| const message = ((_a = e.data.error) == null ? void 0 : _a.message) || e.data.message || ""; | ||
| if (!message) continue; | ||
| const stack = ((_b = e.data.error) == null ? void 0 : _b.stack) || ""; | ||
| const page = e.page || (typeof window !== "undefined" ? window.location.pathname : ""); | ||
| const fp = await computeFingerprint(message, stack, page); | ||
| const precedingAction = describePrecedingAction(session.events, i); | ||
| const existing = groups.get(fp); | ||
| if (existing) { | ||
| existing.issue.occurrences = (existing.issue.occurrences || 1) + 1; | ||
| existing.issue.lastSeenAt = e.timestamp; | ||
| if (existing.samples.length < MAX_CONTEXT_SAMPLES) { | ||
| existing.samples.push({ timestamp: e.timestamp, precedingAction }); | ||
| } | ||
| continue; | ||
| } | ||
| const firstFrame = ((_c = stack.split("\n").find((l) => l.trim().startsWith("at "))) == null ? void 0 : _c.trim()) || ""; | ||
| const issue = { | ||
| id: makeIssueId("console-error"), | ||
| detector: "console-error", | ||
| severity: classifyErrorSeverity(message), | ||
| title: `JS error: ${message.slice(0, 70)}${message.length > 70 ? "\u2026" : ""}`, | ||
| description: firstFrame ? `${message} | ||
| First frame: ${firstFrame}` : message, | ||
| page, | ||
| detectedAt: e.timestamp, | ||
| fingerprint: fp, | ||
| occurrences: 1, | ||
| firstSeenAt: e.timestamp, | ||
| lastSeenAt: e.timestamp | ||
| }; | ||
| groups.set(fp, { issue, samples: [{ timestamp: e.timestamp, precedingAction }] }); | ||
| } | ||
| const out = []; | ||
| for (const g of groups.values()) { | ||
| const n = g.issue.occurrences || 1; | ||
| if (n > 1) { | ||
| g.issue.title = `${g.issue.title} [\xD7${n}]`; | ||
| g.issue.contextSamples = g.samples; | ||
| } | ||
| out.push(g.issue); | ||
| } | ||
| return out; | ||
| } | ||
| function describePrecedingAction(events, i) { | ||
| var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k; | ||
| for (let j = i - 1; j >= 0; j--) { | ||
| const e = events[j]; | ||
| if (e.type === "click") { | ||
| const t = ((_b = (_a = e.data) == null ? void 0 : _a.element) == null ? void 0 : _b.text) || ((_d = (_c = e.data) == null ? void 0 : _c.element) == null ? void 0 : _d.ariaLabel) || ((_f = (_e = e.data) == null ? void 0 : _e.element) == null ? void 0 : _f.tag) || "element"; | ||
| return `clicked "${String(t).slice(0, 40)}"`; | ||
| } | ||
| if (e.type === "input") { | ||
| const n = ((_h = (_g = e.data) == null ? void 0 : _g.element) == null ? void 0 : _h.name) || ((_j = (_i = e.data) == null ? void 0 : _i.element) == null ? void 0 : _j.id) || "field"; | ||
| return `typed in ${n}`; | ||
| } | ||
| if (e.type === "select_change") return "selected an option"; | ||
| if (e.type === "form_submit") return "submitted a form"; | ||
| if (e.type === "route_change") return `navigated to ${((_k = e.data) == null ? void 0 : _k.to) || "page"}`; | ||
| } | ||
| return void 0; | ||
| } | ||
| async function detectFailedRequests(session) { | ||
| if (!session) return []; | ||
| const issues = []; | ||
| const buffer = _chunk4SLN5LQDcjs.getNetworkFailures.call(void 0, ); | ||
| for (const e of session.events) { | ||
| if (e.type !== "api_request") continue; | ||
| const req = e.data.request; | ||
| if (!req) continue; | ||
| const status = req.statusCode || 0; | ||
| if (status >= 200 && status < 400) continue; | ||
| if (status === 0 && req.method === "HEAD") continue; | ||
| const match = buffer.find( | ||
| (b) => b.url === req.url && b.method === req.method && b.status === status && Math.abs(b.timestamp - e.timestamp) < 5e3 | ||
| ); | ||
| const snippet = (match == null ? void 0 : match.response) ? ` | ||
| Response: ${match.response.slice(0, 160)}` : ""; | ||
| issues.push({ | ||
| id: makeIssueId("failed-request"), | ||
| detector: "failed-request", | ||
| severity: status >= 500 ? "critical" : status === 0 ? "serious" : "moderate", | ||
| title: `${req.method} ${truncatePath(req.url)} \u2192 ${status === 0 ? "Network Error" : status}`, | ||
| description: `Request failed in ${req.durationMs || 0}ms.${snippet}`, | ||
| url: req.url, | ||
| page: e.page || window.location.pathname, | ||
| detectedAt: e.timestamp | ||
| }); | ||
| } | ||
| return issues; | ||
| } | ||
| async function detectSlowApis(session) { | ||
| if (!session) return []; | ||
| const issues = []; | ||
| for (const e of session.events) { | ||
| if (e.type !== "api_request") continue; | ||
| const req = e.data.request; | ||
| if (!req) continue; | ||
| const status = req.statusCode || 0; | ||
| if (status < 200 || status >= 400) continue; | ||
| const duration = req.durationMs || 0; | ||
| if (duration < SLOW_API_MS) continue; | ||
| issues.push({ | ||
| id: makeIssueId("slow-api"), | ||
| detector: "slow-api", | ||
| severity: duration > 5e3 ? "serious" : "moderate", | ||
| title: `Slow API: ${req.method} ${truncatePath(req.url)} (${duration}ms)`, | ||
| description: `This request took ${(duration / 1e3).toFixed(1)}s \u2014 over the ${SLOW_API_MS / 1e3}s threshold. Slow APIs are a common UX complaint and a leading cause of perceived bugs ("the page is frozen").`, | ||
| url: req.url, | ||
| page: e.page || window.location.pathname, | ||
| detectedAt: e.timestamp | ||
| }); | ||
| } | ||
| return issues; | ||
| } | ||
| function truncatePath(url) { | ||
| try { | ||
| const u = new URL(url, window.location.origin); | ||
| const p = u.pathname.length > 50 ? u.pathname.slice(0, 47) + "\u2026" : u.pathname; | ||
| return p; | ||
| } catch (e) { | ||
| return url.length > 50 ? url.slice(0, 47) + "\u2026" : url; | ||
| } | ||
| } | ||
| function classifyErrorSeverity(message) { | ||
| if (/TypeError|ReferenceError|SyntaxError/i.test(message)) return "critical"; | ||
| if (/Network|fetch|failed to/i.test(message)) return "serious"; | ||
| return "moderate"; | ||
| } | ||
| // src/scanner/detectors/a11y.ts | ||
| var _axePromise = null; | ||
| function loadAxe() { | ||
| if (_axePromise) return _axePromise; | ||
| _axePromise = Promise.resolve().then(() => _interopRequireWildcard(require("axe-core"))).then((mod) => mod.default || mod).catch((err) => { | ||
| console.warn("[TraceBug] axe-core failed to load:", err); | ||
| return null; | ||
| }); | ||
| return _axePromise; | ||
| } | ||
| async function detectA11yViolations() { | ||
| const axe = await loadAxe(); | ||
| if (!axe || typeof axe.run !== "function") return []; | ||
| let results; | ||
| try { | ||
| results = await axe.run(document, { | ||
| // Only WCAG-tagged rules — keeps signal-to-noise high. Best-practice | ||
| // rules add ~30% more noise without proportional value for QA. | ||
| runOnly: { type: "tag", values: ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"] }, | ||
| // Skip our own UI so QA isn't told their toolbar fails contrast checks. | ||
| // axe accepts a context with exclude — we pass { exclude: [...] } via | ||
| // the second-argument options-shaped form below to keep types loose. | ||
| resultTypes: ["violations"] | ||
| }); | ||
| } catch (err) { | ||
| console.warn("[TraceBug] axe.run failed:", err); | ||
| return []; | ||
| } | ||
| const issues = []; | ||
| const violations = (results == null ? void 0 : results.violations) || []; | ||
| for (const v of violations) { | ||
| const nodes = v.nodes || []; | ||
| const firstNode = nodes[0]; | ||
| const selector = Array.isArray(firstNode == null ? void 0 : firstNode.target) ? firstNode.target.join(" ") : ""; | ||
| const exampleSnippet = ((firstNode == null ? void 0 : firstNode.html) || "").slice(0, 120); | ||
| const moreSuffix = nodes.length > 1 ? ` (+ ${nodes.length - 1} more element${nodes.length === 2 ? "" : "s"})` : ""; | ||
| issues.push({ | ||
| id: makeIssueId("axe-a11y"), | ||
| detector: "axe-a11y", | ||
| severity: coerceSeverity(v.impact), | ||
| title: `${v.help || v.id}${moreSuffix}`, | ||
| description: `${v.description || v.id} | ||
| First element: \`${exampleSnippet}\``, | ||
| selector: selector || void 0, | ||
| helpUrl: v.helpUrl, | ||
| page: window.location.pathname, | ||
| detectedAt: Date.now() | ||
| }); | ||
| } | ||
| return issues; | ||
| } | ||
| // src/scanner/detectors/frustration.ts | ||
| var RAGE_WINDOW_MS = 1500; | ||
| var RAGE_MIN_CLICKS = 3; | ||
| var DEAD_RESPONSE_WINDOW_MS = 1500; | ||
| var ABANDON_WINDOW_MS = 6e4; | ||
| var ERROR_CORRELATION_WINDOW_MS = 2500; | ||
| async function detectFrustration(session) { | ||
| var _a; | ||
| if (!session) return []; | ||
| const events = session.events; | ||
| if (events.length === 0) return []; | ||
| const issues = []; | ||
| const page = ((_a = session.events[0]) == null ? void 0 : _a.page) || window.location.pathname; | ||
| issues.push(...detectRageClicks(events, page)); | ||
| issues.push(...detectDeadClicks(events, page)); | ||
| issues.push(...detectFormAbandonment(events, page)); | ||
| issues.push(...detectErrorCorrelated(events, page)); | ||
| return issues; | ||
| } | ||
| function detectRageClicks(events, page) { | ||
| var _a, _b, _c, _d, _e, _f, _g, _h; | ||
| const out = []; | ||
| const seenGroups = /* @__PURE__ */ new Set(); | ||
| for (let i = 0; i < events.length; i++) { | ||
| const e = events[i]; | ||
| if (e.type !== "click") continue; | ||
| const sel = ((_b = (_a = e.data) == null ? void 0 : _a.element) == null ? void 0 : _b.selector) || ((_d = (_c = e.data) == null ? void 0 : _c.element) == null ? void 0 : _d.testId) || ""; | ||
| if (!sel) continue; | ||
| const cluster = [e]; | ||
| let j = i + 1; | ||
| while (j < events.length) { | ||
| const next = events[j]; | ||
| if (next.timestamp - e.timestamp > RAGE_WINDOW_MS) break; | ||
| if (isResponseEvent(next)) break; | ||
| if (next.type === "click") { | ||
| const nextSel = ((_f = (_e = next.data) == null ? void 0 : _e.element) == null ? void 0 : _f.selector) || ((_h = (_g = next.data) == null ? void 0 : _g.element) == null ? void 0 : _h.testId) || ""; | ||
| if (nextSel === sel) cluster.push(next); | ||
| } | ||
| j++; | ||
| } | ||
| if (cluster.length >= RAGE_MIN_CLICKS) { | ||
| const key = `${sel}@${e.timestamp}`; | ||
| if (seenGroups.has(key)) continue; | ||
| seenGroups.add(key); | ||
| const label = clickLabel(cluster[0]); | ||
| out.push({ | ||
| id: makeIssueId("frustration-rage"), | ||
| detector: "frustration-rage", | ||
| severity: "serious", | ||
| title: `Rage clicks on ${label} (${cluster.length}\xD7 in ${Math.round(cluster[cluster.length - 1].timestamp - cluster[0].timestamp)}ms)`, | ||
| description: `User clicked the same element ${cluster.length} times within ${RAGE_WINDOW_MS}ms with no observable response (no API call, navigation, or DOM update). The element either doesn't respond to clicks or feels broken.`, | ||
| selector: sel, | ||
| page, | ||
| detectedAt: cluster[0].timestamp, | ||
| firstSeenAt: cluster[0].timestamp, | ||
| lastSeenAt: cluster[cluster.length - 1].timestamp, | ||
| occurrences: cluster.length | ||
| }); | ||
| i = j - 1; | ||
| } | ||
| } | ||
| return out; | ||
| } | ||
| function detectDeadClicks(events, page) { | ||
| var _a, _b; | ||
| const out = []; | ||
| const MAX = 5; | ||
| for (let i = 0; i < events.length && out.length < MAX; i++) { | ||
| const e = events[i]; | ||
| if (e.type !== "click") continue; | ||
| let responsive = false; | ||
| for (let j = i + 1; j < events.length; j++) { | ||
| const next = events[j]; | ||
| if (next.timestamp - e.timestamp > DEAD_RESPONSE_WINDOW_MS) break; | ||
| if (isResponseEvent(next)) { | ||
| responsive = true; | ||
| break; | ||
| } | ||
| } | ||
| if (responsive) continue; | ||
| const sel = ((_b = (_a = e.data) == null ? void 0 : _a.element) == null ? void 0 : _b.selector) || ""; | ||
| const label = clickLabel(e); | ||
| out.push({ | ||
| id: makeIssueId("frustration-dead"), | ||
| detector: "frustration-dead", | ||
| severity: "moderate", | ||
| title: `Dead click on ${label}`, | ||
| description: `Clicked but nothing happened within ${DEAD_RESPONSE_WINDOW_MS}ms (no API call, navigation, or DOM input). The element may have an unbound handler, a swallowed event, or be visually clickable but disabled.`, | ||
| selector: sel, | ||
| page: e.page || page, | ||
| detectedAt: e.timestamp | ||
| }); | ||
| } | ||
| return out; | ||
| } | ||
| function detectFormAbandonment(events, _page) { | ||
| var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j; | ||
| const out = []; | ||
| const formActivity = {}; | ||
| for (const e of events) { | ||
| if (e.type === "input") { | ||
| const formId = ((_b = (_a = e.data) == null ? void 0 : _a.element) == null ? void 0 : _b.formId) || ((_d = (_c = e.data) == null ? void 0 : _c.element) == null ? void 0 : _d.formAction) || "_default"; | ||
| if (!formActivity[formId]) { | ||
| formActivity[formId] = { firstInputAt: e.timestamp, fieldsSeen: /* @__PURE__ */ new Set(), lastInputAt: e.timestamp, page: e.page }; | ||
| } | ||
| const name = ((_f = (_e = e.data) == null ? void 0 : _e.element) == null ? void 0 : _f.name) || ((_h = (_g = e.data) == null ? void 0 : _g.element) == null ? void 0 : _h.id) || "field"; | ||
| formActivity[formId].fieldsSeen.add(name); | ||
| formActivity[formId].lastInputAt = e.timestamp; | ||
| } else if (e.type === "form_submit") { | ||
| const formId = ((_j = (_i = e.data) == null ? void 0 : _i.form) == null ? void 0 : _j.id) || "_default"; | ||
| delete formActivity[formId]; | ||
| } else if (e.type === "route_change") { | ||
| for (const formId of Object.keys(formActivity)) { | ||
| const a = formActivity[formId]; | ||
| if (e.timestamp - a.lastInputAt > ABANDON_WINDOW_MS) continue; | ||
| if (a.fieldsSeen.size === 0) continue; | ||
| out.push({ | ||
| id: makeIssueId("frustration-abandon"), | ||
| detector: "frustration-abandon", | ||
| severity: "moderate", | ||
| title: `Form abandoned on ${a.page} (${a.fieldsSeen.size} field${a.fieldsSeen.size === 1 ? "" : "s"} filled)`, | ||
| description: `User typed into ${a.fieldsSeen.size} field${a.fieldsSeen.size === 1 ? "" : "s"} (${Array.from(a.fieldsSeen).slice(0, 5).join(", ")}) and then navigated away without submitting. Likely a UX problem: the submit button is unclear, the form requires too much info, or it's failing silently.`, | ||
| page: a.page, | ||
| detectedAt: a.lastInputAt, | ||
| firstSeenAt: a.firstInputAt, | ||
| lastSeenAt: a.lastInputAt | ||
| }); | ||
| delete formActivity[formId]; | ||
| } | ||
| } | ||
| } | ||
| return out; | ||
| } | ||
| function detectErrorCorrelated(events, page) { | ||
| var _a, _b, _c, _d, _e, _f; | ||
| const out = []; | ||
| const seen = /* @__PURE__ */ new Set(); | ||
| for (let i = 0; i < events.length; i++) { | ||
| const e = events[i]; | ||
| const isError = e.type === "error" || e.type === "unhandled_rejection" || e.type === "console_error"; | ||
| if (!isError) continue; | ||
| let click = null; | ||
| for (let j = i - 1; j >= 0; j--) { | ||
| const prev = events[j]; | ||
| if (e.timestamp - prev.timestamp > ERROR_CORRELATION_WINDOW_MS) break; | ||
| if (prev.type === "click") { | ||
| click = prev; | ||
| break; | ||
| } | ||
| } | ||
| if (!click) continue; | ||
| const errMsg = ((_b = (_a = e.data) == null ? void 0 : _a.error) == null ? void 0 : _b.message) || ""; | ||
| if (!errMsg) continue; | ||
| const key = `${errMsg}::${((_d = (_c = click.data) == null ? void 0 : _c.element) == null ? void 0 : _d.selector) || ""}`; | ||
| if (seen.has(key)) continue; | ||
| seen.add(key); | ||
| const label = clickLabel(click); | ||
| const truncMsg = errMsg.length > 60 ? errMsg.slice(0, 57) + "\u2026" : errMsg; | ||
| const delta = Math.round(e.timestamp - click.timestamp); | ||
| out.push({ | ||
| id: makeIssueId("frustration-error-correlated"), | ||
| detector: "frustration-error-correlated", | ||
| severity: "critical", | ||
| title: `Click on ${label} triggered: ${truncMsg}`, | ||
| description: `An error fired ${delta}ms after the user clicked ${label}. This is almost certainly the offending interaction \u2014 the click handler threw, or its async path failed.`, | ||
| selector: (_f = (_e = click.data) == null ? void 0 : _e.element) == null ? void 0 : _f.selector, | ||
| page: e.page || page, | ||
| detectedAt: e.timestamp | ||
| }); | ||
| } | ||
| return out; | ||
| } | ||
| function isResponseEvent(e) { | ||
| return e.type === "api_request" || e.type === "route_change" || e.type === "input" || e.type === "form_submit" || e.type === "select_change"; | ||
| } | ||
| function clickLabel(e) { | ||
| var _a; | ||
| const el = (_a = e.data) == null ? void 0 : _a.element; | ||
| const raw = (el == null ? void 0 : el.text) || (el == null ? void 0 : el.ariaLabel) || (el == null ? void 0 : el.testId) || (el == null ? void 0 : el.id) || (el == null ? void 0 : el.tag) || "element"; | ||
| const trimmed = String(raw).replace(/\s+/g, " ").trim(); | ||
| return trimmed.length > 40 ? `"${trimmed.slice(0, 37)}\u2026"` : `"${trimmed}"`; | ||
| } | ||
| // src/scanner/index.ts | ||
| var _issues = []; | ||
| var _scanInFlight = null; | ||
| var _lastScanAt = 0; | ||
| var SEVERITY_ORDER = { | ||
| critical: 0, | ||
| serious: 1, | ||
| moderate: 2, | ||
| minor: 3 | ||
| }; | ||
| async function scan() { | ||
| if (_scanInFlight) { | ||
| const issues = await _scanInFlight; | ||
| return { issues, durationMs: 0, scannedAt: _lastScanAt }; | ||
| } | ||
| const startedAt = Date.now(); | ||
| const sessions = _chunk4SLN5LQDcjs.getAllSessions.call(void 0, ).sort((a, b) => b.updatedAt - a.updatedAt); | ||
| const session = sessions[0] || null; | ||
| const safeRun = (p) => p.catch((err) => { | ||
| console.warn("[TraceBug] Detector failed:", err); | ||
| return []; | ||
| }); | ||
| _scanInFlight = Promise.all([ | ||
| safeRun(detectBrokenImages()), | ||
| safeRun(detectMixedContent()), | ||
| safeRun(detectConsoleErrors(session)), | ||
| safeRun(detectFailedRequests(session)), | ||
| safeRun(detectSlowApis(session)), | ||
| safeRun(detectA11yViolations()), | ||
| safeRun(detectFrustration(session)) | ||
| ]).then((results) => { | ||
| const all = [].concat(...results); | ||
| all.sort((a, b) => { | ||
| const sev = SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity]; | ||
| if (sev !== 0) return sev; | ||
| return a.detectedAt - b.detectedAt; | ||
| }); | ||
| _issues = all; | ||
| return all; | ||
| }); | ||
| try { | ||
| const issues = await _scanInFlight; | ||
| _lastScanAt = Date.now(); | ||
| return { issues, durationMs: _lastScanAt - startedAt, scannedAt: _lastScanAt }; | ||
| } finally { | ||
| _scanInFlight = null; | ||
| } | ||
| } | ||
| function getIssues(options) { | ||
| var _a; | ||
| const includeDismissed = (_a = options == null ? void 0 : options.includeDismissed) != null ? _a : false; | ||
| return includeDismissed ? _issues.slice() : _issues.filter((i) => !i.dismissed); | ||
| } | ||
| function dismissIssue(id) { | ||
| const issue = _issues.find((i) => i.id === id); | ||
| if (!issue) return false; | ||
| issue.dismissed = true; | ||
| return true; | ||
| } | ||
| function undismissIssue(id) { | ||
| const issue = _issues.find((i) => i.id === id); | ||
| if (!issue) return false; | ||
| issue.dismissed = false; | ||
| return true; | ||
| } | ||
| function clearIssues() { | ||
| _issues = []; | ||
| _lastScanAt = 0; | ||
| } | ||
| function getIssueCountsByDetector() { | ||
| const counts = { | ||
| "axe-a11y": 0, | ||
| "broken-image": 0, | ||
| "mixed-content": 0, | ||
| "console-error": 0, | ||
| "slow-api": 0, | ||
| "failed-request": 0, | ||
| "frustration-rage": 0, | ||
| "frustration-dead": 0, | ||
| "frustration-abandon": 0, | ||
| "frustration-error-correlated": 0 | ||
| }; | ||
| for (const i of _issues) { | ||
| if (i.dismissed) continue; | ||
| counts[i.detector] = (counts[i.detector] || 0) + 1; | ||
| } | ||
| return counts; | ||
| } | ||
| function getIssueCountsBySeverity() { | ||
| const counts = { | ||
| critical: 0, | ||
| serious: 0, | ||
| moderate: 0, | ||
| minor: 0 | ||
| }; | ||
| for (const i of _issues) { | ||
| if (i.dismissed) continue; | ||
| counts[i.severity] += 1; | ||
| } | ||
| return counts; | ||
| } | ||
| function getIssueById(id) { | ||
| return _issues.find((i) => i.id === id) || null; | ||
| } | ||
| exports.scan = scan; exports.getIssues = getIssues; exports.dismissIssue = dismissIssue; exports.undismissIssue = undismissIssue; exports.clearIssues = clearIssues; exports.getIssueCountsByDetector = getIssueCountsByDetector; exports.getIssueCountsBySeverity = getIssueCountsBySeverity; exports.getIssueById = getIssueById; | ||
| //# sourceMappingURL=chunk-FQTNMBGV.cjs.map |
| {"version":3,"sources":["d:\\Project\\TraceBug-ai\\dist\\chunk-FQTNMBGV.cjs"],"names":[],"mappings":"AAAA;AACE;AACA;AACF,wDAA6B;AAC7B;AACA;AACA,IAAI,cAAc,EAAE,CAAC;AACrB,SAAS,WAAW,CAAC,QAAQ,EAAE;AAC/B,EAAE,cAAc,GAAG,CAAC;AACpB,EAAE,OAAO,CAAC,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA;AACA,aAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA;AACA,UAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA;AACA;AACA,MAAA;AACA;AACA;AACA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA;AACA,iBAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"D:\\Project\\TraceBug-ai\\dist\\chunk-FQTNMBGV.cjs","sourcesContent":[null]} |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
| // src/storage.ts | ||
| var SESSIONS_KEY = "tracebug_sessions"; | ||
| var ACTIVE_SESSION_KEY = "tracebug_active_session"; | ||
| var ACTIVE_CAPTURE_MODE_KEY = "tracebug_active_capture_mode"; | ||
| function generateSessionId() { | ||
| return typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : "bt_" + Date.now().toString(36) + Math.random().toString(36).slice(2, 10); | ||
| } | ||
| function getActiveSessionId() { | ||
| try { | ||
| return localStorage.getItem(ACTIVE_SESSION_KEY); | ||
| } catch (e) { | ||
| return null; | ||
| } | ||
| } | ||
| function setActiveSessionId(id) { | ||
| try { | ||
| localStorage.setItem(ACTIVE_SESSION_KEY, id); | ||
| } catch (e) { | ||
| } | ||
| } | ||
| function clearActiveSessionId() { | ||
| try { | ||
| localStorage.removeItem(ACTIVE_SESSION_KEY); | ||
| } catch (e) { | ||
| } | ||
| try { | ||
| localStorage.removeItem(ACTIVE_CAPTURE_MODE_KEY); | ||
| } catch (e) { | ||
| } | ||
| } | ||
| function getActiveCaptureMode() { | ||
| try { | ||
| const v = localStorage.getItem(ACTIVE_CAPTURE_MODE_KEY); | ||
| return v === "events" || v === "video" ? v : null; | ||
| } catch (e) { | ||
| return null; | ||
| } | ||
| } | ||
| function setActiveCaptureMode(mode) { | ||
| try { | ||
| localStorage.setItem(ACTIVE_CAPTURE_MODE_KEY, mode); | ||
| } catch (e) { | ||
| } | ||
| } | ||
| function getAllSessions() { | ||
| try { | ||
| const raw = localStorage.getItem(SESSIONS_KEY); | ||
| return raw ? JSON.parse(raw) : []; | ||
| } catch (e) { | ||
| return []; | ||
| } | ||
| } | ||
| function saveSessions(sessions) { | ||
| try { | ||
| localStorage.setItem(SESSIONS_KEY, JSON.stringify(sessions)); | ||
| return; | ||
| } catch (e) { | ||
| } | ||
| const commit = (next) => { | ||
| try { | ||
| localStorage.setItem(SESSIONS_KEY, JSON.stringify(next)); | ||
| sessions.length = 0; | ||
| sessions.push(...next); | ||
| return true; | ||
| } catch (e) { | ||
| return false; | ||
| } | ||
| }; | ||
| const working = sessions.slice(); | ||
| while (working.length > 1) { | ||
| working.shift(); | ||
| if (commit(working)) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] Storage full \u2014 dropped oldest session(s) to fit."); | ||
| return; | ||
| } | ||
| } | ||
| const last = working[0]; | ||
| if (last && Array.isArray(last.events)) { | ||
| while (last.events.length > 1) { | ||
| last.events = last.events.slice(Math.ceil(last.events.length / 2)); | ||
| if (commit(working)) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] Storage full \u2014 trimmed older events from the current session to fit."); | ||
| return; | ||
| } | ||
| } | ||
| } | ||
| if (typeof console !== "undefined") console.error("[TraceBug] Could not persist sessions: localStorage quota exceeded."); | ||
| } | ||
| var _cachedSessions = null; | ||
| var _pendingFlush = null; | ||
| var _dirty = false; | ||
| var FLUSH_INTERVAL_MS = 1e3; | ||
| function getCachedSessions() { | ||
| if (!_cachedSessions) { | ||
| _cachedSessions = getAllSessions(); | ||
| } | ||
| return _cachedSessions; | ||
| } | ||
| function scheduleFlush() { | ||
| _dirty = true; | ||
| if (_pendingFlush) return; | ||
| _pendingFlush = setTimeout(() => { | ||
| _pendingFlush = null; | ||
| if (_cachedSessions && _dirty) { | ||
| saveSessions(_cachedSessions); | ||
| _dirty = false; | ||
| } | ||
| }, FLUSH_INTERVAL_MS); | ||
| } | ||
| function flushPendingEvents() { | ||
| if (_pendingFlush) { | ||
| clearTimeout(_pendingFlush); | ||
| _pendingFlush = null; | ||
| } | ||
| if (_cachedSessions) { | ||
| saveSessions(_cachedSessions); | ||
| _dirty = false; | ||
| } | ||
| } | ||
| function invalidateCache() { | ||
| if (_pendingFlush) { | ||
| clearTimeout(_pendingFlush); | ||
| _pendingFlush = null; | ||
| } | ||
| _cachedSessions = null; | ||
| _dirty = false; | ||
| } | ||
| if (typeof window !== "undefined") { | ||
| window.addEventListener("beforeunload", flushPendingEvents); | ||
| window.addEventListener("pagehide", flushPendingEvents); | ||
| document.addEventListener("visibilitychange", () => { | ||
| if (document.visibilityState === "hidden") flushPendingEvents(); | ||
| }); | ||
| } | ||
| function appendEvent(sessionId, event, maxEvents, maxSessions) { | ||
| let sessions = getCachedSessions(); | ||
| let session = sessions.find((s) => s.sessionId === sessionId); | ||
| if (!session) { | ||
| session = { | ||
| sessionId, | ||
| projectId: event.projectId, | ||
| createdAt: Date.now(), | ||
| updatedAt: Date.now(), | ||
| errorMessage: null, | ||
| errorStack: null, | ||
| reproSteps: null, | ||
| errorSummary: null, | ||
| events: [], | ||
| annotations: [], | ||
| environment: null | ||
| }; | ||
| sessions.push(session); | ||
| } | ||
| session.events.push(event); | ||
| session.updatedAt = Date.now(); | ||
| if (session.events.length > maxEvents) { | ||
| session.events = session.events.slice(-maxEvents); | ||
| } | ||
| if (sessions.length > maxSessions) { | ||
| sessions = sessions.slice(-maxSessions); | ||
| _cachedSessions = sessions; | ||
| } | ||
| scheduleFlush(); | ||
| } | ||
| function updateSessionError(sessionId, errorMessage, errorStack, reproSteps, errorSummary) { | ||
| const sessions = getCachedSessions(); | ||
| const session = sessions.find((s) => s.sessionId === sessionId); | ||
| if (!session) return; | ||
| session.errorMessage = errorMessage; | ||
| session.errorStack = errorStack || null; | ||
| session.reproSteps = reproSteps; | ||
| session.errorSummary = errorSummary; | ||
| session.updatedAt = Date.now(); | ||
| scheduleFlush(); | ||
| } | ||
| function deleteSession(sessionId) { | ||
| flushPendingEvents(); | ||
| const remaining = getAllSessions().filter((s) => s.sessionId !== sessionId); | ||
| invalidateCache(); | ||
| saveSessions(remaining); | ||
| } | ||
| function addAnnotation(sessionId, annotation) { | ||
| const sessions = getCachedSessions(); | ||
| const session = sessions.find((s) => s.sessionId === sessionId); | ||
| if (!session) return; | ||
| if (!session.annotations) session.annotations = []; | ||
| session.annotations.push(annotation); | ||
| session.updatedAt = Date.now(); | ||
| scheduleFlush(); | ||
| } | ||
| function saveEnvironment(sessionId, env) { | ||
| const sessions = getCachedSessions(); | ||
| const session = sessions.find((s) => s.sessionId === sessionId); | ||
| if (!session) return; | ||
| session.environment = env; | ||
| scheduleFlush(); | ||
| } | ||
| function setSessionPriority(sessionId, priority) { | ||
| const sessions = getCachedSessions(); | ||
| const session = sessions.find((s) => s.sessionId === sessionId); | ||
| if (!session) return; | ||
| session.priority = priority; | ||
| session.updatedAt = Date.now(); | ||
| scheduleFlush(); | ||
| } | ||
| function markSessionSaved(sessionId) { | ||
| const sessions = getCachedSessions(); | ||
| const session = sessions.find((s) => s.sessionId === sessionId); | ||
| if (!session) return; | ||
| session.saved = true; | ||
| session.updatedAt = Date.now(); | ||
| flushPendingEvents(); | ||
| } | ||
| function clearAllSessions() { | ||
| invalidateCache(); | ||
| try { | ||
| localStorage.removeItem(SESSIONS_KEY); | ||
| } catch (e) { | ||
| } | ||
| } | ||
| // src/sanitize/custom-redaction.ts | ||
| var REDACTED = "[REDACTED]"; | ||
| var _fieldRe = null; | ||
| var _fieldTextRes = []; | ||
| var _patterns = []; | ||
| function escapeRe(s) { | ||
| return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); | ||
| } | ||
| function setRedactRules(rules) { | ||
| _fieldRe = null; | ||
| _fieldTextRes = []; | ||
| _patterns = []; | ||
| if (!rules) return; | ||
| const fields = (rules.fields || []).filter((f) => typeof f === "string" && f.trim().length > 0); | ||
| if (fields.length > 0) { | ||
| const alts = fields.map((f) => escapeRe(f.trim())).join("|"); | ||
| _fieldRe = new RegExp(alts, "i"); | ||
| for (const f of fields) { | ||
| const k = escapeRe(f.trim()); | ||
| _fieldTextRes.push({ | ||
| re: new RegExp(`("([^"]*${k}[^"]*)"\\s*:\\s*)("(?:[^"\\\\]|\\\\.)*"|-?\\d[\\d.eE+-]*|true|false)`, "gi"), | ||
| replace: `$1"${REDACTED}"` | ||
| }); | ||
| _fieldTextRes.push({ | ||
| re: new RegExp(`\\b([\\w.-]*${k}[\\w.-]*)=([^&\\s"']+)`, "gi"), | ||
| replace: `$1=${REDACTED}` | ||
| }); | ||
| } | ||
| } | ||
| for (const p of rules.patterns || []) { | ||
| try { | ||
| if (typeof p === "string") { | ||
| _patterns.push(new RegExp(p, "gi")); | ||
| } else if (p instanceof RegExp) { | ||
| _patterns.push(p.flags.includes("g") ? p : new RegExp(p.source, p.flags + "g")); | ||
| } | ||
| } catch (e) { | ||
| } | ||
| } | ||
| } | ||
| function isCustomSensitiveKey(key) { | ||
| if (!key || !_fieldRe) return false; | ||
| return _fieldRe.test(key); | ||
| } | ||
| function applyCustomRedaction(s) { | ||
| if (!s || _fieldTextRes.length === 0 && _patterns.length === 0) return s; | ||
| let out = s; | ||
| for (const { re, replace } of _fieldTextRes) out = out.replace(re, replace); | ||
| for (const re of _patterns) out = out.replace(re, REDACTED); | ||
| return out; | ||
| } | ||
| // src/sanitize/cloud-upload.ts | ||
| var REDACTED2 = "[REDACTED]"; | ||
| var TOKEN_PATTERNS = [ | ||
| // Bearer <token> in headers, console output, anywhere | ||
| { name: "bearer", re: /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}/gi, replace: () => "Bearer " + REDACTED2 }, | ||
| // JWT (3 base64url segments separated by dots, leading with eyJ which is `{"` in base64) | ||
| { name: "jwt", re: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g, replace: mask }, | ||
| // OpenAI / Stripe sk_* | ||
| { name: "sk_prefix", re: /\bsk-[A-Za-z0-9_-]{20,}\b/g, replace: mask }, | ||
| // Stripe secret + publishable (live/test, secret + publishable + restricted) | ||
| { name: "stripe", re: /\b(?:sk|pk|rk)_(?:live|test)_[A-Za-z0-9]{16,}\b/g, replace: mask }, | ||
| // GitHub PATs (classic + fine-grained + OAuth + server tokens) | ||
| { name: "github_pat", re: /\bghp_[A-Za-z0-9]{30,}\b/g, replace: mask }, | ||
| { name: "github_fine", re: /\bgithub_pat_[A-Za-z0-9_]{60,}\b/g, replace: mask }, | ||
| { name: "github_oauth", re: /\bgho_[A-Za-z0-9]{30,}\b/g, replace: mask }, | ||
| { name: "github_server", re: /\bghs_[A-Za-z0-9]{30,}\b/g, replace: mask }, | ||
| // AWS access keys (begins with AKIA, ASIA, AGPA, AIDA, etc.) + secret key (40-char base64) | ||
| { name: "aws_access", re: /\b(?:AKIA|ASIA|AGPA|AIDA|AROA|ANPA|ANVA)[A-Z0-9]{16}\b/g, replace: mask }, | ||
| { name: "aws_secret", re: /\b(?:aws.{0,20})?[A-Za-z0-9/+]{40}\b(?=.*aws|.*secret|.*key)/gi, replace: mask }, | ||
| // Slack — broader than before (xoxa-z covers all known prefixes) | ||
| { name: "slack", re: /\bxox[abeprs]-[A-Za-z0-9-]{10,}\b/g, replace: mask }, | ||
| // Google API keys | ||
| { name: "google_api", re: /\bAIza[A-Za-z0-9_-]{35}\b/g, replace: mask }, | ||
| // Twilio — Account SID + Auth tokens | ||
| { name: "twilio_sid", re: /\b(?:AC|SK)[a-f0-9]{32}\b/g, replace: mask }, | ||
| // SendGrid | ||
| { name: "sendgrid", re: /\bSG\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}\b/g, replace: mask }, | ||
| // Mailgun | ||
| { name: "mailgun", re: /\bkey-[a-f0-9]{32}\b/g, replace: mask }, | ||
| // Postmark | ||
| { name: "postmark", re: /\b[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\b(?=.{0,30}(postmark|server-token|api-token))/gi, replace: mask }, | ||
| // Linear / Vercel / Cloudflare / Discord | ||
| { name: "linear", re: /\blin_api_[A-Za-z0-9]{40,}\b/g, replace: mask }, | ||
| { name: "discord_bot", re: /\b[MN][A-Za-z\d]{23}\.[A-Za-z\d_-]{6}\.[A-Za-z\d_-]{27,}\b/g, replace: mask }, | ||
| // Generic high-entropy hex (≥32 chars). Catches webhook signing secrets, | ||
| // session IDs, etc. that don't carry a recognizable prefix. Conservative: | ||
| // only triggers when preceded by a common secret-y keyword to avoid | ||
| // mangling legitimate hex like git SHAs. | ||
| { name: "labeled_hex", re: /\b(?:secret|token|key|password|api[_-]?key|auth)["':\s=]{1,5}([a-fA-F0-9]{32,})\b/gi, replace: (s) => s.replace(/[a-fA-F0-9]{32,}/, REDACTED2) } | ||
| ]; | ||
| function mask(s) { | ||
| if (s.length <= 12) return REDACTED2; | ||
| return `${s.slice(0, 4)}\u2026${REDACTED2}\u2026${s.slice(-4)}`; | ||
| } | ||
| var SENSITIVE_QUERY_KEYS = /* @__PURE__ */ new Set([ | ||
| "token", | ||
| "access_token", | ||
| "id_token", | ||
| "refresh_token", | ||
| "api_key", | ||
| "apikey", | ||
| "secret", | ||
| "password", | ||
| "passwd", | ||
| "pwd", | ||
| "auth", | ||
| "authorization", | ||
| "x-api-key", | ||
| "session", | ||
| "sid", | ||
| "csrf" | ||
| ]); | ||
| function sanitizeUrl(url) { | ||
| if (typeof url !== "string" || url.length === 0) return url; | ||
| try { | ||
| const isAbs = /^[a-z][a-z0-9+.-]*:/i.test(url); | ||
| const u = new URL(isAbs ? url : `http://_placeholder_${url.startsWith("/") ? "" : "/"}${url}`); | ||
| let changed = false; | ||
| u.searchParams.forEach((_v, k) => { | ||
| if (SENSITIVE_QUERY_KEYS.has(k.toLowerCase()) || isCustomSensitiveKey(k)) { | ||
| u.searchParams.set(k, REDACTED2); | ||
| changed = true; | ||
| } | ||
| }); | ||
| if (!changed) return sanitizeText(url); | ||
| const out = isAbs ? u.toString() : u.pathname + u.search + u.hash; | ||
| return sanitizeText(out); | ||
| } catch (e) { | ||
| return sanitizeText(url); | ||
| } | ||
| } | ||
| function sanitizeText(s) { | ||
| if (s == null) return s; | ||
| let out = String(s); | ||
| for (const p of TOKEN_PATTERNS) out = out.replace(p.re, p.replace); | ||
| return applyCustomRedaction(out); | ||
| } | ||
| function sanitizeTokenShapes(s) { | ||
| return sanitizeText(s); | ||
| } | ||
| function sanitizeReportForUpload(report) { | ||
| var _a; | ||
| const out = typeof structuredClone === "function" ? structuredClone(report) : JSON.parse(JSON.stringify(report)); | ||
| if (out.consoleErrors) { | ||
| out.consoleErrors = out.consoleErrors.map((e) => { | ||
| var _a2; | ||
| return { | ||
| ...e, | ||
| message: sanitizeText(e.message), | ||
| stack: sanitizeText((_a2 = e.stack) != null ? _a2 : void 0) | ||
| }; | ||
| }); | ||
| } | ||
| if (out.consoleLogs) { | ||
| out.consoleLogs = out.consoleLogs.map((e) => { | ||
| var _a2; | ||
| return { | ||
| ...e, | ||
| message: sanitizeText(e.message), | ||
| stack: sanitizeText((_a2 = e.stack) != null ? _a2 : void 0) | ||
| }; | ||
| }); | ||
| } | ||
| if (out.networkErrors) { | ||
| out.networkErrors = out.networkErrors.map((e) => { | ||
| var _a2; | ||
| return { | ||
| ...e, | ||
| url: sanitizeUrl(e.url), | ||
| response: sanitizeText((_a2 = e.response) != null ? _a2 : void 0) | ||
| }; | ||
| }); | ||
| } | ||
| if (out.networkRequests) { | ||
| out.networkRequests = out.networkRequests.map((e) => { | ||
| var _a2; | ||
| return { | ||
| ...e, | ||
| url: sanitizeUrl(e.url), | ||
| response: sanitizeText((_a2 = e.response) != null ? _a2 : void 0) | ||
| }; | ||
| }); | ||
| } | ||
| if (out.steps) out.steps = sanitizeText(out.steps); | ||
| if (out.summary) out.summary = sanitizeText(out.summary); | ||
| if (out.title) out.title = sanitizeText(out.title); | ||
| if (Array.isArray(out.sessionSteps)) out.sessionSteps = out.sessionSteps.map((s) => sanitizeText(s)); | ||
| if (out.actionChips) { | ||
| out.actionChips = out.actionChips.map((c) => { | ||
| var _a2, _b; | ||
| return { | ||
| ...c, | ||
| target: sanitizeText((_a2 = c.target) != null ? _a2 : void 0), | ||
| detail: sanitizeText((_b = c.detail) != null ? _b : void 0) | ||
| }; | ||
| }); | ||
| } | ||
| if (out.storage) { | ||
| const scrub = (entries) => entries.map((e) => ({ ...e, value: sanitizeText(e.value) })); | ||
| out.storage.local = scrub(out.storage.local || []); | ||
| out.storage.session = scrub(out.storage.session || []); | ||
| if (out.storage.cookies) out.storage.cookies = scrub(out.storage.cookies); | ||
| } | ||
| if ((_a = out.environment) == null ? void 0 : _a.url) out.environment.url = sanitizeUrl(out.environment.url); | ||
| if (out.context && typeof out.context === "object") { | ||
| const ctx = {}; | ||
| for (const [k, v] of Object.entries(out.context)) { | ||
| ctx[k] = typeof v === "string" ? sanitizeText(v) : v; | ||
| } | ||
| out.context = ctx; | ||
| } | ||
| return out; | ||
| } | ||
| // src/collectors.ts | ||
| var ROOT_ID = "tracebug-root"; | ||
| var PANEL_ID = "tracebug-dashboard-panel"; | ||
| var BTN_ID = "tracebug-dashboard-btn"; | ||
| var NETWORK_FAILURE_LIMIT = 10; | ||
| var RESPONSE_SNIPPET_CHARS = 200; | ||
| var _networkFailures = []; | ||
| function pushNetworkFailure(failure) { | ||
| try { | ||
| if (failure.response) failure.response = sanitizeTokenShapes(failure.response); | ||
| _networkFailures.push(failure); | ||
| if (_networkFailures.length > NETWORK_FAILURE_LIMIT) { | ||
| _networkFailures.splice(0, _networkFailures.length - NETWORK_FAILURE_LIMIT); | ||
| } | ||
| } catch (e) { | ||
| } | ||
| } | ||
| function getNetworkFailures() { | ||
| return _networkFailures.slice(); | ||
| } | ||
| function clearNetworkFailures() { | ||
| _networkFailures.length = 0; | ||
| } | ||
| var SENSITIVE_PARAM_RE = /token|key|secret|auth|password|sig|signature/i; | ||
| function sanitizeUrl2(url) { | ||
| if (!url) return url; | ||
| try { | ||
| const qIdx = url.indexOf("?"); | ||
| if (qIdx === -1) return url; | ||
| const base = url.slice(0, qIdx); | ||
| const afterQ = url.slice(qIdx + 1); | ||
| const hashIdx = afterQ.indexOf("#"); | ||
| const query = hashIdx === -1 ? afterQ : afterQ.slice(0, hashIdx); | ||
| const hash = hashIdx === -1 ? "" : afterQ.slice(hashIdx); | ||
| const redacted = query.split("&").map((part) => { | ||
| const eqIdx = part.indexOf("="); | ||
| if (eqIdx === -1) return part; | ||
| const key = part.slice(0, eqIdx); | ||
| if (SENSITIVE_PARAM_RE.test(key) || isCustomSensitiveKey(key)) return `${key}=[REDACTED]`; | ||
| return part; | ||
| }).join("&"); | ||
| return `${base}?${redacted}${hash}`; | ||
| } catch (e) { | ||
| return url; | ||
| } | ||
| } | ||
| var MAX_BODY_BYTES = 10 * 1024; | ||
| var BINARY_CONTENT_TYPE_RE = /^(image|video|audio)\/|^application\/(octet-stream|pdf|zip|x-protobuf|x-msgpack|wasm|vnd\.)/i; | ||
| async function readResponseBodySafe(response) { | ||
| try { | ||
| const ct = response.headers.get("content-type") || ""; | ||
| if (BINARY_CONTENT_TYPE_RE.test(ct)) return ""; | ||
| if (!response.body || typeof response.body.getReader !== "function") { | ||
| try { | ||
| const text = await response.text(); | ||
| return typeof text === "string" ? text.slice(0, RESPONSE_SNIPPET_CHARS) : ""; | ||
| } catch (e) { | ||
| return ""; | ||
| } | ||
| } | ||
| const reader = response.body.getReader(); | ||
| const decoder = new TextDecoder("utf-8", { fatal: false }); | ||
| let collected = ""; | ||
| let bytesRead = 0; | ||
| while (bytesRead < MAX_BODY_BYTES && collected.length < RESPONSE_SNIPPET_CHARS) { | ||
| const { value, done } = await reader.read(); | ||
| if (done) break; | ||
| if (value) { | ||
| bytesRead += value.byteLength; | ||
| collected += decoder.decode(value, { stream: true }); | ||
| } | ||
| } | ||
| try { | ||
| await reader.cancel(); | ||
| } catch (e) { | ||
| } | ||
| return collected.slice(0, RESPONSE_SNIPPET_CHARS); | ||
| } catch (e) { | ||
| return ""; | ||
| } | ||
| } | ||
| var INTERNAL_URL_PATTERNS = [ | ||
| /__nextjs_original-stack-frame/, | ||
| /\/_next\/static\/webpack/, | ||
| /\/__webpack_hmr/, | ||
| /\.hot-update\./, | ||
| /\/sockjs-node\//, | ||
| /\/turbopack-hmr\//, | ||
| /\/_next\/webpack-hmr/, | ||
| /\/webpack-dev-server\//, | ||
| /\/__vite_ping/, | ||
| /\/@vite\/client/, | ||
| /\/@react-refresh/ | ||
| ]; | ||
| function isInternalUrl(url) { | ||
| return INTERNAL_URL_PATTERNS.some((pattern) => pattern.test(url)); | ||
| } | ||
| var _rootCache; | ||
| function getRoot() { | ||
| if (_rootCache === void 0) { | ||
| _rootCache = document.getElementById(ROOT_ID); | ||
| } | ||
| if (_rootCache && !_rootCache.isConnected) { | ||
| _rootCache = document.getElementById(ROOT_ID); | ||
| } | ||
| return _rootCache; | ||
| } | ||
| function isTraceBugElement(el) { | ||
| if (!el) return false; | ||
| if (el.id === ROOT_ID || el.id === BTN_ID || el.id === PANEL_ID) return true; | ||
| if (el.dataset && el.dataset.tracebug) return true; | ||
| const root = getRoot(); | ||
| if (root && root.contains(el)) return true; | ||
| let node = el; | ||
| while (node) { | ||
| const id = node.id || ""; | ||
| if (id.startsWith("tracebug-") || id.startsWith("bt-")) return true; | ||
| const cn = typeof node.className === "string" ? node.className : ""; | ||
| if (cn.includes("tracebug-") || cn.includes("bt-ann") || cn.includes("bt-voice")) return true; | ||
| if (node.dataset && node.dataset.tracebug) return true; | ||
| node = node.parentElement; | ||
| } | ||
| return false; | ||
| } | ||
| function collectClicks(emit) { | ||
| const handler = (e) => { | ||
| try { | ||
| const t = e.target; | ||
| if (!t || isTraceBugElement(t)) return; | ||
| const tag = t.tagName.toLowerCase(); | ||
| const el = { | ||
| tag, | ||
| text: (t.innerText || "").slice(0, 120), | ||
| id: t.id || "", | ||
| className: typeof t.className === "string" ? t.className : "" | ||
| }; | ||
| const data = { element: el }; | ||
| if (tag === "a") el.href = t.href || ""; | ||
| if (tag === "button" || t.type === "submit") { | ||
| el.buttonType = t.type || "button"; | ||
| el.disabled = t.disabled; | ||
| } | ||
| if (tag === "label") el.forField = t.htmlFor || ""; | ||
| const ariaLabel = t.getAttribute("aria-label"); | ||
| if (ariaLabel) el.ariaLabel = ariaLabel; | ||
| const role = t.getAttribute("role"); | ||
| if (role) el.role = role; | ||
| const testId = t.getAttribute("data-testid"); | ||
| if (testId) el.testId = testId; | ||
| const form = t.closest("form"); | ||
| if (form) { | ||
| el.formId = form.id || ""; | ||
| el.formAction = form.action || ""; | ||
| } | ||
| try { | ||
| el.selector = buildSelector(t); | ||
| } catch (e2) { | ||
| } | ||
| try { | ||
| const r = t.getBoundingClientRect(); | ||
| el.boundingBox = { x: Math.round(r.left), y: Math.round(r.top), width: Math.round(r.width), height: Math.round(r.height) }; | ||
| } catch (e2) { | ||
| } | ||
| emit("click", data); | ||
| } catch (err) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] Click capture error:", err); | ||
| } | ||
| }; | ||
| document.addEventListener("click", handler, { capture: true }); | ||
| return () => document.removeEventListener("click", handler, { capture: true }); | ||
| } | ||
| function buildSelector(el) { | ||
| if (!el) return ""; | ||
| if (el.id) return `#${CSS.escape(el.id)}`; | ||
| const testId = el.getAttribute("data-testid"); | ||
| if (testId) return `[data-testid="${testId}"]`; | ||
| const parts = []; | ||
| let node = el; | ||
| let depth = 0; | ||
| while (node && node !== document.body && depth < 4) { | ||
| let part = node.tagName.toLowerCase(); | ||
| if (node.id) { | ||
| parts.unshift(`#${CSS.escape(node.id)}`); | ||
| break; | ||
| } | ||
| const cls = typeof node.className === "string" ? node.className.trim().split(/\s+/).filter(Boolean)[0] : ""; | ||
| if (cls) part += `.${CSS.escape(cls)}`; | ||
| const parent = node.parentElement; | ||
| const currentTag = node.tagName; | ||
| const currentNode = node; | ||
| if (parent) { | ||
| const sameTag = Array.from(parent.children).filter((c) => c.tagName === currentTag); | ||
| if (sameTag.length > 1) part += `:nth-of-type(${sameTag.indexOf(currentNode) + 1})`; | ||
| } | ||
| parts.unshift(part); | ||
| node = parent; | ||
| depth++; | ||
| } | ||
| return parts.join(" > "); | ||
| } | ||
| function collectInputs(emit) { | ||
| const timers = /* @__PURE__ */ new Map(); | ||
| const handler = (e) => { | ||
| try { | ||
| const t = e.target; | ||
| if (!t || !("value" in t) || isTraceBugElement(t)) return; | ||
| if (t.tagName.toLowerCase() === "select") return; | ||
| const prev = timers.get(t); | ||
| if (prev) clearTimeout(prev); | ||
| timers.set( | ||
| t, | ||
| setTimeout(() => { | ||
| try { | ||
| const tag = t.tagName.toLowerCase(); | ||
| const inputType = t.type || ""; | ||
| const isSensitive = ["password", "credit-card", "ssn"].includes(inputType) || /password|secret|token|ssn|credit/i.test(t.name || t.id || "") || isCustomSensitiveKey(t.name || t.id); | ||
| const element = { | ||
| tag, | ||
| name: t.name || t.id || "", | ||
| type: inputType, | ||
| valueLength: (t.value || "").length, | ||
| value: isSensitive ? "[REDACTED]" : (t.value || "").slice(0, 200), | ||
| placeholder: t.placeholder || "" | ||
| }; | ||
| const data = { element }; | ||
| if (inputType === "checkbox" || inputType === "radio") { | ||
| element.checked = t.checked; | ||
| element.value = t.checked ? "checked" : "unchecked"; | ||
| } | ||
| if (inputType === "number" || inputType === "range") { | ||
| element.value = t.value; | ||
| } | ||
| emit("input", data); | ||
| } catch (err) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] Input capture error:", err); | ||
| } | ||
| timers.delete(t); | ||
| }, 300) | ||
| ); | ||
| } catch (err) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] Input capture error:", err); | ||
| } | ||
| }; | ||
| document.addEventListener("input", handler, { capture: true }); | ||
| return () => { | ||
| document.removeEventListener("input", handler, { capture: true }); | ||
| timers.forEach((t) => clearTimeout(t)); | ||
| }; | ||
| } | ||
| function collectSelectChanges(emit) { | ||
| const handler = (e) => { | ||
| try { | ||
| const t = e.target; | ||
| if (!t || t.tagName.toLowerCase() !== "select" || isTraceBugElement(t)) return; | ||
| const selectedOption = t.options[t.selectedIndex]; | ||
| emit("select_change", { | ||
| element: { | ||
| tag: "select", | ||
| name: t.name || t.id || "", | ||
| value: t.value, | ||
| selectedText: selectedOption ? selectedOption.text : "", | ||
| selectedIndex: t.selectedIndex, | ||
| optionCount: t.options.length, | ||
| allOptions: Array.from(t.options).map((o) => o.text).slice(0, 20) | ||
| } | ||
| }); | ||
| } catch (err) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] Select capture error:", err); | ||
| } | ||
| }; | ||
| document.addEventListener("change", handler, { capture: true }); | ||
| return () => document.removeEventListener("change", handler, { capture: true }); | ||
| } | ||
| function collectFormSubmits(emit) { | ||
| const handler = (e) => { | ||
| try { | ||
| const form = e.target; | ||
| if (!form || form.tagName.toLowerCase() !== "form" || isTraceBugElement(form)) return; | ||
| const formData = {}; | ||
| const elements = form.elements; | ||
| for (let i = 0; i < elements.length; i++) { | ||
| const el = elements[i]; | ||
| if (!el.name) continue; | ||
| const isSensitive = ["password"].includes(el.type) || /password|secret|token|ssn|credit/i.test(el.name) || isCustomSensitiveKey(el.name); | ||
| if (el.type === "submit" || el.type === "button") continue; | ||
| formData[el.name] = isSensitive ? "[REDACTED]" : (el.value || "").slice(0, 200); | ||
| } | ||
| emit("form_submit", { | ||
| form: { id: form.id || "", action: form.action || "", method: form.method || "GET", fieldCount: elements.length, fields: formData } | ||
| }); | ||
| } catch (err) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] Form capture error:", err); | ||
| } | ||
| }; | ||
| document.addEventListener("submit", handler, { capture: true }); | ||
| return () => document.removeEventListener("submit", handler, { capture: true }); | ||
| } | ||
| function collectRouteChanges(emit) { | ||
| let lastPath = window.location.pathname; | ||
| const check = () => { | ||
| const current = window.location.pathname; | ||
| if (current !== lastPath) { | ||
| const from = lastPath; | ||
| lastPath = current; | ||
| emit("route_change", { from, to: current }); | ||
| } | ||
| }; | ||
| window.addEventListener("popstate", check); | ||
| const origPush = history.pushState.bind(history); | ||
| const origReplace = history.replaceState.bind(history); | ||
| history.pushState = function(...args) { | ||
| origPush(...args); | ||
| check(); | ||
| }; | ||
| history.replaceState = function(...args) { | ||
| origReplace(...args); | ||
| check(); | ||
| }; | ||
| return () => { | ||
| window.removeEventListener("popstate", check); | ||
| history.pushState = origPush; | ||
| history.replaceState = origReplace; | ||
| }; | ||
| } | ||
| function collectApiRequests(emit) { | ||
| const originalFetch = window.fetch; | ||
| window.fetch = async function(input, init) { | ||
| var _a; | ||
| let url = ""; | ||
| let method = "GET"; | ||
| try { | ||
| if (typeof input === "string") { | ||
| url = input; | ||
| } else if (input instanceof URL) { | ||
| url = input.href; | ||
| } else if (input && typeof input === "object" && "url" in input) { | ||
| url = input.url; | ||
| method = input.method || "GET"; | ||
| } | ||
| if (init == null ? void 0 : init.method) method = init.method; | ||
| } catch (e) { | ||
| } | ||
| const start = Date.now(); | ||
| try { | ||
| if (url && isInternalUrl(url)) return originalFetch.call(window, input, init); | ||
| } catch (e) { | ||
| } | ||
| const safeUrl = sanitizeUrl2(url).slice(0, 500); | ||
| try { | ||
| const response = await originalFetch.call(window, input, init); | ||
| try { | ||
| emit("api_request", { | ||
| request: { url: safeUrl, method: method.toUpperCase(), statusCode: response.status, durationMs: Date.now() - start } | ||
| }); | ||
| } catch (e) { | ||
| } | ||
| try { | ||
| if (response.status >= 400 || response.status === 0) { | ||
| const clone = response.clone(); | ||
| readResponseBodySafe(clone).then((snippet) => { | ||
| pushNetworkFailure({ | ||
| url: safeUrl, | ||
| method: method.toUpperCase(), | ||
| status: response.status, | ||
| response: snippet, | ||
| timestamp: Date.now() | ||
| }); | ||
| }).catch(() => { | ||
| pushNetworkFailure({ | ||
| url: safeUrl, | ||
| method: method.toUpperCase(), | ||
| status: response.status, | ||
| response: "", | ||
| timestamp: Date.now() | ||
| }); | ||
| }); | ||
| } | ||
| } catch (e) { | ||
| } | ||
| return response; | ||
| } catch (err) { | ||
| try { | ||
| emit("api_request", { | ||
| request: { url: safeUrl, method: method.toUpperCase(), statusCode: 0, durationMs: Date.now() - start } | ||
| }); | ||
| } catch (e) { | ||
| } | ||
| try { | ||
| pushNetworkFailure({ | ||
| url: safeUrl, | ||
| method: method.toUpperCase(), | ||
| status: 0, | ||
| response: ((_a = err == null ? void 0 : err.message) == null ? void 0 : _a.slice(0, RESPONSE_SNIPPET_CHARS)) || "", | ||
| timestamp: Date.now() | ||
| }); | ||
| } catch (e) { | ||
| } | ||
| throw err; | ||
| } | ||
| }; | ||
| return () => { | ||
| window.fetch = originalFetch; | ||
| }; | ||
| } | ||
| function collectXhrRequests(emit) { | ||
| const OrigXHR = window.XMLHttpRequest; | ||
| const origOpen = OrigXHR.prototype.open; | ||
| const origSend = OrigXHR.prototype.send; | ||
| const xhrMeta = /* @__PURE__ */ new WeakMap(); | ||
| OrigXHR.prototype.open = function(method, url, ...rest) { | ||
| try { | ||
| xhrMeta.set(this, { method, url: typeof url === "string" ? url : url.toString() }); | ||
| } catch (e) { | ||
| } | ||
| return origOpen.apply(this, [method, url, ...rest]); | ||
| }; | ||
| OrigXHR.prototype.send = function(body) { | ||
| try { | ||
| const xhr = this; | ||
| const start = Date.now(); | ||
| const meta = xhrMeta.get(xhr); | ||
| const method = (meta == null ? void 0 : meta.method) || "GET"; | ||
| const url = (meta == null ? void 0 : meta.url) || ""; | ||
| if (isInternalUrl(url)) return origSend.call(this, body); | ||
| const safeUrl = sanitizeUrl2(url).slice(0, 500); | ||
| xhr.addEventListener("loadend", function() { | ||
| try { | ||
| emit("api_request", { request: { url: safeUrl, method: method.toUpperCase(), statusCode: xhr.status, durationMs: Date.now() - start } }); | ||
| } catch (e) { | ||
| } | ||
| try { | ||
| if (xhr.status >= 400 || xhr.status === 0) { | ||
| let body2 = ""; | ||
| try { | ||
| const ct = xhr.getResponseHeader && xhr.getResponseHeader("content-type") || ""; | ||
| if (!BINARY_CONTENT_TYPE_RE.test(ct)) { | ||
| body2 = typeof xhr.responseText === "string" ? xhr.responseText : ""; | ||
| } | ||
| } catch (e) { | ||
| } | ||
| pushNetworkFailure({ | ||
| url: safeUrl, | ||
| method: method.toUpperCase(), | ||
| status: xhr.status, | ||
| response: body2.slice(0, RESPONSE_SNIPPET_CHARS), | ||
| timestamp: Date.now() | ||
| }); | ||
| } | ||
| } catch (e) { | ||
| } | ||
| }); | ||
| xhr.addEventListener("error", function() { | ||
| try { | ||
| emit("api_request", { request: { url: safeUrl, method: method.toUpperCase(), statusCode: 0, durationMs: Date.now() - start } }); | ||
| } catch (e) { | ||
| } | ||
| try { | ||
| pushNetworkFailure({ | ||
| url: safeUrl, | ||
| method: method.toUpperCase(), | ||
| status: 0, | ||
| response: "", | ||
| timestamp: Date.now() | ||
| }); | ||
| } catch (e) { | ||
| } | ||
| }); | ||
| } catch (err) { | ||
| if (typeof console !== "undefined") console.warn("[TraceBug] XHR capture error:", err); | ||
| } | ||
| return origSend.call(this, body); | ||
| }; | ||
| return () => { | ||
| OrigXHR.prototype.open = origOpen; | ||
| OrigXHR.prototype.send = origSend; | ||
| }; | ||
| } | ||
| var _perfSeen = /* @__PURE__ */ new Set(); | ||
| function _emitPerfEntry(emit, e) { | ||
| if (!e || !e.name) return; | ||
| try { | ||
| if (isInternalUrl(e.name)) return; | ||
| } catch (e2) { | ||
| } | ||
| if (typeof e.name === "string" && e.name.indexOf("tracebug") !== -1) return; | ||
| const key = `${e.name}|${Math.round(e.startTime)}`; | ||
| if (_perfSeen.has(key)) return; | ||
| _perfSeen.add(key); | ||
| const navStart = typeof performance.timeOrigin === "number" ? performance.timeOrigin : Date.now(); | ||
| const ext = e; | ||
| const initiator = e.initiatorType || ""; | ||
| const method = (ext.method || "GET").toUpperCase(); | ||
| const status = ext.responseStatus || 0; | ||
| const url = sanitizeUrl2(e.name).slice(0, 500); | ||
| const timestamp = Math.round(navStart + e.startTime); | ||
| const durationMs = Math.round(e.duration || 0); | ||
| try { | ||
| emit("api_request", { | ||
| request: { url, method, statusCode: status, durationMs, initiatorType: initiator }, | ||
| _ts: timestamp | ||
| }); | ||
| } catch (e2) { | ||
| } | ||
| } | ||
| function drainPerformanceNetwork(emit) { | ||
| if (typeof performance === "undefined") return; | ||
| try { | ||
| const entries = performance.getEntriesByType("resource"); | ||
| for (const e of entries) _emitPerfEntry(emit, e); | ||
| } catch (e) { | ||
| } | ||
| } | ||
| function collectPerformanceNetwork(emit) { | ||
| if (typeof performance === "undefined" || typeof PerformanceObserver === "undefined") { | ||
| return () => { | ||
| }; | ||
| } | ||
| drainPerformanceNetwork(emit); | ||
| let observer = null; | ||
| try { | ||
| observer = new PerformanceObserver((list) => { | ||
| for (const e of list.getEntries()) { | ||
| _emitPerfEntry(emit, e); | ||
| } | ||
| }); | ||
| observer.observe({ type: "resource", buffered: false }); | ||
| } catch (e) { | ||
| } | ||
| return () => { | ||
| try { | ||
| observer == null ? void 0 : observer.disconnect(); | ||
| } catch (e) { | ||
| } | ||
| }; | ||
| } | ||
| function collectErrors(emit) { | ||
| const prevOnError = window.onerror; | ||
| window.onerror = (msg, source, line, col, error) => { | ||
| try { | ||
| emit("error", { | ||
| error: { | ||
| message: sanitizeTokenShapes(typeof msg === "string" ? msg : "Unknown error"), | ||
| stack: (error == null ? void 0 : error.stack) && sanitizeTokenShapes(error.stack), | ||
| source, | ||
| line, | ||
| column: col | ||
| } | ||
| }); | ||
| } catch (e) { | ||
| } | ||
| if (prevOnError) { | ||
| try { | ||
| prevOnError(msg, source, line, col, error); | ||
| } catch (e) { | ||
| } | ||
| } | ||
| }; | ||
| const onRejection = (e) => { | ||
| var _a, _b; | ||
| try { | ||
| emit("unhandled_rejection", { | ||
| error: { | ||
| message: sanitizeTokenShapes(((_a = e.reason) == null ? void 0 : _a.message) || String(e.reason)), | ||
| stack: ((_b = e.reason) == null ? void 0 : _b.stack) && sanitizeTokenShapes(e.reason.stack) | ||
| } | ||
| }); | ||
| } catch (e2) { | ||
| } | ||
| }; | ||
| window.addEventListener("unhandledrejection", onRejection); | ||
| return () => { | ||
| window.onerror = prevOnError; | ||
| window.removeEventListener("unhandledrejection", onRejection); | ||
| }; | ||
| } | ||
| function collectConsoleErrors(emit) { | ||
| const origConsoleError = console.error; | ||
| let _insideEmit = false; | ||
| console.error = function(...args) { | ||
| if (_insideEmit) { | ||
| origConsoleError.apply(console, args); | ||
| return; | ||
| } | ||
| _insideEmit = true; | ||
| try { | ||
| emit("console_error", { | ||
| // Token-shape scrub at capture — a token logged to the console must | ||
| // never reach the offline .html export unmasked (the cloud sanitizer | ||
| // only covers the upload path). | ||
| error: { message: sanitizeTokenShapes(args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")) } | ||
| }); | ||
| } catch (e) { | ||
| } finally { | ||
| _insideEmit = false; | ||
| } | ||
| origConsoleError.apply(console, args); | ||
| }; | ||
| return () => { | ||
| console.error = origConsoleError; | ||
| }; | ||
| } | ||
| var CONSOLE_LEVEL_CAP = 50; | ||
| function wrapConsoleLevel(method, type, emit) { | ||
| const orig = console[method]; | ||
| let _inside = false; | ||
| let _count = 0; | ||
| console[method] = function(...args) { | ||
| const own = typeof args[0] === "string" && args[0].startsWith("[TraceBug]"); | ||
| if (_inside || own || _count >= CONSOLE_LEVEL_CAP) { | ||
| orig.apply(console, args); | ||
| return; | ||
| } | ||
| _inside = true; | ||
| _count++; | ||
| try { | ||
| emit(type, { | ||
| // Same capture-time token scrub as console_error — the offline | ||
| // export path never runs the cloud sanitizer. | ||
| error: { message: sanitizeTokenShapes(args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")) } | ||
| }); | ||
| } catch (e) { | ||
| } finally { | ||
| _inside = false; | ||
| } | ||
| orig.apply(console, args); | ||
| }; | ||
| return () => { | ||
| console[method] = orig; | ||
| }; | ||
| } | ||
| function collectConsoleWarnings(emit) { | ||
| return wrapConsoleLevel("warn", "console_warn", emit); | ||
| } | ||
| function collectConsoleInfo(emit) { | ||
| return wrapConsoleLevel("info", "console_info", emit); | ||
| } | ||
| function collectConsoleLogs(emit) { | ||
| return wrapConsoleLevel("log", "console_log", emit); | ||
| } | ||
| // src/ui/helpers.ts | ||
| function tbIsolationCss(root) { | ||
| return ` | ||
| ${root} { | ||
| box-sizing: border-box; | ||
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; | ||
| font-size: 14px; font-weight: 400; line-height: 1.5; font-style: normal; | ||
| letter-spacing: normal; text-transform: none; text-align: left; | ||
| text-indent: 0; white-space: normal; word-spacing: normal; text-shadow: none; | ||
| -webkit-font-smoothing: antialiased; | ||
| } | ||
| ${root} *, ${root} *::before, ${root} *::after { box-sizing: border-box; } | ||
| ${root} button, ${root} input, ${root} select, ${root} textarea { | ||
| font-family: inherit; font-size: inherit; letter-spacing: normal; | ||
| text-transform: none; margin: 0; | ||
| } | ||
| ${root} svg { max-width: none; max-height: none; vertical-align: middle; } | ||
| ${root} img { max-width: none; } | ||
| ${root} a { text-decoration: none; } | ||
| `; | ||
| } | ||
| function parseShortcut(shortcut) { | ||
| const parts = (shortcut || "").toLowerCase().split("+").map((s) => s.trim()); | ||
| return { | ||
| mod: parts.includes("ctrl") || parts.includes("control") || parts.includes("cmd") || parts.includes("meta"), | ||
| shift: parts.includes("shift"), | ||
| alt: parts.includes("alt") || parts.includes("option"), | ||
| key: parts[parts.length - 1] || "" | ||
| }; | ||
| } | ||
| function matchesShortcut(e, shortcut) { | ||
| if (!shortcut) return false; | ||
| const s = parseShortcut(shortcut); | ||
| const mod = e.ctrlKey || e.metaKey; | ||
| const key = (e.key || "").toLowerCase(); | ||
| return mod === s.mod && e.shiftKey === s.shift && e.altKey === s.alt && key === s.key; | ||
| } | ||
| function escapeHtml(str) { | ||
| return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """); | ||
| } | ||
| export { | ||
| generateSessionId, | ||
| getActiveSessionId, | ||
| setActiveSessionId, | ||
| clearActiveSessionId, | ||
| getActiveCaptureMode, | ||
| setActiveCaptureMode, | ||
| getAllSessions, | ||
| getCachedSessions, | ||
| scheduleFlush, | ||
| flushPendingEvents, | ||
| appendEvent, | ||
| updateSessionError, | ||
| deleteSession, | ||
| addAnnotation, | ||
| saveEnvironment, | ||
| setSessionPriority, | ||
| markSessionSaved, | ||
| clearAllSessions, | ||
| setRedactRules, | ||
| isCustomSensitiveKey, | ||
| sanitizeTokenShapes, | ||
| sanitizeReportForUpload, | ||
| getNetworkFailures, | ||
| clearNetworkFailures, | ||
| collectClicks, | ||
| collectInputs, | ||
| collectSelectChanges, | ||
| collectFormSubmits, | ||
| collectRouteChanges, | ||
| collectApiRequests, | ||
| collectXhrRequests, | ||
| drainPerformanceNetwork, | ||
| collectPerformanceNetwork, | ||
| collectErrors, | ||
| collectConsoleErrors, | ||
| collectConsoleWarnings, | ||
| collectConsoleInfo, | ||
| collectConsoleLogs, | ||
| tbIsolationCss, | ||
| matchesShortcut, | ||
| escapeHtml | ||
| }; | ||
| //# sourceMappingURL=chunk-LQO44M5L.js.map |
Sorry, the diff of this file is too big to display
| import { | ||
| getAllSessions, | ||
| getNetworkFailures | ||
| } from "./chunk-LQO44M5L.js"; | ||
| // src/scanner/helpers.ts | ||
| var _issueCounter = 0; | ||
| function makeIssueId(detector) { | ||
| _issueCounter += 1; | ||
| return `${detector}_${Date.now().toString(36)}_${_issueCounter}`; | ||
| } | ||
| function buildSelector(el) { | ||
| if (!el || el.nodeType !== 1) return ""; | ||
| if (el.id) return `#${cssEscape(el.id)}`; | ||
| const testId = el.getAttribute("data-testid") || el.getAttribute("data-test-id"); | ||
| if (testId) return `[data-testid="${cssEscape(testId)}"]`; | ||
| const parts = []; | ||
| let cur = el; | ||
| let depth = 0; | ||
| while (cur && cur.nodeType === 1 && cur.tagName !== "BODY" && depth < 5) { | ||
| const tag = cur.tagName.toLowerCase(); | ||
| const parent = cur.parentElement; | ||
| if (!parent) { | ||
| parts.unshift(tag); | ||
| break; | ||
| } | ||
| const siblings = Array.from(parent.children).filter((c) => c.tagName === cur.tagName); | ||
| if (siblings.length > 1) { | ||
| const idx = siblings.indexOf(cur) + 1; | ||
| parts.unshift(`${tag}:nth-of-type(${idx})`); | ||
| } else { | ||
| parts.unshift(tag); | ||
| } | ||
| cur = parent; | ||
| depth += 1; | ||
| } | ||
| return parts.join(" > "); | ||
| } | ||
| function coerceSeverity(impact) { | ||
| switch ((impact || "").toLowerCase()) { | ||
| case "critical": | ||
| return "critical"; | ||
| case "serious": | ||
| return "serious"; | ||
| case "moderate": | ||
| return "moderate"; | ||
| case "minor": | ||
| return "minor"; | ||
| default: | ||
| return "minor"; | ||
| } | ||
| } | ||
| function cssEscape(value) { | ||
| if (typeof CSS !== "undefined" && typeof CSS.escape === "function") { | ||
| return CSS.escape(value); | ||
| } | ||
| return value.replace(/[^\w-]/g, (ch) => `\\${ch}`); | ||
| } | ||
| // src/scanner/detectors/broken-images.ts | ||
| async function detectBrokenImages() { | ||
| const issues = []; | ||
| const imgs = Array.from(document.images); | ||
| for (const img of imgs) { | ||
| if (img.closest("#tracebug-root")) continue; | ||
| if (!img.complete) continue; | ||
| if (img.naturalWidth > 0) continue; | ||
| const src = img.currentSrc || img.src; | ||
| if (!src) continue; | ||
| issues.push({ | ||
| id: makeIssueId("broken-image"), | ||
| detector: "broken-image", | ||
| severity: "moderate", | ||
| title: `Broken image: ${truncateUrl(src)}`, | ||
| description: `<img> element failed to load. The browser tried to fetch \`${src}\` and got a network error or a non-image response. ${img.alt ? `Alt text: "${img.alt}"` : "No alt text \u2014 also fails accessibility."}`, | ||
| selector: buildSelector(img), | ||
| url: src, | ||
| page: window.location.pathname, | ||
| detectedAt: Date.now() | ||
| }); | ||
| } | ||
| return issues; | ||
| } | ||
| function truncateUrl(url) { | ||
| if (url.length <= 60) return url; | ||
| const tail = url.split("/").pop() || url.slice(-40); | ||
| return `\u2026/${tail}`; | ||
| } | ||
| // src/scanner/detectors/mixed-content.ts | ||
| var ATTR_TARGETS = [ | ||
| { tag: "img", attr: "src" }, | ||
| { tag: "script", attr: "src" }, | ||
| { tag: "iframe", attr: "src" }, | ||
| { tag: "link", attr: "href" }, | ||
| { tag: "audio", attr: "src" }, | ||
| { tag: "video", attr: "src" }, | ||
| { tag: "source", attr: "src" }, | ||
| { tag: "embed", attr: "src" }, | ||
| { tag: "object", attr: "data" } | ||
| ]; | ||
| async function detectMixedContent() { | ||
| if (typeof window === "undefined" || window.location.protocol !== "https:") { | ||
| return []; | ||
| } | ||
| const issues = []; | ||
| for (const { tag, attr } of ATTR_TARGETS) { | ||
| const elements = document.querySelectorAll(`${tag}[${attr}]`); | ||
| for (const el of Array.from(elements)) { | ||
| if (el.closest("#tracebug-root")) continue; | ||
| const value = el.getAttribute(attr) || ""; | ||
| if (!value.startsWith("http://")) continue; | ||
| if (tag === "link") { | ||
| const rel = (el.rel || "").toLowerCase(); | ||
| const fetchableRels = ["stylesheet", "preload", "prefetch", "manifest", "icon", "shortcut icon"]; | ||
| if (!fetchableRels.some((r) => rel.includes(r))) continue; | ||
| } | ||
| issues.push({ | ||
| id: makeIssueId("mixed-content"), | ||
| detector: "mixed-content", | ||
| severity: tag === "script" || tag === "iframe" ? "serious" : "moderate", | ||
| title: `Mixed content: ${tag} loads over HTTP`, | ||
| description: `<${tag}> on an HTTPS page references \`${value}\`. Browsers block or downgrade this \u2014 the resource usually fails to load and breaks the page's secure-context indicator.`, | ||
| selector: buildSelector(el), | ||
| url: value, | ||
| page: window.location.pathname, | ||
| detectedAt: Date.now() | ||
| }); | ||
| } | ||
| } | ||
| return issues; | ||
| } | ||
| // src/fingerprint.ts | ||
| async function computeFingerprint(errorMessage, errorStack, page) { | ||
| const errorType = extractErrorType(errorMessage); | ||
| const topFrames = extractTopFrames(errorStack || "", 3); | ||
| const input = `${errorType}|${topFrames.join("\n")}|${page}`; | ||
| if (typeof crypto !== "undefined" && crypto.subtle && typeof crypto.subtle.digest === "function") { | ||
| try { | ||
| const buf = new TextEncoder().encode(input); | ||
| const hash = await crypto.subtle.digest("SHA-1", buf); | ||
| return bufferToHex(hash).slice(0, 16); | ||
| } catch (e) { | ||
| } | ||
| } | ||
| return djb2(input).toString(16).padStart(8, "0"); | ||
| } | ||
| function extractErrorType(message) { | ||
| const m = message.match(/^([A-Z][a-zA-Z]+Error|Error)\b/); | ||
| return m ? m[1] : "Error"; | ||
| } | ||
| function extractTopFrames(stack, n) { | ||
| const frames = []; | ||
| const lines = stack.split("\n"); | ||
| for (const line of lines) { | ||
| const m = line.match(/(https?:\/\/[^):\s]+|[^():\s]+\.[a-z]+):(\d+):(\d+)/i); | ||
| if (m) { | ||
| const url = m[1]; | ||
| const path = url.includes("://") ? new URL(url, typeof window !== "undefined" ? window.location.origin : "http://localhost").pathname : url; | ||
| frames.push(`${path}:${m[2]}:${m[3]}`); | ||
| if (frames.length >= n) break; | ||
| } | ||
| } | ||
| return frames; | ||
| } | ||
| function bufferToHex(buf) { | ||
| const bytes = new Uint8Array(buf); | ||
| let out = ""; | ||
| for (let i = 0; i < bytes.length; i++) out += bytes[i].toString(16).padStart(2, "0"); | ||
| return out; | ||
| } | ||
| function djb2(str) { | ||
| let hash = 5381; | ||
| for (let i = 0; i < str.length; i++) hash = (hash << 5) + hash + str.charCodeAt(i) | 0; | ||
| return hash >>> 0; | ||
| } | ||
| // src/scanner/detectors/session-data.ts | ||
| var SLOW_API_MS = 2e3; | ||
| var MAX_CONTEXT_SAMPLES = 10; | ||
| async function detectConsoleErrors(session) { | ||
| var _a, _b, _c; | ||
| if (!session) return []; | ||
| const groups = /* @__PURE__ */ new Map(); | ||
| for (let i = 0; i < session.events.length; i++) { | ||
| const e = session.events[i]; | ||
| if (e.type !== "error" && e.type !== "unhandled_rejection" && e.type !== "console_error") continue; | ||
| const message = ((_a = e.data.error) == null ? void 0 : _a.message) || e.data.message || ""; | ||
| if (!message) continue; | ||
| const stack = ((_b = e.data.error) == null ? void 0 : _b.stack) || ""; | ||
| const page = e.page || (typeof window !== "undefined" ? window.location.pathname : ""); | ||
| const fp = await computeFingerprint(message, stack, page); | ||
| const precedingAction = describePrecedingAction(session.events, i); | ||
| const existing = groups.get(fp); | ||
| if (existing) { | ||
| existing.issue.occurrences = (existing.issue.occurrences || 1) + 1; | ||
| existing.issue.lastSeenAt = e.timestamp; | ||
| if (existing.samples.length < MAX_CONTEXT_SAMPLES) { | ||
| existing.samples.push({ timestamp: e.timestamp, precedingAction }); | ||
| } | ||
| continue; | ||
| } | ||
| const firstFrame = ((_c = stack.split("\n").find((l) => l.trim().startsWith("at "))) == null ? void 0 : _c.trim()) || ""; | ||
| const issue = { | ||
| id: makeIssueId("console-error"), | ||
| detector: "console-error", | ||
| severity: classifyErrorSeverity(message), | ||
| title: `JS error: ${message.slice(0, 70)}${message.length > 70 ? "\u2026" : ""}`, | ||
| description: firstFrame ? `${message} | ||
| First frame: ${firstFrame}` : message, | ||
| page, | ||
| detectedAt: e.timestamp, | ||
| fingerprint: fp, | ||
| occurrences: 1, | ||
| firstSeenAt: e.timestamp, | ||
| lastSeenAt: e.timestamp | ||
| }; | ||
| groups.set(fp, { issue, samples: [{ timestamp: e.timestamp, precedingAction }] }); | ||
| } | ||
| const out = []; | ||
| for (const g of groups.values()) { | ||
| const n = g.issue.occurrences || 1; | ||
| if (n > 1) { | ||
| g.issue.title = `${g.issue.title} [\xD7${n}]`; | ||
| g.issue.contextSamples = g.samples; | ||
| } | ||
| out.push(g.issue); | ||
| } | ||
| return out; | ||
| } | ||
| function describePrecedingAction(events, i) { | ||
| var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k; | ||
| for (let j = i - 1; j >= 0; j--) { | ||
| const e = events[j]; | ||
| if (e.type === "click") { | ||
| const t = ((_b = (_a = e.data) == null ? void 0 : _a.element) == null ? void 0 : _b.text) || ((_d = (_c = e.data) == null ? void 0 : _c.element) == null ? void 0 : _d.ariaLabel) || ((_f = (_e = e.data) == null ? void 0 : _e.element) == null ? void 0 : _f.tag) || "element"; | ||
| return `clicked "${String(t).slice(0, 40)}"`; | ||
| } | ||
| if (e.type === "input") { | ||
| const n = ((_h = (_g = e.data) == null ? void 0 : _g.element) == null ? void 0 : _h.name) || ((_j = (_i = e.data) == null ? void 0 : _i.element) == null ? void 0 : _j.id) || "field"; | ||
| return `typed in ${n}`; | ||
| } | ||
| if (e.type === "select_change") return "selected an option"; | ||
| if (e.type === "form_submit") return "submitted a form"; | ||
| if (e.type === "route_change") return `navigated to ${((_k = e.data) == null ? void 0 : _k.to) || "page"}`; | ||
| } | ||
| return void 0; | ||
| } | ||
| async function detectFailedRequests(session) { | ||
| if (!session) return []; | ||
| const issues = []; | ||
| const buffer = getNetworkFailures(); | ||
| for (const e of session.events) { | ||
| if (e.type !== "api_request") continue; | ||
| const req = e.data.request; | ||
| if (!req) continue; | ||
| const status = req.statusCode || 0; | ||
| if (status >= 200 && status < 400) continue; | ||
| if (status === 0 && req.method === "HEAD") continue; | ||
| const match = buffer.find( | ||
| (b) => b.url === req.url && b.method === req.method && b.status === status && Math.abs(b.timestamp - e.timestamp) < 5e3 | ||
| ); | ||
| const snippet = (match == null ? void 0 : match.response) ? ` | ||
| Response: ${match.response.slice(0, 160)}` : ""; | ||
| issues.push({ | ||
| id: makeIssueId("failed-request"), | ||
| detector: "failed-request", | ||
| severity: status >= 500 ? "critical" : status === 0 ? "serious" : "moderate", | ||
| title: `${req.method} ${truncatePath(req.url)} \u2192 ${status === 0 ? "Network Error" : status}`, | ||
| description: `Request failed in ${req.durationMs || 0}ms.${snippet}`, | ||
| url: req.url, | ||
| page: e.page || window.location.pathname, | ||
| detectedAt: e.timestamp | ||
| }); | ||
| } | ||
| return issues; | ||
| } | ||
| async function detectSlowApis(session) { | ||
| if (!session) return []; | ||
| const issues = []; | ||
| for (const e of session.events) { | ||
| if (e.type !== "api_request") continue; | ||
| const req = e.data.request; | ||
| if (!req) continue; | ||
| const status = req.statusCode || 0; | ||
| if (status < 200 || status >= 400) continue; | ||
| const duration = req.durationMs || 0; | ||
| if (duration < SLOW_API_MS) continue; | ||
| issues.push({ | ||
| id: makeIssueId("slow-api"), | ||
| detector: "slow-api", | ||
| severity: duration > 5e3 ? "serious" : "moderate", | ||
| title: `Slow API: ${req.method} ${truncatePath(req.url)} (${duration}ms)`, | ||
| description: `This request took ${(duration / 1e3).toFixed(1)}s \u2014 over the ${SLOW_API_MS / 1e3}s threshold. Slow APIs are a common UX complaint and a leading cause of perceived bugs ("the page is frozen").`, | ||
| url: req.url, | ||
| page: e.page || window.location.pathname, | ||
| detectedAt: e.timestamp | ||
| }); | ||
| } | ||
| return issues; | ||
| } | ||
| function truncatePath(url) { | ||
| try { | ||
| const u = new URL(url, window.location.origin); | ||
| const p = u.pathname.length > 50 ? u.pathname.slice(0, 47) + "\u2026" : u.pathname; | ||
| return p; | ||
| } catch (e) { | ||
| return url.length > 50 ? url.slice(0, 47) + "\u2026" : url; | ||
| } | ||
| } | ||
| function classifyErrorSeverity(message) { | ||
| if (/TypeError|ReferenceError|SyntaxError/i.test(message)) return "critical"; | ||
| if (/Network|fetch|failed to/i.test(message)) return "serious"; | ||
| return "moderate"; | ||
| } | ||
| // src/scanner/detectors/a11y.ts | ||
| var _axePromise = null; | ||
| function loadAxe() { | ||
| if (_axePromise) return _axePromise; | ||
| _axePromise = import("axe-core").then((mod) => mod.default || mod).catch((err) => { | ||
| console.warn("[TraceBug] axe-core failed to load:", err); | ||
| return null; | ||
| }); | ||
| return _axePromise; | ||
| } | ||
| async function detectA11yViolations() { | ||
| const axe = await loadAxe(); | ||
| if (!axe || typeof axe.run !== "function") return []; | ||
| let results; | ||
| try { | ||
| results = await axe.run(document, { | ||
| // Only WCAG-tagged rules — keeps signal-to-noise high. Best-practice | ||
| // rules add ~30% more noise without proportional value for QA. | ||
| runOnly: { type: "tag", values: ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"] }, | ||
| // Skip our own UI so QA isn't told their toolbar fails contrast checks. | ||
| // axe accepts a context with exclude — we pass { exclude: [...] } via | ||
| // the second-argument options-shaped form below to keep types loose. | ||
| resultTypes: ["violations"] | ||
| }); | ||
| } catch (err) { | ||
| console.warn("[TraceBug] axe.run failed:", err); | ||
| return []; | ||
| } | ||
| const issues = []; | ||
| const violations = (results == null ? void 0 : results.violations) || []; | ||
| for (const v of violations) { | ||
| const nodes = v.nodes || []; | ||
| const firstNode = nodes[0]; | ||
| const selector = Array.isArray(firstNode == null ? void 0 : firstNode.target) ? firstNode.target.join(" ") : ""; | ||
| const exampleSnippet = ((firstNode == null ? void 0 : firstNode.html) || "").slice(0, 120); | ||
| const moreSuffix = nodes.length > 1 ? ` (+ ${nodes.length - 1} more element${nodes.length === 2 ? "" : "s"})` : ""; | ||
| issues.push({ | ||
| id: makeIssueId("axe-a11y"), | ||
| detector: "axe-a11y", | ||
| severity: coerceSeverity(v.impact), | ||
| title: `${v.help || v.id}${moreSuffix}`, | ||
| description: `${v.description || v.id} | ||
| First element: \`${exampleSnippet}\``, | ||
| selector: selector || void 0, | ||
| helpUrl: v.helpUrl, | ||
| page: window.location.pathname, | ||
| detectedAt: Date.now() | ||
| }); | ||
| } | ||
| return issues; | ||
| } | ||
| // src/scanner/detectors/frustration.ts | ||
| var RAGE_WINDOW_MS = 1500; | ||
| var RAGE_MIN_CLICKS = 3; | ||
| var DEAD_RESPONSE_WINDOW_MS = 1500; | ||
| var ABANDON_WINDOW_MS = 6e4; | ||
| var ERROR_CORRELATION_WINDOW_MS = 2500; | ||
| async function detectFrustration(session) { | ||
| var _a; | ||
| if (!session) return []; | ||
| const events = session.events; | ||
| if (events.length === 0) return []; | ||
| const issues = []; | ||
| const page = ((_a = session.events[0]) == null ? void 0 : _a.page) || window.location.pathname; | ||
| issues.push(...detectRageClicks(events, page)); | ||
| issues.push(...detectDeadClicks(events, page)); | ||
| issues.push(...detectFormAbandonment(events, page)); | ||
| issues.push(...detectErrorCorrelated(events, page)); | ||
| return issues; | ||
| } | ||
| function detectRageClicks(events, page) { | ||
| var _a, _b, _c, _d, _e, _f, _g, _h; | ||
| const out = []; | ||
| const seenGroups = /* @__PURE__ */ new Set(); | ||
| for (let i = 0; i < events.length; i++) { | ||
| const e = events[i]; | ||
| if (e.type !== "click") continue; | ||
| const sel = ((_b = (_a = e.data) == null ? void 0 : _a.element) == null ? void 0 : _b.selector) || ((_d = (_c = e.data) == null ? void 0 : _c.element) == null ? void 0 : _d.testId) || ""; | ||
| if (!sel) continue; | ||
| const cluster = [e]; | ||
| let j = i + 1; | ||
| while (j < events.length) { | ||
| const next = events[j]; | ||
| if (next.timestamp - e.timestamp > RAGE_WINDOW_MS) break; | ||
| if (isResponseEvent(next)) break; | ||
| if (next.type === "click") { | ||
| const nextSel = ((_f = (_e = next.data) == null ? void 0 : _e.element) == null ? void 0 : _f.selector) || ((_h = (_g = next.data) == null ? void 0 : _g.element) == null ? void 0 : _h.testId) || ""; | ||
| if (nextSel === sel) cluster.push(next); | ||
| } | ||
| j++; | ||
| } | ||
| if (cluster.length >= RAGE_MIN_CLICKS) { | ||
| const key = `${sel}@${e.timestamp}`; | ||
| if (seenGroups.has(key)) continue; | ||
| seenGroups.add(key); | ||
| const label = clickLabel(cluster[0]); | ||
| out.push({ | ||
| id: makeIssueId("frustration-rage"), | ||
| detector: "frustration-rage", | ||
| severity: "serious", | ||
| title: `Rage clicks on ${label} (${cluster.length}\xD7 in ${Math.round(cluster[cluster.length - 1].timestamp - cluster[0].timestamp)}ms)`, | ||
| description: `User clicked the same element ${cluster.length} times within ${RAGE_WINDOW_MS}ms with no observable response (no API call, navigation, or DOM update). The element either doesn't respond to clicks or feels broken.`, | ||
| selector: sel, | ||
| page, | ||
| detectedAt: cluster[0].timestamp, | ||
| firstSeenAt: cluster[0].timestamp, | ||
| lastSeenAt: cluster[cluster.length - 1].timestamp, | ||
| occurrences: cluster.length | ||
| }); | ||
| i = j - 1; | ||
| } | ||
| } | ||
| return out; | ||
| } | ||
| function detectDeadClicks(events, page) { | ||
| var _a, _b; | ||
| const out = []; | ||
| const MAX = 5; | ||
| for (let i = 0; i < events.length && out.length < MAX; i++) { | ||
| const e = events[i]; | ||
| if (e.type !== "click") continue; | ||
| let responsive = false; | ||
| for (let j = i + 1; j < events.length; j++) { | ||
| const next = events[j]; | ||
| if (next.timestamp - e.timestamp > DEAD_RESPONSE_WINDOW_MS) break; | ||
| if (isResponseEvent(next)) { | ||
| responsive = true; | ||
| break; | ||
| } | ||
| } | ||
| if (responsive) continue; | ||
| const sel = ((_b = (_a = e.data) == null ? void 0 : _a.element) == null ? void 0 : _b.selector) || ""; | ||
| const label = clickLabel(e); | ||
| out.push({ | ||
| id: makeIssueId("frustration-dead"), | ||
| detector: "frustration-dead", | ||
| severity: "moderate", | ||
| title: `Dead click on ${label}`, | ||
| description: `Clicked but nothing happened within ${DEAD_RESPONSE_WINDOW_MS}ms (no API call, navigation, or DOM input). The element may have an unbound handler, a swallowed event, or be visually clickable but disabled.`, | ||
| selector: sel, | ||
| page: e.page || page, | ||
| detectedAt: e.timestamp | ||
| }); | ||
| } | ||
| return out; | ||
| } | ||
| function detectFormAbandonment(events, _page) { | ||
| var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j; | ||
| const out = []; | ||
| const formActivity = {}; | ||
| for (const e of events) { | ||
| if (e.type === "input") { | ||
| const formId = ((_b = (_a = e.data) == null ? void 0 : _a.element) == null ? void 0 : _b.formId) || ((_d = (_c = e.data) == null ? void 0 : _c.element) == null ? void 0 : _d.formAction) || "_default"; | ||
| if (!formActivity[formId]) { | ||
| formActivity[formId] = { firstInputAt: e.timestamp, fieldsSeen: /* @__PURE__ */ new Set(), lastInputAt: e.timestamp, page: e.page }; | ||
| } | ||
| const name = ((_f = (_e = e.data) == null ? void 0 : _e.element) == null ? void 0 : _f.name) || ((_h = (_g = e.data) == null ? void 0 : _g.element) == null ? void 0 : _h.id) || "field"; | ||
| formActivity[formId].fieldsSeen.add(name); | ||
| formActivity[formId].lastInputAt = e.timestamp; | ||
| } else if (e.type === "form_submit") { | ||
| const formId = ((_j = (_i = e.data) == null ? void 0 : _i.form) == null ? void 0 : _j.id) || "_default"; | ||
| delete formActivity[formId]; | ||
| } else if (e.type === "route_change") { | ||
| for (const formId of Object.keys(formActivity)) { | ||
| const a = formActivity[formId]; | ||
| if (e.timestamp - a.lastInputAt > ABANDON_WINDOW_MS) continue; | ||
| if (a.fieldsSeen.size === 0) continue; | ||
| out.push({ | ||
| id: makeIssueId("frustration-abandon"), | ||
| detector: "frustration-abandon", | ||
| severity: "moderate", | ||
| title: `Form abandoned on ${a.page} (${a.fieldsSeen.size} field${a.fieldsSeen.size === 1 ? "" : "s"} filled)`, | ||
| description: `User typed into ${a.fieldsSeen.size} field${a.fieldsSeen.size === 1 ? "" : "s"} (${Array.from(a.fieldsSeen).slice(0, 5).join(", ")}) and then navigated away without submitting. Likely a UX problem: the submit button is unclear, the form requires too much info, or it's failing silently.`, | ||
| page: a.page, | ||
| detectedAt: a.lastInputAt, | ||
| firstSeenAt: a.firstInputAt, | ||
| lastSeenAt: a.lastInputAt | ||
| }); | ||
| delete formActivity[formId]; | ||
| } | ||
| } | ||
| } | ||
| return out; | ||
| } | ||
| function detectErrorCorrelated(events, page) { | ||
| var _a, _b, _c, _d, _e, _f; | ||
| const out = []; | ||
| const seen = /* @__PURE__ */ new Set(); | ||
| for (let i = 0; i < events.length; i++) { | ||
| const e = events[i]; | ||
| const isError = e.type === "error" || e.type === "unhandled_rejection" || e.type === "console_error"; | ||
| if (!isError) continue; | ||
| let click = null; | ||
| for (let j = i - 1; j >= 0; j--) { | ||
| const prev = events[j]; | ||
| if (e.timestamp - prev.timestamp > ERROR_CORRELATION_WINDOW_MS) break; | ||
| if (prev.type === "click") { | ||
| click = prev; | ||
| break; | ||
| } | ||
| } | ||
| if (!click) continue; | ||
| const errMsg = ((_b = (_a = e.data) == null ? void 0 : _a.error) == null ? void 0 : _b.message) || ""; | ||
| if (!errMsg) continue; | ||
| const key = `${errMsg}::${((_d = (_c = click.data) == null ? void 0 : _c.element) == null ? void 0 : _d.selector) || ""}`; | ||
| if (seen.has(key)) continue; | ||
| seen.add(key); | ||
| const label = clickLabel(click); | ||
| const truncMsg = errMsg.length > 60 ? errMsg.slice(0, 57) + "\u2026" : errMsg; | ||
| const delta = Math.round(e.timestamp - click.timestamp); | ||
| out.push({ | ||
| id: makeIssueId("frustration-error-correlated"), | ||
| detector: "frustration-error-correlated", | ||
| severity: "critical", | ||
| title: `Click on ${label} triggered: ${truncMsg}`, | ||
| description: `An error fired ${delta}ms after the user clicked ${label}. This is almost certainly the offending interaction \u2014 the click handler threw, or its async path failed.`, | ||
| selector: (_f = (_e = click.data) == null ? void 0 : _e.element) == null ? void 0 : _f.selector, | ||
| page: e.page || page, | ||
| detectedAt: e.timestamp | ||
| }); | ||
| } | ||
| return out; | ||
| } | ||
| function isResponseEvent(e) { | ||
| return e.type === "api_request" || e.type === "route_change" || e.type === "input" || e.type === "form_submit" || e.type === "select_change"; | ||
| } | ||
| function clickLabel(e) { | ||
| var _a; | ||
| const el = (_a = e.data) == null ? void 0 : _a.element; | ||
| const raw = (el == null ? void 0 : el.text) || (el == null ? void 0 : el.ariaLabel) || (el == null ? void 0 : el.testId) || (el == null ? void 0 : el.id) || (el == null ? void 0 : el.tag) || "element"; | ||
| const trimmed = String(raw).replace(/\s+/g, " ").trim(); | ||
| return trimmed.length > 40 ? `"${trimmed.slice(0, 37)}\u2026"` : `"${trimmed}"`; | ||
| } | ||
| // src/scanner/index.ts | ||
| var _issues = []; | ||
| var _scanInFlight = null; | ||
| var _lastScanAt = 0; | ||
| var SEVERITY_ORDER = { | ||
| critical: 0, | ||
| serious: 1, | ||
| moderate: 2, | ||
| minor: 3 | ||
| }; | ||
| async function scan() { | ||
| if (_scanInFlight) { | ||
| const issues = await _scanInFlight; | ||
| return { issues, durationMs: 0, scannedAt: _lastScanAt }; | ||
| } | ||
| const startedAt = Date.now(); | ||
| const sessions = getAllSessions().sort((a, b) => b.updatedAt - a.updatedAt); | ||
| const session = sessions[0] || null; | ||
| const safeRun = (p) => p.catch((err) => { | ||
| console.warn("[TraceBug] Detector failed:", err); | ||
| return []; | ||
| }); | ||
| _scanInFlight = Promise.all([ | ||
| safeRun(detectBrokenImages()), | ||
| safeRun(detectMixedContent()), | ||
| safeRun(detectConsoleErrors(session)), | ||
| safeRun(detectFailedRequests(session)), | ||
| safeRun(detectSlowApis(session)), | ||
| safeRun(detectA11yViolations()), | ||
| safeRun(detectFrustration(session)) | ||
| ]).then((results) => { | ||
| const all = [].concat(...results); | ||
| all.sort((a, b) => { | ||
| const sev = SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity]; | ||
| if (sev !== 0) return sev; | ||
| return a.detectedAt - b.detectedAt; | ||
| }); | ||
| _issues = all; | ||
| return all; | ||
| }); | ||
| try { | ||
| const issues = await _scanInFlight; | ||
| _lastScanAt = Date.now(); | ||
| return { issues, durationMs: _lastScanAt - startedAt, scannedAt: _lastScanAt }; | ||
| } finally { | ||
| _scanInFlight = null; | ||
| } | ||
| } | ||
| function getIssues(options) { | ||
| var _a; | ||
| const includeDismissed = (_a = options == null ? void 0 : options.includeDismissed) != null ? _a : false; | ||
| return includeDismissed ? _issues.slice() : _issues.filter((i) => !i.dismissed); | ||
| } | ||
| function dismissIssue(id) { | ||
| const issue = _issues.find((i) => i.id === id); | ||
| if (!issue) return false; | ||
| issue.dismissed = true; | ||
| return true; | ||
| } | ||
| function undismissIssue(id) { | ||
| const issue = _issues.find((i) => i.id === id); | ||
| if (!issue) return false; | ||
| issue.dismissed = false; | ||
| return true; | ||
| } | ||
| function clearIssues() { | ||
| _issues = []; | ||
| _lastScanAt = 0; | ||
| } | ||
| function getIssueCountsByDetector() { | ||
| const counts = { | ||
| "axe-a11y": 0, | ||
| "broken-image": 0, | ||
| "mixed-content": 0, | ||
| "console-error": 0, | ||
| "slow-api": 0, | ||
| "failed-request": 0, | ||
| "frustration-rage": 0, | ||
| "frustration-dead": 0, | ||
| "frustration-abandon": 0, | ||
| "frustration-error-correlated": 0 | ||
| }; | ||
| for (const i of _issues) { | ||
| if (i.dismissed) continue; | ||
| counts[i.detector] = (counts[i.detector] || 0) + 1; | ||
| } | ||
| return counts; | ||
| } | ||
| function getIssueCountsBySeverity() { | ||
| const counts = { | ||
| critical: 0, | ||
| serious: 0, | ||
| moderate: 0, | ||
| minor: 0 | ||
| }; | ||
| for (const i of _issues) { | ||
| if (i.dismissed) continue; | ||
| counts[i.severity] += 1; | ||
| } | ||
| return counts; | ||
| } | ||
| function getIssueById(id) { | ||
| return _issues.find((i) => i.id === id) || null; | ||
| } | ||
| export { | ||
| scan, | ||
| getIssues, | ||
| dismissIssue, | ||
| undismissIssue, | ||
| clearIssues, | ||
| getIssueCountsByDetector, | ||
| getIssueCountsBySeverity, | ||
| getIssueById | ||
| }; | ||
| //# sourceMappingURL=chunk-URDH7OBN.js.map |
| {"version":3,"sources":["../src/scanner/helpers.ts","../src/scanner/detectors/broken-images.ts","../src/scanner/detectors/mixed-content.ts","../src/fingerprint.ts","../src/scanner/detectors/session-data.ts","../src/scanner/detectors/a11y.ts","../src/scanner/detectors/frustration.ts","../src/scanner/index.ts"],"sourcesContent":["// ── Scanner helpers ───────────────────────────────────────────────────────\r\n// Shared utilities for detectors: stable issue IDs, robust CSS selectors,\r\n// severity coercion.\r\n\r\nimport { IssueDetector, IssueSeverity } from \"../types\";\r\n\r\nlet _issueCounter = 0;\r\n\r\n/** Stable, unique issue ID across detectors and runs. */\r\nexport function makeIssueId(detector: IssueDetector): string {\r\n _issueCounter += 1;\r\n return `${detector}_${Date.now().toString(36)}_${_issueCounter}`;\r\n}\r\n\r\n/**\r\n * Build a \"good enough\" CSS selector for an element. Prefers id, then\r\n * data-testid, then a tag+nth-of-type chain bounded to depth 5. Not\r\n * guaranteed unique on pathological pages but unique enough for clicking\r\n * back to the offending node.\r\n */\r\nexport function buildSelector(el: Element): string {\r\n if (!el || el.nodeType !== 1) return \"\";\r\n if (el.id) return `#${cssEscape(el.id)}`;\r\n\r\n const testId = el.getAttribute(\"data-testid\") || el.getAttribute(\"data-test-id\");\r\n if (testId) return `[data-testid=\"${cssEscape(testId)}\"]`;\r\n\r\n const parts: string[] = [];\r\n let cur: Element | null = el;\r\n let depth = 0;\r\n while (cur && cur.nodeType === 1 && cur.tagName !== \"BODY\" && depth < 5) {\r\n const tag = cur.tagName.toLowerCase();\r\n // Explicit annotation breaks the circular inference from `cur = parent`\r\n // below (which otherwise makes `parent` — and `parent.children` — `any`).\r\n const parent: HTMLElement | null = cur.parentElement;\r\n if (!parent) {\r\n parts.unshift(tag);\r\n break;\r\n }\r\n const siblings = Array.from(parent.children).filter(c => c.tagName === cur!.tagName);\r\n if (siblings.length > 1) {\r\n const idx = siblings.indexOf(cur) + 1;\r\n parts.unshift(`${tag}:nth-of-type(${idx})`);\r\n } else {\r\n parts.unshift(tag);\r\n }\r\n cur = parent;\r\n depth += 1;\r\n }\r\n return parts.join(\" > \");\r\n}\r\n\r\n/**\r\n * Map axe-core's impact strings to our IssueSeverity enum. Axe's \"serious\"\r\n * is our highest non-critical bucket.\r\n */\r\nexport function coerceSeverity(impact: string | null | undefined): IssueSeverity {\r\n switch ((impact || \"\").toLowerCase()) {\r\n case \"critical\": return \"critical\";\r\n case \"serious\": return \"serious\";\r\n case \"moderate\": return \"moderate\";\r\n case \"minor\": return \"minor\";\r\n default: return \"minor\";\r\n }\r\n}\r\n\r\n/**\r\n * Minimal CSS.escape polyfill — needed when an id contains characters that\r\n * would break a selector (colons, brackets, dots in framework-generated ids).\r\n */\r\nfunction cssEscape(value: string): string {\r\n if (typeof CSS !== \"undefined\" && typeof CSS.escape === \"function\") {\r\n return CSS.escape(value);\r\n }\r\n return value.replace(/[^\\w-]/g, ch => `\\\\${ch}`);\r\n}\r\n","// ── Broken-image detector ─────────────────────────────────────────────────\r\n// Walks every <img> on the page and flags ones that failed to load. Uses\r\n// `naturalWidth === 0 && complete === true` — the standard signal for\r\n// \"image attempted to load and failed.\" Skips images that haven't finished\r\n// loading yet (we can't tell if they're broken until they settle).\r\n\r\nimport { Issue } from \"../../types\";\r\nimport { buildSelector, makeIssueId } from \"../helpers\";\r\n\r\nexport async function detectBrokenImages(): Promise<Issue[]> {\r\n const issues: Issue[] = [];\r\n const imgs = Array.from(document.images);\r\n\r\n for (const img of imgs) {\r\n // Skip TraceBug's own UI.\r\n if (img.closest(\"#tracebug-root\")) continue;\r\n // Image still loading — can't decide yet.\r\n if (!img.complete) continue;\r\n // Decoded successfully — naturalWidth is non-zero.\r\n if (img.naturalWidth > 0) continue;\r\n\r\n // No src is a different problem (missing asset, not a broken load).\r\n const src = img.currentSrc || img.src;\r\n if (!src) continue;\r\n\r\n issues.push({\r\n id: makeIssueId(\"broken-image\"),\r\n detector: \"broken-image\",\r\n severity: \"moderate\",\r\n title: `Broken image: ${truncateUrl(src)}`,\r\n description: `<img> element failed to load. The browser tried to fetch \\`${src}\\` and got a network error or a non-image response. ${\r\n img.alt ? `Alt text: \"${img.alt}\"` : \"No alt text — also fails accessibility.\"\r\n }`,\r\n selector: buildSelector(img),\r\n url: src,\r\n page: window.location.pathname,\r\n detectedAt: Date.now(),\r\n });\r\n }\r\n\r\n return issues;\r\n}\r\n\r\nfunction truncateUrl(url: string): string {\r\n if (url.length <= 60) return url;\r\n // Keep the filename — easier to identify than the host.\r\n const tail = url.split(\"/\").pop() || url.slice(-40);\r\n return `…/${tail}`;\r\n}\r\n","// ── Mixed-content detector ────────────────────────────────────────────────\r\n// Flags every `http://` resource on a `https://` page. Browsers block most\r\n// of these silently (active content) or downgrade them (passive content),\r\n// so users rarely notice — but the asset usually fails or breaks the lock\r\n// icon. Worth surfacing.\r\n\r\nimport { Issue } from \"../../types\";\r\nimport { buildSelector, makeIssueId } from \"../helpers\";\r\n\r\nconst ATTR_TARGETS: Array<{ tag: string; attr: string }> = [\r\n { tag: \"img\", attr: \"src\" },\r\n { tag: \"script\", attr: \"src\" },\r\n { tag: \"iframe\", attr: \"src\" },\r\n { tag: \"link\", attr: \"href\" },\r\n { tag: \"audio\", attr: \"src\" },\r\n { tag: \"video\", attr: \"src\" },\r\n { tag: \"source\", attr: \"src\" },\r\n { tag: \"embed\", attr: \"src\" },\r\n { tag: \"object\", attr: \"data\" },\r\n];\r\n\r\nexport async function detectMixedContent(): Promise<Issue[]> {\r\n // Only relevant on HTTPS pages — skip on plain HTTP and file:// origins.\r\n if (typeof window === \"undefined\" || window.location.protocol !== \"https:\") {\r\n return [];\r\n }\r\n\r\n const issues: Issue[] = [];\r\n for (const { tag, attr } of ATTR_TARGETS) {\r\n const elements = document.querySelectorAll(`${tag}[${attr}]`);\r\n for (const el of Array.from(elements)) {\r\n if (el.closest(\"#tracebug-root\")) continue;\r\n const value = (el as HTMLElement).getAttribute(attr) || \"\";\r\n if (!value.startsWith(\"http://\")) continue;\r\n\r\n // <link> only matters when it's loading something the browser fetches —\r\n // stylesheets, preloads, manifests, icons. Skip rel=\"canonical\" etc.\r\n if (tag === \"link\") {\r\n const rel = ((el as HTMLLinkElement).rel || \"\").toLowerCase();\r\n const fetchableRels = [\"stylesheet\", \"preload\", \"prefetch\", \"manifest\", \"icon\", \"shortcut icon\"];\r\n if (!fetchableRels.some(r => rel.includes(r))) continue;\r\n }\r\n\r\n issues.push({\r\n id: makeIssueId(\"mixed-content\"),\r\n detector: \"mixed-content\",\r\n severity: tag === \"script\" || tag === \"iframe\" ? \"serious\" : \"moderate\",\r\n title: `Mixed content: ${tag} loads over HTTP`,\r\n description: `<${tag}> on an HTTPS page references \\`${value}\\`. Browsers block or downgrade this — the resource usually fails to load and breaks the page's secure-context indicator.`,\r\n selector: buildSelector(el as HTMLElement),\r\n url: value,\r\n page: window.location.pathname,\r\n detectedAt: Date.now(),\r\n });\r\n }\r\n }\r\n return issues;\r\n}\r\n","// ── Bug Fingerprint ───────────────────────────────────────────────────────\r\n// Group identical errors locally so 14× the same TypeError collapses into\r\n// one issue with `[×14]` instead of 14 near-duplicate rows.\r\n//\r\n// Fingerprint inputs (in priority order):\r\n// 1. Error type/class extracted from the message (\"TypeError\", etc.)\r\n// 2. Top three \"at ...\" stack frames (location-only — line numbers\r\n// stable across invocations within the same build)\r\n// 3. Page path\r\n//\r\n// We use SHA-1 via `crypto.subtle.digest` when available — falls back to\r\n// a tiny non-cryptographic hash on older contexts. Fingerprint is not\r\n// security-sensitive; collision rate of djb2 is good enough for this.\r\n\r\n/** Compute a stable fingerprint string for an error + page combo. */\r\nexport async function computeFingerprint(\r\n errorMessage: string,\r\n errorStack: string | undefined,\r\n page: string\r\n): Promise<string> {\r\n const errorType = extractErrorType(errorMessage);\r\n const topFrames = extractTopFrames(errorStack || \"\", 3);\r\n const input = `${errorType}|${topFrames.join(\"\\n\")}|${page}`;\r\n\r\n // Prefer SHA-1 from the Subtle Crypto API for stronger uniqueness.\r\n if (typeof crypto !== \"undefined\" && crypto.subtle && typeof crypto.subtle.digest === \"function\") {\r\n try {\r\n const buf = new TextEncoder().encode(input);\r\n const hash = await crypto.subtle.digest(\"SHA-1\", buf);\r\n return bufferToHex(hash).slice(0, 16);\r\n } catch {}\r\n }\r\n return djb2(input).toString(16).padStart(8, \"0\");\r\n}\r\n\r\n/**\r\n * Synchronous fallback fingerprint — used in code paths that can't `await`.\r\n * Collision rate higher than SHA-1 but acceptable for in-session grouping.\r\n */\r\nexport function computeFingerprintSync(\r\n errorMessage: string,\r\n errorStack: string | undefined,\r\n page: string\r\n): string {\r\n const errorType = extractErrorType(errorMessage);\r\n const topFrames = extractTopFrames(errorStack || \"\", 3);\r\n const input = `${errorType}|${topFrames.join(\"\\n\")}|${page}`;\r\n return djb2(input).toString(16).padStart(8, \"0\");\r\n}\r\n\r\n/** Pick out the JS error class — TypeError, ReferenceError, etc. */\r\nfunction extractErrorType(message: string): string {\r\n const m = message.match(/^([A-Z][a-zA-Z]+Error|Error)\\b/);\r\n return m ? m[1] : \"Error\";\r\n}\r\n\r\n/**\r\n * Extract the location parts (\"foo.js:42:13\") of the top N stack frames,\r\n * dropping function names. Same call site → same fingerprint, even if the\r\n * function gets renamed between minified/dev builds.\r\n */\r\nfunction extractTopFrames(stack: string, n: number): string[] {\r\n const frames: string[] = [];\r\n const lines = stack.split(\"\\n\");\r\n for (const line of lines) {\r\n const m = line.match(/(https?:\\/\\/[^):\\s]+|[^():\\s]+\\.[a-z]+):(\\d+):(\\d+)/i);\r\n if (m) {\r\n const url = m[1];\r\n const path = url.includes(\"://\") ? new URL(url, typeof window !== \"undefined\" ? window.location.origin : \"http://localhost\").pathname : url;\r\n frames.push(`${path}:${m[2]}:${m[3]}`);\r\n if (frames.length >= n) break;\r\n }\r\n }\r\n return frames;\r\n}\r\n\r\nfunction bufferToHex(buf: ArrayBuffer): string {\r\n const bytes = new Uint8Array(buf);\r\n let out = \"\";\r\n for (let i = 0; i < bytes.length; i++) out += bytes[i].toString(16).padStart(2, \"0\");\r\n return out;\r\n}\r\n\r\n/** djb2 — tiny non-cryptographic hash. Stable, deterministic, no deps. */\r\nfunction djb2(str: string): number {\r\n let hash = 5381;\r\n for (let i = 0; i < str.length; i++) hash = ((hash << 5) + hash + str.charCodeAt(i)) | 0;\r\n return hash >>> 0; // unsigned 32-bit\r\n}\r\n","// ── Session-data detectors ────────────────────────────────────────────────\r\n// Three detectors that read from the SDK's existing event buffer and\r\n// network-failure ring buffer — no new tracking, just classification.\r\n//\r\n// console-error: every distinct error/unhandled rejection in the session\r\n// failed-request: every 4xx/5xx/network-error from the api_request stream\r\n// slow-api: every successful api_request that took longer than the threshold\r\n\r\nimport { Issue, StoredSession, TraceBugEvent } from \"../../types\";\r\nimport { getNetworkFailures } from \"../../collectors\";\r\nimport { makeIssueId } from \"../helpers\";\r\nimport { computeFingerprint } from \"../../fingerprint\";\r\n\r\nconst SLOW_API_MS = 2000;\r\nconst MAX_CONTEXT_SAMPLES = 10;\r\n\r\n/**\r\n * Console errors collapsed by fingerprint (errorType + top-3 frames + page).\r\n * Repeats accumulate `occurrences` + `firstSeenAt`/`lastSeenAt` + a few\r\n * `contextSamples` so the UI can show the count and let the user expand\r\n * to see distinct preceding actions.\r\n */\r\nexport async function detectConsoleErrors(session: StoredSession | null): Promise<Issue[]> {\r\n if (!session) return [];\r\n\r\n // Build groups by fingerprint.\r\n type Group = {\r\n issue: Issue;\r\n samples: Array<{ timestamp: number; precedingAction?: string }>;\r\n };\r\n const groups = new Map<string, Group>();\r\n\r\n for (let i = 0; i < session.events.length; i++) {\r\n const e = session.events[i];\r\n if (e.type !== \"error\" && e.type !== \"unhandled_rejection\" && e.type !== \"console_error\") continue;\r\n const message = e.data.error?.message || e.data.message || \"\";\r\n if (!message) continue;\r\n\r\n const stack = e.data.error?.stack || \"\";\r\n const page = e.page || (typeof window !== \"undefined\" ? window.location.pathname : \"\");\r\n const fp = await computeFingerprint(message, stack, page);\r\n\r\n const precedingAction = describePrecedingAction(session.events, i);\r\n\r\n const existing = groups.get(fp);\r\n if (existing) {\r\n existing.issue.occurrences = (existing.issue.occurrences || 1) + 1;\r\n existing.issue.lastSeenAt = e.timestamp;\r\n if (existing.samples.length < MAX_CONTEXT_SAMPLES) {\r\n existing.samples.push({ timestamp: e.timestamp, precedingAction });\r\n }\r\n continue;\r\n }\r\n\r\n const firstFrame = stack.split(\"\\n\").find((l: string) => l.trim().startsWith(\"at \"))?.trim() || \"\";\r\n const issue: Issue = {\r\n id: makeIssueId(\"console-error\"),\r\n detector: \"console-error\",\r\n severity: classifyErrorSeverity(message),\r\n title: `JS error: ${message.slice(0, 70)}${message.length > 70 ? \"…\" : \"\"}`,\r\n description: firstFrame ? `${message}\\n\\nFirst frame: ${firstFrame}` : message,\r\n page,\r\n detectedAt: e.timestamp,\r\n fingerprint: fp,\r\n occurrences: 1,\r\n firstSeenAt: e.timestamp,\r\n lastSeenAt: e.timestamp,\r\n };\r\n groups.set(fp, { issue, samples: [{ timestamp: e.timestamp, precedingAction }] });\r\n }\r\n\r\n // Finalize: attach context samples + adjust title to reflect repeat count.\r\n const out: Issue[] = [];\r\n for (const g of groups.values()) {\r\n const n = g.issue.occurrences || 1;\r\n if (n > 1) {\r\n g.issue.title = `${g.issue.title} [×${n}]`;\r\n g.issue.contextSamples = g.samples;\r\n }\r\n out.push(g.issue);\r\n }\r\n return out;\r\n}\r\n\r\n/** Look back from index `i` for the most recent click/input/navigation. */\r\nfunction describePrecedingAction(events: TraceBugEvent[], i: number): string | undefined {\r\n for (let j = i - 1; j >= 0; j--) {\r\n const e = events[j];\r\n if (e.type === \"click\") {\r\n const t = e.data?.element?.text || e.data?.element?.ariaLabel || e.data?.element?.tag || \"element\";\r\n return `clicked \"${String(t).slice(0, 40)}\"`;\r\n }\r\n if (e.type === \"input\") {\r\n const n = e.data?.element?.name || e.data?.element?.id || \"field\";\r\n return `typed in ${n}`;\r\n }\r\n if (e.type === \"select_change\") return \"selected an option\";\r\n if (e.type === \"form_submit\") return \"submitted a form\";\r\n if (e.type === \"route_change\") return `navigated to ${e.data?.to || \"page\"}`;\r\n }\r\n return undefined;\r\n}\r\n\r\nexport async function detectFailedRequests(session: StoredSession | null): Promise<Issue[]> {\r\n if (!session) return [];\r\n const issues: Issue[] = [];\r\n const buffer = getNetworkFailures();\r\n\r\n for (const e of session.events) {\r\n if (e.type !== \"api_request\") continue;\r\n const req = e.data.request;\r\n if (!req) continue;\r\n const status = req.statusCode || 0;\r\n if (status >= 200 && status < 400) continue; // success\r\n if (status === 0 && req.method === \"HEAD\") continue; // skip our own probe noise\r\n\r\n // Try to find a matching response body snippet from the failure buffer.\r\n const match = buffer.find(b =>\r\n b.url === req.url &&\r\n b.method === req.method &&\r\n b.status === status &&\r\n Math.abs(b.timestamp - e.timestamp) < 5000\r\n );\r\n const snippet = match?.response ? `\\n\\nResponse: ${match.response.slice(0, 160)}` : \"\";\r\n\r\n issues.push({\r\n id: makeIssueId(\"failed-request\"),\r\n detector: \"failed-request\",\r\n severity: status >= 500 ? \"critical\" : status === 0 ? \"serious\" : \"moderate\",\r\n title: `${req.method} ${truncatePath(req.url)} → ${status === 0 ? \"Network Error\" : status}`,\r\n description: `Request failed in ${req.durationMs || 0}ms.${snippet}`,\r\n url: req.url,\r\n page: e.page || window.location.pathname,\r\n detectedAt: e.timestamp,\r\n });\r\n }\r\n\r\n return issues;\r\n}\r\n\r\nexport async function detectSlowApis(session: StoredSession | null): Promise<Issue[]> {\r\n if (!session) return [];\r\n const issues: Issue[] = [];\r\n\r\n for (const e of session.events) {\r\n if (e.type !== \"api_request\") continue;\r\n const req = e.data.request;\r\n if (!req) continue;\r\n const status = req.statusCode || 0;\r\n // Only flag *successful* slow requests — failures are surfaced separately.\r\n if (status < 200 || status >= 400) continue;\r\n const duration = req.durationMs || 0;\r\n if (duration < SLOW_API_MS) continue;\r\n\r\n issues.push({\r\n id: makeIssueId(\"slow-api\"),\r\n detector: \"slow-api\",\r\n severity: duration > 5000 ? \"serious\" : \"moderate\",\r\n title: `Slow API: ${req.method} ${truncatePath(req.url)} (${duration}ms)`,\r\n description: `This request took ${(duration / 1000).toFixed(1)}s — over the ${SLOW_API_MS / 1000}s threshold. Slow APIs are a common UX complaint and a leading cause of perceived bugs (\"the page is frozen\").`,\r\n url: req.url,\r\n page: e.page || window.location.pathname,\r\n detectedAt: e.timestamp,\r\n });\r\n }\r\n\r\n return issues;\r\n}\r\n\r\nfunction truncatePath(url: string): string {\r\n try {\r\n const u = new URL(url, window.location.origin);\r\n const p = u.pathname.length > 50 ? u.pathname.slice(0, 47) + \"…\" : u.pathname;\r\n return p;\r\n } catch {\r\n return url.length > 50 ? url.slice(0, 47) + \"…\" : url;\r\n }\r\n}\r\n\r\nfunction classifyErrorSeverity(message: string): Issue[\"severity\"] {\r\n if (/TypeError|ReferenceError|SyntaxError/i.test(message)) return \"critical\";\r\n if (/Network|fetch|failed to/i.test(message)) return \"serious\";\r\n return \"moderate\";\r\n}\r\n","// ── Accessibility detector ────────────────────────────────────────────────\r\n// Lazy-loads axe-core (~250 KB minified) only on first scan, runs the\r\n// default rule set against the live DOM, and converts axe violations into\r\n// our Issue shape. Skips TraceBug's own UI by passing `exclude: [\"#tracebug-root\"]`.\r\n//\r\n// axe-core has its own dependency graph but bundles cleanly via tsup's\r\n// dynamic-import support — the chunk only loads when scan() is called.\r\n\r\nimport { Issue } from \"../../types\";\r\nimport { coerceSeverity, makeIssueId } from \"../helpers\";\r\n\r\ntype AxeModule = typeof import(\"axe-core\");\r\ntype AxeResults = import(\"axe-core\").AxeResults;\r\n\r\nlet _axePromise: Promise<AxeModule | null> | null = null;\r\n\r\nfunction loadAxe(): Promise<AxeModule | null> {\r\n if (_axePromise) return _axePromise;\r\n _axePromise = import(\"axe-core\")\r\n .then((mod) => (mod as AxeModule & { default?: AxeModule }).default || mod)\r\n .catch((err) => {\r\n console.warn(\"[TraceBug] axe-core failed to load:\", err);\r\n return null;\r\n });\r\n return _axePromise;\r\n}\r\n\r\nexport async function detectA11yViolations(): Promise<Issue[]> {\r\n const axe = await loadAxe();\r\n if (!axe || typeof axe.run !== \"function\") return [];\r\n\r\n let results: AxeResults;\r\n try {\r\n results = await axe.run(document, {\r\n // Only WCAG-tagged rules — keeps signal-to-noise high. Best-practice\r\n // rules add ~30% more noise without proportional value for QA.\r\n runOnly: { type: \"tag\", values: [\"wcag2a\", \"wcag2aa\", \"wcag21a\", \"wcag21aa\"] },\r\n // Skip our own UI so QA isn't told their toolbar fails contrast checks.\r\n // axe accepts a context with exclude — we pass { exclude: [...] } via\r\n // the second-argument options-shaped form below to keep types loose.\r\n resultTypes: [\"violations\"],\r\n });\r\n } catch (err) {\r\n console.warn(\"[TraceBug] axe.run failed:\", err);\r\n return [];\r\n }\r\n\r\n const issues: Issue[] = [];\r\n const violations = results?.violations || [];\r\n\r\n for (const v of violations) {\r\n const nodes = v.nodes || [];\r\n // Each violation often hits multiple elements (e.g. 12 buttons missing\r\n // labels). Roll all of them into a single issue with a node count, so\r\n // the panel doesn't drown in 200 near-duplicate rows.\r\n const firstNode = nodes[0];\r\n const selector = Array.isArray(firstNode?.target) ? firstNode.target.join(\" \") : \"\";\r\n const exampleSnippet: string = (firstNode?.html || \"\").slice(0, 120);\r\n const moreSuffix = nodes.length > 1 ? ` (+ ${nodes.length - 1} more element${nodes.length === 2 ? \"\" : \"s\"})` : \"\";\r\n\r\n issues.push({\r\n id: makeIssueId(\"axe-a11y\"),\r\n detector: \"axe-a11y\",\r\n severity: coerceSeverity(v.impact),\r\n title: `${v.help || v.id}${moreSuffix}`,\r\n description: `${v.description || v.id}\\n\\nFirst element: \\`${exampleSnippet}\\``,\r\n selector: selector || undefined,\r\n helpUrl: v.helpUrl,\r\n page: window.location.pathname,\r\n detectedAt: Date.now(),\r\n });\r\n }\r\n\r\n return issues;\r\n}\r\n","// ── Frustration detectors ─────────────────────────────────────────────────\r\n// Surface user-experienced bugs that don't throw exceptions but make users\r\n// hate the product:\r\n// - Rage clicks: ≥3 clicks on the same selector within 1.5 s with no response\r\n// - Dead clicks: click with no DOM/route/network/input response within 1.5 s\r\n// - Form abandonment: input events on a form, then route_change before submit\r\n// - Error correlation: error fired ≤2.5 s after a click — pair them\r\n//\r\n// Pure analysis over the existing event log. No new tracking code, no extra\r\n// listeners.\r\n\r\nimport { Issue, StoredSession, TraceBugEvent } from \"../../types\";\r\nimport { makeIssueId } from \"../helpers\";\r\n\r\nconst RAGE_WINDOW_MS = 1500;\r\nconst RAGE_MIN_CLICKS = 3;\r\nconst DEAD_RESPONSE_WINDOW_MS = 1500;\r\nconst ABANDON_WINDOW_MS = 60_000;\r\nconst ERROR_CORRELATION_WINDOW_MS = 2500;\r\n\r\nexport async function detectFrustration(session: StoredSession | null): Promise<Issue[]> {\r\n if (!session) return [];\r\n const events = session.events;\r\n if (events.length === 0) return [];\r\n\r\n const issues: Issue[] = [];\r\n const page = session.events[0]?.page || window.location.pathname;\r\n\r\n issues.push(...detectRageClicks(events, page));\r\n issues.push(...detectDeadClicks(events, page));\r\n issues.push(...detectFormAbandonment(events, page));\r\n issues.push(...detectErrorCorrelated(events, page));\r\n\r\n return issues;\r\n}\r\n\r\n// ── Rage clicks ───────────────────────────────────────────────────────\r\n// Sliding window: ≥3 clicks on the same selector inside 1.5 s, with no\r\n// response (api_request, route_change, or input on the same form) between.\r\n\r\nfunction detectRageClicks(events: TraceBugEvent[], page: string): Issue[] {\r\n const out: Issue[] = [];\r\n const seenGroups = new Set<string>();\r\n\r\n for (let i = 0; i < events.length; i++) {\r\n const e = events[i];\r\n if (e.type !== \"click\") continue;\r\n const sel = e.data?.element?.selector || e.data?.element?.testId || \"\";\r\n if (!sel) continue;\r\n\r\n // Look ahead for sibling clicks within RAGE_WINDOW_MS on the same selector.\r\n const cluster: TraceBugEvent[] = [e];\r\n let j = i + 1;\r\n while (j < events.length) {\r\n const next = events[j];\r\n if (next.timestamp - e.timestamp > RAGE_WINDOW_MS) break;\r\n // Stop if a \"response\" event happened — not rage if anything responded.\r\n if (isResponseEvent(next)) break;\r\n if (next.type === \"click\") {\r\n const nextSel = next.data?.element?.selector || next.data?.element?.testId || \"\";\r\n if (nextSel === sel) cluster.push(next);\r\n }\r\n j++;\r\n }\r\n\r\n if (cluster.length >= RAGE_MIN_CLICKS) {\r\n const key = `${sel}@${e.timestamp}`;\r\n if (seenGroups.has(key)) continue;\r\n seenGroups.add(key);\r\n\r\n const label = clickLabel(cluster[0]);\r\n out.push({\r\n id: makeIssueId(\"frustration-rage\"),\r\n detector: \"frustration-rage\",\r\n severity: \"serious\",\r\n title: `Rage clicks on ${label} (${cluster.length}× in ${Math.round((cluster[cluster.length - 1].timestamp - cluster[0].timestamp))}ms)`,\r\n description: `User clicked the same element ${cluster.length} times within ${RAGE_WINDOW_MS}ms with no observable response (no API call, navigation, or DOM update). The element either doesn't respond to clicks or feels broken.`,\r\n selector: sel,\r\n page,\r\n detectedAt: cluster[0].timestamp,\r\n firstSeenAt: cluster[0].timestamp,\r\n lastSeenAt: cluster[cluster.length - 1].timestamp,\r\n occurrences: cluster.length,\r\n });\r\n // Skip past the cluster to avoid double-flagging.\r\n i = j - 1;\r\n }\r\n }\r\n\r\n return out;\r\n}\r\n\r\n// ── Dead clicks ───────────────────────────────────────────────────────\r\n// Click followed by no api_request, route_change, or input within 1.5 s.\r\n\r\nfunction detectDeadClicks(events: TraceBugEvent[], page: string): Issue[] {\r\n const out: Issue[] = [];\r\n // Cap output: dead-click detection is noisy on long sessions.\r\n const MAX = 5;\r\n\r\n for (let i = 0; i < events.length && out.length < MAX; i++) {\r\n const e = events[i];\r\n if (e.type !== \"click\") continue;\r\n // Skip if the next event in the same window is another click on the same\r\n // selector (likely a rage cluster — already flagged separately).\r\n let responsive = false;\r\n for (let j = i + 1; j < events.length; j++) {\r\n const next = events[j];\r\n if (next.timestamp - e.timestamp > DEAD_RESPONSE_WINDOW_MS) break;\r\n if (isResponseEvent(next)) { responsive = true; break; }\r\n }\r\n if (responsive) continue;\r\n\r\n const sel = e.data?.element?.selector || \"\";\r\n const label = clickLabel(e);\r\n out.push({\r\n id: makeIssueId(\"frustration-dead\"),\r\n detector: \"frustration-dead\",\r\n severity: \"moderate\",\r\n title: `Dead click on ${label}`,\r\n description: `Clicked but nothing happened within ${DEAD_RESPONSE_WINDOW_MS}ms (no API call, navigation, or DOM input). The element may have an unbound handler, a swallowed event, or be visually clickable but disabled.`,\r\n selector: sel,\r\n page: e.page || page,\r\n detectedAt: e.timestamp,\r\n });\r\n }\r\n\r\n return out;\r\n}\r\n\r\n// ── Form abandonment ──────────────────────────────────────────────────\r\n// Inputs on a form, then route_change before form_submit.\r\n\r\nfunction detectFormAbandonment(events: TraceBugEvent[], _page: string): Issue[] {\r\n const out: Issue[] = [];\r\n // Track per-form: has the user typed in it but not submitted?\r\n const formActivity: Record<string, { firstInputAt: number; fieldsSeen: Set<string>; lastInputAt: number; page: string }> = {};\r\n\r\n for (const e of events) {\r\n if (e.type === \"input\") {\r\n const formId = e.data?.element?.formId || e.data?.element?.formAction || \"_default\";\r\n if (!formActivity[formId]) {\r\n formActivity[formId] = { firstInputAt: e.timestamp, fieldsSeen: new Set(), lastInputAt: e.timestamp, page: e.page };\r\n }\r\n const name = e.data?.element?.name || e.data?.element?.id || \"field\";\r\n formActivity[formId].fieldsSeen.add(name);\r\n formActivity[formId].lastInputAt = e.timestamp;\r\n } else if (e.type === \"form_submit\") {\r\n const formId = e.data?.form?.id || \"_default\";\r\n delete formActivity[formId];\r\n } else if (e.type === \"route_change\") {\r\n // Any route change with active forms = abandonment if within window.\r\n for (const formId of Object.keys(formActivity)) {\r\n const a = formActivity[formId];\r\n if (e.timestamp - a.lastInputAt > ABANDON_WINDOW_MS) continue;\r\n if (a.fieldsSeen.size === 0) continue;\r\n out.push({\r\n id: makeIssueId(\"frustration-abandon\"),\r\n detector: \"frustration-abandon\",\r\n severity: \"moderate\",\r\n title: `Form abandoned on ${a.page} (${a.fieldsSeen.size} field${a.fieldsSeen.size === 1 ? \"\" : \"s\"} filled)`,\r\n description: `User typed into ${a.fieldsSeen.size} field${a.fieldsSeen.size === 1 ? \"\" : \"s\"} (${Array.from(a.fieldsSeen).slice(0, 5).join(\", \")}) and then navigated away without submitting. Likely a UX problem: the submit button is unclear, the form requires too much info, or it's failing silently.`,\r\n page: a.page,\r\n detectedAt: a.lastInputAt,\r\n firstSeenAt: a.firstInputAt,\r\n lastSeenAt: a.lastInputAt,\r\n });\r\n delete formActivity[formId];\r\n }\r\n }\r\n }\r\n\r\n return out;\r\n}\r\n\r\n// ── Error correlation ─────────────────────────────────────────────────\r\n// For each error, look back ≤2.5 s for the nearest click — likely the\r\n// triggering interaction. Tag both events together.\r\n\r\nfunction detectErrorCorrelated(events: TraceBugEvent[], page: string): Issue[] {\r\n const out: Issue[] = [];\r\n const seen = new Set<string>();\r\n\r\n for (let i = 0; i < events.length; i++) {\r\n const e = events[i];\r\n const isError = e.type === \"error\" || e.type === \"unhandled_rejection\" || e.type === \"console_error\";\r\n if (!isError) continue;\r\n\r\n // Look back for the nearest click.\r\n let click: TraceBugEvent | null = null;\r\n for (let j = i - 1; j >= 0; j--) {\r\n const prev = events[j];\r\n if (e.timestamp - prev.timestamp > ERROR_CORRELATION_WINDOW_MS) break;\r\n if (prev.type === \"click\") { click = prev; break; }\r\n }\r\n if (!click) continue;\r\n\r\n const errMsg = e.data?.error?.message || \"\";\r\n if (!errMsg) continue;\r\n const key = `${errMsg}::${click.data?.element?.selector || \"\"}`;\r\n if (seen.has(key)) continue;\r\n seen.add(key);\r\n\r\n const label = clickLabel(click);\r\n const truncMsg = errMsg.length > 60 ? errMsg.slice(0, 57) + \"…\" : errMsg;\r\n const delta = Math.round((e.timestamp - click.timestamp));\r\n out.push({\r\n id: makeIssueId(\"frustration-error-correlated\"),\r\n detector: \"frustration-error-correlated\",\r\n severity: \"critical\",\r\n title: `Click on ${label} triggered: ${truncMsg}`,\r\n description: `An error fired ${delta}ms after the user clicked ${label}. This is almost certainly the offending interaction — the click handler threw, or its async path failed.`,\r\n selector: click.data?.element?.selector,\r\n page: e.page || page,\r\n detectedAt: e.timestamp,\r\n });\r\n }\r\n\r\n return out;\r\n}\r\n\r\n// ── Helpers ────────────────────────────────────────────────────────────\r\n\r\nfunction isResponseEvent(e: TraceBugEvent): boolean {\r\n return (\r\n e.type === \"api_request\" ||\r\n e.type === \"route_change\" ||\r\n e.type === \"input\" ||\r\n e.type === \"form_submit\" ||\r\n e.type === \"select_change\"\r\n );\r\n}\r\n\r\nfunction clickLabel(e: TraceBugEvent): string {\r\n const el = e.data?.element;\r\n const raw = el?.text || el?.ariaLabel || el?.testId || el?.id || el?.tag || \"element\";\r\n const trimmed = String(raw).replace(/\\s+/g, \" \").trim();\r\n return trimmed.length > 40 ? `\"${trimmed.slice(0, 37)}…\"` : `\"${trimmed}\"`;\r\n}\r\n","// ── Scanner orchestrator ──────────────────────────────────────────────────\r\n// Runs every detector in parallel, collects results into a single in-memory\r\n// store, and exposes simple queries (getIssues, dismissIssue, fileAsBug).\r\n//\r\n// Issues live in memory only — each scan is a fresh run, results clear on\r\n// page reload. Mirrors the screenshot/video memory model.\r\n\r\nimport { Issue, IssueDetector } from \"../types\";\r\nimport { getAllSessions } from \"../storage\";\r\nimport { detectBrokenImages } from \"./detectors/broken-images\";\r\nimport { detectMixedContent } from \"./detectors/mixed-content\";\r\nimport { detectConsoleErrors, detectFailedRequests, detectSlowApis } from \"./detectors/session-data\";\r\nimport { detectA11yViolations } from \"./detectors/a11y\";\r\nimport { detectFrustration } from \"./detectors/frustration\";\r\n\r\nlet _issues: Issue[] = [];\r\nlet _scanInFlight: Promise<Issue[]> | null = null;\r\nlet _lastScanAt = 0;\r\n\r\nconst SEVERITY_ORDER: Record<Issue[\"severity\"], number> = {\r\n critical: 0,\r\n serious: 1,\r\n moderate: 2,\r\n minor: 3,\r\n};\r\n\r\nexport interface ScanResult {\r\n issues: Issue[];\r\n durationMs: number;\r\n scannedAt: number;\r\n}\r\n\r\n/**\r\n * Run every detector in parallel. Concurrent scans are coalesced — calling\r\n * scan() while one is already running returns the in-flight promise.\r\n */\r\nexport async function scan(): Promise<ScanResult> {\r\n if (_scanInFlight) {\r\n const issues = await _scanInFlight;\r\n return { issues, durationMs: 0, scannedAt: _lastScanAt };\r\n }\r\n\r\n const startedAt = Date.now();\r\n const sessions = getAllSessions().sort((a, b) => b.updatedAt - a.updatedAt);\r\n const session = sessions[0] || null;\r\n\r\n // Promise.allSettled isn't in the ES2018 target lib, so wrap each detector\r\n // in a catch that returns []. One failure doesn't block the others.\r\n const safeRun = (p: Promise<Issue[]>): Promise<Issue[]> =>\r\n p.catch((err) => {\r\n console.warn(\"[TraceBug] Detector failed:\", err);\r\n return [];\r\n });\r\n\r\n _scanInFlight = Promise.all([\r\n safeRun(detectBrokenImages()),\r\n safeRun(detectMixedContent()),\r\n safeRun(detectConsoleErrors(session)),\r\n safeRun(detectFailedRequests(session)),\r\n safeRun(detectSlowApis(session)),\r\n safeRun(detectA11yViolations()),\r\n safeRun(detectFrustration(session)),\r\n ]).then((results) => {\r\n const all: Issue[] = ([] as Issue[]).concat(...results);\r\n // Stable sort: severity first, then detection time.\r\n all.sort((a, b) => {\r\n const sev = SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity];\r\n if (sev !== 0) return sev;\r\n return a.detectedAt - b.detectedAt;\r\n });\r\n _issues = all;\r\n return all;\r\n });\r\n\r\n try {\r\n const issues = await _scanInFlight;\r\n _lastScanAt = Date.now();\r\n return { issues, durationMs: _lastScanAt - startedAt, scannedAt: _lastScanAt };\r\n } finally {\r\n _scanInFlight = null;\r\n }\r\n}\r\n\r\n/** Snapshot of current issues. Filters out dismissed by default. */\r\nexport function getIssues(options?: { includeDismissed?: boolean }): Issue[] {\r\n const includeDismissed = options?.includeDismissed ?? false;\r\n return includeDismissed ? _issues.slice() : _issues.filter(i => !i.dismissed);\r\n}\r\n\r\n/** Mark an issue as dismissed for the current session. */\r\nexport function dismissIssue(id: string): boolean {\r\n const issue = _issues.find(i => i.id === id);\r\n if (!issue) return false;\r\n issue.dismissed = true;\r\n return true;\r\n}\r\n\r\n/** Restore a previously dismissed issue. */\r\nexport function undismissIssue(id: string): boolean {\r\n const issue = _issues.find(i => i.id === id);\r\n if (!issue) return false;\r\n issue.dismissed = false;\r\n return true;\r\n}\r\n\r\n/** Clear all issues from memory. Called from destroy() and \"Clear all data\". */\r\nexport function clearIssues(): void {\r\n _issues = [];\r\n _lastScanAt = 0;\r\n}\r\n\r\n/** Aggregate counts grouped by detector, useful for the toolbar badge. */\r\nexport function getIssueCountsByDetector(): Record<IssueDetector, number> {\r\n const counts: Record<IssueDetector, number> = {\r\n \"axe-a11y\": 0,\r\n \"broken-image\": 0,\r\n \"mixed-content\": 0,\r\n \"console-error\": 0,\r\n \"slow-api\": 0,\r\n \"failed-request\": 0,\r\n \"frustration-rage\": 0,\r\n \"frustration-dead\": 0,\r\n \"frustration-abandon\": 0,\r\n \"frustration-error-correlated\": 0,\r\n };\r\n for (const i of _issues) {\r\n if (i.dismissed) continue;\r\n counts[i.detector] = (counts[i.detector] || 0) + 1;\r\n }\r\n return counts;\r\n}\r\n\r\n/** Count of non-dismissed issues at each severity. */\r\nexport function getIssueCountsBySeverity(): Record<Issue[\"severity\"], number> {\r\n const counts: Record<Issue[\"severity\"], number> = {\r\n critical: 0,\r\n serious: 0,\r\n moderate: 0,\r\n minor: 0,\r\n };\r\n for (const i of _issues) {\r\n if (i.dismissed) continue;\r\n counts[i.severity] += 1;\r\n }\r\n return counts;\r\n}\r\n\r\n/** Lookup helper for the issues panel — find by id. */\r\nexport function getIssueById(id: string): Issue | null {\r\n return _issues.find(i => i.id === id) || null;\r\n}\r\n"],"mappings":"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","names":[]} |
| "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } | ||
| var _chunkFQTNMBGVcjs = require('./chunk-FQTNMBGV.cjs'); | ||
| var _chunk4SLN5LQDcjs = require('./chunk-4SLN5LQD.cjs'); | ||
| // src/ui/issues-panel.ts | ||
| var PANEL_ID = "tracebug-issues-panel"; | ||
| var STYLE_ID = "tracebug-issues-panel-styles"; | ||
| var _isOpen = false; | ||
| var _root = null; | ||
| var SEVERITY_COLORS = { | ||
| critical: { bg: "#7f1d1d", fg: "#fee2e2", border: "#dc2626" }, | ||
| serious: { bg: "#7c2d12", fg: "#fed7aa", border: "#ea580c" }, | ||
| moderate: { bg: "#713f12", fg: "#fde68a", border: "#ca8a04" }, | ||
| minor: { bg: "#1e3a8a", fg: "#bfdbfe", border: "#2563eb" } | ||
| }; | ||
| var DETECTOR_LABELS = { | ||
| "axe-a11y": "A11y", | ||
| "broken-image": "Broken image", | ||
| "mixed-content": "Mixed content", | ||
| "console-error": "JS error", | ||
| "slow-api": "Slow API", | ||
| "failed-request": "Failed request", | ||
| "frustration-rage": "Rage clicks", | ||
| "frustration-dead": "Dead click", | ||
| "frustration-abandon": "Form abandoned", | ||
| "frustration-error-correlated": "Click \u2192 error" | ||
| }; | ||
| function isIssuesPanelOpen() { | ||
| return _isOpen; | ||
| } | ||
| async function showIssuesPanel(root, options) { | ||
| var _a; | ||
| if (_isOpen) return; | ||
| _root = root; | ||
| _injectStyles(); | ||
| _open(root, { issues: [], loading: true }); | ||
| try { | ||
| if ((_a = options == null ? void 0 : options.rescan) != null ? _a : true) { | ||
| await _chunkFQTNMBGVcjs.scan.call(void 0, ); | ||
| } | ||
| } catch (err) { | ||
| console.warn("[TraceBug] Scan failed:", err); | ||
| } | ||
| _renderBody(_chunkFQTNMBGVcjs.getIssues.call(void 0, )); | ||
| } | ||
| function _injectStyles() { | ||
| if (document.getElementById(STYLE_ID)) return; | ||
| const style = document.createElement("style"); | ||
| style.id = STYLE_ID; | ||
| style.textContent = ` | ||
| @keyframes tracebug-issue-locate-flash { | ||
| 0%, 100% { box-shadow: 0 0 0 0 rgba(99,102,241,0.0); outline: 2px solid transparent; } | ||
| 30% { box-shadow: 0 0 0 6px rgba(99,102,241,0.5); outline: 2px solid #6366F1; } | ||
| } | ||
| #${PANEL_ID}-overlay { | ||
| position: fixed !important; | ||
| inset: 0 !important; | ||
| z-index: 2147483647 !important; | ||
| background: rgba(0,0,0,0.75) !important; | ||
| backdrop-filter: blur(6px) !important; | ||
| display: flex !important; | ||
| align-items: center !important; | ||
| justify-content: center !important; | ||
| padding: 20px !important; | ||
| pointer-events: auto !important; | ||
| box-sizing: border-box !important; | ||
| } | ||
| #${PANEL_ID} { | ||
| background: var(--tb-bg-secondary, #1a1a2e) !important; | ||
| border: 1px solid var(--tb-border-hover, #3a3a5e) !important; | ||
| border-radius: var(--tb-radius-lg, 12px) !important; | ||
| width: 100% !important; | ||
| max-width: 720px !important; | ||
| max-height: 90vh !important; | ||
| display: flex !important; | ||
| flex-direction: column !important; | ||
| overflow: hidden !important; | ||
| font-family: var(--tb-font-family, system-ui, -apple-system, sans-serif) !important; | ||
| color: var(--tb-text-primary, #e0e0e0) !important; | ||
| box-sizing: border-box !important; | ||
| box-shadow: 0 20px 60px rgba(0,0,0,0.5) !important; | ||
| } | ||
| #${PANEL_ID} *, #${PANEL_ID} *::before, #${PANEL_ID} *::after { box-sizing: border-box !important; } | ||
| #${PANEL_ID} button { font-family: inherit !important; cursor: pointer !important; } | ||
| #${PANEL_ID} .tb-issue-row { | ||
| padding: 12px 14px; | ||
| border-top: 1px solid var(--tb-border, #2a2a3e); | ||
| display: flex; | ||
| gap: 12px; | ||
| align-items: flex-start; | ||
| } | ||
| #${PANEL_ID} .tb-issue-row:hover { background: var(--tb-bg-primary, #0f0f1a); } | ||
| #${PANEL_ID} .tb-sev-badge { | ||
| font-size: 10px; | ||
| font-weight: 700; | ||
| padding: 3px 7px; | ||
| border-radius: 4px; | ||
| letter-spacing: 0.4px; | ||
| text-transform: uppercase; | ||
| flex-shrink: 0; | ||
| border: 1px solid; | ||
| } | ||
| #${PANEL_ID} .tb-detector-tag { | ||
| font-size: 10px; | ||
| color: var(--tb-text-muted, #888); | ||
| background: var(--tb-bg-primary, #0f0f1a); | ||
| border: 1px solid var(--tb-border, #2a2a3e); | ||
| padding: 2px 6px; | ||
| border-radius: 4px; | ||
| flex-shrink: 0; | ||
| } | ||
| #${PANEL_ID} .tb-issue-actions { | ||
| display: flex; | ||
| gap: 6px; | ||
| flex-shrink: 0; | ||
| } | ||
| #${PANEL_ID} .tb-issue-actions button { | ||
| background: transparent; | ||
| color: var(--tb-text-secondary, #aaa); | ||
| border: 1px solid var(--tb-border, #2a2a3e); | ||
| border-radius: 4px; | ||
| padding: 4px 10px; | ||
| font-size: 11px; | ||
| transition: all 0.15s; | ||
| } | ||
| #${PANEL_ID} .tb-issue-actions button:hover { | ||
| background: var(--tb-btn-hover, #ffffff15); | ||
| color: var(--tb-text-primary, #fff); | ||
| } | ||
| #${PANEL_ID} .tb-issue-actions button[data-action="file"] { | ||
| background: var(--tb-accent, #6366F1); | ||
| color: #fff; | ||
| border-color: transparent; | ||
| } | ||
| #${PANEL_ID} .tb-issue-actions button[data-action="file"]:hover { opacity: 0.9; } | ||
| `; | ||
| document.head.appendChild(style); | ||
| } | ||
| function _open(root, state) { | ||
| var _a; | ||
| _isOpen = true; | ||
| (_a = document.getElementById(`${PANEL_ID}-overlay`)) == null ? void 0 : _a.remove(); | ||
| const overlay = document.createElement("div"); | ||
| overlay.id = `${PANEL_ID}-overlay`; | ||
| overlay.dataset.tracebug = "issues-panel-overlay"; | ||
| overlay.setAttribute("role", "dialog"); | ||
| overlay.setAttribute("aria-modal", "true"); | ||
| overlay.setAttribute("aria-label", "Page issues"); | ||
| const panel = document.createElement("div"); | ||
| panel.id = PANEL_ID; | ||
| panel.dataset.tracebug = "issues-panel"; | ||
| panel.innerHTML = _shellHtml(state); | ||
| overlay.appendChild(panel); | ||
| root.appendChild(overlay); | ||
| _wireShellHandlers(overlay, panel); | ||
| } | ||
| function _shellHtml(state) { | ||
| return ` | ||
| <div data-tb-issues="header" style="padding:16px 18px;display:flex;align-items:center;gap:12px;border-bottom:1px solid var(--tb-border, #2a2a3e)"> | ||
| <span style="font-size:20px">\u{1F50D}</span> | ||
| <div style="flex:1;min-width:0"> | ||
| <div style="font-size:16px;font-weight:700;color:var(--tb-text-primary, #fff)">Page Issues</div> | ||
| <div data-tb-issues="subtitle" style="font-size:11px;color:var(--tb-text-muted, #888);margin-top:2px">${state.loading ? "Scanning\u2026" : "No scan yet"}</div> | ||
| </div> | ||
| <button data-tb-issues="rescan" style="background:transparent;color:var(--tb-text-secondary, #aaa);border:1px solid var(--tb-border, #2a2a3e);border-radius:6px;padding:6px 12px;font-size:12px;display:flex;align-items:center;gap:6px"> | ||
| <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-3-6.7"/><polyline points="21 3 21 9 15 9"/></svg> | ||
| Rescan | ||
| </button> | ||
| <button data-tb-issues="close" aria-label="Close" style="background:none;border:none;color:var(--tb-text-muted, #888);font-size:20px;padding:4px 8px;border-radius:6px">×</button> | ||
| </div> | ||
| <div data-tb-issues="body" style="flex:1;overflow-y:auto;min-height:200px"> | ||
| ${state.loading ? _loadingHtml() : ""} | ||
| </div> | ||
| `; | ||
| } | ||
| function _loadingHtml() { | ||
| return ` | ||
| <div style="padding:48px 20px;text-align:center;color:var(--tb-text-muted, #888);font-size:13px"> | ||
| <div style="font-size:24px;margin-bottom:8px">\u{1F50D}</div> | ||
| Scanning page for issues\u2026 | ||
| <div style="font-size:11px;margin-top:6px;opacity:0.7">Loading axe-core, checking images, network calls, JS errors\u2026</div> | ||
| </div> | ||
| `; | ||
| } | ||
| function _wireShellHandlers(overlay, panel) { | ||
| const close = () => { | ||
| _isOpen = false; | ||
| overlay.remove(); | ||
| document.removeEventListener("keydown", escHandler); | ||
| }; | ||
| panel.querySelector('[data-tb-issues="close"]').addEventListener("click", close); | ||
| overlay.addEventListener("click", (e) => { | ||
| if (e.target === overlay) close(); | ||
| }); | ||
| panel.querySelector('[data-tb-issues="rescan"]').addEventListener("click", async () => { | ||
| const body = panel.querySelector('[data-tb-issues="body"]'); | ||
| body.innerHTML = _loadingHtml(); | ||
| const sub = panel.querySelector('[data-tb-issues="subtitle"]'); | ||
| sub.textContent = "Scanning\u2026"; | ||
| await _chunkFQTNMBGVcjs.scan.call(void 0, ); | ||
| _renderBody(_chunkFQTNMBGVcjs.getIssues.call(void 0, )); | ||
| }); | ||
| const escHandler = (e) => { | ||
| if (e.key === "Escape") close(); | ||
| }; | ||
| document.addEventListener("keydown", escHandler); | ||
| } | ||
| function _renderBody(issues) { | ||
| const panel = document.getElementById(PANEL_ID); | ||
| if (!panel) return; | ||
| const body = panel.querySelector('[data-tb-issues="body"]'); | ||
| const subtitle = panel.querySelector('[data-tb-issues="subtitle"]'); | ||
| if (issues.length === 0) { | ||
| subtitle.textContent = "No issues found"; | ||
| body.innerHTML = ` | ||
| <div style="padding:48px 20px;text-align:center;color:var(--tb-text-muted, #888);font-size:13px"> | ||
| <div style="font-size:32px;margin-bottom:8px">\u2713</div> | ||
| Clean scan \u2014 no issues detected on this page. | ||
| <div style="font-size:11px;margin-top:6px;opacity:0.7">Includes a11y \xB7 broken images \xB7 mixed content \xB7 JS errors \xB7 failed/slow API calls</div> | ||
| </div> | ||
| `; | ||
| return; | ||
| } | ||
| const counts = {}; | ||
| for (const i of issues) counts[i.severity] = (counts[i.severity] || 0) + 1; | ||
| const summary = ["critical", "serious", "moderate", "minor"].filter((s) => counts[s]).map((s) => `${counts[s]} ${s}`).join(" \xB7 "); | ||
| subtitle.textContent = `${issues.length} issue${issues.length === 1 ? "" : "s"} \xB7 ${summary}`; | ||
| body.innerHTML = issues.map((issue) => _issueRowHtml(issue)).join(""); | ||
| body.querySelectorAll("[data-tb-issue-id]").forEach((row) => { | ||
| var _a, _b, _c; | ||
| const id = row.dataset.tbIssueId; | ||
| const issue = issues.find((i) => i.id === id); | ||
| if (!issue) return; | ||
| (_a = row.querySelector('[data-action="locate"]')) == null ? void 0 : _a.addEventListener("click", () => _locate(issue)); | ||
| (_b = row.querySelector('[data-action="dismiss"]')) == null ? void 0 : _b.addEventListener("click", () => { | ||
| _chunkFQTNMBGVcjs.dismissIssue.call(void 0, id); | ||
| _renderBody(_chunkFQTNMBGVcjs.getIssues.call(void 0, )); | ||
| }); | ||
| (_c = row.querySelector('[data-action="file"]')) == null ? void 0 : _c.addEventListener("click", () => _fileAsBug(issue)); | ||
| }); | ||
| } | ||
| function _issueRowHtml(issue) { | ||
| const colors = SEVERITY_COLORS[issue.severity]; | ||
| const detectorLabel = DETECTOR_LABELS[issue.detector]; | ||
| const desc = issue.description.length > 240 ? issue.description.slice(0, 237) + "\u2026" : issue.description; | ||
| const repeats = (issue.occurrences || 1) > 1; | ||
| const samplesHtml = repeats && issue.contextSamples && issue.contextSamples.length > 0 ? `<details style="margin-top:6px;font-size:11px"> | ||
| <summary style="cursor:pointer;color:var(--tb-text-muted, #888)"> | ||
| View all ${issue.occurrences} contexts | ||
| </summary> | ||
| <ol style="margin:6px 0 0 20px;padding:0;color:var(--tb-text-secondary, #aaa);line-height:1.5"> | ||
| ${issue.contextSamples.map((s) => ` | ||
| <li>${new Date(s.timestamp).toLocaleTimeString()}${s.precedingAction ? ` \xB7 ${_chunk4SLN5LQDcjs.escapeHtml.call(void 0, s.precedingAction)}` : ""}</li> | ||
| `).join("")} | ||
| </ol> | ||
| </details>` : ""; | ||
| return ` | ||
| <div class="tb-issue-row" data-tb-issue-id="${issue.id}"> | ||
| <span class="tb-sev-badge" style="background:${colors.bg};color:${colors.fg};border-color:${colors.border}">${issue.severity}</span> | ||
| <div style="flex:1;min-width:0"> | ||
| <div style="display:flex;align-items:center;gap:6px;margin-bottom:4px;flex-wrap:wrap"> | ||
| <span class="tb-detector-tag">${detectorLabel}</span> | ||
| <span style="font-size:13px;color:var(--tb-text-primary, #e0e0e0);font-weight:500">${_chunk4SLN5LQDcjs.escapeHtml.call(void 0, issue.title)}</span> | ||
| </div> | ||
| <div style="font-size:11px;color:var(--tb-text-muted, #888);line-height:1.5;white-space:pre-wrap">${_chunk4SLN5LQDcjs.escapeHtml.call(void 0, desc)}</div> | ||
| ${issue.helpUrl ? `<a href="${issue.helpUrl}" target="_blank" rel="noopener noreferrer" style="font-size:11px;color:var(--tb-accent, #6366F1);margin-top:4px;display:inline-block">Learn more \u2192</a>` : ""} | ||
| ${samplesHtml} | ||
| </div> | ||
| <div class="tb-issue-actions"> | ||
| ${issue.selector ? `<button data-action="locate" title="Highlight on page">\u{1F4CD} Locate</button>` : ""} | ||
| <button data-action="file" title="File as bug ticket">File ticket</button> | ||
| <button data-action="dismiss" title="Dismiss for this session">Dismiss</button> | ||
| </div> | ||
| </div> | ||
| `; | ||
| } | ||
| function _locate(issue) { | ||
| var _a; | ||
| if (!issue.selector) return; | ||
| let el = null; | ||
| try { | ||
| el = document.querySelector(issue.selector); | ||
| } catch (e) { | ||
| el = null; | ||
| } | ||
| if (!el) return; | ||
| (_a = document.getElementById(`${PANEL_ID}-overlay`)) == null ? void 0 : _a.remove(); | ||
| _isOpen = false; | ||
| el.scrollIntoView({ behavior: "smooth", block: "center" }); | ||
| const htmlEl = el; | ||
| const prevOutline = htmlEl.style.outline; | ||
| const prevTransition = htmlEl.style.transition; | ||
| htmlEl.style.transition = "outline 0.2s, box-shadow 0.2s"; | ||
| htmlEl.style.outline = "3px solid #6366F1"; | ||
| htmlEl.style.boxShadow = "0 0 0 6px rgba(99,102,241,0.35)"; | ||
| setTimeout(() => { | ||
| htmlEl.style.outline = prevOutline; | ||
| htmlEl.style.boxShadow = ""; | ||
| htmlEl.style.transition = prevTransition; | ||
| }, 2400); | ||
| } | ||
| function _fileAsBug(issue) { | ||
| var _a; | ||
| const root = _root || document.getElementById("tracebug-root"); | ||
| if (!root) return; | ||
| (_a = document.getElementById(`${PANEL_ID}-overlay`)) == null ? void 0 : _a.remove(); | ||
| _isOpen = false; | ||
| Promise.resolve().then(() => _interopRequireWildcard(require("./quick-bug-LFLO2PSW.cjs"))).then((m) => { | ||
| m.showQuickBugCapture(root, { | ||
| prefilledTitle: issue.title, | ||
| prefilledDescription: _bugDescriptionFromIssue(issue) | ||
| }).catch(() => { | ||
| }); | ||
| }); | ||
| } | ||
| function _bugDescriptionFromIssue(issue) { | ||
| const lines = []; | ||
| lines.push(`> Detected by TraceBug auto-scanner: **${DETECTOR_LABELS[issue.detector]}** (${issue.severity})`); | ||
| lines.push(""); | ||
| lines.push(issue.description); | ||
| if (issue.selector) lines.push(` | ||
| **Selector:** \`${issue.selector}\``); | ||
| if (issue.url) lines.push(`**URL:** \`${issue.url}\``); | ||
| if (issue.helpUrl) lines.push(`**Reference:** ${issue.helpUrl}`); | ||
| return lines.join("\n"); | ||
| } | ||
| exports.isIssuesPanelOpen = isIssuesPanelOpen; exports.showIssuesPanel = showIssuesPanel; | ||
| //# sourceMappingURL=issues-panel-LXTIYXNM.cjs.map |
| {"version":3,"sources":["d:\\Project\\TraceBug-ai\\dist\\issues-panel-LXTIYXNM.cjs"],"names":[],"mappings":"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E,QAAQ,CAAC;AAChB,EAAE,CAAC;AACH,EAAE,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC;AAClC;AACA,SAAS,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE;AAC5B,EAAE,IAAI,EAAE;AACR,EAAE,QAAQ,EAAE,IAAI;AAChB,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAC,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,8GAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAA;AACA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA;AACA;AACA;AACA;AACA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA,oBAAA;AACA;AACA;AACA,WAAA;AACA,iBAAA;AACA,WAAA;AACA;AACA,iBAAA;AACA,EAAA;AACA,gDAAA;AACA,mDAAA;AACA;AACA;AACA,wCAAA;AACA,6FAAA;AACA;AACA,0GAAA;AACA,QAAA;AACA,QAAA;AACA;AACA;AACA,QAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,gBAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA","file":"D:\\Project\\TraceBug-ai\\dist\\issues-panel-LXTIYXNM.cjs","sourcesContent":[null]} |
| import { | ||
| dismissIssue, | ||
| getIssues, | ||
| scan | ||
| } from "./chunk-URDH7OBN.js"; | ||
| import { | ||
| escapeHtml | ||
| } from "./chunk-LQO44M5L.js"; | ||
| // src/ui/issues-panel.ts | ||
| var PANEL_ID = "tracebug-issues-panel"; | ||
| var STYLE_ID = "tracebug-issues-panel-styles"; | ||
| var _isOpen = false; | ||
| var _root = null; | ||
| var SEVERITY_COLORS = { | ||
| critical: { bg: "#7f1d1d", fg: "#fee2e2", border: "#dc2626" }, | ||
| serious: { bg: "#7c2d12", fg: "#fed7aa", border: "#ea580c" }, | ||
| moderate: { bg: "#713f12", fg: "#fde68a", border: "#ca8a04" }, | ||
| minor: { bg: "#1e3a8a", fg: "#bfdbfe", border: "#2563eb" } | ||
| }; | ||
| var DETECTOR_LABELS = { | ||
| "axe-a11y": "A11y", | ||
| "broken-image": "Broken image", | ||
| "mixed-content": "Mixed content", | ||
| "console-error": "JS error", | ||
| "slow-api": "Slow API", | ||
| "failed-request": "Failed request", | ||
| "frustration-rage": "Rage clicks", | ||
| "frustration-dead": "Dead click", | ||
| "frustration-abandon": "Form abandoned", | ||
| "frustration-error-correlated": "Click \u2192 error" | ||
| }; | ||
| function isIssuesPanelOpen() { | ||
| return _isOpen; | ||
| } | ||
| async function showIssuesPanel(root, options) { | ||
| var _a; | ||
| if (_isOpen) return; | ||
| _root = root; | ||
| _injectStyles(); | ||
| _open(root, { issues: [], loading: true }); | ||
| try { | ||
| if ((_a = options == null ? void 0 : options.rescan) != null ? _a : true) { | ||
| await scan(); | ||
| } | ||
| } catch (err) { | ||
| console.warn("[TraceBug] Scan failed:", err); | ||
| } | ||
| _renderBody(getIssues()); | ||
| } | ||
| function _injectStyles() { | ||
| if (document.getElementById(STYLE_ID)) return; | ||
| const style = document.createElement("style"); | ||
| style.id = STYLE_ID; | ||
| style.textContent = ` | ||
| @keyframes tracebug-issue-locate-flash { | ||
| 0%, 100% { box-shadow: 0 0 0 0 rgba(99,102,241,0.0); outline: 2px solid transparent; } | ||
| 30% { box-shadow: 0 0 0 6px rgba(99,102,241,0.5); outline: 2px solid #6366F1; } | ||
| } | ||
| #${PANEL_ID}-overlay { | ||
| position: fixed !important; | ||
| inset: 0 !important; | ||
| z-index: 2147483647 !important; | ||
| background: rgba(0,0,0,0.75) !important; | ||
| backdrop-filter: blur(6px) !important; | ||
| display: flex !important; | ||
| align-items: center !important; | ||
| justify-content: center !important; | ||
| padding: 20px !important; | ||
| pointer-events: auto !important; | ||
| box-sizing: border-box !important; | ||
| } | ||
| #${PANEL_ID} { | ||
| background: var(--tb-bg-secondary, #1a1a2e) !important; | ||
| border: 1px solid var(--tb-border-hover, #3a3a5e) !important; | ||
| border-radius: var(--tb-radius-lg, 12px) !important; | ||
| width: 100% !important; | ||
| max-width: 720px !important; | ||
| max-height: 90vh !important; | ||
| display: flex !important; | ||
| flex-direction: column !important; | ||
| overflow: hidden !important; | ||
| font-family: var(--tb-font-family, system-ui, -apple-system, sans-serif) !important; | ||
| color: var(--tb-text-primary, #e0e0e0) !important; | ||
| box-sizing: border-box !important; | ||
| box-shadow: 0 20px 60px rgba(0,0,0,0.5) !important; | ||
| } | ||
| #${PANEL_ID} *, #${PANEL_ID} *::before, #${PANEL_ID} *::after { box-sizing: border-box !important; } | ||
| #${PANEL_ID} button { font-family: inherit !important; cursor: pointer !important; } | ||
| #${PANEL_ID} .tb-issue-row { | ||
| padding: 12px 14px; | ||
| border-top: 1px solid var(--tb-border, #2a2a3e); | ||
| display: flex; | ||
| gap: 12px; | ||
| align-items: flex-start; | ||
| } | ||
| #${PANEL_ID} .tb-issue-row:hover { background: var(--tb-bg-primary, #0f0f1a); } | ||
| #${PANEL_ID} .tb-sev-badge { | ||
| font-size: 10px; | ||
| font-weight: 700; | ||
| padding: 3px 7px; | ||
| border-radius: 4px; | ||
| letter-spacing: 0.4px; | ||
| text-transform: uppercase; | ||
| flex-shrink: 0; | ||
| border: 1px solid; | ||
| } | ||
| #${PANEL_ID} .tb-detector-tag { | ||
| font-size: 10px; | ||
| color: var(--tb-text-muted, #888); | ||
| background: var(--tb-bg-primary, #0f0f1a); | ||
| border: 1px solid var(--tb-border, #2a2a3e); | ||
| padding: 2px 6px; | ||
| border-radius: 4px; | ||
| flex-shrink: 0; | ||
| } | ||
| #${PANEL_ID} .tb-issue-actions { | ||
| display: flex; | ||
| gap: 6px; | ||
| flex-shrink: 0; | ||
| } | ||
| #${PANEL_ID} .tb-issue-actions button { | ||
| background: transparent; | ||
| color: var(--tb-text-secondary, #aaa); | ||
| border: 1px solid var(--tb-border, #2a2a3e); | ||
| border-radius: 4px; | ||
| padding: 4px 10px; | ||
| font-size: 11px; | ||
| transition: all 0.15s; | ||
| } | ||
| #${PANEL_ID} .tb-issue-actions button:hover { | ||
| background: var(--tb-btn-hover, #ffffff15); | ||
| color: var(--tb-text-primary, #fff); | ||
| } | ||
| #${PANEL_ID} .tb-issue-actions button[data-action="file"] { | ||
| background: var(--tb-accent, #6366F1); | ||
| color: #fff; | ||
| border-color: transparent; | ||
| } | ||
| #${PANEL_ID} .tb-issue-actions button[data-action="file"]:hover { opacity: 0.9; } | ||
| `; | ||
| document.head.appendChild(style); | ||
| } | ||
| function _open(root, state) { | ||
| var _a; | ||
| _isOpen = true; | ||
| (_a = document.getElementById(`${PANEL_ID}-overlay`)) == null ? void 0 : _a.remove(); | ||
| const overlay = document.createElement("div"); | ||
| overlay.id = `${PANEL_ID}-overlay`; | ||
| overlay.dataset.tracebug = "issues-panel-overlay"; | ||
| overlay.setAttribute("role", "dialog"); | ||
| overlay.setAttribute("aria-modal", "true"); | ||
| overlay.setAttribute("aria-label", "Page issues"); | ||
| const panel = document.createElement("div"); | ||
| panel.id = PANEL_ID; | ||
| panel.dataset.tracebug = "issues-panel"; | ||
| panel.innerHTML = _shellHtml(state); | ||
| overlay.appendChild(panel); | ||
| root.appendChild(overlay); | ||
| _wireShellHandlers(overlay, panel); | ||
| } | ||
| function _shellHtml(state) { | ||
| return ` | ||
| <div data-tb-issues="header" style="padding:16px 18px;display:flex;align-items:center;gap:12px;border-bottom:1px solid var(--tb-border, #2a2a3e)"> | ||
| <span style="font-size:20px">\u{1F50D}</span> | ||
| <div style="flex:1;min-width:0"> | ||
| <div style="font-size:16px;font-weight:700;color:var(--tb-text-primary, #fff)">Page Issues</div> | ||
| <div data-tb-issues="subtitle" style="font-size:11px;color:var(--tb-text-muted, #888);margin-top:2px">${state.loading ? "Scanning\u2026" : "No scan yet"}</div> | ||
| </div> | ||
| <button data-tb-issues="rescan" style="background:transparent;color:var(--tb-text-secondary, #aaa);border:1px solid var(--tb-border, #2a2a3e);border-radius:6px;padding:6px 12px;font-size:12px;display:flex;align-items:center;gap:6px"> | ||
| <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-3-6.7"/><polyline points="21 3 21 9 15 9"/></svg> | ||
| Rescan | ||
| </button> | ||
| <button data-tb-issues="close" aria-label="Close" style="background:none;border:none;color:var(--tb-text-muted, #888);font-size:20px;padding:4px 8px;border-radius:6px">×</button> | ||
| </div> | ||
| <div data-tb-issues="body" style="flex:1;overflow-y:auto;min-height:200px"> | ||
| ${state.loading ? _loadingHtml() : ""} | ||
| </div> | ||
| `; | ||
| } | ||
| function _loadingHtml() { | ||
| return ` | ||
| <div style="padding:48px 20px;text-align:center;color:var(--tb-text-muted, #888);font-size:13px"> | ||
| <div style="font-size:24px;margin-bottom:8px">\u{1F50D}</div> | ||
| Scanning page for issues\u2026 | ||
| <div style="font-size:11px;margin-top:6px;opacity:0.7">Loading axe-core, checking images, network calls, JS errors\u2026</div> | ||
| </div> | ||
| `; | ||
| } | ||
| function _wireShellHandlers(overlay, panel) { | ||
| const close = () => { | ||
| _isOpen = false; | ||
| overlay.remove(); | ||
| document.removeEventListener("keydown", escHandler); | ||
| }; | ||
| panel.querySelector('[data-tb-issues="close"]').addEventListener("click", close); | ||
| overlay.addEventListener("click", (e) => { | ||
| if (e.target === overlay) close(); | ||
| }); | ||
| panel.querySelector('[data-tb-issues="rescan"]').addEventListener("click", async () => { | ||
| const body = panel.querySelector('[data-tb-issues="body"]'); | ||
| body.innerHTML = _loadingHtml(); | ||
| const sub = panel.querySelector('[data-tb-issues="subtitle"]'); | ||
| sub.textContent = "Scanning\u2026"; | ||
| await scan(); | ||
| _renderBody(getIssues()); | ||
| }); | ||
| const escHandler = (e) => { | ||
| if (e.key === "Escape") close(); | ||
| }; | ||
| document.addEventListener("keydown", escHandler); | ||
| } | ||
| function _renderBody(issues) { | ||
| const panel = document.getElementById(PANEL_ID); | ||
| if (!panel) return; | ||
| const body = panel.querySelector('[data-tb-issues="body"]'); | ||
| const subtitle = panel.querySelector('[data-tb-issues="subtitle"]'); | ||
| if (issues.length === 0) { | ||
| subtitle.textContent = "No issues found"; | ||
| body.innerHTML = ` | ||
| <div style="padding:48px 20px;text-align:center;color:var(--tb-text-muted, #888);font-size:13px"> | ||
| <div style="font-size:32px;margin-bottom:8px">\u2713</div> | ||
| Clean scan \u2014 no issues detected on this page. | ||
| <div style="font-size:11px;margin-top:6px;opacity:0.7">Includes a11y \xB7 broken images \xB7 mixed content \xB7 JS errors \xB7 failed/slow API calls</div> | ||
| </div> | ||
| `; | ||
| return; | ||
| } | ||
| const counts = {}; | ||
| for (const i of issues) counts[i.severity] = (counts[i.severity] || 0) + 1; | ||
| const summary = ["critical", "serious", "moderate", "minor"].filter((s) => counts[s]).map((s) => `${counts[s]} ${s}`).join(" \xB7 "); | ||
| subtitle.textContent = `${issues.length} issue${issues.length === 1 ? "" : "s"} \xB7 ${summary}`; | ||
| body.innerHTML = issues.map((issue) => _issueRowHtml(issue)).join(""); | ||
| body.querySelectorAll("[data-tb-issue-id]").forEach((row) => { | ||
| var _a, _b, _c; | ||
| const id = row.dataset.tbIssueId; | ||
| const issue = issues.find((i) => i.id === id); | ||
| if (!issue) return; | ||
| (_a = row.querySelector('[data-action="locate"]')) == null ? void 0 : _a.addEventListener("click", () => _locate(issue)); | ||
| (_b = row.querySelector('[data-action="dismiss"]')) == null ? void 0 : _b.addEventListener("click", () => { | ||
| dismissIssue(id); | ||
| _renderBody(getIssues()); | ||
| }); | ||
| (_c = row.querySelector('[data-action="file"]')) == null ? void 0 : _c.addEventListener("click", () => _fileAsBug(issue)); | ||
| }); | ||
| } | ||
| function _issueRowHtml(issue) { | ||
| const colors = SEVERITY_COLORS[issue.severity]; | ||
| const detectorLabel = DETECTOR_LABELS[issue.detector]; | ||
| const desc = issue.description.length > 240 ? issue.description.slice(0, 237) + "\u2026" : issue.description; | ||
| const repeats = (issue.occurrences || 1) > 1; | ||
| const samplesHtml = repeats && issue.contextSamples && issue.contextSamples.length > 0 ? `<details style="margin-top:6px;font-size:11px"> | ||
| <summary style="cursor:pointer;color:var(--tb-text-muted, #888)"> | ||
| View all ${issue.occurrences} contexts | ||
| </summary> | ||
| <ol style="margin:6px 0 0 20px;padding:0;color:var(--tb-text-secondary, #aaa);line-height:1.5"> | ||
| ${issue.contextSamples.map((s) => ` | ||
| <li>${new Date(s.timestamp).toLocaleTimeString()}${s.precedingAction ? ` \xB7 ${escapeHtml(s.precedingAction)}` : ""}</li> | ||
| `).join("")} | ||
| </ol> | ||
| </details>` : ""; | ||
| return ` | ||
| <div class="tb-issue-row" data-tb-issue-id="${issue.id}"> | ||
| <span class="tb-sev-badge" style="background:${colors.bg};color:${colors.fg};border-color:${colors.border}">${issue.severity}</span> | ||
| <div style="flex:1;min-width:0"> | ||
| <div style="display:flex;align-items:center;gap:6px;margin-bottom:4px;flex-wrap:wrap"> | ||
| <span class="tb-detector-tag">${detectorLabel}</span> | ||
| <span style="font-size:13px;color:var(--tb-text-primary, #e0e0e0);font-weight:500">${escapeHtml(issue.title)}</span> | ||
| </div> | ||
| <div style="font-size:11px;color:var(--tb-text-muted, #888);line-height:1.5;white-space:pre-wrap">${escapeHtml(desc)}</div> | ||
| ${issue.helpUrl ? `<a href="${issue.helpUrl}" target="_blank" rel="noopener noreferrer" style="font-size:11px;color:var(--tb-accent, #6366F1);margin-top:4px;display:inline-block">Learn more \u2192</a>` : ""} | ||
| ${samplesHtml} | ||
| </div> | ||
| <div class="tb-issue-actions"> | ||
| ${issue.selector ? `<button data-action="locate" title="Highlight on page">\u{1F4CD} Locate</button>` : ""} | ||
| <button data-action="file" title="File as bug ticket">File ticket</button> | ||
| <button data-action="dismiss" title="Dismiss for this session">Dismiss</button> | ||
| </div> | ||
| </div> | ||
| `; | ||
| } | ||
| function _locate(issue) { | ||
| var _a; | ||
| if (!issue.selector) return; | ||
| let el = null; | ||
| try { | ||
| el = document.querySelector(issue.selector); | ||
| } catch (e) { | ||
| el = null; | ||
| } | ||
| if (!el) return; | ||
| (_a = document.getElementById(`${PANEL_ID}-overlay`)) == null ? void 0 : _a.remove(); | ||
| _isOpen = false; | ||
| el.scrollIntoView({ behavior: "smooth", block: "center" }); | ||
| const htmlEl = el; | ||
| const prevOutline = htmlEl.style.outline; | ||
| const prevTransition = htmlEl.style.transition; | ||
| htmlEl.style.transition = "outline 0.2s, box-shadow 0.2s"; | ||
| htmlEl.style.outline = "3px solid #6366F1"; | ||
| htmlEl.style.boxShadow = "0 0 0 6px rgba(99,102,241,0.35)"; | ||
| setTimeout(() => { | ||
| htmlEl.style.outline = prevOutline; | ||
| htmlEl.style.boxShadow = ""; | ||
| htmlEl.style.transition = prevTransition; | ||
| }, 2400); | ||
| } | ||
| function _fileAsBug(issue) { | ||
| var _a; | ||
| const root = _root || document.getElementById("tracebug-root"); | ||
| if (!root) return; | ||
| (_a = document.getElementById(`${PANEL_ID}-overlay`)) == null ? void 0 : _a.remove(); | ||
| _isOpen = false; | ||
| import("./quick-bug-MX3Y3FR3.js").then((m) => { | ||
| m.showQuickBugCapture(root, { | ||
| prefilledTitle: issue.title, | ||
| prefilledDescription: _bugDescriptionFromIssue(issue) | ||
| }).catch(() => { | ||
| }); | ||
| }); | ||
| } | ||
| function _bugDescriptionFromIssue(issue) { | ||
| const lines = []; | ||
| lines.push(`> Detected by TraceBug auto-scanner: **${DETECTOR_LABELS[issue.detector]}** (${issue.severity})`); | ||
| lines.push(""); | ||
| lines.push(issue.description); | ||
| if (issue.selector) lines.push(` | ||
| **Selector:** \`${issue.selector}\``); | ||
| if (issue.url) lines.push(`**URL:** \`${issue.url}\``); | ||
| if (issue.helpUrl) lines.push(`**Reference:** ${issue.helpUrl}`); | ||
| return lines.join("\n"); | ||
| } | ||
| export { | ||
| isIssuesPanelOpen, | ||
| showIssuesPanel | ||
| }; | ||
| //# sourceMappingURL=issues-panel-SNP2F6VR.js.map |
| {"version":3,"sources":["../src/ui/issues-panel.ts"],"sourcesContent":["// ── Issues Panel ──────────────────────────────────────────────────────────\r\n// Modal that lists scanner findings grouped by severity. Each row offers\r\n// \"Locate\" (flash the offending element on the page) and \"File ticket\"\r\n// (open the Quick Bug modal pre-filled with the issue's context).\r\n//\r\n// Styles are injected in <head> with !important so host-page CSS resets\r\n// (Tailwind preflight, Bootstrap, etc.) can't squish the layout.\r\n\r\nimport { Issue } from \"../types\";\r\nimport { dismissIssue, getIssues, scan } from \"../scanner\";\r\nimport { escapeHtml } from \"./helpers\";\r\n\r\nconst PANEL_ID = \"tracebug-issues-panel\";\r\nconst STYLE_ID = \"tracebug-issues-panel-styles\";\r\n\r\nlet _isOpen = false;\r\nlet _root: HTMLElement | null = null;\r\n\r\nconst SEVERITY_COLORS: Record<Issue[\"severity\"], { bg: string; fg: string; border: string }> = {\r\n critical: { bg: \"#7f1d1d\", fg: \"#fee2e2\", border: \"#dc2626\" },\r\n serious: { bg: \"#7c2d12\", fg: \"#fed7aa\", border: \"#ea580c\" },\r\n moderate: { bg: \"#713f12\", fg: \"#fde68a\", border: \"#ca8a04\" },\r\n minor: { bg: \"#1e3a8a\", fg: \"#bfdbfe\", border: \"#2563eb\" },\r\n};\r\n\r\nconst DETECTOR_LABELS: Record<Issue[\"detector\"], string> = {\r\n \"axe-a11y\": \"A11y\",\r\n \"broken-image\": \"Broken image\",\r\n \"mixed-content\": \"Mixed content\",\r\n \"console-error\": \"JS error\",\r\n \"slow-api\": \"Slow API\",\r\n \"failed-request\": \"Failed request\",\r\n \"frustration-rage\": \"Rage clicks\",\r\n \"frustration-dead\": \"Dead click\",\r\n \"frustration-abandon\": \"Form abandoned\",\r\n \"frustration-error-correlated\": \"Click → error\",\r\n};\r\n\r\nexport function isIssuesPanelOpen(): boolean {\r\n return _isOpen;\r\n}\r\n\r\n/**\r\n * Run a scan (or use cached results) and open the panel. The scan promise\r\n * resolves before we render so the panel never flashes empty.\r\n */\r\nexport async function showIssuesPanel(\r\n root: HTMLElement,\r\n options?: { rescan?: boolean }\r\n): Promise<void> {\r\n if (_isOpen) return;\r\n _root = root;\r\n _injectStyles();\r\n\r\n // Render shell with a loading state so the user gets immediate feedback.\r\n _open(root, { issues: [], loading: true });\r\n try {\r\n if (options?.rescan ?? true) {\r\n await scan();\r\n }\r\n } catch (err) {\r\n console.warn(\"[TraceBug] Scan failed:\", err);\r\n }\r\n _renderBody(getIssues());\r\n}\r\n\r\nfunction _injectStyles(): void {\r\n if (document.getElementById(STYLE_ID)) return;\r\n const style = document.createElement(\"style\");\r\n style.id = STYLE_ID;\r\n style.textContent = `\r\n @keyframes tracebug-issue-locate-flash {\r\n 0%, 100% { box-shadow: 0 0 0 0 rgba(99,102,241,0.0); outline: 2px solid transparent; }\r\n 30% { box-shadow: 0 0 0 6px rgba(99,102,241,0.5); outline: 2px solid #6366F1; }\r\n }\r\n #${PANEL_ID}-overlay {\r\n position: fixed !important;\r\n inset: 0 !important;\r\n z-index: 2147483647 !important;\r\n background: rgba(0,0,0,0.75) !important;\r\n backdrop-filter: blur(6px) !important;\r\n display: flex !important;\r\n align-items: center !important;\r\n justify-content: center !important;\r\n padding: 20px !important;\r\n pointer-events: auto !important;\r\n box-sizing: border-box !important;\r\n }\r\n #${PANEL_ID} {\r\n background: var(--tb-bg-secondary, #1a1a2e) !important;\r\n border: 1px solid var(--tb-border-hover, #3a3a5e) !important;\r\n border-radius: var(--tb-radius-lg, 12px) !important;\r\n width: 100% !important;\r\n max-width: 720px !important;\r\n max-height: 90vh !important;\r\n display: flex !important;\r\n flex-direction: column !important;\r\n overflow: hidden !important;\r\n font-family: var(--tb-font-family, system-ui, -apple-system, sans-serif) !important;\r\n color: var(--tb-text-primary, #e0e0e0) !important;\r\n box-sizing: border-box !important;\r\n box-shadow: 0 20px 60px rgba(0,0,0,0.5) !important;\r\n }\r\n #${PANEL_ID} *, #${PANEL_ID} *::before, #${PANEL_ID} *::after { box-sizing: border-box !important; }\r\n #${PANEL_ID} button { font-family: inherit !important; cursor: pointer !important; }\r\n #${PANEL_ID} .tb-issue-row {\r\n padding: 12px 14px;\r\n border-top: 1px solid var(--tb-border, #2a2a3e);\r\n display: flex;\r\n gap: 12px;\r\n align-items: flex-start;\r\n }\r\n #${PANEL_ID} .tb-issue-row:hover { background: var(--tb-bg-primary, #0f0f1a); }\r\n #${PANEL_ID} .tb-sev-badge {\r\n font-size: 10px;\r\n font-weight: 700;\r\n padding: 3px 7px;\r\n border-radius: 4px;\r\n letter-spacing: 0.4px;\r\n text-transform: uppercase;\r\n flex-shrink: 0;\r\n border: 1px solid;\r\n }\r\n #${PANEL_ID} .tb-detector-tag {\r\n font-size: 10px;\r\n color: var(--tb-text-muted, #888);\r\n background: var(--tb-bg-primary, #0f0f1a);\r\n border: 1px solid var(--tb-border, #2a2a3e);\r\n padding: 2px 6px;\r\n border-radius: 4px;\r\n flex-shrink: 0;\r\n }\r\n #${PANEL_ID} .tb-issue-actions {\r\n display: flex;\r\n gap: 6px;\r\n flex-shrink: 0;\r\n }\r\n #${PANEL_ID} .tb-issue-actions button {\r\n background: transparent;\r\n color: var(--tb-text-secondary, #aaa);\r\n border: 1px solid var(--tb-border, #2a2a3e);\r\n border-radius: 4px;\r\n padding: 4px 10px;\r\n font-size: 11px;\r\n transition: all 0.15s;\r\n }\r\n #${PANEL_ID} .tb-issue-actions button:hover {\r\n background: var(--tb-btn-hover, #ffffff15);\r\n color: var(--tb-text-primary, #fff);\r\n }\r\n #${PANEL_ID} .tb-issue-actions button[data-action=\"file\"] {\r\n background: var(--tb-accent, #6366F1);\r\n color: #fff;\r\n border-color: transparent;\r\n }\r\n #${PANEL_ID} .tb-issue-actions button[data-action=\"file\"]:hover { opacity: 0.9; }\r\n `;\r\n document.head.appendChild(style);\r\n}\r\n\r\nfunction _open(root: HTMLElement, state: { issues: Issue[]; loading: boolean }): void {\r\n _isOpen = true;\r\n // Remove any prior overlay (defensive — should not happen).\r\n document.getElementById(`${PANEL_ID}-overlay`)?.remove();\r\n\r\n const overlay = document.createElement(\"div\");\r\n overlay.id = `${PANEL_ID}-overlay`;\r\n overlay.dataset.tracebug = \"issues-panel-overlay\";\r\n overlay.setAttribute(\"role\", \"dialog\");\r\n overlay.setAttribute(\"aria-modal\", \"true\");\r\n overlay.setAttribute(\"aria-label\", \"Page issues\");\r\n\r\n const panel = document.createElement(\"div\");\r\n panel.id = PANEL_ID;\r\n panel.dataset.tracebug = \"issues-panel\";\r\n\r\n panel.innerHTML = _shellHtml(state);\r\n\r\n overlay.appendChild(panel);\r\n root.appendChild(overlay);\r\n\r\n _wireShellHandlers(overlay, panel);\r\n}\r\n\r\nfunction _shellHtml(state: { issues: Issue[]; loading: boolean }): string {\r\n return `\r\n <div data-tb-issues=\"header\" style=\"padding:16px 18px;display:flex;align-items:center;gap:12px;border-bottom:1px solid var(--tb-border, #2a2a3e)\">\r\n <span style=\"font-size:20px\">🔍</span>\r\n <div style=\"flex:1;min-width:0\">\r\n <div style=\"font-size:16px;font-weight:700;color:var(--tb-text-primary, #fff)\">Page Issues</div>\r\n <div data-tb-issues=\"subtitle\" style=\"font-size:11px;color:var(--tb-text-muted, #888);margin-top:2px\">${state.loading ? \"Scanning…\" : \"No scan yet\"}</div>\r\n </div>\r\n <button data-tb-issues=\"rescan\" style=\"background:transparent;color:var(--tb-text-secondary, #aaa);border:1px solid var(--tb-border, #2a2a3e);border-radius:6px;padding:6px 12px;font-size:12px;display:flex;align-items:center;gap:6px\">\r\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 12a9 9 0 1 1-3-6.7\"/><polyline points=\"21 3 21 9 15 9\"/></svg>\r\n Rescan\r\n </button>\r\n <button data-tb-issues=\"close\" aria-label=\"Close\" style=\"background:none;border:none;color:var(--tb-text-muted, #888);font-size:20px;padding:4px 8px;border-radius:6px\">×</button>\r\n </div>\r\n <div data-tb-issues=\"body\" style=\"flex:1;overflow-y:auto;min-height:200px\">\r\n ${state.loading ? _loadingHtml() : \"\"}\r\n </div>\r\n `;\r\n}\r\n\r\nfunction _loadingHtml(): string {\r\n return `\r\n <div style=\"padding:48px 20px;text-align:center;color:var(--tb-text-muted, #888);font-size:13px\">\r\n <div style=\"font-size:24px;margin-bottom:8px\">🔍</div>\r\n Scanning page for issues…\r\n <div style=\"font-size:11px;margin-top:6px;opacity:0.7\">Loading axe-core, checking images, network calls, JS errors…</div>\r\n </div>\r\n `;\r\n}\r\n\r\nfunction _wireShellHandlers(overlay: HTMLElement, panel: HTMLElement): void {\r\n const close = () => {\r\n _isOpen = false;\r\n overlay.remove();\r\n document.removeEventListener(\"keydown\", escHandler);\r\n };\r\n panel.querySelector('[data-tb-issues=\"close\"]')!.addEventListener(\"click\", close);\r\n overlay.addEventListener(\"click\", (e) => { if (e.target === overlay) close(); });\r\n\r\n panel.querySelector('[data-tb-issues=\"rescan\"]')!.addEventListener(\"click\", async () => {\r\n const body = panel.querySelector('[data-tb-issues=\"body\"]') as HTMLElement;\r\n body.innerHTML = _loadingHtml();\r\n const sub = panel.querySelector('[data-tb-issues=\"subtitle\"]') as HTMLElement;\r\n sub.textContent = \"Scanning…\";\r\n await scan();\r\n _renderBody(getIssues());\r\n });\r\n\r\n const escHandler = (e: KeyboardEvent) => { if (e.key === \"Escape\") close(); };\r\n document.addEventListener(\"keydown\", escHandler);\r\n}\r\n\r\nfunction _renderBody(issues: Issue[]): void {\r\n const panel = document.getElementById(PANEL_ID);\r\n if (!panel) return;\r\n const body = panel.querySelector('[data-tb-issues=\"body\"]') as HTMLElement;\r\n const subtitle = panel.querySelector('[data-tb-issues=\"subtitle\"]') as HTMLElement;\r\n\r\n if (issues.length === 0) {\r\n subtitle.textContent = \"No issues found\";\r\n body.innerHTML = `\r\n <div style=\"padding:48px 20px;text-align:center;color:var(--tb-text-muted, #888);font-size:13px\">\r\n <div style=\"font-size:32px;margin-bottom:8px\">✓</div>\r\n Clean scan — no issues detected on this page.\r\n <div style=\"font-size:11px;margin-top:6px;opacity:0.7\">Includes a11y · broken images · mixed content · JS errors · failed/slow API calls</div>\r\n </div>\r\n `;\r\n return;\r\n }\r\n\r\n // Severity counts in subtitle\r\n const counts: Record<string, number> = {};\r\n for (const i of issues) counts[i.severity] = (counts[i.severity] || 0) + 1;\r\n const summary = [\"critical\", \"serious\", \"moderate\", \"minor\"]\r\n .filter(s => counts[s])\r\n .map(s => `${counts[s]} ${s}`)\r\n .join(\" · \");\r\n subtitle.textContent = `${issues.length} issue${issues.length === 1 ? \"\" : \"s\"} · ${summary}`;\r\n\r\n body.innerHTML = issues.map(issue => _issueRowHtml(issue)).join(\"\");\r\n\r\n // Wire row actions\r\n body.querySelectorAll<HTMLElement>(\"[data-tb-issue-id]\").forEach(row => {\r\n const id = row.dataset.tbIssueId!;\r\n const issue = issues.find(i => i.id === id);\r\n if (!issue) return;\r\n\r\n row.querySelector('[data-action=\"locate\"]')?.addEventListener(\"click\", () => _locate(issue));\r\n row.querySelector('[data-action=\"dismiss\"]')?.addEventListener(\"click\", () => {\r\n dismissIssue(id);\r\n _renderBody(getIssues());\r\n });\r\n row.querySelector('[data-action=\"file\"]')?.addEventListener(\"click\", () => _fileAsBug(issue));\r\n });\r\n}\r\n\r\nfunction _issueRowHtml(issue: Issue): string {\r\n const colors = SEVERITY_COLORS[issue.severity];\r\n const detectorLabel = DETECTOR_LABELS[issue.detector];\r\n const desc = issue.description.length > 240\r\n ? issue.description.slice(0, 237) + \"…\"\r\n : issue.description;\r\n\r\n // Fingerprint dedup: when occurrences > 1 show a collapsible <details>\r\n // listing each occurrence with its preceding action.\r\n const repeats = (issue.occurrences || 1) > 1;\r\n const samplesHtml = repeats && issue.contextSamples && issue.contextSamples.length > 0\r\n ? `<details style=\"margin-top:6px;font-size:11px\">\r\n <summary style=\"cursor:pointer;color:var(--tb-text-muted, #888)\">\r\n View all ${issue.occurrences} contexts\r\n </summary>\r\n <ol style=\"margin:6px 0 0 20px;padding:0;color:var(--tb-text-secondary, #aaa);line-height:1.5\">\r\n ${issue.contextSamples.map(s => `\r\n <li>${new Date(s.timestamp).toLocaleTimeString()}${s.precedingAction ? ` · ${escapeHtml(s.precedingAction)}` : \"\"}</li>\r\n `).join(\"\")}\r\n </ol>\r\n </details>`\r\n : \"\";\r\n\r\n return `\r\n <div class=\"tb-issue-row\" data-tb-issue-id=\"${issue.id}\">\r\n <span class=\"tb-sev-badge\" style=\"background:${colors.bg};color:${colors.fg};border-color:${colors.border}\">${issue.severity}</span>\r\n <div style=\"flex:1;min-width:0\">\r\n <div style=\"display:flex;align-items:center;gap:6px;margin-bottom:4px;flex-wrap:wrap\">\r\n <span class=\"tb-detector-tag\">${detectorLabel}</span>\r\n <span style=\"font-size:13px;color:var(--tb-text-primary, #e0e0e0);font-weight:500\">${escapeHtml(issue.title)}</span>\r\n </div>\r\n <div style=\"font-size:11px;color:var(--tb-text-muted, #888);line-height:1.5;white-space:pre-wrap\">${escapeHtml(desc)}</div>\r\n ${issue.helpUrl ? `<a href=\"${issue.helpUrl}\" target=\"_blank\" rel=\"noopener noreferrer\" style=\"font-size:11px;color:var(--tb-accent, #6366F1);margin-top:4px;display:inline-block\">Learn more →</a>` : \"\"}\r\n ${samplesHtml}\r\n </div>\r\n <div class=\"tb-issue-actions\">\r\n ${issue.selector ? `<button data-action=\"locate\" title=\"Highlight on page\">📍 Locate</button>` : \"\"}\r\n <button data-action=\"file\" title=\"File as bug ticket\">File ticket</button>\r\n <button data-action=\"dismiss\" title=\"Dismiss for this session\">Dismiss</button>\r\n </div>\r\n </div>\r\n `;\r\n}\r\n\r\n/**\r\n * Briefly outline the offending element. Closes the panel so the page is\r\n * visible. Restores after 2.4s — non-destructive flash.\r\n */\r\nfunction _locate(issue: Issue): void {\r\n if (!issue.selector) return;\r\n let el: Element | null = null;\r\n try {\r\n el = document.querySelector(issue.selector);\r\n } catch {\r\n el = null;\r\n }\r\n if (!el) return;\r\n\r\n // Close the panel so the user can see the page.\r\n document.getElementById(`${PANEL_ID}-overlay`)?.remove();\r\n _isOpen = false;\r\n\r\n el.scrollIntoView({ behavior: \"smooth\", block: \"center\" });\r\n const htmlEl = el as HTMLElement;\r\n const prevOutline = htmlEl.style.outline;\r\n const prevTransition = htmlEl.style.transition;\r\n htmlEl.style.transition = \"outline 0.2s, box-shadow 0.2s\";\r\n htmlEl.style.outline = \"3px solid #6366F1\";\r\n htmlEl.style.boxShadow = \"0 0 0 6px rgba(99,102,241,0.35)\";\r\n setTimeout(() => {\r\n htmlEl.style.outline = prevOutline;\r\n htmlEl.style.boxShadow = \"\";\r\n htmlEl.style.transition = prevTransition;\r\n }, 2400);\r\n}\r\n\r\n/**\r\n * Pre-fill the Quick Bug modal with the issue's title + description as the\r\n * starting point for a ticket. Reuses the existing ticket-export pipeline.\r\n */\r\nfunction _fileAsBug(issue: Issue): void {\r\n const root = _root || document.getElementById(\"tracebug-root\");\r\n if (!root) return;\r\n // Close the issues panel.\r\n document.getElementById(`${PANEL_ID}-overlay`)?.remove();\r\n _isOpen = false;\r\n\r\n import(\"./quick-bug\").then(m => {\r\n m.showQuickBugCapture(root, {\r\n prefilledTitle: issue.title,\r\n prefilledDescription: _bugDescriptionFromIssue(issue),\r\n }).catch(() => {});\r\n });\r\n}\r\n\r\nfunction _bugDescriptionFromIssue(issue: Issue): string {\r\n const lines: string[] = [];\r\n lines.push(`> Detected by TraceBug auto-scanner: **${DETECTOR_LABELS[issue.detector]}** (${issue.severity})`);\r\n lines.push(\"\");\r\n lines.push(issue.description);\r\n if (issue.selector) lines.push(`\\n**Selector:** \\`${issue.selector}\\``);\r\n if (issue.url) lines.push(`**URL:** \\`${issue.url}\\``);\r\n if (issue.helpUrl) lines.push(`**Reference:** ${issue.helpUrl}`);\r\n return lines.join(\"\\n\");\r\n}\r\n"],"mappings":"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","names":[]} |
| "use strict";Object.defineProperty(exports, "__esModule", {value: true}); | ||
| var _chunkIXAICYFUcjs = require('./chunk-IXAICYFU.cjs'); | ||
| require('./chunk-3HHUTYZ5.cjs'); | ||
| require('./chunk-4SLN5LQD.cjs'); | ||
| exports.isQuickBugOpen = _chunkIXAICYFUcjs.isQuickBugOpen; exports.refreshQuickBugCapture = _chunkIXAICYFUcjs.refreshQuickBugCapture; exports.setCloudEndpoint = _chunkIXAICYFUcjs.setCloudEndpoint; exports.setGithubRepo = _chunkIXAICYFUcjs.setGithubRepo; exports.showQuickBugCapture = _chunkIXAICYFUcjs.showQuickBugCapture; | ||
| //# sourceMappingURL=quick-bug-LFLO2PSW.cjs.map |
| {"version":3,"sources":["d:\\Project\\TraceBug-ai\\dist\\quick-bug-LFLO2PSW.cjs"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACF,wDAA6B;AAC7B,gCAA6B;AAC7B,gCAA6B;AAC7B;AACE;AACA;AACA;AACA;AACA;AACF,kUAAC","file":"D:\\Project\\TraceBug-ai\\dist\\quick-bug-LFLO2PSW.cjs"} |
| import { | ||
| isQuickBugOpen, | ||
| refreshQuickBugCapture, | ||
| setCloudEndpoint, | ||
| setGithubRepo, | ||
| showQuickBugCapture | ||
| } from "./chunk-4RKCUHIO.js"; | ||
| import "./chunk-L3Q7Y6QP.js"; | ||
| import "./chunk-LQO44M5L.js"; | ||
| export { | ||
| isQuickBugOpen, | ||
| refreshQuickBugCapture, | ||
| setCloudEndpoint, | ||
| setGithubRepo, | ||
| showQuickBugCapture | ||
| }; | ||
| //# sourceMappingURL=quick-bug-MX3Y3FR3.js.map |
| {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]} |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 2 instances
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 2 instances
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
5115358
3.35%50117
4.21%718
4.36%69
4.55%