@sphoro/bugcatch-web-sdk 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"bugcatch.umd.js","sources":["../src/redact.js","../src/collectors.js","../src/capture.js","../src/drafts.js","../src/session.js","../src/updates.js","../src/references.js","../src/widget.js","../src/audio.js","../src/live-call.js","../src/incoming-call.js","../src/identity.js","../src/index.js"],"sourcesContent":["// Strips credentials out of captured URLs and error text, at the moment of\n// capture — before anything is buffered, and long before anything is sent.\n//\n// Request and response bodies are never read, so the URL is the whole attack\n// surface, and in practice it is a wide one: `?api_key=`, S3 presigned\n// signatures, OAuth fragments, JWTs sitting in a path segment. A presigned URL\n// is not a hint that a credential exists, it *is* the credential, and captured\n// network logs are shown on public share links and in the reporter portal.\n//\n// The goal is a redaction that leaves the log useful. `?token=[REDACTED]` still\n// tells a triager which endpoint was called with what shape of argument;\n// blanking the whole URL would make the Network tab worthless and push people\n// to turn capture off, which is the worse outcome.\n//\n// Twin implementations live in bugcatch-extension/src/collector.js (no build\n// step there, so it cannot import) and bugcatch-api/src/settings/redaction.util.ts\n// (defence in depth for older SDKs). tests/unit/redact-url.test.mjs runs one\n// corpus through all three and fails if they ever disagree.\n\nexport const PLACEHOLDER = '[REDACTED]';\n\n// Keys whose value is a credential whatever it looks like. Matched after\n// normalising, so `X-Amz-Security-Token` arrives here as `xamzsecuritytoken`.\nconst ALWAYS = new Set([\n 'auth', 'bearer', 'session', 'sid', 'phpsessid', 'otp',\n 'pass', 'passwd', 'pwd', 'sig', 'codeverifier',\n]);\n\n// Substrings that make a key sensitive wherever they appear, so app-specific\n// names like `x_auth_token` or `myAppApiKey` are covered without enumeration.\n// Deliberately excludes bare `auth` (matches `author`) and bare `key`\n// (matches `keyword`, `monkey`) — both are handled more precisely elsewhere.\nconst MARKERS = [\n 'token', 'secret', 'password', 'passwd', 'apikey', 'signature',\n 'credential', 'sessionid', 'privatekey', 'accesskey', 'authorization',\n];\n\n// Keys that are only sometimes a credential. `?key=AIza…` is a Google API key;\n// `?key=name` is a sort order. `?code=` is an OAuth code or an error string.\n// These redact only when the value itself looks like a secret, because\n// over-redacting the common case costs more triage value than it buys.\nconst AMBIGUOUS = new Set(['key', 'code', 'state', 'nonce', 'salt', 'hash']);\n\n// Recognisable credential formats, matched anywhere — query, path segment or\n// free text — so a secret still gets caught when it is not behind a named\n// parameter at all.\nconst SHAPES = [\n /eyJ[\\w-]{8,}\\.[\\w-]{8,}\\.[\\w-]+/g, // JWT\n /(?:AKIA|ASIA|AROA|AIDA|AGPA|ANPA|ANVA|APKA)[0-9A-Z]{16}/g, // AWS key id\n /AIza[\\w-]{35}/g, // Google API key\n /gh[pousr]_[A-Za-z0-9]{36,}/g, // GitHub token\n /github_pat_\\w{22,}/g, // GitHub fine-grained PAT\n /[sprk]k_(?:live|test)_[0-9A-Za-z]{10,}/g, // Stripe\n /xox[abposr]-[A-Za-z0-9-]{10,}/g, // Slack\n /sk-ant-[\\w-]{20,}/g, // Anthropic\n /sk-[\\w-]{20,}/g, // OpenAI\n /glpat-[\\w-]{20,}/g, // GitLab\n /shp(?:at|ss|ca|pa)_[A-Za-z0-9]{32}/g, // Shopify\n];\n\n// `Authorization: Bearer …` as it appears in thrown error text. The class is\n// written without a letter range because the `i` flag already covers case.\nconst BEARER = /\\bBearer\\s+[\\w.~+/=-]{8,}/gi;\n\n// Credentials in the authority, which fetch() honours: https://user:pass@host.\nconst USERINFO = /^([A-Za-z][A-Za-z0-9+.-]*:\\/\\/)([^/?#@]+)@/;\n\nfunction safeDecode(value) {\n try {\n return decodeURIComponent(value.replace(/\\+/g, ' '));\n } catch {\n return value; // a malformed escape is not a reason to give up on the rest\n }\n}\n\nfunction normalizeKey(rawKey) {\n return safeDecode(rawKey).toLowerCase().replace(/[^a-z0-9]/g, '');\n}\n\n/** Whether a value carried by an ambiguous key is credential-shaped. */\nfunction looksSecret(value) {\n if (value.length >= 20) return true;\n return SHAPES.some((re) => {\n re.lastIndex = 0;\n return re.test(value);\n });\n}\n\nfunction isSensitiveKey(rawKey, rawValue) {\n const key = normalizeKey(rawKey);\n if (!key) return false;\n if (ALWAYS.has(key)) return true;\n for (const marker of MARKERS) if (key.includes(marker)) return true;\n if (AMBIGUOUS.has(key)) return looksSecret(safeDecode(rawValue));\n return false;\n}\n\n/** Redact the values of sensitive keys in an `a=1&b=2` string. */\nfunction redactPairs(query) {\n return query\n .split('&')\n .map((pair) => {\n const eq = pair.indexOf('=');\n if (eq === -1) return pair;\n const key = pair.slice(0, eq);\n const value = pair.slice(eq + 1);\n if (!value) return pair;\n return isSensitiveKey(key, value) ? `${key}=${PLACEHOLDER}` : pair;\n })\n .join('&');\n}\n\n/** Replace every recognisable credential format in a string. */\nfunction redactShapes(text) {\n let out = text;\n for (const re of SHAPES) {\n re.lastIndex = 0;\n out = out.replace(re, PLACEHOLDER);\n }\n return out;\n}\n\n/**\n * Scrub a single URL. Structure is preserved — scheme, host, path and the\n * names of every parameter survive, only credential values are replaced.\n * Works on relative URLs too, which is why this parses by hand rather than\n * going through `new URL()`: that would also re-encode and reorder, so the\n * logged URL would stop matching what the app actually requested.\n */\nexport function redactUrl(raw) {\n if (raw === null || raw === undefined) return raw;\n let url = String(raw);\n if (!url) return url;\n\n url = url.replace(USERINFO, `$1${PLACEHOLDER}@`);\n\n const hashAt = url.indexOf('#');\n let head = hashAt === -1 ? url : url.slice(0, hashAt);\n let hash = hashAt === -1 ? '' : url.slice(hashAt);\n\n const queryAt = head.indexOf('?');\n if (queryAt !== -1) {\n head = head.slice(0, queryAt + 1) + redactPairs(head.slice(queryAt + 1));\n }\n\n if (hash.length > 1) {\n const body = hash.slice(1);\n const innerQuery = body.indexOf('?');\n if (innerQuery !== -1) {\n // Hash-routed app: #/reset?token=…\n hash = `#${body.slice(0, innerQuery + 1)}${redactPairs(body.slice(innerQuery + 1))}`;\n } else if (body.includes('=')) {\n // OAuth implicit flow drops the token here, where it never reaches a\n // server but is very much still a credential.\n hash = `#${redactPairs(body)}`;\n }\n }\n\n return redactShapes(head + hash);\n}\n\n/**\n * Scrub free text — a thrown error message, a console line. Any URL inside it\n * is scrubbed as a URL, then bare credentials and `Bearer …` headers go too.\n */\nexport function redactText(raw) {\n if (raw === null || raw === undefined) return raw;\n let text = String(raw);\n if (!text) return text;\n\n // The scheme is length-bounded so a long run of scheme-legal characters that\n // never reaches `://` cannot backtrack quadratically. The same source runs\n // server-side over attacker-influenced text, where that would be a DoS.\n text = text.replace(/[A-Za-z][A-Za-z0-9+.-]{0,30}:\\/\\/[^\\s\"'<>)\\]]+/g, (m) => redactUrl(m));\n text = text.replace(BEARER, `Bearer ${PLACEHOLDER}`);\n return redactShapes(text);\n}\n","// Ring-buffered capture of the things you wish you had when a bug comes in:\n// console output, failed/slow requests, unhandled errors, and the environment.\n//\n// Everything is bounded — a long-lived page must not grow memory without limit.\n\nimport { redactUrl, redactText } from './redact.js';\n\nexport function createRingBuffer(limit) {\n const items = [];\n return {\n push(item) {\n items.push(item);\n if (items.length > limit) items.splice(0, items.length - limit);\n },\n all: () => items.slice(),\n clear: () => { items.length = 0; },\n get size() { return items.length; },\n };\n}\n\nconst LEVELS = ['log', 'info', 'warn', 'error', 'debug'];\n\n/** Wraps console.* and records what goes through, then calls the original. */\nexport function installConsoleCapture(buffer, { levels = ['warn', 'error', 'log', 'info'] } = {}) {\n const originals = {};\n for (const level of LEVELS) {\n originals[level] = console[level];\n if (!levels.includes(level)) continue;\n console[level] = (...args) => {\n try {\n buffer.push({\n level,\n text: args.map(stringify).join(' ').slice(0, 2000),\n at: new Date().toISOString(),\n });\n } catch { /* capture must never break the page */ }\n originals[level].apply(console, args);\n };\n }\n return () => { for (const level of LEVELS) console[level] = originals[level]; };\n}\n\n/**\n * Records fetch + XHR: url, method, status, duration. Bodies are never read,\n * which leaves the URL as the only place a credential can arrive — so every\n * entry goes through redact.js on the way into the buffer.\n */\nexport function installNetworkCapture(buffer, { slowMs = 2000 } = {}) {\n const originalFetch = window.fetch;\n const originalOpen = XMLHttpRequest.prototype.open;\n const originalSend = XMLHttpRequest.prototype.send;\n\n if (originalFetch) {\n window.fetch = async function patchedFetch(...args) {\n const started = performance.now();\n const url = typeof args[0] === 'string' ? args[0] : args[0]?.url;\n const method = args[1]?.method || (typeof args[0] === 'object' ? args[0]?.method : 'GET') || 'GET';\n try {\n const res = await originalFetch.apply(this, args);\n record(buffer, { url, method, status: res.status, ms: Math.round(performance.now() - started), slowMs });\n return res;\n } catch (err) {\n record(buffer, { url, method, status: 0, error: String(err?.message || err), ms: Math.round(performance.now() - started), slowMs });\n throw err;\n }\n };\n }\n\n XMLHttpRequest.prototype.open = function patchedOpen(method, url, ...rest) {\n this.__bc = { method, url, started: 0 };\n return originalOpen.call(this, method, url, ...rest);\n };\n XMLHttpRequest.prototype.send = function patchedSend(...args) {\n if (this.__bc) {\n this.__bc.started = performance.now();\n this.addEventListener('loadend', () => {\n record(buffer, {\n url: this.__bc.url,\n method: this.__bc.method,\n status: this.status,\n ms: Math.round(performance.now() - this.__bc.started),\n slowMs,\n });\n });\n }\n return originalSend.apply(this, args);\n };\n\n return () => {\n if (originalFetch) window.fetch = originalFetch;\n XMLHttpRequest.prototype.open = originalOpen;\n XMLHttpRequest.prototype.send = originalSend;\n };\n}\n\nfunction record(buffer, { url, method, status, ms, error, slowMs }) {\n try {\n buffer.push({\n // Scrubbed before it is buffered, not before it is sent: a credential\n // that never enters the ring buffer cannot leave with a report, and\n // cannot be read back out of memory by the page either. Redact first,\n // then truncate — truncating first would split a token into something\n // no pattern recognises, and the fragment would still be a fragment of\n // a live credential.\n url: redactUrl(String(url)).slice(0, 500),\n method,\n status,\n ms,\n error: error === undefined ? undefined : redactText(error),\n // Flagged so triage can spot the interesting rows without reading everything.\n failed: status === 0 || status >= 400,\n slow: ms >= slowMs,\n at: new Date().toISOString(),\n });\n } catch { /* ignore */ }\n}\n\n/** Unhandled errors and rejections, appended to the console buffer. */\nexport function installErrorCapture(buffer) {\n const onError = (event) => {\n // Browsers already prefix event.message with \"Uncaught\" — don't say it twice.\n const message = String(event.message || 'Error');\n const where = event.filename ? ` (${event.filename}:${event.lineno}:${event.colno})` : '';\n buffer.push({\n level: 'error',\n text: (/^uncaught/i.test(message) ? message : `Uncaught ${message}`) + where,\n at: new Date().toISOString(),\n });\n };\n const onRejection = (event) => {\n buffer.push({\n level: 'error',\n text: `Unhandled rejection: ${stringify(event.reason)}`,\n at: new Date().toISOString(),\n });\n };\n window.addEventListener('error', onError);\n window.addEventListener('unhandledrejection', onRejection);\n return () => {\n window.removeEventListener('error', onError);\n window.removeEventListener('unhandledrejection', onRejection);\n };\n}\n\n/**\n * Battery needs the async Battery Status API, so environment collection is\n * async now. Unsupported browsers (Safari, Firefox) simply omit the fields.\n */\nexport async function collectEnvironment() {\n const base = collectEnvironmentSync();\n try {\n if (navigator.getBattery) {\n const b = await navigator.getBattery();\n base.battery = Math.round(b.level * 100);\n base.charging = b.charging;\n }\n } catch { /* not available — leave the fields out */ }\n return base;\n}\n\nexport function collectEnvironmentSync() {\n const nav = window.navigator || {};\n return {\n userAgent: nav.userAgent,\n platform: nav.platform,\n language: nav.language,\n languages: nav.languages ? [...nav.languages] : null,\n online: nav.onLine,\n cookieEnabled: nav.cookieEnabled,\n maxTouchPoints: nav.maxTouchPoints ?? null,\n viewport: { width: window.innerWidth, height: window.innerHeight },\n screen: {\n width: window.screen?.width,\n height: window.screen?.height,\n availWidth: window.screen?.availWidth,\n availHeight: window.screen?.availHeight,\n colorDepth: window.screen?.colorDepth,\n orientation: window.screen?.orientation?.type || null,\n },\n devicePixelRatio: window.devicePixelRatio,\n referrer: document.referrer || null,\n title: document.title,\n capturedAt: new Date().toISOString(),\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n timezoneOffsetMinutes: new Date().getTimezoneOffset(),\n // A hint, not a guarantee: `webdriver` is the only broadly-available signal\n // that something other than a person is driving the page.\n browserMode: nav.webdriver ? 'Automated' : 'Normal',\n doNotTrack: nav.doNotTrack || null,\n hardwareConcurrency: nav.hardwareConcurrency ?? null,\n deviceMemory: nav.deviceMemory ?? null,\n connection: nav.connection\n ? {\n effectiveType: nav.connection.effectiveType,\n downlink: nav.connection.downlink,\n rtt: nav.connection.rtt,\n saveData: nav.connection.saveData,\n }\n : null,\n prefersColorScheme: window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : 'light',\n prefersReducedMotion: window.matchMedia?.('(prefers-reduced-motion: reduce)').matches || false,\n };\n}\n\nfunction stringify(value) {\n if (typeof value === 'string') return value;\n if (value instanceof Error) return `${value.name}: ${value.message}`;\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n}\n","// Screenshots, screen recordings and file attachments.\n//\n// Capture always goes through getDisplayMedia, so the browser shows its own\n// picker and permission prompt — nothing leaves the page without the person\n// explicitly choosing what to share. There is no DOM-rasterising fallback on\n// purpose: it would need a heavy dependency and would still silently miss\n// cross-origin iframes, canvas and video, which is worse than not offering it.\n\n/**\n * Pristine references, taken at module-evaluation time — which happens on\n * import, before init() patches anything.\n *\n * Uploads must not be recorded by our own network capture: a report would\n * otherwise describe its own attachment upload, and a retry would append to a\n * buffer that is already being serialised.\n */\nconst nativeFetch = typeof window !== 'undefined' && window.fetch\n ? window.fetch.bind(window)\n : null;\nconst nativeXhrOpen = typeof XMLHttpRequest !== 'undefined'\n ? XMLHttpRequest.prototype.open\n : null;\nconst nativeXhrSend = typeof XMLHttpRequest !== 'undefined'\n ? XMLHttpRequest.prototype.send\n : null;\n\n/**\n * What the API will presign. Anything else is rejected before we ask.\n *\n * Mirrors UPLOAD_MIMES in the API's storage.service.ts. The two are checked\n * independently — this one to fail early with a message the person can act on,\n * that one because a browser is not a place to enforce anything.\n */\nexport const UPLOAD_MIMES = [\n 'image/png', 'image/jpeg', 'image/webp', 'image/gif', 'video/webm', 'video/mp4',\n 'audio/webm', 'audio/ogg', 'audio/mpeg', 'audio/mp4', 'audio/wav',\n 'application/pdf', 'text/plain', 'text/csv', 'application/json', 'application/zip',\n 'application/msword',\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n 'application/vnd.ms-excel',\n 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n];\n\n/**\n * What a *bug report* may carry, which is narrower than what may be uploaded.\n *\n * A report's attachments are evidence of the bug — what the screen looked like,\n * what the session did — and the API types them SCREENSHOT or VIDEO. A document\n * has no honest value there: filed as a SCREENSHOT it would render as a broken\n * image in the dashboard's media viewer.\n *\n * Documents and voice notes belong on a *message*, where the type is derived\n * from the mime and the reader gets a player or a download.\n */\nexport const REPORT_MIMES = [\n 'image/png', 'image/jpeg', 'image/webp', 'video/webm', 'video/mp4',\n];\n\nexport const voiceSupported = () =>\n Boolean(navigator?.mediaDevices?.getUserMedia && typeof MediaRecorder !== 'undefined');\n\nexport const captureSupported = () =>\n Boolean(navigator?.mediaDevices?.getDisplayMedia && typeof MediaRecorder !== 'undefined');\n\nexport const screenshotSupported = () =>\n Boolean(navigator?.mediaDevices?.getDisplayMedia);\n\n/**\n * MediaRecorder reports `video/webm;codecs=vp9`, but the presign signature\n * binds an exact Content-Type and the API only allows the base types. Strip\n * the codec parameter so the type we sign, the header we send and the type the\n * API stored all agree.\n */\nexport function normalizeMime(mime, allowed = UPLOAD_MIMES) {\n const base = String(mime || '').split(';')[0].trim().toLowerCase();\n return allowed.includes(base) ? base : null;\n}\n\n/** Grab a single frame of a chosen screen/window/tab as a PNG blob. */\nexport async function captureScreenshot() {\n const stream = await navigator.mediaDevices.getDisplayMedia({\n video: { frameRate: 1 },\n audio: false,\n });\n try {\n const track = stream.getVideoTracks()[0];\n const video = document.createElement('video');\n video.srcObject = stream;\n video.muted = true;\n video.playsInline = true;\n await video.play();\n\n // Wait one frame so the first paint isn't blank.\n await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));\n\n const { width, height } = track.getSettings();\n const canvas = document.createElement('canvas');\n canvas.width = width || video.videoWidth;\n canvas.height = height || video.videoHeight;\n canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);\n\n const blob = await new Promise((res) => canvas.toBlob(res, 'image/png'));\n if (!blob) throw new Error('The browser returned an empty screenshot.');\n return { blob, mime: 'image/png', kind: 'SCREENSHOT' };\n } finally {\n // Always release the capture, including on failure — otherwise the browser\n // keeps showing \"sharing your screen\".\n stream.getTracks().forEach((t) => t.stop());\n }\n}\n\n/**\n * Start recording a chosen screen/window/tab. Returns a handle with stop() and\n * cancel(); the caller decides which. Recording ends on its own if the user\n * clicks the browser's \"Stop sharing\" button or the cap is reached.\n */\nexport async function startRecording({ maxMs = 120_000, onTick, onAutoStop } = {}) {\n const stream = await navigator.mediaDevices.getDisplayMedia({\n video: { frameRate: 15 },\n audio: false,\n });\n\n const mime = ['video/webm;codecs=vp9', 'video/webm;codecs=vp8', 'video/webm']\n .find((t) => MediaRecorder.isTypeSupported(t)) || 'video/webm';\n\n const recorder = new MediaRecorder(stream, { mimeType: mime, videoBitsPerSecond: 2_500_000 });\n const chunks = [];\n recorder.ondataavailable = (e) => { if (e.data.size) chunks.push(e.data); };\n recorder.start(1000);\n\n const startedAt = Date.now();\n const timer = setInterval(() => onTick?.(Math.floor((Date.now() - startedAt) / 1000)), 500);\n\n const cleanup = () => {\n clearInterval(timer);\n clearTimeout(capTimer);\n stream.getTracks().forEach((t) => t.stop());\n };\n\n const finished = new Promise((resolve) => {\n recorder.onstop = () => {\n cleanup();\n resolve(new Blob(chunks, { type: mime }));\n };\n });\n\n const endEarly = () => {\n if (recorder.state !== 'inactive') recorder.stop();\n onAutoStop?.();\n };\n\n // The user can stop sharing from the browser's own UI; treat that as \"done\".\n stream.getVideoTracks()[0].addEventListener('ended', endEarly);\n\n // Hard cap so a forgotten recording can't grow without bound.\n const capTimer = setTimeout(endEarly, maxMs);\n\n return {\n mime,\n maxMs,\n async stop() {\n clearTimeout(capTimer);\n if (recorder.state !== 'inactive') recorder.stop();\n return { blob: await finished, mime, kind: 'VIDEO' };\n },\n cancel() {\n cleanup();\n if (recorder.state !== 'inactive') recorder.stop();\n },\n };\n}\n\n/**\n * Record a voice note from the microphone. Returns a handle with stop() and\n * cancel(), like startRecording() above.\n *\n * Audio only, and from getUserMedia rather than getDisplayMedia: this is\n * somebody talking into their laptop to explain a bug, not a capture of the\n * screen. The browser shows its own microphone prompt, so nothing is recorded\n * without an explicit grant.\n *\n * The container is whichever the browser will give us — Chromium and Firefox\n * produce webm/opus, Safari mp4/aac — because MediaRecorder cannot be made to\n * agree on one, and the API allows both.\n */\nexport async function startVoiceNote({ maxMs = 120_000, onTick, onAutoStop } = {}) {\n const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n\n const mime = ['audio/webm;codecs=opus', 'audio/webm', 'audio/mp4', 'audio/ogg']\n .find((t) => MediaRecorder.isTypeSupported(t)) || '';\n\n const recorder = new MediaRecorder(stream, mime ? { mimeType: mime } : undefined);\n const chunks = [];\n recorder.ondataavailable = (e) => { if (e.data.size) chunks.push(e.data); };\n recorder.start(1000);\n\n const startedAt = Date.now();\n const timer = setInterval(() => onTick?.(Math.floor((Date.now() - startedAt) / 1000)), 500);\n\n const cleanup = () => {\n clearInterval(timer);\n clearTimeout(capTimer);\n stream.getTracks().forEach((t) => t.stop());\n };\n\n const finished = new Promise((resolve) => {\n recorder.onstop = () => {\n cleanup();\n resolve(new Blob(chunks, { type: recorder.mimeType || mime }));\n };\n });\n\n const endEarly = () => {\n if (recorder.state !== 'inactive') recorder.stop();\n onAutoStop?.();\n };\n\n // A hard cap, as for screen recording: a forgotten open microphone is worse\n // than a truncated note.\n const capTimer = setTimeout(endEarly, maxMs);\n\n return {\n mime: recorder.mimeType || mime,\n maxMs,\n async stop() {\n clearTimeout(capTimer);\n if (recorder.state !== 'inactive') recorder.stop();\n const blob = await finished;\n return { blob, mime: blob.type || recorder.mimeType || mime, kind: 'AUDIO' };\n },\n cancel() {\n cleanup();\n if (recorder.state !== 'inactive') recorder.stop();\n },\n };\n}\n\n/**\n * Ask the API for a presigned URL and PUT the blob straight at it, so media\n * never passes through the API request path.\n *\n * Resolves to the storage key, which is what a report references.\n */\nexport async function uploadAttachment({ apiBase, apiKey, blob, mime, onProgress }) {\n const type = normalizeMime(mime || blob.type);\n if (!type) {\n throw new Error(`[BugCatch] ${mime || blob.type || 'that file'} can't be attached.`);\n }\n\n const res = await nativeFetch(`${apiBase}/ingest/presign`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', 'X-BugCatch-Key': apiKey },\n body: JSON.stringify({ mime: type }),\n });\n if (!res.ok) throw new Error(`[BugCatch] could not start the upload (HTTP ${res.status}).`);\n const { key, uploadUrl } = await res.json();\n\n await put(uploadUrl, blob, type, onProgress);\n return { storageKey: key, mime: type };\n}\n\n/**\n * XHR rather than fetch purely for upload progress — a recording is megabytes,\n * and a progress bar is the difference between \"working\" and \"frozen\".\n */\nfunction put(uploadUrl, blob, mime, onProgress) {\n return new Promise((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n nativeXhrOpen.call(xhr, 'PUT', uploadUrl, true);\n xhr.setRequestHeader('Content-Type', mime);\n xhr.upload.onprogress = (e) => {\n if (e.lengthComputable) onProgress?.(e.loaded / e.total);\n };\n xhr.onload = () => {\n if (xhr.status >= 200 && xhr.status < 300) resolve();\n else reject(new Error(`[BugCatch] upload failed (HTTP ${xhr.status}).`));\n };\n // The PUT goes to the bucket, not to the API, so a missing bucket CORS\n // policy arrives here as a bare network error with no status. Reporting it\n // as \"network error\" sends people to read API logs that are perfectly clean.\n xhr.onerror = () => reject(new Error(\n '[BugCatch] the browser blocked the upload to storage. If this is a new '\n + 'environment, the media bucket is probably missing its CORS policy.',\n ));\n xhr.onabort = () => reject(new Error('[BugCatch] upload cancelled.'));\n nativeXhrSend.call(xhr, blob);\n });\n}\n\nexport function humanSize(bytes) {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;\n return `${(bytes / 1024 / 1024).toFixed(1)} MB`;\n}\n\nexport function humanDuration(seconds) {\n const m = Math.floor(seconds / 60);\n const s = seconds % 60;\n return `${m}:${String(s).padStart(2, '0')}`;\n}\n","// Drafts are scoped to the API, project and reporter. Never store credentials.\nconst MAX_AGE = 7 * 24 * 60 * 60 * 1000;\nlet database;\nfunction open() {\n if (!database) database = new Promise((resolve, reject) => {\n const request = indexedDB.open('bugcatch-drafts', 1);\n request.onupgradeneeded = () => request.result.createObjectStore('drafts');\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n request.onblocked = () => reject(new Error('Draft storage is blocked.'));\n }).catch((err) => { database = null; throw err; });\n return database;\n}\nexport function draftKey(config, email) {\n return JSON.stringify([config.apiBase, config.projectId, email || config.user?.email || config.user?.id || 'anonymous']);\n}\nexport async function draftOperation(key, value) {\n const db = await open();\n return new Promise((resolve, reject) => {\n const tx = db.transaction('drafts', value === undefined ? 'readonly' : 'readwrite');\n const store = tx.objectStore('drafts');\n const request = value === undefined ? store.get(key) : value === null ? store.delete(key) : store.put({ ...value, savedAt: Date.now() }, key);\n tx.oncomplete = () => resolve(request.result);\n tx.onerror = () => reject(tx.error);\n tx.onabort = () => reject(tx.error || new Error('Draft was not saved.'));\n });\n}\nexport async function readDraft(key) {\n const value = await draftOperation(key);\n if (value && Date.now() - value.savedAt > MAX_AGE) { await draftOperation(key, null); return null; }\n return value || null;\n}\n","// Signing in to a project's widget.\n//\n// Two different things call themselves \"who is reporting this\" in this SDK, and\n// they are not the same claim:\n//\n// identify() — the host application telling us about its own signed-in user.\n// Free, silent, and worth exactly as much as the application\n// saying it. Nothing here challenges it.\n//\n// signIn() — the person in front of the browser answering a code we mailed\n// to the address they typed. Costs them a round trip to their\n// inbox, and is the only one that survives a project deciding\n// it wants a real address on every report.\n//\n// Which of the two a project asks for is the project's own setting, read from\n// the API rather than configured here — see loginState(). A widget that guessed\n// would either hide a mandatory sign-in or put one in front of a project that\n// never asked for it.\n//\n// The session is stored per project, because that is the boundary the server\n// draws too: one live session per address per project. A browser on a page that\n// mounts two projects' widgets is two independent sign-ins, and signing out of\n// one leaves the other alone.\n\nconst KEY_PREFIX = 'bugcatch.session.';\n\nconst storageKey = (projectId) => `${KEY_PREFIX}${projectId}`;\n\n/**\n * The same sessions, held for the life of the page.\n *\n * Not a cache — a fallback. localStorage is unavailable outright in some\n * privacy modes and on some embedded webviews, and without this a project that\n * requires sign-in would be unusable there: the code would be accepted and the\n * session immediately forgotten, so the report that follows is refused. Held\n * here, signing in still works for as long as the tab is open; all that is lost\n * is surviving a reload, which is the part storage was buying.\n */\nconst inMemory = new Map();\n\n/**\n * The session for one project, or null.\n *\n * Storage can throw outright in private modes, and can hold something another\n * version of this SDK wrote — both come back as \"not signed in\" rather than an\n * exception in the host application's page.\n */\nexport function readSession(projectId) {\n if (!projectId) return null;\n try {\n const raw = window.localStorage?.getItem(storageKey(projectId));\n if (raw) {\n const parsed = JSON.parse(raw);\n if (parsed?.token) return parsed;\n }\n } catch { /* storage unavailable, or holding something we did not write */ }\n return inMemory.get(projectId) || null;\n}\n\nexport function writeSession(projectId, session) {\n if (!projectId || !session?.token) return;\n const held = {\n token: session.token,\n email: session.email || null,\n name: session.name || null,\n };\n inMemory.set(projectId, held);\n try {\n window.localStorage?.setItem(storageKey(projectId), JSON.stringify(held));\n } catch { /* the session just won't survive a reload */ }\n}\n\nexport function clearSession(projectId) {\n if (!projectId) return;\n inMemory.delete(projectId);\n try {\n window.localStorage?.removeItem(storageKey(projectId));\n } catch { /* ignore */ }\n}\n\n/** The headers every call here sends: the publishable key, plus who we are. */\nfunction headers({ apiKey, token }) {\n return {\n 'Content-Type': 'application/json',\n 'X-BugCatch-Key': apiKey,\n // Its own header rather than Authorization, which on these routes already\n // carries the API key. The two answer different questions and a request\n // carries both.\n ...(token ? { 'X-BugCatch-Session': token } : {}),\n };\n}\n\nasync function readError(res) {\n try {\n const body = await res.json();\n const message = Array.isArray(body.message) ? body.message.join(', ') : body.message;\n if (message) return message;\n } catch { /* non-JSON error */ }\n return `Request failed (HTTP ${res.status})`;\n}\n\n/**\n * What this project asks of the reporter, and who this browser is signed in as.\n *\n * { mode: 'off' | 'optional' | 'required',\n * signedIn: { email, name } | null,\n * signedOutReason: string | null }\n *\n * Asked each time the panel opens rather than cached for the page: the session\n * it reports on can end elsewhere — the same address signing in on a phone ends\n * the one on this laptop — and a widget holding a cached \"signed in as you\"\n * would only find out at the moment the report failed.\n *\n * Never throws. A deployment that has not been updated answers 404 here, and an\n * SDK that treated that as fatal would take the whole widget down over a\n * feature the project had not switched on; the answer is \"off\", which is what\n * every project looked like before this existed.\n */\nexport async function loginState({ apiBase, apiKey, projectId, token }) {\n const off = { mode: 'off', signedIn: null, signedOutReason: null };\n try {\n const res = await fetch(\n `${apiBase}/ingest/login?projectId=${encodeURIComponent(projectId)}`,\n { headers: headers({ apiKey, token }) },\n );\n if (!res.ok) return off;\n const body = await res.json();\n return {\n mode: body.mode === 'optional' || body.mode === 'required' ? body.mode : 'off',\n signedIn: body.signedIn || null,\n signedOutReason: body.signedOutReason || null,\n };\n } catch {\n return off;\n }\n}\n\n/** Step one: ask for a code. Throws with the server's sentence on refusal. */\nexport async function startSignIn({ apiBase, apiKey, projectId, email, name }) {\n const res = await fetch(`${apiBase}/ingest/login/start`, {\n method: 'POST',\n headers: headers({ apiKey }),\n body: JSON.stringify({ projectId, email, ...(name ? { name } : {}) }),\n });\n if (!res.ok) throw new Error(await readError(res));\n return res.json();\n}\n\n/**\n * Step two: spend the code.\n *\n * Stores the session on success, which is what makes it survive a reload and\n * what every other call here reads. Signing in ends this address's session on\n * this project everywhere else — that is the server's rule, not something this\n * browser can opt out of.\n */\nexport async function confirmSignIn({ apiBase, apiKey, projectId, email, code }) {\n const res = await fetch(`${apiBase}/ingest/login`, {\n method: 'POST',\n headers: headers({ apiKey }),\n body: JSON.stringify({ projectId, email, code }),\n });\n if (!res.ok) throw new Error(await readError(res));\n\n const body = await res.json();\n const session = { token: body.sessionToken, email: body.email, name: body.name };\n writeSession(projectId, session);\n return session;\n}\n\n/**\n * Sign out, here and on the server.\n *\n * Local storage is cleared whatever the network says. Somebody pressing sign\n * out on a shared machine has to end up signed out on it, and a fetch that\n * failed is not a reason to leave their address in the next person's widget.\n */\nexport async function endSignIn({ apiBase, apiKey, projectId, token }) {\n clearSession(projectId);\n if (!token) return { signedOut: true };\n try {\n await fetch(`${apiBase}/ingest/logout`, {\n method: 'POST',\n headers: headers({ apiKey, token }),\n body: JSON.stringify({ projectId }),\n });\n } catch { /* the token is already unusable from this browser */ }\n return { signedOut: true };\n}\n","// Live status changes on the reports this browser has filed.\n//\n// The person who reported a bug is the one person a tracker usually forgets to\n// tell. They are already in your app, so this is where they hear about it —\n// not in an email they may not read, and not on a page they have to remember\n// to visit.\n//\n// Authentication is the token handed back when the report was filed, kept in\n// localStorage. Deliberately not the API key: that is publishable and sits in\n// your page source, so a key-authenticated feed keyed on an email address would\n// let anyone read anyone's reports. Holding this token is proof of having\n// filed them.\n\nconst TOKEN_KEY = 'bugcatch.watch';\nconst BACKOFF_MS = [2000, 5000, 15000, 30000, 60000];\n\n/**\n * How long a stream has to survive before it counts as having worked.\n *\n * The backoff above is only a backoff if \"it failed\" and \"it worked\" are told\n * apart by more than the response arriving, and they were not: the counter\n * reset the moment the headers landed. A server that accepts the connection and\n * ends it immediately therefore reset the backoff every time, so the delay\n * never left its first rung and the retry became a two-second loop that no\n * amount of failing could slow down.\n *\n * That is not hypothetical here. An expired watch token used to be answered\n * with a 200 whose body closed at once — the status could not be set, because\n * an SSE response has already begun by the time the token is opened — and this\n * client only treats a 401 as fatal. Prod on 2026-08-31: 6,001 of 6,012\n * requests to the stream aborted under 10ms, ~50/min, for hours. The server\n * sends a real 401 now (see WatchTokenGuard in the API), and this is the other\n * half: a stream that ends the instant it opens must cost a longer wait than\n * the one before it, whatever the reason.\n *\n * Comfortably over the connect-and-die case and well under the 25s server\n * heartbeat, so a stream genuinely carrying traffic always qualifies.\n */\nconst STABLE_MS = 5000;\n\n/** Where the watch token lives. Storage can throw in private modes — never fatal. */\nexport function readToken() {\n try {\n return window.localStorage?.getItem(TOKEN_KEY) || null;\n } catch {\n return null;\n }\n}\n\nexport function writeToken(token) {\n if (!token) return;\n try {\n window.localStorage?.setItem(TOKEN_KEY, token);\n } catch { /* storage unavailable — updates just won't survive a reload */ }\n}\n\nexport function clearToken() {\n try {\n window.localStorage?.removeItem(TOKEN_KEY);\n } catch { /* ignore */ }\n // Anything still waiting to be read goes with it. Unread updates are about\n // reports this browser can no longer prove it filed — and on the sign-out\n // path, about somebody who has left a machine the next person will use.\n forgetAllUpdates();\n}\n\n/* ---------------------------------------------------------------------------\n * Unread updates.\n *\n * An update used to be shown for eight seconds and then dropped on the floor.\n * Somebody switching tabs, reading something else on the page, or away from\n * the keyboard when their bug was fixed simply never found out — the one\n * notification they were waiting for was the one most likely to be missed.\n *\n * So an update is now held until it has been read, and \"read\" is something the\n * reporter says rather than something a timer decides. The store below is what\n * remembers that across a reload: the stream replays nothing on reconnect, so\n * an unread notification that lived only in a variable would not survive the\n * reporter pressing refresh.\n *\n * Kept beside the watch token, and dropped with it — a browser that can no\n * longer prove it filed anything has no reports to be told about.\n * ------------------------------------------------------------------------ */\n\nexport const UNREAD_KEY = 'bugcatch.unread';\n// Enough that a fortnight away does not lose anything, small enough that the\n// store stays a notification list rather than a history. The full record of\n// what happened lives on the report itself.\nconst UNREAD_LIMIT = 20;\n\n/**\n * A stable id for an update, so the same one is not shown twice and so a host\n * app can say which one it has read.\n *\n * Built from the frame rather than generated, because it has to survive a\n * reload: the server sends no id of its own, and a random one would make every\n * restored notification a new notification.\n */\nexport function updateId(event) {\n return [event?.bugId || 'report', event?.kind || 'status', event?.at || ''].join('|');\n}\n\n/** Unread updates, oldest first. Never throws — a corrupt store reads as empty. */\nexport function readUnread() {\n try {\n const raw = window.localStorage?.getItem(UNREAD_KEY);\n const list = raw ? JSON.parse(raw) : [];\n return Array.isArray(list) ? list.filter((u) => u && typeof u.id === 'string') : [];\n } catch {\n return [];\n }\n}\n\nfunction writeUnread(list) {\n try {\n if (list.length) window.localStorage?.setItem(UNREAD_KEY, JSON.stringify(list));\n else window.localStorage?.removeItem(UNREAD_KEY);\n } catch { /* storage unavailable — unread then lasts only for this page */ }\n}\n\n/**\n * Hold an update until it is read. Returns the stored record, which is the\n * frame the API sent plus the id everything else refers to it by.\n *\n * Re-delivery of one already held replaces it in place rather than stacking a\n * second copy: a reconnect that repeats a frame must not double the badge.\n */\nexport function rememberUpdate(event) {\n const record = { ...event, id: updateId(event) };\n const list = readUnread().filter((u) => u.id !== record.id);\n list.push(record);\n writeUnread(list.slice(-UNREAD_LIMIT));\n return record;\n}\n\n/** Read. Returns what is still unread. */\nexport function forgetUpdate(id) {\n const list = readUnread().filter((u) => u.id !== id);\n writeUnread(list);\n return list;\n}\n\n/**\n * Everything about one report, read.\n *\n * Opening the report *is* reading its updates — the widget calls this when the\n * thread opens — and leaving a \"your report was fixed\" card up over the screen\n * where they are reading that it was fixed would be the notification arguing\n * with itself.\n */\nexport function forgetReportUpdates(bugId) {\n const list = readUnread().filter((u) => u.bugId !== bugId);\n writeUnread(list);\n return list;\n}\n\n/** All read. */\nexport function forgetAllUpdates() {\n writeUnread([]);\n return [];\n}\n\n/**\n * Pull complete SSE frames out of a growing buffer, returning the partial one\n * left over. Frames are separated by a blank line and one can span several\n * `data:` lines, so this cannot split on newlines — a payload cut in half by\n * the network would parse as garbage.\n */\nfunction drain(buffer, onFrame) {\n let rest = buffer;\n let split = rest.indexOf('\\n\\n');\n while (split !== -1) {\n const frame = rest.slice(0, split);\n rest = rest.slice(split + 2);\n\n const data = frame\n .split('\\n')\n .filter((line) => line.startsWith('data:'))\n .map((line) => line.slice(5).trim())\n .join('\\n');\n\n if (data) {\n try {\n onFrame(JSON.parse(data));\n } catch { /* a malformed frame is skipped, never fatal to the stream */ }\n }\n split = rest.indexOf('\\n\\n');\n }\n return rest;\n}\n\n/**\n * Everything this browser has reported, with where each one got to.\n *\n * Same credential as the stream: the token handed back when a report was\n * filed. Returns an empty history rather than throwing when nothing has been\n * filed here — \"you have not reported anything\" is an answer, not an error.\n */\nexport async function fetchMyReports({ apiBase }) {\n const token = readToken();\n if (!token) return { reporter: null, reports: [] };\n\n const res = await fetch(`${apiBase}/ingest/watch/reports`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n // Expired or revoked. Drop it so a later report can replace it, and answer\n // as though nothing had been filed — which, as far as this browser can now\n // prove, is true.\n if (res.status === 401) {\n clearToken();\n return { reporter: null, reports: [] };\n }\n if (!res.ok) throw new Error(`[BugCatch] could not load your reports (HTTP ${res.status})`);\n return res.json();\n}\n\n/**\n * One report in full, including how it got to where it is.\n *\n * The list endpoint answers \"what have I reported and where is each one\"; this\n * one answers \"what has actually happened to this\", which is the question\n * somebody opening a report is asking. Same credential as the rest.\n */\nexport async function fetchReport({ apiBase, bugId }) {\n const token = readToken();\n if (!token) return null;\n\n const res = await fetch(`${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (res.status === 401) {\n clearToken();\n return null;\n }\n if (res.status === 404) return null;\n if (!res.ok) throw new Error(`[BugCatch] could not load that report (HTTP ${res.status})`);\n return res.json();\n}\n\n/**\n * Write back on a report this browser filed.\n *\n * The other half of the feed. Everything above tells the reporter what happened\n * to their report; this is how they answer — \"still broken on Safari\", \"here is\n * the file that breaks it\" — without leaving the app they hit the bug in.\n *\n * Same credential as the rest: the watch token, which is proof of having filed\n * this. Resolves to the stored message, so the caller can render exactly what\n * the server kept rather than what it hoped it sent.\n */\nexport async function sendMessage({ apiBase, bugId, body, attachments }) {\n const token = readToken();\n if (!token) throw new Error('[BugCatch] nothing filed from this browser yet.');\n\n const res = await fetch(\n `${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}/messages`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },\n body: JSON.stringify({\n ...(body ? { body } : {}),\n ...(attachments?.length ? { attachments } : {}),\n }),\n },\n );\n\n if (res.status === 401) {\n clearToken();\n throw new Error('[BugCatch] this browser can no longer prove it filed that report.');\n }\n if (!res.ok) {\n // The API's own message is worth surfacing — \"Unsupported file type\" and\n // \"Write something, or attach a file\" are both things the sender can fix,\n // and an HTTP code is not.\n let detail = '';\n try {\n const problem = await res.json();\n detail = Array.isArray(problem.message) ? problem.message.join(', ') : problem.message || '';\n } catch { /* non-JSON error */ }\n throw new Error(detail || `[BugCatch] could not send that (HTTP ${res.status}).`);\n }\n return res.json();\n}\n\n/**\n * Whether this project can take a call instead of a form.\n *\n * Asked before anything has been filed, so this one is authenticated by the\n * publishable API key rather than by a watch token — there is no report yet to\n * hold a token for. It answers a fact about the project's configuration and\n * nothing about any report, which is all a publishable key should ever be able\n * to learn.\n */\nexport async function callAvailableForProject({ apiBase, apiKey, projectId }) {\n return Boolean(await callKindForProject({ apiBase, apiKey, projectId }));\n}\n\n/**\n * Which kind of call this project can take: 'live', or null for none.\n *\n * The same round trip as above, answering the question that actually matters.\n * Asking only \"can it call\" is what drew a call button on a deployment that did\n * not have one: pressing it filed the report and then failed the call, so the\n * reporter got a ticket they had not asked for and nobody's dashboard rang.\n *\n * There is one kind of call now — the team's dashboards ring and a colleague\n * picks up — so this has one non-null answer. The shape is kept because callers\n * are written against it and because a deployment answering something else is a\n * deployment this SDK must not place a call on.\n */\nexport async function callKindForProject({ apiBase, apiKey, projectId }) {\n const kinds = await callKindsForProject({ apiBase, apiKey, projectId });\n return kinds.live ? 'live' : null;\n}\n\n/**\n * Which kinds of call this project can take, as booleans.\n *\n * `agent` is always false and is still here because the API still sends it: it\n * was a triage agent that answered instantly instead of a person, and it is\n * gone. Reading it rather than dropping it keeps this honest about what the\n * response actually said.\n *\n * An older API answers `available` and a bare `kind` with no `kinds` object.\n * Anything that is not 'live' there is a kind this SDK can no longer place, so\n * it reads as no call at all rather than as a call that would fail on connect.\n */\nexport async function callKindsForProject({ apiBase, apiKey, projectId }) {\n const none = { agent: false, live: false };\n try {\n const res = await fetch(\n `${apiBase}/ingest/calls?projectId=${encodeURIComponent(projectId)}`,\n { headers: { 'X-BugCatch-Key': apiKey } },\n );\n if (!res.ok) return none;\n const body = await res.json();\n if (!body?.available) return none;\n if (body.kinds && typeof body.kinds === 'object') {\n return { agent: false, live: Boolean(body.kinds.live) };\n }\n return { agent: false, live: body.kind === 'live' };\n } catch {\n return none;\n }\n}\n\n/**\n * File a report by calling about it, and get back the socket to talk on.\n *\n * The report is written before the call starts rather than after it ends, so\n * whatever this page was doing survives a call that drops or a reporter who\n * changes their mind. `realtime_url` is null when the report was filed but no\n * call could be placed — which is a success for the report and a failure only\n * for the call, and the caller is expected to tell them apart.\n */\nexport async function reportByCall({ apiBase, apiKey, token, payload }) {\n const res = await fetch(`${apiBase}/ingest/calls`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-BugCatch-Key': apiKey,\n // The same header `report()` sends, and for the same reason: this route\n // files a report too. Without it a project that requires sign-in refuses\n // the call outright — and the reporter is told to sign in by a panel that\n // is saying, an inch below the message, who they are signed in as.\n ...(token ? { 'X-BugCatch-Session': token } : {}),\n },\n body: JSON.stringify(payload),\n });\n\n if (!res.ok) {\n let detail = '';\n try {\n const problem = await res.json();\n detail = Array.isArray(problem.message) ? problem.message.join(', ') : problem.message || '';\n } catch { /* non-JSON error */ }\n const err = new Error(\n detail || `[BugCatch] the call could not be started (HTTP ${res.status}).`,\n );\n // Carried so a session the server has stopped recognising can be told from\n // every other refusal and forgotten, rather than pressing Call into the\n // same 401 again.\n err.status = res.status;\n throw err;\n }\n return res.json();\n}\n\n/**\n * Whether this report can be talked through with a person on the team.\n *\n * Asked before anything is rendered, so a button never appears on a deployment\n * with no sphoro.voice configured, on a workspace that has not switched calling\n * on, or on a report that has already had its allowance. False for every\n * failure too: a call is an extra, and an extra that cannot be offered quietly\n * is better than an error about a feature nobody asked for.\n */\nexport async function liveCallAvailable({ apiBase, bugId }) {\n const token = readToken();\n if (!token) return false;\n\n try {\n const res = await fetch(\n `${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}/live-call`,\n { headers: { Authorization: `Bearer ${token}` } },\n );\n if (!res.ok) return false;\n const body = await res.json();\n return Boolean(body?.offered);\n } catch {\n return false;\n }\n}\n\n/**\n * Ring the team, and get back the socket to wait on.\n *\n * Carries `rings_until` as well as the socket, because the browser is what\n * decides the ringing is over — it is the one client guaranteed to still be\n * listening at that moment, and a server-side timer would have to guess whether\n * anybody was still there to hear it stop.\n */\nexport async function requestLiveCall({ apiBase, bugId }) {\n const token = readToken();\n if (!token) throw new Error('[BugCatch] nothing filed from this browser yet.');\n\n const res = await fetch(\n `${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}/live-call`,\n { method: 'POST', headers: { Authorization: `Bearer ${token}` } },\n );\n\n if (res.status === 401) {\n clearToken();\n throw new Error('[BugCatch] this browser can no longer prove it filed that report.');\n }\n if (!res.ok) {\n // The API's own message is the one worth showing: \"there is already a call\n // in progress about this report\" is something the reader can act on, and a\n // status code is not.\n let detail = '';\n try {\n const problem = await res.json();\n detail = Array.isArray(problem.message) ? problem.message.join(', ') : problem.message || '';\n } catch { /* non-JSON error */ }\n throw new Error(detail || `[BugCatch] the call could not be started (HTTP ${res.status}).`);\n }\n return res.json();\n}\n\n/**\n * Say that nobody picked up.\n *\n * Best-effort and deliberately unreported: the reporter's call is over either\n * way, and the only thing this changes is what the team's thread says about it\n * afterwards. sphoro.voice refuses it while the call is genuinely still\n * ringing, so a browser with a fast clock cannot hang up on somebody halfway\n * through answering.\n */\nexport async function giveUpLiveCall({ apiBase, bugId, callId }) {\n const token = readToken();\n if (!token) return;\n\n try {\n await fetch(\n `${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}`\n + `/live-call/${encodeURIComponent(callId)}/give-up`,\n { method: 'POST', headers: { Authorization: `Bearer ${token}` } },\n );\n } catch { /* the call is over regardless */ }\n}\n\n/**\n * Take a call the team placed, and get back the socket to hold it on.\n *\n * The other direction, and the only call this SDK does not start. A member\n * pressed a button on the report and the update stream put a card on screen;\n * this is what happens when the person reading it presses Answer, and nothing\n * connects — and no microphone opens — until they do.\n *\n * 409 is an ordinary outcome and says so plainly: the ringing stopped while the\n * button was being pressed, or this is a second tab losing to the one the\n * person actually answered in.\n */\nexport async function answerIncomingCall({ apiBase, bugId, callId }) {\n const token = readToken();\n if (!token) throw new Error('[BugCatch] nothing filed from this browser yet.');\n\n const res = await fetch(\n `${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}`\n + `/live-call/${encodeURIComponent(callId)}/answer`,\n { method: 'POST', headers: { Authorization: `Bearer ${token}` } },\n );\n\n if (res.status === 401) {\n clearToken();\n throw new Error('[BugCatch] this browser can no longer prove it filed that report.');\n }\n if (res.status === 409) {\n throw new Error('[BugCatch] that call is no longer ringing.');\n }\n if (!res.ok) {\n let detail = '';\n try {\n const problem = await res.json();\n detail = Array.isArray(problem.message) ? problem.message.join(', ') : problem.message || '';\n } catch { /* non-JSON error */ }\n throw new Error(detail || `[BugCatch] the call could not be answered (HTTP ${res.status}).`);\n }\n return res.json();\n}\n\n/**\n * Or don't.\n *\n * Sent rather than left to time out, because the two are different facts on the\n * report: somebody who was not at the screen invites being rung again in ten\n * minutes and somebody who declined does not. Best-effort and unreported — the\n * card is going away either way, and there is nothing the reader could do about\n * a failure here.\n */\nexport async function declineIncomingCall({ apiBase, bugId, callId }) {\n const token = readToken();\n if (!token) return;\n\n try {\n await fetch(\n `${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}`\n + `/live-call/${encodeURIComponent(callId)}/decline`,\n { method: 'POST', headers: { Authorization: `Bearer ${token}` } },\n );\n } catch { /* they have declined; nothing here can change that */ }\n}\n\n/**\n * Watch for updates until stopped. Returns a stop function.\n *\n * Reconnects with backoff, which starts slower than the dashboard's: this runs\n * on someone else's site, and a tight retry loop against their network is not\n * ours to spend.\n */\nexport function watchUpdates({ apiBase, onUpdate }) {\n let running = true;\n let attempt = 0;\n let controller = null;\n\n async function connect() {\n const token = readToken();\n if (!token) throw new Error('nothing filed from this browser yet');\n\n controller = new AbortController();\n const res = await fetch(`${apiBase}/ingest/watch/stream`, {\n headers: { Authorization: `Bearer ${token}`, Accept: 'text/event-stream' },\n signal: controller.signal,\n });\n\n // The token expired or was revoked. Retrying cannot fix that, and the\n // stale token is dropped so a later report can replace it.\n if (res.status === 401) {\n running = false;\n clearToken();\n throw new Error('subscription no longer valid');\n }\n if (!res.ok || !res.body) throw new Error(`stream failed (${res.status})`);\n\n const openedAt = Date.now();\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n try {\n for (;;) {\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n buffer = drain(buffer, (event) => {\n if (event.type !== 'ping') {\n try {\n onUpdate(event);\n } catch { /* a host handler that throws must not kill the stream */ }\n }\n });\n }\n } finally {\n // In a `finally` so a stream that ends by throwing is judged the same way\n // as one that ends cleanly — both have to earn the reset by lasting.\n if (Date.now() - openedAt >= STABLE_MS) attempt = 0;\n }\n }\n\n (async () => {\n while (running) {\n try {\n await connect();\n } catch (err) {\n if (err?.name === 'AbortError') return;\n }\n if (!running) return;\n const wait = BACKOFF_MS[Math.min(attempt, BACKOFF_MS.length - 1)];\n attempt += 1;\n await new Promise((resolve) => setTimeout(resolve, wait));\n }\n })();\n\n return () => {\n running = false;\n controller?.abort();\n controller = null;\n };\n}\n\nconst STATUS_COPY = {\n OPEN: 'is open',\n IN_PROGRESS: 'is being worked on',\n RESOLVED: 'has been fixed',\n CLOSED: 'has been closed',\n};\n\n/**\n * The colour each kind of update wears, so a fix and a severity change are not\n * the same anonymous grey card. Keyed on the update kind the API sends.\n */\nconst KIND_ACCENT = {\n status: null, // falls back to the host's configured accent\n severity: '#e8912a',\n assignment: '#4da3ff',\n reply: '#35b87f',\n};\n\n/**\n * The headline for an update.\n *\n * The API writes this line — it knows what changed and phrases it for the\n * person who reported it, and one copy of that wording serves the toast, the\n * timeline and the portal. `summary` is preferred for exactly that reason; the\n * status fallback below is only for a page still talking to an older API.\n */\nexport function updateHeadline(event) {\n if (event?.summary) return event.summary;\n return `Your report ${STATUS_COPY[event?.status] || 'was updated'}`;\n}\n\n/** The second line: a shared reply reads as the reply, everything else names the report. */\nfunction updateDetail(event) {\n if (event?.kind === 'reply' && event.reply?.body) {\n const who = event.reply.author ? `${event.reply.author}: ` : '';\n return `${who}${event.reply.body}`;\n }\n return event?.title || '';\n}\n\n/** How long ago, in the fewest words. Persisted cards outlive the moment. */\nfunction agoLabel(at) {\n const then = Date.parse(at || '');\n if (!Number.isFinite(then)) return '';\n const mins = Math.round((Date.now() - then) / 60000);\n if (mins < 1) return 'just now';\n if (mins < 60) return `${mins}m ago`;\n const hours = Math.round(mins / 60);\n if (hours < 24) return `${hours}h ago`;\n const days = Math.round(hours / 24);\n return days === 1 ? 'yesterday' : `${days}d ago`;\n}\n\n// At most this many cards on screen at once; the rest are counted on a line\n// under them. Somebody who has been away for a week should find a corner they\n// can still see their app through.\nconst MAX_CARDS = 3;\n\n/**\n * How far above the corner to start, so the stack clears the widget's launcher\n * rather than sitting on it. Measured rather than assumed: the dock is a\n * launcher, sometimes with a theme switch above it, and its height is the only\n * honest source for how much room it takes.\n */\nfunction dockOffset() {\n try {\n const host = document.querySelector('[data-bugcatch=\"widget\"]');\n const dock = host?.shadowRoot?.querySelector('.dock');\n const height = dock?.getBoundingClientRect().height || 0;\n return height ? Math.round(height) + 12 : 0;\n } catch {\n return 0;\n }\n}\n\n/**\n * The built-in notifications, for integrations that do not want to render their\n * own.\n *\n * A card per unread update, stacked in the same corner as the widget and\n * staying there until the reporter marks it read. Nothing here is on a timer:\n * the whole point of telling somebody their bug was fixed is that they find\n * out, and a card that removes itself after eight seconds only reaches the\n * people who happened to be looking.\n *\n * Everything is inline-styled and scoped to elements this owns: the SDK runs on\n * someone else's page and must not depend on, or contribute to, their CSS. It\n * sits *below* the widget's own layers, so an open report panel covers it\n * instead of a notification floating over the report it is about.\n */\nexport function createUpdateNotifier({\n accent = '#4F46E5',\n position = 'bottom-right',\n onRead = null,\n onReadAll = null,\n onCall = null,\n} = {}) {\n const [vertical, horizontal] = String(position).split('-');\n const atTop = vertical === 'top';\n const atLeft = horizontal === 'left';\n // id -> card element, so an update already on screen is left alone. Rebuilding\n // the stack on every frame would restart the animations and take the button\n // out from under a finger already on its way to it.\n const cards = new Map();\n let stack = null;\n let more = null;\n\n function ensureStack() {\n if (stack?.isConnected) return stack;\n stack = document.createElement('div');\n stack.setAttribute('data-bugcatch', 'updates');\n stack.style.cssText = [\n 'position:fixed',\n `${atTop ? 'top' : 'bottom'}:${20 + dockOffset()}px`,\n `${atLeft ? 'left' : 'right'}:20px`,\n // Under the widget's dock and its panel scrim, both of which are higher.\n // A notification is worth interrupting a page for, not the report being\n // read on top of it.\n 'z-index:2147482998',\n 'display:flex',\n 'flex-direction:column',\n 'gap:10px',\n `align-items:${atLeft ? 'flex-start' : 'flex-end'}`,\n 'width:min(320px,calc(100vw - 40px))',\n // The gaps between cards belong to the page underneath.\n 'pointer-events:none',\n ].join(';');\n document.body.appendChild(stack);\n return stack;\n }\n\n /** Fade out and go. Instant for a card that has not been painted yet. */\n function drop(el) {\n if (!el._bcShown) { el.remove(); return; }\n el.style.opacity = '0';\n el.style.transform = `translateY(${atTop ? -8 : 8}px)`;\n setTimeout(() => el.remove(), 250);\n }\n\n function button(label, primary) {\n const el = document.createElement('button');\n el.type = 'button';\n el.textContent = label;\n el.style.cssText = [\n 'padding:5px 11px',\n `border:${primary ? '0' : '1px solid rgba(236,238,242,.24)'}`,\n 'border-radius:6px',\n `background:${primary ? accent : 'transparent'}`,\n `color:${primary ? '#fff' : '#eceef2'}`,\n 'font:600 12px/1.4 inherit',\n 'cursor:pointer',\n 'white-space:nowrap',\n ].join(';');\n return el;\n }\n\n function card(record) {\n const el = document.createElement('div');\n el.setAttribute('role', 'status');\n el.style.cssText = [\n 'box-sizing:border-box',\n 'width:100%',\n 'padding:12px 14px',\n 'border-radius:10px',\n 'background:#1b1e26',\n 'color:#eceef2',\n 'font:13px/1.5 -apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif',\n 'box-shadow:0 12px 30px rgba(0,0,0,0.45)',\n `border-left:3px solid ${KIND_ACCENT[record?.kind] || accent}`,\n 'opacity:0',\n `transform:translateY(${atTop ? -8 : 8}px)`,\n 'transition:opacity .2s ease,transform .2s ease',\n 'pointer-events:auto',\n ].join(';');\n\n const title = document.createElement('div');\n title.style.cssText = 'font-weight:600;margin-bottom:2px';\n title.textContent = updateHeadline(record);\n\n const body = document.createElement('div');\n // A reply is the one kind whose detail is worth more room than a title, and\n // it is the only text here the team wrote rather than the reporter.\n body.style.cssText = record?.kind === 'reply'\n ? 'color:#a4abba;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden'\n : 'color:#a4abba';\n // textContent, not innerHTML: this is text a person typed, being written\n // back into somebody else's page.\n body.textContent = updateDetail(record);\n\n const actions = document.createElement('div');\n actions.style.cssText =\n 'display:flex;align-items:center;gap:8px;margin-top:10px;flex-wrap:wrap';\n\n const when = document.createElement('span');\n when.style.cssText = 'color:#7b8394;font-size:11px;margin-right:auto';\n when.textContent = agoLabel(record?.at);\n\n const read = button('Mark as read', false);\n read.setAttribute('aria-label', `Mark as read: ${updateHeadline(record)}`);\n read.addEventListener('click', () => onRead?.(record.id));\n\n actions.append(when, read);\n\n // The call offer, when there is one. A button rather than a call: a page\n // somebody may have walked away from must not raise a microphone prompt by\n // itself, and a reporter who would rather type has lost nothing by ignoring\n // this.\n if (record?.call?.offered && typeof onCall === 'function') {\n const talk = button('Talk it through', true);\n talk.addEventListener('click', () => onCall(record));\n actions.insertBefore(talk, read);\n }\n\n el.append(title, body, actions);\n return el;\n }\n\n /** The line under the stack when there is more unread than fits. */\n function moreRow(count) {\n const el = document.createElement('div');\n el.style.cssText = [\n 'box-sizing:border-box',\n 'width:100%',\n 'display:flex',\n 'align-items:center',\n 'gap:8px',\n 'padding:8px 12px',\n 'border-radius:10px',\n 'background:#1b1e26',\n 'color:#a4abba',\n 'font:12px/1.4 -apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif',\n 'box-shadow:0 12px 30px rgba(0,0,0,0.45)',\n 'pointer-events:auto',\n ].join(';');\n\n const label = document.createElement('span');\n label.style.cssText = 'margin-right:auto';\n label.textContent = `${count} more update${count === 1 ? '' : 's'}`;\n\n const all = button('Mark all as read', false);\n all.style.cssText += ';padding:4px 9px';\n all.addEventListener('click', () => onReadAll?.());\n\n el.append(label, all);\n return el;\n }\n\n return {\n /**\n * Draw what is unread, oldest first.\n *\n * The newest card sits nearest the corner the widget is in, which is the\n * corner the reporter's eye is already trained on — so the stack grows away\n * from the launcher rather than over it.\n */\n render(list) {\n const unread = Array.isArray(list) ? list : [];\n if (!unread.length) {\n for (const el of cards.values()) drop(el);\n cards.clear();\n more?.remove();\n more = null;\n // The container goes too: an empty fixed element in someone else's page\n // is one more thing for their own layout debugging to trip over.\n stack?.remove();\n stack = null;\n return;\n }\n\n const visible = unread.slice(-MAX_CARDS);\n const shown = new Set(visible.map((u) => u.id));\n const root = ensureStack();\n\n for (const [id, el] of cards) {\n if (!shown.has(id)) { drop(el); cards.delete(id); }\n }\n\n const fresh = [];\n for (const record of visible) {\n if (cards.has(record.id)) continue;\n const el = card(record);\n cards.set(record.id, el);\n fresh.push(el);\n }\n\n // Nearest the corner is the newest, so a top-anchored stack is drawn in\n // the opposite order to a bottom-anchored one.\n const ordered = atTop ? [...visible].reverse() : visible;\n for (const record of ordered) root.appendChild(cards.get(record.id));\n\n const hidden = unread.length - visible.length;\n if (hidden > 0) {\n more?.remove();\n more = moreRow(hidden);\n // Furthest from the corner, past the oldest card on screen.\n if (atTop) root.appendChild(more);\n else root.insertBefore(more, root.firstChild);\n } else {\n more?.remove();\n more = null;\n }\n\n // One frame later, so the transition has a start to run from. Cards read\n // as unpainted until this fires, which is what lets an update marked read\n // in the same tick — by the widget, with the report already open — leave\n // without a card flashing up and fading out behind it.\n requestAnimationFrame(() => {\n for (const el of fresh) {\n if (!el.isConnected) continue;\n el._bcShown = true;\n el.style.opacity = '1';\n el.style.transform = 'translateY(0)';\n }\n });\n },\n\n destroy() {\n cards.clear();\n more = null;\n stack?.remove();\n stack = null;\n },\n };\n}\n\n/**\n * What this project is willing to say is already broken.\n *\n * Asked when the widget opens, before anything is typed. Failure is silent and\n * answers an empty list: this is a help panel in front of a form, and a page\n * that cannot reach the API still has to let somebody report the thing they\n * came to report.\n */\nexport async function fetchKnownIssues({ apiBase, apiKey, projectId, sessionToken }) {\n try {\n const res = await fetch(\n `${apiBase}/ingest/known-issues?projectId=${encodeURIComponent(projectId)}`,\n { headers: knownIssueHeaders(apiKey, sessionToken) },\n );\n if (!res.ok) return [];\n const body = await res.json();\n return Array.isArray(body?.issues) ? body.issues : [];\n } catch {\n return [];\n }\n}\n\n/**\n * The listed issues that look like what somebody is typing.\n *\n * Takes an AbortSignal because it runs on a keystroke debounce: without one, a\n * fast typist has six of these in flight and the panel renders whichever\n * happens to land last, which is not necessarily the one matching what is now\n * in the box.\n */\nexport async function matchKnownIssues({ apiBase, apiKey, projectId, q, sessionToken, signal }) {\n try {\n const url = `${apiBase}/ingest/known-issues/match`\n + `?projectId=${encodeURIComponent(projectId)}&q=${encodeURIComponent(q)}`;\n const res = await fetch(url, { headers: knownIssueHeaders(apiKey, sessionToken), signal });\n if (!res.ok) return [];\n const body = await res.json();\n return Array.isArray(body?.issues) ? body.issues : [];\n } catch {\n // Includes the AbortError of a superseded keystroke, which is the ordinary\n // case rather than a failure.\n return [];\n }\n}\n\n/**\n * Say an existing report is happening to you too.\n *\n * Throws, unlike the two reads above: this one is a button somebody pressed,\n * and a press that silently does nothing is worse than an error they can read.\n * The watch token comes back for the same reason it does on a filed report —\n * it is what lets this browser hear that the bug was fixed.\n */\nexport async function joinKnownIssue({\n apiBase, apiKey, projectId, bugId, reporterEmail, reporterName, metadata, logs, sessionToken,\n}) {\n const res = await fetch(\n `${apiBase}/ingest/known-issues/${encodeURIComponent(bugId)}/join`,\n {\n method: 'POST',\n headers: knownIssueHeaders(apiKey, sessionToken),\n body: JSON.stringify({ projectId, reporterEmail, reporterName, metadata, logs }),\n },\n );\n if (!res.ok) throw new Error(await readJoinError(res));\n const body = await res.json();\n if (body?.watchToken) writeToken(body.watchToken);\n return body;\n}\n\nfunction knownIssueHeaders(apiKey, sessionToken) {\n return {\n 'Content-Type': 'application/json',\n 'X-BugCatch-Key': apiKey,\n ...(sessionToken ? { 'X-BugCatch-Session': sessionToken } : {}),\n };\n}\n\nasync function readJoinError(res) {\n try {\n const body = await res.json();\n const message = Array.isArray(body.message) ? body.message.join(', ') : body.message;\n if (message) return message;\n } catch { /* non-JSON error */ }\n return `Could not add you to this issue (HTTP ${res.status})`;\n}\n\nexport async function verifyFix({ apiBase, bugId, ...feedback }) {\n const token = readToken();\n if (!token) throw new Error('This browser cannot access that report.');\n const res = await fetch(`${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}/verification`, {\n method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }, body: JSON.stringify(feedback),\n });\n const data = await res.json().catch(() => ({}));\n if (!res.ok) throw new Error(data.message || 'Could not record feedback. Please refresh and try again.');\n return data;\n}\n","/**\n * Reading the reporter's own text for something that identifies their record.\n *\n * A browser-side twin of bugcatch-api/src/settings/reference-extract.ts, and\n * deliberately only the half this side needs. The server does the extraction\n * that counts — what a payload claims it found is not evidence, and the value\n * an investigation eventually joins on is the one the API read out of the text\n * it stored. This copy exists to answer one question, locally and instantly:\n *\n * does what this person has already typed contain an identifier, or do we\n * have to interrupt them for one?\n *\n * Getting that wrong in the lenient direction costs an unnecessary question.\n * Getting it wrong in the strict direction means asking a reporter who has\n * already told us, in the sentence above the box, which is the fastest way to\n * make somebody close a bug reporter and not come back. So the matching rule\n * here is the same rule, run over the same patterns, which the API ships with\n * the field list precisely so that both sides can agree.\n *\n * The patterns arrive over the wire and are compiled here. That is a thing to\n * be deliberate about rather than nervous about: they are shapes, not secrets —\n * \"a loan number looks like LN- followed by digits\" is not a fact worth hiding\n * from somebody already looking at their own loan number — and the alternative\n * is a round trip per keystroke or a question for everybody.\n */\n\nconst MAX_SCAN_CHARS = 20000;\nconst MAX_VALUE_CHARS = 120;\nconst MAX_MATCHES = 200;\n\n/**\n * Compile, or null.\n *\n * Null on anything that will not compile, which in a browser is a real case\n * rather than a defensive one: a regex feature one engine lacks throws here\n * rather than failing to match. A field that cannot be compiled is simply not\n * scanned, the reporter gets asked, and the server — which compiled it fine —\n * still reads the text. Degrading to the question is the whole design.\n */\nfunction compile(source) {\n if (typeof source !== 'string' || !source.trim() || source.length > 400) return null;\n try {\n return new RegExp(source.trim(), 'gi');\n } catch {\n return null;\n }\n}\n\nconst tidy = (value) => String(value).replace(/\\s+/g, ' ').trim().slice(0, MAX_VALUE_CHARS);\n\n/** Group 1 where the pattern has one, the whole match otherwise. */\nfunction valueOf(match) {\n const raw = match.length > 1 && match[1] !== undefined ? match[1] : match[0];\n if (typeof raw !== 'string' || !raw.trim()) return null;\n // The span claimed is the whole match, never the capture: a labelled rule has\n // eaten the words in front of the value too, and leaving them unclaimed lets\n // a later field match them again.\n return { value: tidy(raw), start: match.index, end: match.index + match[0].length };\n}\n\n/**\n * Every configured identifier found in the given text.\n *\n * Fields are taken in the order the project arranged them and a span an earlier\n * field claimed is not offered to a later one — which is what keeps the rule for\n * \"alternate number\" and the rule for \"mobile number\", which match the same ten\n * digits, from both claiming it.\n */\nexport function matchReferences(fields, texts) {\n const found = [];\n const seen = new Set();\n\n for (const block of texts || []) {\n const text = typeof block === 'string' ? block.slice(0, MAX_SCAN_CHARS) : '';\n if (!text) continue;\n\n const claimed = [];\n const overlaps = (a, b) => claimed.some(([s, e]) => a < e && b > s);\n\n for (const field of fields || []) {\n if (!field || field.scan === false) continue;\n const re = compile(field.pattern);\n if (!re) continue;\n\n let match;\n let guard = 0;\n re.lastIndex = 0;\n while ((match = re.exec(text)) !== null) {\n if (++guard > MAX_MATCHES) break;\n // exec does not advance past a zero-width match, so this would spin.\n if (match[0] === '') { re.lastIndex += 1; continue; }\n\n const hit = valueOf(match);\n if (hit && !overlaps(hit.start, hit.end)) {\n claimed.push([hit.start, hit.end]);\n const dedupe = `${field.key}:${hit.value.toLowerCase()}`;\n if (!seen.has(dedupe)) {\n seen.add(dedupe);\n found.push({ key: field.key, label: field.label, value: hit.value });\n }\n }\n }\n }\n }\n\n return found;\n}\n\n/**\n * Whether what somebody typed into the box is the shape that field asks for.\n *\n * Anchored, unlike the scan above: reading prose means finding a value inside a\n * sentence, and checking an answer means the answer *is* the value. `valuePattern`\n * where the field has one — a rule that finds \"alternate number: 98…\" in prose\n * needs the words, and a box already headed \"Alternate number\" will not have\n * them typed into it again.\n *\n * Returns the tidied value, or null. Advisory only: the widget uses it to say\n * \"that does not look like a loan number\" before sending, and the API keeps a\n * value that fails it rather than dropping what the reporter believes is their\n * reference.\n */\nexport function validateReference(field, raw) {\n const value = typeof raw === 'string' ? raw.trim() : '';\n if (!value || value.length > MAX_VALUE_CHARS) return null;\n\n const re = compile(field && (field.valuePattern || field.pattern));\n if (!re) return null;\n\n let match;\n let guard = 0;\n re.lastIndex = 0;\n while ((match = re.exec(value)) !== null) {\n if (++guard > MAX_MATCHES) break;\n if (match[0] === '') { re.lastIndex += 1; continue; }\n // One character of slack at the front and only there, for a pattern whose\n // left boundary consumes rather than looks behind — see the phone rule,\n // written that way so it compiles in older Safari.\n const toEnd = match.index + match[0].length === value.length;\n const atEdge = match.index === 0 || match.index === 1;\n if (toEnd && atEdge) {\n const hit = valueOf(match);\n if (hit) return hit.value;\n }\n }\n return null;\n}\n\n/**\n * What this project needs in order to look a report up.\n *\n * Every failure answers `{ policy: 'off', fields: [] }`, which is the answer\n * that draws nothing. A widget that could not reach this route must not be a\n * widget that refuses to send a report — the whole feature is a help, and the\n * server reads the text either way.\n */\nexport async function referenceFields({ apiBase, apiKey, projectId }) {\n const off = { policy: 'off', fields: [] };\n try {\n const res = await fetch(\n `${apiBase}/ingest/reference-fields?projectId=${encodeURIComponent(projectId)}`,\n { headers: { 'X-BugCatch-Key': apiKey } },\n );\n if (!res.ok) return off;\n const body = await res.json();\n const policy = body.policy === 'optional' || body.policy === 'required' ? body.policy : 'off';\n const fields = Array.isArray(body.fields) ? body.fields : [];\n return fields.length ? { policy, fields } : off;\n } catch {\n return off;\n }\n}\n","// The floating \"Report a bug\" button and its form. Rendered in a shadow root\n// so the host page's CSS can't leak in and break it (or vice versa).\n//\n// Everything the panel needs is declared here as custom properties, which is\n// what makes theming a config option rather than a fork: the host element\n// carries the accent colour and the light/dark choice, and the rules below\n// only ever read variables.\n\nimport {\n captureScreenshot, startRecording, screenshotSupported, captureSupported,\n startVoiceNote, voiceSupported,\n humanSize, humanDuration, normalizeMime, UPLOAD_MIMES, REPORT_MIMES,\n} from './capture.js';\nimport { draftKey, draftOperation, readDraft } from './drafts.js';\nimport { readSession } from './session.js';\nimport { sendMessage } from './updates.js';\nimport { matchReferences, validateReference } from './references.js';\n\nconst POSITIONS = {\n 'bottom-right': 'bottom:20px;right:20px;',\n 'bottom-left': 'bottom:20px;left:20px;',\n 'top-right': 'top:20px;right:20px;',\n 'top-left': 'top:20px;left:20px;',\n};\n\nconst PRIORITIES = [\n { value: 'LOW', label: 'Low' },\n { value: 'MEDIUM', label: 'Medium' },\n { value: 'HIGH', label: 'High' },\n { value: 'CRITICAL', label: 'Critical' },\n];\n\nconst ICONS = {\n camera: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 8.5A1.5 1.5 0 0 1 4.5 7h2.2a1 1 0 0 0 .83-.45l.94-1.4A1 1 0 0 1 9.3 4.7h5.4a1 1 0 0 1 .83.45l.94 1.4a1 1 0 0 0 .83.45h2.2A1.5 1.5 0 0 1 21 8.5v9A1.5 1.5 0 0 1 19.5 19h-15A1.5 1.5 0 0 1 3 17.5z\"/><circle cx=\"12\" cy=\"13\" r=\"3.4\"/></svg>',\n video: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"6\" width=\"12.5\" height=\"12\" rx=\"2.2\"/><path d=\"M15.5 10.8l4.2-2.6a.6.6 0 0 1 .92.5v6.6a.6.6 0 0 1-.92.5l-4.2-2.6z\"/></svg>',\n paperclip: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M20 11.5l-7.6 7.6a4.6 4.6 0 0 1-6.5-6.5l8-8a3.2 3.2 0 0 1 4.5 4.5l-8 8a1.8 1.8 0 0 1-2.5-2.5l7.1-7.1\"/></svg>',\n close: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><path d=\"M6 6l12 12M18 6L6 18\"/></svg>',\n mic: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"9\" y=\"2.5\" width=\"6\" height=\"11\" rx=\"3\"/><path d=\"M5.5 11a6.5 6.5 0 0 0 13 0M12 17.5V21M9 21h6\"/></svg>',\n send: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 12l16-8-6 16-2.6-6.2L4 12Z\"/></svg>',\n stopSquare: '<svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><rect x=\"7\" y=\"7\" width=\"10\" height=\"10\" rx=\"2\"/></svg>',\n back: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M14.5 5.5L8 12l6.5 6.5\"/></svg>',\n phone: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6.2 3.5h3l1.5 3.7-2 1.4a11.5 11.5 0 0 0 5.7 5.7l1.4-2 3.7 1.5v3a1.8 1.8 0 0 1-2 1.8A15.6 15.6 0 0 1 4.4 5.5a1.8 1.8 0 0 1 1.8-2z\"/></svg>',\n phoneOff: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6.2 3.5h3l1.5 3.7-2 1.4a11.5 11.5 0 0 0 5.7 5.7l1.4-2 3.7 1.5v3a1.8 1.8 0 0 1-2 1.8A15.6 15.6 0 0 1 4.4 5.5a1.8 1.8 0 0 1 1.8-2z\"/><path d=\"M3 21L21 3\"/></svg>',\n bug: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M8 6a4 4 0 0 1 8 0\"/><rect x=\"7\" y=\"7.5\" width=\"10\" height=\"12\" rx=\"5\"/><path d=\"M7 11H3.5M7 15.5H4M17 11h3.5M17 15.5h3M12 8v11.5\"/></svg>',\n check: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M5 12.8l4.6 4.4L19 7\"/></svg>',\n retry: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M20 12a8 8 0 1 1-2.6-5.9M20 4v4.5h-4.5\"/></svg>',\n sun: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"4.2\"/><path d=\"M12 2.6v2.2M12 19.2v2.2M2.6 12h2.2M19.2 12h2.2M5.4 5.4l1.6 1.6M17 17l1.6 1.6M18.6 5.4L17 7M7 17l-1.6 1.6\"/></svg>',\n moon: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M20 13.4A8.2 8.2 0 1 1 10.6 4a6.6 6.6 0 0 0 9.4 9.4z\"/></svg>',\n auto: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"2.8\" y=\"4.4\" width=\"18.4\" height=\"12.6\" rx=\"2.2\"/><path d=\"M8.6 20.2h6.8\"/><path d=\"M12 4.4v12.6\"/><path d=\"M12 7.2a3.6 3.6 0 0 1 0 7.2z\" fill=\"currentColor\" stroke=\"none\"/></svg>',\n // The assistant's mark: a four-point spark, drawn wherever it speaks.\n sparkle: '<svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2.5c.5 4.6 2.9 7 7.5 7.5v.1c-4.6.5-7 2.9-7.5 7.5h-.1c-.5-4.6-2.9-7-7.5-7.5V10c4.6-.5 7-2.9 7.5-7.5z\"/><path d=\"M18.6 15.2c.2 1.9 1.2 2.9 3.1 3.1v.1c-1.9.2-2.9 1.2-3.1 3.1h-.1c-.2-1.9-1.2-2.9-3.1-3.1v-.1c1.9-.2 2.9-1.2 3.1-3.1z\" opacity=\".7\"/></svg>',\n chat: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 11.5a7.5 7 0 0 1 7.5-7h1a7.5 7 0 0 1 0 14H9l-4.2 2.6a.5.5 0 0 1-.76-.5l.5-3.1A7 7 0 0 1 4 11.5z\"/><path d=\"M8.5 11.5h.01M12 11.5h.01M15.5 11.5h.01\" stroke-width=\"2.6\"/></svg>',\n chevronDown: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6 9.5l6 6 6-6\"/></svg>',\n chevronRight: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M9.5 6l6 6-6 6\"/></svg>',\n alert: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 7.5v5.2M12 16.3h.01\"/></svg>',\n inbox: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3.5 13.5l2.6-7.2A1.5 1.5 0 0 1 7.5 5.3h9a1.5 1.5 0 0 1 1.4 1L20.5 13.5V18a1.5 1.5 0 0 1-1.5 1.5H5A1.5 1.5 0 0 1 3.5 18z\"/><path d=\"M3.5 13.5h4.8l1.2 2.2h5l1.2-2.2h4.8\"/></svg>',\n};\n\n/* The assistant's avatar, wherever it appears. */\nconst AVATAR = `<span class=\"avatar\" aria-hidden=\"true\">${ICONS.sparkle}</span>`;\n\n/* The sphoro lockup, as outlines rather than text.\n *\n * This SDK runs inside other people's pages, where Space Grotesk is not loaded\n * and cannot be assumed. Live <text> would render the mark in whatever the host\n * site happens to use — a different typeface on every customer. Outlines are\n * self-contained, so the mark is identical everywhere, at the cost of the path\n * data below.\n *\n * The BUGCATCH half reads --bc-brand-mark, not a bare --accent. Custom properties\n * are exempt from `all: initial`, so they cross the shadow boundary: a host page\n * that happens to define --accent would have repainted our mark in their brand\n * colour. The --bc- prefix is ours and no host is going to set it by accident. */\nconst BRAND = '<svg class=\"bc-brand\" viewBox=\"0 0 254 60\" fill=\"none\" role=\"img\" aria-label=\"sphoro BugCatch\"><g fill=\"currentColor\"><path transform=\"translate(0.00 40.00) scale(0.04000 -0.04000)\" d=\"M276.00 -14.00Q179.00 -14.00 117.00 28.00Q55.00 70.00 42.00 148.00L158.00 178.00Q165.00 143.00 181.50 123.00Q198.00 103.00 222.50 94.50Q247.00 86.00 276.00 86.00Q320.00 86.00 341.00 101.50Q362.00 117.00 362.00 140.00Q362.00 163.00 342.00 175.50Q322.00 188.00 278.00 196.00L250.00 201.00Q198.00 211.00 155.00 228.50Q112.00 246.00 86.00 277.00Q60.00 308.00 60.00 357.00Q60.00 431.00 114.00 470.50Q168.00 510.00 256.00 510.00Q339.00 510.00 394.00 473.00Q449.00 436.00 466.00 376.00L349.00 340.00Q341.00 378.00 316.50 394.00Q292.00 410.00 256.00 410.00Q220.00 410.00 201.00 397.50Q182.00 385.00 182.00 363.00Q182.00 339.00 202.00 327.50Q222.00 316.00 256.00 310.00L284.00 305.00Q340.00 295.00 385.50 278.50Q431.00 262.00 457.50 231.50Q484.00 201.00 484.00 149.00Q484.00 71.00 427.50 28.50Q371.00 -14.00 276.00 -14.00Z\"/><path transform=\"translate(19.56 40.00) scale(0.04000 -0.04000)\" d=\"M70.00 -200.00V496.00H194.00V436.00H212.00Q229.00 465.00 265.00 487.50Q301.00 510.00 368.00 510.00Q428.00 510.00 479.00 480.50Q530.00 451.00 561.00 394.00Q592.00 337.00 592.00 256.00V240.00Q592.00 159.00 561.00 102.00Q530.00 45.00 479.00 15.50Q428.00 -14.00 368.00 -14.00Q323.00 -14.00 292.50 -3.50Q262.00 7.00 243.50 23.50Q225.00 40.00 214.00 57.00H196.00V-200.00ZM330.00 96.00Q389.00 96.00 427.50 133.50Q466.00 171.00 466.00 243.00V253.00Q466.00 325.00 427.00 362.50Q388.00 400.00 330.00 400.00Q272.00 400.00 233.00 362.50Q194.00 325.00 194.00 253.00V243.00Q194.00 171.00 233.00 133.50Q272.00 96.00 330.00 96.00Z\"/><path transform=\"translate(43.68 40.00) scale(0.04000 -0.04000)\" d=\"M70.00 0.00V700.00H196.00V435.00H214.00Q222.00 451.00 239.00 467.00Q256.00 483.00 284.50 493.50Q313.00 504.00 357.00 504.00Q415.00 504.00 458.50 477.50Q502.00 451.00 526.00 404.50Q550.00 358.00 550.00 296.00V0.00H424.00V286.00Q424.00 342.00 396.50 370.00Q369.00 398.00 318.00 398.00Q260.00 398.00 228.00 359.50Q196.00 321.00 196.00 252.00V0.00Z\"/><path transform=\"translate(66.92 40.00) scale(0.04000 -0.04000)\" d=\"M306.00 -14.00Q232.00 -14.00 173.00 16.00Q114.00 46.00 80.00 103.00Q46.00 160.00 46.00 240.00V256.00Q46.00 336.00 80.00 393.00Q114.00 450.00 173.00 480.00Q232.00 510.00 306.00 510.00Q380.00 510.00 439.00 480.00Q498.00 450.00 532.00 393.00Q566.00 336.00 566.00 256.00V240.00Q566.00 160.00 532.00 103.00Q498.00 46.00 439.00 16.00Q380.00 -14.00 306.00 -14.00ZM306.00 98.00Q364.00 98.00 402.00 135.50Q440.00 173.00 440.00 243.00V253.00Q440.00 323.00 402.50 360.50Q365.00 398.00 306.00 398.00Q248.00 398.00 210.00 360.50Q172.00 323.00 172.00 253.00V243.00Q172.00 173.00 210.00 135.50Q248.00 98.00 306.00 98.00Z\"/><path transform=\"translate(90.00 40.00) scale(0.04000 -0.04000)\" d=\"M70.00 0.00V496.00H194.00V440.00H212.00Q223.00 470.00 248.50 484.00Q274.00 498.00 308.00 498.00H368.00V386.00H306.00Q258.00 386.00 227.00 360.50Q196.00 335.00 196.00 282.00V0.00Z\"/><path transform=\"translate(104.44 40.00) scale(0.04000 -0.04000)\" d=\"M306.00 -14.00Q232.00 -14.00 173.00 16.00Q114.00 46.00 80.00 103.00Q46.00 160.00 46.00 240.00V256.00Q46.00 336.00 80.00 393.00Q114.00 450.00 173.00 480.00Q232.00 510.00 306.00 510.00Q380.00 510.00 439.00 480.00Q498.00 450.00 532.00 393.00Q566.00 336.00 566.00 256.00V240.00Q566.00 160.00 532.00 103.00Q498.00 46.00 439.00 16.00Q380.00 -14.00 306.00 -14.00ZM306.00 98.00Q364.00 98.00 402.00 135.50Q440.00 173.00 440.00 243.00V253.00Q440.00 323.00 402.50 360.50Q365.00 398.00 306.00 398.00Q248.00 398.00 210.00 360.50Q172.00 323.00 172.00 253.00V243.00Q172.00 173.00 210.00 135.50Q248.00 98.00 306.00 98.00Z\"/></g><rect x=\"139.52\" y=\"16\" width=\"1.6\" height=\"26\" rx=\"0.8\" fill=\"var(--bc-brand-mark, #5647ee)\" opacity=\"0.4\"/><g fill=\"var(--bc-brand-mark, #5647ee)\"><path transform=\"translate(154.52 35.00) scale(0.01400 -0.01400)\" d=\"M46.35 0.00V105.85H139.75V594.15H46.35V700.00H404.08Q467.38 700.00 514.79 678.41Q562.20 656.83 588.70 617.33Q615.20 577.83 615.20 523.35V513.70Q615.20 465.52 597.03 434.76Q578.85 404.00 553.91 387.24Q528.98 370.48 506.28 363.30V346.35Q528.98 340.35 555.24 323.50Q581.50 306.65 600.00 275.48Q618.50 244.30 618.50 194.48V184.48Q618.50 126.65 591.41 85.24Q564.33 43.83 516.83 21.91Q469.33 0.00 406.85 0.00ZM259.85 109.15H392.60Q441.20 109.15 469.80 132.60Q498.40 156.05 498.40 198.55V208.20Q498.40 250.70 470.21 274.15Q442.02 297.60 392.60 297.60H259.85ZM259.85 406.75H391.65Q437.55 406.75 466.32 430.11Q495.10 453.48 495.10 493.80V503.80Q495.10 544.95 466.74 567.90Q438.37 590.85 391.65 590.85H259.85Z\"/><path transform=\"translate(167.00 35.00) scale(0.01400 -0.01400)\" d=\"M335.30 -14.00Q251.22 -14.00 190.60 16.63Q129.97 47.25 97.56 104.96Q65.15 162.68 65.15 243.03V700.00H185.25V239.73Q185.25 170.48 224.03 131.99Q262.80 93.50 335.30 93.50Q407.80 93.50 446.57 131.99Q485.35 170.48 485.35 239.73V700.00H605.45V243.03Q605.45 162.68 573.13 104.96Q540.80 47.25 480.09 16.63Q419.38 -14.00 335.30 -14.00Z\"/><path transform=\"translate(179.60 35.00) scale(0.01400 -0.01400)\" d=\"M311.85 -14.00Q238.45 -14.00 179.69 18.50Q120.92 51.00 86.69 113.67Q52.45 176.35 52.45 266.87V433.13Q52.45 569.00 128.01 641.50Q203.57 714.00 332.35 714.00Q460.13 714.00 529.23 645.59Q598.33 577.17 598.33 460.87V456.70H479.87V464.52Q479.87 504.67 463.76 536.86Q447.65 569.05 414.99 587.77Q382.32 606.50 332.35 606.50Q257.63 606.50 215.09 560.70Q172.55 514.90 172.55 435.13V264.87Q172.55 185.93 215.09 138.89Q257.63 91.85 334.00 91.85Q410.02 91.85 445.12 132.83Q480.22 173.80 480.22 237.92V249.67H302.85V351.88H598.33V0.00H487.52V69.10H470.57Q462.70 50.87 446.06 31.44Q429.42 12.00 397.64 -1.00Q365.85 -14.00 311.85 -14.00Z\"/><path transform=\"translate(192.05 35.00) scale(0.01400 -0.01400)\" d=\"M332.35 -14.00Q203.57 -14.00 128.01 58.50Q52.45 131.00 52.45 266.87V433.13Q52.45 569.00 128.01 641.50Q203.57 714.00 332.35 714.00Q460.13 714.00 529.58 643.85Q599.03 573.70 599.03 451.57V445.75H480.57V455.22Q480.57 521.42 444.15 563.96Q407.72 606.50 332.35 606.50Q257.63 606.50 215.09 560.70Q172.55 514.90 172.55 435.13V264.87Q172.55 185.93 215.09 139.71Q257.63 93.50 332.35 93.50Q407.72 93.50 444.15 136.45Q480.57 179.40 480.57 244.78V262.25H599.03V248.43Q599.03 126.30 529.58 56.15Q460.13 -14.00 332.35 -14.00Z\"/><path transform=\"translate(204.25 35.00) scale(0.01400 -0.01400)\" d=\"M19.75 0.00 211.62 700.00H421.33L613.20 0.00H489.27L447.42 159.60H185.53L143.68 0.00ZM214.25 270.40H418.70L324.95 624.87H308.00Z\"/><path transform=\"translate(216.31 35.00) scale(0.01400 -0.01400)\" d=\"M233.95 0.00V590.85H24.70V700.00H563.30V590.85H354.05V0.00Z\"/><path transform=\"translate(227.75 35.00) scale(0.01400 -0.01400)\" d=\"M332.35 -14.00Q203.57 -14.00 128.01 58.50Q52.45 131.00 52.45 266.87V433.13Q52.45 569.00 128.01 641.50Q203.57 714.00 332.35 714.00Q460.13 714.00 529.58 643.85Q599.03 573.70 599.03 451.57V445.75H480.57V455.22Q480.57 521.42 444.15 563.96Q407.72 606.50 332.35 606.50Q257.63 606.50 215.09 560.70Q172.55 514.90 172.55 435.13V264.87Q172.55 185.93 215.09 139.71Q257.63 93.50 332.35 93.50Q407.72 93.50 444.15 136.45Q480.57 179.40 480.57 244.78V262.25H599.03V248.43Q599.03 126.30 529.58 56.15Q460.13 -14.00 332.35 -14.00Z\"/><path transform=\"translate(239.95 35.00) scale(0.01400 -0.01400)\" d=\"M69.50 0.00V700.00H189.60V405.58H466.40V700.00H586.50V0.00H466.40V296.42H189.60V0.00Z\"/></g></svg>';\n\n// 'auto' is the default and means \"follow the visitor's system setting\".\nconst THEMES = [\n { value: 'light', label: 'Light', icon: ICONS.sun },\n { value: 'dark', label: 'Dark', icon: ICONS.moon },\n { value: 'auto', label: 'System default', icon: ICONS.auto },\n];\n\n// The visitor's pick outlives the page. Storage is wrapped because a sandboxed\n// iframe or a locked-down privacy mode throws on access rather than returning\n// null, and a theme preference is never worth breaking the widget over.\nconst THEME_KEY = 'bugcatch:theme';\n\nfunction readStoredTheme() {\n try {\n const value = localStorage.getItem(THEME_KEY);\n return THEMES.some((t) => t.value === value) ? value : null;\n } catch {\n return null;\n }\n}\n\nfunction writeStoredTheme(value) {\n try {\n localStorage.setItem(THEME_KEY, value);\n } catch { /* storage denied — the choice just won't outlive the page */ }\n}\n\nconst css = (accent) => `\n:host { all: initial; }\n:host {\n --bc-accent: ${accent};\n --bc-on-accent: #fff;\n --bc-bg: #fff;\n --bc-elevated: #fff;\n --bc-fg: #14161c;\n --bc-muted: #5f6673;\n --bc-faint: #9aa1ad;\n --bc-border: #e6e8ee;\n --bc-subtle: #f4f5f8;\n --bc-raised: #fff;\n --bc-danger: #dc2626;\n --bc-ok: #059669;\n /* The lockup's own two colours, kept off --bc-accent on purpose: that one is\n the customer's brand, and this mark is ours. Values are the light cut of\n brand/sphoro-bugcatch-primary.svg, pulled up from near-black so the mark\n identifies itself without out-shouting the host's page. */\n --bc-brand-ink: #4b5563;\n --bc-brand-mark: #5647ee;\n --bc-shadow: 0 24px 64px rgba(10,12,16,.22), 0 4px 14px rgba(10,12,16,.08);\n --bc-shadow-sm: 0 1px 2px rgba(10,12,16,.06), 0 1px 1px rgba(10,12,16,.04);\n --bc-dur: 240ms;\n --bc-ease: cubic-bezier(.22,1,.36,1);\n /* The system everything below is drawn from. Derived tokens are written in\n terms of the ones above, so a host that overrides --bc-accent or the\n palette gets a coherent result without touching these. */\n --bc-accent-soft: color-mix(in srgb, var(--bc-accent) 10%, var(--bc-bg));\n --bc-accent-line: color-mix(in srgb, var(--bc-accent) 32%, var(--bc-border));\n --bc-ring: 0 0 0 3px color-mix(in srgb, var(--bc-accent) 22%, transparent);\n --bc-r-sm: 8px;\n --bc-r-md: 12px;\n --bc-r-lg: 18px;\n --bc-r-xl: 22px;\n --bc-fs-xs: 11.5px;\n --bc-fs-sm: 12.5px;\n --bc-fs-md: 14px;\n --bc-fs-lg: 16px;\n --bc-pad: 18px;\n}\n:host(.bc-dark) {\n --bc-bg: #14161c;\n --bc-elevated: #1c1f27;\n --bc-fg: #eef0f4;\n --bc-muted: #a3aab6;\n --bc-faint: #6f7682;\n --bc-border: #2a2e38;\n --bc-subtle: #1f222a;\n --bc-raised: #363d4b;\n --bc-brand-ink: #9aa1ad;\n --bc-brand-mark: #9b8fff;\n --bc-shadow: 0 24px 64px rgba(0,0,0,.6), 0 4px 14px rgba(0,0,0,.4);\n --bc-shadow-sm: 0 1px 2px rgba(0,0,0,.4);\n --bc-accent-soft: color-mix(in srgb, var(--bc-accent) 18%, var(--bc-bg));\n}\n@media (prefers-color-scheme: dark) {\n :host(.bc-auto) {\n --bc-bg: #14161c;\n --bc-elevated: #1c1f27;\n --bc-fg: #eef0f4;\n --bc-muted: #a3aab6;\n --bc-faint: #6f7682;\n --bc-border: #2a2e38;\n --bc-subtle: #1f222a;\n --bc-raised: #363d4b;\n --bc-brand-ink: #9aa1ad;\n --bc-brand-mark: #9b8fff;\n --bc-shadow: 0 24px 64px rgba(0,0,0,.6), 0 4px 14px rgba(0,0,0,.4);\n --bc-shadow-sm: 0 1px 2px rgba(0,0,0,.4);\n --bc-accent-soft: color-mix(in srgb, var(--bc-accent) 18%, var(--bc-bg));\n }\n}\n\n* { box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, system-ui, sans-serif; }\nsvg { width: 1em; height: 1em; display: block; }\n/* The hidden attribute has to actually hide. Nearly every element in here is given a\n display by a rule below, and any of them beats the attribute's own UA style\n — which is why .dock and .launcher each had to say so themselves. Said once,\n for all of them, so the next element hidden from JavaScript works. */\n[hidden] { display: none !important; }\n.sr-only {\n position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;\n overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;\n}\n\n/* One focus ring for everything clickable, so keyboard users never have to\n guess where they are. Fields draw their own ring (a glow, not an outline). */\nbutton:focus-visible, a:focus-visible, select:focus-visible, [tabindex]:focus-visible {\n outline: 2px solid var(--bc-accent); outline-offset: 2px;\n}\n\n/* Motion is decoration here — every animation below is an entrance or a state\n hint, so switching them off costs nothing but the polish. */\n@media (prefers-reduced-motion: reduce) {\n *, *::before, *::after {\n animation-duration: .01ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: .01ms !important;\n }\n}\n\n@keyframes bc-fade-in { from { opacity: 0 } to { opacity: 1 } }\n@keyframes bc-fade-out { to { opacity: 0 } }\n@keyframes bc-panel-in { from { opacity: 0; transform: translateY(14px) scale(.965) } to { opacity: 1; transform: none } }\n@keyframes bc-panel-out { to { opacity: 0; transform: translateY(10px) scale(.975) } }\n@keyframes bc-sheet-in { from { transform: translateY(100%) } to { transform: none } }\n@keyframes bc-sheet-out { to { transform: translateY(100%) } }\n@keyframes bc-launcher-in { from { opacity: 0; transform: translateY(12px) scale(.86) } to { opacity: 1; transform: none } }\n@keyframes bc-item-in { from { opacity: 0; transform: translateY(8px) scale(.96) } to { opacity: 1; transform: none } }\n@keyframes bc-rise { from { opacity: 0; transform: translateY(6px) } to { opacity: 1; transform: none } }\n@keyframes bc-pulse { 0%, 100% { opacity: 1; transform: scale(1) } 50% { opacity: .3; transform: scale(.78) } }\n@keyframes bc-spin { to { transform: rotate(360deg) } }\n@keyframes bc-pop { 0% { transform: scale(.5); opacity: 0 } 65% { transform: scale(1.1) } 100% { transform: scale(1); opacity: 1 } }\n@keyframes bc-nudge { 25% { transform: translateX(-3px) } 75% { transform: translateX(3px) } }\n@keyframes bc-typing { 0%, 60%, 100% { transform: translateY(0); opacity: .45 } 30% { transform: translateY(-3px); opacity: 1 } }\n@keyframes bc-shimmer { from { background-position: 200% 0 } to { background-position: -200% 0 } }\n\n/* ---------- dock: theme switch stacked above the launcher ---------- */\n.dock {\n position: fixed; z-index: 2147483000;\n display: flex; flex-direction: column; gap: 10px;\n}\n.dock[hidden] { display: none; }\n/* While the panel is open the switch rises above the scrim so it stays usable\n — the panel shares the shadow root, so a change re-themes the open form. */\n.dock.over { z-index: 2147483002; }\n.dock.at-right { align-items: flex-end; }\n.dock.at-left { align-items: flex-start; }\n/* Anchored to the top, \"above the launcher\" would be off-screen, so the\n switch goes under it and the launcher stays the outermost thing. */\n.dock.at-top { flex-direction: column-reverse; }\n\n.themer {\n display: inline-flex; align-items: center; gap: 2px;\n padding: 3px; border-radius: 999px;\n background: var(--bc-elevated); border: 1px solid var(--bc-border);\n box-shadow: 0 6px 18px rgba(10,12,16,.14), 0 1px 3px rgba(10,12,16,.08);\n animation: bc-launcher-in 420ms var(--bc-ease) both;\n}\n.themer button {\n display: grid; place-items: center;\n width: 28px; height: 28px; padding: 0;\n border: none; border-radius: 999px; background: transparent;\n color: var(--bc-muted); cursor: pointer;\n transition: background var(--bc-dur) var(--bc-ease), color var(--bc-dur) var(--bc-ease), transform 140ms var(--bc-ease);\n}\n.themer button svg { font-size: 15px; }\n.themer button:hover { background: var(--bc-subtle); color: var(--bc-fg); }\n.themer button:active { transform: scale(.92); }\n.themer button[aria-pressed=\"true\"] { background: var(--bc-accent); color: var(--bc-on-accent); }\n.themer button[aria-pressed=\"true\"]:hover { background: var(--bc-accent); filter: brightness(1.06); }\n\n/* ---------- launcher ---------- */\n.launcher {\n position: relative;\n display: inline-flex; align-items: center; gap: 9px;\n height: 48px; padding: 0 20px 0 16px; border: none; border-radius: 999px;\n background: var(--bc-accent); color: var(--bc-on-accent);\n font-size: var(--bc-fs-md); font-weight: 620; line-height: 1; letter-spacing: -.01em;\n cursor: pointer;\n box-shadow: 0 10px 28px color-mix(in srgb, var(--bc-accent) 34%, rgba(10,12,16,.2)), 0 2px 6px rgba(10,12,16,.16);\n transition: transform var(--bc-dur) var(--bc-ease), box-shadow var(--bc-dur) var(--bc-ease), filter 160ms linear, padding var(--bc-dur) var(--bc-ease);\n animation: bc-launcher-in 420ms var(--bc-ease) both;\n}\n/* Two glyphs in one box: the bubble when closed, a chevron when open, turned\n into each other so the button reads as the panel's own handle. */\n.launcher .ico { position: relative; width: 20px; height: 20px; flex: none; }\n.launcher .ico > span {\n position: absolute; inset: 0;\n transition: transform 320ms var(--bc-ease), opacity 200ms ease;\n}\n.launcher .ico svg { width: 20px; height: 20px; }\n.launcher .ico .when-open { opacity: 0; transform: rotate(-90deg) scale(.6); }\n.launcher[aria-expanded=\"true\"] .ico .when-closed { opacity: 0; transform: rotate(90deg) scale(.6); }\n.launcher[aria-expanded=\"true\"] .ico .when-open { opacity: 1; transform: none; }\n.launcher:hover { transform: translateY(-2px); filter: brightness(1.06); }\n.launcher:active { transform: translateY(0) scale(.97); transition-duration: 90ms; }\n.launcher:focus-visible { outline-offset: 3px; }\n.launcher[hidden] { display: none; }\n\n/* ---------- scrim + panel ---------- */\n.scrim {\n position: fixed; inset: 0; z-index: 2147483001;\n background: rgba(10,12,16,.32);\n animation: bc-fade-in 200ms ease both;\n}\n.scrim.closing { animation: bc-fade-out 170ms ease both; }\n\n/* The panel belongs to the launcher, so it opens out of that corner rather\n than in the middle of the screen. --bc-dock-h is measured at open time\n because the dock's height depends on whether the launcher is showing.\n A column: the header and footer stay put and only the middle scrolls. */\n.panel {\n position: fixed;\n display: flex; flex-direction: column;\n width: calc(100vw - 40px); max-width: 420px;\n max-height: calc(100vh - var(--bc-dock-h, 84px) - 52px);\n overflow: hidden;\n background: var(--bc-bg); color: var(--bc-fg);\n border-radius: var(--bc-r-xl); box-shadow: var(--bc-shadow);\n border: 1px solid color-mix(in srgb, var(--bc-border) 70%, transparent);\n animation: bc-panel-in 340ms var(--bc-ease) both;\n}\n/* A conversation wants a height of its own, so the thread fills the panel\n rather than shrinking to its first two messages. */\n.panel.is-chat { height: 640px; }\n.scrim.at-right .panel { right: 20px; transform-origin: bottom right; }\n.scrim.at-left .panel { left: 20px; transform-origin: bottom left; }\n.scrim.at-bottom .panel { bottom: calc(20px + var(--bc-dock-h, 84px) + 12px); }\n.scrim.at-top .panel { top: calc(20px + var(--bc-dock-h, 84px) + 12px); transform-origin: top right; }\n.scrim.closing .panel { animation: bc-panel-out 170ms ease both; }\n\n/* ---------- header ---------- */\n.head {\n flex: none; position: relative;\n display: flex; align-items: center; gap: 12px;\n padding: 16px 14px 14px var(--bc-pad);\n background: linear-gradient(180deg, var(--bc-accent-soft), var(--bc-bg));\n border-bottom: 1px solid var(--bc-border);\n}\n.head .mark, .avatar {\n flex: none; display: grid; place-items: center; position: relative;\n width: 38px; height: 38px; border-radius: 50%;\n background: linear-gradient(140deg, var(--bc-accent), color-mix(in srgb, var(--bc-accent) 60%, #fff));\n color: var(--bc-on-accent); font-size: 19px;\n box-shadow: 0 4px 12px color-mix(in srgb, var(--bc-accent) 30%, transparent);\n}\n/* The little \"online\" dot says someone — or something — is on the other end. */\n.head .mark::after {\n content: ''; position: absolute; right: -1px; bottom: -1px;\n width: 11px; height: 11px; border-radius: 50%;\n background: #22c55e; border: 2px solid var(--bc-bg);\n}\n.head .titles { flex: 1; min-width: 0; }\n.head h2 {\n margin: 0 0 2px; font-size: var(--bc-fs-lg); font-weight: 680; letter-spacing: -.02em; line-height: 1.25;\n overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\n}\n.head p { margin: 0; font-size: var(--bc-fs-sm); line-height: 1.45; color: var(--bc-muted); }\n.icon-btn {\n flex: none; display: grid; place-items: center;\n width: 32px; height: 32px; padding: 0; font-size: 16px;\n border: none; border-radius: 10px; background: none; color: var(--bc-muted);\n cursor: pointer; transition: background 160ms, color 160ms, transform 160ms var(--bc-ease);\n}\n.icon-btn:hover { background: color-mix(in srgb, var(--bc-fg) 7%, transparent); color: var(--bc-fg); }\n.icon-btn:active { transform: scale(.92); }\n.icon-btn.back { margin: 0 -6px 0 -8px; }\n\n.body {\n flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain;\n padding: 16px var(--bc-pad) 6px;\n}\n\nlabel { display: block; font-size: var(--bc-fs-sm); font-weight: 620; color: var(--bc-fg); margin-bottom: 7px; }\nlabel .opt { font-weight: 450; color: var(--bc-faint); }\ninput[type=text], input[type=email], textarea {\n width: 100%; padding: 11px 13px; font-size: var(--bc-fs-md); line-height: 1.45;\n color: var(--bc-fg); background: var(--bc-elevated);\n border: 1px solid var(--bc-border); border-radius: var(--bc-r-md);\n transition: border-color 160ms, box-shadow 160ms, background 160ms;\n}\ninput[type=text]::placeholder, input[type=email]::placeholder, textarea::placeholder { color: var(--bc-faint); }\ninput[type=text]:hover, input[type=email]:hover, textarea:hover { border-color: color-mix(in srgb, var(--bc-fg) 18%, var(--bc-border)); }\ninput[type=text]:focus, input[type=email]:focus, textarea:focus {\n outline: none; border-color: var(--bc-accent); box-shadow: var(--bc-ring);\n}\ninput.invalid, input.invalid:focus, textarea.invalid {\n border-color: var(--bc-danger); animation: bc-nudge 240ms ease;\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--bc-danger) 16%, transparent);\n}\n.field-err {\n display: flex; align-items: center; gap: 6px;\n margin: 7px 0 0; font-size: var(--bc-fs-xs); font-weight: 550; color: var(--bc-danger);\n animation: bc-rise 200ms var(--bc-ease) both;\n}\n.field-err svg { font-size: 13px; flex: none; }\ntextarea { min-height: 88px; resize: vertical; }\n.field { margin-bottom: 16px; }\n.field-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }\n.field-head .count { font-size: var(--bc-fs-xs); color: var(--bc-faint); font-variant-numeric: tabular-nums; }\n\n/* The draft line: a quiet note, or a card offering to pick up where they left off. */\n.compose > .note { font-size: var(--bc-fs-xs); color: var(--bc-faint); margin: -4px 0 10px; }\n.compose > .note:empty { display: none; }\n.compose > .note:has(button) {\n display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px;\n margin: 0 0 14px; padding: 10px 12px; border-radius: var(--bc-r-md);\n background: var(--bc-accent-soft); border: 1px solid var(--bc-accent-line);\n font-size: var(--bc-fs-sm); color: var(--bc-fg);\n}\n.compose > .note button {\n padding: 5px 11px; font: inherit; font-size: var(--bc-fs-xs); font-weight: 620;\n border-radius: 999px; cursor: pointer;\n border: 1px solid var(--bc-accent); background: var(--bc-accent); color: var(--bc-on-accent);\n}\n.compose > .note button[data-discard] { background: transparent; color: var(--bc-muted); border-color: var(--bc-border); }\n\n/* ---------- what is already known ----------\n A help surface in front of the form, not a step in it. Everything here is\n dismissible by ignoring it: the reporter can read what is already known and\n leave, tap \"this is happening to me too\" and leave, or type straight past all\n of it and file the report they came to file. Nothing is ever in their way,\n because a reporter who feels handled rather than helped stops reporting. */\n.known { margin-bottom: 16px; }\n.known .known-h {\n margin: 0 0 8px; font-size: 11px; font-weight: 680; letter-spacing: .06em;\n text-transform: uppercase; color: var(--bc-faint);\n}\n.known .issue {\n display: flex; align-items: center; gap: 10px;\n padding: 10px 12px; margin-bottom: 6px;\n background: var(--bc-subtle); border: 1px solid var(--bc-border); border-radius: var(--bc-r-md);\n animation: bc-rise 240ms var(--bc-ease) both;\n}\n.known .issue.is-joined { border-color: color-mix(in srgb, var(--bc-ok) 55%, var(--bc-border)); }\n.known .issue .who { flex: 1; min-width: 0; }\n.known .issue .t {\n display: block; font-size: 13px; font-weight: 620; line-height: 1.4;\n color: var(--bc-fg); overflow-wrap: anywhere;\n}\n.known .issue .meta {\n display: block; margin-top: 3px; font-size: var(--bc-fs-xs); line-height: 1.45; color: var(--bc-muted);\n}\n.known .issue .join {\n flex: none; padding: 7px 12px; font-size: 12px; font-weight: 620;\n color: var(--bc-accent); background: var(--bc-bg);\n border: 1px solid var(--bc-accent-line); border-radius: 999px; cursor: pointer;\n transition: background 160ms, border-color 160ms;\n}\n.known .issue .join:hover { border-color: var(--bc-accent); background: var(--bc-accent-soft); }\n.known .issue .join:disabled { color: var(--bc-ok); border-color: var(--bc-border); background: var(--bc-bg); cursor: default; opacity: 1; }\n.known .ask { display: flex; gap: 8px; margin: 2px 0 8px; }\n.known .ask input { flex: 1; min-width: 0; }\n.known .ask button { flex: none; }\n.known .err { margin: 6px 0 0; font-size: 12px; line-height: 1.45; color: var(--bc-danger); }\n\n/* ---------- who is reporting ----------\n A strip above the form rather than a screen in front of it: where signing in\n is optional this is a line the reporter may ignore, and it should read as\n one. Where it is required the composer below is not rendered at all and this\n strip is the whole panel. */\n.whoami {\n display: flex; align-items: center; flex-wrap: wrap; gap: 8px;\n margin-bottom: 16px; padding: 10px 12px;\n font-size: var(--bc-fs-sm); line-height: 1.45; color: var(--bc-muted);\n background: var(--bc-subtle); border: 1px solid var(--bc-border); border-radius: var(--bc-r-md);\n}\n.whoami.asking { display: block; padding: 14px; }\n.whoami .who { flex: 1; min-width: 0; }\n.whoami .who strong { color: var(--bc-fg); font-weight: 620; word-break: break-all; }\n.link {\n padding: 0; font: inherit; font-size: var(--bc-fs-sm); font-weight: 620; line-height: 1.45;\n color: var(--bc-accent); background: none; border: none; cursor: pointer; border-radius: 4px;\n}\n.link:hover { text-decoration: underline; text-underline-offset: 2px; }\n.link:disabled { color: var(--bc-faint); cursor: default; text-decoration: none; }\n.whoami .why { margin: 0 0 10px; font-size: var(--bc-fs-sm); line-height: 1.5; color: var(--bc-muted); }\n.whoami .why strong { color: var(--bc-fg); font-weight: 620; word-break: break-all; }\n.whoami .row { display: flex; gap: 8px; align-items: stretch; }\n.whoami .row input { flex: 1; min-width: 0; }\n.whoami .row button { flex: none; }\n.whoami .after { display: flex; gap: 14px; align-items: center; margin-top: 9px; }\n.whoami .err { color: var(--bc-danger); }\n.whoami .code { letter-spacing: .28em; font-variant-numeric: tabular-nums; }\n\n/* ---------- which record is this about ----------\n Drawn only after Send has been pressed and only when what they wrote did not\n already answer it, so most people never see this at all. Presented as the\n assistant asking one friendly follow-up, not as a form field that appeared. */\n.refs {\n position: relative;\n margin-bottom: 16px; padding: 14px;\n font-size: var(--bc-fs-sm); line-height: 1.45; color: var(--bc-muted);\n background: var(--bc-accent-soft);\n border: 1px solid var(--bc-accent-line); border-radius: var(--bc-r-lg);\n animation: bc-item-in 280ms var(--bc-ease) both;\n}\n.refs .ask-head { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 11px; }\n.refs .ask-head .avatar { width: 28px; height: 28px; font-size: 14px; box-shadow: none; }\n.refs .ask-head strong { display: block; font-size: 13.5px; font-weight: 650; color: var(--bc-fg); margin-bottom: 1px; }\n.refs .why { margin: 0; font-size: var(--bc-fs-sm); line-height: 1.5; color: var(--bc-muted); }\n.refs .row { display: flex; gap: 8px; align-items: stretch; }\n.refs .row input { flex: 1; min-width: 0; background: var(--bc-bg); }\n.refs select {\n flex: none; max-width: 45%; padding: 10px 10px; font-size: 13px;\n color: var(--bc-fg); background: var(--bc-bg);\n border: 1px solid var(--bc-border); border-radius: var(--bc-r-md);\n}\n.refs select:focus { outline: none; border-color: var(--bc-accent); box-shadow: var(--bc-ring); }\n.refs .chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 9px; }\n.refs .chips button {\n padding: 5px 11px; font: inherit; font-size: 12px; font-weight: 600; border-radius: 999px; cursor: pointer;\n color: var(--bc-muted); background: var(--bc-bg); border: 1px solid var(--bc-border);\n transition: background 160ms, color 160ms, border-color 160ms;\n}\n.refs .chips button[aria-pressed=\"true\"] { color: var(--bc-on-accent); background: var(--bc-accent); border-color: var(--bc-accent); }\n.refs .hint { margin: 8px 0 0; font-size: 12px; line-height: 1.45; color: var(--bc-muted); }\n.refs .after { display: flex; gap: 12px; align-items: center; margin-top: 9px; }\n.refs .err { color: var(--bc-danger); font-weight: 550; }\n.refs .link { margin-top: 10px; font-size: 12px; color: var(--bc-muted); }\n.refs .link:hover { color: var(--bc-fg); }\n\n/* ---------- severity ---------- */\n.seg { display: grid; grid-template-columns: repeat(4, 1fr); gap: 3px; padding: 3px; background: var(--bc-subtle); border: 1px solid var(--bc-border); border-radius: var(--bc-r-md); }\n.seg button {\n display: inline-flex; align-items: center; justify-content: center; gap: 6px;\n padding: 8px 4px; font-size: var(--bc-fs-sm); font-weight: 550; line-height: 1;\n color: var(--bc-muted); background: none; border: none; border-radius: 9px;\n cursor: pointer; transition: background 180ms var(--bc-ease), color 180ms, box-shadow 180ms;\n}\n.seg button::before { content: ''; width: 7px; height: 7px; border-radius: 50%; background: var(--sev, var(--bc-faint)); opacity: .55; transition: opacity 180ms; }\n.seg button[data-prio=\"LOW\"] { --sev: #94a3b8; }\n.seg button[data-prio=\"MEDIUM\"] { --sev: #3b82f6; }\n.seg button[data-prio=\"HIGH\"] { --sev: #f59e0b; }\n.seg button[data-prio=\"CRITICAL\"] { --sev: #ef4444; }\n.seg button:hover { color: var(--bc-fg); }\n.seg button[aria-pressed=true] {\n background: var(--bc-raised); color: var(--bc-fg); font-weight: 650;\n box-shadow: 0 1px 3px rgba(10,12,16,.14), 0 0 0 1px color-mix(in srgb, var(--bc-border) 60%, transparent);\n}\n.seg button[aria-pressed=true]::before { opacity: 1; }\n.seg button:focus-visible { outline-offset: -2px; }\n\n/* ---------- attachments ---------- */\n.tools { display: grid; grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); gap: 8px; }\n.tool {\n display: inline-flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;\n padding: 12px 8px; font-size: 12px; font-weight: 600; line-height: 1;\n color: var(--bc-fg); background: var(--bc-elevated);\n border: 1px dashed color-mix(in srgb, var(--bc-fg) 20%, var(--bc-border)); border-radius: var(--bc-r-md); cursor: pointer;\n transition: border-color 160ms, background 160ms, color 160ms, transform 160ms var(--bc-ease);\n}\n.tool svg { font-size: 19px; color: var(--bc-muted); transition: color 160ms; }\n.tool:hover:not(:disabled) { border-color: var(--bc-accent); border-style: solid; background: var(--bc-accent-soft); transform: translateY(-1px); }\n.tool:hover:not(:disabled) svg { color: var(--bc-accent); }\n.tool:active:not(:disabled) { transform: translateY(0) scale(.98); }\n.tool:disabled { opacity: .45; cursor: not-allowed; }\n.attach-hint { margin: 8px 0 0; font-size: var(--bc-fs-xs); color: var(--bc-faint); }\n\n.thumbs { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }\n.thumbs:empty { display: none; }\n.thumb {\n position: relative; width: 76px; height: 76px; flex: none;\n border-radius: var(--bc-r-md); overflow: hidden;\n border: 1px solid var(--bc-border); background: var(--bc-subtle);\n box-shadow: var(--bc-shadow-sm);\n animation: bc-item-in 300ms var(--bc-ease) both;\n}\n.thumb img, .thumb video { width: 100%; height: 100%; object-fit: cover; display: block; }\n.thumb .kind {\n position: absolute; left: 4px; bottom: 4px;\n display: inline-flex; align-items: center; gap: 3px;\n padding: 2px 5px; border-radius: 5px;\n background: rgba(10,12,16,.72); color: #fff; font-size: 9.5px; font-weight: 600; line-height: 1.3;\n}\n.thumb .drop {\n position: absolute; top: 4px; right: 4px;\n width: 20px; height: 20px; font-size: 11px; border-radius: 50%;\n background: rgba(10,12,16,.7); color: #fff; border: none;\n display: grid; place-items: center; cursor: pointer;\n opacity: 0; transition: opacity 160ms, background 160ms;\n}\n.thumb:hover .drop, .thumb:focus-within .drop { opacity: 1; }\n.thumb .drop:hover { background: var(--bc-danger); }\n.thumb .veil {\n position: absolute; inset: 0; display: grid; place-items: center; gap: 6px;\n background: rgba(10,12,16,.62); color: #fff; font-size: 10.5px; font-weight: 600;\n align-content: center;\n}\n.thumb .veil.err { background: color-mix(in srgb, var(--bc-danger) 82%, transparent); }\n.spinner {\n width: 17px; height: 17px; border-radius: 50%;\n border: 2px solid rgba(255,255,255,.35); border-top-color: #fff;\n animation: bc-spin 720ms linear infinite;\n}\n.bar { width: 46px; height: 3px; border-radius: 2px; background: rgba(255,255,255,.3); overflow: hidden; }\n.bar i { display: block; height: 100%; width: 0; background: #fff; transition: width 180ms linear; }\n.veil button {\n background: none; border: none; color: #fff; font-size: 14px; padding: 2px;\n cursor: pointer; display: grid; place-items: center;\n}\n\n/* ---------- messages ---------- */\n.msg {\n display: flex; align-items: flex-start; gap: 8px;\n font-size: var(--bc-fs-sm); line-height: 1.45; padding: 10px 12px; border-radius: var(--bc-r-md); margin-bottom: 14px;\n animation: bc-item-in 240ms var(--bc-ease) both;\n}\n.msg svg { flex: none; font-size: 15px; margin-top: 1px; }\n.msg.err { background: color-mix(in srgb, var(--bc-danger) 10%, var(--bc-bg)); color: var(--bc-danger); border: 1px solid color-mix(in srgb, var(--bc-danger) 30%, transparent); }\n.msg.info { background: var(--bc-subtle); color: var(--bc-muted); border: 1px solid var(--bc-border); }\n\n/* ---------- footer ---------- */\n.foot {\n flex: none;\n display: flex; align-items: center; flex-wrap: wrap; gap: 8px;\n padding: 12px var(--bc-pad); border-top: 1px solid var(--bc-border);\n background: var(--bc-bg);\n}\n.foot .spacer { flex: 1; }\n.foot .note { flex-basis: 100%; order: -1; font-size: var(--bc-fs-xs); color: var(--bc-muted); }\n.foot .note:empty { display: none; }\n/* Quiet, but still legible: this sits in someone else's product, so it identifies\n where the report goes without competing with their interface. Quiet is bought\n with colour (--bc-brand-ink sits a step below body text) rather than with a\n blanket opacity — the BUGCATCH half is only about 3px of cap height at this\n size, and dimming it on top of that left it as a smudge in both themes. */\n.powered {\n flex: none; display: flex; align-items: center; justify-content: center; gap: 6px;\n padding: 7px var(--bc-pad) 9px; font-size: 10.5px; color: var(--bc-faint);\n background: var(--bc-subtle); border-top: 1px solid var(--bc-border);\n}\n.brand-credit { display: flex; align-items: center; transition: color 0.15s; border-radius: 4px; }\n.bc-brand { height: 16px; width: auto; display: block; color: var(--bc-brand-ink); }\n.brand-credit:hover .bc-brand { color: var(--bc-fg); }\nbutton.ghost, button.primary {\n display: inline-flex; align-items: center; justify-content: center; gap: 7px;\n height: 38px; padding: 0 15px; font: inherit; font-size: 13px; font-weight: 620; line-height: 1;\n border-radius: 999px; cursor: pointer; white-space: nowrap;\n transition: transform 160ms var(--bc-ease), filter 160ms, background 160ms, border-color 160ms, color 160ms, box-shadow 160ms;\n}\nbutton.ghost { background: var(--bc-bg); border: 1px solid var(--bc-border); color: var(--bc-fg); }\nbutton.ghost:hover:not(:disabled) { background: var(--bc-subtle); border-color: color-mix(in srgb, var(--bc-fg) 20%, var(--bc-border)); }\nbutton.ghost svg, button.primary svg { font-size: 15px; }\nbutton.primary {\n padding: 0 18px;\n background: var(--bc-accent); border: 1px solid var(--bc-accent); color: var(--bc-on-accent);\n box-shadow: 0 4px 12px color-mix(in srgb, var(--bc-accent) 30%, transparent);\n}\nbutton.primary:hover:not(:disabled) { transform: translateY(-1px); filter: brightness(1.07); }\nbutton.primary:active:not(:disabled) { transform: none; }\nbutton:disabled { opacity: .5; cursor: not-allowed; }\nbutton.primary:disabled { box-shadow: none; }\nbutton.primary .spinner { border-color: rgba(255,255,255,.4); border-top-color: #fff; width: 14px; height: 14px; }\nbutton.quiet {\n padding: 0 4px; height: 38px; font: inherit; font-size: 13px; font-weight: 600;\n border: none; background: none; color: var(--bc-muted); cursor: pointer; border-radius: 8px;\n}\nbutton.quiet:hover { color: var(--bc-accent); }\n\n/* ---------- your reports ---------- */\n.history {\n flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain;\n padding: 8px 12px 14px;\n}\n/* On the conversation screen the inner thread does the scrolling, so this\n wrapper must not — two nested scrollers means the composer drifts out of\n reach exactly when someone reaches for it. */\n.history.chatting {\n overflow: hidden; display: flex; flex-direction: column; padding: 0;\n}\n.history .row {\n display: flex; align-items: center; gap: 12px;\n padding: 12px 10px; border-bottom: 1px solid var(--bc-border);\n}\n.history .row:last-child { border-bottom: 0; }\n.history .who { flex: 1; min-width: 0; }\n.history .ttl {\n display: block; font-size: 13.5px; font-weight: 600; color: var(--bc-fg);\n overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\n}\n.history .sub { display: block; margin-top: 3px; font-size: var(--bc-fs-xs); color: var(--bc-faint); }\n.history .row-ico {\n flex: none; display: grid; place-items: center; width: 34px; height: 34px; border-radius: 10px;\n background: var(--bc-subtle); color: var(--bc-muted); font-size: 16px;\n}\n.history .chev { flex: none; color: var(--bc-faint); font-size: 15px; transition: transform 160ms var(--bc-ease), color 160ms; }\n.empty {\n display: flex; flex-direction: column; align-items: center; gap: 10px;\n padding: 40px 16px; text-align: center; font-size: var(--bc-fs-sm); color: var(--bc-muted);\n}\n.empty .empty-ico {\n display: grid; place-items: center; width: 44px; height: 44px; border-radius: 50%;\n background: var(--bc-accent-soft); color: var(--bc-accent); font-size: 20px;\n}\n.skel {\n display: block; height: 12px; border-radius: 6px; margin: 6px 0;\n background: linear-gradient(90deg, var(--bc-subtle) 25%, color-mix(in srgb, var(--bc-fg) 8%, var(--bc-subtle)) 50%, var(--bc-subtle) 75%);\n background-size: 200% 100%; animation: bc-shimmer 1.4s linear infinite;\n}\n/* A history row is a button now — it opens the report — so the UA styling has\n to go, and the hit area has to read as one. */\n.history button.row {\n width: 100%; background: none; border: 0; margin-bottom: 2px;\n font: inherit; color: inherit; text-align: left; cursor: pointer;\n border-radius: var(--bc-r-md); transition: background 160ms;\n animation: bc-rise 260ms var(--bc-ease) both;\n}\n.history button.row:hover { background: var(--bc-subtle); }\n.history button.row:hover .chev { transform: translateX(2px); color: var(--bc-accent); }\n.history button.row:focus-visible { outline-offset: -2px; }\n\n/* ---------- one report, and what happened to it ---------- */\n.report-head {\n flex: none;\n display: grid; grid-template-columns: 1fr auto; gap: 2px 10px; align-items: center;\n padding: 10px var(--bc-pad); border-bottom: 1px solid var(--bc-border);\n background: var(--bc-bg);\n}\n.report-head .ttl { font-size: 13px; font-weight: 620; color: var(--bc-fg); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }\n.report-head .sub { font-size: var(--bc-fs-xs); color: var(--bc-faint); }\n.report-head .pill { grid-row: 1 / span 2; grid-column: 2; align-self: center; }\n\n/* The thread scrolls; the composer under it does not. Both live inside the\n panel, which stops scrolling as a whole on this screen — a composer that\n scrolls away is a composer people think isn't there. */\n.thread {\n flex: 1 1 auto; min-height: 140px;\n display: flex; flex-direction: column; gap: 4px;\n padding: 14px var(--bc-pad) 12px; overflow-y: auto; overscroll-behavior: contain;\n}\n\n/* The day a run of messages happened on, as a centred label on a rule. */\n.day-sep {\n display: flex; align-items: center; gap: 10px; margin: 10px 0 6px;\n font-size: 11px; font-weight: 620; letter-spacing: .02em; color: var(--bc-faint);\n}\n.day-sep::before, .day-sep::after { content: ''; flex: 1; height: 1px; background: var(--bc-border); }\n\n/* A status change is not a message and is not drawn as one: no side, no\n bubble, no author. Centred and quiet, the way every chat marks the things\n that happened rather than the things that were said. */\n.sys {\n align-self: center; max-width: 90%; text-align: center; margin: 6px 0;\n padding: 4px 10px; border-radius: 999px; background: var(--bc-subtle);\n font-size: var(--bc-fs-xs); line-height: 1.45; color: var(--bc-muted);\n animation: bc-fade-in 240ms ease both;\n}\n.sys .dot {\n display: inline-block; width: 6px; height: 6px; margin-right: 6px;\n border-radius: 50%; background: var(--bc-muted); vertical-align: middle;\n}\n.sys .t { color: var(--bc-faint); }\n.sys.k-status .dot { background: #4da3ff; }\n.sys.k-severity .dot { background: #e8912a; }\n.sys.k-assignment .dot { background: #8b7dff; }\n.sys.k-filed .dot { background: var(--bc-faint); }\n\n/* ---------- the conversation ----------\n\n A status change and a message are different things and are drawn as such: a\n change is a line on the rail above, a message is a bubble with a side. The\n reporter's own words sit right and accented, the other side's left beside an\n avatar that says who it is — the arrangement every chat has taught people to\n read without a legend. */\n.msg-row, .typing-row {\n display: grid; grid-template-columns: 28px minmax(0, 1fr); column-gap: 8px;\n max-width: 88%; margin-top: 10px;\n animation: bc-rise 260ms var(--bc-ease) both;\n}\n.msg-row.cont { margin-top: 3px; }\n.msg-row > *, .typing-row > * { grid-column: 2; }\n/* Fixed rows, so the avatar lines up with the bubble whatever else the\n message carries (a name above it, answers or a time below it). */\n.msg-row > .msg-who, .typing-row > .msg-who { grid-row: 1; }\n.msg-row > .bubble, .typing-row > .bubble { grid-row: 2; }\n.msg-row > .quick { grid-row: 3; }\n.msg-row > .msg-when { grid-row: 4; }\n.msg-row > .av, .typing-row > .av { grid-column: 1; grid-row: 2; align-self: end; }\n/* In a run of messages the avatar sits by the last bubble and the time under\n it, the way a chat marks where one turn ends. */\n.msg-row:has(+ .msg-row.cont) > .av { visibility: hidden; }\n.msg-row:has(+ .msg-row.cont) > .msg-when { display: none; }\n.msg-row.theirs, .typing-row { align-self: flex-start; }\n.msg-row.mine { align-self: flex-end; grid-template-columns: minmax(0, 1fr); justify-items: end; }\n.msg-row.mine > * { grid-column: 1; }\n.av {\n display: grid; place-items: center; width: 28px; height: 28px; border-radius: 50%;\n font-size: 11px; font-weight: 700; letter-spacing: .02em;\n background: color-mix(in srgb, var(--bc-fg) 10%, var(--bc-bg)); color: var(--bc-fg);\n}\n.av.bot {\n background: linear-gradient(140deg, var(--bc-accent), color-mix(in srgb, var(--bc-accent) 60%, #fff));\n color: var(--bc-on-accent); font-size: 14px;\n}\n.msg-who { font-size: var(--bc-fs-xs); font-weight: 620; color: var(--bc-muted); padding: 0 4px 4px; }\n.msg-who.auto { color: var(--bc-accent); display: inline-flex; align-items: center; gap: 5px; }\n.msg-who .tag {\n padding: 1px 6px; border-radius: 999px; font-size: 9.5px; font-weight: 700; letter-spacing: .04em;\n text-transform: uppercase; background: var(--bc-accent-soft); color: var(--bc-accent);\n}\n.bubble {\n justify-self: start; max-width: 100%;\n padding: 9px 13px; border-radius: 18px; font-size: 13.5px; line-height: 1.5;\n background: var(--bc-subtle); color: var(--bc-fg);\n border: 1px solid var(--bc-border);\n border-bottom-left-radius: 6px;\n}\n/* pre-wrap belongs to the text, not the box — on the box it would also render\n the markup's own indentation. */\n.msg-text { display: block; white-space: pre-wrap; overflow-wrap: anywhere; }\n.msg-row.mine .bubble {\n justify-self: end;\n background: var(--bc-accent); border-color: var(--bc-accent); color: var(--bc-on-accent);\n border-bottom-left-radius: 18px; border-bottom-right-radius: 6px;\n}\n.msg-row.cont.theirs .bubble { border-top-left-radius: 6px; }\n.msg-row.cont.mine .bubble { border-top-right-radius: 6px; }\n/* A reply nobody wrote: tinted with the accent and labelled \"Assistant\" above\n it, so it reads the same to someone who cannot see the colour at all. */\n.msg-row.auto .bubble { background: var(--bc-accent-soft); border-color: var(--bc-accent-line); }\n/* An attachment-only message has no text to wrap, so the bubble would be an\n empty coloured box around it. */\n.bubble.silent { padding: 0; background: none !important; border: 0; }\n.msg-when { font-size: 10.5px; color: var(--bc-faint); padding: 4px 4px 0; }\n.msg-row.pending { opacity: .6; }\n.msg-row.pending .msg-when::after { content: ' · Sending…'; }\n.msg-row.failed .bubble { border: 1px solid var(--bc-danger); }\n.msg-row.failed .msg-when { color: var(--bc-danger); }\n.msg-row.failed .msg-when::after { content: ' · Not sent'; }\n\n/* The assistant, thinking. Three dots in a bubble where its next message will be. */\n.typing { display: inline-flex; align-items: center; gap: 4px; padding: 12px 14px; justify-self: start; }\n.typing i {\n width: 7px; height: 7px; border-radius: 50%; background: var(--bc-accent); opacity: .45;\n animation: bc-typing 1.2s ease-in-out infinite;\n}\n.typing i:nth-child(2) { animation-delay: .15s; }\n.typing i:nth-child(3) { animation-delay: .3s; }\n.typing-row .msg-when { padding-top: 4px; }\n\n.att { display: flex; flex-direction: column; gap: 6px; margin-top: 6px; }\n.bubble.silent .att { margin-top: 0; }\n.att img {\n display: block; max-width: 100%; max-height: 190px; border-radius: var(--bc-r-md);\n border: 1px solid var(--bc-border); object-fit: cover; cursor: zoom-in;\n}\n.att audio { width: 232px; max-width: 100%; height: 34px; }\n.att video { max-width: 100%; max-height: 190px; border-radius: var(--bc-r-md); }\n.att .file {\n display: flex; align-items: center; gap: 8px; text-decoration: none;\n padding: 8px 10px; border-radius: 10px; font-size: 12px;\n background: var(--bc-elevated); border: 1px solid var(--bc-border); color: var(--bc-fg);\n}\n.att .file:hover { border-color: var(--bc-accent); }\n.att .file .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.att .file .sz { flex: none; color: var(--bc-faint); font-size: 11px; }\n\n/* ---------- the assistant's live card ----------\n A status header pinned between the report and the conversation: where the\n investigation is, as words and as a stepper. It does not scroll with the\n thread, because \"is anything happening?\" is the question on screen the\n whole time. */\n.assistant {\n flex: none; position: relative; overflow: hidden;\n margin: 10px 12px 0; padding: 12px 14px 12px;\n border-radius: var(--bc-r-lg); border: 1px solid var(--bc-border);\n background: var(--bc-subtle);\n transition: background 400ms ease, border-color 400ms ease;\n}\n.assistant.analyzing, .assistant.confirming, .assistant.needs_info {\n background: var(--bc-accent-soft); border-color: var(--bc-accent-line);\n}\n.assistant.resolved { background: color-mix(in srgb, var(--bc-ok) 9%, var(--bc-bg)); border-color: color-mix(in srgb, var(--bc-ok) 35%, var(--bc-border)); }\n.as-body.entering { animation: bc-rise 360ms var(--bc-ease) both; }\n.as-top { display: flex; gap: 11px; align-items: center; }\n.as-orb {\n flex: none; width: 32px; height: 32px; border-radius: 50%; display: grid; place-items: center;\n color: var(--bc-on-accent); font-size: 15px;\n background: linear-gradient(140deg, var(--bc-accent), color-mix(in srgb, var(--bc-accent) 60%, #fff));\n}\n.assistant.resolved .as-orb { background: var(--bc-ok); }\n.assistant.reviewing .as-orb { background: color-mix(in srgb, var(--bc-fg) 55%, var(--bc-bg)); }\n.as-spin { width: 16px; height: 16px; border-radius: 50%; border: 2px solid rgba(255,255,255,.4); border-top-color: #fff; animation: as-rot .8s linear infinite; }\n@keyframes as-rot { to { transform: rotate(360deg); } }\n.as-text { min-width: 0; flex: 1; }\n.as-title { font-weight: 650; font-size: 13.5px; letter-spacing: -.01em; color: var(--bc-fg); }\n.as-detail { font-size: 12px; line-height: 1.45; color: var(--bc-muted); margin-top: 2px; }\n.as-progress {\n height: 4px; margin: 12px 0 0; border-radius: 999px; overflow: hidden;\n background: color-mix(in srgb, var(--bc-fg) 9%, transparent);\n}\n.as-progress i {\n display: block; height: 100%; border-radius: inherit; background: var(--bc-accent);\n transition: width 700ms var(--bc-ease);\n}\n.assistant.resolved .as-progress i { background: var(--bc-ok); }\n.assistant.analyzing .as-progress i {\n background: linear-gradient(90deg, var(--bc-accent) 0%, color-mix(in srgb, var(--bc-accent) 45%, #fff) 50%, var(--bc-accent) 100%);\n background-size: 200% 100%; animation: bc-shimmer 1.6s linear infinite;\n}\n.as-steps {\n list-style: none; display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px;\n margin: 8px 0 0; padding: 0; font-size: 10.5px; color: var(--bc-faint);\n}\n.as-steps li { display: flex; align-items: center; gap: 5px; min-width: 0; white-space: nowrap; }\n.as-steps li .lbl { min-width: 0; overflow: hidden; text-overflow: ellipsis; }\n.as-steps li .dot { flex: none; width: 8px; height: 8px; border-radius: 50%; border: 1.5px solid currentColor; }\n.as-steps li.done { color: var(--bc-muted); }\n.as-steps li.done .dot { background: var(--bc-accent); border-color: var(--bc-accent); }\n.assistant.resolved .as-steps li.done .dot { background: var(--bc-ok); border-color: var(--bc-ok); }\n.as-steps li.active { color: var(--bc-fg); font-weight: 650; }\n.as-steps li.active .dot { border-color: var(--bc-accent); background: var(--bc-bg); animation: as-pulse 1.2s ease-in-out infinite; }\n@keyframes as-pulse { 50% { box-shadow: 0 0 0 4px color-mix(in srgb, var(--bc-accent) 25%, transparent); } }\n@media (prefers-reduced-motion: reduce) { .as-spin, .as-steps li.active .dot, .assistant.analyzing .as-progress i, .typing i { animation: none; } }\n\n/* Quick answers under the confirm question — only on the latest message. */\n.msg-row .quick { display: none; gap: 6px; flex-wrap: wrap; margin-top: 8px; }\n.msg-row:last-child .quick { display: flex; }\n.quick button {\n display: inline-flex; align-items: center; gap: 6px;\n font: inherit; font-size: 13px; font-weight: 600; padding: 8px 14px; border-radius: 999px; cursor: pointer;\n border: 1px solid var(--bc-accent); background: var(--bc-bg); color: var(--bc-accent);\n transition: background 160ms, color 160ms, transform 160ms var(--bc-ease);\n animation: bc-item-in 300ms var(--bc-ease) both;\n}\n.quick button:first-child { background: var(--bc-accent); color: var(--bc-on-accent); }\n.quick button:nth-child(2) { animation-delay: 60ms; }\n.quick button svg { font-size: 13px; }\n.quick button:hover { transform: translateY(-1px); background: var(--bc-accent-soft); }\n.quick button:first-child:hover { background: var(--bc-accent); filter: brightness(1.07); }\n\n/* ---------- the composer ---------- */\n.composer {\n flex: none; position: relative;\n padding: 10px 12px max(12px, env(safe-area-inset-bottom, 0px));\n /* The panel's own background, not --bc-elevated: the two are the same white\n in light mode but differ in dark, where a raised composer reads as a band\n laid over the panel rather than the foot of it. */\n background: var(--bc-bg);\n border-top: 1px solid var(--bc-border);\n}\n/* One rounded field holding the tools, the text and the send button — the\n shape every chat box has, so nobody has to look for where to type. */\n.composer .row {\n display: flex; align-items: flex-end; gap: 4px;\n padding: 5px; border-radius: 22px;\n background: var(--bc-subtle); border: 1px solid var(--bc-border);\n transition: border-color 160ms, box-shadow 160ms;\n}\n.composer .row:focus-within { border-color: var(--bc-accent); box-shadow: var(--bc-ring); }\n.composer textarea {\n flex: 1; min-width: 0; min-height: 34px; max-height: 120px; resize: none; padding: 7px 6px;\n font: inherit; font-size: 13.5px; line-height: 1.45;\n color: var(--bc-fg); background: transparent; border: 0; border-radius: 0;\n}\n.composer textarea:hover, .composer textarea:focus { border: 0; box-shadow: none; outline: none; }\n.composer .tools { display: flex; gap: 0; grid-template-columns: none; }\n.composer .tool {\n width: 34px; height: 34px; min-height: 0; padding: 0; display: grid; place-items: center; cursor: pointer;\n border-radius: 50%; border: 0; background: none; font-size: 17px;\n color: var(--bc-muted);\n}\n.composer .tool svg { font-size: 17px; }\n.composer .tool:hover:not(:disabled) { background: color-mix(in srgb, var(--bc-fg) 8%, transparent); color: var(--bc-fg); border: 0; transform: none; }\n.composer .tool:hover:not(:disabled) svg { color: var(--bc-fg); }\n.composer .tool:disabled { opacity: .4; cursor: default; }\n.composer .tool.recording { color: var(--bc-danger); background: color-mix(in srgb, var(--bc-danger) 12%, transparent); }\n.composer .tool.recording svg { color: var(--bc-danger); }\n.composer .send {\n width: 34px; height: 34px; flex: none; border-radius: 50%; cursor: pointer; font-size: 16px;\n background: var(--bc-accent); border: 0; color: var(--bc-on-accent);\n display: grid; place-items: center;\n transition: transform 160ms var(--bc-ease), opacity 160ms, filter 160ms;\n}\n.composer .send:hover:not(:disabled) { filter: brightness(1.08); transform: scale(1.05); }\n.composer .send:disabled { opacity: .35; cursor: default; background: var(--bc-faint); }\n.composer .staged { display: flex; flex-wrap: wrap; gap: 6px; padding: 0 0 8px; }\n.composer .chip {\n display: flex; align-items: center; gap: 6px; max-width: 200px;\n padding: 5px 7px 5px 10px; border-radius: 999px; font-size: var(--bc-fs-xs);\n background: var(--bc-subtle); border: 1px solid var(--bc-border); color: var(--bc-muted);\n animation: bc-item-in 240ms var(--bc-ease) both;\n}\n.composer .chip .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--bc-fg); }\n.composer .chip button {\n flex: none; width: 18px; height: 18px; display: grid; place-items: center;\n border: 0; border-radius: 50%; background: none; color: var(--bc-faint); cursor: pointer;\n}\n.composer .chip button:hover { color: var(--bc-danger); }\n.composer .chip.uploading { border-style: dashed; }\n.composer .note { padding: 6px 6px 0; font-size: var(--bc-fs-xs); color: var(--bc-muted); }\n.composer .note.err { color: var(--bc-danger); }\n.reccount { font-variant-numeric: tabular-nums; }\n\n/* Status carries the only colour in this list, so it is the thing the eye\n lands on — which is what someone opening this screen came to check. */\n.pill {\n flex: none; display: inline-flex; align-items: center; gap: 5px;\n font-size: 11px; font-weight: 650;\n padding: 3px 9px; border-radius: 999px;\n color: var(--bc-muted); background: var(--bc-subtle);\n}\n.pill::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }\n.pill.open { color: #c2410c; background: color-mix(in srgb, #f59e0b 14%, var(--bc-bg)); }\n.pill.in_progress { color: #1d6fd1; background: color-mix(in srgb, #3b82f6 14%, var(--bc-bg)); }\n.pill.resolved { color: #0f8a5a; background: color-mix(in srgb, #10b981 14%, var(--bc-bg)); }\n.pill.closed { color: var(--bc-muted); }\n:host(.bc-dark) .pill.open { color: #fbbf24; }\n:host(.bc-dark) .pill.in_progress { color: #7cb8ff; }\n:host(.bc-dark) .pill.resolved { color: #4ade9a; }\n@media (prefers-color-scheme: dark) {\n :host(.bc-auto) .pill.open { color: #fbbf24; }\n :host(.bc-auto) .pill.in_progress { color: #7cb8ff; }\n :host(.bc-auto) .pill.resolved { color: #4ade9a; }\n}\n\n/* ---------- does the fix work ---------- */\n.fix-verification {\n flex: none; margin: 10px 12px 0; padding: 12px 14px;\n border-radius: var(--bc-r-lg); border: 1px solid color-mix(in srgb, var(--bc-ok) 35%, var(--bc-border));\n background: color-mix(in srgb, var(--bc-ok) 8%, var(--bc-bg));\n font-size: var(--bc-fs-sm); color: var(--bc-fg);\n animation: bc-rise 280ms var(--bc-ease) both;\n}\n.fix-verification p { margin: 0 0 8px; font-weight: 620; }\n.fix-verification textarea { min-height: 56px; margin-bottom: 8px; font-size: 13px; background: var(--bc-bg); }\n.fix-verification button {\n padding: 7px 13px; font: inherit; font-size: 12.5px; font-weight: 620; border-radius: 999px; cursor: pointer;\n border: 1px solid var(--bc-border); background: var(--bc-bg); color: var(--bc-fg);\n}\n.fix-verification button[data-result=\"CONFIRMED\"] { background: var(--bc-ok); border-color: var(--bc-ok); color: #fff; }\n.fix-verification .feedback-status { margin: 6px 0 0; font-weight: 450; color: var(--bc-danger); font-size: var(--bc-fs-xs); }\n.fix-verification .feedback-status:empty { display: none; }\n\n/* A call in progress, in place of the form.\n ---------------------------------------------------------------------------\n Deliberately not styled as a chat. Nothing arrives here to read — the\n conversation is happening out loud — so this is a state, a line of\n reassurance about the report, and a way to hang up. */\n.call { display: flex; flex-direction: column; padding: 26px 0 0; animation: bc-fade-in 240ms ease both; }\n.call-head, .call-note { padding: 0 var(--bc-pad); }\n.call-head { display: flex; align-items: center; gap: 9px; margin-bottom: 8px; }\n.call-head strong { font-size: 15.5px; font-weight: 680; letter-spacing: -.01em; }\n.call-dot {\n width: 9px; height: 9px; border-radius: 50%; background: var(--bc-accent);\n animation: bc-call-pulse 1.6s ease-in-out infinite;\n}\n.call-dot.off { background: var(--bc-faint); animation: none; }\n@keyframes bc-call-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }\n/* Said while it is still ringing, because that is when somebody is deciding\n whether it is worth waiting. */\n.call-note { margin: 0 0 18px; font-size: 13px; color: var(--bc-muted); line-height: 1.55; }\n\n/* ---------- the same call, on top of the report it is about ---------- */\n/* A bar rather than its own screen, unlike the one the composer opens. The\n thread underneath is exactly what both people are talking about, so taking it\n off screen would be taking away the only shared reference. */\n.livecall {\n flex: none;\n display: flex; align-items: center; gap: 9px;\n margin: 10px 12px 0; padding: 9px 11px;\n border: 1px solid var(--bc-accent-line); border-radius: var(--bc-r-md);\n background: var(--bc-accent-soft); font-size: var(--bc-fs-sm); color: var(--bc-fg);\n animation: bc-rise 240ms var(--bc-ease) both;\n}\n.livecall .who { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.livecall .secs { font-variant-numeric: tabular-nums; color: var(--bc-muted); }\n.livecall .act {\n padding: 5px 11px; border-radius: 999px; border: 1px solid var(--bc-border);\n background: var(--bc-bg); color: var(--bc-fg);\n font: inherit; font-size: 12px; font-weight: 600; cursor: pointer;\n}\n.livecall .act:hover { background: var(--bc-subtle); }\n.livecall .act.danger { border-color: transparent; background: var(--bc-danger); color: #fff; }\n.livecall.over { color: var(--bc-muted); background: var(--bc-subtle); border-color: var(--bc-border); }\n.call-note code { font-size: 11px; }\n@media (prefers-reduced-motion: reduce) { .call-dot { animation: none; } }\n\n/* div.done, not .done: the assistant's stepper marks finished steps li.done,\n and this screen's padding was landing on every one of them. */\ndiv.done { padding: 44px 26px 44px; text-align: center; animation: bc-fade-in 240ms ease both; }\n.done .tick {\n width: 56px; height: 56px; margin: 0 auto 16px; border-radius: 50%;\n display: grid; place-items: center; font-size: 26px;\n background: color-mix(in srgb, var(--bc-ok) 14%, transparent); color: var(--bc-ok);\n box-shadow: 0 0 0 8px color-mix(in srgb, var(--bc-ok) 6%, transparent);\n animation: bc-pop 420ms var(--bc-ease) both;\n}\n.done h3 { margin: 0 0 6px; font-size: 17px; font-weight: 680; letter-spacing: -.015em; }\n.done p { margin: 0; font-size: 13px; line-height: 1.5; color: var(--bc-muted); }\n.done code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; padding: 1px 5px; border-radius: 5px; background: var(--bc-subtle); color: var(--bc-fg); }\n/* The likely wait. Set apart from the reference line above it by a rule rather\n than by weight: one is bookkeeping and the other is the answer to the\n question the reporter actually has, and they should not read as one\n paragraph. */\n.done .eta {\n margin: 14px auto 0;\n padding-top: 12px;\n border-top: 1px solid var(--bc-border);\n max-width: 30ch;\n line-height: 1.5;\n}\n.done .eta strong { color: var(--bc-fg); font-weight: 620; }\n/* The same figure on the report the reporter came back to. Sits under the\n status pill in the header block, muted — it is context, not the headline the\n status is. */\n.report-head .eta-line {\n grid-column: 1 / -1;\n font-size: var(--bc-fs-xs);\n color: var(--bc-muted);\n margin-top: 2px;\n}\n\n/* ---------- recording bar ---------- */\n.recbar {\n position: fixed; left: 50%; bottom: calc(24px + env(safe-area-inset-bottom, 0px)); transform: translateX(-50%);\n z-index: 2147483002;\n display: flex; align-items: center; gap: 12px;\n padding: 8px 8px 8px 16px; border-radius: 999px;\n background: var(--bc-elevated); color: var(--bc-fg);\n border: 1px solid var(--bc-border); box-shadow: var(--bc-shadow);\n animation: bc-item-in 300ms var(--bc-ease) both;\n}\n.recbar .dot { width: 9px; height: 9px; border-radius: 50%; background: var(--bc-danger); animation: bc-pulse 1.4s ease-in-out infinite; }\n.recbar .time { font-variant-numeric: tabular-nums; font-size: 13px; font-weight: 650; min-width: 38px; }\n.recbar .hint { font-size: 12px; color: var(--bc-muted); }\n.recbar button { height: 34px; padding: 0 14px; font-size: 12.5px; font-weight: 620; border-radius: 999px; cursor: pointer; }\n.recbar .stop { background: var(--bc-danger); border: 1px solid var(--bc-danger); color: #fff; }\n.recbar .stop:hover { filter: brightness(1.08); }\n\n/* ---------- phones ----------------------------------------------------------\n This widget runs inside a shadow root on somebody else's site, so its own\n stylesheet is the whole story: the host page cannot correct any of this, and\n anything that goes wrong here goes wrong on their site, in front of their\n visitor, at the moment that visitor is already annoyed enough to file a bug.\n -------------------------------------------------------------------------- */\n\n/* 100vh on a phone is the viewport with the browser chrome hidden, so the\n panel could be taller than the space it actually has and put \"Send report\"\n under the address bar. */\n@supports (height: 100dvh) {\n .panel { max-height: calc(100dvh - var(--bc-dock-h, 84px) - 52px); }\n}\n\n/* The dock sits in a corner of a page the widget does not own. On a notched\n phone that corner is under the home indicator, and in landscape it is under\n the camera housing. env() is 0 everywhere else, so the offsets are unchanged\n on hardware that has no insets. */\n.dock {\n padding-bottom: env(safe-area-inset-bottom, 0px);\n padding-right: env(safe-area-inset-right, 0px);\n padding-left: env(safe-area-inset-left, 0px);\n}\n\n/* Short desktop windows: the chat height gives way to the space there is. */\n@media (max-height: 820px) { .panel.is-chat { height: calc(100vh - var(--bc-dock-h, 84px) - 52px); } }\n\n/* A phone gets the whole screen: a sheet that slides up from the bottom, with\n the notch and the home indicator kept clear. The dock steps aside while it\n is open — the sheet has its own close button, and a launcher floating over\n the send button would be in the way of the one thing that matters. */\n@media (max-width: 480px) {\n .scrim { background: rgba(10,12,16,.4); }\n .panel, .scrim.at-right .panel, .scrim.at-left .panel, .scrim.at-bottom .panel, .scrim.at-top .panel {\n inset: 0; width: 100%; max-width: none; height: 100%; max-height: none;\n border-radius: 0; border: 0;\n animation: bc-sheet-in 380ms var(--bc-ease) both;\n }\n @supports (height: 100dvh) { .panel { height: 100dvh; max-height: none; } }\n .panel.is-chat { height: 100%; }\n .scrim.closing .panel { animation: bc-sheet-out 200ms ease both; }\n .head { padding-top: max(14px, env(safe-area-inset-top, 0px)); padding-left: max(var(--bc-pad), env(safe-area-inset-left, 0px)); padding-right: max(12px, env(safe-area-inset-right, 0px)); }\n .foot, .powered { padding-bottom: max(10px, env(safe-area-inset-bottom, 0px)); }\n .foot + .powered { padding-bottom: max(9px, env(safe-area-inset-bottom, 0px)); }\n .foot:has(+ .powered) { padding-bottom: 12px; }\n .dock.over { display: none; }\n .recbar .hint { display: none; }\n .msg-row, .typing-row { max-width: 92%; }\n}\n\n@media (pointer: coarse) {\n /* iOS zooms the whole page when a field it focuses is under 16px, and does\n not zoom back out. On the host's site that is not the widget's page to\n zoom — the visitor is left on a magnified checkout wondering what they\n touched. !important because this is a property of the device, not a\n preference about type. */\n input, textarea, select { font-size: 16px !important; }\n\n /* 44px is the floor a thumb hits reliably. Every control in this form was\n under it, on the one surface in the product designed for someone who is\n not an operator and gets no second attempt. */\n .seg button { min-height: 44px; }\n .tool { min-height: 56px; }\n button.primary, button.ghost { height: 44px; }\n .composer .send, .composer .tool { width: 40px; height: 40px; min-height: 0; }\n textarea { min-height: 104px; }\n .composer textarea { min-height: 40px; }\n\n /* The theme switch and the close button are small glyphs whose boxes cannot\n grow without moving the header text beside them, so each keeps its size\n and gets an invisible target drawn around it instead. */\n .themer button, .head .icon-btn, .thumb .drop, .brand-credit { position: relative; }\n .thumb .drop { position: absolute; opacity: 1; }\n .themer button::after, .head .icon-btn::after, .thumb .drop::after, .brand-credit::after {\n content: '';\n position: absolute;\n inset: -8px;\n }\n /* The themer's three buttons sit side by side, so their expanders would\n overlap each other without a gap to grow into. */\n .themer { gap: 4px; }\n}\n\n/* Hover is a lie on a touchscreen: it latches on tap and stays. The launcher's\n lift in particular was left applied after the panel closed. */\n@media (hover: none) {\n .launcher:hover { transform: none; filter: none; }\n .tool:hover:not(:disabled), .themer button:hover { transform: none; }\n}\n`;\n\nexport function mountWidget(client) {\n const host = document.createElement('div');\n host.setAttribute('data-bugcatch', 'widget');\n\n // A visitor who has picked a theme outranks the site's init() default: the\n // config says how the widget should look to someone who has never touched\n // it, not how it should look to someone who has.\n let theme = readStoredTheme() || themeValue(client.config.theme);\n host.className = themeClass(theme);\n\n const root = host.attachShadow({ mode: 'open' });\n const style = document.createElement('style');\n style.textContent = css(client.config.accent || '#4F46E5');\n root.appendChild(style);\n\n const position = POSITIONS[client.config.widgetPosition] ? client.config.widgetPosition : 'bottom-right';\n const dock = document.createElement('div');\n dock.className = `dock ${position.includes('right') ? 'at-right' : 'at-left'}`\n + (position.startsWith('top') ? ' at-top' : '');\n dock.style.cssText = POSITIONS[position];\n\n const themer = document.createElement('div');\n themer.className = 'themer';\n themer.setAttribute('role', 'group');\n themer.setAttribute('aria-label', 'Report widget theme');\n themer.innerHTML = THEMES.map((t) => `<button type=\"button\" data-theme=\"${t.value}\" `\n + `title=\"${t.label}\" aria-label=\"${t.label}\" aria-pressed=\"${t.value === theme}\">${t.icon}</button>`).join('');\n\n themer.addEventListener('click', (e) => {\n const btn = e.target.closest('button[data-theme]');\n if (!btn || btn.dataset.theme === theme) return;\n theme = btn.dataset.theme;\n // The panel lives in the same shadow root, so an open form re-themes with\n // it rather than waiting for the next open.\n host.className = themeClass(theme);\n writeStoredTheme(theme);\n themer.querySelectorAll('button').forEach((b) => {\n b.setAttribute('aria-pressed', String(b.dataset.theme === theme));\n });\n });\n\n const launcher = document.createElement('button');\n launcher.className = 'launcher';\n launcher.type = 'button';\n launcher.innerHTML = `<span class=\"ico\" aria-hidden=\"true\"><span class=\"when-closed\">${ICONS.chat}</span>`\n + `<span class=\"when-open\">${ICONS.chevronDown}</span></span><span>Report a bug</span>`;\n launcher.setAttribute('aria-haspopup', 'dialog');\n launcher.setAttribute('aria-expanded', 'false');\n\n let open = null;\n launcher.addEventListener('click', () => {\n // The launcher stays visible with the panel anchored just above it, so a\n // second click reads as \"close that\" rather than doing nothing.\n if (open) { open.close(); return; }\n dock.classList.add('over');\n launcher.setAttribute('aria-expanded', 'true');\n open = openPanel({\n root,\n host,\n dock,\n client,\n onClose: () => {\n open = null;\n dock.classList.remove('over');\n launcher.setAttribute('aria-expanded', 'false');\n },\n });\n });\n\n dock.append(themer, launcher);\n root.appendChild(dock);\n\n document.body.appendChild(host);\n // Integration hook: BugCatch.open() / close() / isOpen() drive the same\n // launcher a person would press, so there is one way in to the panel.\n client.widgetControls = {\n open: () => { if (!open) launcher.click(); },\n close: () => { open?.close(); },\n isOpen: () => Boolean(open),\n };\n return () => { open?.destroy(); host.remove(); client.widgetControls = null; };\n}\n\n/** Normalises a configured theme to one of the three the switch offers. */\nfunction themeValue(theme) {\n return theme === 'dark' || theme === 'light' ? theme : 'auto';\n}\n\nfunction themeClass(theme) {\n if (theme === 'dark') return 'bc-dark';\n if (theme === 'light') return 'bc-light';\n return 'bc-auto';\n}\n\nfunction openPanel({ root, host, dock, client, onClose }) {\n const cfg = client.config;\n const canCapture = cfg.attachments !== false;\n const canShoot = canCapture && screenshotSupported();\n const canRecord = canCapture && captureSupported();\n\n // Uploaded as they are added rather than on send: the upload is the slow\n // part, and doing it while the reporter is still typing means Send is\n // instant and a failure is visible next to the thing that failed.\n const items = [];\n let priority = 'MEDIUM';\n let recorder = null;\n let sending = false;\n\n const scrim = document.createElement('div');\n // The panel opens out of whichever corner the dock is in, clearing it by the\n // dock's measured height rather than a guess at the launcher's size.\n scrim.className = `scrim ${dock.classList.contains('at-left') ? 'at-left' : 'at-right'} `\n + `${dock.classList.contains('at-top') ? 'at-top' : 'at-bottom'}`;\n scrim.style.setProperty('--bc-dock-h', `${Math.round(dock.getBoundingClientRect().height)}px`);\n scrim.innerHTML = `\n <div class=\"panel\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"bc-h\">\n ${headHtml({\n id: 'bc-h',\n title: 'Tell us what went wrong',\n sub: \"Our assistant starts looking right away. We attach the page details for you.\",\n })}\n <div class=\"body\">\n <div class=\"slot\" role=\"alert\"></div>\n <div class=\"whoami\" hidden></div>\n <div class=\"known\" hidden></div>\n <div class=\"compose\">\n <div class=\"field\">\n <label for=\"bc-title\">What went wrong?</label>\n <input id=\"bc-title\" type=\"text\" placeholder=\"e.g. The checkout button does nothing\" autocomplete=\"off\"\n aria-describedby=\"bc-title-err\" />\n <p class=\"field-err\" id=\"bc-title-err\" hidden></p>\n </div>\n <div class=\"field\">\n <label for=\"bc-desc\">Anything else that helps <span class=\"opt\">(optional)</span></label>\n <textarea id=\"bc-desc\" placeholder=\"What you were doing, what you expected, what happened instead.\"></textarea>\n </div>\n <!-- Drawn only when this project asks for an identifier and the report\n does not already carry one. Between the description and the\n severity because that is where it belongs in the reading order:\n it is a question about what they just typed. -->\n <div class=\"refs\" hidden></div>\n <div class=\"field\">\n <label id=\"bc-prio-l\">How much is it getting in your way?</label>\n <div class=\"seg\" role=\"group\" aria-labelledby=\"bc-prio-l\">\n ${PRIORITIES.map((p) => `\n <button type=\"button\" data-prio=\"${p.value}\" aria-pressed=\"${p.value === priority}\">${p.label}</button>\n `).join('')}\n </div>\n </div>\n ${canCapture ? `\n <div class=\"field\">\n <div class=\"field-head\">\n <label id=\"bc-att-l\">Show us <span class=\"opt\">(optional)</span></label>\n <span class=\"count\" aria-live=\"polite\"></span>\n </div>\n <div class=\"tools\" role=\"group\" aria-labelledby=\"bc-att-l\">\n ${canShoot ? `<button class=\"tool\" type=\"button\" data-act=\"shot\">${ICONS.camera}Screenshot</button>` : ''}\n ${canRecord ? `<button class=\"tool\" type=\"button\" data-act=\"rec\">${ICONS.video}Record screen</button>` : ''}\n <button class=\"tool\" type=\"button\" data-act=\"file\">${ICONS.paperclip}Upload file</button>\n </div>\n <div class=\"thumbs\"></div>\n <input type=\"file\" accept=\"${REPORT_MIMES.join(',')}\" multiple hidden />\n </div>` : ''}\n </div>\n </div>\n <div class=\"foot\">\n <span class=\"note\" aria-live=\"polite\"></span>\n ${client.hasReports() ? '<button class=\"quiet\" type=\"button\" data-act=\"history\">Your reports</button>' : ''}\n <span class=\"spacer\"></span>\n <!-- Hidden until the API confirms this project can take a call, which\n most cannot. Drawn here rather than as a second launcher because it\n is the same errand as Send report — the difference is only whether\n the reporter would rather say it than type it. -->\n <button class=\"ghost\" type=\"button\" data-act=\"call\" hidden>${ICONS.phone}Talk to the team</button>\n <button class=\"primary\" type=\"button\" data-act=\"send\">Send report ${ICONS.send}</button>\n </div>\n <div class=\"powered\">Powered by\n <a class=\"brand-credit\" href=\"https://bugcatch.sphoro.com\" target=\"_blank\" rel=\"noopener noreferrer\" aria-label=\"BugCatch\">${BRAND}</a>\n </div>\n </div>`;\n\n const $ = (sel) => scrim.querySelector(sel);\n const titleInput = $('#bc-title');\n // Held, not looked up: the history and report screens replace the panel's\n // markup, and a draft saved on the way out must still read what was typed.\n const descInput = $('#bc-desc');\n const slot = $('.slot');\n const thumbs = $('.thumbs');\n const filePicker = scrim.querySelector('input[type=file]');\n const sendBtn = $('[data-act=\"send\"]');\n const callBtn = $('[data-act=\"call\"]');\n const note = $('.note');\n const compose = $('.compose');\n const whoami = $('.whoami');\n const refsBox = $('.refs');\n const known = $('.known');\n const historyBtn = $('[data-act=\"history\"]');\n\n const draftStatus = document.createElement('div');\n draftStatus.setAttribute('role', 'status');\n draftStatus.className = 'note';\n compose.prepend(draftStatus);\n const savedDraftKey = draftKey(cfg, readSession(cfg.projectId)?.email);\n let draftLoaded = cfg.drafts === false;\n let draftSubmitted = false;\n let draftWrites = Promise.resolve();\n let draftTimer;\n function persistDraft() {\n clearTimeout(draftTimer);\n if (!draftLoaded || draftSubmitted || cfg.drafts === false) return draftWrites;\n const value = titleInput.value || descInput.value || items.length ? {\n title: titleInput.value, description: descInput.value, priority,\n items: items.map(({ blob, mime, kind }) => ({ blob, mime, kind })),\n } : null;\n draftWrites = draftWrites.catch(() => {}).then(() => draftOperation(savedDraftKey, value))\n .then(() => { draftStatus.textContent = value ? 'Draft saved on this device' : ''; })\n .catch(() => { draftStatus.textContent = 'Draft could not be saved. Keep this tab open.'; });\n return draftWrites;\n }\n function scheduleDraft() { clearTimeout(draftTimer); draftTimer = setTimeout(persistDraft, 250); }\n compose.addEventListener('input', scheduleDraft);\n compose.addEventListener('change', scheduleDraft);\n const onPageHide = () => { void persistDraft(); };\n window.addEventListener('pagehide', onPageHide);\n function reconnectUploads() { items.filter((it) => it.status === 'failed').forEach(upload); }\n window.addEventListener('online', reconnectUploads);\n if (cfg.drafts !== false) {\n // Disable composition briefly so a late storage read cannot replace new text.\n compose.inert = true;\n readDraft(savedDraftKey).then((draft) => {\n if (closed || !draft) return;\n draftStatus.innerHTML = '<span>You have an unfinished report on this device.</span> <button type=\"button\" data-resume>Resume report</button> <button type=\"button\" data-discard>Discard</button>';\n draftStatus.querySelector('[data-resume]').onclick = () => {\n titleInput.value = draft.title || '';\n descInput.value = draft.description || '';\n priority = PRIORITIES.some((p) => p.value === draft.priority) ? draft.priority : 'MEDIUM';\n scrim.querySelectorAll('[data-prio]').forEach((b) => b.setAttribute('aria-pressed', String(b.dataset.prio === priority)));\n for (const item of (draft.items || []).slice(0, cfg.maxAttachments ?? 5)) addItem(item);\n draftLoaded = true;\n void persistDraft();\n };\n draftStatus.querySelector('[data-discard]').onclick = () => {\n draftLoaded = true;\n void persistDraft();\n };\n }).catch(() => { draftStatus.textContent = 'Draft storage is unavailable. Keep this tab open.'; })\n .finally(() => { compose.inert = false; if (!draftStatus.querySelector('[data-resume]')) draftLoaded = true; });\n }\n\n /* ---------- lifecycle ---------- */\n\n const previouslyFocused = document.activeElement;\n const rootStyle = document.documentElement.style;\n const previousOverflow = rootStyle.overflow;\n rootStyle.overflow = 'hidden';\n\n let closed = false;\n // Assigned further down, once `showReport` exists to be called back into.\n let stopListening = null;\n function destroy() {\n if (closed) return;\n void persistDraft();\n closed = true;\n clearTimeout(draftTimer);\n window.removeEventListener('pagehide', onPageHide);\n window.removeEventListener('online', reconnectUploads);\n recorder?.cancel();\n // Stop listening before the DOM goes: a frame arriving after this would\n // redraw a panel that is no longer on the page.\n stopListening?.();\n items.forEach((it) => it.previewUrl && URL.revokeObjectURL(it.previewUrl));\n rootStyle.overflow = previousOverflow;\n document.removeEventListener('keydown', onKeydown, true);\n scrim.remove();\n root.querySelector('.recbar')?.remove();\n dock.hidden = false;\n host.style.opacity = '';\n onClose?.();\n }\n\n function close() {\n if (closed) return;\n // Let the exit animation play, but never wait on an event that a\n // reduced-motion user's near-zero animation might deliver unpredictably.\n scrim.classList.add('closing');\n setTimeout(destroy, 160);\n try { previouslyFocused?.focus?.(); } catch { /* element may be gone */ }\n }\n\n function onKeydown(e) {\n if (e.key === 'Escape') {\n e.stopPropagation();\n if (recorder) { stopRecording({ keep: false }); return; }\n if (!sending) close();\n return;\n }\n if (e.key !== 'Tab' || recorder) return;\n // Focus trap: the panel is modal, so Tab must not reach the host page.\n const focusable = [...scrim.querySelectorAll(\n 'button:not(:disabled), input:not([hidden]), textarea, select, [href]',\n )].filter((el) => el.offsetParent !== null || el === document.activeElement);\n if (!focusable.length) return;\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n const active = scrim.getRootNode().activeElement;\n if (e.shiftKey && active === first) { e.preventDefault(); last.focus(); }\n else if (!e.shiftKey && active === last) { e.preventDefault(); first.focus(); }\n }\n\n document.addEventListener('keydown', onKeydown, true);\n scrim.addEventListener('click', (e) => { if (e.target === scrim && !sending) close(); });\n $('[data-act=\"close\"]').addEventListener('click', close);\n\n /* ---------- severity ---------- */\n\n scrim.querySelectorAll('[data-prio]').forEach((btn) => {\n btn.addEventListener('click', () => {\n priority = btn.dataset.prio;\n scheduleDraft();\n scrim.querySelectorAll('[data-prio]').forEach((b) => {\n b.setAttribute('aria-pressed', String(b === btn));\n });\n });\n });\n\n /* ---------- attachments ---------- */\n\n function say(text, kind = 'err') {\n slot.innerHTML = text\n ? `<div class=\"msg ${kind}\">${kind === 'err' ? ICONS.alert : ''}<span>${escapeHtml(text)}</span></div>`\n : '';\n }\n\n // The one required field says what is missing right under itself, rather\n // than in a banner the reporter has to connect back to it.\n const titleErr = $('#bc-title-err');\n function flagTitle(text) {\n titleErr.hidden = !text;\n titleErr.innerHTML = text ? `${ICONS.alert}<span>${escapeHtml(text)}</span>` : '';\n titleInput.classList.toggle('invalid', Boolean(text));\n titleInput.setAttribute('aria-invalid', String(Boolean(text)));\n }\n titleInput.addEventListener('input', () => { if (titleInput.value.trim()) flagTitle(''); });\n\n function refreshSendState() {\n const busy = items.some((it) => it.status === 'uploading');\n sendBtn.disabled = sending || busy || items.some((it) => it.status === 'failed');\n note.textContent = busy ? 'Waiting for the upload to finish…' : items.some((it) => it.status === 'failed') ? 'Retry or remove failed attachments before sending.' : '';\n const max = cfg.maxAttachments ?? 5;\n scrim.querySelectorAll('.tool').forEach((t) => {\n t.disabled = sending || items.length >= max;\n });\n const count = $('.field-head .count');\n if (count) count.textContent = items.length ? `${items.length} of ${max}` : '';\n }\n\n function drawThumb(item) {\n const el = item.el;\n const label = item.kind === 'VIDEO' ? 'Video' : 'Shot';\n const preview = item.kind === 'VIDEO'\n ? `<video src=\"${item.previewUrl}\" muted playsinline preload=\"metadata\"></video>`\n : `<img src=\"${item.previewUrl}\" alt=\"\" />`;\n\n let veil = '';\n if (item.status === 'uploading') {\n veil = `<div class=\"veil\"><div class=\"spinner\"></div><div class=\"bar\"><i style=\"width:${Math.round(item.progress * 100)}%\"></i></div></div>`;\n } else if (item.status === 'failed') {\n veil = `<div class=\"veil err\"><button type=\"button\" data-act=\"retry\" aria-label=\"Retry upload\">${ICONS.retry}</button>Failed</div>`;\n }\n\n el.innerHTML = `\n ${preview}\n <span class=\"kind\">${label} · ${humanSize(item.size)}</span>\n <button class=\"drop\" type=\"button\" data-act=\"drop\" aria-label=\"Remove ${label.toLowerCase()}\">${ICONS.close}</button>\n ${veil}`;\n\n el.querySelector('[data-act=\"drop\"]').addEventListener('click', () => removeItem(item));\n el.querySelector('[data-act=\"retry\"]')?.addEventListener('click', () => upload(item));\n }\n\n function removeItem(item) {\n const i = items.indexOf(item);\n if (i >= 0) items.splice(i, 1);\n if (item.previewUrl) URL.revokeObjectURL(item.previewUrl);\n item.el.remove();\n void persistDraft();\n refreshSendState();\n }\n\n async function upload(item) {\n item.status = 'uploading';\n item.progress = 0;\n drawThumb(item);\n refreshSendState();\n try {\n const { storageKey, mime } = await client.uploadAttachment(item.blob, item.mime, (p) => {\n item.progress = p;\n // Only the width changes while uploading — repainting the whole tile\n // would restart the entrance animation on every progress event.\n const fill = item.el.querySelector('.bar i');\n if (fill) fill.style.width = `${Math.round(p * 100)}%`;\n });\n item.storageKey = storageKey;\n item.mime = mime;\n item.status = 'ready';\n } catch (err) {\n item.status = 'failed';\n say(err.message.replace('[BugCatch] ', ''));\n }\n drawThumb(item);\n refreshSendState();\n }\n\n function addItem({ blob, mime, kind }) {\n const max = cfg.maxAttachmentBytes ?? 25 * 1024 * 1024;\n if (blob.size > max) {\n say(`That file is ${humanSize(blob.size)} — the limit is ${humanSize(max)}.`);\n return;\n }\n // Held to the report set, not the full upload set. A report's attachments\n // are typed SCREENSHOT or VIDEO, so a document accepted here would be filed\n // as a screenshot and render as a broken image. Documents and voice notes\n // go on a message instead, where they keep their own type.\n const normalized = normalizeMime(mime || blob.type, REPORT_MIMES);\n if (!normalized) {\n say('Only PNG, JPEG, WebP, WebM and MP4 files can be attached to a report.');\n return;\n }\n const item = {\n blob, kind, mime: normalized, size: blob.size,\n previewUrl: URL.createObjectURL(blob),\n status: 'uploading', progress: 0, storageKey: null,\n el: document.createElement('div'),\n };\n item.el.className = 'thumb';\n items.push(item);\n thumbs.appendChild(item.el);\n say('');\n void persistDraft();\n upload(item);\n }\n\n /* Hide our own UI so the capture shows the page, not this dialog. */\n async function withWidgetHidden(fn) {\n host.style.opacity = '0';\n host.style.pointerEvents = 'none';\n // One frame so the browser has actually painted the hidden state before\n // the picker — and the frame we grab — is taken.\n await new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(r)));\n try {\n return await fn();\n } finally {\n host.style.opacity = '';\n host.style.pointerEvents = '';\n }\n }\n\n scrim.querySelector('[data-act=\"shot\"]')?.addEventListener('click', async () => {\n try {\n const shot = await withWidgetHidden(captureScreenshot);\n addItem(shot);\n } catch (err) {\n // NotAllowedError is the user dismissing the picker — not a failure.\n if (err?.name !== 'NotAllowedError') say(`Screenshot failed: ${err.message}`);\n }\n });\n\n scrim.querySelector('[data-act=\"file\"]')?.addEventListener('click', () => filePicker.click());\n filePicker?.addEventListener('change', () => {\n const room = (cfg.maxAttachments ?? 5) - items.length;\n [...filePicker.files].slice(0, Math.max(room, 0)).forEach((file) => {\n addItem({\n blob: file,\n mime: file.type,\n kind: file.type.startsWith('video/') ? 'VIDEO' : 'SCREENSHOT',\n });\n });\n filePicker.value = '';\n });\n\n /* ---------- recording ---------- */\n\n scrim.querySelector('[data-act=\"rec\"]')?.addEventListener('click', async () => {\n const maxMs = cfg.maxRecordingMs ?? 120_000;\n let bar;\n try {\n recorder = await startRecording({\n maxMs,\n onTick: (s) => { if (bar) bar.querySelector('.time').textContent = humanDuration(s); },\n onAutoStop: () => { if (recorder) stopRecording({ keep: true }); },\n });\n } catch (err) {\n if (err?.name !== 'NotAllowedError') say(`Recording failed: ${err.message}`);\n return;\n }\n\n // Step out of the way for the duration: a recording of this dialog sitting\n // over the page is not what anyone means by \"record the bug\".\n scrim.style.display = 'none';\n dock.hidden = true;\n\n bar = document.createElement('div');\n bar.className = 'recbar';\n bar.innerHTML = `\n <span class=\"dot\"></span>\n <span class=\"time\">0:00</span>\n <span class=\"hint\">Recording — up to ${humanDuration(Math.round(maxMs / 1000))}</span>\n <button class=\"stop\" type=\"button\">Stop</button>\n <button class=\"ghost\" type=\"button\">Discard</button>`;\n bar.querySelector('.stop').addEventListener('click', () => stopRecording({ keep: true }));\n bar.querySelector('.ghost').addEventListener('click', () => stopRecording({ keep: false }));\n root.appendChild(bar);\n bar.querySelector('.stop').focus();\n });\n\n async function stopRecording({ keep }) {\n const rec = recorder;\n if (!rec) return;\n recorder = null;\n root.querySelector('.recbar')?.remove();\n scrim.style.display = '';\n dock.hidden = false;\n\n if (!keep) { rec.cancel(); titleInput.focus(); return; }\n try {\n const clip = await rec.stop();\n if (clip.blob.size) addItem(clip);\n else say('That recording came back empty — nothing was captured.');\n } catch (err) {\n say(`Recording failed: ${err.message}`);\n }\n titleInput.focus();\n }\n\n /* ---------- which record is this about ---------- */\n\n // What this project asks for, and what the reporter has answered.\n //\n // The question is asked at Send and not before, which is the whole design of\n // it. A form that opens with \"loan number\" above \"what went wrong\" tells\n // somebody they have paperwork to do before they may complain, and most of\n // them will not — while the same question, asked once, after they have said\n // their piece, and only when what they said did not already contain the\n // answer, is a reasonable thing to be asked. Most reporters never see it: the\n // loan number was in the sentence.\n let refConfig = { policy: 'off', fields: [] };\n let refChoice = null; // which field they are answering\n let refValue = '';\n let refError = '';\n // Set once they have answered or explicitly skipped, so Send stops asking.\n let refSettled = false;\n // Set once we have said \"that does not look right\" about the current value.\n // Its own flag rather than reusing refSettled, which was the first attempt\n // and was a loop: the warning returns early, the next press re-validates the\n // same unchanged value, warns again, and the report can never be sent. One\n // warning per value, and then it is their call.\n let refWarned = false;\n\n client.referenceFields()\n .then((cfg) => { refConfig = cfg; })\n .catch(() => { /* off is the safe answer to every failure */ });\n\n /** What the report already carries, read out of what they typed. */\n function referencesInText() {\n if (refConfig.policy === 'off' || !refConfig.fields.length) return [];\n return matchReferences(refConfig.fields, [titleInput.value, descInput.value]);\n }\n\n /** The answer to send, or an empty list. */\n function answeredReference() {\n if (!refChoice || !refValue.trim()) return [];\n return [{ key: refChoice, value: refValue.trim().slice(0, 120) }];\n }\n\n function drawRefs() {\n if (!refChoice) { refsBox.hidden = true; refsBox.innerHTML = ''; return; }\n const field = refConfig.fields.find((f) => f.key === refChoice) || refConfig.fields[0];\n const optional = refConfig.policy !== 'required';\n\n refsBox.hidden = false;\n // A handful of kinds read best as chips to tap; a long list goes back to\n // a select so the card does not turn into a wall of pills.\n const many = refConfig.fields.length > 6;\n refsBox.innerHTML = `\n <div class=\"ask-head\">\n <span class=\"avatar\" aria-hidden=\"true\">${ICONS.sparkle}</span>\n <div>\n <strong>${optional ? 'One more thing' : 'We need one detail'}</strong>\n <p class=\"why\">${escapeHtml(\n optional\n ? 'So we can find your record — this helps us answer faster.'\n : 'We need one of these to look this up.',\n )}</p>\n </div>\n </div>\n ${refConfig.fields.length > 1 && !many ? `\n <div class=\"chips\" role=\"group\" aria-label=\"Which detail you are giving\">\n ${refConfig.fields.map((f) => `\n <button type=\"button\" data-ref-kind=\"${escapeHtml(f.key)}\" aria-pressed=\"${f.key === field.key}\">${escapeHtml(f.label)}</button>\n `).join('')}\n </div>` : ''}\n <div class=\"row\">\n ${many ? `\n <select class=\"ref-kind\" aria-label=\"Which detail you are giving\">\n ${refConfig.fields.map((f) => `\n <option value=\"${escapeHtml(f.key)}\" ${f.key === field.key ? 'selected' : ''}>${escapeHtml(f.label)}</option>\n `).join('')}\n </select>` : ''}\n <input class=\"ref-value${refError ? ' invalid' : ''}\" type=\"${field.inputMode === 'email' ? 'email' : 'text'}\"\n inputmode=\"${escapeHtml(field.inputMode === 'numeric' || field.inputMode === 'tel' ? 'numeric' : 'text')}\"\n value=\"${escapeHtml(refValue)}\"\n placeholder=\"${escapeHtml(field.placeholder || field.label)}\"\n aria-label=\"${escapeHtml(field.label)}\" ${refError ? 'aria-invalid=\"true\"' : ''} />\n </div>\n ${field.hint ? `<p class=\"hint\">${escapeHtml(field.hint)}</p>` : ''}\n ${refError ? `<p class=\"field-err\" role=\"alert\">${ICONS.alert}<span>${escapeHtml(refError)}</span></p>` : ''}\n ${optional ? '<button type=\"button\" class=\"link\" data-act=\"ref-skip\">I don\\'t have this — send without it</button>' : ''}`;\n\n refsBox.querySelectorAll('[data-ref-kind]').forEach((chip) => {\n chip.addEventListener('click', () => {\n refChoice = chip.dataset.refKind;\n refError = '';\n refWarned = false;\n drawRefs();\n });\n });\n const value = refsBox.querySelector('.ref-value');\n value.addEventListener('input', () => {\n refValue = value.value;\n refError = '';\n // A changed value has not been warned about yet.\n refWarned = false;\n });\n // Enter in the box is the same gesture as pressing Send, which is what\n // somebody who has just been asked one question expects it to be.\n value.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') { e.preventDefault(); sendBtn.click(); }\n });\n refsBox.querySelector('.ref-kind')?.addEventListener('change', (e) => {\n refChoice = e.target.value;\n refError = '';\n drawRefs();\n refsBox.querySelector('.ref-value')?.focus();\n });\n refsBox.querySelector('[data-act=\"ref-skip\"]')?.addEventListener('click', () => {\n // Skipping is answering. Without this the panel asks again on the next\n // press and the reporter is in a loop with a button that does nothing.\n refSettled = true;\n refChoice = null;\n refValue = '';\n refError = '';\n refWarned = false;\n drawRefs();\n say('');\n });\n\n value.focus();\n }\n\n /**\n * Whether Send should stop and ask.\n *\n * True at most once per report. The question is skippable on an `optional`\n * project and not on a `required` one, and on both the answer to \"have we\n * already got one\" is read from the text every time — somebody who types the\n * loan number into the description after being asked has answered the\n * question, and being asked again would be the widget not listening.\n */\n function needsReference() {\n if (refConfig.policy === 'off' || !refConfig.fields.length) return false;\n if (referencesInText().length) return false;\n if (answeredReference().length) return false;\n return !refSettled || refConfig.policy === 'required';\n }\n\n /* ---------- send ---------- */\n\n sendBtn.addEventListener('click', async () => {\n const title = titleInput.value;\n if (!title.trim()) {\n flagTitle('Add a few words about what went wrong — a short sentence is plenty.');\n titleInput.focus();\n return;\n }\n\n // Ask, if this project asks and nothing they wrote answered it. The first\n // press opens the question rather than sending; the second sends. That\n // costs one extra press on projects that want an identifier, and it is the\n // only shape that does not put paperwork in front of a person who came here\n // to say something is broken.\n if (needsReference()) {\n refChoice = refChoice || refConfig.fields[0].key;\n drawRefs();\n say('');\n return;\n }\n\n // They chose a field and typed something that is not that shape. Advisory\n // on the server — a value that fails is still what they believe identifies\n // them, and it is kept — but worth one chance to fix a typo here, where the\n // number is still on screen.\n if (refChoice && refValue.trim()) {\n const field = refConfig.fields.find((f) => f.key === refChoice);\n if (!refWarned && field && !validateReference(field, refValue)) {\n refError = `That does not look like a ${field.label.toLowerCase()}. Press Send again to use it anyway.`;\n // Once, and then it is their call. Our idea of the shape of somebody's\n // account number is not better than theirs, and refusing outright is\n // how a genuine reference gets lost — so the second press sends it and\n // the API keeps it, marked as not matching the expected format.\n refWarned = true;\n drawRefs();\n return;\n }\n }\n\n sending = true;\n say('');\n refreshSendState();\n const original = sendBtn.innerHTML;\n sendBtn.innerHTML = '<span class=\"spinner\" aria-hidden=\"true\"></span>Sending…';\n\n try {\n const res = await client.report({\n title,\n description: descInput.value,\n priority,\n references: answeredReference(),\n attachments: items\n .filter((it) => it.status === 'ready')\n .map((it) => ({ type: it.kind, storageKey: it.storageKey, mime: it.mime })),\n });\n if (res?.cancelled) { sending = false; sendBtn.innerHTML = original; refreshSendState(); return; }\n draftSubmitted = true;\n clearTimeout(draftTimer);\n await draftWrites;\n if (cfg.drafts !== false) await draftOperation(savedDraftKey, null).catch(() => {});\n succeed(res);\n } catch (err) {\n sending = false;\n sendBtn.innerHTML = original;\n say(err.message.replace('[BugCatch] ', ''));\n refreshSendState();\n // One reason a send fails is that this browser was signed out while the\n // panel sat open — the same address signed in elsewhere. The message\n // above already says so; this puts the panel back in step with it\n // instead of leaving a Send button that will fail the same way again.\n if (login.mode !== 'off') refreshLogin().catch(() => {});\n }\n });\n\n scrim.querySelector('[data-act=\"history\"]')?.addEventListener('click', showHistory);\n\n /* ---------- what is already known ---------- */\n\n // The list as it was last fetched, and who has said what about it. Held here\n // rather than re-read from the DOM so a re-render after a join does not have\n // to parse its own markup back out.\n let issues = [];\n let issueError = '';\n let askingFor = null;\n let matchAbort = null;\n\n // Asked once when the dialog opens, like the call button above and for the\n // same reason: it is one request per opened widget rather than one per page\n // load of every site running this SDK, and most projects will answer with an\n // empty list forever.\n function loadKnownIssues() {\n if (cfg.knownIssues === false) return;\n client.knownIssues()\n .then((rows) => { issues = rows; drawKnown(); })\n .catch(() => { /* a help panel that cannot load is simply not drawn */ });\n }\n\n // Re-run as the title is typed, so the panel narrows to what they are\n // actually describing. Debounced, and the previous request is aborted rather\n // than left to land: a fast typist otherwise renders whichever response comes\n // back last, which is not necessarily the one matching the box.\n let matchTimer = null;\n function matchKnown(query) {\n if (cfg.knownIssues === false) return;\n clearTimeout(matchTimer);\n matchAbort?.abort();\n const q = (query || '').trim();\n if (q.length < 3) { loadKnownIssues(); return; }\n matchTimer = setTimeout(() => {\n matchAbort = new AbortController();\n client.matchKnownIssues(q, { signal: matchAbort.signal })\n .then((rows) => {\n // An empty result means \"nothing like this is known\", which is not\n // the same as \"nothing is known\" — fall back to the full list rather\n // than blanking a panel the reporter was reading.\n if (rows.length) { issues = rows; drawKnown(); }\n })\n .catch(() => {});\n }, 250);\n }\n\n function drawKnown() {\n if (!issues.length) { known.hidden = true; known.innerHTML = ''; return; }\n known.hidden = false;\n known.innerHTML = `\n <p class=\"known-h\">Already reported</p>\n ${issues.map((issue) => `\n <div class=\"issue ${issue.joined ? 'is-joined' : ''}\">\n <span class=\"who\">\n <span class=\"t\">${escapeHtml(issue.title)}</span>\n <span class=\"meta\">${escapeHtml(knownMeta(issue))}</span>\n </span>\n <button class=\"join\" type=\"button\" data-join=\"${escapeHtml(issue.id)}\"\n ${issue.joined ? 'disabled' : ''}>${issue.joined ? 'Following' : 'Me too'}</button>\n </div>\n ${askingFor === issue.id ? `\n <div class=\"ask\">\n <input type=\"email\" class=\"join-email\" placeholder=\"you@example.com\"\n aria-label=\"Your email, so we can tell you when this is fixed\" />\n <button class=\"ghost\" type=\"button\" data-join-confirm=\"${escapeHtml(issue.id)}\">Follow</button>\n </div>` : ''}\n `).join('')}\n ${issueError ? `<p class=\"err\">${escapeHtml(issueError)}</p>` : ''}`;\n }\n\n // What a reporter is told about an issue beside its title. Deliberately not\n // its severity: how bad the team thinks this is, published to everybody who\n // opens the widget, is an internal judgement that reads as a promise.\n function knownMeta(issue) {\n const state = issue.status === 'IN_PROGRESS' ? 'Being worked on now' : 'Not fixed yet';\n // The count is dropped entirely at zero rather than written as \"0 people\",\n // or as a bare \"Reported\" that says nothing the heading above has not\n // already said. Nobody having pressed the button yet is not a fact about\n // this issue worth a reporter's attention.\n const others = issue.affectedCount === 0\n ? ''\n : ` · ${issue.affectedCount} ${issue.affectedCount === 1 ? 'person has' : 'people have'} said this is happening to them`;\n return `${state} · reported ${relativeDay(issue.since)}${others}`;\n }\n\n known.addEventListener('click', async (e) => {\n const start = e.target.closest('[data-join]');\n const confirm = e.target.closest('[data-join-confirm]');\n if (!start && !confirm) return;\n\n const bugId = (confirm || start).getAttribute(confirm ? 'data-join-confirm' : 'data-join');\n issueError = '';\n\n // An address is what joining buys them — being told when it is fixed — so\n // ask for one when neither a sign-in nor identify() has supplied it, rather\n // than recording somebody nobody can ever write to.\n const email = known.querySelector('.join-email')?.value?.trim()\n || client.signedInAs()?.email\n || client.config.user?.email;\n if (!email) {\n askingFor = bugId;\n drawKnown();\n known.querySelector('.join-email')?.focus();\n return;\n }\n\n try {\n const result = await client.meToo(bugId, { email });\n askingFor = null;\n issues = issues.map((issue) => (issue.id === bugId\n ? { ...issue, joined: true, affectedCount: result.affectedCount ?? issue.affectedCount }\n : issue));\n drawKnown();\n } catch (err) {\n issueError = err.message.replace('[BugCatch] ', '');\n drawKnown();\n }\n });\n\n loadKnownIssues();\n titleInput.addEventListener('input', () => matchKnown(titleInput.value));\n\n /* ---------- talk to the team ---------- */\n\n // Asked once, when the dialog opens, and the button stays hidden unless the\n // answer is yes. Doing it here rather than at init keeps one request off the\n // page load of every site this SDK is on, for a button most of them will\n // never show.\n //\n // The wording is fixed because there is one kind of call: it rings a room and\n // may go unanswered. A button that said \"talk to us\" would have somebody\n // expecting an instant answer sit through twenty seconds of ringing.\n let callOffered = false;\n function offerCall() {\n client.callKind()\n .then((kind) => {\n if (!kind) return;\n callOffered = true;\n // Never over a panel that is still asking who this is: the call would\n // file a report, and on a project that requires sign-in that is the one\n // thing this panel is holding back.\n callBtn.hidden = mustSignIn();\n })\n .catch(() => { /* not available is the safe answer to every failure */ });\n }\n offerCall();\n\n /* ---------- who is reporting ---------- */\n\n // Three states, and the project picks which: 'off' is every project that has\n // not asked for this and draws nothing at all; 'optional' offers a sign-in\n // above the form that the reporter may ignore; 'required' replaces the form\n // with it until they have.\n //\n // Asked on every open rather than remembered for the page, because the answer\n // includes whether this browser's session is still live — and it can end\n // somewhere else, when the same address signs in on another device. A widget\n // holding a cached \"signed in as you\" would find out at the moment the report\n // failed, which is the worst moment to discover it.\n let login = { mode: client.lastLoginMode || 'off', signedIn: null };\n // 'idle' — showing who they are; 'email' / 'code' — the two steps of getting there.\n let step = 'idle';\n let pendingEmail = client.config.user?.email || '';\n let pendingCode = '';\n let signInBusy = false;\n let signInError = '';\n\n const mustSignIn = () => login.mode === 'required' && !login.signedIn;\n\n /** Show or hide everything that only makes sense once we know who is filing. */\n function applyGate() {\n const shut = mustSignIn();\n compose.hidden = shut;\n sendBtn.hidden = shut;\n if (historyBtn) historyBtn.hidden = shut;\n // The call offer is the same errand as the form, so it goes with it — and\n // comes back only if the API had already said this project can take one.\n if (shut) callBtn.hidden = true;\n else if (callOffered) callBtn.hidden = false;\n }\n\n function drawWhoami() {\n if (login.mode === 'off') {\n whoami.hidden = true;\n whoami.innerHTML = '';\n return;\n }\n whoami.hidden = false;\n whoami.classList.toggle('asking', step !== 'idle');\n\n const problem = signInError ? `<div class=\"after err\">${escapeHtml(signInError)}</div>` : '';\n\n if (step === 'idle') {\n if (login.signedIn) {\n whoami.innerHTML = `<span class=\"who\">Signed in as <strong>${escapeHtml(login.signedIn.email)}</strong></span>`\n + '<button type=\"button\" class=\"link\" data-act=\"signout\">Sign out</button>';\n return;\n }\n // An address the host application passed for its own signed-in user is\n // not nothing — the report will carry it — so saying \"anonymously\" over\n // the top of one would be untrue. It is simply not proved, which is what\n // the offer beside it is for.\n const claimed = client.config.user?.email;\n whoami.innerHTML = `<span class=\"who\">${login.mode === 'required'\n ? 'Sign in to report a bug here.'\n : claimed\n ? `Reporting as <strong>${escapeHtml(claimed)}</strong>.`\n : 'Reporting anonymously — sign in and we can tell you what happens next.'}</span>`\n + '<button type=\"button\" class=\"link\" data-act=\"signin\">Sign in</button>';\n return;\n }\n\n if (step === 'email') {\n whoami.innerHTML = `\n <p class=\"why\">Sign in with any email address — we'll send you a code.\n It's how you hear back about this report${login.mode === 'required' ? ', and this project asks for it' : ''}.</p>\n <div class=\"row\">\n <input type=\"email\" data-fld=\"email\" value=\"${escapeHtml(pendingEmail)}\"\n placeholder=\"you@example.com\" autocomplete=\"email\" spellcheck=\"false\"\n aria-label=\"Your email address\" ${signInBusy ? 'disabled' : ''} />\n <button type=\"button\" class=\"primary\" data-act=\"code\" ${signInBusy ? 'disabled' : ''}>\n ${signInBusy ? 'Sending…' : 'Send code'}\n </button>\n </div>\n ${problem}\n ${login.mode === 'optional'\n ? '<div class=\"after\"><button type=\"button\" class=\"link\" data-act=\"skip\">Not now</button></div>'\n : ''}`;\n return;\n }\n\n whoami.innerHTML = `\n <p class=\"why\">We sent a code to <strong>${escapeHtml(pendingEmail)}</strong>. It expires in ten minutes.</p>\n <div class=\"row\">\n <input type=\"text\" class=\"code\" data-fld=\"code\" value=\"${escapeHtml(pendingCode)}\"\n placeholder=\"123456\" inputmode=\"numeric\" autocomplete=\"one-time-code\"\n maxlength=\"8\" aria-label=\"The code we emailed you\" ${signInBusy ? 'disabled' : ''} />\n <button type=\"button\" class=\"primary\" data-act=\"confirm\" ${signInBusy ? 'disabled' : ''}>\n ${signInBusy ? 'Checking…' : 'Sign in'}\n </button>\n </div>\n ${problem}\n <div class=\"after\">\n <button type=\"button\" class=\"link\" data-act=\"resend\" ${signInBusy ? 'disabled' : ''}>Send it again</button>\n <button type=\"button\" class=\"link\" data-act=\"signin-back\" ${signInBusy ? 'disabled' : ''}>Use another address</button>\n </div>`;\n }\n\n /** Redraw, and put the cursor where the reporter is being asked to type. */\n function drawSignIn({ focus = false } = {}) {\n drawWhoami();\n applyGate();\n if (!focus || signInBusy) return;\n const field = whoami.querySelector('[data-fld]');\n if (field) { field.focus(); field.setSelectionRange?.(field.value.length, field.value.length); }\n }\n\n // Typed values survive a redraw because they are held here, not read back off\n // a node that the next render replaces.\n whoami.addEventListener('input', (e) => {\n const field = e.target.closest('[data-fld]');\n if (!field) return;\n if (field.dataset.fld === 'email') pendingEmail = field.value;\n else pendingCode = field.value;\n });\n\n whoami.addEventListener('keydown', (e) => {\n if (e.key !== 'Enter' || !e.target.closest('[data-fld]')) return;\n e.preventDefault();\n if (step === 'email') requestCode();\n else confirmCode();\n });\n\n async function requestCode({ resent = false } = {}) {\n const email = pendingEmail.trim();\n if (!email) { signInError = 'Enter the address to send the code to.'; drawSignIn({ focus: true }); return; }\n\n signInBusy = true; signInError = ''; drawSignIn();\n try {\n await client.signIn(email);\n pendingEmail = email;\n if (!resent) pendingCode = '';\n step = 'code';\n } catch (err) {\n signInError = err.message;\n } finally {\n signInBusy = false;\n drawSignIn({ focus: true });\n }\n }\n\n async function confirmCode() {\n const code = pendingCode.trim();\n if (!code) { signInError = 'Enter the code we emailed you.'; drawSignIn({ focus: true }); return; }\n\n signInBusy = true; signInError = ''; drawSignIn();\n try {\n const who = await client.confirmSignIn(pendingEmail.trim(), code);\n login.signedIn = who;\n step = 'idle';\n pendingCode = '';\n say('');\n // Signing in is what makes a call possible on most projects — there is\n // now somebody to attribute it to — so the offer is asked for again\n // rather than staying hidden until the panel is reopened.\n offerCall();\n } catch (err) {\n signInError = err.message;\n } finally {\n signInBusy = false;\n drawSignIn({ focus: step !== 'idle' });\n }\n }\n\n whoami.addEventListener('click', async (e) => {\n const btn = e.target.closest('button[data-act]');\n if (!btn) return;\n const act = btn.dataset.act;\n\n if (act === 'signin') { step = 'email'; signInError = ''; drawSignIn({ focus: true }); return; }\n if (act === 'skip') { step = 'idle'; signInError = ''; drawSignIn(); return; }\n // Namespaced: the report screen has a back button of its own, and the two\n // must not be one selector away from each other.\n if (act === 'signin-back') { step = 'email'; signInError = ''; pendingCode = ''; drawSignIn({ focus: true }); return; }\n if (act === 'code') { requestCode(); return; }\n if (act === 'resend') { requestCode({ resent: true }); return; }\n if (act === 'confirm') { confirmCode(); return; }\n if (act === 'signout') {\n signInBusy = true; drawSignIn();\n try {\n await client.signOut();\n } finally {\n login.signedIn = null;\n signInBusy = false;\n // On a project that requires it, signing out puts the form away again\n // and asks — which is what applyGate does off the back of this.\n step = login.mode === 'required' ? 'email' : 'idle';\n drawSignIn({ focus: step !== 'idle' });\n }\n }\n });\n\n /** Re-read the project's setting and this browser's session, and redraw. */\n async function refreshLogin({ announce = false } = {}) {\n const state = await client.loginState();\n login = { mode: state.mode, signedIn: state.signedIn };\n client.lastLoginMode = state.mode;\n // Nothing to type on a project that requires sign-in and has nobody in it.\n if (mustSignIn() && step === 'idle') step = 'email';\n if (login.signedIn) step = 'idle';\n if (announce && state.signedOutReason) say(state.signedOutReason, 'info');\n drawSignIn();\n }\n\n refreshLogin({ announce: true }).catch(() => { /* a widget that can't ask stays open */ });\n\n callBtn.addEventListener('click', async () => {\n sending = true;\n say('');\n refreshSendState();\n callBtn.disabled = true;\n callBtn.innerHTML = '<span class=\"spinner\"></span>Ringing…';\n\n try {\n // No id: this files the report from what the page was doing and rings the\n // team about it, in one round trip.\n const handle = await client.startCall();\n showCall(handle);\n } catch (err) {\n sending = false;\n callBtn.disabled = false;\n callBtn.innerHTML = `${ICONS.phone}Talk to the team`;\n refreshSendState();\n // A report filed with no call is still a report. Saying \"the call\n // failed\" and nothing else would leave the reporter thinking their\n // context went nowhere, and pressing Send again would file it twice.\n if (err.filed) succeed(err.filed, { callFailed: err.reason || 'The call could not be started.' });\n else {\n say(err.message.replace('[BugCatch] ', ''));\n // The same follow-up the Send button does, because a call can fail for\n // the same reason a report can: this browser was signed out while the\n // panel sat open. Without it the message says sign in while the row\n // under it still names who signed in, which reads as a broken widget.\n if (login.mode !== 'off') refreshLogin().catch(() => {});\n }\n }\n });\n\n /**\n * The call, once the team is being rung.\n *\n * Replaces the panel for the same reason the history view does: it is the\n * same dialog answering a different question, and the reporter got here from\n * one launcher. There is no way back to the form — the report has already\n * been filed, and a Cancel that implied otherwise would be a lie.\n */\n function showCall(handle) {\n const panel = scrim.querySelector('.panel');\n const ref = handle?.bugId ? String(handle.bugId).slice(0, 8) : null;\n\n panel.innerHTML = `\n <div class=\"call\">\n <div class=\"call-head\">\n <span class=\"call-dot\" aria-hidden=\"true\"></span>\n <strong class=\"call-state\">Ringing…</strong>\n </div>\n <p class=\"call-note\">\n We're ringing the team now. Someone will pick up if they're free — your report${\n ref ? ` <code>${escapeHtml(ref)}</code>` : ''} is already filed either way, so nothing is lost if nobody answers.\n </p>\n <div class=\"foot\">\n <span class=\"spacer\"></span>\n <button class=\"primary\" type=\"button\" data-act=\"hangup\">Cancel</button>\n </div>\n </div>`;\n\n const stateEl = panel.querySelector('.call-state');\n const dot = panel.querySelector('.call-dot');\n\n // A call spends its first twenty seconds ringing, and can end by nobody\n // picking up — which is not a failure and must not read as one.\n const WORDING = {\n ringing: 'Ringing the team…',\n connecting: 'Someone answered — connecting…',\n connected: 'Connected',\n missed: 'Nobody was free',\n ended: 'Call ended',\n failed: 'The call dropped',\n };\n\n // `missed` is over too, and is the ordinary way a live call ends when the\n // team is busy. Left out of this list it would leave the dialog showing\n // \"Nobody was free\" above a Cancel button for a call that has already\n // stopped.\n const OVER = ['ended', 'failed', 'missed'];\n\n const offState = client.on('call', ({ state }) => {\n stateEl.textContent = WORDING[state] || state;\n dot.classList.toggle('off', OVER.includes(state));\n if (OVER.includes(state)) {\n panel.querySelector('[data-act=\"hangup\"]').textContent = 'Close';\n if (state === 'missed') {\n // Said plainly, and said as the non-event it is. The report is filed\n // and the thread already says the call went unanswered, so the one\n // thing left to tell them is that they have not lost anything.\n const note = panel.querySelector('.call-note');\n if (note) {\n note.textContent = 'Nobody was free to pick up just now. Your report is filed '\n + 'and the team will reply in the chat.';\n }\n }\n }\n });\n\n panel.querySelector('[data-act=\"hangup\"]').addEventListener('click', () => {\n offState();\n client.endCall();\n close();\n });\n }\n\n /**\n * The confirmation, and the one answer somebody actually wants from it.\n *\n * \"How long will this take\" is the first thing anybody thinks after reporting\n * a bug, and until now the answer was nothing at all — so the honest reading\n * of a confirmation screen was \"into the void\". The API computes the figure\n * on the same request that files the report, precisely so it can be said\n * here: an estimate that needed a second call would arrive after this panel\n * had closed itself.\n *\n * Absent whenever the project has decided not to tell reporters, or has\n * nothing to base a figure on. Nothing is drawn in that case — no \"unknown\",\n * no placeholder — because a confirmation that admits it cannot answer is\n * worse than one that does not raise the question.\n *\n * The dwell is longer when there is something to read. Two seconds is right\n * for one line and is not enough for two, and a panel that closes over the\n * sentence somebody started reading is the reason they open it again.\n */\n function succeed(res, { callFailed = null } = {}) {\n // The assistant: straight into the report's conversation, where the\n // investigation shows itself working (\"Analyzing the issue…\") and asks\n // what it needs. The plain \"Thanks\" below stays for when that view cannot\n // load — a report filed without a way to follow it.\n if (res?.id && !callFailed) {\n showReport(res.id, { fresh: true }).then((ok) => { if (!ok) doneScreen(res, { callFailed }); });\n return;\n }\n doneScreen(res, { callFailed });\n }\n\n function doneScreen(res, { callFailed = null } = {}) {\n const ref = res?.id ? String(res.id).slice(0, 8) : null;\n const eta = res?.estimate?.label ? String(res.estimate.label) : null;\n // A press of Call that filed the report and then could not ring anybody.\n // Shown as a plain \"Thanks\" it read as a call that had quietly worked, and\n // the reporter sat waiting for a phone that was never going to ring.\n const failed = callFailed\n ? `<p class=\"eta\">${escapeHtml(callFailed)} Your report is filed, and the team will reply in the chat.</p>`\n : '';\n scrim.querySelector('.panel').innerHTML = `\n <div class=\"done\">\n <div class=\"tick\">${ICONS.check}</div>\n <h3>Thanks — that's on its way.</h3>\n <p>${ref ? `Filed as <code>${escapeHtml(ref)}</code>.` : 'The team has what they need to reproduce it.'}</p>\n ${failed}\n ${eta ? `<p class=\"eta\">Reports like this usually take <strong>${escapeHtml(eta)}</strong>. We'll let you know when it moves.</p>` : ''}\n </div>`;\n setTimeout(close, failed ? 6000 : eta ? 4000 : 2000);\n }\n\n /**\n * The reporter's own history: what they have filed and where each one got to.\n *\n * Replaces the panel rather than opening a second surface — it is the same\n * dialog answering a different question, and the reporter came here from one\n * launcher.\n */\n async function showHistory() {\n const panel = scrim.querySelector('.panel');\n clearTimeout(assistantTimer);\n openReportId = null;\n panel.classList.remove('is-chat');\n panel.innerHTML = `\n ${headHtml({ title: 'Your reports', sub: \"Everything you've reported, and where each one got to.\" })}\n <div class=\"history\" aria-busy=\"true\">${skeletonRows(3)}</div>`;\n\n panel.querySelector('[data-act=\"close\"]').addEventListener('click', close);\n\n let reports = [];\n try {\n ({ reports } = await client.myReports());\n } catch {\n const failed = panel.querySelector('.history');\n if (failed) failed.innerHTML = emptyHtml('Couldn\\'t load your reports just now.', ICONS.alert);\n return;\n }\n\n // The panel can be closed while the request is in flight.\n const list = panel.querySelector('.history');\n if (!list) return;\n\n list.removeAttribute('aria-busy');\n list.innerHTML = reports.length\n ? reports.map((r, i) => `\n <button class=\"row\" type=\"button\" data-report=\"${escapeHtml(r.id)}\" style=\"animation-delay:${Math.min(i, 8) * 30}ms\">\n <span class=\"row-ico\" aria-hidden=\"true\">${ICONS.chat}</span>\n <span class=\"who\">\n <span class=\"ttl\">${escapeHtml(r.title)}</span>\n <span class=\"sub\">${escapeHtml(r.reference)} · ${escapeHtml(relativeDay(r.createdAt))}</span>\n </span>\n <span class=\"pill ${escapeHtml(String(r.status).toLowerCase())}\">${escapeHtml(statusWord(r.status))}</span>\n <span class=\"chev\" aria-hidden=\"true\">${ICONS.chevronRight}</span>\n </button>`).join('')\n : emptyHtml('You haven\\'t reported anything yet.', ICONS.inbox);\n\n for (const row of list.querySelectorAll('[data-report]')) {\n row.addEventListener('click', () => showReport(row.dataset.report));\n }\n }\n\n /**\n * One report as a conversation.\n *\n * This screen used to be a timeline: everything that had happened, read-only,\n * newest first. It answered \"what happened to my report\" and nothing else —\n * so when the team's reply was a question (\"which browser?\"), the person best\n * placed to answer it was looking straight at it with no way to.\n *\n * Now it is a thread. Status changes still appear, as the quiet centred lines\n * that mark events in any chat; the messages between the two sides are\n * bubbles either side of them; and there is a box at the bottom. Oldest at\n * the top, because that is the direction a conversation is read.\n */\n async function showReport(bugId, { fresh = false } = {}) {\n const panel = scrim.querySelector('.panel');\n clearTimeout(assistantTimer);\n panel.classList.add('is-chat');\n panel.innerHTML = `\n ${headHtml({\n back: true,\n title: fresh ? 'Looking into it' : 'Your report',\n sub: fresh ? 'Our assistant is on it — it may ask you a question here.' : 'Everything that has happened, and a way to answer.',\n })}\n <div class=\"history chatting\" aria-busy=\"true\"><div class=\"thread\">${skeletonBubbles()}</div></div>`;\n\n panel.querySelector('[data-act=\"close\"]').addEventListener('click', close);\n panel.querySelector('[data-act=\"back\"]').addEventListener('click', showHistory);\n\n let report = null;\n try {\n report = await client.myReport(bugId);\n } catch {\n report = null;\n }\n\n const body = panel.querySelector('.history');\n // The panel can be closed, or navigated away from, while this is in flight.\n if (!body || !panel.querySelector('[data-act=\"back\"]')) return false;\n\n if (!report) {\n if (fresh) { panel.classList.remove('is-chat'); return false; }\n body.removeAttribute('aria-busy');\n body.innerHTML = emptyHtml('Couldn\\'t load that report just now.', ICONS.alert);\n return false;\n }\n\n openReportId = bugId;\n // Reading the report is reading its updates. Leaving a \"your report was\n // fixed\" card up over the screen where they are reading that it was fixed\n // would be the notification arguing with itself.\n client.markReportRead(bugId);\n const drawn = threadHtml(report.timeline);\n body.classList.add('chatting');\n body.removeAttribute('aria-busy');\n body.innerHTML = `\n <div class=\"report-head\">\n <span class=\"ttl\">${escapeHtml(report.title)}</span>\n <span class=\"sub\">${escapeHtml(report.reference)} · ${escapeHtml(report.project || '')}</span>\n <span class=\"pill ${escapeHtml(String(report.status).toLowerCase())}\">${escapeHtml(statusWord(report.status))}</span>\n ${report.estimate?.label\n // The same figure they were given when they filed it, so coming back\n // to check is not a worse experience than not checking. Absent on a\n // report that has already been settled — the API stops sending it —\n // because a prediction beside a finished report is either redundant\n // or an argument.\n ? `<span class=\"eta-line\">Usually takes ${escapeHtml(String(report.estimate.label))}</span>`\n : ''}\n </div>\n <div class=\"thread\" role=\"log\" aria-live=\"polite\" aria-label=\"Conversation\">${drawn}</div>`;\n\n const thread = body.querySelector('.thread');\n threadDrawn.set(thread, drawn);\n scrollToLatest(thread);\n mountVerification(body, report);\n if (report.canReply) mountComposer(body, thread, bugId);\n mountAssistant(body, report, bugId);\n // Again, now the cards above it have taken their share of the height: the\n // latest message — and any quick answers under it — is what they came for.\n scrollToLatest(thread);\n return true;\n }\n\n /**\n * The assistant's live card: where the investigation is, and a gentle\n * refresh while it works. Analyzing polls every few seconds until the\n * outcome lands — a question, a found cause, or a hand-off — so the\n * reporter watches it happen rather than wondering whether anything is.\n */\n let assistantTimer = null;\n function mountAssistant(body, report, bugId) {\n clearTimeout(assistantTimer);\n const a = report.assistant;\n const thread = body.querySelector('.thread');\n let card = body.querySelector('.assistant');\n if (!a) {\n card?.remove();\n thread?.querySelector('.typing-row')?.remove();\n return;\n }\n\n // Updated in place rather than rebuilt: the card is polled every few\n // seconds while the assistant works, and a card that re-entered on every\n // poll would flicker — and re-announce itself to a screen reader — for\n // nothing. It only animates when the phase actually moves.\n const previous = card?.dataset.phase || null;\n if (!card) {\n card = document.createElement('div');\n card.setAttribute('role', 'status');\n card.setAttribute('aria-live', 'polite');\n body.querySelector('.report-head')?.after(card);\n }\n card.className = `assistant ${escapeHtml(a.phase)}`;\n card.dataset.phase = a.phase;\n\n const steps = a.steps || [];\n const done = steps.filter((st) => st.state === 'done').length;\n const active = steps.some((st) => st.state === 'active') ? 0.5 : 0;\n const pct = steps.length ? Math.round(((done + active) / steps.length) * 100) : 0;\n const content = `\n <div class=\"as-body${previous && previous !== a.phase ? ' entering' : ''}\">\n <div class=\"as-top\">\n <span class=\"as-orb\" aria-hidden=\"true\">${a.phase === 'analyzing' ? '<span class=\"as-spin\"></span>'\n : a.phase === 'resolved' ? ICONS.check : ICONS.sparkle}</span>\n <div class=\"as-text\">\n <div class=\"as-title\">${escapeHtml(a.title)}</div>\n <div class=\"as-detail\">${escapeHtml(a.detail)}</div>\n </div>\n </div>\n </div>\n ${steps.length ? `\n <div class=\"as-progress\" role=\"progressbar\" aria-label=\"Progress\" aria-valuemin=\"0\" aria-valuemax=\"100\" aria-valuenow=\"${pct}\"><i style=\"width:${pct}%\"></i></div>\n <ol class=\"as-steps\">${steps.map((st) => `<li class=\"${escapeHtml(st.state)}\"${st.state === 'active' ? ' aria-current=\"step\"' : ''}><span class=\"dot\"></span><span class=\"lbl\">${escapeHtml(st.label)}</span></li>`).join('')}</ol>` : ''}`;\n const key = JSON.stringify([a.phase, a.title, a.detail, steps]);\n if (card.dataset.key !== key) {\n const bar = card.querySelector('.as-progress i');\n const from = bar?.style.width;\n card.innerHTML = content;\n card.dataset.key = key;\n // Let the bar travel from where it was to where it is now.\n const next = card.querySelector('.as-progress i');\n if (next && from) {\n next.style.width = from;\n requestAnimationFrame(() => { next.style.width = `${pct}%`; });\n }\n }\n\n // While it works, a \"thinking\" bubble sits where its next message will land.\n if (thread) {\n const pinned = atLatest(thread);\n const typing = thread.querySelector('.typing-row');\n if (a.phase !== 'analyzing') typing?.remove();\n else if (!typing || typing !== thread.lastElementChild) {\n typing?.remove();\n thread.insertAdjacentHTML('beforeend', typingHtml());\n if (pinned) scrollToLatest(thread);\n }\n }\n\n if (a.phase === 'analyzing') {\n assistantTimer = setTimeout(() => refreshThread(bugId), 3000);\n } else if (a.phase === 'needs_info' && previous !== 'needs_info') {\n body.querySelector('.composer textarea')?.focus();\n }\n }\n\n /**\n * Redraw the open thread in place, keeping whatever is half-typed.\n *\n * Rebuilding the whole screen on every incoming message would be simpler and\n * would also throw away a draft, a staged file and the scroll position — for\n * someone mid-sentence, a reply arriving would look like the widget crashed.\n */\n function mountVerification(body, report) {\n body.querySelector('.fix-verification')?.remove();\n if (!report.resolvedAt || !['RESOLVED', 'CLOSED'].includes(report.status)) return;\n const box = document.createElement('div'); box.className = 'fix-verification';\n if (report.verification) { box.textContent = report.verification === 'CONFIRMED' ? 'You confirmed that this fix works.' : 'Your feedback was recorded.'; }\n else {\n box.innerHTML = '<p>Does the fix work for you?</p><textarea maxlength=\"4000\" aria-label=\"Fix feedback\" placeholder=\"What happens when you try again? (optional)\"></textarea><button type=\"button\" data-result=\"CONFIRMED\">It works now</button> <button type=\"button\" data-result=\"STILL_HAPPENING\">Still happening</button><p class=\"feedback-status\" role=\"status\"></p>';\n box.querySelectorAll('[data-result]').forEach((btn) => { btn.onclick = async () => {\n box.querySelectorAll('button').forEach((b) => { b.disabled = true; });\n try { await client.verifyFix(report.id, { result: btn.dataset.result, resolvedAt: report.resolvedAt, note: box.querySelector('textarea').value }); await showReport(report.id); }\n catch (err) { box.querySelector('.feedback-status').textContent = err.message; box.querySelectorAll('button').forEach((b) => { b.disabled = false; }); }\n }; });\n }\n body.querySelector('.report-head')?.after(box);\n }\n\n async function refreshThread(bugId) {\n const body = scrim.querySelector('.history.chatting');\n const thread = body?.querySelector('.thread');\n if (!thread || openReportId !== bugId) return;\n\n let report = null;\n try {\n report = await client.myReport(bugId);\n } catch { /* a failed refresh leaves what is on screen, which is not wrong */ }\n if (!report || openReportId !== bugId) return;\n\n mountVerification(body, report);\n const pinned = atLatest(thread);\n const html = threadHtml(report.timeline);\n // Only rewritten when something changed, so a poll that brings nothing new\n // does not replay every bubble's entrance or re-announce the log.\n if (threadDrawn.get(thread) !== html) {\n thread.innerHTML = html;\n threadDrawn.set(thread, html);\n }\n mountAssistant(body, report, bugId);\n // Only follow the conversation down if they were already at the bottom.\n // Yanking someone back from something they scrolled up to read is worse\n // than making them scroll.\n if (pinned) scrollToLatest(thread);\n\n const pill = body.querySelector('.report-head .pill');\n if (pill) {\n pill.className = `pill ${String(report.status).toLowerCase()}`;\n pill.textContent = statusWord(report.status);\n }\n }\n\n /**\n * The box at the bottom: text, files, and a voice note.\n *\n * Files are uploaded as they are picked, not on send, so the wait happens\n * while the person is still writing rather than after they have pressed the\n * button — and so a failed upload is visible as a failed chip instead of a\n * message that never went.\n */\n function mountComposer(body, thread, bugId) {\n const composer = document.createElement('div');\n composer.className = 'composer';\n composer.innerHTML = `\n <div class=\"staged\" hidden></div>\n <div class=\"row\">\n <div class=\"tools\">\n <button class=\"tool\" type=\"button\" data-act=\"attach\" title=\"Attach a file\" aria-label=\"Attach a file\">${ICONS.paperclip}</button>\n <button class=\"tool\" type=\"button\" data-act=\"voice\" title=\"Record a voice note\" aria-label=\"Record a voice note\">${ICONS.mic}</button>\n <button class=\"tool\" type=\"button\" data-act=\"livecall\" title=\"Call the team\" aria-label=\"Call the team\" hidden>${ICONS.phone}</button>\n </div>\n <textarea rows=\"1\" placeholder=\"Write a reply…\" aria-label=\"Write a reply\"></textarea>\n <button class=\"send\" type=\"button\" data-act=\"send\" title=\"Send\" aria-label=\"Send\" disabled>${ICONS.send}</button>\n </div>\n <div class=\"note\" hidden></div>\n <input type=\"file\" hidden multiple accept=\"${UPLOAD_MIMES.join(',')}\">`;\n body.appendChild(composer);\n\n const staged = composer.querySelector('.staged');\n const input = composer.querySelector('textarea');\n const sendBtn = composer.querySelector('[data-act=\"send\"]');\n const voiceBtn = composer.querySelector('[data-act=\"voice\"]');\n const liveBtn = composer.querySelector('[data-act=\"livecall\"]');\n const picker = composer.querySelector('input[type=file]');\n const note = composer.querySelector('.note');\n\n // Uploaded files waiting to ride on the next message.\n const files = [];\n let recorder = null;\n let sending = false;\n\n function say(text, kind = '') {\n note.hidden = !text;\n note.className = `note ${kind}`;\n note.textContent = text || '';\n }\n\n function refresh() {\n const ready = files.filter((f) => f.storageKey);\n sendBtn.disabled = sending || (!input.value.trim() && !ready.length);\n staged.hidden = !files.length;\n staged.innerHTML = files.map((f, i) => `\n <span class=\"chip ${f.storageKey ? '' : 'uploading'}\">\n <span class=\"nm\">${escapeHtml(f.name)}</span>\n <span class=\"sz\">${f.storageKey ? humanSize(f.size) : 'sending…'}</span>\n <button type=\"button\" data-drop=\"${i}\" aria-label=\"Remove ${escapeHtml(f.name)}\">${ICONS.close}</button>\n </span>`).join('');\n for (const btn of staged.querySelectorAll('[data-drop]')) {\n btn.addEventListener('click', () => {\n files.splice(Number(btn.dataset.drop), 1);\n refresh();\n });\n }\n }\n\n /** Upload one blob and stage it. Its chip is on screen for the whole trip. */\n async function stage(blob, mime, name) {\n const entry = { name, size: blob.size, storageKey: null };\n files.push(entry);\n refresh();\n try {\n const { storageKey, mime: stored } = await client.uploadAttachment(blob, mime);\n entry.storageKey = storageKey;\n entry.mime = stored;\n } catch (err) {\n files.splice(files.indexOf(entry), 1);\n say(err.message || 'That file could not be sent.', 'err');\n }\n refresh();\n }\n\n input.addEventListener('input', () => {\n // Grow with the text, up to the CSS cap. A reply about a bug is often\n // three lines, and a one-line box makes people write one line.\n input.style.height = 'auto';\n input.style.height = `${Math.min(input.scrollHeight, 120)}px`;\n refresh();\n });\n\n // Enter sends, Shift+Enter breaks the line — the chat convention. Anyone\n // who wants a paragraph gets it; nobody has to hunt for the button.\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter' && !e.shiftKey && !e.isComposing) {\n e.preventDefault();\n send();\n }\n });\n\n composer.querySelector('[data-act=\"attach\"]').addEventListener('click', () => picker.click());\n picker.addEventListener('change', () => {\n say('');\n for (const file of picker.files) {\n if (file.size > client.config.maxAttachmentBytes) {\n say(`${file.name} is larger than ${humanSize(client.config.maxAttachmentBytes)}.`, 'err');\n continue;\n }\n stage(file, file.type, file.name);\n }\n picker.value = '';\n });\n\n if (!voiceSupported()) {\n voiceBtn.disabled = true;\n voiceBtn.title = 'This browser cannot record audio';\n }\n\n voiceBtn.addEventListener('click', async () => {\n if (recorder) {\n const handle = recorder;\n recorder = null;\n voiceBtn.classList.remove('recording');\n voiceBtn.innerHTML = ICONS.mic;\n say('');\n const { blob, mime } = await handle.stop();\n if (blob.size) stage(blob, mime, 'Voice note');\n return;\n }\n try {\n say('');\n recorder = await startVoiceNote({\n maxMs: client.config.maxRecordingMs,\n onTick: (s) => say(`Recording — ${humanDuration(s)}. Tap the mic to stop.`),\n onAutoStop: () => voiceBtn.click(),\n });\n voiceBtn.classList.add('recording');\n voiceBtn.innerHTML = ICONS.stopSquare;\n } catch {\n recorder = null;\n say('The microphone is not available — check the browser permission.', 'err');\n }\n });\n\n /* ---------- calling the team ---------- */\n\n // Asked once, when a report is opened, and the button stays hidden unless\n // the answer is yes. Most workspaces have live calling off, and a phone\n // icon that apologises on press is worse than no phone icon.\n client.canCallTeam(bugId)\n .then((available) => { if (available) liveBtn.hidden = false; })\n .catch(() => { /* not available is the safe answer to every failure */ });\n\n liveBtn.addEventListener('click', async () => {\n liveBtn.disabled = true;\n say('');\n try {\n const handle = await client.startLiveCall(bugId);\n showLiveCall(body, thread, handle);\n } catch (err) {\n say(err.message.replace('[BugCatch] ', ''), 'err');\n } finally {\n liveBtn.disabled = false;\n }\n });\n\n sendBtn.addEventListener('click', send);\n\n // The confirm question's quick answers: the same as typing and sending.\n thread.addEventListener('click', (e) => {\n const quick = e.target.closest?.('[data-quick]');\n if (!quick || sending) return;\n input.value = quick.dataset.quick;\n send();\n });\n\n async function send() {\n const text = input.value.trim();\n const ready = files.filter((f) => f.storageKey);\n if (sending || (!text && !ready.length)) return;\n\n sending = true;\n refresh();\n say('');\n\n // Shown immediately, greyed, and replaced by the refresh below. Waiting\n // for the round trip before anything appears makes a slow network look\n // like a dropped message, which is when people send it twice.\n thread.insertAdjacentHTML('beforeend', messageHtml({\n from: 'reporter',\n body: text,\n at: new Date().toISOString(),\n attachments: ready.map((f) => ({ type: 'FILE', name: f.name, size: f.size })),\n }, 'pending'));\n scrollToLatest(thread, { smooth: true });\n\n try {\n await sendMessage({\n apiBase: client.config.apiBase,\n bugId,\n body: text,\n attachments: ready.map((f) => ({\n storageKey: f.storageKey, mime: f.mime, name: f.name, size: f.size,\n })),\n });\n input.value = '';\n input.style.height = 'auto';\n files.length = 0;\n await refreshThread(bugId);\n } catch (err) {\n thread.querySelector('.msg-row.pending')?.classList.replace('pending', 'failed');\n say(err.message || 'That could not be sent — try again.', 'err');\n } finally {\n sending = false;\n refresh();\n }\n }\n\n refresh();\n }\n\n /**\n * The call, on top of the report it is about.\n *\n * A bar rather than a screen of its own, unlike the one the composer's button\n * opens — and for a reason that only applies here. A call has nothing to show\n * while it runs, because nobody is listening to it but the person on the\n * other end, so what is worth looking at is the report both people are\n * talking about.\n */\n function showLiveCall(body, thread, handle) {\n const bar = document.createElement('div');\n bar.className = 'livecall';\n bar.innerHTML = `\n <span class=\"call-dot\" aria-hidden=\"true\"></span>\n <span class=\"who\" role=\"status\" aria-live=\"polite\">Calling the team…</span>\n <span class=\"secs\" hidden></span>\n <button class=\"act\" type=\"button\" data-act=\"mute\" hidden>Mute</button>\n <button class=\"act danger\" type=\"button\" data-act=\"hangup\">Cancel</button>`;\n body.insertBefore(bar, thread);\n\n const who = bar.querySelector('.who');\n const secs = bar.querySelector('.secs');\n const dot = bar.querySelector('.call-dot');\n const muteBtn = bar.querySelector('[data-act=\"mute\"]');\n const hangBtn = bar.querySelector('[data-act=\"hangup\"]');\n\n let ticker = null;\n const stopTicking = () => { if (ticker) { clearInterval(ticker); ticker = null; } };\n\n /** Left up rather than removed, so somebody who looked away can see how it\n * ended — and dismissed by hand, or by the next call replacing it. */\n const settle = (text) => {\n stopTicking();\n dot.classList.add('off');\n bar.classList.add('over');\n who.textContent = text;\n secs.hidden = true;\n muteBtn.hidden = true;\n hangBtn.textContent = 'Dismiss';\n hangBtn.classList.remove('danger');\n };\n\n const off = client.on('call', ({ state }) => {\n if (state === 'ringing') who.textContent = 'Calling the team…';\n\n if (state === 'connecting') {\n who.textContent = 'Someone answered — connecting…';\n }\n\n if (state === 'connected') {\n who.textContent = \"You're on a call with the team\";\n muteBtn.hidden = false;\n secs.hidden = false;\n const from = Date.now();\n stopTicking();\n // Started on connect rather than on answer: the seconds a person is\n // charged for looking at are the ones they could hear.\n ticker = setInterval(() => {\n secs.textContent = humanDuration(Math.floor((Date.now() - from) / 1000));\n }, 1000);\n secs.textContent = '0s';\n }\n\n if (state === 'missed') {\n settle('Nobody was free. Send a message instead and the team will pick it up.');\n }\n if (state === 'ended') settle('Call ended.');\n if (state === 'failed') settle('The call dropped.');\n });\n\n muteBtn.addEventListener('click', () => {\n const muted = handle.setMuted(!handle.isMuted());\n muteBtn.textContent = muted ? 'Unmute' : 'Mute';\n // Announced rather than only shown: somebody who muted by accident finds\n // out when the other person stops replying, which is a long way in.\n who.textContent = muted ? \"You're muted\" : \"You're on a call with the team\";\n });\n\n hangBtn.addEventListener('click', () => {\n off();\n stopTicking();\n client.endCall();\n bar.remove();\n // The note about the call lands in the thread a moment after it ends, and\n // the reporter is looking straight at where it goes.\n refreshThread(openReportId);\n });\n }\n\n // The report currently open, if any. A live update for a different report\n // must not redraw the one being read.\n let openReportId = null;\n\n // Re-read the open thread when something happens to it. Without this the\n // reader is looking at a conversation that stopped being true while they read\n // it — the very screen where a stale view is most obvious, and now the screen\n // where the other side may be typing.\n stopListening = client.on('update', (event) => {\n if (!openReportId || event?.bugId !== openReportId) return;\n // Already on screen, so already read — and marking it here, before the\n // client draws its notifications, is what keeps a card from appearing over\n // the thread that is about to show the same thing.\n client.markReportRead(openReportId);\n refreshThread(openReportId);\n });\n\n root.appendChild(scrim);\n refreshSendState();\n titleInput.focus();\n\n return { destroy, close };\n}\n\n/**\n * The whole thread, oldest first.\n *\n * The API hands back one ordered list holding both kinds of entry, which is\n * what keeps a reply sitting under the status change that prompted it. Entries\n * carrying `from` are messages; the rest are things that happened.\n */\nfunction threadHtml(timeline) {\n const entries = timeline || [];\n if (!entries.length) return '';\n let day = null;\n let prev = null;\n return entries.map((e) => {\n // A rule with the date on it whenever the calendar day turns, the way a\n // chat marks that the next message came later.\n const d = dayKey(e.at);\n const sep = d && d !== day ? `<div class=\"day-sep\" role=\"separator\">${escapeHtml(dayLabel(e.at))}</div>` : '';\n if (sep) { day = d; prev = null; }\n if (!e.from) { prev = null; return sep + systemHtml(e); }\n const message = {\n from: e.from,\n author: e.reply?.author,\n automated: e.reply?.automated,\n body: e.reply?.body,\n at: e.at,\n attachments: e.reply?.attachments,\n };\n // Consecutive messages from the same voice within a few minutes are one\n // turn: one avatar, one name, the bubbles stacked under it.\n const who = speakerOf(message);\n const cont = prev && prev.who === who\n && Math.abs(new Date(e.at).getTime() - new Date(prev.at).getTime()) < 5 * 60_000;\n prev = { who, at: e.at };\n return sep + messageHtml(message, cont ? 'cont' : '');\n }).join('');\n}\n\n/** Which voice a message is in, for grouping: the reporter, the assistant, or a named person. */\nfunction speakerOf(message) {\n if (message.from === 'reporter') return 'me';\n if (message.automated) return 'assistant';\n return `team:${message.author || ''}`;\n}\n\n/**\n * Something that happened, rather than something someone said.\n *\n * `summary` is written by the API, which knows what changed and phrases it for\n * the person who reported it — so the wording here matches the toast and the\n * portal exactly rather than being a third attempt at the same sentence.\n */\nfunction systemHtml(entry) {\n const when = clockTime(entry.at);\n return `\n <div class=\"sys k-${escapeHtml(entry.kind)}\">\n <span class=\"dot\" aria-hidden=\"true\"></span>${escapeHtml(entry.summary || '')}${when ? `\n <span class=\"t\">· ${escapeHtml(when)}</span>` : ''}\n </div>`;\n}\n\n/**\n * One bubble. `state` marks a message that is still in flight, or failed, or\n * one that continues the turn above it.\n *\n * A reply that was sent automatically says so, above the words rather than\n * after them. The person reading it is deciding whether their problem has been\n * understood, and that decision is different when nobody has read the report —\n * so it is not a footnote, and it is not left to be inferred from the tone.\n */\nfunction messageHtml(message, state = '') {\n const mine = message.from === 'reporter';\n const body = message.body || '';\n const attachments = message.attachments || [];\n const automated = !mine && Boolean(message.automated);\n\n // The text goes in its own element rather than as a bare child of .bubble:\n // it is the only part that wants `white-space: pre-wrap`, and with that rule\n // on the bubble the template's own newlines and indentation render as a\n // blank line above and below every message.\n const text = body ? `<span class=\"msg-text\">${escapeHtml(body)}</span>` : '';\n const files = attachments.length\n ? `<div class=\"att\">${attachments.map(attachmentHtml).join('')}</div>`\n : '';\n\n // Named, or labelled, never both and never neither: an automated message has\n // no author to name, and naming one would be the lie the label exists to\n // prevent. The avatar says the same thing at a glance.\n let who = '';\n let avatar = '';\n if (automated) {\n who = '<span class=\"msg-who auto\">Assistant <span class=\"tag\">AI</span></span>';\n avatar = `<span class=\"av bot\" aria-hidden=\"true\">${ICONS.sparkle}</span>`;\n } else if (!mine) {\n const name = message.author || 'Support team';\n who = `<span class=\"msg-who\">${escapeHtml(name)}</span>`;\n avatar = `<span class=\"av\" aria-hidden=\"true\">${escapeHtml(initials(name))}</span>`;\n }\n if (state.includes('cont')) who = '';\n\n // \"Is this the problem you are facing?\" — the confirm question the\n // investigation asks (bugcatch-workers check-loop). One tap answers it; the\n // buttons only show on the latest message, so an answered question has none.\n const confirm = automated && /reply \"yes\" or \"no\"/i.test(body)\n ? `<div class=\"quick\" role=\"group\" aria-label=\"Answer\">\n <button type=\"button\" data-quick=\"Yes, this is the problem\">${ICONS.check}Yes, that's it</button>\n <button type=\"button\" data-quick=\"No, that is not the problem\">No, it's something else</button>\n </div>`\n : '';\n\n return `\n <div class=\"msg-row ${mine ? 'mine' : 'theirs'} ${automated ? 'auto' : ''} ${state}\">\n ${avatar}\n ${who}\n <div class=\"bubble ${body ? '' : 'silent'}\">${text}${files}</div>\n ${confirm}\n <span class=\"msg-when\">${escapeHtml(clockTime(message.at) || relativeDay(message.at))}</span>\n </div>`;\n}\n\n/** The assistant, thinking: where its next message will appear. */\nfunction typingHtml() {\n return `\n <div class=\"typing-row\">\n <span class=\"av bot\" aria-hidden=\"true\">${ICONS.sparkle}</span>\n <span class=\"msg-who auto\">Assistant <span class=\"tag\">AI</span></span>\n <div class=\"bubble typing\" role=\"img\" aria-label=\"The assistant is working on it\"><i></i><i></i><i></i></div>\n </div>`;\n}\n\n/**\n * The panel's header, on every screen. The assistant's avatar carries the\n * identity; `back` adds the way out of a report to the list of them.\n */\nfunction headHtml({ title, sub, id = '', back = false }) {\n return `\n <div class=\"head\">\n ${back ? `<button class=\"icon-btn back\" type=\"button\" data-act=\"back\" aria-label=\"Back to your reports\">${ICONS.back}</button>` : ''}\n <div class=\"mark\" aria-hidden=\"true\">${ICONS.sparkle}</div>\n <div class=\"titles\">\n <h2${id ? ` id=\"${id}\"` : ''}>${escapeHtml(title)}</h2>\n <p>${escapeHtml(sub)}</p>\n </div>\n <button class=\"icon-btn\" type=\"button\" data-act=\"close\" aria-label=\"Close\">${ICONS.close}</button>\n </div>`;\n}\n\nfunction emptyHtml(text, icon) {\n return `<div class=\"empty\"><span class=\"empty-ico\" aria-hidden=\"true\">${icon}</span>${escapeHtml(text)}</div>`;\n}\n\n/** Placeholder rows while the list loads — the shape of what is coming. */\nfunction skeletonRows(n) {\n return Array.from({ length: n }, () => `\n <div class=\"row\" aria-hidden=\"true\">\n <span class=\"row-ico\"></span>\n <span class=\"who\"><span class=\"skel\" style=\"width:70%\"></span><span class=\"skel\" style=\"width:40%;height:9px\"></span></span>\n </div>`).join('') + '<span class=\"sr-only\">Loading…</span>';\n}\n\nfunction skeletonBubbles() {\n return `\n <div aria-hidden=\"true\" style=\"display:flex;flex-direction:column;gap:10px;padding-top:6px\">\n <span class=\"skel\" style=\"width:42%;height:34px;border-radius:16px;align-self:center\"></span>\n <span class=\"skel\" style=\"width:66%;height:52px;border-radius:18px\"></span>\n <span class=\"skel\" style=\"width:48%;height:38px;border-radius:18px;align-self:flex-end\"></span>\n </div>\n <span class=\"sr-only\">Loading…</span>`;\n}\n\n// What each open thread was last drawn from, so a poll that brings nothing new\n// leaves the DOM (and the reader's place in it) alone.\nconst threadDrawn = new WeakMap();\n\nfunction initials(name) {\n const parts = String(name).trim().split(/\\s+/).filter(Boolean);\n if (!parts.length) return '?';\n return (parts[0][0] + (parts.length > 1 ? parts[parts.length - 1][0] : '')).toUpperCase();\n}\n\nfunction dayKey(iso) {\n const d = new Date(iso);\n return Number.isFinite(d.getTime()) ? `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}` : null;\n}\n\n/** \"Today\" / \"Yesterday\" / \"Mon, 14 Sep\" — the label on a day separator. */\nfunction dayLabel(iso) {\n const d = new Date(iso);\n const today = new Date();\n const yesterday = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 1);\n if (dayKey(d) === dayKey(today)) return 'Today';\n if (dayKey(d) === dayKey(yesterday)) return 'Yesterday';\n try {\n return d.toLocaleDateString(undefined, {\n weekday: 'short', day: 'numeric', month: 'short',\n ...(d.getFullYear() !== today.getFullYear() ? { year: 'numeric' } : {}),\n });\n } catch {\n return relativeDay(iso);\n }\n}\n\n/** \"14:05\" or \"2:05 PM\", in the reader's own convention. */\nfunction clockTime(iso) {\n const d = new Date(iso);\n if (!Number.isFinite(d.getTime())) return '';\n try {\n return d.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' });\n } catch {\n return '';\n }\n}\n\n/**\n * One attachment, rendered as what it is.\n *\n * An image people can see, a voice note they can play and a document they can\n * download are three different things, and collapsing them all to a link is\n * how a chat stops feeling like one. A pending upload has no URL yet, so it\n * falls through to the file row with nothing to click.\n */\nfunction attachmentHtml(a) {\n const name = escapeHtml(a.name || 'attachment');\n if (!a.url) return `<span class=\"file\"><span class=\"nm\">${name}</span></span>`;\n const url = escapeHtml(a.url);\n\n if (a.type === 'SCREENSHOT') {\n return `<a href=\"${url}\" target=\"_blank\" rel=\"noopener noreferrer\"><img src=\"${url}\" alt=\"${name}\" loading=\"lazy\"></a>`;\n }\n if (a.type === 'AUDIO') {\n return `<audio controls preload=\"metadata\" src=\"${url}\"></audio>`;\n }\n if (a.type === 'VIDEO') {\n return `<video controls preload=\"metadata\" src=\"${url}\"></video>`;\n }\n return `\n <a class=\"file\" href=\"${url}\" target=\"_blank\" rel=\"noopener noreferrer\" download>\n <span class=\"nm\">${name}</span>\n ${a.size ? `<span class=\"sz\">${escapeHtml(humanSize(a.size))}</span>` : ''}\n </a>`;\n}\n\n/** Whether the reader is at the foot of the thread, within a line or two. */\nfunction atLatest(thread) {\n return thread.scrollHeight - thread.scrollTop - thread.clientHeight < 40;\n}\n\nfunction scrollToLatest(thread, { smooth = false } = {}) {\n const calm = typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;\n if (smooth && !calm && thread.scrollTo) thread.scrollTo({ top: thread.scrollHeight, behavior: 'smooth' });\n else thread.scrollTop = thread.scrollHeight;\n}\n\n/** Status as a word a reporter would use, not the enum the database stores. */\nfunction statusWord(status) {\n return {\n OPEN: 'Open',\n IN_PROGRESS: 'In progress',\n RESOLVED: 'Fixed',\n CLOSED: 'Closed',\n }[status] || String(status || '').toLowerCase();\n}\n\n/** \"today\" / \"3 days ago\" — enough for a history list, no library. */\nfunction relativeDay(iso) {\n const then = new Date(iso).getTime();\n if (!Number.isFinite(then)) return '';\n const days = Math.floor((Date.now() - then) / 86400000);\n if (days <= 0) return 'today';\n if (days === 1) return 'yesterday';\n if (days < 30) return `${days} days ago`;\n const months = Math.floor(days / 30);\n return months === 1 ? 'a month ago' : `${months} months ago`;\n}\n\nfunction escapeHtml(s) {\n return String(s).replace(/[&<>\"']/g, (c) => (\n { '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;' }[c]\n ));\n}\n","// Microphone to µ-law, and back.\n//\n// sphoro.voice speaks G.711 µ-law at 8 kHz in both directions — the format a\n// telephone carrier hands it, which is what makes a browser call and a phone\n// call indistinguishable to everything downstream. A browser has neither: it\n// captures 32-bit floats at whatever rate the sound card runs at. The gap\n// between those two is a codec and a resampler, and neither is available from\n// the platform, so both are here.\n//\n// This file exists rather than the two callers each having a copy. The agent\n// call sends µ-law because that is the whole conversation; a live call sends it\n// because sphoro.voice cannot record or transcribe a conversation it is not in\n// the middle of, and asks each browser for a copy of its own microphone. Two\n// implementations of G.711 in one package would be one implementation and one\n// that is subtly wrong on the day somebody fixes only the first.\n//\n// Nothing is imported to do it. The SDK runs on someone else's page and has no\n// dependencies, and that rule does not bend for audio.\n\n/** The wire's sample rate. Not negotiable — see the note above. */\nexport const WIRE_RATE = 8000;\n\n/**\n * An audio context, at the wire's rate where the browser will give us one.\n *\n * Asking for 8 kHz outright makes the resampler below dead code on most\n * machines. Some browsers refuse a rate their hardware does not like, so the\n * fallback is an ordinary context and the manual path.\n */\nexport function createContext(rate = WIRE_RATE) {\n const Ctor = window.AudioContext || window.webkitAudioContext;\n try {\n return new Ctor({ sampleRate: rate });\n } catch {\n return new Ctor();\n }\n}\n\n/**\n * Start a context that the browser has parked, if it will let us.\n *\n * Resolves either way. There is nothing useful to do with the rejection: it\n * means this browser wants a user gesture we are not inside, and the call is\n * still worth attempting — a context that stays suspended is a silent call,\n * which is what the caller finds out about from the states it is already being\n * sent.\n */\nexport async function resumeContext(context) {\n if (context.state !== 'suspended') return;\n try {\n await context.resume();\n } catch { /* no activation to spend */ }\n}\n\n/**\n * Drop a signal to the wire rate by averaging each source window.\n *\n * Exported for its own test: the browsers that refuse an 8 kHz context are the\n * ones that need this, and they are exactly the browsers a developer is least\n * likely to be looking at when they change it.\n *\n * Averaging rather than picking every nth sample: plain decimation aliases\n * everything above 4 kHz down into the speech band, and consonants are exactly\n * what lives up there. This is a crude low-pass, but it is the difference\n * between \"sixty\" and \"sixteen\" surviving the trip.\n */\nexport function downsample(input, fromRate, toRate) {\n const ratio = fromRate / toRate;\n const out = new Float32Array(Math.floor(input.length / ratio));\n for (let i = 0; i < out.length; i++) {\n const start = Math.floor(i * ratio);\n const end = Math.min(Math.floor((i + 1) * ratio), input.length);\n let sum = 0;\n for (let j = start; j < end; j++) sum += input[j];\n out[i] = end > start ? sum / (end - start) : 0;\n }\n return out;\n}\n\n/** G.711 µ-law: the bias and clip the standard specifies. */\nconst MU_BIAS = 0x84;\nconst MU_CLIP = 32635;\n\n/**\n * Float samples to µ-law bytes, ready to put on the wire.\n *\n * Exported for the codec test rather than for callers: this file is not the\n * package's entry point, and a hand-rolled G.711 that nothing checks is a\n * silent call nobody can debug from the outside.\n */\nexport function encodeMuLaw(samples) {\n const out = new Uint8Array(samples.length);\n for (let i = 0; i < samples.length; i++) {\n // Clamped before scaling: a float outside [-1, 1] is legal in Web Audio and\n // would wrap round to the opposite sign as an integer.\n const clamped = Math.max(-1, Math.min(1, samples[i]));\n let sample = Math.round(clamped * 32767);\n\n const sign = sample < 0 ? 0x80 : 0;\n if (sign) sample = -sample;\n if (sample > MU_CLIP) sample = MU_CLIP;\n sample += MU_BIAS;\n\n let exponent = 7;\n for (let mask = 0x4000; (sample & mask) === 0 && exponent > 0; exponent--, mask >>= 1);\n const mantissa = (sample >> (exponent + 3)) & 0x0F;\n out[i] = ~(sign | (exponent << 4) | mantissa) & 0xFF;\n }\n return out;\n}\n\n/** µ-law bytes back to float samples, ready for an AudioBuffer. */\nexport function decodeMuLaw(bytes) {\n const out = new Float32Array(bytes.length);\n for (let i = 0; i < bytes.length; i++) {\n const byte = ~bytes[i] & 0xFF;\n const sign = byte & 0x80;\n const exponent = (byte >> 4) & 0x07;\n const mantissa = byte & 0x0F;\n const magnitude = (((mantissa << 3) + MU_BIAS) << exponent) - MU_BIAS;\n out[i] = (sign ? -magnitude : magnitude) / 32768;\n }\n return out;\n}\n\n/**\n * How much audio rides in one frame.\n *\n * Measured in samples, and the context above runs at 8 kHz wherever the browser\n * allows it — so this is 64 ms of speech, not the 20-odd it would be at\n * hardware rate. That distinction is the whole reason for the number: at 2048\n * the same code sends a quarter of a second per frame, which is a quarter of a\n * second added to the start of every turn and to every barge-in, and it sounds\n * exactly like an agent that is slow to notice you talking.\n *\n * Not smaller than this. ScriptProcessorNode runs on the main thread, and a\n * block it cannot fill in time is a block it drops — which is a gap in the\n * middle of a word rather than a delay before one. Powers of two only.\n */\nexport const CAPTURE_BLOCK = 512;\n\n/**\n * Tap a microphone and hand each block over as µ-law bytes.\n *\n * ScriptProcessorNode is deprecated in favour of AudioWorklet, and is used\n * anyway. A worklet's processor has to be loaded from a URL, which on this SDK\n * means a blob: URL — and a blob: worklet is refused outright by the\n * Content-Security-Policy of a great many of the sites this code runs on. A\n * deprecated node that works on a customer's page beats a modern one that is\n * blocked by their headers.\n *\n * Returns a `stop` that lets go of the whole chain. The microphone track is\n * deliberately *not* stopped by it: on a live call the same track is carrying\n * the conversation, and stopping it to end a recording would end the call.\n *\n * @param {AudioContext} context\n * @param {MediaStream} stream\n * @param {(bytes: Uint8Array) => void} onFrame\n * @param {number} [wireRate]\n * @returns {{stop: () => void}}\n */\nexport function tapMicrophone(context, stream, onFrame, wireRate = WIRE_RATE) {\n const source = context.createMediaStreamSource(stream);\n const capture = context.createScriptProcessor(CAPTURE_BLOCK, 1, 1);\n\n capture.onaudioprocess = (event) => {\n const input = event.inputBuffer.getChannelData(0);\n const frames = context.sampleRate === wireRate\n ? input\n : downsample(input, context.sampleRate, wireRate);\n onFrame(encodeMuLaw(frames));\n };\n\n source.connect(capture);\n // A ScriptProcessorNode does not run unless it is connected to a destination,\n // even when nothing is meant to come out of it. Zero gain is what keeps the\n // speaker from hearing their own voice played back at them.\n const mute = context.createGain();\n mute.gain.value = 0;\n capture.connect(mute);\n mute.connect(context.destination);\n\n return {\n stop() {\n capture.onaudioprocess = null;\n try { source.disconnect(); } catch { /* already gone */ }\n try { capture.disconnect(); } catch { /* already gone */ }\n try { mute.disconnect(); } catch { /* already gone */ }\n },\n };\n}\n","// Two people, talking directly.\n//\n// The agent call next door is a microphone, a socket and a codec: the browser\n// sends µ-law up to sphoro.voice, a model answers, and every sample passes\n// through a server. This is the opposite arrangement. The audio here goes\n// straight from one browser to the other over WebRTC, and sphoro.voice's part is\n// only to introduce them — it passes an offer, an answer and a handful of ICE\n// candidates between the two sockets and then has nothing left to do.\n//\n// Which means there is no codec in this file, no resampler, and no audio path\n// at all. The browser's own WebRTC stack owns those, and it is better at them\n// than anything hand-written: Opus at whatever rate the two ends agree on,\n// echo cancellation, jitter buffering and packet loss concealment, none of\n// which the µ-law path can have because a telephone carrier cannot receive\n// them. A live call sounds better than the agent call, and costs nothing per\n// minute, because nobody is in the middle of it.\n//\n// # Shared with the dashboard\n//\n// Both ends of a live call run this file: the reporter's widget and the member\n// who answers in the portal, which imports it from here rather than keeping a\n// second copy. The two sides differ by one flag — who makes the offer — and it\n// arrives from the server rather than being decided here, so the two ends\n// cannot both sit waiting for the other to go first.\n//\n// # The one exception: the copy that gets kept\n//\n// An agent that is set to record or transcribe cannot do either from here —\n// the audio it would need never reaches it. So sphoro.voice asks instead: it\n// names, in `session.created`, what this call is keeping and in what format,\n// and each browser uplinks a copy of *its own* microphone over the signaling\n// socket it already has open. That copy is the only way a recording or a\n// transcript of a peer-to-peer call can exist at all, and it is why the codec\n// this file said it did not have is imported below.\n//\n// It is a copy and not the call. The conversation still runs browser to\n// browser; an uplink that stalls or is refused costs a recording, never a call.\n//\n// Nothing outside the package is imported to do any of it. The SDK runs on\n// someone else's page and has no dependencies, and that rule does not bend for\n// a peer connection.\n\nimport { createContext, resumeContext, tapMicrophone } from './audio.js';\n\n/**\n * sphoro.voice's realtime protocol, sent as the WebSocket subprotocol. The same\n * one the agent call speaks — a bridged call is a different conversation over\n * the same envelope, not a different protocol.\n */\nconst SUBPROTOCOL = 'voiceai.realtime.v1';\n\n/**\n * Where a browser looks to find its way to the other one, when the server names\n * nowhere.\n *\n * The server almost always does name somewhere — it is configured per\n * deployment, because a network behind symmetric NAT needs a TURN relay and\n * that costs money. This is the floor: a public STUN server, which is enough\n * for most home and office networks and free.\n */\nconst FALLBACK_ICE = [{ urls: 'stun:stun.l.google.com:19302' }];\n\n/**\n * Ask for the microphone.\n *\n * Separate from joining, and deliberately. The permission prompt takes as long\n * as the person takes to read it, and both ends have something they should not\n * do until it is answered: the reporter should not make a room full of\n * dashboards ring before knowing they can speak, and whoever answers should not\n * claim a call they then cannot take. So both ask first and act second.\n *\n * @returns {Promise<MediaStream>}\n */\nexport function openMicrophone() {\n return navigator.mediaDevices.getUserMedia({\n audio: {\n // Both ends are on speakers as often as not. Without cancellation each\n // hears themselves back through the other, half a second late, which is\n // the single most effective way to make a conversation impossible.\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n channelCount: 1,\n },\n });\n}\n\n/** Whether this browser can be on a live call at all. */\nexport function liveCallSupported() {\n return typeof window !== 'undefined'\n && typeof window.RTCPeerConnection === 'function'\n && Boolean(navigator.mediaDevices?.getUserMedia);\n}\n\n/**\n * Join a live call and stay on it until somebody hangs up.\n *\n * @param {object} opts\n * @param {string} opts.realtimeUrl From the API, carrying its own credential\n * and its own seat. Never log it and never build one by hand.\n * @param {MediaStream} [opts.stream] A microphone already opened by\n * `openMicrophone`. One is opened here if not, which is the wrong order for\n * anything that had a decision to make first.\n * @param {string} [opts.ringsUntil] ISO-8601, on the caller's side only: when\n * the ringing stops. The browser is the thing that decides it has, because it\n * is the one client guaranteed to still be listening at that moment.\n * @param {(state: string, detail?: object) => void} [opts.onState]\n * 'ringing' | 'connecting' | 'connected' | 'reconnecting' | 'ended' | 'missed'\n * | 'failed'. 'reconnecting' is not a failure and never ends a call — see\n * `routeChanged`.\n * @param {(blocked: boolean) => void} [opts.onAudioBlocked] The browser refused\n * to play the other person, or has since started. Its own callback rather\n * than a state, because it is orthogonal to every one of them: the call is\n * connected and working, and one end cannot hear it. See `playRemote`.\n * @param {(live: boolean) => void} [opts.onMicrophone] Whether this person's\n * microphone is still reaching the call. Also orthogonal, and for the same\n * reason: the call is up, the other end is audible, and nothing this person\n * says is going anywhere. See `watchMicrophone`.\n * @returns {Promise<{hangUp: () => void, setMuted: (muted: boolean) => boolean,\n * isMuted: () => boolean}>}\n */\nexport async function joinLiveCall({\n realtimeUrl, stream, ringsUntil, onState, onAudioBlocked, onMicrophone,\n}) {\n const emit = (state, detail) => { try { onState?.(state, detail); } catch { /* theirs */ } };\n const sayBlocked = (blocked) => {\n try { onAudioBlocked?.(blocked); } catch { /* theirs */ }\n };\n const sayMicrophone = (live) => {\n try { onMicrophone?.(live); } catch { /* theirs */ }\n };\n\n if (!liveCallSupported()) {\n const err = new Error('[BugCatch] this browser cannot make live calls.');\n emit('failed', { reason: 'unsupported' });\n throw err;\n }\n\n let microphone = stream;\n if (!microphone) {\n try {\n microphone = await openMicrophone();\n } catch (err) {\n emit('failed', { reason: 'microphone', error: String(err?.name || err) });\n throw err;\n }\n }\n\n let socket;\n try {\n socket = await openSocket(realtimeUrl);\n } catch (err) {\n stopStream(microphone);\n emit('failed', { reason: 'connect' });\n throw err;\n }\n\n let peer = null;\n let remoteAudio = null;\n let finished = false;\n let ringTimer = null;\n // What sphoro.voice said this call is keeping, and the tap feeding it. Both\n // stay null on the ordinary call, which keeps nothing and uplinks nothing.\n let capture = null;\n let uplink = null;\n // Whether the browser is currently refusing to play the other person, and the\n // teardown for the gesture listeners armed to try again. See playRemote.\n let blocked = false;\n let disarmGesture = null;\n /** Teardown for the microphone watch, armed once the peer connection is built. */\n let unwatchMicrophone = null;\n // Claimed synchronously, before the first await below, because the seat that\n // answers is told the call has started twice — once in its own\n // `session.created`, which says somebody is already waiting, and again in the\n // `peer.joined` that goes to both ends a moment later. Two starts that both\n // check `uplink` before either has set it are two taps on one microphone, and\n // two copies of every frame interleaved into one recording.\n let starting = false;\n // Candidates that arrived before there was anywhere to put them.\n //\n // The offer and the first candidates are sent within a millisecond of each\n // other and arrive in that order, but `setRemoteDescription` is a promise —\n // so a candidate can be in hand while the description it belongs to is still\n // being applied. Adding one then throws, and the call comes up with a worse\n // route than it could have had, or none.\n const pending = [];\n\n // --- surviving a change of network ------------------------------------------------\n //\n // A phone call moves. Somebody walks out of the office and the handset hands the\n // connection from Wi-Fi to cellular; a lift takes the signal for four seconds; a laptop\n // wakes on a different access point. Every one of those changes the local address the\n // call was negotiated against, and WebRTC's answer to all of them is the same: the ICE\n // agent reports `disconnected`, and unless somebody restarts it, `failed` a moment later.\n //\n // Before this, `failed` ended the call and `disconnected` was not handled at all — so a\n // call that would have recovered on its own in two seconds was hung up on, and one that\n // needed a restart was hung up on with no attempt made. On a desk that is rare. On the\n // phone this app also ships as, it is what happens when you stand up.\n //\n // What replaces it is a grace period, then an ICE restart, then a deadline. Each is\n // there for a distinct reason:\n //\n // the grace `disconnected` is frequently transient — a couple of lost packets on a\n // route that is still fine. Restarting immediately would tear down a\n // working connection to rebuild the same one, and take audio with it.\n //\n // the restart Renegotiation from scratch over the signaling socket, which is still\n // open: it never depended on the media route. This is the part that\n // actually crosses a network change.\n //\n // the deadline Reconnecting forever is its own failure. Somebody staring at a card\n // that says \"reconnecting\" for five minutes has been abandoned more\n // thoroughly than somebody told the call dropped.\n //\n // Only one side offers, and it is the side the server nominated at the start. Both ends\n // see the drop, so if the two sides both restarted there would be two offers crossing —\n // a glare condition WebRTC resolves badly. The other side asks instead, over the\n // signaling socket, and its request is best-effort: the offerer's own detection is what\n // this actually rests on.\n\n /** Which side was told to make the offer. Set from `peer.joined`, for both ends. */\n let shouldOffer = false;\n /** Whether the two ends have ever been connected. A drop before that is a failed setup. */\n let connectedOnce = false;\n let reconnecting = false;\n let recoverTimer = null;\n let giveUpTimer = null;\n let restarts = 0;\n\n /** How long `disconnected` is allowed to resolve itself before a restart is attempted. */\n const RECOVER_GRACE_MS = 2000;\n /** How long between one restart and the next, when the first did not take. */\n const RECOVER_RETRY_MS = 5000;\n /** How long a call may spend off the air in total before it is called dropped. */\n const RECOVER_DEADLINE_MS = 30000;\n const MAX_RESTARTS = 4;\n\n /** Everything ends here, whatever ended it, exactly once. */\n const finish = (state, detail) => {\n if (finished) return;\n finished = true;\n if (ringTimer) { clearTimeout(ringTimer); ringTimer = null; }\n // Before the peer is closed, and before the stream is stopped. Closing a connection\n // moves it to `closed` and stopping a track fires `ended`, and both would otherwise\n // reach handlers that would report a dropped call and a dead microphone to somebody\n // who has just hung up.\n clearRecovery();\n unwatchMicrophone?.();\n unwatchMicrophone = null;\n stopUplink();\n // Before anything else is torn down: these are listeners on `window`, and a\n // call that left them behind would keep retrying playback on an element\n // that is about to be removed — once per click, for the rest of the session.\n disarmGesture?.();\n disarmGesture = null;\n try { peer?.close(); } catch { /* already gone */ }\n stopStream(microphone);\n if (remoteAudio) {\n remoteAudio.srcObject = null;\n remoteAudio.remove();\n remoteAudio = null;\n }\n try {\n if (socket.readyState === WebSocket.OPEN) socket.close(1000, 'call over');\n } catch { /* already closing */ }\n emit(state, detail);\n };\n\n const send = (message) => {\n if (socket.readyState === WebSocket.OPEN) socket.send(JSON.stringify(message));\n };\n\n /**\n * Start sending sphoro.voice a copy of this person's microphone.\n *\n * Only when it asked, and only once both people are on the call. The server\n * throws away anything that arrives while the phone is still ringing — a\n * recording is timed from the moment the second person joined, and a minute\n * of somebody waiting alone at the head of the file is a timeline that\n * disagrees with the call's own duration. Sending it anyway would just be\n * bandwidth spent on audio that gets dropped, on the same network carrying\n * the call.\n *\n * Every failure here is survivable and none of them ends the call. A browser\n * with no Web Audio, a context the autoplay policy will not start, a format\n * this version does not know how to produce — each costs the recording and\n * leaves two people talking, which is the right way round. It is said out\n * loud, though: an agent set to record whose calls silently arrive with no\n * audio is the failure that is impossible to diagnose from the outside.\n */\n const startUplink = async () => {\n if (starting || uplink || finished || !capture) return;\n if (!capture.audio && !capture.transcript) return;\n starting = true;\n\n // The server names the format rather than assuming one, so a client that\n // does not recognise it sends nothing rather than sending µ-law into a\n // recording that expected something else — which would store a file of\n // noise and read as a working recording to everything downstream.\n const encoding = capture.encoding || 'mulaw';\n if (encoding !== 'mulaw') {\n warn(`this call is kept as ${encoding}, which this SDK cannot produce.`);\n return;\n }\n\n\n const rate = Number(capture.sample_rate) || 8000;\n try {\n const context = createContext(rate);\n // A context built without a user gesture starts suspended, and a\n // suspended context's ScriptProcessorNode never fires — so nothing would\n // be uplinked and nothing would say why. Both ends granted microphone\n // access a moment ago, which is the activation this spends.\n await resumeContext(context);\n if (finished) {\n try { context.close(); } catch { /* already closed */ }\n return;\n }\n\n const tap = tapMicrophone(context, microphone, (bytes) => {\n // Binary, not the base64 envelope the protocol also accepts: this is a\n // second copy of audio going up a network that is already carrying the\n // call, and base64 would make it a third larger for nothing.\n if (socket.readyState === WebSocket.OPEN) socket.send(bytes);\n }, rate);\n\n uplink = { context, tap };\n } catch (err) {\n warn(`could not send the copy this call is keeping: ${String(err?.message || err)}`);\n }\n };\n\n /**\n * Stop sending it.\n *\n * The microphone track is deliberately left alone — `finish` stops the whole\n * stream a moment later, and stopping it here would cut the conversation off\n * to end a recording.\n */\n function stopUplink() {\n starting = false;\n if (!uplink) return;\n const { context, tap } = uplink;\n uplink = null;\n try { tap.stop(); } catch { /* already gone */ }\n try { context.close(); } catch { /* already closed */ }\n }\n\n /**\n * Play the other person, and do something about it when the browser says no.\n *\n * The refusal used to be swallowed: `play().catch(() => {})`, on the reasoning\n * that both ends had granted microphone access a moment earlier and that\n * satisfies every autoplay policy. It does not always. A call answered from a\n * notification arrives with no user gesture on the page at all — the tap\n * happened on a system notification, which is not this document — and inside\n * an Android WebView that is exactly the case that fails.\n *\n * What that produced is the worst shape a bug can have: the call connects,\n * every state says `connected`, both ends are billed for it, and one person\n * hears nothing. Nothing is logged, because the rejection was caught and\n * dropped, so there is no evidence anywhere that it happened.\n *\n * So now it is reported and it is retried. The retry is armed on the next\n * gesture anywhere on the page, which is the one thing that reliably lifts an\n * autoplay block, and it is armed in capture so a handler calling\n * `stopPropagation` cannot hide the gesture from it. Whoever is on the call is\n * about to touch something — the mute button, the hang-up button, the report\n * they are talking about — and the sound comes up when they do, with the\n * caller free to say so on screen in the meantime.\n */\n function playRemote() {\n if (!remoteAudio || finished) return;\n let attempt;\n try {\n attempt = remoteAudio.play?.();\n } catch {\n // A synchronous throw, which some engines do instead of rejecting.\n markBlocked();\n return;\n }\n if (!attempt || typeof attempt.then !== 'function') {\n // No promise: an older engine, or a stub. Nothing to wait on and nothing\n // to report — it either made a sound or it did not.\n markPlaying();\n return;\n }\n attempt.then(markPlaying, markBlocked);\n }\n\n function markPlaying() {\n disarmGesture?.();\n disarmGesture = null;\n if (!blocked) return;\n blocked = false;\n sayBlocked(false);\n }\n\n function markBlocked() {\n if (finished) return;\n if (!blocked) {\n blocked = true;\n sayBlocked(true);\n }\n armGesture();\n }\n\n /** Try again on the next thing the person touches. Armed once. */\n function armGesture() {\n if (disarmGesture) return;\n // Guarded rather than assumed: this file is also loaded by a host page's\n // bundler into environments that define a partial `window`, and a retry\n // that cannot be armed is a quieter failure than one that throws inside a\n // promise nobody is awaiting.\n if (typeof window === 'undefined' || typeof window.addEventListener !== 'function') return;\n const retry = () => playRemote();\n const events = ['pointerdown', 'keydown', 'touchstart'];\n for (const name of events) {\n window.addEventListener(name, retry, { capture: true, passive: true });\n }\n disarmGesture = () => {\n for (const name of events) {\n try { window.removeEventListener(name, retry, { capture: true }); } catch { /* gone */ }\n }\n };\n }\n\n /**\n * Build the peer connection.\n *\n * Made on `session.created` rather than lazily on the first signal, because\n * the ICE gathering it starts is the slowest part of connecting and the whole\n * of it can happen while the phone is still ringing. Waiting until somebody\n * answers would add that delay to the exact moment a person says hello, which\n * is the one moment on this call where it is audible.\n */\n const build = (iceServers) => {\n peer = new RTCPeerConnection({ iceServers: iceServers?.length ? iceServers : FALLBACK_ICE });\n\n for (const track of microphone.getAudioTracks()) peer.addTrack(track, microphone);\n\n // Armed here rather than at the top, so a call that never got a peer connection has\n // nothing to tear down — and so the watch covers exactly the span in which being\n // unheard is a problem worth telling somebody about.\n unwatchMicrophone = watchMicrophone();\n\n peer.onicecandidate = (event) => {\n if (!event.candidate) return;\n send({ type: 'signal', signal: { kind: 'candidate', payload: event.candidate.toJSON() } });\n };\n\n peer.ontrack = (event) => {\n const [incoming] = event.streams;\n if (!incoming) return;\n // An element rather than the Web Audio API. Audio routed through a\n // MediaStreamAudioSourceNode loses the browser's own echo cancellation on\n // several engines, and this call has a live microphone next to it.\n if (!remoteAudio) {\n remoteAudio = document.createElement('audio');\n remoteAudio.autoplay = true;\n // Inline, for a WebView that would otherwise hand a media element to a\n // full-screen player — which on a call is a black rectangle over the\n // dashboard.\n remoteAudio.playsInline = true;\n remoteAudio.setAttribute?.('playsinline', '');\n // Off-screen rather than display:none — a hidden media element is\n // treated as invisible by some autoplay heuristics and refused.\n remoteAudio.style.cssText = 'position:fixed;width:0;height:0;opacity:0;pointer-events:none';\n document.body.appendChild(remoteAudio);\n }\n remoteAudio.srcObject = incoming;\n playRemote();\n };\n\n peer.onconnectionstatechange = routeChanged;\n // The same signal, read the other way, for the engines that do not give a reliable\n // `connectionState`. WebKit — which is every browser on iOS and the WebView this app\n // ships inside there — has historically fired `iceconnectionstatechange` and not the\n // aggregate one. Both are wired to the same handler, which reads whichever of the two\n // the engine actually maintains, so a duplicate event costs one comparison.\n peer.oniceconnectionstatechange = routeChanged;\n };\n\n /**\n * What the media route is doing, and what to do about it.\n *\n * Reads whichever state the engine keeps up to date, and treats `completed` — which only\n * the ICE-level state has — as connected, because it is: it means a route was found and\n * checking has stopped.\n */\n function routeChanged() {\n if (!peer || finished) return;\n const state = peer.connectionState || peer.iceConnectionState;\n\n if (state === 'connected' || state === 'completed') {\n connectedOnce = true;\n restarts = 0;\n clearRecovery();\n // Emitted whether or not this is a reconnection. The caller renders one state, and a\n // card that came back from \"reconnecting\" has to be told the call is up again.\n reconnecting = false;\n emit('connected');\n return;\n }\n\n if (state === 'disconnected') {\n // Before the two ends ever met, a `disconnected` is ICE still working — it goes\n // there between checking one candidate pair and the next. Only `failed` is final at\n // that stage, and it is handled below.\n if (!connectedOnce) return;\n enterRecovery(RECOVER_GRACE_MS);\n return;\n }\n\n if (state === 'failed') {\n if (!connectedOnce) {\n // The two browsers never found a route to each other at all. Almost always a\n // network that needs a TURN relay the deployment has not configured; reported as\n // its own reason so that is diagnosable from a support ticket rather than from a\n // packet capture. A restart would try the same candidates again.\n finish('failed', { reason: 'ice' });\n return;\n }\n // A route that worked and then stopped. That is a network change, and a restart is\n // exactly the remedy — no grace, because `failed` is not going to resolve itself.\n enterRecovery(0);\n }\n }\n\n /** Say the call is off the air, and line up an attempt to bring it back. */\n function enterRecovery(delay) {\n if (finished) return;\n if (!reconnecting) {\n reconnecting = true;\n emit('reconnecting');\n giveUpTimer = setTimeout(() => finish('failed', { reason: 'network' }), RECOVER_DEADLINE_MS);\n }\n // One attempt in flight at a time. `disconnected` and `failed` both arrive on the way\n // down, and two timers would mean two offers crossing on one connection.\n if (recoverTimer) return;\n recoverTimer = setTimeout(recover, delay);\n }\n\n function clearRecovery() {\n if (recoverTimer) { clearTimeout(recoverTimer); recoverTimer = null; }\n if (giveUpTimer) { clearTimeout(giveUpTimer); giveUpTimer = null; }\n }\n\n /**\n * Renegotiate the media route over the signaling socket, which never left.\n *\n * `createOffer({ iceRestart: true })` rather than `restartIce()`: the latter is the\n * modern spelling and does the same thing by firing `negotiationneeded`, but it is\n * missing from engines this SDK is expected to run in and the explicit form works\n * everywhere. It also keeps the offer on the same path as the original one, so there is a\n * single place where an offer is made.\n */\n async function recover() {\n recoverTimer = null;\n if (finished || !peer) return;\n // It came back on its own while the grace period was running, which is the common case\n // and the reason the grace period exists.\n const state = peer.connectionState || peer.iceConnectionState;\n if (state === 'connected' || state === 'completed') return;\n\n if (!shouldOffer) {\n // Not this side's to restart. Ask, and keep asking — the offerer sees the same drop\n // and is very likely already doing it, so this is the belt to that braces.\n send({ type: 'signal', signal: { kind: 'restart', payload: {} } });\n recoverTimer = setTimeout(recover, RECOVER_RETRY_MS);\n return;\n }\n\n if (restarts >= MAX_RESTARTS) return; // the deadline is what ends it\n restarts += 1;\n await restartIce();\n recoverTimer = setTimeout(recover, RECOVER_RETRY_MS);\n }\n\n /** The restart itself, also reachable from the other side's request. */\n async function restartIce() {\n if (finished || !peer) return;\n try {\n const description = await peer.createOffer({ iceRestart: true });\n await peer.setLocalDescription(description);\n send({ type: 'signal', signal: { kind: 'offer', payload: peer.localDescription.toJSON() } });\n } catch {\n // Nothing to do here but let the next attempt or the deadline decide. Ending the\n // call on one refused renegotiation would throw away the retries that follow.\n }\n }\n\n /**\n * Whether this person is still being heard.\n *\n * A track's `muted` is not the mute button — that is `enabled`, which this file sets and\n * therefore already knows about. `muted` is the *source* stopping: the OS handed the\n * microphone to an incoming phone call, a headset was unplugged, or — the case this app\n * meets daily — Android revoked microphone access because the app went to the background.\n *\n * Which produces the same shape as every other bug on this path: the call is up, the\n * other person is audible, both ends are billed, and nothing this person says goes\n * anywhere. They find out when the other person says \"hello? are you there?\".\n *\n * The loss is reported on a short delay and the recovery immediately. Several engines\n * flick `muted` on for a few frames while a track settles, and a card that said \"your\n * microphone was cut off\" for 200ms at the start of every call would be noise — while a\n * microphone that came back and was still reported lost is the error saying the wrong\n * thing.\n */\n function watchMicrophone() {\n const SETTLE_MS = 600;\n let settle = null;\n let lost = false;\n\n const say = (live) => {\n if (settle) { clearTimeout(settle); settle = null; }\n if (live) {\n if (!lost) return;\n lost = false;\n sayMicrophone(true);\n return;\n }\n if (lost) return;\n settle = setTimeout(() => {\n settle = null;\n if (finished) return;\n lost = true;\n sayMicrophone(false);\n }, SETTLE_MS);\n };\n\n for (const track of microphone.getAudioTracks()) {\n track.onmute = () => say(false);\n track.onunmute = () => say(true);\n // An ended track never comes back — the device is gone. Said at once rather than\n // after the settle, because there is nothing to wait for.\n track.onended = () => {\n if (settle) { clearTimeout(settle); settle = null; }\n if (lost) return;\n lost = true;\n sayMicrophone(false);\n };\n }\n return () => {\n if (settle) { clearTimeout(settle); settle = null; }\n for (const track of microphone.getAudioTracks()) {\n track.onmute = null;\n track.onunmute = null;\n track.onended = null;\n }\n };\n }\n\n const offer = async () => {\n try {\n const description = await peer.createOffer();\n await peer.setLocalDescription(description);\n send({ type: 'signal', signal: { kind: 'offer', payload: peer.localDescription.toJSON() } });\n } catch {\n finish('failed', { reason: 'negotiation' });\n }\n };\n\n const accept = async (payload) => {\n try {\n await peer.setRemoteDescription(new RTCSessionDescription(payload));\n const answer = await peer.createAnswer();\n await peer.setLocalDescription(answer);\n send({ type: 'signal', signal: { kind: 'answer', payload: peer.localDescription.toJSON() } });\n await drainCandidates();\n } catch {\n finish('failed', { reason: 'negotiation' });\n }\n };\n\n const drainCandidates = async () => {\n while (pending.length) {\n const candidate = pending.shift();\n try {\n await peer.addIceCandidate(new RTCIceCandidate(candidate));\n } catch {\n // One unusable candidate is not a failed call — there are usually\n // several, and the connection needs only one of them to work.\n }\n }\n };\n\n /**\n * One message at a time, in the order they arrived.\n *\n * Handling is asynchronous — `setRemoteDescription` and `createAnswer` are\n * promises — and the socket does not wait. Without this chain a candidate\n * arriving mid-answer is examined while the description it belongs to is\n * still being applied, so it goes on the pending list a moment after the\n * pending list was drained, and stays there for the rest of the call.\n */\n let handling = Promise.resolve();\n socket.onmessage = (event) => {\n handling = handling.then(() => handle(event)).catch(() => { /* one bad frame */ });\n };\n\n const handle = async (event) => {\n if (typeof event.data !== 'string') return;\n let message;\n try { message = JSON.parse(event.data); } catch { return; }\n\n switch (message.type) {\n case 'session.created': {\n build(message.data?.ice_servers);\n // What this call keeps, and in what format. Held rather than acted on:\n // nothing is uplinked until somebody is actually on the other end.\n capture = message.data?.capture || null;\n // Ringing, until somebody is on the other side. Whoever answers finds\n // the caller already waiting and skips straight past it — the\n // `peer.joined` that follows a moment later says the same thing, and\n // this is what keeps the answerer's first frame from being silence.\n if (message.data?.peer_present) {\n emit('connecting');\n void startUplink();\n break;\n }\n emit('ringing');\n armRingTimer();\n break;\n }\n\n case 'peer.joined': {\n if (ringTimer) { clearTimeout(ringTimer); ringTimer = null; }\n emit('connecting');\n // Told, not inferred. Both ends learn somebody arrived in the same\n // frame, and only one of them is asked to go first.\n //\n // Remembered as well as acted on: it is also the answer to \"whose job is it to\n // renegotiate when the network changes\", and that question is asked later, when\n // the frame that answered it is long gone. See `recover`.\n shouldOffer = Boolean(message.data?.should_offer);\n if (shouldOffer) await offer();\n // After the offer, and not awaited: opening an audio context is\n // milliseconds, but they would be milliseconds added to the moment\n // somebody says hello, which is the one place on this call where they\n // are audible.\n void startUplink();\n break;\n }\n\n case 'signal': {\n if (!peer) break;\n const { kind, payload } = message.data || {};\n if (kind === 'offer') {\n await accept(payload);\n } else if (kind === 'answer') {\n try {\n await peer.setRemoteDescription(new RTCSessionDescription(payload));\n await drainCandidates();\n } catch {\n finish('failed', { reason: 'negotiation' });\n }\n } else if (kind === 'candidate') {\n if (peer.remoteDescription) {\n try { await peer.addIceCandidate(new RTCIceCandidate(payload)); } catch { /* see above */ }\n } else {\n pending.push(payload);\n }\n } else if (kind === 'restart') {\n // The other side lost the route and cannot renegotiate itself. Honoured only by\n // the side that was nominated to offer, so a request arriving at both ends of a\n // mis-signalled call cannot produce two offers crossing.\n if (shouldOffer) await restartIce();\n }\n break;\n }\n\n case 'peer.left':\n finish('ended', { reason: 'peer_left' });\n break;\n\n case 'error':\n finish('failed', { reason: 'server', detail: message.error?.detail });\n break;\n\n case 'session.ended':\n finish('ended', { reason: 'server' });\n break;\n\n default:\n // Every message the agent conversation has — audio deltas, transcripts,\n // state changes. None of them happen on a bridged call, and one\n // arriving is not worth ending a working call over.\n break;\n }\n };\n\n socket.onclose = () => finish('ended', { reason: 'disconnected' });\n socket.onerror = () => finish('failed', { reason: 'socket' });\n\n /**\n * Stop waiting.\n *\n * The deadline comes from the server, which wrote it down when the call was\n * created — so a browser with a slow clock waits a little longer rather than\n * hanging up on somebody mid-answer, and the server refuses a give-up that\n * arrives early in any case.\n */\n function armRingTimer() {\n if (!ringsUntil) return;\n const remaining = new Date(ringsUntil).getTime() - Date.now();\n if (!Number.isFinite(remaining)) return;\n ringTimer = setTimeout(() => finish('missed'), Math.max(0, remaining));\n }\n\n return {\n hangUp() {\n // Said out loud before the socket closes. A close on its own would end\n // the call too — sphoro.voice ends a bridge the moment one side leaves —\n // but saying so first is what distinguishes hanging up from a tunnel.\n send({ type: 'session.end' });\n finish('ended', { reason: 'hung_up' });\n },\n setMuted(muted) {\n for (const track of microphone.getAudioTracks()) track.enabled = !muted;\n return muted;\n },\n isMuted() {\n return microphone.getAudioTracks().every((track) => !track.enabled);\n },\n };\n}\n\n/** Open the socket, resolving only once it is actually open. */\nfunction openSocket(url) {\n return new Promise((resolve, reject) => {\n let socket;\n try {\n socket = new WebSocket(url, SUBPROTOCOL);\n } catch (err) {\n reject(err);\n return;\n }\n socket.onopen = () => {\n socket.onopen = null;\n socket.onerror = null;\n resolve(socket);\n };\n socket.onerror = () => {\n socket.onopen = null;\n socket.onerror = null;\n reject(new Error('[BugCatch] could not connect the call.'));\n };\n });\n}\n\n/**\n * Say something went wrong with the copy, without ending anything.\n *\n * `console.warn` and not a state: every caller of this file renders the states\n * to somebody who is mid-conversation, and \"the recording did not start\" is not\n * news to the two people talking. It is news to whoever turned recording on,\n * and the console is where they will look.\n */\nfunction warn(message) {\n try { console.warn(`[BugCatch] ${message}`); } catch { /* no console */ }\n}\n\nfunction stopStream(stream) {\n for (const track of stream?.getTracks?.() ?? []) {\n try { track.stop(); } catch { /* already stopped */ }\n }\n}\n","// The reporter's telephone.\n//\n// Everything else this SDK draws is a consequence of something the person on\n// the page did: they pressed the widget, they filed a report, they asked to\n// talk it through. This is the one thing that arrives unasked — a member of the\n// team has a question about a report and is ringing to ask it — and that makes\n// it the piece with the most rules around it.\n//\n// # What it is allowed to do\n//\n// Appear, and make a noise. That is the whole of it. No microphone is opened,\n// no audio moves and nothing is connected until the person presses Answer, and\n// if they press nothing at all it takes itself away when the ringing stops. The\n// card is an offer; the SDK does not put a customer on a call because somebody\n// at the vendor clicked a button.\n//\n// # And what it says\n//\n// A name, a product and what the call is about, because a card that says\n// \"someone is calling about a report\" is indistinguishable from the thing every\n// bank tells people to hang up on. And whether the call would be recorded,\n// stated before Answer rather than after — on a peer-to-peer call the copy that\n// gets kept is uplinked by this browser, so telling somebody afterwards is\n// telling them about something that already happened.\n//\n// # The noise\n//\n// Synthesised, for the reason the dashboard's is: a ringtone as an audio file\n// is a binary in a bundle that runs on somebody else's site, an extra request\n// on their page, and a thing that can go missing behind a cache header. Four\n// oscillators and a table of numbers cost none of that.\n//\n// It is also softer and shorter than the dashboard's. A phone on a support\n// desk is meant to interrupt; this one is ringing in the middle of a stranger's\n// shopping cart, and a page that suddenly plays a loud telephone at somebody is\n// a page they close. If the browser refuses to play it at all — no interaction\n// yet, which is its rule and not ours — the card rings silently rather than not\n// appearing.\n\n/**\n * A quiet double-pulse, once every three seconds.\n *\n * Two notes rather than one so it reads as a telephone rather than as an alert,\n * and a fifth apart because that interval is the one people hear as \"ringing\"\n * across every phone system anybody has used.\n */\nconst RING = {\n cycleMs: 3000,\n peak: 0.045,\n bursts: [\n { at: 0, seconds: 0.4, hz: [440, 660] },\n { at: 0.55, seconds: 0.4, hz: [440, 660] },\n ],\n};\n\n/**\n * Whether anybody has touched this page yet.\n *\n * Tracked rather than asked, because there is nothing to ask: a browser will\n * tell you an AudioContext is suspended, but only after you have built one —\n * and building one on a page with no interaction is what writes\n *\n * The AudioContext was not allowed to start. It must be resumed (or\n * created) after a user gesture on the page.\n *\n * into the console. This SDK runs on somebody else's site, and that console\n * belongs to whoever is trying to debug their own product. A warning we cannot\n * act on, for a sound that was never going to play, is not ours to put there.\n */\nlet touched = false;\nif (typeof window !== 'undefined' && window.addEventListener) {\n for (const evt of ['pointerdown', 'keydown', 'touchstart']) {\n // Capture, and it matters more here than it would in our own app: this runs\n // on somebody else's page, whose handlers we have never seen and one of\n // which may well stop a click before it reaches the window.\n window.addEventListener(evt, () => { touched = true; }, {\n once: true, capture: true, passive: true,\n });\n }\n}\n\n/**\n * The same question asked of the browser rather than of ourselves.\n *\n * Chrome and Edge implement this and are the browsers that enforce the policy\n * in the first place, so where it matters it answers. It is the recovery for a\n * host page that swallowed every gesture before we saw it — better to make a\n * quiet noise the person expects than to stay silent because of somebody else's\n * event handler.\n */\nfunction activatedByBrowser() {\n try {\n return Boolean(navigator.userActivation?.hasBeenActive);\n } catch {\n return false;\n }\n}\n\n/**\n * Start ringing, and give back the way to stop.\n *\n * Returns a no-op teardown on every browser that will not do it, which is a\n * real and ordinary case: a page nobody has clicked on yet has no audio\n * permission, and the correct behaviour there is a card that appears in silence\n * rather than no card.\n */\nfunction startRinging() {\n if (!touched && !activatedByBrowser()) return () => {};\n\n const Ctx = typeof window !== 'undefined' && (window.AudioContext || window.webkitAudioContext);\n if (!Ctx) return () => {};\n\n let context;\n try {\n context = new Ctx();\n } catch {\n return () => {};\n }\n\n let stopped = false;\n let timer = null;\n const voices = [];\n\n const burst = ({ at, seconds, hz }) => {\n const start = context.currentTime + at;\n const gain = context.createGain();\n gain.connect(context.destination);\n // Shaped rather than switched. A square edge on a sine is a click, and a\n // click every three seconds is more irritating than the tone it introduces.\n gain.gain.setValueAtTime(0, start);\n gain.gain.linearRampToValueAtTime(RING.peak, start + 0.02);\n gain.gain.setValueAtTime(RING.peak, start + seconds - 0.06);\n gain.gain.linearRampToValueAtTime(0, start + seconds);\n\n for (const frequency of hz) {\n const osc = context.createOscillator();\n osc.type = 'sine';\n osc.frequency.value = frequency;\n osc.connect(gain);\n osc.start(start);\n osc.stop(start + seconds + 0.02);\n voices.push(osc);\n }\n };\n\n const cycle = () => {\n if (stopped) return;\n try {\n RING.bursts.forEach(burst);\n } catch { /* the card is still on screen, which is the part that matters */ }\n };\n\n const begin = () => {\n if (stopped) return;\n cycle();\n timer = setInterval(cycle, RING.cycleMs);\n };\n\n if (context.state === 'running') begin();\n else context.resume().then(begin).catch(() => {});\n\n return () => {\n stopped = true;\n if (timer) clearInterval(timer);\n // Stopped rather than left to finish. Answered, this is cut off by the\n // microphone opening a fraction of a second later, and a ring still sounding\n // over the first word of a conversation is the thing people apologise for.\n for (const osc of voices) {\n try { osc.stop(); } catch { /* already stopped on its own schedule */ }\n }\n try { context.close(); } catch { /* nothing left to close */ }\n };\n}\n\n/** Seconds left until a deadline, floored at zero. */\nfunction secondsLeft(ringsUntil) {\n const until = Date.parse(ringsUntil || '');\n if (Number.isNaN(until)) return 0;\n return Math.max(0, Math.round((until - Date.now()) / 1000));\n}\n\nconst BUTTON = [\n 'flex:1',\n 'padding:9px 12px',\n 'border:0',\n 'border-radius:8px',\n 'font:600 13px/1.4 inherit',\n 'cursor:pointer',\n].join(';');\n\n/**\n * Put the card on screen and ring until something happens to it.\n *\n * @param {object} opts\n * @param {object} opts.call The `incomingCall` object off the update stream —\n * `callId`, `from`, `project`, `reference`, `ringsUntil`, `recorded`.\n * @param {string} opts.accent The integration's own colour, for Answer.\n * @param {string} [opts.position] Same two words the toast takes.\n * @param {() => void} opts.onAnswer Pressed Answer.\n * @param {() => void} opts.onDecline Pressed Decline.\n * @param {() => void} [opts.onExpire] Nobody pressed anything in time.\n * @returns {(reason?: string) => void} Take it away. Called with no reason on\n * an ordinary dismissal; the caller passes one only for its own logging.\n */\nexport function showIncomingCall({\n call, accent = '#5b6ef5', position = 'bottom-right',\n onAnswer, onDecline, onExpire,\n}) {\n if (typeof document === 'undefined' || !document.body) return () => {};\n\n const [vertical, horizontal] = String(position).split('-');\n const el = document.createElement('div');\n el.setAttribute('role', 'alertdialog');\n el.setAttribute('aria-live', 'assertive');\n el.setAttribute('aria-label', `${call.from || 'Support'} is calling about ${call.reference}`);\n el.style.cssText = [\n 'position:fixed',\n `${vertical === 'top' ? 'top' : 'bottom'}:20px`,\n `${horizontal === 'left' ? 'left' : 'right'}:20px`,\n // Above the update toast, which uses the maximum and may be on screen at\n // the same moment — a status line must not sit on top of a ringing phone.\n 'z-index:2147483647',\n 'width:300px',\n 'max-width:calc(100vw - 40px)',\n 'padding:14px',\n 'border-radius:12px',\n 'background:#1b1e26',\n 'color:#eceef2',\n 'font:13px/1.5 -apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif',\n 'box-shadow:0 18px 44px rgba(0,0,0,0.5)',\n `border:1px solid ${accent}`,\n 'opacity:0',\n 'transform:translateY(8px)',\n 'transition:opacity .2s ease,transform .2s ease',\n ].join(';');\n\n const heading = document.createElement('div');\n heading.style.cssText = 'font-weight:600;font-size:14px;margin-bottom:2px';\n // textContent throughout: every string on this card came from a person typing\n // it, and it is being written into somebody else's page.\n heading.textContent = `${call.from || 'Support'} is calling`;\n\n const about = document.createElement('div');\n about.style.cssText = 'color:#a4abba;margin-bottom:10px';\n about.textContent = call.project\n ? `About your report ${call.reference} · ${call.project}`\n : `About your report ${call.reference}`;\n\n const countdown = document.createElement('div');\n countdown.style.cssText = 'color:#7d8598;font-size:12px;margin-bottom:10px';\n\n const answer = document.createElement('button');\n answer.type = 'button';\n answer.textContent = 'Answer';\n answer.style.cssText = `${BUTTON};background:${accent};color:#fff`;\n\n const decline = document.createElement('button');\n decline.type = 'button';\n decline.textContent = 'Not now';\n decline.style.cssText = `${BUTTON};background:#2b2f3a;color:#c8cedb`;\n\n const actions = document.createElement('div');\n actions.style.cssText = 'display:flex;gap:8px';\n actions.append(answer, decline);\n\n el.append(heading, about, countdown);\n if (call.recorded) {\n const kept = document.createElement('div');\n kept.style.cssText = 'color:#7d8598;font-size:12px;margin-bottom:10px';\n kept.textContent = 'This call will be recorded.';\n el.append(kept);\n }\n el.append(actions);\n document.body.appendChild(el);\n\n const stopRinging = startRinging();\n let closed = false;\n let ticker = null;\n\n const close = () => {\n if (closed) return;\n closed = true;\n if (ticker) clearInterval(ticker);\n stopRinging();\n el.style.opacity = '0';\n el.style.transform = 'translateY(8px)';\n setTimeout(() => el.remove(), 250);\n };\n\n const tick = () => {\n const left = secondsLeft(call.ringsUntil);\n countdown.textContent = left\n ? `Ringing — ${left}s`\n : 'The call has stopped ringing.';\n if (left) return;\n // The deadline is the server's, not this browser's arithmetic, for the\n // reason the API writes it down: two clocks disagreeing about when a phone\n // stopped ringing is how somebody gets cut off mid-answer.\n close();\n try { onExpire?.(); } catch { /* theirs */ }\n };\n tick();\n ticker = setInterval(tick, 1000);\n\n answer.addEventListener('click', () => {\n // Stopped before the handler runs, not after: opening a microphone takes a\n // moment, and a phone still ringing during it sounds like the button\n // did nothing.\n close();\n try { onAnswer?.(); } catch { /* theirs */ }\n });\n\n decline.addEventListener('click', () => {\n close();\n try { onDecline?.(); } catch { /* theirs */ }\n });\n\n requestAnimationFrame(() => {\n el.style.opacity = '1';\n el.style.transform = 'translateY(0)';\n });\n\n return close;\n}\n","/**\n * Who is reporting, what they are looking at, and whether init() was given\n * something that can work — shaped here, before anything is sent.\n *\n * Pure and import-free so the unit tests load it directly. Every function takes\n * a `warn(message)` callback instead of calling console itself: the client\n * decides whether (and how often) a warning is printed, and the tests collect\n * them.\n *\n * WHY THE BUSINESS IDS HAVE NAMES. A lending app knows exactly which customer is\n * signed in and which loan is on screen. Passed as `loanId`, `applicationId` or\n * `customerNumber`, the API reads them as identifiers the app itself asserted\n * (reference source `identify`), which is what lets bugcatch look the customer\n * up in the lender's own system over MCP without asking the reporter — and lets\n * that lookup count as a trusted join. Passed under any other name they are\n * still sent, but only as metadata a person can read.\n *\n * WHERE THEY GO. The signed-in user's fields travel as `metadata.user`, which is\n * where the API has always read `id` and where the ownership check reads `phone`\n * and `email`. The current screen travels as `metadata.context`. Both shapes are\n * mirrored by bugcatch-api/src/settings/reference-extract.ts identifyReferences.\n */\n\n/** Longest identifier the API keeps; anything longer is dropped there too. */\nexport const MAX_ID_CHARS = 120;\n/** Longest free-form string value kept on user or context. */\nexport const MAX_TEXT_CHARS = 500;\n/** Most extra (non-standard) keys kept on user or context. */\nexport const MAX_EXTRA_KEYS = 30;\n/** Largest nested object or array kept, as JSON. */\nconst MAX_NESTED_JSON = 1000;\n\n/**\n * The identifier fields bugcatch understands, and the names each accepts.\n * The first name is canonical; the rest are accepted and rewritten to it so the\n * API sees one spelling.\n */\nexport const ID_FIELDS = {\n loanId: ['loanId', 'loanNumber'],\n applicationId: ['applicationId'],\n customerNumber: ['customerNumber', 'customerId'],\n};\n\nconst CONTROL = /[\\u0000-\\u001f\\u007f]/;\n\n/** An identifier: a non-empty string or finite number, as a trimmed string, or null. */\nexport function idValue(raw) {\n if (typeof raw === 'number') return Number.isFinite(raw) ? String(raw) : null;\n if (typeof raw !== 'string') return null;\n const v = raw.trim();\n if (!v || v.length > MAX_ID_CHARS || CONTROL.test(v)) return null;\n return v;\n}\n\nfunction describe(v) {\n if (v === null) return 'null';\n if (Array.isArray(v)) return 'an array';\n return typeof v === 'object' ? 'an object' : `a ${typeof v}`;\n}\n\n/** Read one identifier field (under any of its names) off `src`, warning on a bad value. */\nfunction readId(src, canonical, names, where, warn) {\n for (const name of names) {\n if (!(name in src) || src[name] === undefined || src[name] === null || src[name] === '') continue;\n const v = idValue(src[name]);\n if (v) return v;\n const raw = src[name];\n warn(typeof raw === 'string' && raw.trim().length > MAX_ID_CHARS\n ? `${where}.${name} is longer than ${MAX_ID_CHARS} characters and was dropped — pass the identifier itself, not a sentence or a URL.`\n : `${where}.${name} should be a string or number, got ${describe(raw)} — it was dropped.`);\n }\n return null;\n}\n\n/** An extra field worth keeping: a primitive, or a small plain object / array. */\nfunction extraValue(v) {\n if (v === null || typeof v === 'boolean') return { ok: true, value: v };\n if (typeof v === 'number') return Number.isFinite(v) ? { ok: true, value: v } : { ok: false };\n if (typeof v === 'string') return { ok: true, value: v.slice(0, MAX_TEXT_CHARS) };\n if (typeof v === 'object') {\n try {\n const json = JSON.stringify(v);\n if (json && json.length <= MAX_NESTED_JSON) return { ok: true, value: JSON.parse(json) };\n } catch { /* cyclic or otherwise unserialisable */ }\n }\n return { ok: false };\n}\n\nconst USER_TEXT = ['email', 'name', 'phone'];\nconst RESERVED_USER = new Set(['id', 'userId', ...USER_TEXT, ...Object.values(ID_FIELDS).flat()]);\n\n/**\n * identify()'s argument, as it will be sent under `metadata.user`.\n *\n * Returns null for null/undefined (sign-out) and for anything that is not a\n * plain object, with a warning for the latter. Never throws.\n *\n * id string | number the customer's own account id in your system\n * email string threads their reports; where \"resolved\" mail goes\n * name string\n * phone string | number used, with email, to confirm the reporter owns\n * the record bugcatch looked up\n * loanId string | number (alias loanNumber)\n * applicationId string | number\n * customerNumber string | number (alias customerId)\n *\n * Anything else is kept as readable metadata, within limits.\n */\nexport function normalizeUser(user, warn = () => {}) {\n if (user === null || user === undefined) return null;\n if (typeof user !== 'object' || Array.isArray(user)) {\n warn(`identify() takes an object like { id, email, name } or null, got ${describe(user)} — ignored.`);\n return null;\n }\n const out = {};\n try {\n const id = readId(user, 'id', ['id', 'userId'], 'identify()', warn);\n if (id) out.id = id;\n for (const field of USER_TEXT) {\n const raw = user[field];\n if (raw === undefined || raw === null || raw === '') continue;\n if (field === 'phone' && typeof raw === 'number' && Number.isFinite(raw)) { out.phone = String(raw); continue; }\n if (typeof raw !== 'string') {\n warn(`identify().${field} should be a string, got ${describe(raw)} — it was dropped.`);\n continue;\n }\n const v = raw.trim().slice(0, field === 'name' ? 200 : MAX_ID_CHARS);\n if (!v) continue;\n if (field === 'email' && !v.includes('@')) {\n warn(`identify().email \"${v}\" does not look like an email address — reports cannot be threaded or followed up without one.`);\n }\n out[field] = v;\n }\n for (const [canonical, names] of Object.entries(ID_FIELDS)) {\n const v = readId(user, canonical, names, 'identify()', warn);\n if (v) out[canonical] = v;\n }\n let extras = 0;\n for (const [key, raw] of Object.entries(user)) {\n if (RESERVED_USER.has(key) || raw === undefined || typeof raw === 'function') continue;\n if (extras >= MAX_EXTRA_KEYS) { warn(`identify() was given more than ${MAX_EXTRA_KEYS} extra fields; the rest were dropped.`); break; }\n const kept = extraValue(raw);\n if (!kept.ok) { warn(`identify().${key} could not be sent (${describe(raw)} too large or not serialisable) — dropped.`); continue; }\n out[key] = kept.value;\n extras += 1;\n }\n } catch {\n // A getter that throws, a proxy — whatever it was, the report goes out with\n // what was read before it.\n }\n if (!out.id && !out.email) {\n warn('identify() was called without an id or email — reports will not be attributed to anyone, and nothing can be looked up by user.');\n }\n return out;\n}\n\n/**\n * setContext()'s argument merged over what was already set, as it will be sent\n * under `metadata.context`.\n *\n * `null` for the patch clears everything; a key set to null/undefined removes\n * that key. Identifier fields are validated exactly as identify()'s are and\n * rewritten to their canonical names; everything else (screen, route, feature\n * flag…) is kept within limits. Never throws.\n */\nexport function mergeContext(prev, patch, warn = () => {}) {\n if (patch === null) return {};\n const out = { ...(prev || {}) };\n if (patch === undefined) return out;\n if (typeof patch !== 'object' || Array.isArray(patch)) {\n warn(`setContext() takes an object like { loanId, screen } or null, got ${describe(patch)} — ignored.`);\n return out;\n }\n try {\n const aliases = new Map();\n for (const [canonical, names] of Object.entries(ID_FIELDS)) for (const n of names) aliases.set(n, canonical);\n for (const [key, raw] of Object.entries(patch)) {\n const canonical = aliases.get(key);\n if (raw === null || raw === undefined) { delete out[canonical || key]; continue; }\n if (canonical) {\n const v = readId({ [key]: raw }, canonical, [key], 'setContext()', warn);\n if (v) out[canonical] = v; else delete out[canonical];\n continue;\n }\n if (typeof raw === 'function') continue;\n if (!(key in out) && Object.keys(out).length >= MAX_EXTRA_KEYS) {\n warn(`setContext() holds at most ${MAX_EXTRA_KEYS} keys; \"${key}\" was dropped.`);\n continue;\n }\n const kept = extraValue(raw);\n if (!kept.ok) { warn(`setContext().${key} could not be sent (${describe(raw)} too large or not serialisable) — dropped.`); continue; }\n out[key] = kept.value;\n }\n } catch { /* keep what was merged so far */ }\n return out;\n}\n\n/** Every init() option this SDK reads. Anything else is probably a typo. */\nexport const KNOWN_OPTIONS = [\n 'apiBase', 'apiKey', 'projectId', 'consoleLimit', 'networkLimit', 'captureConsole',\n 'captureNetwork', 'captureErrors', 'widget', 'widgetPosition', 'theme', 'accent',\n 'attachments', 'maxAttachments', 'maxAttachmentBytes', 'maxRecordingMs', 'beforeSend',\n 'user', 'context', 'updates', 'updateToast', 'onUpdate', 'voice', 'onCallState',\n 'onIncomingCall', 'onReportSubmitted', 'onAssistantPhase', 'onError', 'debug',\n 'drafts', 'knownIssues', 'release', 'environment', 'buildId',\n];\n\nconst CALLBACK_OPTIONS = [\n 'beforeSend', 'onUpdate', 'onCallState', 'onIncomingCall', 'onReportSubmitted',\n 'onAssistantPhase', 'onError',\n];\n\n/** The closest known option to a misspelt one, when it is close enough to suggest. */\nfunction suggest(name) {\n const lower = name.toLowerCase().replace(/[-_]/g, '');\n return KNOWN_OPTIONS.find((k) => k.toLowerCase() === lower) || null;\n}\n\nconst PLACEHOLDER = /^(your[_-]|<|xxx|todo|changeme)/i;\nconst UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/**\n * What is wrong with init()'s options, as two lists: `errors` stop init (it\n * throws the first), `warnings` are printed and init carries on.\n *\n * `pageHost` is the page's hostname, passed in so this stays pure; it is what\n * tells a production page still pointed at the localhost default apart from a\n * developer's machine.\n */\nexport function checkOptions(options, { pageHost = '' } = {}) {\n const errors = [];\n const warnings = [];\n if (!options || typeof options !== 'object') {\n errors.push('init() takes an options object: BugCatch.init({ apiBase, apiKey, projectId }).');\n return { errors, warnings };\n }\n const { apiKey, projectId, apiBase } = options;\n\n if (!apiKey) errors.push('apiKey is required — create one under Workspace Settings → API Keys.');\n else if (typeof apiKey !== 'string') errors.push(`apiKey must be a string, got ${describe(apiKey)}.`);\n else if (PLACEHOLDER.test(apiKey)) errors.push(`apiKey is still the placeholder \"${apiKey}\" — paste the key you saved when you created it.`);\n else if (UUID.test(apiKey)) warnings.push('apiKey looks like a project id — did you swap apiKey and projectId?');\n else if (!apiKey.startsWith('bc_')) warnings.push('apiKey does not look like a BugCatch key (they start with \"bc_live_\") — reports will be refused with HTTP 401.');\n\n if (!projectId) errors.push('projectId is required — copy it from Project Settings → Web SDK.');\n else if (typeof projectId !== 'string') errors.push(`projectId must be a string, got ${describe(projectId)}.`);\n else if (PLACEHOLDER.test(projectId)) errors.push(`projectId is still the placeholder \"${projectId}\".`);\n else if (projectId.startsWith('bc_')) warnings.push('projectId looks like an API key — did you swap apiKey and projectId?');\n else if (/\\s/.test(projectId)) warnings.push('projectId contains whitespace — check it was copied cleanly.');\n\n if (apiBase === undefined || apiBase === null) {\n const local = !pageHost || /^(localhost|127\\.|0\\.0\\.0\\.0|\\[::1\\])/.test(pageHost);\n if (!local) warnings.push('apiBase is not set, so reports go to http://localhost:3000 — set apiBase to your BugCatch API URL.');\n } else if (typeof apiBase !== 'string' || !/^https?:\\/\\//.test(apiBase)) {\n warnings.push(`apiBase should be an absolute http(s) URL, got ${JSON.stringify(apiBase)}.`);\n }\n\n for (const key of Object.keys(options)) {\n if (KNOWN_OPTIONS.includes(key)) continue;\n const near = suggest(key);\n warnings.push(near\n ? `unknown option \"${key}\" — did you mean \"${near}\"?`\n : `unknown option \"${key}\" is ignored.`);\n }\n for (const key of CALLBACK_OPTIONS) {\n const v = options[key];\n if (v !== undefined && v !== null && typeof v !== 'function') {\n warnings.push(`${key} should be a function, got ${describe(v)} — it will not be called.`);\n }\n }\n return { errors, warnings };\n}\n\n/** Drop a trailing slash so `${apiBase}/ingest/bugs` never has two. */\nexport function cleanApiBase(apiBase) {\n return typeof apiBase === 'string' ? apiBase.replace(/\\/+$/, '') : apiBase;\n}\n","import {\n createRingBuffer, installConsoleCapture, installNetworkCapture,\n installErrorCapture, collectEnvironment, collectEnvironmentSync,\n} from './collectors.js';\nimport { mountWidget } from './widget.js';\nimport { uploadAttachment, normalizeMime, REPORT_MIMES } from './capture.js';\nimport {\n watchUpdates, createUpdateNotifier, fetchMyReports, fetchReport, writeToken, readToken,\n clearToken, sendMessage, verifyFix, callKindsForProject,\n reportByCall, liveCallAvailable, requestLiveCall, giveUpLiveCall,\n answerIncomingCall, declineIncomingCall,\n readUnread, rememberUpdate, forgetUpdate, forgetReportUpdates, forgetAllUpdates,\n fetchKnownIssues, matchKnownIssues, joinKnownIssue,\n UNREAD_KEY,\n} from './updates.js';\nimport {\n loginState, startSignIn, confirmSignIn, endSignIn, readSession, clearSession,\n} from './session.js';\nimport { joinLiveCall, liveCallSupported, openMicrophone } from './live-call.js';\nimport { showIncomingCall } from './incoming-call.js';\nimport { referenceFields } from './references.js';\nimport { normalizeUser, mergeContext, checkOptions, cleanApiBase } from './identity.js';\n\n// Replaced at build time from package.json. The guard is for anyone importing\n// src/ directly, where the define never runs.\nconst SDK_IDENTITY = {\n name: typeof __SDK_NAME__ === 'string' ? __SDK_NAME__ : '@sphoro/bugcatch-web-sdk',\n version: typeof __SDK_VERSION__ === 'string' ? __SDK_VERSION__ : '0.0.0-dev',\n};\n\nconst DEFAULTS = {\n apiBase: 'http://localhost:3000',\n apiKey: null,\n projectId: null,\n consoleLimit: 100,\n networkLimit: 50,\n captureConsole: true,\n captureNetwork: true,\n captureErrors: true,\n // Shows the floating \"Report a bug\" button. Turn off to drive it yourself.\n widget: true,\n widgetPosition: 'bottom-right',\n // 'auto' follows the visitor's system setting; 'light' / 'dark' pin it.\n theme: 'auto',\n accent: '#4F46E5',\n // Screenshot / screen recording / file attach. Capture needs getDisplayMedia,\n // so it is offered only where the browser has it — file attach always works.\n attachments: true,\n maxAttachments: 5,\n maxAttachmentBytes: 25 * 1024 * 1024,\n maxRecordingMs: 120_000,\n // Called before send; return false to cancel, or mutate the payload to scrub.\n beforeSend: null,\n user: null,\n // Tell the reporter when a bug they filed changes status — live, in your\n // app, where they already are. Off means no connection is opened at all.\n updates: true,\n // Show the built-in notification when an update arrives. It stays until the\n // reporter marks it read — including across a reload — so an update is not\n // lost to whoever happened to be looking at the tab. Turn it off and drive\n // the notification yourself from onUpdate and unreadUpdates().\n updateToast: true,\n // Called with { id, bugId, title, status, at } whenever one of this browser's\n // reports moves. Runs whether or not the built-in notification is on; `id` is\n // what markUpdateRead() takes.\n onUpdate: null,\n // Offer a call to the team when a report is picked up, if the workspace has\n // sphoro.voice configured. Nothing is dialled and no permission is asked for\n // until somebody presses the button — see startCall().\n voice: true,\n // Called as a call moves: 'ringing', 'connecting', 'connected', 'missed',\n // 'ended', 'failed'. The SDK shows nothing of its own beyond the offer, so an\n // integration that wants a call UI builds it from this.\n onCallState: null,\n // Called with { bugId, callId, from, project, reference, ringsUntil,\n // recorded } when somebody on the team rings this page about a report filed\n // from it. The SDK has already drawn the card by then — this is for a host\n // application that wants to duck its own audio or pause a video, not a hook\n // that can suppress a call the person can see ringing. Set `voice: false` to\n // switch the whole thing off.\n onIncomingCall: null,\n // Called with { bugId, title, via: 'form' | 'call', result } once a report\n // has been accepted — from the widget, from report(), or from pressing Call.\n // The same thing is emitted as the 'report' event.\n onReportSubmitted: null,\n // Called with { bugId, phase, title, detail } when the AI assistant working\n // a report moves: 'analyzing' → 'needs_info' | 'confirming' | 'reviewing' |\n // 'resolving' | 'resolved'. Seen whenever the report is fetched — the widget\n // does while it is open; myReport(id) does for an integration's own view.\n onAssistantPhase: null,\n // Called with { action, error } when something the SDK did on the page's\n // behalf failed (a report that was refused, a listener that threw). Also the\n // 'error' event.\n onError: null,\n // Business context for the screen the SDK starts on — see setContext().\n context: null,\n // Log what the SDK is doing (identify, context, each report's shape) to the\n // console. Off in production; the warnings about misconfiguration are\n // printed either way.\n debug: false,\n};\n\nclass BugCatchClient {\n constructor() {\n this.config = { ...DEFAULTS };\n this.started = false;\n this.teardown = [];\n this.consoleBuffer = createRingBuffer(DEFAULTS.consoleLimit);\n this.networkBuffer = createRingBuffer(DEFAULTS.networkLimit);\n this.stopWatching = null;\n // The built-in notification stack, built on first use: an integration that\n // renders its own never has one, and neither does a visitor who has nothing\n // unread.\n this.notifier = null;\n // The call in progress, if any, and whether one is being connected. One at\n // a time: a second would be two microphones on one page talking over each\n // other.\n this.call = null;\n this.callStarting = false;\n // The card on screen for a call the team is placing to this page, if any.\n // Distinct from `call` above, which is a conversation in progress: this is\n // a telephone that has not been picked up, and the two exist at different\n // moments — a call cannot be ringing and connected at once.\n this.ringing = null;\n // event -> Set<handler>. Several parts of a host app can listen without\n // having to share the single `onUpdate` config callback between them.\n this.listeners = new Map();\n // What setContext() has said about the screen the reporter is on. Its own\n // field rather than part of config, so init() — which rebuilds config —\n // cannot wipe a context set before it ran.\n this.context = {};\n // Warnings already printed, so a misconfiguration in a render loop says so\n // once rather than a thousand times.\n this.warned = new Set();\n // The last assistant phase seen per report, so onAssistantPhase fires on a\n // change rather than on every poll.\n this.phases = new Map();\n }\n\n /** A misconfiguration, said once. Always printed — it is what saves a day of guessing. */\n warn(message) {\n if (this.warned.has(message)) return;\n this.warned.add(message);\n // eslint-disable-next-line no-console\n console.warn(`[BugCatch] ${message}`);\n }\n\n /** What the SDK is doing, for `debug: true`. */\n debugLog(...args) {\n // eslint-disable-next-line no-console\n if (this.config.debug) console.info('[BugCatch]', ...args);\n }\n\n /** Call a host callback without letting it break the SDK. */\n #callback(name, payload) {\n const fn = this.config[name];\n if (typeof fn !== 'function') return;\n try { fn(payload); } catch (err) {\n // eslint-disable-next-line no-console\n console.error(`[BugCatch] your ${name} callback threw:`, err);\n }\n }\n\n init(options = {}) {\n if (this.started) {\n console.warn('[BugCatch] init() called twice — ignoring the second call.');\n return this;\n }\n const { errors, warnings } = checkOptions(options, {\n pageHost: typeof location !== 'undefined' ? location.hostname : '',\n });\n if (errors.length) throw new Error(`[BugCatch] ${errors[0]}`);\n for (const w of warnings) this.warn(w);\n\n // identify() and setContext() may run before init() — an app that knows its\n // user before it has loaded its config is ordinary. Rebuilding config here\n // used to throw that user away; it is carried over unless init names one.\n const earlier = this.config.user;\n this.config = { ...DEFAULTS, ...options, apiBase: cleanApiBase(options.apiBase ?? DEFAULTS.apiBase) };\n this.config.user = options.user !== undefined\n ? normalizeUser(options.user, (m) => this.warn(m.replace('identify()', 'init({ user })')))\n : earlier || null;\n if (options.context) this.setContext(options.context);\n\n this.consoleBuffer = createRingBuffer(this.config.consoleLimit);\n this.networkBuffer = createRingBuffer(this.config.networkLimit);\n\n if (this.config.captureConsole) this.teardown.push(installConsoleCapture(this.consoleBuffer));\n if (this.config.captureNetwork) this.teardown.push(installNetworkCapture(this.networkBuffer));\n if (this.config.captureErrors) this.teardown.push(installErrorCapture(this.consoleBuffer));\n if (this.config.widget) this.teardown.push(mountWidget(this));\n\n // Only if this browser has actually filed something. A visitor who has\n // never reported has nothing to be told about, and opening a connection\n // for them would be a request on every page load that can never deliver.\n if (this.config.updates && readToken()) {\n this.startWatching();\n // Whatever was still unread when they last closed the tab. The stream\n // replays nothing on connect, so without this an update that arrived\n // while somebody was away would be gone by the time they came back.\n //\n // A tick late, so the count reaches a listener registered on the line\n // after init() — which is where every integration registers one, and\n // where a synchronous emit would fire into an empty room.\n queueMicrotask(() => this.#drawUnread());\n }\n\n this.started = true;\n this.debugLog('initialised', {\n version: SDK_IDENTITY.version, apiBase: this.config.apiBase, projectId: this.config.projectId,\n user: this.config.user, context: this.context,\n });\n return this;\n }\n\n /**\n * Open the update stream. Idempotent — init() and a first report both reach\n * here, and a second connection would double every notification.\n */\n startWatching() {\n if (this.stopWatching) return this;\n this.stopWatching = watchUpdates({\n apiBase: this.config.apiBase,\n onUpdate: (event) => {\n // A telephone, not an update — taken first and returned on, so it\n // neither queues behind a status line nor lands in the unread store. A\n // call is worthless thirty seconds after it is sent, and an unread\n // badge that still says \"someone called\" tomorrow morning is a badge\n // nobody trusts.\n if (event?.incomingCall) { this.#ring(event); return; }\n if (event?.callWithdrawn) { this.#stopRinging(event.callWithdrawn.callId); return; }\n\n // Held before it is shown, and held until it is read. The card on\n // screen is a view of this store rather than the notification itself,\n // which is what lets a reload, a second tab, or the widget marking it\n // read all agree about what is still outstanding.\n const update = rememberUpdate(event);\n if (typeof this.config.onUpdate === 'function') this.config.onUpdate(update);\n // Listeners registered with on('update') get the same frame. The\n // widget uses this to refresh a report the reader has open, so a\n // change lands on screen rather than waiting for them to reopen it —\n // and to mark it read, which is why this runs before the redraw.\n this.emit('update', update);\n this.#drawUnread();\n },\n });\n this.teardown.push(() => this.stopWatching?.());\n // And a card left ringing when the SDK is torn down is a card that rings\n // forever: the stream that would withdraw it has just been closed.\n this.teardown.push(() => this.#stopRinging());\n\n // Another tab of the same app reads one. They share the store, so they\n // share what is outstanding — without this, a card marked read in one tab\n // sits there in the other, over an app that already knows about it.\n const onStorage = (event) => {\n // A null key is localStorage.clear(), which takes the store with it.\n if (event.key === UNREAD_KEY || event.key === null) this.#drawUnread();\n };\n window.addEventListener('storage', onStorage);\n this.teardown.push(() => window.removeEventListener('storage', onStorage));\n return this;\n }\n\n /**\n * Subscribe to updates on this browser's reports. Returns an unsubscribe.\n *\n * const off = BugCatch.on('update', (u) => console.log(u.summary));\n *\n * Alongside the `onUpdate` config option rather than replacing it: that one\n * is the single handler an integration sets at init, this one lets several\n * parts of an app listen independently — a badge, a toast of your own, an\n * open report view — without them having to agree on one callback.\n */\n on(event, handler) {\n if (typeof handler !== 'function') return () => {};\n const set = this.listeners.get(event) || new Set();\n set.add(handler);\n this.listeners.set(event, set);\n return () => this.off(event, handler);\n }\n\n off(event, handler) {\n this.listeners.get(event)?.delete(handler);\n return this;\n }\n\n /** Notify listeners; one that throws must not stop the rest. */\n emit(event, payload) {\n for (const fn of this.listeners.get(event) || []) {\n try {\n fn(payload);\n } catch (err) {\n // Someone else's handler, on someone else's page.\n // eslint-disable-next-line no-console\n console.error(`[BugCatch] a '${event}' listener threw:`, err);\n }\n }\n }\n\n /**\n * Updates this browser has been told about and nobody has read yet, oldest\n * first.\n *\n * BugCatch.unreadUpdates().length // a badge on your own bell\n *\n * Survives a reload: the stream replays nothing when it reconnects, so an\n * update that arrived while the tab was closed would otherwise be gone.\n * Each carries the `id` the mark-read calls take.\n */\n unreadUpdates() {\n return readUnread();\n }\n\n /**\n * Mark one update read. It stops being shown, here and on the next load.\n *\n * BugCatch.on('update', (u) => myBell.add(u, () => BugCatch.markUpdateRead(u.id)));\n *\n * Read is something the reporter says, not something a timer decides — which\n * is the whole reason these are held rather than shown for eight seconds and\n * dropped. An integration rendering its own notifications is the one that\n * knows when its reader has seen one, so it has to say.\n */\n markUpdateRead(id) {\n forgetUpdate(id);\n this.#drawUnread();\n return this;\n }\n\n /**\n * Mark everything about one report read.\n *\n * Opening the report is reading its updates, so the widget calls this when\n * the thread opens; an integration with its own report view should too.\n */\n markReportRead(bugId) {\n forgetReportUpdates(bugId);\n this.#drawUnread();\n return this;\n }\n\n /** Mark everything read. The \"clear all\" behind your own notification list. */\n markUpdatesRead() {\n forgetAllUpdates();\n this.#drawUnread();\n return this;\n }\n\n /**\n * Redraw the built-in notifications, and tell anyone counting.\n *\n * The single path out of the unread store: every add and every mark-read ends\n * here, so the cards on screen and a host app's own badge cannot disagree\n * about what is outstanding.\n */\n #drawUnread() {\n const unread = readUnread();\n // Emitted whether or not the built-in cards are on — this is the number a\n // host app puts on its own bell.\n this.emit('unread', { count: unread.length, updates: unread });\n\n if (!this.config.updateToast) return;\n if (!this.notifier) {\n if (!unread.length) return;\n this.notifier = createUpdateNotifier({\n accent: this.config.accent,\n position: this.config.widgetPosition,\n onRead: (id) => this.markUpdateRead(id),\n onReadAll: () => this.markUpdatesRead(),\n // The call button appears only where the API said the offer stands and\n // this page has calling switched on. Pressing it reads the update —\n // acting on a notification is the clearest way there is of having seen\n // it — and `startCall` does the rest.\n onCall: this.config.voice\n ? (update) => {\n this.markUpdateRead(update.id);\n void this.startCall(update.bugId);\n }\n : null,\n });\n this.teardown.push(() => this.notifier?.destroy());\n }\n this.notifier.render(unread);\n }\n\n /**\n * One report in full, with its timeline — every step from filing to now.\n *\n * const report = await BugCatch.myReport(id);\n * report.timeline // [{ kind: 'filed' | 'status' | 'severity' | ... , summary, at }]\n *\n * Resolves to null when this browser cannot prove it filed that report.\n */\n async verifyFix(bugId, { result, resolvedAt, note }) {\n const context = result === 'STILL_HAPPENING' ? await this.#payload({}) : {};\n return verifyFix({ apiBase: this.config.apiBase, bugId, result, resolvedAt, note, metadata: context.metadata, logs: context.logs });\n }\n\n async myReport(bugId) {\n const report = await fetchReport({ apiBase: this.config.apiBase, bugId });\n this.#notePhase(bugId, report?.assistant);\n return report;\n }\n\n /**\n * Tell the host app the assistant moved, once per change.\n *\n * The phase is only known when a report is fetched, which the widget does\n * every few seconds while its thread is open and an integration does through\n * myReport(). The first sighting counts as a change: a host that has just\n * opened a report wants to know it is being analysed.\n */\n #notePhase(bugId, assistant) {\n const phase = assistant?.phase;\n if (!bugId || typeof phase !== 'string') return;\n if (this.phases.get(bugId) === phase) return;\n this.phases.set(bugId, phase);\n const detail = { bugId, phase, title: assistant.title ?? null, detail: assistant.detail ?? null };\n this.debugLog('assistant', detail);\n this.#callback('onAssistantPhase', detail);\n this.emit('assistant', detail);\n }\n\n /** A report was accepted: tell the host app, however it was filed. */\n #submitted(result, { title, via }) {\n if (!result?.id) return;\n const detail = { bugId: result.id, title: title ?? null, via, result };\n this.debugLog('report accepted', detail);\n this.#callback('onReportSubmitted', detail);\n this.emit('report', detail);\n }\n\n /** Something done on the page's behalf failed. */\n #failed(action, error) {\n this.#callback('onError', { action, error });\n this.emit('error', { action, error });\n }\n\n /**\n * Write back on a report this browser filed.\n *\n * const key = await BugCatch.uploadAttachment(file, file.type);\n * await BugCatch.replyToReport(id, {\n * body: 'Still happening on Safari',\n * attachments: [{ ...key, name: file.name, size: file.size }],\n * });\n *\n * The widget's own conversation view calls this; it is public so an\n * integration rendering its own can too. Resolves to the stored message, so\n * the caller renders what the server kept rather than what it hoped it sent.\n */\n replyToReport(bugId, { body, attachments } = {}) {\n return sendMessage({ apiBase: this.config.apiBase, bugId, body, attachments });\n }\n\n /**\n * Everything a report carries, however it was started.\n *\n * Shared by `report()` and by pressing Call, because the two file the same\n * thing — the difference is only whether a person typed a title first. Two\n * copies of this would drift, and the half that drifted would be the one that\n * stopped attaching the console.\n */\n async #payload({ title, description = '', priority = 'MEDIUM', metadata = {}, media = [], references = [], context = null }) {\n // This report's own context over the screen's, without keeping it.\n const ctx = context ? mergeContext(this.context, context, (m) => this.warn(m.replace('setContext()', 'report({ context })'))) : this.context;\n return {\n projectId: this.config.projectId,\n ...(title ? { title } : {}),\n description,\n priority,\n url: window.location.href,\n reporterEmail: this.config.user?.email,\n // Sent alongside the address so the dashboard can show a person rather\n // than a mailbox, and so the resolution email can open with their name.\n reporterName: this.config.user?.name,\n metadata: {\n ...(await collectEnvironment()),\n release: this.config.release,\n environment: this.config.environment,\n buildId: this.config.buildId,\n ...metadata,\n // Who is signed in, as identify() shaped it: `id`, `email` and `phone`\n // are where the API and the ownership check read them, and `loanId`,\n // `applicationId`, `customerNumber` become trusted lookup keys.\n user: this.config.user || undefined,\n // The screen the report came from — setContext(). Its identifiers\n // outrank the same kind on `user`.\n ...(Object.keys(ctx).length ? { context: ctx } : {}),\n sdk: SDK_IDENTITY,\n },\n logs: {\n console: { entries: this.consoleBuffer.all() },\n network: { entries: this.networkBuffer.all() },\n },\n // Omitted rather than sent empty: the field is optional server-side, and\n // an empty array in every payload makes beforeSend hooks harder to read.\n ...(media.length ? { attachments: media } : {}),\n // What the reporter answered when asked which record this is about. Only\n // ever what they typed into the box — never what this browser matched in\n // their own text, which the API reads for itself out of the title and\n // description it is being sent anyway. A payload is a claim, and there is\n // no reason to make the join key one when the source text is right here.\n ...(references.length ? { references } : {}),\n };\n }\n\n /**\n * File a report because somebody pressed Call instead of filling the form in.\n *\n * Filing is the half a call shares with the form, and it is the half that has\n * to say who is filing. Held in one place after a session header went missing\n * from this route while `report()` kept sending it: nothing looked wrong until\n * a project switched sign-in on, and then a signed-in reporter pressing Call\n * was told to sign in.\n */\n async #fileByCall() {\n const session = readSession(this.config.projectId);\n\n let filed;\n try {\n filed = await reportByCall({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n token: session?.token,\n payload: await this.#payload({}),\n });\n } catch (err) {\n // A session the server no longer recognises, dropped exactly as\n // `report()` drops it: the widget re-reads state every time it opens, so\n // forgetting it now is what makes it draw the sign-in step instead.\n if (err.status === 401 && session) clearSession(this.config.projectId);\n this.#failed('call', err);\n throw err;\n }\n this.#submitted(filed, { title: null, via: 'call' });\n\n // The same token `report()` keeps, and for the same reason: it is what lets\n // this browser hear what happens to the report afterwards.\n if (filed.watchToken) {\n writeToken(filed.watchToken);\n if (this.config.updates) this.startWatching();\n }\n return filed;\n }\n\n /**\n * Whether this visitor can report something by talking about it instead.\n *\n * if (await BugCatch.canCallNow()) showYourOwnCallButton();\n *\n * Two conditions, and both have to hold before anything is drawn. The project\n * has to have calling configured — false on most deployments — and this\n * browser has to know who the visitor is, because a call with nobody to\n * attribute it to has no watch token to follow it with and nobody to tell\n * afterwards what happened to the report. That is the same limit every other\n * follow-up feature in this SDK has: it all hangs off `identify()`.\n */\n async canCallNow() {\n return Boolean(await this.callKind());\n }\n\n /**\n * Which kind of call this visitor would get: 'live', or null for none.\n *\n * const kind = await BugCatch.callKind();\n * // 'live' — the team's dashboards ring and a colleague picks up\n *\n * One kind, and this says whether it is on offer. It is still worth asking\n * rather than assuming: on most deployments the answer is null, and a button\n * promising a call nobody can place is worse than no button.\n *\n * The answer is remembered for the life of the page. It is a property of the\n * deployment's configuration rather than of this visitor, and re-asking on\n * every dialog open is a request per page view for something that changes\n * about once a year.\n */\n async callKind() {\n const kinds = await this.callKinds();\n return kinds.live ? 'live' : null;\n }\n\n /**\n * The same answer as booleans: `{ agent, live }`.\n *\n * const { live } = await BugCatch.callKinds();\n * if (live) drawCallTheTeam();\n *\n * `agent` is always false. It was a triage agent that answered instantly\n * instead of a person, it is gone, and the key stays only so an integration\n * that reads it does not get `undefined` and draw a button on it.\n *\n * Same round trip and same page-lifetime cache as `callKind()`. False on a\n * deployment without sphoro.voice, on a workspace that has switched calling\n * off, and on a visitor this SDK has no address for — a call needs somebody\n * to attribute it to.\n */\n async callKinds() {\n const none = { agent: false, live: false };\n if (!this.config.voice) return none;\n if (!this.config.user?.email) return none;\n if (this.kinds !== undefined) return this.kinds;\n this.kinds = await callKindsForProject({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n });\n return this.kinds;\n }\n\n /**\n * Whether a report can be talked through right now.\n *\n * if (await BugCatch.canCall(id)) showYourOwnButton();\n *\n * False on every deployment without sphoro.voice configured, on every\n * workspace that has not switched calling on, and on a report that has had\n * its allowance of calls — so an integration rendering its own affordance can\n * ask rather than guess.\n *\n * The same question as `canCallTeam`, which is the name the widget uses. Both\n * are kept because both are documented, and there is one kind of call left\n * for either of them to be asking about.\n */\n canCall(bugId) {\n if (!this.config.voice) return Promise.resolve(false);\n return liveCallAvailable({ apiBase: this.config.apiBase, bugId });\n }\n\n /**\n * Ring the team.\n *\n * await BugCatch.startCall(id); // about a report already filed\n * await BugCatch.startCall(); // instead of filling the form in\n *\n * With a report id, it rings about that report. With none, it files one first\n * — carrying the console, network and environment this SDK is already holding\n * — and rings about that. The report is written before the phones ring either\n * way, so a call nobody answers still leaves the context behind; the handle\n * carries `bugId` so the caller can say what it was filed as.\n *\n * Asks for the microphone, places the call, and resolves once it is\n * *ringing* — not once somebody answers. The handle's `onState` is how the\n * caller follows what happens after that:\n *\n * 'ringing' the team's dashboards are lighting up\n * 'connecting' somebody answered; the two browsers are finding each other\n * 'connected' they can hear each other\n * 'missed' nobody picked up before the ringing stopped\n * 'ended' it is over\n *\n * What comes back is a conversation with a colleague: the audio travels\n * directly between the two browsers over WebRTC and never reaches bugcatch or\n * sphoro.voice, so it is not transcribed. The thread gets a note saying the\n * call happened and who took it.\n *\n * Rejects if the microphone is refused, if calling is not available, or if\n * one is already running. There is deliberately no way to start a second: two\n * microphones on one page talk over each other. A rejection carrying `.filed`\n * means the report was written and only the call failed.\n */\n async startCall(bugId) {\n if (!this.config.voice) throw new Error('[BugCatch] calling is switched off.');\n // Two checks, because there are two ways to already be on a call: one that\n // has connected, and one still asking for the microphone. A double-click\n // races past a check on `this.call` alone — the second press gets through\n // while the first is still awaiting, and the page ends up with two\n // microphones in one conversation.\n if (this.call || this.callStarting) {\n throw new Error('[BugCatch] a call is already running.');\n }\n this.callStarting = true;\n\n try {\n // Whether this deployment can place a call at all, asked before the\n // microphone prompt and before anything is filed. Without it a press from\n // a visitor with no address, or on a project with calling off, filed a\n // report anyway — and the reporter, promised a call, got a ticket and a\n // phone that never rang.\n if (!await this.callKind()) {\n throw new Error(this.config.user?.email\n ? '[BugCatch] calling is not available on this project.'\n : '[BugCatch] a call needs to know who is calling — call identify() with an email first.');\n }\n return await this.#ringTheTeam(bugId);\n } finally {\n this.callStarting = false;\n }\n }\n\n /**\n * Ring the team, under the name an integration may already be calling.\n *\n * await BugCatch.startLiveCall(id);\n *\n * Exactly `startCall`, which is now the only call there is. The id is\n * optional here too: it was required while this was one of two kinds, because\n * the other one was the only way to call without a report to call about.\n */\n startLiveCall(bugId) {\n return this.startCall(bugId);\n }\n\n /** The body of startCall, so the in-flight flag clears on every path. */\n async #ringTheTeam(bugId) {\n // Before the call is placed, not after. A permission prompt takes as long\n // as the person takes to read it, and asking afterwards would have a room\n // full of dashboards ringing about a call its own caller may yet refuse to\n // be on. That ordering is why the report below is filed *after* the\n // microphone too, rather than first as the form does: filing is what makes\n // the phones ring.\n if (!liveCallSupported()) {\n throw new Error('[BugCatch] this browser cannot make live calls.');\n }\n const microphone = await openMicrophone();\n\n let started;\n let filed = null;\n try {\n if (bugId) {\n started = await requestLiveCall({ apiBase: this.config.apiBase, bugId });\n } else {\n // Nobody filled the form in. One round trip writes what the page was\n // doing and rings the team about it, so a call nobody answers still\n // leaves the context behind.\n filed = await this.#fileByCall();\n bugId = filed.id;\n started = filed.live_call;\n if (!started?.realtime_url) {\n // Filed, but nobody rang. Reported as such rather than thrown, so the\n // caller can say what the report is called instead of losing it.\n const err = new Error(`[BugCatch] the report was filed, but the team could not be rung${\n filed.call_error ? `: ${filed.call_error}` : '.'}`);\n err.filed = filed;\n err.reason = filed.call_error || null;\n throw err;\n }\n }\n } catch (err) {\n for (const track of microphone.getTracks()) track.stop();\n throw err;\n }\n\n const handle = await joinLiveCall({\n realtimeUrl: started.realtime_url,\n stream: microphone,\n ringsUntil: started.rings_until,\n onState: (state, detail) => {\n if (state === 'ended' || state === 'failed' || state === 'missed') this.call = null;\n // Nobody picked up, as decided by the one client that was listening to\n // it. Told to the API so the thread says so; the reporter has already\n // been shown it, and nothing here waits for the answer.\n if (state === 'missed') {\n giveUpLiveCall({ apiBase: this.config.apiBase, bugId, callId: started.call_id });\n }\n if (typeof this.config.onCallState === 'function') {\n this.config.onCallState(state, { bugId, live: true, ...detail });\n }\n this.emit('call', { bugId, live: true, state, ...detail });\n },\n });\n\n // Set after the await, not before: a call that never connected is not one\n // this page is on, and leaving the slot taken would block the retry.\n this.call = handle;\n return Object.assign(handle, { bugId, callId: started.call_id, live: true, filed });\n }\n\n /**\n * Whether this report can be talked through with a person.\n *\n * For a host application drawing its own button. The widget asks this itself.\n */\n canCallTeam(bugId) {\n if (!this.config.voice) return Promise.resolve(false);\n return liveCallAvailable({ apiBase: this.config.apiBase, bugId });\n }\n\n // --- being called ------------------------------------------------------------\n\n /**\n * Somebody on the team is ringing this page.\n *\n * The only thing in this SDK that starts without the person on the page\n * asking for it, which is why what it does is so much narrower than what the\n * outgoing calls do: it draws a card and makes a soft noise. No microphone is\n * opened and nothing is connected until Answer is pressed, and if nothing is\n * pressed the card takes itself away when the ringing stops.\n *\n * Silently ignored when calling is switched off in this integration's config\n * or the browser cannot hold a peer connection. A host application that has\n * said `voice: false` has said its customers are not to be rung, and a card\n * offering a call this page could not join would be worse than none.\n */\n #ring(event) {\n const call = event.incomingCall;\n if (!this.config.voice || !liveCallSupported()) return;\n // Already ringing about this one — a reconnect replaying the frame, or a\n // second delivery. One card either way.\n if (this.ringing?.callId === call.callId) return;\n // One microphone on this page, so one call. Somebody already talking is not\n // interrupted by a card offering them a second conversation.\n if (this.call || this.callStarting) return;\n\n this.#stopRinging();\n\n const dismiss = showIncomingCall({\n call,\n accent: this.config.accent,\n position: this.config.widgetPosition,\n onAnswer: () => { void this.#answer(event.bugId, call); },\n onDecline: () => {\n this.ringing = null;\n declineIncomingCall({\n apiBase: this.config.apiBase, bugId: event.bugId, callId: call.callId,\n });\n this.emit('incoming-call', { bugId: event.bugId, callId: call.callId, state: 'declined' });\n },\n // Nothing is reported here. The member who placed it is watching it ring\n // on their own screen and their browser tells the API when it stops —\n // the same rule as the other direction, where whoever placed the call is\n // the one that reports nobody answered.\n onExpire: () => {\n this.ringing = null;\n this.emit('incoming-call', { bugId: event.bugId, callId: call.callId, state: 'missed' });\n },\n });\n\n this.ringing = { callId: call.callId, dismiss };\n if (typeof this.config.onIncomingCall === 'function') {\n // After the card, not instead of it. This is for a host application that\n // wants to know — to duck its own audio, to pause a video — and not a\n // hook that can suppress a call the person can see ringing.\n try { this.config.onIncomingCall({ bugId: event.bugId, ...call }); } catch { /* theirs */ }\n }\n this.emit('incoming-call', { bugId: event.bugId, callId: call.callId, state: 'ringing' });\n }\n\n /** Take the card away, whatever it is showing. */\n #stopRinging(callId) {\n if (!this.ringing) return;\n if (callId && this.ringing.callId !== callId) return;\n try { this.ringing.dismiss(); } catch { /* already gone */ }\n this.ringing = null;\n }\n\n /**\n * Answer it: microphone first, then the claim, then the socket.\n *\n * That order is the same one every other call in this file uses and it\n * matters most here. The permission prompt takes as long as the person takes\n * to read it, and claiming the call before they have answered it would put\n * somebody on a conversation they may yet decline to be able to speak on —\n * with a member of the team on the other end saying hello to a muted browser.\n */\n async #answer(bugId, call) {\n this.ringing = null;\n if (this.call || this.callStarting) return;\n this.callStarting = true;\n\n let microphone;\n try {\n microphone = await openMicrophone();\n } catch (err) {\n this.callStarting = false;\n // Refused the prompt, or no microphone on this machine. The call is still\n // ringing at the other end, so it is declined rather than left — the\n // member calling should not wait out the deadline for somebody who\n // cannot speak to them.\n declineIncomingCall({ apiBase: this.config.apiBase, bugId, callId: call.callId });\n this.emit('incoming-call', { bugId, callId: call.callId, state: 'failed', error: err });\n return;\n }\n\n let answered;\n try {\n answered = await answerIncomingCall({\n apiBase: this.config.apiBase, bugId, callId: call.callId,\n });\n } catch (err) {\n for (const track of microphone.getTracks()) track.stop();\n this.callStarting = false;\n // 409 is the ordinary one: it stopped ringing while Answer was being\n // pressed. Reported as a state rather than thrown, because nobody called\n // this — the page did, on somebody's behalf, and there is no promise\n // waiting to reject.\n this.emit('incoming-call', { bugId, callId: call.callId, state: 'missed', error: err });\n return;\n }\n\n try {\n const handle = await joinLiveCall({\n realtimeUrl: answered.realtime_url,\n stream: microphone,\n // No deadline on this side. The ringing is over — it ended when this\n // browser claimed the call — and a countdown here would hang up on a\n // conversation that has already started.\n onState: (state, detail) => {\n if (state === 'ended' || state === 'failed' || state === 'missed') this.call = null;\n if (typeof this.config.onCallState === 'function') {\n this.config.onCallState(state, { bugId, live: true, incoming: true, ...detail });\n }\n this.emit('call', { bugId, live: true, incoming: true, state, ...detail });\n },\n });\n this.call = handle;\n this.emit('incoming-call', { bugId, callId: call.callId, state: 'answered' });\n } catch (err) {\n for (const track of microphone.getTracks()) track.stop();\n this.emit('incoming-call', { bugId, callId: call.callId, state: 'failed', error: err });\n } finally {\n this.callStarting = false;\n }\n }\n\n /**\n * End the call in progress, of either kind. Does nothing when there is not\n * one — there is a single slot, because there is a single microphone.\n */\n endCall() {\n this.call?.hangUp();\n this.call = null;\n return this;\n }\n\n /**\n * Everything this browser has reported, newest first, with the status of\n * each — the history a reporter normally has no way to see.\n *\n * const { reports } = await BugCatch.myReports();\n * // [{ reference: '3F2A9B01', title, status, priority, createdAt, ... }]\n *\n * Resolves to an empty history rather than throwing when nothing has been\n * filed from this browser, or when identify() supplied no email — there is\n * nothing to look up in either case.\n */\n myReports() {\n return fetchMyReports({ apiBase: this.config.apiBase });\n }\n\n /** True when this browser has filed something, so a \"your reports\" entry\n * point can be hidden until it would show anything. */\n hasReports() {\n return Boolean(readToken());\n }\n\n /**\n * Forget this browser's reports — call on sign-out, or the next person at\n * this machine inherits the previous one's notifications.\n */\n forgetReports() {\n this.stopWatching?.();\n this.stopWatching = null;\n // Ended, not just forgotten. This is the sign-out path, and a call left\n // running on it would keep a microphone open on behalf of somebody who has\n // just left — and go on transcribing them into a report they can no longer\n // prove is theirs.\n this.endCall();\n // Drops the unread notifications with the token — see clearToken — so the\n // next person at a shared machine does not find the last one's bug reports\n // stacked in the corner.\n clearToken();\n this.#drawUnread();\n return this;\n }\n\n /**\n * Attach the signed-in user so reports say who hit the bug.\n *\n * `email` is the field that matters beyond display: it threads every report\n * from the same person together in the dashboard, and it is where the team's\n * \"your issue is resolved\" email goes. Because your app has already\n * authenticated this person, an address sent here is treated as reachable —\n * unlike one typed into a public recording-link form, which stays unusable\n * for outbound mail until the reporter answers a confirmation code.\n *\n * BugCatch.identify({ id: 'u_42', email: 'alex@acme.com', name: 'Alex' });\n *\n * Pass null to forget them again on sign-out.\n */\n identify(user) {\n const shaped = normalizeUser(user, (m) => this.warn(m));\n // Anything but null/undefined that yielded nothing usable is a mistake, not\n // a sign-out: keep who was there rather than silently forgetting them.\n if (user !== null && user !== undefined && !shaped) return this;\n this.config.user = shaped;\n this.debugLog('identify', shaped);\n return this;\n }\n\n /**\n * Say what the reporter is looking at — the loan, application or customer on\n * this screen — so every report filed from it carries that record.\n *\n * BugCatch.setContext({ loanId: 'L26090028459', screen: 'repayment' });\n * BugCatch.setContext({ loanId: null }); // left the loan screen\n * BugCatch.setContext(null); // clear everything\n *\n * Merged over what was set before and kept until changed — for the life of\n * the page, not across reloads. `loanId`, `applicationId` and\n * `customerNumber` are read by bugcatch as identifiers your app asserted, and\n * outrank the same field on identify(); anything else is readable metadata.\n */\n setContext(context) {\n this.context = mergeContext(this.context, context, (m) => this.warn(m));\n this.debugLog('context', this.context);\n return this;\n }\n\n /** What setContext() currently holds (a copy). */\n getContext() {\n return { ...this.context };\n }\n\n /**\n * Open the report panel, as if the floating button had been pressed — for a\n * \"Report a problem\" item in your own menu.\n *\n * Needs the built-in widget (`widget: true`, the default). With `widget:\n * false` there is no panel to open: draw your own form and call report().\n */\n open() {\n if (!this.started) { this.warn('open() was called before init() — nothing to open yet.'); return this; }\n if (!this.widgetControls) { this.warn('open() needs the built-in widget; it was turned off with widget: false.'); return this; }\n this.widgetControls.open();\n return this;\n }\n\n /** Close the report panel if it is open. */\n close() {\n this.widgetControls?.close();\n return this;\n }\n\n /** Whether the report panel is open. */\n isOpen() {\n return Boolean(this.widgetControls?.isOpen());\n }\n\n /**\n * Upload a screenshot, recording or file and get back the storage key that\n * `report({ attachments })` references.\n *\n * The blob goes straight to storage on a presigned URL — it never passes\n * through the API — so this is also what you call when you drive the SDK\n * yourself with `widget: false`.\n */\n async uploadAttachment(blob, mime, onProgress) {\n if (!this.started) throw new Error('[BugCatch] call init() first.');\n return uploadAttachment({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n blob,\n mime: mime || blob?.type,\n onProgress,\n });\n }\n\n /** File a bug with everything captured so far. */\n async report({\n title, description = '', priority = 'MEDIUM', metadata = {}, attachments = [],\n references = [], context = null,\n } = {}) {\n if (!this.started) throw new Error('[BugCatch] call init() first.');\n if (typeof title !== 'string' || !title.trim()) throw new Error('[BugCatch] a title is required.');\n if (!['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'].includes(String(priority).toUpperCase())) {\n this.warn(`priority \"${priority}\" is not one of LOW, MEDIUM, HIGH, CRITICAL — sent as MEDIUM.`);\n priority = 'MEDIUM';\n }\n\n // Drop anything the API would reject outright, so one bad entry can't cost\n // the reporter the whole report.\n const media = (attachments || [])\n // REPORT_MIMES, not the full upload set: a report's attachments are typed\n // SCREENSHOT or VIDEO, so a document reaching here would be filed as a\n // screenshot. Documents and voice notes belong on a message.\n .filter((a) => a?.storageKey && normalizeMime(a.mime, REPORT_MIMES))\n .map((a) => ({\n type: a.type === 'VIDEO' ? 'VIDEO' : 'SCREENSHOT',\n storageKey: a.storageKey,\n mime: normalizeMime(a.mime, REPORT_MIMES),\n }));\n\n // Only well-formed entries: one malformed reference must not cost the\n // reporter the report (the API refuses the whole body on a bad one).\n const refs = (Array.isArray(references) ? references : [])\n .filter((r) => r && typeof r.key === 'string' && r.key && (typeof r.value === 'string' || typeof r.value === 'number'))\n .map((r) => ({ key: r.key.slice(0, 64), value: String(r.value).trim().slice(0, 120) }))\n .filter((r) => r.value);\n if (Array.isArray(references) && refs.length < references.length) {\n this.warn('report({ references }) takes [{ key, value }] with string values; malformed entries were dropped.');\n }\n\n let payload = await this.#payload({\n title: title.trim(), description: typeof description === 'string' ? description : String(description ?? ''),\n priority: String(priority).toUpperCase(), metadata, media, references: refs, context,\n });\n\n if (typeof this.config.beforeSend === 'function') {\n const result = this.config.beforeSend(payload);\n if (result === false) return { cancelled: true };\n if (result && typeof result === 'object') payload = result;\n }\n\n this.debugLog('sending report', {\n title: payload.title, user: payload.metadata?.user, context: payload.metadata?.context,\n references: payload.references, console: payload.logs?.console?.entries?.length,\n network: payload.logs?.network?.entries?.length,\n });\n const session = readSession(this.config.projectId);\n let res;\n try {\n res = await fetch(`${this.config.apiBase}/ingest/bugs`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-BugCatch-Key': this.config.apiKey,\n // Present only where the reporter signed in. The server takes the\n // address from here rather than from the payload when it is — on a\n // project that requires sign-in, letting the page name a different\n // reporter would undo the requirement.\n ...(session?.token ? { 'X-BugCatch-Session': session.token } : {}),\n },\n body: JSON.stringify(payload),\n });\n } catch (cause) {\n // No response at all: the wrong apiBase, the API down, or CORS refusing\n // this origin. The browser's own \"Failed to fetch\" names none of them.\n const err = new Error(`[BugCatch] could not reach ${this.config.apiBase} — check apiBase, and that this site's origin is allowed by the API.`);\n err.cause = cause;\n this.#failed('report', err);\n throw err;\n }\n\n // A session the server no longer recognises — expired, or ended by a\n // sign-in somewhere else. Dropped here rather than left to fail again on\n // the next report: the widget re-reads state every time it opens, so\n // forgetting it now is what makes it draw the sign-in step instead.\n if (res.status === 401 && session) clearSession(this.config.projectId);\n\n if (!res.ok) {\n let detail = '';\n try {\n const body = await res.json();\n detail = Array.isArray(body.message) ? body.message.join(', ') : body.message || '';\n } catch { /* non-JSON error */ }\n // The two refusals an integrator hits first, said in their terms.\n if (res.status === 401 && !session) detail ||= 'the apiKey was not accepted';\n if (res.status === 403) detail ||= 'the apiKey is not allowed to report to this projectId';\n const err = new Error(`[BugCatch] report failed (HTTP ${res.status})${detail ? ': ' + detail : ''}`);\n err.status = res.status;\n this.#failed('report', err);\n throw err;\n }\n\n const result = await res.json();\n this.#submitted(result, { title: payload.title ?? title.trim(), via: 'form' });\n\n // The API returns a watch token only when it could attribute the report to\n // someone — which means identify() was called with an email. Without one\n // there is nobody to notify, and none comes back.\n if (result.watchToken) {\n writeToken(result.watchToken);\n if (this.config.updates) this.startWatching();\n }\n\n return result;\n }\n\n /**\n * What this project asks of the person reporting, and who this browser is\n * signed in as right now.\n *\n * const { mode, signedIn } = await BugCatch.loginState();\n * // mode: 'off' | 'optional' | 'required'\n *\n * The widget calls this itself every time it opens. It is here for\n * integrations running with `widget: false`, which have to draw their own\n * sign-in and need the same answer to know whether to draw one at all.\n *\n * The mode is the project's setting, changed in BugCatch under Project\n * Settings → General, not in this config. A widget that could opt out of a\n * required sign-in would not be a requirement.\n */\n loginState() {\n return loginState({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n token: readSession(this.config.projectId)?.token,\n });\n }\n\n /**\n * What this project needs in order to look a report up, and how it asks.\n *\n * const { policy, fields } = await BugCatch.referenceFields();\n * // policy: 'off' | 'optional' | 'required'\n *\n * The widget calls this itself when it opens, and caches the answer for the\n * life of the page: unlike the sign-in state, this cannot change underneath a\n * reporter in a way that matters — an admin editing the field list mid-report\n * costs one stale question, not a failed send.\n *\n * Here for the same reason loginState() is: an integration running with\n * `widget: false` draws its own form and needs the same answer to know\n * whether that form should have an identifier box on it.\n */\n referenceFields() {\n if (this.refFields) return this.refFields;\n this.refFields = referenceFields({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n });\n return this.refFields;\n }\n\n /**\n * Who this browser is signed in as on this project, from storage alone.\n *\n * BugCatch.signedInAs() // { email, name } | null\n *\n * Synchronous, and therefore only as current as the last round trip: a\n * session ended elsewhere still reads as signed in here until something asks\n * the server. Use loginState() where that matters.\n */\n signedInAs() {\n const session = readSession(this.config.projectId);\n return session ? { email: session.email, name: session.name } : null;\n }\n\n /**\n * Step one of signing in: mail a code to any address.\n *\n * await BugCatch.signIn('someone@example.com');\n * await BugCatch.confirmSignIn('someone@example.com', '481920');\n *\n * Any address, deliberately — there is no list of people who may report a\n * bug in somebody's product, and asking for one would be asking the customer\n * to maintain it. What the code proves is that whoever typed it can read the\n * address, which is all a reply-to has ever needed to be worth.\n */\n signIn(email, { name } = {}) {\n return startSignIn({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n email,\n // The name the host app already knows beats asking for one again.\n name: name ?? this.config.user?.name ?? null,\n });\n }\n\n /**\n * Step two: spend the code and hold the session.\n *\n * Signing in signs this address out of this project in every other browser —\n * one live session per address per project. Somebody who signs in on their\n * phone has signed their laptop out, on purpose.\n */\n async confirmSignIn(email, code) {\n const session = await confirmSignIn({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n email,\n code,\n });\n\n // A signed-in reporter is an identity, so reports carry it the same way\n // identify() makes them — but never over one the host application set:\n // that address is its own claim about its own user, and this SDK is not\n // the thing that decides it was wrong.\n if (!this.config.user?.email) {\n this.config.user = { email: session.email, name: session.name || undefined };\n }\n this.emit('signin', { email: session.email, name: session.name });\n return { email: session.email, name: session.name };\n }\n\n /**\n * Sign out of this project.\n *\n * Ends the session on the server too, so the token cannot be replayed from a\n * copy of this browser's storage. Local state goes whatever the network\n * says — sign-out on a shared machine has to mean signed out on it.\n */\n async signOut() {\n const session = readSession(this.config.projectId);\n const result = await endSignIn({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n token: session?.token,\n });\n // Only the identity this session put there. One the host application set\n // through identify() is theirs, and outlives a widget sign-out.\n if (session?.email && this.config.user?.email === session.email) this.config.user = null;\n this.emit('signout', { email: session?.email || null });\n return result;\n }\n\n /**\n * What this project is already known to be having trouble with.\n *\n * The widget asks this itself when it opens; it is public for a host\n * application drawing its own help surface with `widget: false`. Resolves to\n * an empty list on every project that has not switched the feature on, which\n * is the default — draw nothing rather than an empty heading.\n */\n knownIssues() {\n return fetchKnownIssues({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n sessionToken: readSession(this.config.projectId)?.token,\n });\n }\n\n /**\n * The known issues that look like a description somebody is writing.\n *\n * Pass an AbortSignal when calling this on a keystroke, or a fast typist gets\n * the results of whichever request happens to return last.\n */\n matchKnownIssues(query, { signal } = {}) {\n return matchKnownIssues({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n q: query || '',\n sessionToken: readSession(this.config.projectId)?.token,\n signal,\n });\n }\n\n /**\n * Say a known issue is happening to you too, instead of filing a second\n * report about it.\n *\n * Sends the same capture a report would — this browser's console, network and\n * environment — because that is what the team gets out of somebody joining\n * rather than filing: one report with everybody's environment on it.\n *\n * Needs an address. A signed-in widget has one, and identify() supplies one\n * otherwise; pass `email` for the case where neither did. Joining means \"tell\n * me when this is fixed\", and there is no way to keep that promise to\n * somebody anonymous.\n */\n async meToo(bugId, { email, name } = {}) {\n const session = readSession(this.config.projectId);\n const environment = await collectEnvironment();\n return joinKnownIssue({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n bugId,\n reporterEmail: email || this.config.user?.email,\n reporterName: name || this.config.user?.name,\n metadata: {\n ...environment,\n user: this.config.user || undefined,\n ...(Object.keys(this.context).length ? { context: this.context } : {}),\n sdk: SDK_IDENTITY,\n },\n logs: {\n console: { entries: this.consoleBuffer.all() },\n network: { entries: this.networkBuffer.all() },\n },\n sessionToken: session?.token,\n });\n }\n\n /** What would be sent right now — useful for debugging integrations. */\n snapshot() {\n return {\n user: this.config.user || null,\n context: { ...this.context },\n console: this.consoleBuffer.all(),\n network: this.networkBuffer.all(),\n environment: collectEnvironmentSync(),\n };\n }\n\n clear() {\n this.consoleBuffer.clear();\n this.networkBuffer.clear();\n }\n\n /** Restore every patched global and remove the widget. */\n destroy() {\n // The call first. Everything else here is listeners and buffers; this one\n // holds a live microphone, and a page that tore the SDK down without\n // releasing it would keep the tab's recording indicator lit.\n this.endCall();\n this.teardown.forEach((fn) => { try { fn(); } catch { /* ignore */ } });\n this.teardown = [];\n // Torn down with everything else, so a later init() builds a fresh one\n // rather than rendering into a stack that has been removed.\n this.notifier = null;\n this.started = false;\n }\n}\n\nconst BugCatch = new BugCatchClient();\n\n// Exposed on the singleton rather than as a second named export: with a UMD\n// build, a named export alongside the default turns `window.BugCatch` into a\n// namespace object, so `BugCatch.init(...)` from a <script> tag would break.\nBugCatch.Client = BugCatchClient;\n// For support tickets and `console.log(BugCatch.version)`.\nBugCatch.version = SDK_IDENTITY.version;\n\nexport default BugCatch;\n"],"names":["PLACEHOLDER","ALWAYS","MARKERS","AMBIGUOUS","SHAPES","BEARER","USERINFO","safeDecode","value","normalizeKey","rawKey","looksSecret","re","isSensitiveKey","rawValue","key","marker","redactPairs","query","pair","eq","redactShapes","text","out","redactUrl","raw","url","hashAt","head","hash","queryAt","body","innerQuery","redactText","m","createRingBuffer","limit","items","item","LEVELS","installConsoleCapture","buffer","levels","originals","level","args","stringify","installNetworkCapture","slowMs","originalFetch","originalOpen","originalSend","started","_a","method","_b","_c","res","record","err","rest","status","ms","error","installErrorCapture","onError","event","message","where","onRejection","collectEnvironment","base","collectEnvironmentSync","b","nav","_d","_e","_g","_f","_h","_i","nativeFetch","nativeXhrOpen","nativeXhrSend","UPLOAD_MIMES","REPORT_MIMES","voiceSupported","captureSupported","screenshotSupported","normalizeMime","mime","allowed","captureScreenshot","stream","track","video","resolve","width","height","canvas","blob","t","startRecording","maxMs","onTick","onAutoStop","recorder","chunks","e","startedAt","timer","cleanup","capTimer","finished","endEarly","startVoiceNote","uploadAttachment","apiBase","apiKey","onProgress","type","uploadUrl","put","reject","xhr","humanSize","bytes","humanDuration","seconds","s","MAX_AGE","database","open","request","draftKey","config","email","draftOperation","db","tx","store","readDraft","KEY_PREFIX","storageKey","projectId","inMemory","readSession","parsed","writeSession","session","held","clearSession","headers","token","readError","loginState","off","startSignIn","name","confirmSignIn","code","endSignIn","TOKEN_KEY","BACKOFF_MS","STABLE_MS","readToken","writeToken","clearToken","forgetAllUpdates","UNREAD_KEY","updateId","readUnread","list","u","writeUnread","rememberUpdate","forgetUpdate","id","forgetReportUpdates","bugId","drain","onFrame","split","frame","data","line","fetchMyReports","fetchReport","sendMessage","attachments","detail","problem","callKindsForProject","none","reportByCall","payload","liveCallAvailable","requestLiveCall","giveUpLiveCall","callId","answerIncomingCall","declineIncomingCall","watchUpdates","onUpdate","running","attempt","controller","connect","openedAt","reader","decoder","done","wait","STATUS_COPY","KIND_ACCENT","updateHeadline","updateDetail","agoLabel","at","then","mins","hours","days","dockOffset","host","dock","createUpdateNotifier","accent","position","onRead","onReadAll","onCall","vertical","horizontal","atTop","atLeft","cards","stack","more","ensureStack","drop","el","button","label","primary","card","title","actions","when","read","talk","moreRow","count","all","unread","visible","shown","root","fresh","ordered","hidden","fetchKnownIssues","sessionToken","knownIssueHeaders","matchKnownIssues","q","signal","joinKnownIssue","reporterEmail","reporterName","metadata","logs","readJoinError","verifyFix","feedback","MAX_SCAN_CHARS","MAX_VALUE_CHARS","MAX_MATCHES","compile","source","tidy","valueOf","match","matchReferences","fields","texts","found","seen","block","claimed","overlaps","a","field","guard","hit","dedupe","validateReference","toEnd","atEdge","referenceFields","policy","POSITIONS","PRIORITIES","ICONS","BRAND","THEMES","THEME_KEY","readStoredTheme","writeStoredTheme","css","mountWidget","client","theme","themeValue","themeClass","style","themer","btn","launcher","openPanel","onClose","cfg","canCapture","canShoot","canRecord","priority","sending","scrim","headHtml","p","$","sel","titleInput","descInput","slot","thumbs","filePicker","sendBtn","callBtn","note","compose","whoami","refsBox","known","historyBtn","draftStatus","savedDraftKey","draftLoaded","draftSubmitted","draftWrites","draftTimer","persistDraft","kind","scheduleDraft","onPageHide","reconnectUploads","it","upload","draft","closed","addItem","previouslyFocused","rootStyle","previousOverflow","stopListening","destroy","onKeydown","close","stopRecording","focusable","first","last","active","say","escapeHtml","titleErr","flagTitle","refreshSendState","busy","max","drawThumb","preview","veil","removeItem","i","fill","normalized","withWidgetHidden","fn","r","shot","room","file","bar","keep","rec","clip","refConfig","refChoice","refValue","refError","refSettled","refWarned","referencesInText","answeredReference","drawRefs","f","optional","many","chip","needsReference","original","succeed","login","refreshLogin","showHistory","issues","issueError","askingFor","matchAbort","loadKnownIssues","rows","drawKnown","matchTimer","matchKnown","issue","knownMeta","state","others","relativeDay","start","confirm","result","callOffered","offerCall","mustSignIn","step","pendingEmail","pendingCode","signInBusy","signInError","applyGate","shut","drawWhoami","drawSignIn","focus","requestCode","confirmCode","resent","who","act","announce","handle","showCall","panel","ref","stateEl","dot","WORDING","OVER","offState","callFailed","showReport","ok","doneScreen","eta","failed","assistantTimer","openReportId","skeletonRows","reports","emptyHtml","statusWord","row","skeletonBubbles","report","drawn","threadHtml","thread","threadDrawn","scrollToLatest","mountVerification","mountComposer","mountAssistant","previous","steps","st","pct","content","from","next","pinned","atLatest","typing","typingHtml","refreshThread","box","html","pill","composer","staged","input","voiceBtn","liveBtn","picker","files","refresh","ready","stage","entry","stored","send","available","showLiveCall","quick","messageHtml","secs","muteBtn","hangBtn","ticker","stopTicking","settle","muted","timeline","entries","day","prev","d","dayKey","sep","dayLabel","systemHtml","speakerOf","cont","clockTime","mine","automated","attachmentHtml","avatar","initials","sub","back","icon","n","parts","iso","today","yesterday","smooth","calm","months","c","WIRE_RATE","createContext","rate","Ctor","resumeContext","context","downsample","fromRate","toRate","ratio","end","sum","j","MU_BIAS","MU_CLIP","encodeMuLaw","samples","clamped","sample","sign","exponent","mask","mantissa","CAPTURE_BLOCK","tapMicrophone","wireRate","capture","frames","mute","SUBPROTOCOL","FALLBACK_ICE","openMicrophone","liveCallSupported","joinLiveCall","realtimeUrl","ringsUntil","onState","onAudioBlocked","onMicrophone","emit","sayBlocked","blocked","sayMicrophone","live","microphone","socket","openSocket","stopStream","peer","remoteAudio","ringTimer","uplink","disarmGesture","unwatchMicrophone","starting","pending","shouldOffer","connectedOnce","reconnecting","recoverTimer","giveUpTimer","restarts","RECOVER_GRACE_MS","RECOVER_RETRY_MS","RECOVER_DEADLINE_MS","MAX_RESTARTS","finish","clearRecovery","stopUplink","startUplink","encoding","warn","tap","playRemote","markBlocked","markPlaying","armGesture","retry","events","build","iceServers","watchMicrophone","incoming","routeChanged","enterRecovery","delay","recover","restartIce","description","lost","offer","accept","answer","drainCandidates","candidate","handling","armRingTimer","remaining","RING","touched","evt","activatedByBrowser","startRinging","Ctx","stopped","voices","burst","hz","gain","frequency","osc","cycle","begin","secondsLeft","until","BUTTON","showIncomingCall","call","onAnswer","onDecline","onExpire","heading","about","countdown","decline","kept","stopRinging","tick","left","MAX_ID_CHARS","MAX_TEXT_CHARS","MAX_EXTRA_KEYS","MAX_NESTED_JSON","ID_FIELDS","CONTROL","idValue","v","describe","readId","src","canonical","names","extraValue","json","USER_TEXT","RESERVED_USER","normalizeUser","user","extras","mergeContext","patch","aliases","KNOWN_OPTIONS","CALLBACK_OPTIONS","suggest","lower","k","UUID","checkOptions","options","pageHost","errors","warnings","near","cleanApiBase","SDK_IDENTITY","DEFAULTS","BugCatchClient","__privateAdd","_BugCatchClient_instances","w","earlier","__privateMethod","drawUnread_fn","ring_fn","stopRinging_fn","update","onStorage","handler","set","resolvedAt","payload_fn","notePhase_fn","ringTheTeam_fn","shaped","references","media","refs","cause","failed_fn","submitted_fn","environment","callback_fn","assistant","phase","via","action","ctx","fileByCall_fn","filed","dismiss","answer_fn","answered","BugCatch"],"mappings":"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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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"}
1
+ {"version":3,"file":"bugcatch.umd.js","sources":["../src/redact.js","../src/collectors.js","../src/capture.js","../src/drafts.js","../src/session.js","../src/updates.js","../src/references.js","../src/agent-issues.js","../src/widget.js","../src/audio.js","../src/live-call.js","../src/incoming-call.js","../src/identity.js","../src/index.js"],"sourcesContent":["// Strips credentials out of captured URLs and error text, at the moment of\n// capture — before anything is buffered, and long before anything is sent.\n//\n// Request and response bodies are never read, so the URL is the whole attack\n// surface, and in practice it is a wide one: `?api_key=`, S3 presigned\n// signatures, OAuth fragments, JWTs sitting in a path segment. A presigned URL\n// is not a hint that a credential exists, it *is* the credential, and captured\n// network logs are shown on public share links and in the reporter portal.\n//\n// The goal is a redaction that leaves the log useful. `?token=[REDACTED]` still\n// tells a triager which endpoint was called with what shape of argument;\n// blanking the whole URL would make the Network tab worthless and push people\n// to turn capture off, which is the worse outcome.\n//\n// Twin implementations live in bugcatch-extension/src/collector.js (no build\n// step there, so it cannot import) and bugcatch-api/src/settings/redaction.util.ts\n// (defence in depth for older SDKs). tests/unit/redact-url.test.mjs runs one\n// corpus through all three and fails if they ever disagree.\n\nexport const PLACEHOLDER = '[REDACTED]';\n\n// Keys whose value is a credential whatever it looks like. Matched after\n// normalising, so `X-Amz-Security-Token` arrives here as `xamzsecuritytoken`.\nconst ALWAYS = new Set([\n 'auth', 'bearer', 'session', 'sid', 'phpsessid', 'otp',\n 'pass', 'passwd', 'pwd', 'sig', 'codeverifier',\n]);\n\n// Substrings that make a key sensitive wherever they appear, so app-specific\n// names like `x_auth_token` or `myAppApiKey` are covered without enumeration.\n// Deliberately excludes bare `auth` (matches `author`) and bare `key`\n// (matches `keyword`, `monkey`) — both are handled more precisely elsewhere.\nconst MARKERS = [\n 'token', 'secret', 'password', 'passwd', 'apikey', 'signature',\n 'credential', 'sessionid', 'privatekey', 'accesskey', 'authorization',\n];\n\n// Keys that are only sometimes a credential. `?key=AIza…` is a Google API key;\n// `?key=name` is a sort order. `?code=` is an OAuth code or an error string.\n// These redact only when the value itself looks like a secret, because\n// over-redacting the common case costs more triage value than it buys.\nconst AMBIGUOUS = new Set(['key', 'code', 'state', 'nonce', 'salt', 'hash']);\n\n// Recognisable credential formats, matched anywhere — query, path segment or\n// free text — so a secret still gets caught when it is not behind a named\n// parameter at all.\nconst SHAPES = [\n /eyJ[\\w-]{8,}\\.[\\w-]{8,}\\.[\\w-]+/g, // JWT\n /(?:AKIA|ASIA|AROA|AIDA|AGPA|ANPA|ANVA|APKA)[0-9A-Z]{16}/g, // AWS key id\n /AIza[\\w-]{35}/g, // Google API key\n /gh[pousr]_[A-Za-z0-9]{36,}/g, // GitHub token\n /github_pat_\\w{22,}/g, // GitHub fine-grained PAT\n /[sprk]k_(?:live|test)_[0-9A-Za-z]{10,}/g, // Stripe\n /xox[abposr]-[A-Za-z0-9-]{10,}/g, // Slack\n /sk-ant-[\\w-]{20,}/g, // Anthropic\n /sk-[\\w-]{20,}/g, // OpenAI\n /glpat-[\\w-]{20,}/g, // GitLab\n /shp(?:at|ss|ca|pa)_[A-Za-z0-9]{32}/g, // Shopify\n];\n\n// `Authorization: Bearer …` as it appears in thrown error text. The class is\n// written without a letter range because the `i` flag already covers case.\nconst BEARER = /\\bBearer\\s+[\\w.~+/=-]{8,}/gi;\n\n// Credentials in the authority, which fetch() honours: https://user:pass@host.\nconst USERINFO = /^([A-Za-z][A-Za-z0-9+.-]*:\\/\\/)([^/?#@]+)@/;\n\nfunction safeDecode(value) {\n try {\n return decodeURIComponent(value.replace(/\\+/g, ' '));\n } catch {\n return value; // a malformed escape is not a reason to give up on the rest\n }\n}\n\nfunction normalizeKey(rawKey) {\n return safeDecode(rawKey).toLowerCase().replace(/[^a-z0-9]/g, '');\n}\n\n/** Whether a value carried by an ambiguous key is credential-shaped. */\nfunction looksSecret(value) {\n if (value.length >= 20) return true;\n return SHAPES.some((re) => {\n re.lastIndex = 0;\n return re.test(value);\n });\n}\n\nfunction isSensitiveKey(rawKey, rawValue) {\n const key = normalizeKey(rawKey);\n if (!key) return false;\n if (ALWAYS.has(key)) return true;\n for (const marker of MARKERS) if (key.includes(marker)) return true;\n if (AMBIGUOUS.has(key)) return looksSecret(safeDecode(rawValue));\n return false;\n}\n\n/** Redact the values of sensitive keys in an `a=1&b=2` string. */\nfunction redactPairs(query) {\n return query\n .split('&')\n .map((pair) => {\n const eq = pair.indexOf('=');\n if (eq === -1) return pair;\n const key = pair.slice(0, eq);\n const value = pair.slice(eq + 1);\n if (!value) return pair;\n return isSensitiveKey(key, value) ? `${key}=${PLACEHOLDER}` : pair;\n })\n .join('&');\n}\n\n/** Replace every recognisable credential format in a string. */\nfunction redactShapes(text) {\n let out = text;\n for (const re of SHAPES) {\n re.lastIndex = 0;\n out = out.replace(re, PLACEHOLDER);\n }\n return out;\n}\n\n/**\n * Scrub a single URL. Structure is preserved — scheme, host, path and the\n * names of every parameter survive, only credential values are replaced.\n * Works on relative URLs too, which is why this parses by hand rather than\n * going through `new URL()`: that would also re-encode and reorder, so the\n * logged URL would stop matching what the app actually requested.\n */\nexport function redactUrl(raw) {\n if (raw === null || raw === undefined) return raw;\n let url = String(raw);\n if (!url) return url;\n\n url = url.replace(USERINFO, `$1${PLACEHOLDER}@`);\n\n const hashAt = url.indexOf('#');\n let head = hashAt === -1 ? url : url.slice(0, hashAt);\n let hash = hashAt === -1 ? '' : url.slice(hashAt);\n\n const queryAt = head.indexOf('?');\n if (queryAt !== -1) {\n head = head.slice(0, queryAt + 1) + redactPairs(head.slice(queryAt + 1));\n }\n\n if (hash.length > 1) {\n const body = hash.slice(1);\n const innerQuery = body.indexOf('?');\n if (innerQuery !== -1) {\n // Hash-routed app: #/reset?token=…\n hash = `#${body.slice(0, innerQuery + 1)}${redactPairs(body.slice(innerQuery + 1))}`;\n } else if (body.includes('=')) {\n // OAuth implicit flow drops the token here, where it never reaches a\n // server but is very much still a credential.\n hash = `#${redactPairs(body)}`;\n }\n }\n\n return redactShapes(head + hash);\n}\n\n/**\n * Scrub free text — a thrown error message, a console line. Any URL inside it\n * is scrubbed as a URL, then bare credentials and `Bearer …` headers go too.\n */\nexport function redactText(raw) {\n if (raw === null || raw === undefined) return raw;\n let text = String(raw);\n if (!text) return text;\n\n // The scheme is length-bounded so a long run of scheme-legal characters that\n // never reaches `://` cannot backtrack quadratically. The same source runs\n // server-side over attacker-influenced text, where that would be a DoS.\n text = text.replace(/[A-Za-z][A-Za-z0-9+.-]{0,30}:\\/\\/[^\\s\"'<>)\\]]+/g, (m) => redactUrl(m));\n text = text.replace(BEARER, `Bearer ${PLACEHOLDER}`);\n return redactShapes(text);\n}\n","// Ring-buffered capture of the things you wish you had when a bug comes in:\n// console output, failed/slow requests, unhandled errors, and the environment.\n//\n// Everything is bounded — a long-lived page must not grow memory without limit.\n\nimport { redactUrl, redactText } from './redact.js';\n\nexport function createRingBuffer(limit) {\n const items = [];\n return {\n push(item) {\n items.push(item);\n if (items.length > limit) items.splice(0, items.length - limit);\n },\n all: () => items.slice(),\n clear: () => { items.length = 0; },\n get size() { return items.length; },\n };\n}\n\nconst LEVELS = ['log', 'info', 'warn', 'error', 'debug'];\n\n/** Wraps console.* and records what goes through, then calls the original. */\nexport function installConsoleCapture(buffer, { levels = ['warn', 'error', 'log', 'info'] } = {}) {\n const originals = {};\n for (const level of LEVELS) {\n originals[level] = console[level];\n if (!levels.includes(level)) continue;\n console[level] = (...args) => {\n try {\n buffer.push({\n level,\n text: args.map(stringify).join(' ').slice(0, 2000),\n at: new Date().toISOString(),\n });\n } catch { /* capture must never break the page */ }\n originals[level].apply(console, args);\n };\n }\n return () => { for (const level of LEVELS) console[level] = originals[level]; };\n}\n\n/**\n * Records fetch + XHR: url, method, status, duration. Bodies are never read,\n * which leaves the URL as the only place a credential can arrive — so every\n * entry goes through redact.js on the way into the buffer.\n */\nexport function installNetworkCapture(buffer, { slowMs = 2000 } = {}) {\n const originalFetch = window.fetch;\n const originalOpen = XMLHttpRequest.prototype.open;\n const originalSend = XMLHttpRequest.prototype.send;\n\n if (originalFetch) {\n window.fetch = async function patchedFetch(...args) {\n const started = performance.now();\n const url = typeof args[0] === 'string' ? args[0] : args[0]?.url;\n const method = args[1]?.method || (typeof args[0] === 'object' ? args[0]?.method : 'GET') || 'GET';\n try {\n const res = await originalFetch.apply(this, args);\n record(buffer, { url, method, status: res.status, ms: Math.round(performance.now() - started), slowMs });\n return res;\n } catch (err) {\n record(buffer, { url, method, status: 0, error: String(err?.message || err), ms: Math.round(performance.now() - started), slowMs });\n throw err;\n }\n };\n }\n\n XMLHttpRequest.prototype.open = function patchedOpen(method, url, ...rest) {\n this.__bc = { method, url, started: 0 };\n return originalOpen.call(this, method, url, ...rest);\n };\n XMLHttpRequest.prototype.send = function patchedSend(...args) {\n if (this.__bc) {\n this.__bc.started = performance.now();\n this.addEventListener('loadend', () => {\n record(buffer, {\n url: this.__bc.url,\n method: this.__bc.method,\n status: this.status,\n ms: Math.round(performance.now() - this.__bc.started),\n slowMs,\n });\n });\n }\n return originalSend.apply(this, args);\n };\n\n return () => {\n if (originalFetch) window.fetch = originalFetch;\n XMLHttpRequest.prototype.open = originalOpen;\n XMLHttpRequest.prototype.send = originalSend;\n };\n}\n\nfunction record(buffer, { url, method, status, ms, error, slowMs }) {\n try {\n buffer.push({\n // Scrubbed before it is buffered, not before it is sent: a credential\n // that never enters the ring buffer cannot leave with a report, and\n // cannot be read back out of memory by the page either. Redact first,\n // then truncate — truncating first would split a token into something\n // no pattern recognises, and the fragment would still be a fragment of\n // a live credential.\n url: redactUrl(String(url)).slice(0, 500),\n method,\n status,\n ms,\n error: error === undefined ? undefined : redactText(error),\n // Flagged so triage can spot the interesting rows without reading everything.\n failed: status === 0 || status >= 400,\n slow: ms >= slowMs,\n at: new Date().toISOString(),\n });\n } catch { /* ignore */ }\n}\n\n/** Unhandled errors and rejections, appended to the console buffer. */\nexport function installErrorCapture(buffer) {\n const onError = (event) => {\n // Browsers already prefix event.message with \"Uncaught\" — don't say it twice.\n const message = String(event.message || 'Error');\n const where = event.filename ? ` (${event.filename}:${event.lineno}:${event.colno})` : '';\n buffer.push({\n level: 'error',\n text: (/^uncaught/i.test(message) ? message : `Uncaught ${message}`) + where,\n at: new Date().toISOString(),\n });\n };\n const onRejection = (event) => {\n buffer.push({\n level: 'error',\n text: `Unhandled rejection: ${stringify(event.reason)}`,\n at: new Date().toISOString(),\n });\n };\n window.addEventListener('error', onError);\n window.addEventListener('unhandledrejection', onRejection);\n return () => {\n window.removeEventListener('error', onError);\n window.removeEventListener('unhandledrejection', onRejection);\n };\n}\n\n/**\n * Battery needs the async Battery Status API, so environment collection is\n * async now. Unsupported browsers (Safari, Firefox) simply omit the fields.\n */\nexport async function collectEnvironment() {\n const base = collectEnvironmentSync();\n try {\n if (navigator.getBattery) {\n const b = await navigator.getBattery();\n base.battery = Math.round(b.level * 100);\n base.charging = b.charging;\n }\n } catch { /* not available — leave the fields out */ }\n return base;\n}\n\nexport function collectEnvironmentSync() {\n const nav = window.navigator || {};\n return {\n userAgent: nav.userAgent,\n platform: nav.platform,\n language: nav.language,\n languages: nav.languages ? [...nav.languages] : null,\n online: nav.onLine,\n cookieEnabled: nav.cookieEnabled,\n maxTouchPoints: nav.maxTouchPoints ?? null,\n viewport: { width: window.innerWidth, height: window.innerHeight },\n screen: {\n width: window.screen?.width,\n height: window.screen?.height,\n availWidth: window.screen?.availWidth,\n availHeight: window.screen?.availHeight,\n colorDepth: window.screen?.colorDepth,\n orientation: window.screen?.orientation?.type || null,\n },\n devicePixelRatio: window.devicePixelRatio,\n referrer: document.referrer || null,\n title: document.title,\n capturedAt: new Date().toISOString(),\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n timezoneOffsetMinutes: new Date().getTimezoneOffset(),\n // A hint, not a guarantee: `webdriver` is the only broadly-available signal\n // that something other than a person is driving the page.\n browserMode: nav.webdriver ? 'Automated' : 'Normal',\n doNotTrack: nav.doNotTrack || null,\n hardwareConcurrency: nav.hardwareConcurrency ?? null,\n deviceMemory: nav.deviceMemory ?? null,\n connection: nav.connection\n ? {\n effectiveType: nav.connection.effectiveType,\n downlink: nav.connection.downlink,\n rtt: nav.connection.rtt,\n saveData: nav.connection.saveData,\n }\n : null,\n prefersColorScheme: window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : 'light',\n prefersReducedMotion: window.matchMedia?.('(prefers-reduced-motion: reduce)').matches || false,\n };\n}\n\nfunction stringify(value) {\n if (typeof value === 'string') return value;\n if (value instanceof Error) return `${value.name}: ${value.message}`;\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n}\n","// Screenshots, screen recordings and file attachments.\n//\n// Capture always goes through getDisplayMedia, so the browser shows its own\n// picker and permission prompt — nothing leaves the page without the person\n// explicitly choosing what to share. There is no DOM-rasterising fallback on\n// purpose: it would need a heavy dependency and would still silently miss\n// cross-origin iframes, canvas and video, which is worse than not offering it.\n\n/**\n * Pristine references, taken at module-evaluation time — which happens on\n * import, before init() patches anything.\n *\n * Uploads must not be recorded by our own network capture: a report would\n * otherwise describe its own attachment upload, and a retry would append to a\n * buffer that is already being serialised.\n */\nconst nativeFetch = typeof window !== 'undefined' && window.fetch\n ? window.fetch.bind(window)\n : null;\nconst nativeXhrOpen = typeof XMLHttpRequest !== 'undefined'\n ? XMLHttpRequest.prototype.open\n : null;\nconst nativeXhrSend = typeof XMLHttpRequest !== 'undefined'\n ? XMLHttpRequest.prototype.send\n : null;\n\n/**\n * What the API will presign. Anything else is rejected before we ask.\n *\n * Mirrors UPLOAD_MIMES in the API's storage.service.ts. The two are checked\n * independently — this one to fail early with a message the person can act on,\n * that one because a browser is not a place to enforce anything.\n */\nexport const UPLOAD_MIMES = [\n 'image/png', 'image/jpeg', 'image/webp', 'image/gif', 'video/webm', 'video/mp4',\n 'audio/webm', 'audio/ogg', 'audio/mpeg', 'audio/mp4', 'audio/wav',\n 'application/pdf', 'text/plain', 'text/csv', 'application/json', 'application/zip',\n 'application/msword',\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n 'application/vnd.ms-excel',\n 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n];\n\n/**\n * What a *bug report* may carry, which is narrower than what may be uploaded.\n *\n * A report's attachments are evidence of the bug — what the screen looked like,\n * what the session did — and the API types them SCREENSHOT or VIDEO. A document\n * has no honest value there: filed as a SCREENSHOT it would render as a broken\n * image in the dashboard's media viewer.\n *\n * Documents and voice notes belong on a *message*, where the type is derived\n * from the mime and the reader gets a player or a download.\n */\nexport const REPORT_MIMES = [\n 'image/png', 'image/jpeg', 'image/webp', 'video/webm', 'video/mp4',\n];\n\nexport const voiceSupported = () =>\n Boolean(navigator?.mediaDevices?.getUserMedia && typeof MediaRecorder !== 'undefined');\n\nexport const captureSupported = () =>\n Boolean(navigator?.mediaDevices?.getDisplayMedia && typeof MediaRecorder !== 'undefined');\n\nexport const screenshotSupported = () =>\n Boolean(navigator?.mediaDevices?.getDisplayMedia);\n\n/**\n * MediaRecorder reports `video/webm;codecs=vp9`, but the presign signature\n * binds an exact Content-Type and the API only allows the base types. Strip\n * the codec parameter so the type we sign, the header we send and the type the\n * API stored all agree.\n */\nexport function normalizeMime(mime, allowed = UPLOAD_MIMES) {\n const base = String(mime || '').split(';')[0].trim().toLowerCase();\n return allowed.includes(base) ? base : null;\n}\n\n/** Grab a single frame of a chosen screen/window/tab as a PNG blob. */\nexport async function captureScreenshot() {\n const stream = await navigator.mediaDevices.getDisplayMedia({\n video: { frameRate: 1 },\n audio: false,\n });\n try {\n const track = stream.getVideoTracks()[0];\n const video = document.createElement('video');\n video.srcObject = stream;\n video.muted = true;\n video.playsInline = true;\n await video.play();\n\n // Wait one frame so the first paint isn't blank.\n await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));\n\n const { width, height } = track.getSettings();\n const canvas = document.createElement('canvas');\n canvas.width = width || video.videoWidth;\n canvas.height = height || video.videoHeight;\n canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);\n\n const blob = await new Promise((res) => canvas.toBlob(res, 'image/png'));\n if (!blob) throw new Error('The browser returned an empty screenshot.');\n return { blob, mime: 'image/png', kind: 'SCREENSHOT' };\n } finally {\n // Always release the capture, including on failure — otherwise the browser\n // keeps showing \"sharing your screen\".\n stream.getTracks().forEach((t) => t.stop());\n }\n}\n\n/**\n * Start recording a chosen screen/window/tab. Returns a handle with stop() and\n * cancel(); the caller decides which. Recording ends on its own if the user\n * clicks the browser's \"Stop sharing\" button or the cap is reached.\n */\nexport async function startRecording({ maxMs = 120_000, onTick, onAutoStop } = {}) {\n const stream = await navigator.mediaDevices.getDisplayMedia({\n video: { frameRate: 15 },\n audio: false,\n });\n\n const mime = ['video/webm;codecs=vp9', 'video/webm;codecs=vp8', 'video/webm']\n .find((t) => MediaRecorder.isTypeSupported(t)) || 'video/webm';\n\n const recorder = new MediaRecorder(stream, { mimeType: mime, videoBitsPerSecond: 2_500_000 });\n const chunks = [];\n recorder.ondataavailable = (e) => { if (e.data.size) chunks.push(e.data); };\n recorder.start(1000);\n\n const startedAt = Date.now();\n const timer = setInterval(() => onTick?.(Math.floor((Date.now() - startedAt) / 1000)), 500);\n\n const cleanup = () => {\n clearInterval(timer);\n clearTimeout(capTimer);\n stream.getTracks().forEach((t) => t.stop());\n };\n\n const finished = new Promise((resolve) => {\n recorder.onstop = () => {\n cleanup();\n resolve(new Blob(chunks, { type: mime }));\n };\n });\n\n const endEarly = () => {\n if (recorder.state !== 'inactive') recorder.stop();\n onAutoStop?.();\n };\n\n // The user can stop sharing from the browser's own UI; treat that as \"done\".\n stream.getVideoTracks()[0].addEventListener('ended', endEarly);\n\n // Hard cap so a forgotten recording can't grow without bound.\n const capTimer = setTimeout(endEarly, maxMs);\n\n return {\n mime,\n maxMs,\n async stop() {\n clearTimeout(capTimer);\n if (recorder.state !== 'inactive') recorder.stop();\n return { blob: await finished, mime, kind: 'VIDEO' };\n },\n cancel() {\n cleanup();\n if (recorder.state !== 'inactive') recorder.stop();\n },\n };\n}\n\n/**\n * Record a voice note from the microphone. Returns a handle with stop() and\n * cancel(), like startRecording() above.\n *\n * Audio only, and from getUserMedia rather than getDisplayMedia: this is\n * somebody talking into their laptop to explain a bug, not a capture of the\n * screen. The browser shows its own microphone prompt, so nothing is recorded\n * without an explicit grant.\n *\n * The container is whichever the browser will give us — Chromium and Firefox\n * produce webm/opus, Safari mp4/aac — because MediaRecorder cannot be made to\n * agree on one, and the API allows both.\n */\nexport async function startVoiceNote({ maxMs = 120_000, onTick, onAutoStop } = {}) {\n const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n\n const mime = ['audio/webm;codecs=opus', 'audio/webm', 'audio/mp4', 'audio/ogg']\n .find((t) => MediaRecorder.isTypeSupported(t)) || '';\n\n const recorder = new MediaRecorder(stream, mime ? { mimeType: mime } : undefined);\n const chunks = [];\n recorder.ondataavailable = (e) => { if (e.data.size) chunks.push(e.data); };\n recorder.start(1000);\n\n const startedAt = Date.now();\n const timer = setInterval(() => onTick?.(Math.floor((Date.now() - startedAt) / 1000)), 500);\n\n const cleanup = () => {\n clearInterval(timer);\n clearTimeout(capTimer);\n stream.getTracks().forEach((t) => t.stop());\n };\n\n const finished = new Promise((resolve) => {\n recorder.onstop = () => {\n cleanup();\n resolve(new Blob(chunks, { type: recorder.mimeType || mime }));\n };\n });\n\n const endEarly = () => {\n if (recorder.state !== 'inactive') recorder.stop();\n onAutoStop?.();\n };\n\n // A hard cap, as for screen recording: a forgotten open microphone is worse\n // than a truncated note.\n const capTimer = setTimeout(endEarly, maxMs);\n\n return {\n mime: recorder.mimeType || mime,\n maxMs,\n async stop() {\n clearTimeout(capTimer);\n if (recorder.state !== 'inactive') recorder.stop();\n const blob = await finished;\n return { blob, mime: blob.type || recorder.mimeType || mime, kind: 'AUDIO' };\n },\n cancel() {\n cleanup();\n if (recorder.state !== 'inactive') recorder.stop();\n },\n };\n}\n\n/**\n * Ask the API for a presigned URL and PUT the blob straight at it, so media\n * never passes through the API request path.\n *\n * Resolves to the storage key, which is what a report references.\n */\nexport async function uploadAttachment({ apiBase, apiKey, blob, mime, onProgress }) {\n const type = normalizeMime(mime || blob.type);\n if (!type) {\n throw new Error(`[BugCatch] ${mime || blob.type || 'that file'} can't be attached.`);\n }\n\n const res = await nativeFetch(`${apiBase}/ingest/presign`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', 'X-BugCatch-Key': apiKey },\n body: JSON.stringify({ mime: type }),\n });\n if (!res.ok) throw new Error(`[BugCatch] could not start the upload (HTTP ${res.status}).`);\n const { key, uploadUrl } = await res.json();\n\n await put(uploadUrl, blob, type, onProgress);\n return { storageKey: key, mime: type };\n}\n\n/**\n * XHR rather than fetch purely for upload progress — a recording is megabytes,\n * and a progress bar is the difference between \"working\" and \"frozen\".\n */\nfunction put(uploadUrl, blob, mime, onProgress) {\n return new Promise((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n nativeXhrOpen.call(xhr, 'PUT', uploadUrl, true);\n xhr.setRequestHeader('Content-Type', mime);\n xhr.upload.onprogress = (e) => {\n if (e.lengthComputable) onProgress?.(e.loaded / e.total);\n };\n xhr.onload = () => {\n if (xhr.status >= 200 && xhr.status < 300) resolve();\n else reject(new Error(`[BugCatch] upload failed (HTTP ${xhr.status}).`));\n };\n // The PUT goes to the bucket, not to the API, so a missing bucket CORS\n // policy arrives here as a bare network error with no status. Reporting it\n // as \"network error\" sends people to read API logs that are perfectly clean.\n xhr.onerror = () => reject(new Error(\n '[BugCatch] the browser blocked the upload to storage. If this is a new '\n + 'environment, the media bucket is probably missing its CORS policy.',\n ));\n xhr.onabort = () => reject(new Error('[BugCatch] upload cancelled.'));\n nativeXhrSend.call(xhr, blob);\n });\n}\n\nexport function humanSize(bytes) {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;\n return `${(bytes / 1024 / 1024).toFixed(1)} MB`;\n}\n\nexport function humanDuration(seconds) {\n const m = Math.floor(seconds / 60);\n const s = seconds % 60;\n return `${m}:${String(s).padStart(2, '0')}`;\n}\n","// Drafts are scoped to the API, project and reporter. Never store credentials.\nconst MAX_AGE = 7 * 24 * 60 * 60 * 1000;\nlet database;\nfunction open() {\n if (!database) database = new Promise((resolve, reject) => {\n const request = indexedDB.open('bugcatch-drafts', 1);\n request.onupgradeneeded = () => request.result.createObjectStore('drafts');\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n request.onblocked = () => reject(new Error('Draft storage is blocked.'));\n }).catch((err) => { database = null; throw err; });\n return database;\n}\nexport function draftKey(config, email) {\n return JSON.stringify([config.apiBase, config.projectId, email || config.user?.email || config.user?.id || 'anonymous']);\n}\nexport async function draftOperation(key, value) {\n const db = await open();\n return new Promise((resolve, reject) => {\n const tx = db.transaction('drafts', value === undefined ? 'readonly' : 'readwrite');\n const store = tx.objectStore('drafts');\n const request = value === undefined ? store.get(key) : value === null ? store.delete(key) : store.put({ ...value, savedAt: Date.now() }, key);\n tx.oncomplete = () => resolve(request.result);\n tx.onerror = () => reject(tx.error);\n tx.onabort = () => reject(tx.error || new Error('Draft was not saved.'));\n });\n}\nexport async function readDraft(key) {\n const value = await draftOperation(key);\n if (value && Date.now() - value.savedAt > MAX_AGE) { await draftOperation(key, null); return null; }\n return value || null;\n}\n","// Signing in to a project's widget.\n//\n// Two different things call themselves \"who is reporting this\" in this SDK, and\n// they are not the same claim:\n//\n// identify() — the host application telling us about its own signed-in user.\n// Free, silent, and worth exactly as much as the application\n// saying it. Nothing here challenges it.\n//\n// signIn() — the person in front of the browser answering a code we mailed\n// to the address they typed. Costs them a round trip to their\n// inbox, and is the only one that survives a project deciding\n// it wants a real address on every report.\n//\n// Which of the two a project asks for is the project's own setting, read from\n// the API rather than configured here — see loginState(). A widget that guessed\n// would either hide a mandatory sign-in or put one in front of a project that\n// never asked for it.\n//\n// The session is stored per project, because that is the boundary the server\n// draws too: one live session per address per project. A browser on a page that\n// mounts two projects' widgets is two independent sign-ins, and signing out of\n// one leaves the other alone.\n\nconst KEY_PREFIX = 'bugcatch.session.';\n\nconst storageKey = (projectId) => `${KEY_PREFIX}${projectId}`;\n\n/**\n * The same sessions, held for the life of the page.\n *\n * Not a cache — a fallback. localStorage is unavailable outright in some\n * privacy modes and on some embedded webviews, and without this a project that\n * requires sign-in would be unusable there: the code would be accepted and the\n * session immediately forgotten, so the report that follows is refused. Held\n * here, signing in still works for as long as the tab is open; all that is lost\n * is surviving a reload, which is the part storage was buying.\n */\nconst inMemory = new Map();\n\n/**\n * The session for one project, or null.\n *\n * Storage can throw outright in private modes, and can hold something another\n * version of this SDK wrote — both come back as \"not signed in\" rather than an\n * exception in the host application's page.\n */\nexport function readSession(projectId) {\n if (!projectId) return null;\n try {\n const raw = window.localStorage?.getItem(storageKey(projectId));\n if (raw) {\n const parsed = JSON.parse(raw);\n if (parsed?.token) return parsed;\n }\n } catch { /* storage unavailable, or holding something we did not write */ }\n return inMemory.get(projectId) || null;\n}\n\nexport function writeSession(projectId, session) {\n if (!projectId || !session?.token) return;\n const held = {\n token: session.token,\n email: session.email || null,\n name: session.name || null,\n };\n inMemory.set(projectId, held);\n try {\n window.localStorage?.setItem(storageKey(projectId), JSON.stringify(held));\n } catch { /* the session just won't survive a reload */ }\n}\n\nexport function clearSession(projectId) {\n if (!projectId) return;\n inMemory.delete(projectId);\n try {\n window.localStorage?.removeItem(storageKey(projectId));\n } catch { /* ignore */ }\n}\n\n/** The headers every call here sends: the publishable key, plus who we are. */\nfunction headers({ apiKey, token }) {\n return {\n 'Content-Type': 'application/json',\n 'X-BugCatch-Key': apiKey,\n // Its own header rather than Authorization, which on these routes already\n // carries the API key. The two answer different questions and a request\n // carries both.\n ...(token ? { 'X-BugCatch-Session': token } : {}),\n };\n}\n\nasync function readError(res) {\n try {\n const body = await res.json();\n const message = Array.isArray(body.message) ? body.message.join(', ') : body.message;\n if (message) return message;\n } catch { /* non-JSON error */ }\n return `Request failed (HTTP ${res.status})`;\n}\n\n/**\n * What this project asks of the reporter, and who this browser is signed in as.\n *\n * { mode: 'off' | 'optional' | 'required',\n * signedIn: { email, name } | null,\n * signedOutReason: string | null }\n *\n * Asked each time the panel opens rather than cached for the page: the session\n * it reports on can end elsewhere — the same address signing in on a phone ends\n * the one on this laptop — and a widget holding a cached \"signed in as you\"\n * would only find out at the moment the report failed.\n *\n * Never throws. A deployment that has not been updated answers 404 here, and an\n * SDK that treated that as fatal would take the whole widget down over a\n * feature the project had not switched on; the answer is \"off\", which is what\n * every project looked like before this existed.\n */\nexport async function loginState({ apiBase, apiKey, projectId, token }) {\n const off = { mode: 'off', signedIn: null, signedOutReason: null };\n try {\n const res = await fetch(\n `${apiBase}/ingest/login?projectId=${encodeURIComponent(projectId)}`,\n { headers: headers({ apiKey, token }) },\n );\n if (!res.ok) return off;\n const body = await res.json();\n return {\n mode: body.mode === 'optional' || body.mode === 'required' ? body.mode : 'off',\n signedIn: body.signedIn || null,\n signedOutReason: body.signedOutReason || null,\n };\n } catch {\n return off;\n }\n}\n\n/** Step one: ask for a code. Throws with the server's sentence on refusal. */\nexport async function startSignIn({ apiBase, apiKey, projectId, email, name }) {\n const res = await fetch(`${apiBase}/ingest/login/start`, {\n method: 'POST',\n headers: headers({ apiKey }),\n body: JSON.stringify({ projectId, email, ...(name ? { name } : {}) }),\n });\n if (!res.ok) throw new Error(await readError(res));\n return res.json();\n}\n\n/**\n * Step two: spend the code.\n *\n * Stores the session on success, which is what makes it survive a reload and\n * what every other call here reads. Signing in ends this address's session on\n * this project everywhere else — that is the server's rule, not something this\n * browser can opt out of.\n */\nexport async function confirmSignIn({ apiBase, apiKey, projectId, email, code }) {\n const res = await fetch(`${apiBase}/ingest/login`, {\n method: 'POST',\n headers: headers({ apiKey }),\n body: JSON.stringify({ projectId, email, code }),\n });\n if (!res.ok) throw new Error(await readError(res));\n\n const body = await res.json();\n const session = { token: body.sessionToken, email: body.email, name: body.name };\n writeSession(projectId, session);\n return session;\n}\n\n/**\n * Sign out, here and on the server.\n *\n * Local storage is cleared whatever the network says. Somebody pressing sign\n * out on a shared machine has to end up signed out on it, and a fetch that\n * failed is not a reason to leave their address in the next person's widget.\n */\nexport async function endSignIn({ apiBase, apiKey, projectId, token }) {\n clearSession(projectId);\n if (!token) return { signedOut: true };\n try {\n await fetch(`${apiBase}/ingest/logout`, {\n method: 'POST',\n headers: headers({ apiKey, token }),\n body: JSON.stringify({ projectId }),\n });\n } catch { /* the token is already unusable from this browser */ }\n return { signedOut: true };\n}\n","// Live status changes on the reports this browser has filed.\n//\n// The person who reported a bug is the one person a tracker usually forgets to\n// tell. They are already in your app, so this is where they hear about it —\n// not in an email they may not read, and not on a page they have to remember\n// to visit.\n//\n// Authentication is the token handed back when the report was filed, kept in\n// localStorage. Deliberately not the API key: that is publishable and sits in\n// your page source, so a key-authenticated feed keyed on an email address would\n// let anyone read anyone's reports. Holding this token is proof of having\n// filed them.\n\nconst TOKEN_KEY = 'bugcatch.watch';\nconst BACKOFF_MS = [2000, 5000, 15000, 30000, 60000];\n\n/**\n * How long a stream has to survive before it counts as having worked.\n *\n * The backoff above is only a backoff if \"it failed\" and \"it worked\" are told\n * apart by more than the response arriving, and they were not: the counter\n * reset the moment the headers landed. A server that accepts the connection and\n * ends it immediately therefore reset the backoff every time, so the delay\n * never left its first rung and the retry became a two-second loop that no\n * amount of failing could slow down.\n *\n * That is not hypothetical here. An expired watch token used to be answered\n * with a 200 whose body closed at once — the status could not be set, because\n * an SSE response has already begun by the time the token is opened — and this\n * client only treats a 401 as fatal. Prod on 2026-08-31: 6,001 of 6,012\n * requests to the stream aborted under 10ms, ~50/min, for hours. The server\n * sends a real 401 now (see WatchTokenGuard in the API), and this is the other\n * half: a stream that ends the instant it opens must cost a longer wait than\n * the one before it, whatever the reason.\n *\n * Comfortably over the connect-and-die case and well under the 25s server\n * heartbeat, so a stream genuinely carrying traffic always qualifies.\n */\nconst STABLE_MS = 5000;\n\n/**\n * The project this page's widget reports to. Set once by init().\n *\n * The token used to live under one key for the whole origin, and the server\n * answered it with everything its reporter had filed across the workspace — so\n * \"Your reports\" on project A listed project B's bugs, and two projects on one\n * origin overwrote each other's token. It is now kept per project, and sent\n * with the project so the server can narrow tokens minted before that.\n */\nlet project = null;\n\nexport function scopeReports(projectId) {\n project = projectId || null;\n}\n\nconst tokenKey = () => (project ? `${TOKEN_KEY}:${project}` : TOKEN_KEY);\n\n/** `?projectId=` for the watch routes that list or open reports. */\nexport function projectQuery() {\n return project ? `?projectId=${encodeURIComponent(project)}` : '';\n}\n\n/**\n * Where the watch token lives. Storage can throw in private modes — never fatal.\n *\n * Falls back to the unscoped key an older SDK wrote, so an upgrade does not\n * lose someone's history. That token may belong to another project on this\n * origin; the `projectId` sent with every read is what keeps it to this one.\n */\nexport function readToken() {\n try {\n const store = window.localStorage;\n return store?.getItem(tokenKey()) || store?.getItem(TOKEN_KEY) || null;\n } catch {\n return null;\n }\n}\n\nexport function writeToken(token) {\n if (!token) return;\n try {\n window.localStorage?.setItem(tokenKey(), token);\n } catch { /* storage unavailable — updates just won't survive a reload */ }\n}\n\nexport function clearToken() {\n try {\n window.localStorage?.removeItem(tokenKey());\n window.localStorage?.removeItem(TOKEN_KEY);\n } catch { /* ignore */ }\n // Anything still waiting to be read goes with it. Unread updates are about\n // reports this browser can no longer prove it filed — and on the sign-out\n // path, about somebody who has left a machine the next person will use.\n forgetAllUpdates();\n}\n\n/* ---------------------------------------------------------------------------\n * Unread updates.\n *\n * An update used to be shown for eight seconds and then dropped on the floor.\n * Somebody switching tabs, reading something else on the page, or away from\n * the keyboard when their bug was fixed simply never found out — the one\n * notification they were waiting for was the one most likely to be missed.\n *\n * So an update is now held until it has been read, and \"read\" is something the\n * reporter says rather than something a timer decides. The store below is what\n * remembers that across a reload: the stream replays nothing on reconnect, so\n * an unread notification that lived only in a variable would not survive the\n * reporter pressing refresh.\n *\n * Kept beside the watch token, and dropped with it — a browser that can no\n * longer prove it filed anything has no reports to be told about.\n * ------------------------------------------------------------------------ */\n\nexport const UNREAD_KEY = 'bugcatch.unread';\n// Enough that a fortnight away does not lose anything, small enough that the\n// store stays a notification list rather than a history. The full record of\n// what happened lives on the report itself.\nconst UNREAD_LIMIT = 20;\n\n/**\n * A stable id for an update, so the same one is not shown twice and so a host\n * app can say which one it has read.\n *\n * Built from the frame rather than generated, because it has to survive a\n * reload: the server sends no id of its own, and a random one would make every\n * restored notification a new notification.\n */\nexport function updateId(event) {\n return [event?.bugId || 'report', event?.kind || 'status', event?.at || ''].join('|');\n}\n\n/** Unread updates, oldest first. Never throws — a corrupt store reads as empty. */\nexport function readUnread() {\n try {\n const raw = window.localStorage?.getItem(UNREAD_KEY);\n const list = raw ? JSON.parse(raw) : [];\n return Array.isArray(list) ? list.filter((u) => u && typeof u.id === 'string') : [];\n } catch {\n return [];\n }\n}\n\nfunction writeUnread(list) {\n try {\n if (list.length) window.localStorage?.setItem(UNREAD_KEY, JSON.stringify(list));\n else window.localStorage?.removeItem(UNREAD_KEY);\n } catch { /* storage unavailable — unread then lasts only for this page */ }\n}\n\n/**\n * Hold an update until it is read. Returns the stored record, which is the\n * frame the API sent plus the id everything else refers to it by.\n *\n * Re-delivery of one already held replaces it in place rather than stacking a\n * second copy: a reconnect that repeats a frame must not double the badge.\n */\nexport function rememberUpdate(event) {\n const record = { ...event, id: updateId(event) };\n const list = readUnread().filter((u) => u.id !== record.id);\n list.push(record);\n writeUnread(list.slice(-UNREAD_LIMIT));\n return record;\n}\n\n/** Read. Returns what is still unread. */\nexport function forgetUpdate(id) {\n const list = readUnread().filter((u) => u.id !== id);\n writeUnread(list);\n return list;\n}\n\n/**\n * Everything about one report, read.\n *\n * Opening the report *is* reading its updates — the widget calls this when the\n * thread opens — and leaving a \"your report was fixed\" card up over the screen\n * where they are reading that it was fixed would be the notification arguing\n * with itself.\n */\nexport function forgetReportUpdates(bugId) {\n const list = readUnread().filter((u) => u.bugId !== bugId);\n writeUnread(list);\n return list;\n}\n\n/** All read. */\nexport function forgetAllUpdates() {\n writeUnread([]);\n return [];\n}\n\n/**\n * Pull complete SSE frames out of a growing buffer, returning the partial one\n * left over. Frames are separated by a blank line and one can span several\n * `data:` lines, so this cannot split on newlines — a payload cut in half by\n * the network would parse as garbage.\n */\nfunction drain(buffer, onFrame) {\n let rest = buffer;\n let split = rest.indexOf('\\n\\n');\n while (split !== -1) {\n const frame = rest.slice(0, split);\n rest = rest.slice(split + 2);\n\n const data = frame\n .split('\\n')\n .filter((line) => line.startsWith('data:'))\n .map((line) => line.slice(5).trim())\n .join('\\n');\n\n if (data) {\n try {\n onFrame(JSON.parse(data));\n } catch { /* a malformed frame is skipped, never fatal to the stream */ }\n }\n split = rest.indexOf('\\n\\n');\n }\n return rest;\n}\n\n/**\n * Everything this browser has reported, with where each one got to.\n *\n * Same credential as the stream: the token handed back when a report was\n * filed. Returns an empty history rather than throwing when nothing has been\n * filed here — \"you have not reported anything\" is an answer, not an error.\n */\nexport async function fetchMyReports({ apiBase }) {\n const token = readToken();\n if (!token) return { reporter: null, reports: [] };\n\n const res = await fetch(`${apiBase}/ingest/watch/reports${projectQuery()}`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n // Expired or revoked. Drop it so a later report can replace it, and answer\n // as though nothing had been filed — which, as far as this browser can now\n // prove, is true.\n if (res.status === 401) {\n clearToken();\n return { reporter: null, reports: [] };\n }\n if (!res.ok) throw new Error(`[BugCatch] could not load your reports (HTTP ${res.status})`);\n return res.json();\n}\n\n/**\n * One report in full, including how it got to where it is.\n *\n * The list endpoint answers \"what have I reported and where is each one\"; this\n * one answers \"what has actually happened to this\", which is the question\n * somebody opening a report is asking. Same credential as the rest.\n */\nexport async function fetchReport({ apiBase, bugId }) {\n const token = readToken();\n if (!token) return null;\n\n const res = await fetch(`${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}${projectQuery()}`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (res.status === 401) {\n clearToken();\n return null;\n }\n if (res.status === 404) return null;\n if (!res.ok) throw new Error(`[BugCatch] could not load that report (HTTP ${res.status})`);\n return res.json();\n}\n\n/**\n * Write back on a report this browser filed.\n *\n * The other half of the feed. Everything above tells the reporter what happened\n * to their report; this is how they answer — \"still broken on Safari\", \"here is\n * the file that breaks it\" — without leaving the app they hit the bug in.\n *\n * Same credential as the rest: the watch token, which is proof of having filed\n * this. Resolves to the stored message, so the caller can render exactly what\n * the server kept rather than what it hoped it sent.\n */\nexport async function sendMessage({ apiBase, bugId, body, attachments }) {\n const token = readToken();\n if (!token) throw new Error('[BugCatch] nothing filed from this browser yet.');\n\n const res = await fetch(\n `${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}/messages`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },\n body: JSON.stringify({\n ...(body ? { body } : {}),\n ...(attachments?.length ? { attachments } : {}),\n }),\n },\n );\n\n if (res.status === 401) {\n clearToken();\n throw new Error('[BugCatch] this browser can no longer prove it filed that report.');\n }\n if (!res.ok) {\n // The API's own message is worth surfacing — \"Unsupported file type\" and\n // \"Write something, or attach a file\" are both things the sender can fix,\n // and an HTTP code is not.\n let detail = '';\n try {\n const problem = await res.json();\n detail = Array.isArray(problem.message) ? problem.message.join(', ') : problem.message || '';\n } catch { /* non-JSON error */ }\n throw new Error(detail || `[BugCatch] could not send that (HTTP ${res.status}).`);\n }\n return res.json();\n}\n\n/**\n * Whether this project can take a call instead of a form.\n *\n * Asked before anything has been filed, so this one is authenticated by the\n * publishable API key rather than by a watch token — there is no report yet to\n * hold a token for. It answers a fact about the project's configuration and\n * nothing about any report, which is all a publishable key should ever be able\n * to learn.\n */\nexport async function callAvailableForProject({ apiBase, apiKey, projectId }) {\n return Boolean(await callKindForProject({ apiBase, apiKey, projectId }));\n}\n\n/**\n * Which kind of call this project can take: 'live', or null for none.\n *\n * The same round trip as above, answering the question that actually matters.\n * Asking only \"can it call\" is what drew a call button on a deployment that did\n * not have one: pressing it filed the report and then failed the call, so the\n * reporter got a ticket they had not asked for and nobody's dashboard rang.\n *\n * There is one kind of call now — the team's dashboards ring and a colleague\n * picks up — so this has one non-null answer. The shape is kept because callers\n * are written against it and because a deployment answering something else is a\n * deployment this SDK must not place a call on.\n */\nexport async function callKindForProject({ apiBase, apiKey, projectId }) {\n const kinds = await callKindsForProject({ apiBase, apiKey, projectId });\n return kinds.live ? 'live' : null;\n}\n\n/**\n * Which kinds of call this project can take, as booleans.\n *\n * `agent` is always false and is still here because the API still sends it: it\n * was a triage agent that answered instantly instead of a person, and it is\n * gone. Reading it rather than dropping it keeps this honest about what the\n * response actually said.\n *\n * An older API answers `available` and a bare `kind` with no `kinds` object.\n * Anything that is not 'live' there is a kind this SDK can no longer place, so\n * it reads as no call at all rather than as a call that would fail on connect.\n */\nexport async function callKindsForProject({ apiBase, apiKey, projectId }) {\n const none = { agent: false, live: false };\n try {\n const res = await fetch(\n `${apiBase}/ingest/calls?projectId=${encodeURIComponent(projectId)}`,\n { headers: { 'X-BugCatch-Key': apiKey } },\n );\n if (!res.ok) return none;\n const body = await res.json();\n if (!body?.available) return none;\n if (body.kinds && typeof body.kinds === 'object') {\n return { agent: false, live: Boolean(body.kinds.live) };\n }\n return { agent: false, live: body.kind === 'live' };\n } catch {\n return none;\n }\n}\n\n/**\n * File a report by calling about it, and get back the socket to talk on.\n *\n * The report is written before the call starts rather than after it ends, so\n * whatever this page was doing survives a call that drops or a reporter who\n * changes their mind. `realtime_url` is null when the report was filed but no\n * call could be placed — which is a success for the report and a failure only\n * for the call, and the caller is expected to tell them apart.\n */\nexport async function reportByCall({ apiBase, apiKey, token, payload }) {\n const res = await fetch(`${apiBase}/ingest/calls`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-BugCatch-Key': apiKey,\n // The same header `report()` sends, and for the same reason: this route\n // files a report too. Without it a project that requires sign-in refuses\n // the call outright — and the reporter is told to sign in by a panel that\n // is saying, an inch below the message, who they are signed in as.\n ...(token ? { 'X-BugCatch-Session': token } : {}),\n },\n body: JSON.stringify(payload),\n });\n\n if (!res.ok) {\n let detail = '';\n try {\n const problem = await res.json();\n detail = Array.isArray(problem.message) ? problem.message.join(', ') : problem.message || '';\n } catch { /* non-JSON error */ }\n const err = new Error(\n detail || `[BugCatch] the call could not be started (HTTP ${res.status}).`,\n );\n // Carried so a session the server has stopped recognising can be told from\n // every other refusal and forgotten, rather than pressing Call into the\n // same 401 again.\n err.status = res.status;\n throw err;\n }\n return res.json();\n}\n\n/**\n * Whether this report can be talked through with a person on the team.\n *\n * Asked before anything is rendered, so a button never appears on a deployment\n * with no sphoro.voice configured, on a workspace that has not switched calling\n * on, or on a report that has already had its allowance. False for every\n * failure too: a call is an extra, and an extra that cannot be offered quietly\n * is better than an error about a feature nobody asked for.\n */\nexport async function liveCallAvailable({ apiBase, bugId }) {\n const token = readToken();\n if (!token) return false;\n\n try {\n const res = await fetch(\n `${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}/live-call`,\n { headers: { Authorization: `Bearer ${token}` } },\n );\n if (!res.ok) return false;\n const body = await res.json();\n return Boolean(body?.offered);\n } catch {\n return false;\n }\n}\n\n/**\n * Ring the team, and get back the socket to wait on.\n *\n * Carries `rings_until` as well as the socket, because the browser is what\n * decides the ringing is over — it is the one client guaranteed to still be\n * listening at that moment, and a server-side timer would have to guess whether\n * anybody was still there to hear it stop.\n */\nexport async function requestLiveCall({ apiBase, bugId }) {\n const token = readToken();\n if (!token) throw new Error('[BugCatch] nothing filed from this browser yet.');\n\n const res = await fetch(\n `${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}/live-call`,\n { method: 'POST', headers: { Authorization: `Bearer ${token}` } },\n );\n\n if (res.status === 401) {\n clearToken();\n throw new Error('[BugCatch] this browser can no longer prove it filed that report.');\n }\n if (!res.ok) {\n // The API's own message is the one worth showing: \"there is already a call\n // in progress about this report\" is something the reader can act on, and a\n // status code is not.\n let detail = '';\n try {\n const problem = await res.json();\n detail = Array.isArray(problem.message) ? problem.message.join(', ') : problem.message || '';\n } catch { /* non-JSON error */ }\n throw new Error(detail || `[BugCatch] the call could not be started (HTTP ${res.status}).`);\n }\n return res.json();\n}\n\n/**\n * Say that nobody picked up.\n *\n * Best-effort and deliberately unreported: the reporter's call is over either\n * way, and the only thing this changes is what the team's thread says about it\n * afterwards. sphoro.voice refuses it while the call is genuinely still\n * ringing, so a browser with a fast clock cannot hang up on somebody halfway\n * through answering.\n */\nexport async function giveUpLiveCall({ apiBase, bugId, callId }) {\n const token = readToken();\n if (!token) return;\n\n try {\n await fetch(\n `${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}`\n + `/live-call/${encodeURIComponent(callId)}/give-up`,\n { method: 'POST', headers: { Authorization: `Bearer ${token}` } },\n );\n } catch { /* the call is over regardless */ }\n}\n\n/**\n * Take a call the team placed, and get back the socket to hold it on.\n *\n * The other direction, and the only call this SDK does not start. A member\n * pressed a button on the report and the update stream put a card on screen;\n * this is what happens when the person reading it presses Answer, and nothing\n * connects — and no microphone opens — until they do.\n *\n * 409 is an ordinary outcome and says so plainly: the ringing stopped while the\n * button was being pressed, or this is a second tab losing to the one the\n * person actually answered in.\n */\nexport async function answerIncomingCall({ apiBase, bugId, callId }) {\n const token = readToken();\n if (!token) throw new Error('[BugCatch] nothing filed from this browser yet.');\n\n const res = await fetch(\n `${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}`\n + `/live-call/${encodeURIComponent(callId)}/answer`,\n { method: 'POST', headers: { Authorization: `Bearer ${token}` } },\n );\n\n if (res.status === 401) {\n clearToken();\n throw new Error('[BugCatch] this browser can no longer prove it filed that report.');\n }\n if (res.status === 409) {\n throw new Error('[BugCatch] that call is no longer ringing.');\n }\n if (!res.ok) {\n let detail = '';\n try {\n const problem = await res.json();\n detail = Array.isArray(problem.message) ? problem.message.join(', ') : problem.message || '';\n } catch { /* non-JSON error */ }\n throw new Error(detail || `[BugCatch] the call could not be answered (HTTP ${res.status}).`);\n }\n return res.json();\n}\n\n/**\n * Or don't.\n *\n * Sent rather than left to time out, because the two are different facts on the\n * report: somebody who was not at the screen invites being rung again in ten\n * minutes and somebody who declined does not. Best-effort and unreported — the\n * card is going away either way, and there is nothing the reader could do about\n * a failure here.\n */\nexport async function declineIncomingCall({ apiBase, bugId, callId }) {\n const token = readToken();\n if (!token) return;\n\n try {\n await fetch(\n `${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}`\n + `/live-call/${encodeURIComponent(callId)}/decline`,\n { method: 'POST', headers: { Authorization: `Bearer ${token}` } },\n );\n } catch { /* they have declined; nothing here can change that */ }\n}\n\n/**\n * Watch for updates until stopped. Returns a stop function.\n *\n * Reconnects with backoff, which starts slower than the dashboard's: this runs\n * on someone else's site, and a tight retry loop against their network is not\n * ours to spend.\n */\nexport function watchUpdates({ apiBase, onUpdate }) {\n let running = true;\n let attempt = 0;\n let controller = null;\n\n async function connect() {\n const token = readToken();\n if (!token) throw new Error('nothing filed from this browser yet');\n\n controller = new AbortController();\n const res = await fetch(`${apiBase}/ingest/watch/stream${projectQuery()}`, {\n headers: { Authorization: `Bearer ${token}`, Accept: 'text/event-stream' },\n signal: controller.signal,\n });\n\n // The token expired or was revoked. Retrying cannot fix that, and the\n // stale token is dropped so a later report can replace it.\n if (res.status === 401) {\n running = false;\n clearToken();\n throw new Error('subscription no longer valid');\n }\n if (!res.ok || !res.body) throw new Error(`stream failed (${res.status})`);\n\n const openedAt = Date.now();\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n try {\n for (;;) {\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n buffer = drain(buffer, (event) => {\n if (event.type !== 'ping') {\n try {\n onUpdate(event);\n } catch { /* a host handler that throws must not kill the stream */ }\n }\n });\n }\n } finally {\n // In a `finally` so a stream that ends by throwing is judged the same way\n // as one that ends cleanly — both have to earn the reset by lasting.\n if (Date.now() - openedAt >= STABLE_MS) attempt = 0;\n }\n }\n\n (async () => {\n while (running) {\n try {\n await connect();\n } catch (err) {\n if (err?.name === 'AbortError') return;\n }\n if (!running) return;\n const wait = BACKOFF_MS[Math.min(attempt, BACKOFF_MS.length - 1)];\n attempt += 1;\n await new Promise((resolve) => setTimeout(resolve, wait));\n }\n })();\n\n return () => {\n running = false;\n controller?.abort();\n controller = null;\n };\n}\n\nconst STATUS_COPY = {\n OPEN: 'is open',\n IN_PROGRESS: 'is being worked on',\n RESOLVED: 'has been fixed',\n CLOSED: 'has been closed',\n};\n\n/**\n * The colour each kind of update wears, so a fix and a severity change are not\n * the same anonymous grey card. Keyed on the update kind the API sends.\n */\nconst KIND_ACCENT = {\n status: null, // falls back to the host's configured accent\n severity: '#e8912a',\n assignment: '#4da3ff',\n reply: '#35b87f',\n};\n\n/**\n * The headline for an update.\n *\n * The API writes this line — it knows what changed and phrases it for the\n * person who reported it, and one copy of that wording serves the toast, the\n * timeline and the portal. `summary` is preferred for exactly that reason; the\n * status fallback below is only for a page still talking to an older API.\n */\nexport function updateHeadline(event) {\n if (event?.summary) return event.summary;\n return `Your report ${STATUS_COPY[event?.status] || 'was updated'}`;\n}\n\n/** The second line: a shared reply reads as the reply, everything else names the report. */\nfunction updateDetail(event) {\n if (event?.kind === 'reply' && event.reply?.body) {\n const who = event.reply.author ? `${event.reply.author}: ` : '';\n return `${who}${event.reply.body}`;\n }\n return event?.title || '';\n}\n\n/** How long ago, in the fewest words. Persisted cards outlive the moment. */\nfunction agoLabel(at) {\n const then = Date.parse(at || '');\n if (!Number.isFinite(then)) return '';\n const mins = Math.round((Date.now() - then) / 60000);\n if (mins < 1) return 'just now';\n if (mins < 60) return `${mins}m ago`;\n const hours = Math.round(mins / 60);\n if (hours < 24) return `${hours}h ago`;\n const days = Math.round(hours / 24);\n return days === 1 ? 'yesterday' : `${days}d ago`;\n}\n\n// At most this many cards on screen at once; the rest are counted on a line\n// under them. Somebody who has been away for a week should find a corner they\n// can still see their app through.\nconst MAX_CARDS = 3;\n\n/**\n * How far above the corner to start, so the stack clears the widget's launcher\n * rather than sitting on it. Measured rather than assumed: the dock is a\n * launcher, sometimes with a theme switch above it, and its height is the only\n * honest source for how much room it takes.\n */\nfunction dockOffset() {\n try {\n const host = document.querySelector('[data-bugcatch=\"widget\"]');\n const dock = host?.shadowRoot?.querySelector('.dock');\n const height = dock?.getBoundingClientRect().height || 0;\n return height ? Math.round(height) + 12 : 0;\n } catch {\n return 0;\n }\n}\n\n/**\n * The built-in notifications, for integrations that do not want to render their\n * own.\n *\n * A card per unread update, stacked in the same corner as the widget and\n * staying there until the reporter marks it read. Nothing here is on a timer:\n * the whole point of telling somebody their bug was fixed is that they find\n * out, and a card that removes itself after eight seconds only reaches the\n * people who happened to be looking.\n *\n * Everything is inline-styled and scoped to elements this owns: the SDK runs on\n * someone else's page and must not depend on, or contribute to, their CSS. It\n * sits *below* the widget's own layers, so an open report panel covers it\n * instead of a notification floating over the report it is about.\n */\nexport function createUpdateNotifier({\n accent = '#4F46E5',\n position = 'bottom-right',\n onRead = null,\n onReadAll = null,\n onCall = null,\n} = {}) {\n const [vertical, horizontal] = String(position).split('-');\n const atTop = vertical === 'top';\n const atLeft = horizontal === 'left';\n // id -> card element, so an update already on screen is left alone. Rebuilding\n // the stack on every frame would restart the animations and take the button\n // out from under a finger already on its way to it.\n const cards = new Map();\n let stack = null;\n let more = null;\n\n function ensureStack() {\n if (stack?.isConnected) return stack;\n stack = document.createElement('div');\n stack.setAttribute('data-bugcatch', 'updates');\n stack.style.cssText = [\n 'position:fixed',\n `${atTop ? 'top' : 'bottom'}:${20 + dockOffset()}px`,\n `${atLeft ? 'left' : 'right'}:20px`,\n // Under the widget's dock and its panel scrim, both of which are higher.\n // A notification is worth interrupting a page for, not the report being\n // read on top of it.\n 'z-index:2147482998',\n 'display:flex',\n 'flex-direction:column',\n 'gap:10px',\n `align-items:${atLeft ? 'flex-start' : 'flex-end'}`,\n 'width:min(320px,calc(100vw - 40px))',\n // The gaps between cards belong to the page underneath.\n 'pointer-events:none',\n ].join(';');\n document.body.appendChild(stack);\n return stack;\n }\n\n /** Fade out and go. Instant for a card that has not been painted yet. */\n function drop(el) {\n if (!el._bcShown) { el.remove(); return; }\n el.style.opacity = '0';\n el.style.transform = `translateY(${atTop ? -8 : 8}px)`;\n setTimeout(() => el.remove(), 250);\n }\n\n function button(label, primary) {\n const el = document.createElement('button');\n el.type = 'button';\n el.textContent = label;\n el.style.cssText = [\n 'padding:5px 11px',\n `border:${primary ? '0' : '1px solid rgba(236,238,242,.24)'}`,\n 'border-radius:6px',\n `background:${primary ? accent : 'transparent'}`,\n `color:${primary ? '#fff' : '#eceef2'}`,\n 'font:600 12px/1.4 inherit',\n 'cursor:pointer',\n 'white-space:nowrap',\n ].join(';');\n return el;\n }\n\n function card(record) {\n const el = document.createElement('div');\n el.setAttribute('role', 'status');\n el.style.cssText = [\n 'box-sizing:border-box',\n 'width:100%',\n 'padding:12px 14px',\n 'border-radius:10px',\n 'background:#1b1e26',\n 'color:#eceef2',\n 'font:13px/1.5 -apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif',\n 'box-shadow:0 12px 30px rgba(0,0,0,0.45)',\n `border-left:3px solid ${KIND_ACCENT[record?.kind] || accent}`,\n 'opacity:0',\n `transform:translateY(${atTop ? -8 : 8}px)`,\n 'transition:opacity .2s ease,transform .2s ease',\n 'pointer-events:auto',\n ].join(';');\n\n const title = document.createElement('div');\n title.style.cssText = 'font-weight:600;margin-bottom:2px';\n title.textContent = updateHeadline(record);\n\n const body = document.createElement('div');\n // A reply is the one kind whose detail is worth more room than a title, and\n // it is the only text here the team wrote rather than the reporter.\n body.style.cssText = record?.kind === 'reply'\n ? 'color:#a4abba;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden'\n : 'color:#a4abba';\n // textContent, not innerHTML: this is text a person typed, being written\n // back into somebody else's page.\n body.textContent = updateDetail(record);\n\n const actions = document.createElement('div');\n actions.style.cssText =\n 'display:flex;align-items:center;gap:8px;margin-top:10px;flex-wrap:wrap';\n\n const when = document.createElement('span');\n when.style.cssText = 'color:#7b8394;font-size:11px;margin-right:auto';\n when.textContent = agoLabel(record?.at);\n\n const read = button('Mark as read', false);\n read.setAttribute('aria-label', `Mark as read: ${updateHeadline(record)}`);\n read.addEventListener('click', () => onRead?.(record.id));\n\n actions.append(when, read);\n\n // The call offer, when there is one. A button rather than a call: a page\n // somebody may have walked away from must not raise a microphone prompt by\n // itself, and a reporter who would rather type has lost nothing by ignoring\n // this.\n if (record?.call?.offered && typeof onCall === 'function') {\n const talk = button('Talk it through', true);\n talk.addEventListener('click', () => onCall(record));\n actions.insertBefore(talk, read);\n }\n\n el.append(title, body, actions);\n return el;\n }\n\n /** The line under the stack when there is more unread than fits. */\n function moreRow(count) {\n const el = document.createElement('div');\n el.style.cssText = [\n 'box-sizing:border-box',\n 'width:100%',\n 'display:flex',\n 'align-items:center',\n 'gap:8px',\n 'padding:8px 12px',\n 'border-radius:10px',\n 'background:#1b1e26',\n 'color:#a4abba',\n 'font:12px/1.4 -apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif',\n 'box-shadow:0 12px 30px rgba(0,0,0,0.45)',\n 'pointer-events:auto',\n ].join(';');\n\n const label = document.createElement('span');\n label.style.cssText = 'margin-right:auto';\n label.textContent = `${count} more update${count === 1 ? '' : 's'}`;\n\n const all = button('Mark all as read', false);\n all.style.cssText += ';padding:4px 9px';\n all.addEventListener('click', () => onReadAll?.());\n\n el.append(label, all);\n return el;\n }\n\n return {\n /**\n * Draw what is unread, oldest first.\n *\n * The newest card sits nearest the corner the widget is in, which is the\n * corner the reporter's eye is already trained on — so the stack grows away\n * from the launcher rather than over it.\n */\n render(list) {\n const unread = Array.isArray(list) ? list : [];\n if (!unread.length) {\n for (const el of cards.values()) drop(el);\n cards.clear();\n more?.remove();\n more = null;\n // The container goes too: an empty fixed element in someone else's page\n // is one more thing for their own layout debugging to trip over.\n stack?.remove();\n stack = null;\n return;\n }\n\n const visible = unread.slice(-MAX_CARDS);\n const shown = new Set(visible.map((u) => u.id));\n const root = ensureStack();\n\n for (const [id, el] of cards) {\n if (!shown.has(id)) { drop(el); cards.delete(id); }\n }\n\n const fresh = [];\n for (const record of visible) {\n if (cards.has(record.id)) continue;\n const el = card(record);\n cards.set(record.id, el);\n fresh.push(el);\n }\n\n // Nearest the corner is the newest, so a top-anchored stack is drawn in\n // the opposite order to a bottom-anchored one.\n const ordered = atTop ? [...visible].reverse() : visible;\n for (const record of ordered) root.appendChild(cards.get(record.id));\n\n const hidden = unread.length - visible.length;\n if (hidden > 0) {\n more?.remove();\n more = moreRow(hidden);\n // Furthest from the corner, past the oldest card on screen.\n if (atTop) root.appendChild(more);\n else root.insertBefore(more, root.firstChild);\n } else {\n more?.remove();\n more = null;\n }\n\n // One frame later, so the transition has a start to run from. Cards read\n // as unpainted until this fires, which is what lets an update marked read\n // in the same tick — by the widget, with the report already open — leave\n // without a card flashing up and fading out behind it.\n requestAnimationFrame(() => {\n for (const el of fresh) {\n if (!el.isConnected) continue;\n el._bcShown = true;\n el.style.opacity = '1';\n el.style.transform = 'translateY(0)';\n }\n });\n },\n\n destroy() {\n cards.clear();\n more = null;\n stack?.remove();\n stack = null;\n },\n };\n}\n\n/**\n * What this project is willing to say is already broken.\n *\n * Asked when the widget opens, before anything is typed. Failure is silent and\n * answers an empty list: this is a help panel in front of a form, and a page\n * that cannot reach the API still has to let somebody report the thing they\n * came to report.\n */\nexport async function fetchKnownIssues({ apiBase, apiKey, projectId, sessionToken }) {\n try {\n const res = await fetch(\n `${apiBase}/ingest/known-issues?projectId=${encodeURIComponent(projectId)}`,\n { headers: knownIssueHeaders(apiKey, sessionToken) },\n );\n if (!res.ok) return [];\n const body = await res.json();\n return Array.isArray(body?.issues) ? body.issues : [];\n } catch {\n return [];\n }\n}\n\n/**\n * The listed issues that look like what somebody is typing.\n *\n * Takes an AbortSignal because it runs on a keystroke debounce: without one, a\n * fast typist has six of these in flight and the panel renders whichever\n * happens to land last, which is not necessarily the one matching what is now\n * in the box.\n */\nexport async function matchKnownIssues({ apiBase, apiKey, projectId, q, sessionToken, signal }) {\n try {\n const url = `${apiBase}/ingest/known-issues/match`\n + `?projectId=${encodeURIComponent(projectId)}&q=${encodeURIComponent(q)}`;\n const res = await fetch(url, { headers: knownIssueHeaders(apiKey, sessionToken), signal });\n if (!res.ok) return [];\n const body = await res.json();\n return Array.isArray(body?.issues) ? body.issues : [];\n } catch {\n // Includes the AbortError of a superseded keystroke, which is the ordinary\n // case rather than a failure.\n return [];\n }\n}\n\n/**\n * Say an existing report is happening to you too.\n *\n * Throws, unlike the two reads above: this one is a button somebody pressed,\n * and a press that silently does nothing is worse than an error they can read.\n * The watch token comes back for the same reason it does on a filed report —\n * it is what lets this browser hear that the bug was fixed.\n */\nexport async function joinKnownIssue({\n apiBase, apiKey, projectId, bugId, reporterEmail, reporterName, metadata, logs, sessionToken,\n}) {\n const res = await fetch(\n `${apiBase}/ingest/known-issues/${encodeURIComponent(bugId)}/join`,\n {\n method: 'POST',\n headers: knownIssueHeaders(apiKey, sessionToken),\n body: JSON.stringify({ projectId, reporterEmail, reporterName, metadata, logs }),\n },\n );\n if (!res.ok) throw new Error(await readJoinError(res));\n const body = await res.json();\n if (body?.watchToken) writeToken(body.watchToken);\n return body;\n}\n\nfunction knownIssueHeaders(apiKey, sessionToken) {\n return {\n 'Content-Type': 'application/json',\n 'X-BugCatch-Key': apiKey,\n ...(sessionToken ? { 'X-BugCatch-Session': sessionToken } : {}),\n };\n}\n\nasync function readJoinError(res) {\n try {\n const body = await res.json();\n const message = Array.isArray(body.message) ? body.message.join(', ') : body.message;\n if (message) return message;\n } catch { /* non-JSON error */ }\n return `Could not add you to this issue (HTTP ${res.status})`;\n}\n\nexport async function verifyFix({ apiBase, bugId, ...feedback }) {\n const token = readToken();\n if (!token) throw new Error('This browser cannot access that report.');\n const res = await fetch(`${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}/verification`, {\n method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }, body: JSON.stringify(feedback),\n });\n const data = await res.json().catch(() => ({}));\n if (!res.ok) throw new Error(data.message || 'Could not record feedback. Please refresh and try again.');\n return data;\n}\n","/**\n * Reading the reporter's own text for something that identifies their record.\n *\n * A browser-side twin of bugcatch-api/src/settings/reference-extract.ts, and\n * deliberately only the half this side needs. The server does the extraction\n * that counts — what a payload claims it found is not evidence, and the value\n * an investigation eventually joins on is the one the API read out of the text\n * it stored. This copy exists to answer one question, locally and instantly:\n *\n * does what this person has already typed contain an identifier, or do we\n * have to interrupt them for one?\n *\n * Getting that wrong in the lenient direction costs an unnecessary question.\n * Getting it wrong in the strict direction means asking a reporter who has\n * already told us, in the sentence above the box, which is the fastest way to\n * make somebody close a bug reporter and not come back. So the matching rule\n * here is the same rule, run over the same patterns, which the API ships with\n * the field list precisely so that both sides can agree.\n *\n * The patterns arrive over the wire and are compiled here. That is a thing to\n * be deliberate about rather than nervous about: they are shapes, not secrets —\n * \"a loan number looks like LN- followed by digits\" is not a fact worth hiding\n * from somebody already looking at their own loan number — and the alternative\n * is a round trip per keystroke or a question for everybody.\n */\n\nconst MAX_SCAN_CHARS = 20000;\nconst MAX_VALUE_CHARS = 120;\nconst MAX_MATCHES = 200;\n\n/**\n * Compile, or null.\n *\n * Null on anything that will not compile, which in a browser is a real case\n * rather than a defensive one: a regex feature one engine lacks throws here\n * rather than failing to match. A field that cannot be compiled is simply not\n * scanned, the reporter gets asked, and the server — which compiled it fine —\n * still reads the text. Degrading to the question is the whole design.\n */\nfunction compile(source) {\n if (typeof source !== 'string' || !source.trim() || source.length > 400) return null;\n try {\n return new RegExp(source.trim(), 'gi');\n } catch {\n return null;\n }\n}\n\nconst tidy = (value) => String(value).replace(/\\s+/g, ' ').trim().slice(0, MAX_VALUE_CHARS);\n\n/**\n * The value as the customer's database stores it — the same rule as\n * normalizeReferenceValue in bugcatch-api/src/settings/reference-extract.ts,\n * driven by the `normalize` the API sends with each built-in field.\n */\nexport function normalizeValue(field, value) {\n const how = field && field.normalize;\n if (how === 'phone') {\n const digits = value.replace(/\\D/g, '');\n if (digits.length === 10) return digits;\n if (digits.length === 12 && digits.startsWith('91')) return digits.slice(2);\n if (digits.length === 11 && digits.startsWith('0')) return digits.slice(1);\n return value;\n }\n if (how === 'upper') return value.toUpperCase();\n return value;\n}\n\n/** Group 1 where the pattern has one, the whole match otherwise. */\nfunction valueOf(match) {\n const raw = match.length > 1 && match[1] !== undefined ? match[1] : match[0];\n if (typeof raw !== 'string' || !raw.trim()) return null;\n // The span claimed is the whole match, never the capture: a labelled rule has\n // eaten the words in front of the value too, and leaving them unclaimed lets\n // a later field match them again.\n return { value: tidy(raw), start: match.index, end: match.index + match[0].length };\n}\n\n/**\n * Every configured identifier found in the given text.\n *\n * Fields are taken in the order the project arranged them and a span an earlier\n * field claimed is not offered to a later one — which is what keeps the rule for\n * \"alternate number\" and the rule for \"mobile number\", which match the same ten\n * digits, from both claiming it.\n */\nexport function matchReferences(fields, texts) {\n const found = [];\n const seen = new Set();\n\n for (const block of texts || []) {\n const text = typeof block === 'string' ? block.slice(0, MAX_SCAN_CHARS) : '';\n if (!text) continue;\n\n const claimed = [];\n const overlaps = (a, b) => claimed.some(([s, e]) => a < e && b > s);\n\n for (const field of fields || []) {\n if (!field || field.scan === false) continue;\n const re = compile(field.pattern);\n if (!re) continue;\n\n let match;\n let guard = 0;\n re.lastIndex = 0;\n while ((match = re.exec(text)) !== null) {\n if (++guard > MAX_MATCHES) break;\n // exec does not advance past a zero-width match, so this would spin.\n if (match[0] === '') { re.lastIndex += 1; continue; }\n\n const hit = valueOf(match);\n if (hit && !overlaps(hit.start, hit.end)) {\n claimed.push([hit.start, hit.end]);\n const value = normalizeValue(field, hit.value);\n const dedupe = `${field.key}:${value.toLowerCase()}`;\n if (!seen.has(dedupe)) {\n seen.add(dedupe);\n found.push({ key: field.key, label: field.label, value });\n }\n }\n }\n }\n }\n\n return found;\n}\n\n/** Letters the match read outside its value — the label words, roughly. */\nfunction labelWeight(text, hit) {\n const around = text.slice(hit.start, hit.valueStart) + text.slice(hit.valueEnd, hit.end);\n return (around.match(/[a-z]/gi) || []).length;\n}\n\n/**\n * Things in the text that look like an identifier nobody labelled: a UUID, a\n * run of six or more digits, a code mixing letters and digits. Only ever used\n * to pre-fill the question, never to skip it.\n */\n// No lookbehind: older Safari throws on it at parse time, which here would\n// take the whole SDK down rather than one field. Boundaries are checked by hand.\nconst LOOSE_ID = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|[A-Za-z0-9][\\w-]{5,40}/gi;\nconst WORDISH = /[\\w-]/;\n\n/**\n * What the reporter's text says about which record this is, sorted by how\n * sure we can be.\n *\n * found — each value one field reads best, and the only value for that\n * field. Enough to send without asking.\n * conflicts — a value two fields read equally well (two bare UUID rules), or\n * a field that found two different values. Asking which is the\n * only honest answer; picking the first is a coin toss filed as\n * a fact.\n * loose — ID-shaped strings no rule claimed, with the fields whose value\n * shape they fit. Offered as the answer when we have to ask\n * anyway, so the reporter confirms instead of retyping.\n *\n * \"Reads best\" is how many label letters the match consumed: `partner user id\n * 4821` is matched by the partner rule and the plain user rule, and the one\n * that read \"partner\" knows more. Two rules that read the same words tie, and\n * a tie is a question.\n */\nexport function readReferences(fields, texts) {\n const scannable = (fields || []).filter((f) => f && f.scan !== false && f.pattern);\n const hits = [];\n const loose = [];\n const blocks = (texts || []).map((t) => (typeof t === 'string' ? t.slice(0, MAX_SCAN_CHARS) : ''));\n\n blocks.forEach((text, block) => {\n if (!text) return;\n scannable.forEach((field, order) => {\n const re = compile(field.pattern);\n if (!re) return;\n let match;\n let guard = 0;\n re.lastIndex = 0;\n while ((match = re.exec(text)) !== null) {\n if (++guard > MAX_MATCHES) break;\n if (match[0] === '') { re.lastIndex += 1; continue; }\n const hit = valueOf(match);\n if (!hit) continue;\n const raw = match.length > 1 && match[1] !== undefined ? match[1] : match[0];\n const valueStart = match.index + Math.max(0, match[0].indexOf(raw));\n const h = {\n block, order, key: field.key, label: field.label, value: normalizeValue(field, hit.value),\n start: hit.start, end: hit.end, valueStart, valueEnd: valueStart + raw.length,\n };\n h.weight = labelWeight(text, h);\n hits.push(h);\n }\n });\n });\n\n // A hit loses to one that covers the same value and read more label.\n const survivors = hits.filter((h) => !hits.some((o) => o !== h && o.block === h.block\n && o.key !== h.key && o.valueStart <= h.valueStart && o.valueEnd >= h.valueEnd\n && o.weight > h.weight));\n\n // Group what survived by value: one value, several fields = which kind is it?\n const byValue = new Map();\n for (const h of survivors) {\n const id = h.value.toLowerCase();\n if (!byValue.has(id)) byValue.set(id, { value: h.value, keys: new Map() });\n const entry = byValue.get(id);\n if (!entry.keys.has(h.key)) entry.keys.set(h.key, { key: h.key, label: h.label, order: h.order });\n }\n\n const found = [];\n const conflicts = [];\n const settledKey = new Map(); // key -> values found for it\n for (const entry of byValue.values()) {\n const kinds = [...entry.keys.values()].sort((a, b) => a.order - b.order);\n if (kinds.length > 1) {\n conflicts.push({ value: entry.value, kinds: kinds.map(({ key, label }) => ({ key, label })) });\n continue;\n }\n const [{ key, label }] = kinds;\n if (!settledKey.has(key)) settledKey.set(key, []);\n settledKey.get(key).push({ key, label, value: entry.value });\n }\n for (const list of settledKey.values()) {\n if (list.length === 1) found.push(list[0]);\n else for (const r of list) conflicts.push({ value: r.value, kinds: [{ key: r.key, label: r.label }] });\n }\n\n // Loose candidates: ID-shaped, untouched by any rule, and fitting at least\n // one field's value shape. Nothing that is a URL path or an email.\n const seen = new Set(byValue.keys());\n blocks.forEach((text, block) => {\n if (!text) return;\n const taken = hits.filter((h) => h.block === block);\n const re = new RegExp(LOOSE_ID.source, 'gi');\n let m;\n let guard = 0;\n while ((m = re.exec(text)) !== null && ++guard <= MAX_MATCHES) {\n const s = m.index;\n const e = s + m[0].length;\n if (taken.some((h) => s < h.end && e > h.start)) continue;\n const before = text[s - 1] || '';\n const after = text[e] || '';\n if (WORDISH.test(before) || WORDISH.test(after)) continue;\n // Part of a URL, an address or a file name, not a thing someone quoted.\n if (before === '/' || before === '@' || after === '@' || before === '.') continue;\n if (after === '.' && /\\w/.test(text[e + 1] || '')) continue;\n if (!/\\d/.test(m[0])) continue;\n const value = tidy(m[0]);\n const id = value.toLowerCase();\n if (seen.has(id)) continue;\n const kinds = (fields || [])\n .filter((f) => f && validateReference(f, value))\n .map(({ key, label }) => ({ key, label }));\n if (!kinds.length) continue;\n seen.add(id);\n loose.push({ value, kinds });\n }\n });\n\n return { found, conflicts, loose };\n}\n\n/**\n * Whether what somebody typed into the box is the shape that field asks for.\n *\n * Anchored, unlike the scan above: reading prose means finding a value inside a\n * sentence, and checking an answer means the answer *is* the value. `valuePattern`\n * where the field has one — a rule that finds \"alternate number: 98…\" in prose\n * needs the words, and a box already headed \"Alternate number\" will not have\n * them typed into it again.\n *\n * Returns the tidied value, or null. Advisory only: the widget uses it to say\n * \"that does not look like a loan number\" before sending, and the API keeps a\n * value that fails it rather than dropping what the reporter believes is their\n * reference.\n */\nexport function validateReference(field, raw) {\n const value = typeof raw === 'string' ? raw.trim() : '';\n if (!value || value.length > MAX_VALUE_CHARS) return null;\n\n const re = compile(field && (field.valuePattern || field.pattern));\n if (!re) return null;\n\n let match;\n let guard = 0;\n re.lastIndex = 0;\n while ((match = re.exec(value)) !== null) {\n if (++guard > MAX_MATCHES) break;\n if (match[0] === '') { re.lastIndex += 1; continue; }\n // One character of slack at the front and only there, for a pattern whose\n // left boundary consumes rather than looks behind — see the phone rule,\n // written that way so it compiles in older Safari.\n const toEnd = match.index + match[0].length === value.length;\n const atEdge = match.index === 0 || match.index === 1;\n if (toEnd && atEdge) {\n const hit = valueOf(match);\n if (hit) return normalizeValue(field, hit.value);\n }\n }\n return null;\n}\n\n/**\n * What this project needs in order to look a report up.\n *\n * Every failure answers `{ policy: 'off', fields: [] }`, which is the answer\n * that draws nothing. A widget that could not reach this route must not be a\n * widget that refuses to send a report — the whole feature is a help, and the\n * server reads the text either way.\n */\nexport async function referenceFields({ apiBase, apiKey, projectId }) {\n const off = { policy: 'off', fields: [] };\n try {\n const res = await fetch(\n `${apiBase}/ingest/reference-fields?projectId=${encodeURIComponent(projectId)}`,\n { headers: { 'X-BugCatch-Key': apiKey } },\n );\n if (!res.ok) return off;\n const body = await res.json();\n const policy = body.policy === 'optional' || body.policy === 'required' ? body.policy : 'off';\n const fields = Array.isArray(body.fields) ? body.fields : [];\n return fields.length ? { policy, fields } : off;\n } catch {\n return off;\n }\n}\n","/**\n * The predefined issues: questions the project's assistant can answer from the\n * reporter's own account and close, offered in the widget as one-tap choices.\n *\n * The API decides the list (GET /ingest/agent-issues) — it is empty unless the\n * project has the Sphoro Agent answering questions and a data source that can\n * answer them. Every failure is an empty list: the tiles are a shortcut, and a\n * widget that could not load them is still a widget that files reports.\n *\n * The agent ends its answer with SOLVED_QUESTION's wording; the widget draws\n * Yes / No under it (widget.js messageHtml). Keep this pattern in step with\n * agent-issues.ts SOLVED_QUESTION in the API and workers.\n */\nexport const SOLVED_PROMPT = /Did this answer your question\\?/i;\n\nexport async function fetchAgentIssues({ apiBase, apiKey, projectId }) {\n try {\n const res = await fetch(\n `${apiBase}/ingest/agent-issues?projectId=${encodeURIComponent(projectId)}`,\n { headers: { 'X-BugCatch-Key': apiKey } },\n );\n if (!res.ok) return [];\n const body = await res.json();\n return (Array.isArray(body?.issues) ? body.issues : [])\n .filter((i) => i && typeof i.key === 'string' && typeof i.title === 'string')\n .map((i) => ({ key: i.key, title: i.title, hint: typeof i.hint === 'string' ? i.hint : '' }));\n } catch {\n return [];\n }\n}\n","// The floating \"Report a bug\" button and its form. Rendered in a shadow root\n// so the host page's CSS can't leak in and break it (or vice versa).\n//\n// Everything the panel needs is declared here as custom properties, which is\n// what makes theming a config option rather than a fork: the host element\n// carries the accent colour and the light/dark choice, and the rules below\n// only ever read variables.\n\nimport {\n captureScreenshot, startRecording, screenshotSupported, captureSupported,\n startVoiceNote, voiceSupported,\n humanSize, humanDuration, normalizeMime, UPLOAD_MIMES, REPORT_MIMES,\n} from './capture.js';\nimport { draftKey, draftOperation, readDraft } from './drafts.js';\nimport { readSession } from './session.js';\nimport { sendMessage } from './updates.js';\nimport { readReferences, validateReference } from './references.js';\nimport { SOLVED_PROMPT } from './agent-issues.js';\n\nconst POSITIONS = {\n 'bottom-right': 'bottom:20px;right:20px;',\n 'bottom-left': 'bottom:20px;left:20px;',\n 'top-right': 'top:20px;right:20px;',\n 'top-left': 'top:20px;left:20px;',\n};\n\nconst PRIORITIES = [\n { value: 'LOW', label: 'Low' },\n { value: 'MEDIUM', label: 'Medium' },\n { value: 'HIGH', label: 'High' },\n { value: 'CRITICAL', label: 'Critical' },\n];\n\nconst ICONS = {\n camera: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 8.5A1.5 1.5 0 0 1 4.5 7h2.2a1 1 0 0 0 .83-.45l.94-1.4A1 1 0 0 1 9.3 4.7h5.4a1 1 0 0 1 .83.45l.94 1.4a1 1 0 0 0 .83.45h2.2A1.5 1.5 0 0 1 21 8.5v9A1.5 1.5 0 0 1 19.5 19h-15A1.5 1.5 0 0 1 3 17.5z\"/><circle cx=\"12\" cy=\"13\" r=\"3.4\"/></svg>',\n video: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"6\" width=\"12.5\" height=\"12\" rx=\"2.2\"/><path d=\"M15.5 10.8l4.2-2.6a.6.6 0 0 1 .92.5v6.6a.6.6 0 0 1-.92.5l-4.2-2.6z\"/></svg>',\n paperclip: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M20 11.5l-7.6 7.6a4.6 4.6 0 0 1-6.5-6.5l8-8a3.2 3.2 0 0 1 4.5 4.5l-8 8a1.8 1.8 0 0 1-2.5-2.5l7.1-7.1\"/></svg>',\n close: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><path d=\"M6 6l12 12M18 6L6 18\"/></svg>',\n mic: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"9\" y=\"2.5\" width=\"6\" height=\"11\" rx=\"3\"/><path d=\"M5.5 11a6.5 6.5 0 0 0 13 0M12 17.5V21M9 21h6\"/></svg>',\n send: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 12l16-8-6 16-2.6-6.2L4 12Z\"/></svg>',\n stopSquare: '<svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><rect x=\"7\" y=\"7\" width=\"10\" height=\"10\" rx=\"2\"/></svg>',\n back: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M14.5 5.5L8 12l6.5 6.5\"/></svg>',\n phone: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6.2 3.5h3l1.5 3.7-2 1.4a11.5 11.5 0 0 0 5.7 5.7l1.4-2 3.7 1.5v3a1.8 1.8 0 0 1-2 1.8A15.6 15.6 0 0 1 4.4 5.5a1.8 1.8 0 0 1 1.8-2z\"/></svg>',\n phoneOff: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6.2 3.5h3l1.5 3.7-2 1.4a11.5 11.5 0 0 0 5.7 5.7l1.4-2 3.7 1.5v3a1.8 1.8 0 0 1-2 1.8A15.6 15.6 0 0 1 4.4 5.5a1.8 1.8 0 0 1 1.8-2z\"/><path d=\"M3 21L21 3\"/></svg>',\n bug: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M8 6a4 4 0 0 1 8 0\"/><rect x=\"7\" y=\"7.5\" width=\"10\" height=\"12\" rx=\"5\"/><path d=\"M7 11H3.5M7 15.5H4M17 11h3.5M17 15.5h3M12 8v11.5\"/></svg>',\n check: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M5 12.8l4.6 4.4L19 7\"/></svg>',\n retry: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M20 12a8 8 0 1 1-2.6-5.9M20 4v4.5h-4.5\"/></svg>',\n sun: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"4.2\"/><path d=\"M12 2.6v2.2M12 19.2v2.2M2.6 12h2.2M19.2 12h2.2M5.4 5.4l1.6 1.6M17 17l1.6 1.6M18.6 5.4L17 7M7 17l-1.6 1.6\"/></svg>',\n moon: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M20 13.4A8.2 8.2 0 1 1 10.6 4a6.6 6.6 0 0 0 9.4 9.4z\"/></svg>',\n auto: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"2.8\" y=\"4.4\" width=\"18.4\" height=\"12.6\" rx=\"2.2\"/><path d=\"M8.6 20.2h6.8\"/><path d=\"M12 4.4v12.6\"/><path d=\"M12 7.2a3.6 3.6 0 0 1 0 7.2z\" fill=\"currentColor\" stroke=\"none\"/></svg>',\n // The assistant's mark: a four-point spark, drawn wherever it speaks.\n sparkle: '<svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2.5c.5 4.6 2.9 7 7.5 7.5v.1c-4.6.5-7 2.9-7.5 7.5h-.1c-.5-4.6-2.9-7-7.5-7.5V10c4.6-.5 7-2.9 7.5-7.5z\"/><path d=\"M18.6 15.2c.2 1.9 1.2 2.9 3.1 3.1v.1c-1.9.2-2.9 1.2-3.1 3.1h-.1c-.2-1.9-1.2-2.9-3.1-3.1v-.1c1.9-.2 2.9-1.2 3.1-3.1z\" opacity=\".7\"/></svg>',\n chat: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 11.5a7.5 7 0 0 1 7.5-7h1a7.5 7 0 0 1 0 14H9l-4.2 2.6a.5.5 0 0 1-.76-.5l.5-3.1A7 7 0 0 1 4 11.5z\"/><path d=\"M8.5 11.5h.01M12 11.5h.01M15.5 11.5h.01\" stroke-width=\"2.6\"/></svg>',\n chevronDown: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6 9.5l6 6 6-6\"/></svg>',\n chevronRight: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M9.5 6l6 6-6 6\"/></svg>',\n alert: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 7.5v5.2M12 16.3h.01\"/></svg>',\n inbox: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3.5 13.5l2.6-7.2A1.5 1.5 0 0 1 7.5 5.3h9a1.5 1.5 0 0 1 1.4 1L20.5 13.5V18a1.5 1.5 0 0 1-1.5 1.5H5A1.5 1.5 0 0 1 3.5 18z\"/><path d=\"M3.5 13.5h4.8l1.2 2.2h5l1.2-2.2h4.8\"/></svg>',\n};\n\n/* The assistant's avatar, wherever it appears. */\nconst AVATAR = `<span class=\"avatar\" aria-hidden=\"true\">${ICONS.sparkle}</span>`;\n\n/* The sphoro lockup, as outlines rather than text.\n *\n * This SDK runs inside other people's pages, where Space Grotesk is not loaded\n * and cannot be assumed. Live <text> would render the mark in whatever the host\n * site happens to use — a different typeface on every customer. Outlines are\n * self-contained, so the mark is identical everywhere, at the cost of the path\n * data below.\n *\n * The BUGCATCH half reads --bc-brand-mark, not a bare --accent. Custom properties\n * are exempt from `all: initial`, so they cross the shadow boundary: a host page\n * that happens to define --accent would have repainted our mark in their brand\n * colour. The --bc- prefix is ours and no host is going to set it by accident. */\nconst BRAND = '<svg class=\"bc-brand\" viewBox=\"0 0 254 60\" fill=\"none\" role=\"img\" aria-label=\"sphoro BugCatch\"><g fill=\"currentColor\"><path transform=\"translate(0.00 40.00) scale(0.04000 -0.04000)\" d=\"M276.00 -14.00Q179.00 -14.00 117.00 28.00Q55.00 70.00 42.00 148.00L158.00 178.00Q165.00 143.00 181.50 123.00Q198.00 103.00 222.50 94.50Q247.00 86.00 276.00 86.00Q320.00 86.00 341.00 101.50Q362.00 117.00 362.00 140.00Q362.00 163.00 342.00 175.50Q322.00 188.00 278.00 196.00L250.00 201.00Q198.00 211.00 155.00 228.50Q112.00 246.00 86.00 277.00Q60.00 308.00 60.00 357.00Q60.00 431.00 114.00 470.50Q168.00 510.00 256.00 510.00Q339.00 510.00 394.00 473.00Q449.00 436.00 466.00 376.00L349.00 340.00Q341.00 378.00 316.50 394.00Q292.00 410.00 256.00 410.00Q220.00 410.00 201.00 397.50Q182.00 385.00 182.00 363.00Q182.00 339.00 202.00 327.50Q222.00 316.00 256.00 310.00L284.00 305.00Q340.00 295.00 385.50 278.50Q431.00 262.00 457.50 231.50Q484.00 201.00 484.00 149.00Q484.00 71.00 427.50 28.50Q371.00 -14.00 276.00 -14.00Z\"/><path transform=\"translate(19.56 40.00) scale(0.04000 -0.04000)\" d=\"M70.00 -200.00V496.00H194.00V436.00H212.00Q229.00 465.00 265.00 487.50Q301.00 510.00 368.00 510.00Q428.00 510.00 479.00 480.50Q530.00 451.00 561.00 394.00Q592.00 337.00 592.00 256.00V240.00Q592.00 159.00 561.00 102.00Q530.00 45.00 479.00 15.50Q428.00 -14.00 368.00 -14.00Q323.00 -14.00 292.50 -3.50Q262.00 7.00 243.50 23.50Q225.00 40.00 214.00 57.00H196.00V-200.00ZM330.00 96.00Q389.00 96.00 427.50 133.50Q466.00 171.00 466.00 243.00V253.00Q466.00 325.00 427.00 362.50Q388.00 400.00 330.00 400.00Q272.00 400.00 233.00 362.50Q194.00 325.00 194.00 253.00V243.00Q194.00 171.00 233.00 133.50Q272.00 96.00 330.00 96.00Z\"/><path transform=\"translate(43.68 40.00) scale(0.04000 -0.04000)\" d=\"M70.00 0.00V700.00H196.00V435.00H214.00Q222.00 451.00 239.00 467.00Q256.00 483.00 284.50 493.50Q313.00 504.00 357.00 504.00Q415.00 504.00 458.50 477.50Q502.00 451.00 526.00 404.50Q550.00 358.00 550.00 296.00V0.00H424.00V286.00Q424.00 342.00 396.50 370.00Q369.00 398.00 318.00 398.00Q260.00 398.00 228.00 359.50Q196.00 321.00 196.00 252.00V0.00Z\"/><path transform=\"translate(66.92 40.00) scale(0.04000 -0.04000)\" d=\"M306.00 -14.00Q232.00 -14.00 173.00 16.00Q114.00 46.00 80.00 103.00Q46.00 160.00 46.00 240.00V256.00Q46.00 336.00 80.00 393.00Q114.00 450.00 173.00 480.00Q232.00 510.00 306.00 510.00Q380.00 510.00 439.00 480.00Q498.00 450.00 532.00 393.00Q566.00 336.00 566.00 256.00V240.00Q566.00 160.00 532.00 103.00Q498.00 46.00 439.00 16.00Q380.00 -14.00 306.00 -14.00ZM306.00 98.00Q364.00 98.00 402.00 135.50Q440.00 173.00 440.00 243.00V253.00Q440.00 323.00 402.50 360.50Q365.00 398.00 306.00 398.00Q248.00 398.00 210.00 360.50Q172.00 323.00 172.00 253.00V243.00Q172.00 173.00 210.00 135.50Q248.00 98.00 306.00 98.00Z\"/><path transform=\"translate(90.00 40.00) scale(0.04000 -0.04000)\" d=\"M70.00 0.00V496.00H194.00V440.00H212.00Q223.00 470.00 248.50 484.00Q274.00 498.00 308.00 498.00H368.00V386.00H306.00Q258.00 386.00 227.00 360.50Q196.00 335.00 196.00 282.00V0.00Z\"/><path transform=\"translate(104.44 40.00) scale(0.04000 -0.04000)\" d=\"M306.00 -14.00Q232.00 -14.00 173.00 16.00Q114.00 46.00 80.00 103.00Q46.00 160.00 46.00 240.00V256.00Q46.00 336.00 80.00 393.00Q114.00 450.00 173.00 480.00Q232.00 510.00 306.00 510.00Q380.00 510.00 439.00 480.00Q498.00 450.00 532.00 393.00Q566.00 336.00 566.00 256.00V240.00Q566.00 160.00 532.00 103.00Q498.00 46.00 439.00 16.00Q380.00 -14.00 306.00 -14.00ZM306.00 98.00Q364.00 98.00 402.00 135.50Q440.00 173.00 440.00 243.00V253.00Q440.00 323.00 402.50 360.50Q365.00 398.00 306.00 398.00Q248.00 398.00 210.00 360.50Q172.00 323.00 172.00 253.00V243.00Q172.00 173.00 210.00 135.50Q248.00 98.00 306.00 98.00Z\"/></g><rect x=\"139.52\" y=\"16\" width=\"1.6\" height=\"26\" rx=\"0.8\" fill=\"var(--bc-brand-mark, #5647ee)\" opacity=\"0.4\"/><g fill=\"var(--bc-brand-mark, #5647ee)\"><path transform=\"translate(154.52 35.00) scale(0.01400 -0.01400)\" d=\"M46.35 0.00V105.85H139.75V594.15H46.35V700.00H404.08Q467.38 700.00 514.79 678.41Q562.20 656.83 588.70 617.33Q615.20 577.83 615.20 523.35V513.70Q615.20 465.52 597.03 434.76Q578.85 404.00 553.91 387.24Q528.98 370.48 506.28 363.30V346.35Q528.98 340.35 555.24 323.50Q581.50 306.65 600.00 275.48Q618.50 244.30 618.50 194.48V184.48Q618.50 126.65 591.41 85.24Q564.33 43.83 516.83 21.91Q469.33 0.00 406.85 0.00ZM259.85 109.15H392.60Q441.20 109.15 469.80 132.60Q498.40 156.05 498.40 198.55V208.20Q498.40 250.70 470.21 274.15Q442.02 297.60 392.60 297.60H259.85ZM259.85 406.75H391.65Q437.55 406.75 466.32 430.11Q495.10 453.48 495.10 493.80V503.80Q495.10 544.95 466.74 567.90Q438.37 590.85 391.65 590.85H259.85Z\"/><path transform=\"translate(167.00 35.00) scale(0.01400 -0.01400)\" d=\"M335.30 -14.00Q251.22 -14.00 190.60 16.63Q129.97 47.25 97.56 104.96Q65.15 162.68 65.15 243.03V700.00H185.25V239.73Q185.25 170.48 224.03 131.99Q262.80 93.50 335.30 93.50Q407.80 93.50 446.57 131.99Q485.35 170.48 485.35 239.73V700.00H605.45V243.03Q605.45 162.68 573.13 104.96Q540.80 47.25 480.09 16.63Q419.38 -14.00 335.30 -14.00Z\"/><path transform=\"translate(179.60 35.00) scale(0.01400 -0.01400)\" d=\"M311.85 -14.00Q238.45 -14.00 179.69 18.50Q120.92 51.00 86.69 113.67Q52.45 176.35 52.45 266.87V433.13Q52.45 569.00 128.01 641.50Q203.57 714.00 332.35 714.00Q460.13 714.00 529.23 645.59Q598.33 577.17 598.33 460.87V456.70H479.87V464.52Q479.87 504.67 463.76 536.86Q447.65 569.05 414.99 587.77Q382.32 606.50 332.35 606.50Q257.63 606.50 215.09 560.70Q172.55 514.90 172.55 435.13V264.87Q172.55 185.93 215.09 138.89Q257.63 91.85 334.00 91.85Q410.02 91.85 445.12 132.83Q480.22 173.80 480.22 237.92V249.67H302.85V351.88H598.33V0.00H487.52V69.10H470.57Q462.70 50.87 446.06 31.44Q429.42 12.00 397.64 -1.00Q365.85 -14.00 311.85 -14.00Z\"/><path transform=\"translate(192.05 35.00) scale(0.01400 -0.01400)\" d=\"M332.35 -14.00Q203.57 -14.00 128.01 58.50Q52.45 131.00 52.45 266.87V433.13Q52.45 569.00 128.01 641.50Q203.57 714.00 332.35 714.00Q460.13 714.00 529.58 643.85Q599.03 573.70 599.03 451.57V445.75H480.57V455.22Q480.57 521.42 444.15 563.96Q407.72 606.50 332.35 606.50Q257.63 606.50 215.09 560.70Q172.55 514.90 172.55 435.13V264.87Q172.55 185.93 215.09 139.71Q257.63 93.50 332.35 93.50Q407.72 93.50 444.15 136.45Q480.57 179.40 480.57 244.78V262.25H599.03V248.43Q599.03 126.30 529.58 56.15Q460.13 -14.00 332.35 -14.00Z\"/><path transform=\"translate(204.25 35.00) scale(0.01400 -0.01400)\" d=\"M19.75 0.00 211.62 700.00H421.33L613.20 0.00H489.27L447.42 159.60H185.53L143.68 0.00ZM214.25 270.40H418.70L324.95 624.87H308.00Z\"/><path transform=\"translate(216.31 35.00) scale(0.01400 -0.01400)\" d=\"M233.95 0.00V590.85H24.70V700.00H563.30V590.85H354.05V0.00Z\"/><path transform=\"translate(227.75 35.00) scale(0.01400 -0.01400)\" d=\"M332.35 -14.00Q203.57 -14.00 128.01 58.50Q52.45 131.00 52.45 266.87V433.13Q52.45 569.00 128.01 641.50Q203.57 714.00 332.35 714.00Q460.13 714.00 529.58 643.85Q599.03 573.70 599.03 451.57V445.75H480.57V455.22Q480.57 521.42 444.15 563.96Q407.72 606.50 332.35 606.50Q257.63 606.50 215.09 560.70Q172.55 514.90 172.55 435.13V264.87Q172.55 185.93 215.09 139.71Q257.63 93.50 332.35 93.50Q407.72 93.50 444.15 136.45Q480.57 179.40 480.57 244.78V262.25H599.03V248.43Q599.03 126.30 529.58 56.15Q460.13 -14.00 332.35 -14.00Z\"/><path transform=\"translate(239.95 35.00) scale(0.01400 -0.01400)\" d=\"M69.50 0.00V700.00H189.60V405.58H466.40V700.00H586.50V0.00H466.40V296.42H189.60V0.00Z\"/></g></svg>';\n\n// 'auto' is the default and means \"follow the visitor's system setting\".\nconst THEMES = [\n { value: 'light', label: 'Light', icon: ICONS.sun },\n { value: 'dark', label: 'Dark', icon: ICONS.moon },\n { value: 'auto', label: 'System default', icon: ICONS.auto },\n];\n\n// The visitor's pick outlives the page. Storage is wrapped because a sandboxed\n// iframe or a locked-down privacy mode throws on access rather than returning\n// null, and a theme preference is never worth breaking the widget over.\nconst THEME_KEY = 'bugcatch:theme';\n\nfunction readStoredTheme() {\n try {\n const value = localStorage.getItem(THEME_KEY);\n return THEMES.some((t) => t.value === value) ? value : null;\n } catch {\n return null;\n }\n}\n\nfunction writeStoredTheme(value) {\n try {\n localStorage.setItem(THEME_KEY, value);\n } catch { /* storage denied — the choice just won't outlive the page */ }\n}\n\nconst css = (accent) => `\n:host { all: initial; }\n:host {\n --bc-accent: ${accent};\n --bc-on-accent: #fff;\n --bc-bg: #fff;\n --bc-elevated: #fff;\n --bc-fg: #14161c;\n --bc-muted: #5f6673;\n --bc-faint: #9aa1ad;\n --bc-border: #e6e8ee;\n --bc-subtle: #f4f5f8;\n --bc-raised: #fff;\n --bc-danger: #dc2626;\n --bc-ok: #059669;\n /* The lockup's own two colours, kept off --bc-accent on purpose: that one is\n the customer's brand, and this mark is ours. Values are the light cut of\n brand/sphoro-bugcatch-primary.svg, pulled up from near-black so the mark\n identifies itself without out-shouting the host's page. */\n --bc-brand-ink: #4b5563;\n --bc-brand-mark: #5647ee;\n --bc-shadow: 0 24px 64px rgba(10,12,16,.22), 0 4px 14px rgba(10,12,16,.08);\n --bc-shadow-sm: 0 1px 2px rgba(10,12,16,.06), 0 1px 1px rgba(10,12,16,.04);\n --bc-dur: 240ms;\n --bc-ease: cubic-bezier(.22,1,.36,1);\n /* The system everything below is drawn from. Derived tokens are written in\n terms of the ones above, so a host that overrides --bc-accent or the\n palette gets a coherent result without touching these. */\n --bc-accent-soft: color-mix(in srgb, var(--bc-accent) 10%, var(--bc-bg));\n --bc-accent-line: color-mix(in srgb, var(--bc-accent) 32%, var(--bc-border));\n --bc-ring: 0 0 0 3px color-mix(in srgb, var(--bc-accent) 22%, transparent);\n --bc-r-sm: 8px;\n --bc-r-md: 12px;\n --bc-r-lg: 18px;\n --bc-r-xl: 22px;\n --bc-fs-xs: 11.5px;\n --bc-fs-sm: 12.5px;\n --bc-fs-md: 14px;\n --bc-fs-lg: 16px;\n --bc-pad: 18px;\n}\n:host(.bc-dark) {\n --bc-bg: #14161c;\n --bc-elevated: #1c1f27;\n --bc-fg: #eef0f4;\n --bc-muted: #a3aab6;\n --bc-faint: #6f7682;\n --bc-border: #2a2e38;\n --bc-subtle: #1f222a;\n --bc-raised: #363d4b;\n --bc-brand-ink: #9aa1ad;\n --bc-brand-mark: #9b8fff;\n --bc-shadow: 0 24px 64px rgba(0,0,0,.6), 0 4px 14px rgba(0,0,0,.4);\n --bc-shadow-sm: 0 1px 2px rgba(0,0,0,.4);\n --bc-accent-soft: color-mix(in srgb, var(--bc-accent) 18%, var(--bc-bg));\n}\n@media (prefers-color-scheme: dark) {\n :host(.bc-auto) {\n --bc-bg: #14161c;\n --bc-elevated: #1c1f27;\n --bc-fg: #eef0f4;\n --bc-muted: #a3aab6;\n --bc-faint: #6f7682;\n --bc-border: #2a2e38;\n --bc-subtle: #1f222a;\n --bc-raised: #363d4b;\n --bc-brand-ink: #9aa1ad;\n --bc-brand-mark: #9b8fff;\n --bc-shadow: 0 24px 64px rgba(0,0,0,.6), 0 4px 14px rgba(0,0,0,.4);\n --bc-shadow-sm: 0 1px 2px rgba(0,0,0,.4);\n --bc-accent-soft: color-mix(in srgb, var(--bc-accent) 18%, var(--bc-bg));\n }\n}\n\n* { box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, system-ui, sans-serif; }\nsvg { width: 1em; height: 1em; display: block; }\n/* The hidden attribute has to actually hide. Nearly every element in here is given a\n display by a rule below, and any of them beats the attribute's own UA style\n — which is why .dock and .launcher each had to say so themselves. Said once,\n for all of them, so the next element hidden from JavaScript works. */\n[hidden] { display: none !important; }\n.sr-only {\n position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;\n overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;\n}\n\n/* One focus ring for everything clickable, so keyboard users never have to\n guess where they are. Fields draw their own ring (a glow, not an outline). */\nbutton:focus-visible, a:focus-visible, select:focus-visible, [tabindex]:focus-visible {\n outline: 2px solid var(--bc-accent); outline-offset: 2px;\n}\n\n/* Motion is decoration here — every animation below is an entrance or a state\n hint, so switching them off costs nothing but the polish. */\n@media (prefers-reduced-motion: reduce) {\n *, *::before, *::after {\n animation-duration: .01ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: .01ms !important;\n }\n}\n\n@keyframes bc-fade-in { from { opacity: 0 } to { opacity: 1 } }\n@keyframes bc-fade-out { to { opacity: 0 } }\n@keyframes bc-panel-in { from { opacity: 0; transform: translateY(14px) scale(.965) } to { opacity: 1; transform: none } }\n@keyframes bc-panel-out { to { opacity: 0; transform: translateY(10px) scale(.975) } }\n@keyframes bc-sheet-in { from { transform: translateY(100%) } to { transform: none } }\n@keyframes bc-sheet-out { to { transform: translateY(100%) } }\n@keyframes bc-launcher-in { from { opacity: 0; transform: translateY(12px) scale(.86) } to { opacity: 1; transform: none } }\n@keyframes bc-item-in { from { opacity: 0; transform: translateY(8px) scale(.96) } to { opacity: 1; transform: none } }\n@keyframes bc-rise { from { opacity: 0; transform: translateY(6px) } to { opacity: 1; transform: none } }\n@keyframes bc-pulse { 0%, 100% { opacity: 1; transform: scale(1) } 50% { opacity: .3; transform: scale(.78) } }\n@keyframes bc-spin { to { transform: rotate(360deg) } }\n@keyframes bc-pop { 0% { transform: scale(.5); opacity: 0 } 65% { transform: scale(1.1) } 100% { transform: scale(1); opacity: 1 } }\n@keyframes bc-nudge { 25% { transform: translateX(-3px) } 75% { transform: translateX(3px) } }\n@keyframes bc-typing { 0%, 60%, 100% { transform: translateY(0); opacity: .45 } 30% { transform: translateY(-3px); opacity: 1 } }\n@keyframes bc-shimmer { from { background-position: 200% 0 } to { background-position: -200% 0 } }\n\n/* ---------- dock: theme switch stacked above the launcher ---------- */\n.dock {\n position: fixed; z-index: 2147483000;\n display: flex; flex-direction: column; gap: 10px;\n}\n.dock[hidden] { display: none; }\n/* While the panel is open the switch rises above the scrim so it stays usable\n — the panel shares the shadow root, so a change re-themes the open form. */\n.dock.over { z-index: 2147483002; }\n.dock.at-right { align-items: flex-end; }\n.dock.at-left { align-items: flex-start; }\n/* Anchored to the top, \"above the launcher\" would be off-screen, so the\n switch goes under it and the launcher stays the outermost thing. */\n.dock.at-top { flex-direction: column-reverse; }\n\n.themer {\n display: inline-flex; align-items: center; gap: 2px;\n padding: 3px; border-radius: 999px;\n background: var(--bc-elevated); border: 1px solid var(--bc-border);\n box-shadow: 0 6px 18px rgba(10,12,16,.14), 0 1px 3px rgba(10,12,16,.08);\n animation: bc-launcher-in 420ms var(--bc-ease) both;\n}\n.themer button {\n display: grid; place-items: center;\n width: 28px; height: 28px; padding: 0;\n border: none; border-radius: 999px; background: transparent;\n color: var(--bc-muted); cursor: pointer;\n transition: background var(--bc-dur) var(--bc-ease), color var(--bc-dur) var(--bc-ease), transform 140ms var(--bc-ease);\n}\n.themer button svg { font-size: 15px; }\n.themer button:hover { background: var(--bc-subtle); color: var(--bc-fg); }\n.themer button:active { transform: scale(.92); }\n.themer button[aria-pressed=\"true\"] { background: var(--bc-accent); color: var(--bc-on-accent); }\n.themer button[aria-pressed=\"true\"]:hover { background: var(--bc-accent); filter: brightness(1.06); }\n\n/* ---------- launcher ---------- */\n.launcher {\n position: relative;\n display: inline-flex; align-items: center; gap: 9px;\n height: 48px; padding: 0 20px 0 16px; border: none; border-radius: 999px;\n background: var(--bc-accent); color: var(--bc-on-accent);\n font-size: var(--bc-fs-md); font-weight: 620; line-height: 1; letter-spacing: -.01em;\n cursor: pointer;\n box-shadow: 0 10px 28px color-mix(in srgb, var(--bc-accent) 34%, rgba(10,12,16,.2)), 0 2px 6px rgba(10,12,16,.16);\n transition: transform var(--bc-dur) var(--bc-ease), box-shadow var(--bc-dur) var(--bc-ease), filter 160ms linear, padding var(--bc-dur) var(--bc-ease);\n animation: bc-launcher-in 420ms var(--bc-ease) both;\n}\n/* Two glyphs in one box: the bubble when closed, a chevron when open, turned\n into each other so the button reads as the panel's own handle. */\n.launcher .ico { position: relative; width: 20px; height: 20px; flex: none; }\n.launcher .ico > span {\n position: absolute; inset: 0;\n transition: transform 320ms var(--bc-ease), opacity 200ms ease;\n}\n.launcher .ico svg { width: 20px; height: 20px; }\n.launcher .ico .when-open { opacity: 0; transform: rotate(-90deg) scale(.6); }\n.launcher[aria-expanded=\"true\"] .ico .when-closed { opacity: 0; transform: rotate(90deg) scale(.6); }\n.launcher[aria-expanded=\"true\"] .ico .when-open { opacity: 1; transform: none; }\n.launcher:hover { transform: translateY(-2px); filter: brightness(1.06); }\n.launcher:active { transform: translateY(0) scale(.97); transition-duration: 90ms; }\n.launcher:focus-visible { outline-offset: 3px; }\n.launcher[hidden] { display: none; }\n\n/* ---------- scrim + panel ---------- */\n.scrim {\n position: fixed; inset: 0; z-index: 2147483001;\n background: rgba(10,12,16,.32);\n animation: bc-fade-in 200ms ease both;\n}\n.scrim.closing { animation: bc-fade-out 170ms ease both; }\n\n/* The panel belongs to the launcher, so it opens out of that corner rather\n than in the middle of the screen. --bc-dock-h is measured at open time\n because the dock's height depends on whether the launcher is showing.\n A column: the header and footer stay put and only the middle scrolls. */\n.panel {\n position: fixed;\n display: flex; flex-direction: column;\n width: calc(100vw - 40px); max-width: 420px;\n max-height: calc(100vh - var(--bc-dock-h, 84px) - 52px);\n overflow: hidden;\n background: var(--bc-bg); color: var(--bc-fg);\n border-radius: var(--bc-r-xl); box-shadow: var(--bc-shadow);\n border: 1px solid color-mix(in srgb, var(--bc-border) 70%, transparent);\n animation: bc-panel-in 340ms var(--bc-ease) both;\n}\n/* A conversation wants a height of its own, so the thread fills the panel\n rather than shrinking to its first two messages. */\n.panel.is-chat { height: 640px; }\n.scrim.at-right .panel { right: 20px; transform-origin: bottom right; }\n.scrim.at-left .panel { left: 20px; transform-origin: bottom left; }\n.scrim.at-bottom .panel { bottom: calc(20px + var(--bc-dock-h, 84px) + 12px); }\n.scrim.at-top .panel { top: calc(20px + var(--bc-dock-h, 84px) + 12px); transform-origin: top right; }\n.scrim.closing .panel { animation: bc-panel-out 170ms ease both; }\n\n/* ---------- header ---------- */\n.head {\n flex: none; position: relative;\n display: flex; align-items: center; gap: 12px;\n padding: 16px 14px 14px var(--bc-pad);\n background: linear-gradient(180deg, var(--bc-accent-soft), var(--bc-bg));\n border-bottom: 1px solid var(--bc-border);\n}\n.head .mark, .avatar {\n flex: none; display: grid; place-items: center; position: relative;\n width: 38px; height: 38px; border-radius: 50%;\n background: linear-gradient(140deg, var(--bc-accent), color-mix(in srgb, var(--bc-accent) 60%, #fff));\n color: var(--bc-on-accent); font-size: 19px;\n box-shadow: 0 4px 12px color-mix(in srgb, var(--bc-accent) 30%, transparent);\n}\n/* The little \"online\" dot says someone — or something — is on the other end. */\n.head .mark::after {\n content: ''; position: absolute; right: -1px; bottom: -1px;\n width: 11px; height: 11px; border-radius: 50%;\n background: #22c55e; border: 2px solid var(--bc-bg);\n}\n.head .titles { flex: 1; min-width: 0; }\n.head h2 {\n margin: 0 0 2px; font-size: var(--bc-fs-lg); font-weight: 680; letter-spacing: -.02em; line-height: 1.25;\n overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\n}\n.head p { margin: 0; font-size: var(--bc-fs-sm); line-height: 1.45; color: var(--bc-muted); }\n.icon-btn {\n flex: none; display: grid; place-items: center;\n width: 32px; height: 32px; padding: 0; font-size: 16px;\n border: none; border-radius: 10px; background: none; color: var(--bc-muted);\n cursor: pointer; transition: background 160ms, color 160ms, transform 160ms var(--bc-ease);\n}\n.icon-btn:hover { background: color-mix(in srgb, var(--bc-fg) 7%, transparent); color: var(--bc-fg); }\n.icon-btn:active { transform: scale(.92); }\n.icon-btn.back { margin: 0 -6px 0 -8px; }\n\n.body {\n flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain;\n padding: 16px var(--bc-pad) 6px;\n}\n\nlabel { display: block; font-size: var(--bc-fs-sm); font-weight: 620; color: var(--bc-fg); margin-bottom: 7px; }\nlabel .opt { font-weight: 450; color: var(--bc-faint); }\ninput[type=text], input[type=email], textarea {\n width: 100%; padding: 11px 13px; font-size: var(--bc-fs-md); line-height: 1.45;\n color: var(--bc-fg); background: var(--bc-elevated);\n border: 1px solid var(--bc-border); border-radius: var(--bc-r-md);\n transition: border-color 160ms, box-shadow 160ms, background 160ms;\n}\ninput[type=text]::placeholder, input[type=email]::placeholder, textarea::placeholder { color: var(--bc-faint); }\ninput[type=text]:hover, input[type=email]:hover, textarea:hover { border-color: color-mix(in srgb, var(--bc-fg) 18%, var(--bc-border)); }\ninput[type=text]:focus, input[type=email]:focus, textarea:focus {\n outline: none; border-color: var(--bc-accent); box-shadow: var(--bc-ring);\n}\ninput.invalid, input.invalid:focus, textarea.invalid {\n border-color: var(--bc-danger); animation: bc-nudge 240ms ease;\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--bc-danger) 16%, transparent);\n}\n.field-err {\n display: flex; align-items: center; gap: 6px;\n margin: 7px 0 0; font-size: var(--bc-fs-xs); font-weight: 550; color: var(--bc-danger);\n animation: bc-rise 200ms var(--bc-ease) both;\n}\n.field-err svg { font-size: 13px; flex: none; }\ntextarea { min-height: 88px; resize: vertical; }\n.field { margin-bottom: 16px; }\n.field-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }\n.field-head .count { font-size: var(--bc-fs-xs); color: var(--bc-faint); font-variant-numeric: tabular-nums; }\n\n/* The draft line: a quiet note, or a card offering to pick up where they left off. */\n.compose > .note { font-size: var(--bc-fs-xs); color: var(--bc-faint); margin: -4px 0 10px; }\n.compose > .note:empty { display: none; }\n.compose > .note:has(button) {\n display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px;\n margin: 0 0 14px; padding: 10px 12px; border-radius: var(--bc-r-md);\n background: var(--bc-accent-soft); border: 1px solid var(--bc-accent-line);\n font-size: var(--bc-fs-sm); color: var(--bc-fg);\n}\n.compose > .note button {\n padding: 5px 11px; font: inherit; font-size: var(--bc-fs-xs); font-weight: 620;\n border-radius: 999px; cursor: pointer;\n border: 1px solid var(--bc-accent); background: var(--bc-accent); color: var(--bc-on-accent);\n}\n.compose > .note button[data-discard] { background: transparent; color: var(--bc-muted); border-color: var(--bc-border); }\n\n/* ---------- what is already known ----------\n A help surface in front of the form, not a step in it. Everything here is\n dismissible by ignoring it: the reporter can read what is already known and\n leave, tap \"this is happening to me too\" and leave, or type straight past all\n of it and file the report they came to file. Nothing is ever in their way,\n because a reporter who feels handled rather than helped stops reporting. */\n.known { margin-bottom: 16px; }\n.known .known-h {\n margin: 0 0 8px; font-size: 11px; font-weight: 680; letter-spacing: .06em;\n text-transform: uppercase; color: var(--bc-faint);\n}\n.known .issue {\n display: flex; align-items: center; gap: 10px;\n padding: 10px 12px; margin-bottom: 6px;\n background: var(--bc-subtle); border: 1px solid var(--bc-border); border-radius: var(--bc-r-md);\n animation: bc-rise 240ms var(--bc-ease) both;\n}\n.known .issue.is-joined { border-color: color-mix(in srgb, var(--bc-ok) 55%, var(--bc-border)); }\n.known .issue .who { flex: 1; min-width: 0; }\n.known .issue .t {\n display: block; font-size: 13px; font-weight: 620; line-height: 1.4;\n color: var(--bc-fg); overflow-wrap: anywhere;\n}\n.known .issue .meta {\n display: block; margin-top: 3px; font-size: var(--bc-fs-xs); line-height: 1.45; color: var(--bc-muted);\n}\n.known .issue .join {\n flex: none; padding: 7px 12px; font-size: 12px; font-weight: 620;\n color: var(--bc-accent); background: var(--bc-bg);\n border: 1px solid var(--bc-accent-line); border-radius: 999px; cursor: pointer;\n transition: background 160ms, border-color 160ms;\n}\n.known .issue .join:hover { border-color: var(--bc-accent); background: var(--bc-accent-soft); }\n.known .issue .join:disabled { color: var(--bc-ok); border-color: var(--bc-border); background: var(--bc-bg); cursor: default; opacity: 1; }\n.known .ask { display: flex; gap: 8px; margin: 2px 0 8px; }\n.known .ask input { flex: 1; min-width: 0; }\n.known .ask button { flex: none; }\n.known .err { margin: 6px 0 0; font-size: 12px; line-height: 1.45; color: var(--bc-danger); }\n\n/* ---------- questions the assistant answers by itself ---------- */\n.picks { margin-bottom: 16px; }\n.picks .picks-h {\n margin: 0 0 8px; font-size: 11px; font-weight: 680; letter-spacing: .06em;\n text-transform: uppercase; color: var(--bc-faint);\n}\n.picks .picks-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 6px; }\n.picks .pick {\n display: flex; flex-direction: column; align-items: flex-start; gap: 2px;\n padding: 9px 11px; text-align: left; cursor: pointer; font: inherit;\n color: var(--bc-fg); background: var(--bc-subtle);\n border: 1px solid var(--bc-border); border-radius: var(--bc-r-md);\n transition: background 160ms, border-color 160ms, transform 160ms;\n animation: bc-rise 240ms var(--bc-ease) both;\n}\n.picks .pick:hover { border-color: var(--bc-accent-line); background: var(--bc-accent-soft); }\n.picks .pick[aria-pressed=\"true\"] { border-color: var(--bc-accent); background: var(--bc-accent-soft); }\n.picks .pick .t { font-size: 13px; font-weight: 620; line-height: 1.35; overflow-wrap: anywhere; }\n.picks .pick .h { font-size: var(--bc-fs-xs); line-height: 1.4; color: var(--bc-muted); }\n.picks .picks-note {\n display: flex; gap: 6px; align-items: flex-start; margin: 8px 0 0;\n font-size: 12px; line-height: 1.45; color: var(--bc-muted);\n}\n.picks .picks-note svg { flex: none; margin-top: 2px; color: var(--bc-ok); }\n\n/* ---------- who is reporting ----------\n A strip above the form rather than a screen in front of it: where signing in\n is optional this is a line the reporter may ignore, and it should read as\n one. Where it is required the composer below is not rendered at all and this\n strip is the whole panel. */\n.whoami {\n display: flex; align-items: center; flex-wrap: wrap; gap: 8px;\n margin-bottom: 16px; padding: 10px 12px;\n font-size: var(--bc-fs-sm); line-height: 1.45; color: var(--bc-muted);\n background: var(--bc-subtle); border: 1px solid var(--bc-border); border-radius: var(--bc-r-md);\n}\n.whoami.asking { display: block; padding: 14px; }\n.whoami .who { flex: 1; min-width: 0; }\n.whoami .who strong { color: var(--bc-fg); font-weight: 620; word-break: break-all; }\n.link {\n padding: 0; font: inherit; font-size: var(--bc-fs-sm); font-weight: 620; line-height: 1.45;\n color: var(--bc-accent); background: none; border: none; cursor: pointer; border-radius: 4px;\n}\n.link:hover { text-decoration: underline; text-underline-offset: 2px; }\n.link:disabled { color: var(--bc-faint); cursor: default; text-decoration: none; }\n.whoami .why { margin: 0 0 10px; font-size: var(--bc-fs-sm); line-height: 1.5; color: var(--bc-muted); }\n.whoami .why strong { color: var(--bc-fg); font-weight: 620; word-break: break-all; }\n.whoami .row { display: flex; gap: 8px; align-items: stretch; }\n.whoami .row input { flex: 1; min-width: 0; }\n.whoami .row button { flex: none; }\n.whoami .after { display: flex; gap: 14px; align-items: center; margin-top: 9px; }\n.whoami .err { color: var(--bc-danger); }\n.whoami .code { letter-spacing: .28em; font-variant-numeric: tabular-nums; }\n\n/* ---------- which record is this about ----------\n Drawn only after Send has been pressed and only when what they wrote did not\n already answer it, so most people never see this at all. Presented as the\n assistant asking one friendly follow-up, not as a form field that appeared. */\n.refs {\n position: relative;\n margin-bottom: 16px; padding: 14px;\n font-size: var(--bc-fs-sm); line-height: 1.45; color: var(--bc-muted);\n background: var(--bc-accent-soft);\n border: 1px solid var(--bc-accent-line); border-radius: var(--bc-r-lg);\n animation: bc-item-in 280ms var(--bc-ease) both;\n}\n.refs .ask-head { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 11px; }\n.refs .ask-head .avatar { width: 28px; height: 28px; font-size: 14px; box-shadow: none; }\n.refs .ask-head strong { display: block; font-size: 13.5px; font-weight: 650; color: var(--bc-fg); margin-bottom: 1px; }\n.refs .why { margin: 0; font-size: var(--bc-fs-sm); line-height: 1.5; color: var(--bc-muted); }\n.refs .row { display: flex; gap: 8px; align-items: stretch; }\n.refs .row input { flex: 1; min-width: 0; background: var(--bc-bg); }\n.refs select {\n flex: none; max-width: 45%; padding: 10px 10px; font-size: 13px;\n color: var(--bc-fg); background: var(--bc-bg);\n border: 1px solid var(--bc-border); border-radius: var(--bc-r-md);\n}\n.refs select:focus { outline: none; border-color: var(--bc-accent); box-shadow: var(--bc-ring); }\n.refs .chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 9px; }\n.refs .chips button {\n padding: 5px 11px; font: inherit; font-size: 12px; font-weight: 600; border-radius: 999px; cursor: pointer;\n color: var(--bc-muted); background: var(--bc-bg); border: 1px solid var(--bc-border);\n transition: background 160ms, color 160ms, border-color 160ms;\n}\n.refs .chips button[aria-pressed=\"true\"] { color: var(--bc-on-accent); background: var(--bc-accent); border-color: var(--bc-accent); }\n.refs .hint { margin: 8px 0 0; font-size: 12px; line-height: 1.45; color: var(--bc-muted); }\n.refs .after { display: flex; gap: 12px; align-items: center; margin-top: 9px; }\n.refs .err { color: var(--bc-danger); font-weight: 550; }\n.refs .link { margin-top: 10px; font-size: 12px; color: var(--bc-muted); }\n.refs .link:hover { color: var(--bc-fg); }\n.refs .chips.cands button { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 550; }\n/* \"We read your ID\" — under the description, while they type. */\n.ref-found {\n display: flex; align-items: center; gap: 6px;\n margin: -6px 0 14px; font-size: 12px; line-height: 1.45; color: var(--bc-muted);\n}\n.ref-found svg { width: 14px; height: 14px; flex: none; color: var(--bc-ok); }\n.ref-found b { font-weight: 600; color: var(--bc-fg); }\n\n/* ---------- severity ---------- */\n.seg { display: grid; grid-template-columns: repeat(4, 1fr); gap: 3px; padding: 3px; background: var(--bc-subtle); border: 1px solid var(--bc-border); border-radius: var(--bc-r-md); }\n.seg button {\n display: inline-flex; align-items: center; justify-content: center; gap: 6px;\n padding: 8px 4px; font-size: var(--bc-fs-sm); font-weight: 550; line-height: 1;\n color: var(--bc-muted); background: none; border: none; border-radius: 9px;\n cursor: pointer; transition: background 180ms var(--bc-ease), color 180ms, box-shadow 180ms;\n}\n.seg button::before { content: ''; width: 7px; height: 7px; border-radius: 50%; background: var(--sev, var(--bc-faint)); opacity: .55; transition: opacity 180ms; }\n.seg button[data-prio=\"LOW\"] { --sev: #94a3b8; }\n.seg button[data-prio=\"MEDIUM\"] { --sev: #3b82f6; }\n.seg button[data-prio=\"HIGH\"] { --sev: #f59e0b; }\n.seg button[data-prio=\"CRITICAL\"] { --sev: #ef4444; }\n.seg button:hover { color: var(--bc-fg); }\n.seg button[aria-pressed=true] {\n background: var(--bc-raised); color: var(--bc-fg); font-weight: 650;\n box-shadow: 0 1px 3px rgba(10,12,16,.14), 0 0 0 1px color-mix(in srgb, var(--bc-border) 60%, transparent);\n}\n.seg button[aria-pressed=true]::before { opacity: 1; }\n.seg button:focus-visible { outline-offset: -2px; }\n\n/* ---------- attachments ---------- */\n.tools { display: grid; grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); gap: 8px; }\n.tool {\n display: inline-flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;\n padding: 12px 8px; font-size: 12px; font-weight: 600; line-height: 1;\n color: var(--bc-fg); background: var(--bc-elevated);\n border: 1px dashed color-mix(in srgb, var(--bc-fg) 20%, var(--bc-border)); border-radius: var(--bc-r-md); cursor: pointer;\n transition: border-color 160ms, background 160ms, color 160ms, transform 160ms var(--bc-ease);\n}\n.tool svg { font-size: 19px; color: var(--bc-muted); transition: color 160ms; }\n.tool:hover:not(:disabled) { border-color: var(--bc-accent); border-style: solid; background: var(--bc-accent-soft); transform: translateY(-1px); }\n.tool:hover:not(:disabled) svg { color: var(--bc-accent); }\n.tool:active:not(:disabled) { transform: translateY(0) scale(.98); }\n.tool:disabled { opacity: .45; cursor: not-allowed; }\n.attach-hint { margin: 8px 0 0; font-size: var(--bc-fs-xs); color: var(--bc-faint); }\n\n.thumbs { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }\n.thumbs:empty { display: none; }\n.thumb {\n position: relative; width: 76px; height: 76px; flex: none;\n border-radius: var(--bc-r-md); overflow: hidden;\n border: 1px solid var(--bc-border); background: var(--bc-subtle);\n box-shadow: var(--bc-shadow-sm);\n animation: bc-item-in 300ms var(--bc-ease) both;\n}\n.thumb img, .thumb video { width: 100%; height: 100%; object-fit: cover; display: block; }\n.thumb .kind {\n position: absolute; left: 4px; bottom: 4px;\n display: inline-flex; align-items: center; gap: 3px;\n padding: 2px 5px; border-radius: 5px;\n background: rgba(10,12,16,.72); color: #fff; font-size: 9.5px; font-weight: 600; line-height: 1.3;\n}\n.thumb .drop {\n position: absolute; top: 4px; right: 4px;\n width: 20px; height: 20px; font-size: 11px; border-radius: 50%;\n background: rgba(10,12,16,.7); color: #fff; border: none;\n display: grid; place-items: center; cursor: pointer;\n opacity: 0; transition: opacity 160ms, background 160ms;\n}\n.thumb:hover .drop, .thumb:focus-within .drop { opacity: 1; }\n.thumb .drop:hover { background: var(--bc-danger); }\n.thumb .veil {\n position: absolute; inset: 0; display: grid; place-items: center; gap: 6px;\n background: rgba(10,12,16,.62); color: #fff; font-size: 10.5px; font-weight: 600;\n align-content: center;\n}\n.thumb .veil.err { background: color-mix(in srgb, var(--bc-danger) 82%, transparent); }\n.spinner {\n width: 17px; height: 17px; border-radius: 50%;\n border: 2px solid rgba(255,255,255,.35); border-top-color: #fff;\n animation: bc-spin 720ms linear infinite;\n}\n.bar { width: 46px; height: 3px; border-radius: 2px; background: rgba(255,255,255,.3); overflow: hidden; }\n.bar i { display: block; height: 100%; width: 0; background: #fff; transition: width 180ms linear; }\n.veil button {\n background: none; border: none; color: #fff; font-size: 14px; padding: 2px;\n cursor: pointer; display: grid; place-items: center;\n}\n\n/* ---------- messages ---------- */\n.msg {\n display: flex; align-items: flex-start; gap: 8px;\n font-size: var(--bc-fs-sm); line-height: 1.45; padding: 10px 12px; border-radius: var(--bc-r-md); margin-bottom: 14px;\n animation: bc-item-in 240ms var(--bc-ease) both;\n}\n.msg svg { flex: none; font-size: 15px; margin-top: 1px; }\n.msg.err { background: color-mix(in srgb, var(--bc-danger) 10%, var(--bc-bg)); color: var(--bc-danger); border: 1px solid color-mix(in srgb, var(--bc-danger) 30%, transparent); }\n.msg.info { background: var(--bc-subtle); color: var(--bc-muted); border: 1px solid var(--bc-border); }\n\n/* ---------- footer ---------- */\n.foot {\n flex: none;\n display: flex; align-items: center; flex-wrap: wrap; gap: 8px;\n padding: 12px var(--bc-pad); border-top: 1px solid var(--bc-border);\n background: var(--bc-bg);\n}\n.foot .spacer { flex: 1; }\n.foot .note { flex-basis: 100%; order: -1; font-size: var(--bc-fs-xs); color: var(--bc-muted); }\n.foot .note:empty { display: none; }\n/* Quiet, but still legible: this sits in someone else's product, so it identifies\n where the report goes without competing with their interface. Quiet is bought\n with colour (--bc-brand-ink sits a step below body text) rather than with a\n blanket opacity — the BUGCATCH half is only about 3px of cap height at this\n size, and dimming it on top of that left it as a smudge in both themes. */\n.powered {\n flex: none; display: flex; align-items: center; justify-content: center; gap: 6px;\n padding: 7px var(--bc-pad) 9px; font-size: 10.5px; color: var(--bc-faint);\n background: var(--bc-subtle); border-top: 1px solid var(--bc-border);\n}\n.brand-credit { display: flex; align-items: center; transition: color 0.15s; border-radius: 4px; }\n.bc-brand { height: 16px; width: auto; display: block; color: var(--bc-brand-ink); }\n.brand-credit:hover .bc-brand { color: var(--bc-fg); }\nbutton.ghost, button.primary {\n display: inline-flex; align-items: center; justify-content: center; gap: 7px;\n height: 38px; padding: 0 15px; font: inherit; font-size: 13px; font-weight: 620; line-height: 1;\n border-radius: 999px; cursor: pointer; white-space: nowrap;\n transition: transform 160ms var(--bc-ease), filter 160ms, background 160ms, border-color 160ms, color 160ms, box-shadow 160ms;\n}\nbutton.ghost { background: var(--bc-bg); border: 1px solid var(--bc-border); color: var(--bc-fg); }\nbutton.ghost:hover:not(:disabled) { background: var(--bc-subtle); border-color: color-mix(in srgb, var(--bc-fg) 20%, var(--bc-border)); }\nbutton.ghost svg, button.primary svg { font-size: 15px; }\nbutton.primary {\n padding: 0 18px;\n background: var(--bc-accent); border: 1px solid var(--bc-accent); color: var(--bc-on-accent);\n box-shadow: 0 4px 12px color-mix(in srgb, var(--bc-accent) 30%, transparent);\n}\nbutton.primary:hover:not(:disabled) { transform: translateY(-1px); filter: brightness(1.07); }\nbutton.primary:active:not(:disabled) { transform: none; }\nbutton:disabled { opacity: .5; cursor: not-allowed; }\nbutton.primary:disabled { box-shadow: none; }\nbutton.primary .spinner { border-color: rgba(255,255,255,.4); border-top-color: #fff; width: 14px; height: 14px; }\nbutton.quiet {\n padding: 0 4px; height: 38px; font: inherit; font-size: 13px; font-weight: 600;\n border: none; background: none; color: var(--bc-muted); cursor: pointer; border-radius: 8px;\n}\nbutton.quiet:hover { color: var(--bc-accent); }\n\n/* ---------- your reports ---------- */\n.history {\n flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain;\n padding: 8px 12px 14px;\n}\n/* On the conversation screen the inner thread does the scrolling, so this\n wrapper must not — two nested scrollers means the composer drifts out of\n reach exactly when someone reaches for it. */\n.history.chatting {\n overflow: hidden; display: flex; flex-direction: column; padding: 0;\n}\n.history .row {\n display: flex; align-items: center; gap: 12px;\n padding: 12px 10px; border-bottom: 1px solid var(--bc-border);\n}\n.history .row:last-child { border-bottom: 0; }\n.history .who { flex: 1; min-width: 0; }\n.history .ttl {\n display: block; font-size: 13.5px; font-weight: 600; color: var(--bc-fg);\n overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\n}\n.history .sub { display: block; margin-top: 3px; font-size: var(--bc-fs-xs); color: var(--bc-faint); }\n.history .row-ico {\n flex: none; display: grid; place-items: center; width: 34px; height: 34px; border-radius: 10px;\n background: var(--bc-subtle); color: var(--bc-muted); font-size: 16px;\n}\n.history .chev { flex: none; color: var(--bc-faint); font-size: 15px; transition: transform 160ms var(--bc-ease), color 160ms; }\n.empty {\n display: flex; flex-direction: column; align-items: center; gap: 10px;\n padding: 40px 16px; text-align: center; font-size: var(--bc-fs-sm); color: var(--bc-muted);\n}\n.empty .empty-ico {\n display: grid; place-items: center; width: 44px; height: 44px; border-radius: 50%;\n background: var(--bc-accent-soft); color: var(--bc-accent); font-size: 20px;\n}\n.skel {\n display: block; height: 12px; border-radius: 6px; margin: 6px 0;\n background: linear-gradient(90deg, var(--bc-subtle) 25%, color-mix(in srgb, var(--bc-fg) 8%, var(--bc-subtle)) 50%, var(--bc-subtle) 75%);\n background-size: 200% 100%; animation: bc-shimmer 1.4s linear infinite;\n}\n/* A history row is a button now — it opens the report — so the UA styling has\n to go, and the hit area has to read as one. */\n.history button.row {\n width: 100%; background: none; border: 0; margin-bottom: 2px;\n font: inherit; color: inherit; text-align: left; cursor: pointer;\n border-radius: var(--bc-r-md); transition: background 160ms;\n animation: bc-rise 260ms var(--bc-ease) both;\n}\n.history button.row:hover { background: var(--bc-subtle); }\n.history button.row:hover .chev { transform: translateX(2px); color: var(--bc-accent); }\n.history button.row:focus-visible { outline-offset: -2px; }\n\n/* ---------- one report, and what happened to it ---------- */\n.report-head {\n flex: none;\n display: grid; grid-template-columns: 1fr auto; gap: 2px 10px; align-items: center;\n padding: 10px var(--bc-pad); border-bottom: 1px solid var(--bc-border);\n background: var(--bc-bg);\n}\n.report-head .ttl { font-size: 13px; font-weight: 620; color: var(--bc-fg); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }\n.report-head .sub { font-size: var(--bc-fs-xs); color: var(--bc-faint); }\n.report-head .pill { grid-row: 1 / span 2; grid-column: 2; align-self: center; }\n\n/* The thread scrolls; the composer under it does not. Both live inside the\n panel, which stops scrolling as a whole on this screen — a composer that\n scrolls away is a composer people think isn't there. */\n.thread {\n flex: 1 1 auto; min-height: 140px;\n display: flex; flex-direction: column; gap: 4px;\n padding: 14px var(--bc-pad) 12px; overflow-y: auto; overscroll-behavior: contain;\n}\n\n/* The day a run of messages happened on, as a centred label on a rule. */\n.day-sep {\n display: flex; align-items: center; gap: 10px; margin: 10px 0 6px;\n font-size: 11px; font-weight: 620; letter-spacing: .02em; color: var(--bc-faint);\n}\n.day-sep::before, .day-sep::after { content: ''; flex: 1; height: 1px; background: var(--bc-border); }\n\n/* A status change is not a message and is not drawn as one: no side, no\n bubble, no author. Centred and quiet, the way every chat marks the things\n that happened rather than the things that were said. */\n.sys {\n align-self: center; max-width: 90%; text-align: center; margin: 6px 0;\n padding: 4px 10px; border-radius: 999px; background: var(--bc-subtle);\n font-size: var(--bc-fs-xs); line-height: 1.45; color: var(--bc-muted);\n animation: bc-fade-in 240ms ease both;\n}\n.sys .dot {\n display: inline-block; width: 6px; height: 6px; margin-right: 6px;\n border-radius: 50%; background: var(--bc-muted); vertical-align: middle;\n}\n.sys .t { color: var(--bc-faint); }\n.sys.k-status .dot { background: #4da3ff; }\n.sys.k-severity .dot { background: #e8912a; }\n.sys.k-assignment .dot { background: #8b7dff; }\n.sys.k-filed .dot { background: var(--bc-faint); }\n\n/* ---------- the conversation ----------\n\n A status change and a message are different things and are drawn as such: a\n change is a line on the rail above, a message is a bubble with a side. The\n reporter's own words sit right and accented, the other side's left beside an\n avatar that says who it is — the arrangement every chat has taught people to\n read without a legend. */\n.msg-row, .typing-row {\n display: grid; grid-template-columns: 28px minmax(0, 1fr); column-gap: 8px;\n max-width: 88%; margin-top: 10px;\n animation: bc-rise 260ms var(--bc-ease) both;\n}\n.msg-row.cont { margin-top: 3px; }\n.msg-row > *, .typing-row > * { grid-column: 2; }\n/* Fixed rows, so the avatar lines up with the bubble whatever else the\n message carries (a name above it, answers or a time below it). */\n.msg-row > .msg-who, .typing-row > .msg-who { grid-row: 1; }\n.msg-row > .bubble, .typing-row > .bubble { grid-row: 2; }\n.msg-row > .quick { grid-row: 3; }\n.msg-row > .msg-when { grid-row: 4; }\n.msg-row > .av, .typing-row > .av { grid-column: 1; grid-row: 2; align-self: end; }\n/* In a run of messages the avatar sits by the last bubble and the time under\n it, the way a chat marks where one turn ends. */\n.msg-row:has(+ .msg-row.cont) > .av { visibility: hidden; }\n.msg-row:has(+ .msg-row.cont) > .msg-when { display: none; }\n.msg-row.theirs, .typing-row { align-self: flex-start; }\n.msg-row.mine { align-self: flex-end; grid-template-columns: minmax(0, 1fr); justify-items: end; }\n.msg-row.mine > * { grid-column: 1; }\n.av {\n display: grid; place-items: center; width: 28px; height: 28px; border-radius: 50%;\n font-size: 11px; font-weight: 700; letter-spacing: .02em;\n background: color-mix(in srgb, var(--bc-fg) 10%, var(--bc-bg)); color: var(--bc-fg);\n}\n.av.bot {\n background: linear-gradient(140deg, var(--bc-accent), color-mix(in srgb, var(--bc-accent) 60%, #fff));\n color: var(--bc-on-accent); font-size: 14px;\n}\n.msg-who { font-size: var(--bc-fs-xs); font-weight: 620; color: var(--bc-muted); padding: 0 4px 4px; }\n.msg-who.auto { color: var(--bc-accent); display: inline-flex; align-items: center; gap: 5px; }\n.msg-who .tag {\n padding: 1px 6px; border-radius: 999px; font-size: 9.5px; font-weight: 700; letter-spacing: .04em;\n text-transform: uppercase; background: var(--bc-accent-soft); color: var(--bc-accent);\n}\n.bubble {\n justify-self: start; max-width: 100%;\n padding: 9px 13px; border-radius: 18px; font-size: 13.5px; line-height: 1.5;\n background: var(--bc-subtle); color: var(--bc-fg);\n border: 1px solid var(--bc-border);\n border-bottom-left-radius: 6px;\n}\n/* pre-wrap belongs to the text, not the box — on the box it would also render\n the markup's own indentation. */\n.msg-text { display: block; white-space: pre-wrap; overflow-wrap: anywhere; }\n.msg-row.mine .bubble {\n justify-self: end;\n background: var(--bc-accent); border-color: var(--bc-accent); color: var(--bc-on-accent);\n border-bottom-left-radius: 18px; border-bottom-right-radius: 6px;\n}\n.msg-row.cont.theirs .bubble { border-top-left-radius: 6px; }\n.msg-row.cont.mine .bubble { border-top-right-radius: 6px; }\n/* A reply nobody wrote: tinted with the accent and labelled \"Assistant\" above\n it, so it reads the same to someone who cannot see the colour at all. */\n.msg-row.auto .bubble { background: var(--bc-accent-soft); border-color: var(--bc-accent-line); }\n/* An attachment-only message has no text to wrap, so the bubble would be an\n empty coloured box around it. */\n.bubble.silent { padding: 0; background: none !important; border: 0; }\n.msg-when { font-size: 10.5px; color: var(--bc-faint); padding: 4px 4px 0; }\n.msg-row.pending { opacity: .6; }\n.msg-row.pending .msg-when::after { content: ' · Sending…'; }\n.msg-row.failed .bubble { border: 1px solid var(--bc-danger); }\n.msg-row.failed .msg-when { color: var(--bc-danger); }\n.msg-row.failed .msg-when::after { content: ' · Not sent'; }\n\n/* The assistant, thinking. Three dots in a bubble where its next message will be. */\n.typing { display: inline-flex; align-items: center; gap: 4px; padding: 12px 14px; justify-self: start; }\n.typing i {\n width: 7px; height: 7px; border-radius: 50%; background: var(--bc-accent); opacity: .45;\n animation: bc-typing 1.2s ease-in-out infinite;\n}\n.typing i:nth-child(2) { animation-delay: .15s; }\n.typing i:nth-child(3) { animation-delay: .3s; }\n.typing-row .msg-when { padding-top: 4px; }\n\n.att { display: flex; flex-direction: column; gap: 6px; margin-top: 6px; }\n.bubble.silent .att { margin-top: 0; }\n.att img {\n display: block; max-width: 100%; max-height: 190px; border-radius: var(--bc-r-md);\n border: 1px solid var(--bc-border); object-fit: cover; cursor: zoom-in;\n}\n.att audio { width: 232px; max-width: 100%; height: 34px; }\n.att video { max-width: 100%; max-height: 190px; border-radius: var(--bc-r-md); }\n.att .file {\n display: flex; align-items: center; gap: 8px; text-decoration: none;\n padding: 8px 10px; border-radius: 10px; font-size: 12px;\n background: var(--bc-elevated); border: 1px solid var(--bc-border); color: var(--bc-fg);\n}\n.att .file:hover { border-color: var(--bc-accent); }\n.att .file .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.att .file .sz { flex: none; color: var(--bc-faint); font-size: 11px; }\n\n/* ---------- the assistant's live card ----------\n A status header pinned between the report and the conversation: where the\n investigation is, as words and as a stepper. It does not scroll with the\n thread, because \"is anything happening?\" is the question on screen the\n whole time. */\n.assistant {\n flex: none; position: relative; overflow: hidden;\n margin: 10px 12px 0; padding: 12px 14px 12px;\n border-radius: var(--bc-r-lg); border: 1px solid var(--bc-border);\n background: var(--bc-subtle);\n transition: background 400ms ease, border-color 400ms ease;\n}\n.assistant.analyzing, .assistant.confirming, .assistant.needs_info {\n background: var(--bc-accent-soft); border-color: var(--bc-accent-line);\n}\n.assistant.resolved { background: color-mix(in srgb, var(--bc-ok) 9%, var(--bc-bg)); border-color: color-mix(in srgb, var(--bc-ok) 35%, var(--bc-border)); }\n/* On a call: the call's own card is what they are watching. This one states\n what is happening and stops moving — no spinner, no travelling bar. */\n.assistant.calling { background: var(--bc-bg); }\n.assistant.calling .as-progress i { transition: none; }\n.as-body.entering { animation: bc-rise 360ms var(--bc-ease) both; }\n.as-top { display: flex; gap: 11px; align-items: center; }\n.as-orb {\n flex: none; width: 32px; height: 32px; border-radius: 50%; display: grid; place-items: center;\n color: var(--bc-on-accent); font-size: 15px;\n background: linear-gradient(140deg, var(--bc-accent), color-mix(in srgb, var(--bc-accent) 60%, #fff));\n}\n.assistant.resolved .as-orb { background: var(--bc-ok); }\n.assistant.reviewing .as-orb { background: color-mix(in srgb, var(--bc-fg) 55%, var(--bc-bg)); }\n.as-spin { width: 16px; height: 16px; border-radius: 50%; border: 2px solid rgba(255,255,255,.4); border-top-color: #fff; animation: as-rot .8s linear infinite; }\n@keyframes as-rot { to { transform: rotate(360deg); } }\n.as-text { min-width: 0; flex: 1; }\n.as-title { font-weight: 650; font-size: 13.5px; letter-spacing: -.01em; color: var(--bc-fg); }\n.as-detail { font-size: 12px; line-height: 1.45; color: var(--bc-muted); margin-top: 2px; }\n.as-progress {\n height: 4px; margin: 12px 0 0; border-radius: 999px; overflow: hidden;\n background: color-mix(in srgb, var(--bc-fg) 9%, transparent);\n}\n.as-progress i {\n display: block; height: 100%; border-radius: inherit; background: var(--bc-accent);\n transition: width 700ms var(--bc-ease);\n}\n.assistant.resolved .as-progress i { background: var(--bc-ok); }\n.assistant.analyzing .as-progress i {\n background: linear-gradient(90deg, var(--bc-accent) 0%, color-mix(in srgb, var(--bc-accent) 45%, #fff) 50%, var(--bc-accent) 100%);\n background-size: 200% 100%; animation: bc-shimmer 1.6s linear infinite;\n}\n.as-steps {\n list-style: none; display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px;\n margin: 8px 0 0; padding: 0; font-size: 10.5px; color: var(--bc-faint);\n}\n.as-steps li { display: flex; align-items: center; gap: 5px; min-width: 0; white-space: nowrap; }\n.as-steps li .lbl { min-width: 0; overflow: hidden; text-overflow: ellipsis; }\n.as-steps li .dot { flex: none; width: 8px; height: 8px; border-radius: 50%; border: 1.5px solid currentColor; }\n.as-steps li.done { color: var(--bc-muted); }\n.as-steps li.done .dot { background: var(--bc-accent); border-color: var(--bc-accent); }\n.assistant.resolved .as-steps li.done .dot { background: var(--bc-ok); border-color: var(--bc-ok); }\n.as-steps li.active { color: var(--bc-fg); font-weight: 650; }\n.as-steps li.active .dot { border-color: var(--bc-accent); background: var(--bc-bg); animation: as-pulse 1.2s ease-in-out infinite; }\n@keyframes as-pulse { 50% { box-shadow: 0 0 0 4px color-mix(in srgb, var(--bc-accent) 25%, transparent); } }\n@media (prefers-reduced-motion: reduce) { .as-spin, .as-steps li.active .dot, .assistant.analyzing .as-progress i, .typing i { animation: none; } }\n\n/* Quick answers under the confirm question — only on the latest message. */\n.msg-row .quick { display: none; gap: 6px; flex-wrap: wrap; margin-top: 8px; }\n.msg-row:last-child .quick { display: flex; }\n.quick button {\n display: inline-flex; align-items: center; gap: 6px;\n font: inherit; font-size: 13px; font-weight: 600; padding: 8px 14px; border-radius: 999px; cursor: pointer;\n border: 1px solid var(--bc-accent); background: var(--bc-bg); color: var(--bc-accent);\n transition: background 160ms, color 160ms, transform 160ms var(--bc-ease);\n animation: bc-item-in 300ms var(--bc-ease) both;\n}\n.quick button:first-child { background: var(--bc-accent); color: var(--bc-on-accent); }\n.quick button:nth-child(2) { animation-delay: 60ms; }\n.quick button svg { font-size: 13px; }\n.quick button:hover { transform: translateY(-1px); background: var(--bc-accent-soft); }\n.quick button:first-child:hover { background: var(--bc-accent); filter: brightness(1.07); }\n\n/* ---------- the composer ---------- */\n.composer {\n flex: none; position: relative;\n padding: 10px 12px max(12px, env(safe-area-inset-bottom, 0px));\n /* The panel's own background, not --bc-elevated: the two are the same white\n in light mode but differ in dark, where a raised composer reads as a band\n laid over the panel rather than the foot of it. */\n background: var(--bc-bg);\n border-top: 1px solid var(--bc-border);\n}\n/* One rounded field holding the tools, the text and the send button — the\n shape every chat box has, so nobody has to look for where to type. */\n.composer .row {\n display: flex; align-items: flex-end; gap: 4px;\n padding: 5px; border-radius: 22px;\n background: var(--bc-subtle); border: 1px solid var(--bc-border);\n transition: border-color 160ms, box-shadow 160ms;\n}\n.composer .row:focus-within { border-color: var(--bc-accent); box-shadow: var(--bc-ring); }\n.composer textarea {\n flex: 1; min-width: 0; min-height: 34px; max-height: 120px; resize: none; padding: 7px 6px;\n font: inherit; font-size: 13.5px; line-height: 1.45;\n color: var(--bc-fg); background: transparent; border: 0; border-radius: 0;\n}\n.composer textarea:hover, .composer textarea:focus { border: 0; box-shadow: none; outline: none; }\n.composer .tools { display: flex; gap: 0; grid-template-columns: none; }\n.composer .tool {\n width: 34px; height: 34px; min-height: 0; padding: 0; display: grid; place-items: center; cursor: pointer;\n border-radius: 50%; border: 0; background: none; font-size: 17px;\n color: var(--bc-muted);\n}\n.composer .tool svg { font-size: 17px; }\n.composer .tool:hover:not(:disabled) { background: color-mix(in srgb, var(--bc-fg) 8%, transparent); color: var(--bc-fg); border: 0; transform: none; }\n.composer .tool:hover:not(:disabled) svg { color: var(--bc-fg); }\n.composer .tool:disabled { opacity: .4; cursor: default; }\n.composer .tool.recording { color: var(--bc-danger); background: color-mix(in srgb, var(--bc-danger) 12%, transparent); }\n.composer .tool.recording svg { color: var(--bc-danger); }\n.composer .send {\n width: 34px; height: 34px; flex: none; border-radius: 50%; cursor: pointer; font-size: 16px;\n background: var(--bc-accent); border: 0; color: var(--bc-on-accent);\n display: grid; place-items: center;\n transition: transform 160ms var(--bc-ease), opacity 160ms, filter 160ms;\n}\n.composer .send:hover:not(:disabled) { filter: brightness(1.08); transform: scale(1.05); }\n.composer .send:disabled { opacity: .35; cursor: default; background: var(--bc-faint); }\n.composer .staged { display: flex; flex-wrap: wrap; gap: 6px; padding: 0 0 8px; }\n.composer .chip {\n display: flex; align-items: center; gap: 6px; max-width: 200px;\n padding: 5px 7px 5px 10px; border-radius: 999px; font-size: var(--bc-fs-xs);\n background: var(--bc-subtle); border: 1px solid var(--bc-border); color: var(--bc-muted);\n animation: bc-item-in 240ms var(--bc-ease) both;\n}\n.composer .chip .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--bc-fg); }\n.composer .chip button {\n flex: none; width: 18px; height: 18px; display: grid; place-items: center;\n border: 0; border-radius: 50%; background: none; color: var(--bc-faint); cursor: pointer;\n}\n.composer .chip button:hover { color: var(--bc-danger); }\n.composer .chip.uploading { border-style: dashed; }\n.composer .note { padding: 6px 6px 0; font-size: var(--bc-fs-xs); color: var(--bc-muted); }\n.composer .note.err { color: var(--bc-danger); }\n.reccount { font-variant-numeric: tabular-nums; }\n\n/* Status carries the only colour in this list, so it is the thing the eye\n lands on — which is what someone opening this screen came to check. */\n.pill {\n flex: none; display: inline-flex; align-items: center; gap: 5px;\n font-size: 11px; font-weight: 650;\n padding: 3px 9px; border-radius: 999px;\n color: var(--bc-muted); background: var(--bc-subtle);\n}\n.pill::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }\n.pill.open { color: #c2410c; background: color-mix(in srgb, #f59e0b 14%, var(--bc-bg)); }\n.pill.in_progress { color: #1d6fd1; background: color-mix(in srgb, #3b82f6 14%, var(--bc-bg)); }\n.pill.resolved { color: #0f8a5a; background: color-mix(in srgb, #10b981 14%, var(--bc-bg)); }\n.pill.closed { color: var(--bc-muted); }\n:host(.bc-dark) .pill.open { color: #fbbf24; }\n:host(.bc-dark) .pill.in_progress { color: #7cb8ff; }\n:host(.bc-dark) .pill.resolved { color: #4ade9a; }\n@media (prefers-color-scheme: dark) {\n :host(.bc-auto) .pill.open { color: #fbbf24; }\n :host(.bc-auto) .pill.in_progress { color: #7cb8ff; }\n :host(.bc-auto) .pill.resolved { color: #4ade9a; }\n}\n\n/* ---------- does the fix work ---------- */\n.fix-verification {\n flex: none; margin: 10px 12px 0; padding: 12px 14px;\n border-radius: var(--bc-r-lg); border: 1px solid color-mix(in srgb, var(--bc-ok) 35%, var(--bc-border));\n background: color-mix(in srgb, var(--bc-ok) 8%, var(--bc-bg));\n font-size: var(--bc-fs-sm); color: var(--bc-fg);\n animation: bc-rise 280ms var(--bc-ease) both;\n}\n.fix-verification p { margin: 0 0 8px; font-weight: 620; }\n.fix-verification textarea { min-height: 56px; margin-bottom: 8px; font-size: 13px; background: var(--bc-bg); }\n.fix-verification button {\n padding: 7px 13px; font: inherit; font-size: 12.5px; font-weight: 620; border-radius: 999px; cursor: pointer;\n border: 1px solid var(--bc-border); background: var(--bc-bg); color: var(--bc-fg);\n}\n.fix-verification button[data-result=\"CONFIRMED\"] { background: var(--bc-ok); border-color: var(--bc-ok); color: #fff; }\n.fix-verification .feedback-status { margin: 6px 0 0; font-weight: 450; color: var(--bc-danger); font-size: var(--bc-fs-xs); }\n.fix-verification .feedback-status:empty { display: none; }\n\n/* A call in progress, in place of the form.\n ---------------------------------------------------------------------------\n Deliberately not styled as a chat. Nothing arrives here to read — the\n conversation is happening out loud — so this is a state, a line of\n reassurance about the report, and a way to hang up. */\n.call { display: flex; flex-direction: column; padding: 26px 0 0; animation: bc-fade-in 240ms ease both; }\n.call-head, .call-note { padding: 0 var(--bc-pad); }\n.call-head { display: flex; align-items: center; gap: 9px; margin-bottom: 8px; }\n.call-head strong { font-size: 15.5px; font-weight: 680; letter-spacing: -.01em; }\n.call-dot {\n width: 9px; height: 9px; border-radius: 50%; background: var(--bc-accent);\n animation: bc-call-pulse 1.6s ease-in-out infinite;\n}\n.call-dot.off { background: var(--bc-faint); animation: none; }\n@keyframes bc-call-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }\n/* Said while it is still ringing, because that is when somebody is deciding\n whether it is worth waiting. */\n.call-note { margin: 0 0 18px; font-size: 13px; color: var(--bc-muted); line-height: 1.55; }\n\n/* ---------- the same call, on top of the report it is about ---------- */\n/* A bar rather than its own screen, unlike the one the composer opens. The\n thread underneath is exactly what both people are talking about, so taking it\n off screen would be taking away the only shared reference. */\n.livecall {\n flex: none;\n display: flex; align-items: center; gap: 9px;\n margin: 10px 12px 0; padding: 9px 11px;\n border: 1px solid var(--bc-accent-line); border-radius: var(--bc-r-md);\n background: var(--bc-accent-soft); font-size: var(--bc-fs-sm); color: var(--bc-fg);\n animation: bc-rise 240ms var(--bc-ease) both;\n}\n.livecall .who { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.livecall .call-kept { font-size: 11px; opacity: .75; }\n.livecall .secs { font-variant-numeric: tabular-nums; color: var(--bc-muted); }\n.livecall .act {\n padding: 5px 11px; border-radius: 999px; border: 1px solid var(--bc-border);\n background: var(--bc-bg); color: var(--bc-fg);\n font: inherit; font-size: 12px; font-weight: 600; cursor: pointer;\n}\n.livecall .act:hover { background: var(--bc-subtle); }\n.livecall .act.danger { border-color: transparent; background: var(--bc-danger); color: #fff; }\n.livecall.over { color: var(--bc-muted); background: var(--bc-subtle); border-color: var(--bc-border); }\n.call-note code { font-size: 11px; }\n@media (prefers-reduced-motion: reduce) { .call-dot { animation: none; } }\n\n/* div.done, not .done: the assistant's stepper marks finished steps li.done,\n and this screen's padding was landing on every one of them. */\ndiv.done { padding: 44px 26px 44px; text-align: center; animation: bc-fade-in 240ms ease both; }\n.done .tick {\n width: 56px; height: 56px; margin: 0 auto 16px; border-radius: 50%;\n display: grid; place-items: center; font-size: 26px;\n background: color-mix(in srgb, var(--bc-ok) 14%, transparent); color: var(--bc-ok);\n box-shadow: 0 0 0 8px color-mix(in srgb, var(--bc-ok) 6%, transparent);\n animation: bc-pop 420ms var(--bc-ease) both;\n}\n.done h3 { margin: 0 0 6px; font-size: 17px; font-weight: 680; letter-spacing: -.015em; }\n.done p { margin: 0; font-size: 13px; line-height: 1.5; color: var(--bc-muted); }\n.done code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; padding: 1px 5px; border-radius: 5px; background: var(--bc-subtle); color: var(--bc-fg); }\n/* The likely wait. Set apart from the reference line above it by a rule rather\n than by weight: one is bookkeeping and the other is the answer to the\n question the reporter actually has, and they should not read as one\n paragraph. */\n.done .eta {\n margin: 14px auto 0;\n padding-top: 12px;\n border-top: 1px solid var(--bc-border);\n max-width: 30ch;\n line-height: 1.5;\n}\n.done .eta strong { color: var(--bc-fg); font-weight: 620; }\n/* The same figure on the report the reporter came back to. Sits under the\n status pill in the header block, muted — it is context, not the headline the\n status is. */\n.report-head .eta-line {\n grid-column: 1 / -1;\n font-size: var(--bc-fs-xs);\n color: var(--bc-muted);\n margin-top: 2px;\n}\n\n/* ---------- recording bar ---------- */\n.recbar {\n position: fixed; left: 50%; bottom: calc(24px + env(safe-area-inset-bottom, 0px)); transform: translateX(-50%);\n z-index: 2147483002;\n display: flex; align-items: center; gap: 12px;\n padding: 8px 8px 8px 16px; border-radius: 999px;\n background: var(--bc-elevated); color: var(--bc-fg);\n border: 1px solid var(--bc-border); box-shadow: var(--bc-shadow);\n animation: bc-item-in 300ms var(--bc-ease) both;\n}\n.recbar .dot { width: 9px; height: 9px; border-radius: 50%; background: var(--bc-danger); animation: bc-pulse 1.4s ease-in-out infinite; }\n.recbar .time { font-variant-numeric: tabular-nums; font-size: 13px; font-weight: 650; min-width: 38px; }\n.recbar .hint { font-size: 12px; color: var(--bc-muted); }\n.recbar button { height: 34px; padding: 0 14px; font-size: 12.5px; font-weight: 620; border-radius: 999px; cursor: pointer; }\n.recbar .stop { background: var(--bc-danger); border: 1px solid var(--bc-danger); color: #fff; }\n.recbar .stop:hover { filter: brightness(1.08); }\n\n/* ---------- phones ----------------------------------------------------------\n This widget runs inside a shadow root on somebody else's site, so its own\n stylesheet is the whole story: the host page cannot correct any of this, and\n anything that goes wrong here goes wrong on their site, in front of their\n visitor, at the moment that visitor is already annoyed enough to file a bug.\n -------------------------------------------------------------------------- */\n\n/* 100vh on a phone is the viewport with the browser chrome hidden, so the\n panel could be taller than the space it actually has and put \"Send report\"\n under the address bar. */\n@supports (height: 100dvh) {\n .panel { max-height: calc(100dvh - var(--bc-dock-h, 84px) - 52px); }\n}\n\n/* The dock sits in a corner of a page the widget does not own. On a notched\n phone that corner is under the home indicator, and in landscape it is under\n the camera housing. env() is 0 everywhere else, so the offsets are unchanged\n on hardware that has no insets. */\n.dock {\n padding-bottom: env(safe-area-inset-bottom, 0px);\n padding-right: env(safe-area-inset-right, 0px);\n padding-left: env(safe-area-inset-left, 0px);\n}\n\n/* Short desktop windows: the chat height gives way to the space there is. */\n@media (max-height: 820px) { .panel.is-chat { height: calc(100vh - var(--bc-dock-h, 84px) - 52px); } }\n\n/* A phone gets the whole screen: a sheet that slides up from the bottom, with\n the notch and the home indicator kept clear. The dock steps aside while it\n is open — the sheet has its own close button, and a launcher floating over\n the send button would be in the way of the one thing that matters. */\n@media (max-width: 480px) {\n .scrim { background: rgba(10,12,16,.4); }\n .panel, .scrim.at-right .panel, .scrim.at-left .panel, .scrim.at-bottom .panel, .scrim.at-top .panel {\n inset: 0; width: 100%; max-width: none; height: 100%; max-height: none;\n border-radius: 0; border: 0;\n animation: bc-sheet-in 380ms var(--bc-ease) both;\n }\n @supports (height: 100dvh) { .panel { height: 100dvh; max-height: none; } }\n .panel.is-chat { height: 100%; }\n .scrim.closing .panel { animation: bc-sheet-out 200ms ease both; }\n .head { padding-top: max(14px, env(safe-area-inset-top, 0px)); padding-left: max(var(--bc-pad), env(safe-area-inset-left, 0px)); padding-right: max(12px, env(safe-area-inset-right, 0px)); }\n .foot, .powered { padding-bottom: max(10px, env(safe-area-inset-bottom, 0px)); }\n .foot + .powered { padding-bottom: max(9px, env(safe-area-inset-bottom, 0px)); }\n .foot:has(+ .powered) { padding-bottom: 12px; }\n .dock.over { display: none; }\n .recbar .hint { display: none; }\n .msg-row, .typing-row { max-width: 92%; }\n}\n\n@media (pointer: coarse) {\n /* iOS zooms the whole page when a field it focuses is under 16px, and does\n not zoom back out. On the host's site that is not the widget's page to\n zoom — the visitor is left on a magnified checkout wondering what they\n touched. !important because this is a property of the device, not a\n preference about type. */\n input, textarea, select { font-size: 16px !important; }\n\n /* 44px is the floor a thumb hits reliably. Every control in this form was\n under it, on the one surface in the product designed for someone who is\n not an operator and gets no second attempt. */\n .seg button { min-height: 44px; }\n .tool { min-height: 56px; }\n button.primary, button.ghost { height: 44px; }\n .composer .send, .composer .tool { width: 40px; height: 40px; min-height: 0; }\n textarea { min-height: 104px; }\n .composer textarea { min-height: 40px; }\n\n /* The theme switch and the close button are small glyphs whose boxes cannot\n grow without moving the header text beside them, so each keeps its size\n and gets an invisible target drawn around it instead. */\n .themer button, .head .icon-btn, .thumb .drop, .brand-credit { position: relative; }\n .thumb .drop { position: absolute; opacity: 1; }\n .themer button::after, .head .icon-btn::after, .thumb .drop::after, .brand-credit::after {\n content: '';\n position: absolute;\n inset: -8px;\n }\n /* The themer's three buttons sit side by side, so their expanders would\n overlap each other without a gap to grow into. */\n .themer { gap: 4px; }\n}\n\n/* Hover is a lie on a touchscreen: it latches on tap and stays. The launcher's\n lift in particular was left applied after the panel closed. */\n@media (hover: none) {\n .launcher:hover { transform: none; filter: none; }\n .tool:hover:not(:disabled), .themer button:hover { transform: none; }\n}\n`;\n\nexport function mountWidget(client) {\n const host = document.createElement('div');\n host.setAttribute('data-bugcatch', 'widget');\n\n // A visitor who has picked a theme outranks the site's init() default: the\n // config says how the widget should look to someone who has never touched\n // it, not how it should look to someone who has.\n let theme = readStoredTheme() || themeValue(client.config.theme);\n host.className = themeClass(theme);\n\n const root = host.attachShadow({ mode: 'open' });\n const style = document.createElement('style');\n style.textContent = css(client.config.accent || '#4F46E5');\n root.appendChild(style);\n\n const position = POSITIONS[client.config.widgetPosition] ? client.config.widgetPosition : 'bottom-right';\n const dock = document.createElement('div');\n dock.className = `dock ${position.includes('right') ? 'at-right' : 'at-left'}`\n + (position.startsWith('top') ? ' at-top' : '');\n dock.style.cssText = POSITIONS[position];\n\n const themer = document.createElement('div');\n themer.className = 'themer';\n themer.setAttribute('role', 'group');\n themer.setAttribute('aria-label', 'Report widget theme');\n themer.innerHTML = THEMES.map((t) => `<button type=\"button\" data-theme=\"${t.value}\" `\n + `title=\"${t.label}\" aria-label=\"${t.label}\" aria-pressed=\"${t.value === theme}\">${t.icon}</button>`).join('');\n\n themer.addEventListener('click', (e) => {\n const btn = e.target.closest('button[data-theme]');\n if (!btn || btn.dataset.theme === theme) return;\n theme = btn.dataset.theme;\n // The panel lives in the same shadow root, so an open form re-themes with\n // it rather than waiting for the next open.\n host.className = themeClass(theme);\n writeStoredTheme(theme);\n themer.querySelectorAll('button').forEach((b) => {\n b.setAttribute('aria-pressed', String(b.dataset.theme === theme));\n });\n });\n\n const launcher = document.createElement('button');\n launcher.className = 'launcher';\n launcher.type = 'button';\n launcher.innerHTML = `<span class=\"ico\" aria-hidden=\"true\"><span class=\"when-closed\">${ICONS.chat}</span>`\n + `<span class=\"when-open\">${ICONS.chevronDown}</span></span><span>Report a bug</span>`;\n launcher.setAttribute('aria-haspopup', 'dialog');\n launcher.setAttribute('aria-expanded', 'false');\n\n let open = null;\n launcher.addEventListener('click', () => {\n // The launcher stays visible with the panel anchored just above it, so a\n // second click reads as \"close that\" rather than doing nothing.\n if (open) { open.close(); return; }\n dock.classList.add('over');\n launcher.setAttribute('aria-expanded', 'true');\n open = openPanel({\n root,\n host,\n dock,\n client,\n onClose: () => {\n open = null;\n dock.classList.remove('over');\n launcher.setAttribute('aria-expanded', 'false');\n },\n });\n });\n\n dock.append(themer, launcher);\n root.appendChild(dock);\n\n document.body.appendChild(host);\n // Integration hook: BugCatch.open() / close() / isOpen() drive the same\n // launcher a person would press, so there is one way in to the panel.\n client.widgetControls = {\n open: () => { if (!open) launcher.click(); },\n close: () => { open?.close(); },\n isOpen: () => Boolean(open),\n };\n return () => { open?.destroy(); host.remove(); client.widgetControls = null; };\n}\n\n/** Normalises a configured theme to one of the three the switch offers. */\nfunction themeValue(theme) {\n return theme === 'dark' || theme === 'light' ? theme : 'auto';\n}\n\nfunction themeClass(theme) {\n if (theme === 'dark') return 'bc-dark';\n if (theme === 'light') return 'bc-light';\n return 'bc-auto';\n}\n\nfunction openPanel({ root, host, dock, client, onClose }) {\n const cfg = client.config;\n const canCapture = cfg.attachments !== false;\n const canShoot = canCapture && screenshotSupported();\n const canRecord = canCapture && captureSupported();\n\n // Uploaded as they are added rather than on send: the upload is the slow\n // part, and doing it while the reporter is still typing means Send is\n // instant and a failure is visible next to the thing that failed.\n const items = [];\n let priority = 'MEDIUM';\n let recorder = null;\n let sending = false;\n\n const scrim = document.createElement('div');\n // The panel opens out of whichever corner the dock is in, clearing it by the\n // dock's measured height rather than a guess at the launcher's size.\n scrim.className = `scrim ${dock.classList.contains('at-left') ? 'at-left' : 'at-right'} `\n + `${dock.classList.contains('at-top') ? 'at-top' : 'at-bottom'}`;\n scrim.style.setProperty('--bc-dock-h', `${Math.round(dock.getBoundingClientRect().height)}px`);\n scrim.innerHTML = `\n <div class=\"panel\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"bc-h\">\n ${headHtml({\n id: 'bc-h',\n title: 'Tell us what went wrong',\n sub: \"Our assistant starts looking right away. We attach the page details for you.\",\n })}\n <div class=\"body\">\n <div class=\"slot\" role=\"alert\"></div>\n <div class=\"whoami\" hidden></div>\n <!-- The questions the assistant can answer from their account and\n close by itself. Drawn only when the project offers any. -->\n <div class=\"picks\" hidden></div>\n <div class=\"known\" hidden></div>\n <div class=\"compose\">\n <div class=\"field\">\n <label for=\"bc-title\">What went wrong?</label>\n <input id=\"bc-title\" type=\"text\" placeholder=\"e.g. The checkout button does nothing\" autocomplete=\"off\"\n aria-describedby=\"bc-title-err\" />\n <p class=\"field-err\" id=\"bc-title-err\" hidden></p>\n </div>\n <div class=\"field\">\n <label for=\"bc-desc\">Anything else that helps <span class=\"opt\">(optional)</span></label>\n <textarea id=\"bc-desc\" placeholder=\"What you were doing, what you expected, what happened instead.\"></textarea>\n </div>\n <!-- Drawn only when this project asks for an identifier and the report\n does not already carry one. Between the description and the\n severity because that is where it belongs in the reading order:\n it is a question about what they just typed. -->\n <div class=\"refs\" hidden></div>\n <div class=\"field\">\n <label id=\"bc-prio-l\">How much is it getting in your way?</label>\n <div class=\"seg\" role=\"group\" aria-labelledby=\"bc-prio-l\">\n ${PRIORITIES.map((p) => `\n <button type=\"button\" data-prio=\"${p.value}\" aria-pressed=\"${p.value === priority}\">${p.label}</button>\n `).join('')}\n </div>\n </div>\n ${canCapture ? `\n <div class=\"field\">\n <div class=\"field-head\">\n <label id=\"bc-att-l\">Show us <span class=\"opt\">(optional)</span></label>\n <span class=\"count\" aria-live=\"polite\"></span>\n </div>\n <div class=\"tools\" role=\"group\" aria-labelledby=\"bc-att-l\">\n ${canShoot ? `<button class=\"tool\" type=\"button\" data-act=\"shot\">${ICONS.camera}Screenshot</button>` : ''}\n ${canRecord ? `<button class=\"tool\" type=\"button\" data-act=\"rec\">${ICONS.video}Record screen</button>` : ''}\n <button class=\"tool\" type=\"button\" data-act=\"file\">${ICONS.paperclip}Upload file</button>\n </div>\n <div class=\"thumbs\"></div>\n <input type=\"file\" accept=\"${REPORT_MIMES.join(',')}\" multiple hidden />\n </div>` : ''}\n </div>\n </div>\n <div class=\"foot\">\n <span class=\"note\" aria-live=\"polite\"></span>\n ${client.hasReports() ? '<button class=\"quiet\" type=\"button\" data-act=\"history\">Your reports</button>' : ''}\n <span class=\"spacer\"></span>\n <!-- Hidden until the API confirms this project can take a call, which\n most cannot. Drawn here rather than as a second launcher because it\n is the same errand as Send report — the difference is only whether\n the reporter would rather say it than type it. -->\n <button class=\"ghost\" type=\"button\" data-act=\"call\" hidden>${ICONS.phone}Talk to the team</button>\n <button class=\"primary\" type=\"button\" data-act=\"send\">Send report ${ICONS.send}</button>\n </div>\n <div class=\"powered\">Powered by\n <a class=\"brand-credit\" href=\"https://bugcatch.sphoro.com\" target=\"_blank\" rel=\"noopener noreferrer\" aria-label=\"BugCatch\">${BRAND}</a>\n </div>\n </div>`;\n\n const $ = (sel) => scrim.querySelector(sel);\n const titleInput = $('#bc-title');\n // Held, not looked up: the history and report screens replace the panel's\n // markup, and a draft saved on the way out must still read what was typed.\n const descInput = $('#bc-desc');\n const slot = $('.slot');\n const thumbs = $('.thumbs');\n const filePicker = scrim.querySelector('input[type=file]');\n const sendBtn = $('[data-act=\"send\"]');\n const callBtn = $('[data-act=\"call\"]');\n const note = $('.note');\n const compose = $('.compose');\n const whoami = $('.whoami');\n const refsBox = $('.refs');\n const known = $('.known');\n const picks = $('.picks');\n const historyBtn = $('[data-act=\"history\"]');\n\n const draftStatus = document.createElement('div');\n draftStatus.setAttribute('role', 'status');\n draftStatus.className = 'note';\n compose.prepend(draftStatus);\n const savedDraftKey = draftKey(cfg, readSession(cfg.projectId)?.email);\n let draftLoaded = cfg.drafts === false;\n let draftSubmitted = false;\n let draftWrites = Promise.resolve();\n let draftTimer;\n function persistDraft() {\n clearTimeout(draftTimer);\n if (!draftLoaded || draftSubmitted || cfg.drafts === false) return draftWrites;\n const value = titleInput.value || descInput.value || items.length ? {\n title: titleInput.value, description: descInput.value, priority,\n items: items.map(({ blob, mime, kind }) => ({ blob, mime, kind })),\n } : null;\n draftWrites = draftWrites.catch(() => {}).then(() => draftOperation(savedDraftKey, value))\n .then(() => { draftStatus.textContent = value ? 'Draft saved on this device' : ''; })\n .catch(() => { draftStatus.textContent = 'Draft could not be saved. Keep this tab open.'; });\n return draftWrites;\n }\n function scheduleDraft() { clearTimeout(draftTimer); draftTimer = setTimeout(persistDraft, 250); }\n compose.addEventListener('input', scheduleDraft);\n compose.addEventListener('change', scheduleDraft);\n const onPageHide = () => { void persistDraft(); };\n window.addEventListener('pagehide', onPageHide);\n function reconnectUploads() { items.filter((it) => it.status === 'failed').forEach(upload); }\n window.addEventListener('online', reconnectUploads);\n if (cfg.drafts !== false) {\n // Disable composition briefly so a late storage read cannot replace new text.\n compose.inert = true;\n readDraft(savedDraftKey).then((draft) => {\n if (closed || !draft) return;\n draftStatus.innerHTML = '<span>You have an unfinished report on this device.</span> <button type=\"button\" data-resume>Resume report</button> <button type=\"button\" data-discard>Discard</button>';\n draftStatus.querySelector('[data-resume]').onclick = () => {\n titleInput.value = draft.title || '';\n descInput.value = draft.description || '';\n priority = PRIORITIES.some((p) => p.value === draft.priority) ? draft.priority : 'MEDIUM';\n scrim.querySelectorAll('[data-prio]').forEach((b) => b.setAttribute('aria-pressed', String(b.dataset.prio === priority)));\n for (const item of (draft.items || []).slice(0, cfg.maxAttachments ?? 5)) addItem(item);\n draftLoaded = true;\n void persistDraft();\n };\n draftStatus.querySelector('[data-discard]').onclick = () => {\n draftLoaded = true;\n void persistDraft();\n };\n }).catch(() => { draftStatus.textContent = 'Draft storage is unavailable. Keep this tab open.'; })\n .finally(() => { compose.inert = false; if (!draftStatus.querySelector('[data-resume]')) draftLoaded = true; });\n }\n\n /* ---------- lifecycle ---------- */\n\n const previouslyFocused = document.activeElement;\n const rootStyle = document.documentElement.style;\n const previousOverflow = rootStyle.overflow;\n rootStyle.overflow = 'hidden';\n\n let closed = false;\n // Assigned further down, once `showReport` exists to be called back into.\n let stopListening = null;\n function destroy() {\n if (closed) return;\n void persistDraft();\n closed = true;\n clearTimeout(draftTimer);\n window.removeEventListener('pagehide', onPageHide);\n window.removeEventListener('online', reconnectUploads);\n recorder?.cancel();\n // Stop listening before the DOM goes: a frame arriving after this would\n // redraw a panel that is no longer on the page.\n stopListening?.();\n items.forEach((it) => it.previewUrl && URL.revokeObjectURL(it.previewUrl));\n rootStyle.overflow = previousOverflow;\n document.removeEventListener('keydown', onKeydown, true);\n scrim.remove();\n root.querySelector('.recbar')?.remove();\n dock.hidden = false;\n host.style.opacity = '';\n onClose?.();\n }\n\n function close() {\n if (closed) return;\n // Let the exit animation play, but never wait on an event that a\n // reduced-motion user's near-zero animation might deliver unpredictably.\n scrim.classList.add('closing');\n setTimeout(destroy, 160);\n try { previouslyFocused?.focus?.(); } catch { /* element may be gone */ }\n }\n\n function onKeydown(e) {\n if (e.key === 'Escape') {\n e.stopPropagation();\n if (recorder) { stopRecording({ keep: false }); return; }\n if (!sending) close();\n return;\n }\n if (e.key !== 'Tab' || recorder) return;\n // Focus trap: the panel is modal, so Tab must not reach the host page.\n const focusable = [...scrim.querySelectorAll(\n 'button:not(:disabled), input:not([hidden]), textarea, select, [href]',\n )].filter((el) => el.offsetParent !== null || el === document.activeElement);\n if (!focusable.length) return;\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n const active = scrim.getRootNode().activeElement;\n if (e.shiftKey && active === first) { e.preventDefault(); last.focus(); }\n else if (!e.shiftKey && active === last) { e.preventDefault(); first.focus(); }\n }\n\n document.addEventListener('keydown', onKeydown, true);\n scrim.addEventListener('click', (e) => { if (e.target === scrim && !sending) close(); });\n $('[data-act=\"close\"]').addEventListener('click', close);\n\n /* ---------- severity ---------- */\n\n scrim.querySelectorAll('[data-prio]').forEach((btn) => {\n btn.addEventListener('click', () => {\n priority = btn.dataset.prio;\n scheduleDraft();\n scrim.querySelectorAll('[data-prio]').forEach((b) => {\n b.setAttribute('aria-pressed', String(b === btn));\n });\n });\n });\n\n /* ---------- attachments ---------- */\n\n function say(text, kind = 'err') {\n slot.innerHTML = text\n ? `<div class=\"msg ${kind}\">${kind === 'err' ? ICONS.alert : ''}<span>${escapeHtml(text)}</span></div>`\n : '';\n }\n\n // The one required field says what is missing right under itself, rather\n // than in a banner the reporter has to connect back to it.\n const titleErr = $('#bc-title-err');\n function flagTitle(text) {\n titleErr.hidden = !text;\n titleErr.innerHTML = text ? `${ICONS.alert}<span>${escapeHtml(text)}</span>` : '';\n titleInput.classList.toggle('invalid', Boolean(text));\n titleInput.setAttribute('aria-invalid', String(Boolean(text)));\n }\n titleInput.addEventListener('input', () => { if (titleInput.value.trim()) flagTitle(''); });\n\n function refreshSendState() {\n const busy = items.some((it) => it.status === 'uploading');\n sendBtn.disabled = sending || busy || items.some((it) => it.status === 'failed');\n note.textContent = busy ? 'Waiting for the upload to finish…' : items.some((it) => it.status === 'failed') ? 'Retry or remove failed attachments before sending.' : '';\n const max = cfg.maxAttachments ?? 5;\n scrim.querySelectorAll('.tool').forEach((t) => {\n t.disabled = sending || items.length >= max;\n });\n const count = $('.field-head .count');\n if (count) count.textContent = items.length ? `${items.length} of ${max}` : '';\n }\n\n function drawThumb(item) {\n const el = item.el;\n const label = item.kind === 'VIDEO' ? 'Video' : 'Shot';\n const preview = item.kind === 'VIDEO'\n ? `<video src=\"${item.previewUrl}\" muted playsinline preload=\"metadata\"></video>`\n : `<img src=\"${item.previewUrl}\" alt=\"\" />`;\n\n let veil = '';\n if (item.status === 'uploading') {\n veil = `<div class=\"veil\"><div class=\"spinner\"></div><div class=\"bar\"><i style=\"width:${Math.round(item.progress * 100)}%\"></i></div></div>`;\n } else if (item.status === 'failed') {\n veil = `<div class=\"veil err\"><button type=\"button\" data-act=\"retry\" aria-label=\"Retry upload\">${ICONS.retry}</button>Failed</div>`;\n }\n\n el.innerHTML = `\n ${preview}\n <span class=\"kind\">${label} · ${humanSize(item.size)}</span>\n <button class=\"drop\" type=\"button\" data-act=\"drop\" aria-label=\"Remove ${label.toLowerCase()}\">${ICONS.close}</button>\n ${veil}`;\n\n el.querySelector('[data-act=\"drop\"]').addEventListener('click', () => removeItem(item));\n el.querySelector('[data-act=\"retry\"]')?.addEventListener('click', () => upload(item));\n }\n\n function removeItem(item) {\n const i = items.indexOf(item);\n if (i >= 0) items.splice(i, 1);\n if (item.previewUrl) URL.revokeObjectURL(item.previewUrl);\n item.el.remove();\n void persistDraft();\n refreshSendState();\n }\n\n async function upload(item) {\n item.status = 'uploading';\n item.progress = 0;\n drawThumb(item);\n refreshSendState();\n try {\n const { storageKey, mime } = await client.uploadAttachment(item.blob, item.mime, (p) => {\n item.progress = p;\n // Only the width changes while uploading — repainting the whole tile\n // would restart the entrance animation on every progress event.\n const fill = item.el.querySelector('.bar i');\n if (fill) fill.style.width = `${Math.round(p * 100)}%`;\n });\n item.storageKey = storageKey;\n item.mime = mime;\n item.status = 'ready';\n } catch (err) {\n item.status = 'failed';\n say(err.message.replace('[BugCatch] ', ''));\n }\n drawThumb(item);\n refreshSendState();\n }\n\n function addItem({ blob, mime, kind }) {\n const max = cfg.maxAttachmentBytes ?? 25 * 1024 * 1024;\n if (blob.size > max) {\n say(`That file is ${humanSize(blob.size)} — the limit is ${humanSize(max)}.`);\n return;\n }\n // Held to the report set, not the full upload set. A report's attachments\n // are typed SCREENSHOT or VIDEO, so a document accepted here would be filed\n // as a screenshot and render as a broken image. Documents and voice notes\n // go on a message instead, where they keep their own type.\n const normalized = normalizeMime(mime || blob.type, REPORT_MIMES);\n if (!normalized) {\n say('Only PNG, JPEG, WebP, WebM and MP4 files can be attached to a report.');\n return;\n }\n const item = {\n blob, kind, mime: normalized, size: blob.size,\n previewUrl: URL.createObjectURL(blob),\n status: 'uploading', progress: 0, storageKey: null,\n el: document.createElement('div'),\n };\n item.el.className = 'thumb';\n items.push(item);\n thumbs.appendChild(item.el);\n say('');\n void persistDraft();\n upload(item);\n }\n\n /* Hide our own UI so the capture shows the page, not this dialog. */\n async function withWidgetHidden(fn) {\n host.style.opacity = '0';\n host.style.pointerEvents = 'none';\n // One frame so the browser has actually painted the hidden state before\n // the picker — and the frame we grab — is taken.\n await new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(r)));\n try {\n return await fn();\n } finally {\n host.style.opacity = '';\n host.style.pointerEvents = '';\n }\n }\n\n scrim.querySelector('[data-act=\"shot\"]')?.addEventListener('click', async () => {\n try {\n const shot = await withWidgetHidden(captureScreenshot);\n addItem(shot);\n } catch (err) {\n // NotAllowedError is the user dismissing the picker — not a failure.\n if (err?.name !== 'NotAllowedError') say(`Screenshot failed: ${err.message}`);\n }\n });\n\n scrim.querySelector('[data-act=\"file\"]')?.addEventListener('click', () => filePicker.click());\n filePicker?.addEventListener('change', () => {\n const room = (cfg.maxAttachments ?? 5) - items.length;\n [...filePicker.files].slice(0, Math.max(room, 0)).forEach((file) => {\n addItem({\n blob: file,\n mime: file.type,\n kind: file.type.startsWith('video/') ? 'VIDEO' : 'SCREENSHOT',\n });\n });\n filePicker.value = '';\n });\n\n /* ---------- recording ---------- */\n\n scrim.querySelector('[data-act=\"rec\"]')?.addEventListener('click', async () => {\n const maxMs = cfg.maxRecordingMs ?? 120_000;\n let bar;\n try {\n recorder = await startRecording({\n maxMs,\n onTick: (s) => { if (bar) bar.querySelector('.time').textContent = humanDuration(s); },\n onAutoStop: () => { if (recorder) stopRecording({ keep: true }); },\n });\n } catch (err) {\n if (err?.name !== 'NotAllowedError') say(`Recording failed: ${err.message}`);\n return;\n }\n\n // Step out of the way for the duration: a recording of this dialog sitting\n // over the page is not what anyone means by \"record the bug\".\n scrim.style.display = 'none';\n dock.hidden = true;\n\n bar = document.createElement('div');\n bar.className = 'recbar';\n bar.innerHTML = `\n <span class=\"dot\"></span>\n <span class=\"time\">0:00</span>\n <span class=\"hint\">Recording — up to ${humanDuration(Math.round(maxMs / 1000))}</span>\n <button class=\"stop\" type=\"button\">Stop</button>\n <button class=\"ghost\" type=\"button\">Discard</button>`;\n bar.querySelector('.stop').addEventListener('click', () => stopRecording({ keep: true }));\n bar.querySelector('.ghost').addEventListener('click', () => stopRecording({ keep: false }));\n root.appendChild(bar);\n bar.querySelector('.stop').focus();\n });\n\n async function stopRecording({ keep }) {\n const rec = recorder;\n if (!rec) return;\n recorder = null;\n root.querySelector('.recbar')?.remove();\n scrim.style.display = '';\n dock.hidden = false;\n\n if (!keep) { rec.cancel(); titleInput.focus(); return; }\n try {\n const clip = await rec.stop();\n if (clip.blob.size) addItem(clip);\n else say('That recording came back empty — nothing was captured.');\n } catch (err) {\n say(`Recording failed: ${err.message}`);\n }\n titleInput.focus();\n }\n\n /* ---------- which record is this about ---------- */\n\n // What this project asks for, and what the reporter has answered.\n //\n // The question is asked at Send and not before, which is the whole design of\n // it. A form that opens with \"loan number\" above \"what went wrong\" tells\n // somebody they have paperwork to do before they may complain, and most of\n // them will not — while the same question, asked once, after they have said\n // their piece, and only when what they said did not already contain the\n // answer, is a reasonable thing to be asked. Most reporters never see it: the\n // loan number was in the sentence.\n let refConfig = { policy: 'off', fields: [] };\n let refOpen = false;\n let refChoice = null; // which field they are answering; null = not picked yet\n let refValue = '';\n let refError = '';\n // What we read in their text but could not place for sure, offered as taps:\n // [{ value, kinds: [{ key, label }] }]. `refMode` says why the card is open —\n // 'confirm' when the text had two answers, 'suggest' when it had an unlabelled\n // one, 'ask' when it had none.\n let refCandidates = [];\n let refMode = 'ask';\n // Set once they have answered or explicitly skipped, so Send stops asking.\n let refSettled = false;\n // Set once we have said \"that does not look right\" about the current value.\n // Its own flag rather than reusing refSettled, which was the first attempt\n // and was a loop: the warning returns early, the next press re-validates the\n // same unchanged value, warns again, and the report can never be sent. One\n // warning per value, and then it is their call.\n let refWarned = false;\n\n client.referenceFields()\n .then((cfg) => { refConfig = cfg; showFound(); })\n .catch(() => { /* off is the safe answer to every failure */ });\n\n /** What the text says: found (sure), conflicts (ask which), loose (offer). */\n function readText() {\n if (refConfig.policy === 'off' || !refConfig.fields.length) return { found: [], conflicts: [], loose: [] };\n return readReferences(refConfig.fields, [titleInput.value, descInput.value]);\n }\n\n /** Sure enough to send without a question: something found, nothing contested. */\n function textAnswers(read = readText()) {\n return read.found.length > 0 && read.conflicts.length === 0;\n }\n\n /** The answer to send, or an empty list. */\n function answeredReference() {\n if (!refChoice || !refValue.trim()) return [];\n return [{ key: refChoice, value: refValue.trim().slice(0, 120) }];\n }\n\n // A quiet line under the description while they type, so somebody who put\n // their loan number in the sentence can see it was read and does not go\n // looking for a box to type it into again.\n const foundNote = document.createElement('p');\n foundNote.className = 'ref-found';\n foundNote.setAttribute('aria-live', 'polite');\n foundNote.hidden = true;\n descInput.closest('.field').after(foundNote);\n let foundTimer;\n function showFound() {\n const read = readText();\n if (textAnswers(read)) {\n const list = read.found.slice(0, 2).map((r) => `${escapeHtml(r.label)} <b>${escapeHtml(shortId(r.value))}</b>`).join(', ');\n foundNote.innerHTML = `${ICONS.check}<span>Found your ${list} — no need to add it again.</span>`;\n foundNote.hidden = false;\n // They have answered in the text; a card still asking would be not listening.\n if (refOpen && !answeredReference().length) { refOpen = false; drawRefs(); }\n } else {\n foundNote.hidden = true;\n foundNote.innerHTML = '';\n }\n }\n const queueFound = () => { clearTimeout(foundTimer); foundTimer = setTimeout(showFound, 250); };\n titleInput.addEventListener('input', queueFound);\n descInput.addEventListener('input', queueFound);\n\n /** A UUID is long; the chip shows its ends, the title shows all of it. */\n function shortId(value) {\n return value.length > 20 ? `${value.slice(0, 8)}…${value.slice(-4)}` : value;\n }\n\n function candidateFor(value) {\n const v = value.trim().toLowerCase();\n return refCandidates.find((c) => c.value.toLowerCase() === v) || null;\n }\n\n /** Pick a candidate: its value, and its kind when only one kind fits. */\n function takeCandidate(c) {\n refValue = c.value;\n if (c.kinds.length === 1) refChoice = c.kinds[0].key;\n else if (!c.kinds.some((k) => k.key === refChoice)) refChoice = null;\n refError = '';\n refWarned = false;\n }\n\n function drawRefs() {\n if (!refOpen) { refsBox.hidden = true; refsBox.innerHTML = ''; return; }\n const field = refConfig.fields.find((f) => f.key === refChoice) || null;\n const optional = refConfig.policy !== 'required';\n const current = candidateFor(refValue);\n // Kinds worth offering: the ones the picked value could be, when we know;\n // every kind otherwise. Never zero — a guess of ours must not lock out the\n // right answer.\n const kinds = current && current.kinds.length > 1\n ? refConfig.fields.filter((f) => current.kinds.some((k) => k.key === f.key))\n : refConfig.fields;\n // Two loan numbers: the kind is not in question, only which one — so the\n // kind chips would be a second question nobody needs to answer.\n const sharedKind = refCandidates.length > 1\n && refCandidates.every((c) => c.kinds.length === 1 && c.kinds[0].key === refCandidates[0].kinds[0].key);\n\n const head = {\n confirm: refCandidates.length > 1\n ? ['Which one is this about?', 'We found more than one ID in your message. Pick the one this problem is about.']\n : ['Is this the right ID?', 'This could be more than one kind of ID. Tell us which it is.'],\n suggest: ['Is this your ID?', 'We spotted this in your message. Tell us what it is so we can find your record.'],\n ask: optional\n ? ['One more thing', 'So we can find your record — this helps us answer faster.']\n : ['We need one detail', 'We need one of these to look this up.'],\n }[refMode];\n\n refsBox.hidden = false;\n // A handful of kinds read best as chips to tap; a long list goes back to\n // a select so the card does not turn into a wall of pills.\n const many = kinds.length > 6;\n refsBox.innerHTML = `\n <div class=\"ask-head\">\n <span class=\"avatar\" aria-hidden=\"true\">${ICONS.sparkle}</span>\n <div>\n <strong>${escapeHtml(head[0])}</strong>\n <p class=\"why\">${escapeHtml(head[1])}</p>\n </div>\n </div>\n ${refCandidates.length > 1 ? `\n <div class=\"chips cands\" role=\"group\" aria-label=\"IDs found in your message\">\n ${refCandidates.map((c, i) => `\n <button type=\"button\" data-ref-cand=\"${i}\" title=\"${escapeHtml(c.value)}\"\n aria-pressed=\"${!!current && current === c}\">${escapeHtml(shortId(c.value))}</button>\n `).join('')}\n </div>` : ''}\n ${kinds.length > 1 && !many && !sharedKind ? `\n <div class=\"chips\" role=\"group\" aria-label=\"Which detail you are giving\">\n ${kinds.map((f) => `\n <button type=\"button\" data-ref-kind=\"${escapeHtml(f.key)}\" aria-pressed=\"${!!field && f.key === field.key}\">${escapeHtml(f.label)}</button>\n `).join('')}\n </div>` : ''}\n <div class=\"row\">\n ${many ? `\n <select class=\"ref-kind\" aria-label=\"Which detail you are giving\">\n ${field ? '' : '<option value=\"\" selected disabled>What is it?</option>'}\n ${kinds.map((f) => `\n <option value=\"${escapeHtml(f.key)}\" ${field && f.key === field.key ? 'selected' : ''}>${escapeHtml(f.label)}</option>\n `).join('')}\n </select>` : ''}\n <input class=\"ref-value${refError ? ' invalid' : ''}\" type=\"${field?.inputMode === 'email' ? 'email' : 'text'}\"\n inputmode=\"${escapeHtml(field?.inputMode === 'numeric' || field?.inputMode === 'tel' ? 'numeric' : 'text')}\"\n value=\"${escapeHtml(refValue)}\"\n placeholder=\"${escapeHtml(field ? (field.placeholder || field.label) : 'Your ID')}\"\n aria-label=\"${escapeHtml(field ? field.label : 'Your ID')}\" ${refError ? 'aria-invalid=\"true\"' : ''} />\n </div>\n ${field?.hint ? `<p class=\"hint\">${escapeHtml(field.hint)}</p>` : ''}\n ${refError ? `<p class=\"field-err\" role=\"alert\">${ICONS.alert}<span>${escapeHtml(refError)}</span></p>` : ''}\n ${optional ? `<button type=\"button\" class=\"link\" data-act=\"ref-skip\">${\n refMode === 'ask' ? 'I don\\'t have this — send without it' : 'Not sure — send as it is'}</button>` : ''}`;\n\n refsBox.querySelectorAll('[data-ref-cand]').forEach((chip) => {\n chip.addEventListener('click', () => {\n takeCandidate(refCandidates[Number(chip.dataset.refCand)]);\n drawRefs();\n });\n });\n refsBox.querySelectorAll('[data-ref-kind]').forEach((chip) => {\n chip.addEventListener('click', () => {\n refChoice = chip.dataset.refKind;\n refError = '';\n refWarned = false;\n drawRefs();\n });\n });\n const value = refsBox.querySelector('.ref-value');\n value.addEventListener('input', () => {\n refValue = value.value;\n refError = '';\n // A changed value has not been warned about yet.\n refWarned = false;\n });\n // Enter in the box is the same gesture as pressing Send, which is what\n // somebody who has just been asked one question expects it to be.\n value.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') { e.preventDefault(); sendBtn.click(); }\n });\n refsBox.querySelector('.ref-kind')?.addEventListener('change', (e) => {\n refChoice = e.target.value;\n refError = '';\n drawRefs();\n refsBox.querySelector('.ref-value')?.focus();\n });\n refsBox.querySelector('[data-act=\"ref-skip\"]')?.addEventListener('click', () => {\n // Skipping is answering. Without this the panel asks again on the next\n // press and the reporter is in a loop with a button that does nothing.\n // The text still goes; the server reads it either way.\n refSettled = true;\n refOpen = false;\n refChoice = null;\n refValue = '';\n refError = '';\n refWarned = false;\n drawRefs();\n say('');\n });\n\n // Focus where the next decision is: the kind, when that is all that is\n // missing; the box otherwise.\n const next = !field && refValue ? refsBox.querySelector('[data-ref-kind], .ref-kind') : null;\n (next || value).focus();\n }\n\n /**\n * Open the question, pre-filled with the best of what the text offered.\n *\n * Conflicts first — the text said two things and only the reporter knows\n * which — then loose IDs, which are probably the answer and only need a\n * name. Nothing, and it is the plain question it always was.\n */\n function openRefs(read) {\n refCandidates = read.conflicts.length ? read.conflicts : read.loose.slice(0, 4);\n refMode = read.conflicts.length ? 'confirm' : read.loose.length ? 'suggest' : 'ask';\n refOpen = true;\n if (refCandidates.length) {\n // One contested value: pre-fill it so they only choose what it is. Two\n // or more: pick nothing, because picking is the question.\n if (refCandidates.length === 1) takeCandidate(refCandidates[0]);\n else {\n refValue = '';\n const [first] = refCandidates;\n const same = refCandidates.every((c) => c.kinds.length === 1 && c.kinds[0].key === first.kinds[0].key);\n refChoice = same ? first.kinds[0].key : null;\n }\n } else {\n refChoice = refChoice || refConfig.fields[0].key;\n }\n drawRefs();\n }\n\n /**\n * Whether Send should stop and ask.\n *\n * Never when the text answered it outright. Once when the text said two\n * things or something unlabelled — on every policy, because sending the\n * wrong record's id with confidence is worse than one extra tap. And the\n * plain question on `optional`/`required` projects as before.\n */\n function needsReference() {\n if (refConfig.policy === 'off' || !refConfig.fields.length) return false;\n if (answeredReference().length) return false;\n const read = readText();\n if (textAnswers(read)) return false;\n if (refSettled && refConfig.policy !== 'required') return false;\n return read;\n }\n\n /* ---------- send ---------- */\n\n sendBtn.addEventListener('click', async () => {\n const title = titleInput.value;\n if (!title.trim()) {\n flagTitle('Add a few words about what went wrong — a short sentence is plenty.');\n titleInput.focus();\n return;\n }\n\n // Ask, if this project asks and nothing they wrote answered it. The first\n // press opens the question rather than sending; the second sends. That\n // costs one extra press on projects that want an identifier, and it is the\n // only shape that does not put paperwork in front of a person who came here\n // to say something is broken.\n //\n // First, though: a value in the box but no kind picked. Asked rather than\n // guessed — a wrong kind joins on the wrong table.\n if (refOpen && refValue.trim() && !refChoice) {\n refError = 'Pick what this ID is.';\n drawRefs();\n return;\n }\n\n const read = needsReference();\n if (read) {\n if (!refOpen) openRefs(read); else drawRefs();\n say('');\n return;\n }\n\n // They chose a field and typed something that is not that shape. Advisory\n // on the server — a value that fails is still what they believe identifies\n // them, and it is kept — but worth one chance to fix a typo here, where the\n // number is still on screen.\n if (refChoice && refValue.trim()) {\n const field = refConfig.fields.find((f) => f.key === refChoice);\n if (!refWarned && field && !validateReference(field, refValue)) {\n refError = `That does not look like a ${field.label.toLowerCase()}. Press Send again to use it anyway.`;\n // Once, and then it is their call. Our idea of the shape of somebody's\n // account number is not better than theirs, and refusing outright is\n // how a genuine reference gets lost — so the second press sends it and\n // the API keeps it, marked as not matching the expected format.\n refWarned = true;\n drawRefs();\n return;\n }\n }\n\n sending = true;\n say('');\n refreshSendState();\n const original = sendBtn.innerHTML;\n sendBtn.innerHTML = '<span class=\"spinner\" aria-hidden=\"true\"></span>Sending…';\n\n try {\n const res = await client.report({\n title,\n description: descInput.value,\n priority,\n references: answeredReference(),\n issue: pickedIssue?.key || null,\n attachments: items\n .filter((it) => it.status === 'ready')\n .map((it) => ({ type: it.kind, storageKey: it.storageKey, mime: it.mime })),\n });\n if (res?.cancelled) { sending = false; sendBtn.innerHTML = original; refreshSendState(); return; }\n draftSubmitted = true;\n clearTimeout(draftTimer);\n await draftWrites;\n if (cfg.drafts !== false) await draftOperation(savedDraftKey, null).catch(() => {});\n succeed(res);\n } catch (err) {\n sending = false;\n sendBtn.innerHTML = original;\n say(err.message.replace('[BugCatch] ', ''));\n refreshSendState();\n // One reason a send fails is that this browser was signed out while the\n // panel sat open — the same address signed in elsewhere. The message\n // above already says so; this puts the panel back in step with it\n // instead of leaving a Send button that will fail the same way again.\n if (login.mode !== 'off') refreshLogin().catch(() => {});\n }\n });\n\n scrim.querySelector('[data-act=\"history\"]')?.addEventListener('click', showHistory);\n\n /* ---------- what is already known ---------- */\n\n // The list as it was last fetched, and who has said what about it. Held here\n // rather than re-read from the DOM so a re-render after a join does not have\n // to parse its own markup back out.\n let issues = [];\n let issueError = '';\n let askingFor = null;\n let matchAbort = null;\n\n // Asked once when the dialog opens, like the call button above and for the\n // same reason: it is one request per opened widget rather than one per page\n // load of every site running this SDK, and most projects will answer with an\n // empty list forever.\n function loadKnownIssues() {\n if (cfg.knownIssues === false) return;\n client.knownIssues()\n .then((rows) => { issues = rows; drawKnown(); })\n .catch(() => { /* a help panel that cannot load is simply not drawn */ });\n }\n\n // Re-run as the title is typed, so the panel narrows to what they are\n // actually describing. Debounced, and the previous request is aborted rather\n // than left to land: a fast typist otherwise renders whichever response comes\n // back last, which is not necessarily the one matching the box.\n let matchTimer = null;\n function matchKnown(query) {\n if (cfg.knownIssues === false) return;\n clearTimeout(matchTimer);\n matchAbort?.abort();\n const q = (query || '').trim();\n if (q.length < 3) { loadKnownIssues(); return; }\n matchTimer = setTimeout(() => {\n matchAbort = new AbortController();\n client.matchKnownIssues(q, { signal: matchAbort.signal })\n .then((rows) => {\n // An empty result means \"nothing like this is known\", which is not\n // the same as \"nothing is known\" — fall back to the full list rather\n // than blanking a panel the reporter was reading.\n if (rows.length) { issues = rows; drawKnown(); }\n })\n .catch(() => {});\n }, 250);\n }\n\n function drawKnown() {\n if (!issues.length) { known.hidden = true; known.innerHTML = ''; return; }\n known.hidden = false;\n known.innerHTML = `\n <p class=\"known-h\">Already reported</p>\n ${issues.map((issue) => `\n <div class=\"issue ${issue.joined ? 'is-joined' : ''}\">\n <span class=\"who\">\n <span class=\"t\">${escapeHtml(issue.title)}</span>\n <span class=\"meta\">${escapeHtml(knownMeta(issue))}</span>\n </span>\n <button class=\"join\" type=\"button\" data-join=\"${escapeHtml(issue.id)}\"\n ${issue.joined ? 'disabled' : ''}>${issue.joined ? 'Following' : 'Me too'}</button>\n </div>\n ${askingFor === issue.id ? `\n <div class=\"ask\">\n <input type=\"email\" class=\"join-email\" placeholder=\"you@example.com\"\n aria-label=\"Your email, so we can tell you when this is fixed\" />\n <button class=\"ghost\" type=\"button\" data-join-confirm=\"${escapeHtml(issue.id)}\">Follow</button>\n </div>` : ''}\n `).join('')}\n ${issueError ? `<p class=\"err\">${escapeHtml(issueError)}</p>` : ''}`;\n }\n\n // What a reporter is told about an issue beside its title. Deliberately not\n // its severity: how bad the team thinks this is, published to everybody who\n // opens the widget, is an internal judgement that reads as a promise.\n function knownMeta(issue) {\n const state = issue.status === 'IN_PROGRESS' ? 'Being worked on now' : 'Not fixed yet';\n // The count is dropped entirely at zero rather than written as \"0 people\",\n // or as a bare \"Reported\" that says nothing the heading above has not\n // already said. Nobody having pressed the button yet is not a fact about\n // this issue worth a reporter's attention.\n const others = issue.affectedCount === 0\n ? ''\n : ` · ${issue.affectedCount} ${issue.affectedCount === 1 ? 'person has' : 'people have'} said this is happening to them`;\n return `${state} · reported ${relativeDay(issue.since)}${others}`;\n }\n\n known.addEventListener('click', async (e) => {\n const start = e.target.closest('[data-join]');\n const confirm = e.target.closest('[data-join-confirm]');\n if (!start && !confirm) return;\n\n const bugId = (confirm || start).getAttribute(confirm ? 'data-join-confirm' : 'data-join');\n issueError = '';\n\n // An address is what joining buys them — being told when it is fixed — so\n // ask for one when neither a sign-in nor identify() has supplied it, rather\n // than recording somebody nobody can ever write to.\n const email = known.querySelector('.join-email')?.value?.trim()\n || client.signedInAs()?.email\n || client.config.user?.email;\n if (!email) {\n askingFor = bugId;\n drawKnown();\n known.querySelector('.join-email')?.focus();\n return;\n }\n\n try {\n const result = await client.meToo(bugId, { email });\n askingFor = null;\n issues = issues.map((issue) => (issue.id === bugId\n ? { ...issue, joined: true, affectedCount: result.affectedCount ?? issue.affectedCount }\n : issue));\n drawKnown();\n } catch (err) {\n issueError = err.message.replace('[BugCatch] ', '');\n drawKnown();\n }\n });\n\n loadKnownIssues();\n titleInput.addEventListener('input', () => matchKnown(titleInput.value));\n\n /* ---------- questions the assistant answers by itself ---------- */\n\n // One tap instead of a sentence: the report's title becomes the question,\n // and the assistant knows exactly what is being asked — it looks the\n // reporter up, answers from their account, and asks whether that settled\n // it. Tapping the chosen one again lets it go.\n let agentIssues = [];\n let pickedIssue = null;\n\n function drawPicks() {\n if (!agentIssues.length) { picks.hidden = true; picks.innerHTML = ''; return; }\n picks.hidden = false;\n picks.innerHTML = `\n <p class=\"picks-h\">Get an answer right away</p>\n <div class=\"picks-grid\" role=\"group\" aria-label=\"Common questions\">\n ${agentIssues.map((issue) => `\n <button type=\"button\" class=\"pick\" data-pick=\"${escapeHtml(issue.key)}\"\n aria-pressed=\"${pickedIssue?.key === issue.key}\">\n <span class=\"t\">${escapeHtml(issue.title)}</span>\n ${issue.hint ? `<span class=\"h\">${escapeHtml(issue.hint)}</span>` : ''}\n </button>`).join('')}\n </div>\n ${pickedIssue ? `<p class=\"picks-note\">${ICONS.check}<span>Our assistant will check your account and reply here. Add anything else below, then send.</span></p>` : ''}`;\n }\n\n picks.addEventListener('click', (e) => {\n const btn = e.target.closest('[data-pick]');\n if (!btn) return;\n const issue = agentIssues.find((i) => i.key === btn.dataset.pick);\n if (!issue) return;\n const titleWasPick = pickedIssue && titleInput.value.trim() === pickedIssue.title;\n if (pickedIssue?.key === issue.key) {\n pickedIssue = null;\n if (titleWasPick) titleInput.value = '';\n } else {\n pickedIssue = issue;\n // Their own words are never overwritten — only an empty title, or the\n // one the previous tap put there.\n if (!titleInput.value.trim() || titleWasPick) titleInput.value = issue.title;\n }\n titleInput.dispatchEvent(new Event('input', { bubbles: true }));\n drawPicks();\n if (pickedIssue) descInput.focus();\n });\n\n if (cfg.agentIssues !== false && typeof client.agentIssues === 'function') {\n client.agentIssues()\n .then((rows) => { agentIssues = rows; drawPicks(); })\n .catch(() => { /* no tiles is the safe answer to every failure */ });\n }\n\n /* ---------- talk to the team ---------- */\n\n // Asked once, when the dialog opens, and the button stays hidden unless the\n // answer is yes. Doing it here rather than at init keeps one request off the\n // page load of every site this SDK is on, for a button most of them will\n // never show.\n //\n // The wording is fixed because there is one kind of call: it rings a room and\n // may go unanswered. A button that said \"talk to us\" would have somebody\n // expecting an instant answer sit through twenty seconds of ringing.\n let callOffered = false;\n function offerCall() {\n client.callKind()\n .then((kind) => {\n if (!kind) return;\n callOffered = true;\n // Never over a panel that is still asking who this is: the call would\n // file a report, and on a project that requires sign-in that is the one\n // thing this panel is holding back.\n callBtn.hidden = mustSignIn();\n })\n .catch(() => { /* not available is the safe answer to every failure */ });\n }\n offerCall();\n\n /* ---------- who is reporting ---------- */\n\n // Three states, and the project picks which: 'off' is every project that has\n // not asked for this and draws nothing at all; 'optional' offers a sign-in\n // above the form that the reporter may ignore; 'required' replaces the form\n // with it until they have.\n //\n // Asked on every open rather than remembered for the page, because the answer\n // includes whether this browser's session is still live — and it can end\n // somewhere else, when the same address signs in on another device. A widget\n // holding a cached \"signed in as you\" would find out at the moment the report\n // failed, which is the worst moment to discover it.\n let login = { mode: client.lastLoginMode || 'off', signedIn: null };\n // 'idle' — showing who they are; 'email' / 'code' — the two steps of getting there.\n let step = 'idle';\n let pendingEmail = client.config.user?.email || '';\n let pendingCode = '';\n let signInBusy = false;\n let signInError = '';\n\n const mustSignIn = () => login.mode === 'required' && !login.signedIn;\n\n /** Show or hide everything that only makes sense once we know who is filing. */\n function applyGate() {\n const shut = mustSignIn();\n compose.hidden = shut;\n sendBtn.hidden = shut;\n if (historyBtn) historyBtn.hidden = shut;\n // The call offer is the same errand as the form, so it goes with it — and\n // comes back only if the API had already said this project can take one.\n if (shut) callBtn.hidden = true;\n else if (callOffered) callBtn.hidden = false;\n }\n\n function drawWhoami() {\n if (login.mode === 'off') {\n whoami.hidden = true;\n whoami.innerHTML = '';\n return;\n }\n whoami.hidden = false;\n whoami.classList.toggle('asking', step !== 'idle');\n\n const problem = signInError ? `<div class=\"after err\">${escapeHtml(signInError)}</div>` : '';\n\n if (step === 'idle') {\n if (login.signedIn) {\n whoami.innerHTML = `<span class=\"who\">Signed in as <strong>${escapeHtml(login.signedIn.email)}</strong></span>`\n + '<button type=\"button\" class=\"link\" data-act=\"signout\">Sign out</button>';\n return;\n }\n // An address the host application passed for its own signed-in user is\n // not nothing — the report will carry it — so saying \"anonymously\" over\n // the top of one would be untrue. It is simply not proved, which is what\n // the offer beside it is for.\n const claimed = client.config.user?.email;\n whoami.innerHTML = `<span class=\"who\">${login.mode === 'required'\n ? 'Sign in to report a bug here.'\n : claimed\n ? `Reporting as <strong>${escapeHtml(claimed)}</strong>.`\n : 'Reporting anonymously — sign in and we can tell you what happens next.'}</span>`\n + '<button type=\"button\" class=\"link\" data-act=\"signin\">Sign in</button>';\n return;\n }\n\n if (step === 'email') {\n whoami.innerHTML = `\n <p class=\"why\">Sign in with any email address — we'll send you a code.\n It's how you hear back about this report${login.mode === 'required' ? ', and this project asks for it' : ''}.</p>\n <div class=\"row\">\n <input type=\"email\" data-fld=\"email\" value=\"${escapeHtml(pendingEmail)}\"\n placeholder=\"you@example.com\" autocomplete=\"email\" spellcheck=\"false\"\n aria-label=\"Your email address\" ${signInBusy ? 'disabled' : ''} />\n <button type=\"button\" class=\"primary\" data-act=\"code\" ${signInBusy ? 'disabled' : ''}>\n ${signInBusy ? 'Sending…' : 'Send code'}\n </button>\n </div>\n ${problem}\n ${login.mode === 'optional'\n ? '<div class=\"after\"><button type=\"button\" class=\"link\" data-act=\"skip\">Not now</button></div>'\n : ''}`;\n return;\n }\n\n whoami.innerHTML = `\n <p class=\"why\">We sent a code to <strong>${escapeHtml(pendingEmail)}</strong>. It expires in ten minutes.</p>\n <div class=\"row\">\n <input type=\"text\" class=\"code\" data-fld=\"code\" value=\"${escapeHtml(pendingCode)}\"\n placeholder=\"123456\" inputmode=\"numeric\" autocomplete=\"one-time-code\"\n maxlength=\"8\" aria-label=\"The code we emailed you\" ${signInBusy ? 'disabled' : ''} />\n <button type=\"button\" class=\"primary\" data-act=\"confirm\" ${signInBusy ? 'disabled' : ''}>\n ${signInBusy ? 'Checking…' : 'Sign in'}\n </button>\n </div>\n ${problem}\n <div class=\"after\">\n <button type=\"button\" class=\"link\" data-act=\"resend\" ${signInBusy ? 'disabled' : ''}>Send it again</button>\n <button type=\"button\" class=\"link\" data-act=\"signin-back\" ${signInBusy ? 'disabled' : ''}>Use another address</button>\n </div>`;\n }\n\n /** Redraw, and put the cursor where the reporter is being asked to type. */\n function drawSignIn({ focus = false } = {}) {\n drawWhoami();\n applyGate();\n if (!focus || signInBusy) return;\n const field = whoami.querySelector('[data-fld]');\n if (field) { field.focus(); field.setSelectionRange?.(field.value.length, field.value.length); }\n }\n\n // Typed values survive a redraw because they are held here, not read back off\n // a node that the next render replaces.\n whoami.addEventListener('input', (e) => {\n const field = e.target.closest('[data-fld]');\n if (!field) return;\n if (field.dataset.fld === 'email') pendingEmail = field.value;\n else pendingCode = field.value;\n });\n\n whoami.addEventListener('keydown', (e) => {\n if (e.key !== 'Enter' || !e.target.closest('[data-fld]')) return;\n e.preventDefault();\n if (step === 'email') requestCode();\n else confirmCode();\n });\n\n async function requestCode({ resent = false } = {}) {\n const email = pendingEmail.trim();\n if (!email) { signInError = 'Enter the address to send the code to.'; drawSignIn({ focus: true }); return; }\n\n signInBusy = true; signInError = ''; drawSignIn();\n try {\n await client.signIn(email);\n pendingEmail = email;\n if (!resent) pendingCode = '';\n step = 'code';\n } catch (err) {\n signInError = err.message;\n } finally {\n signInBusy = false;\n drawSignIn({ focus: true });\n }\n }\n\n async function confirmCode() {\n const code = pendingCode.trim();\n if (!code) { signInError = 'Enter the code we emailed you.'; drawSignIn({ focus: true }); return; }\n\n signInBusy = true; signInError = ''; drawSignIn();\n try {\n const who = await client.confirmSignIn(pendingEmail.trim(), code);\n login.signedIn = who;\n step = 'idle';\n pendingCode = '';\n say('');\n // Signing in is what makes a call possible on most projects — there is\n // now somebody to attribute it to — so the offer is asked for again\n // rather than staying hidden until the panel is reopened.\n offerCall();\n } catch (err) {\n signInError = err.message;\n } finally {\n signInBusy = false;\n drawSignIn({ focus: step !== 'idle' });\n }\n }\n\n whoami.addEventListener('click', async (e) => {\n const btn = e.target.closest('button[data-act]');\n if (!btn) return;\n const act = btn.dataset.act;\n\n if (act === 'signin') { step = 'email'; signInError = ''; drawSignIn({ focus: true }); return; }\n if (act === 'skip') { step = 'idle'; signInError = ''; drawSignIn(); return; }\n // Namespaced: the report screen has a back button of its own, and the two\n // must not be one selector away from each other.\n if (act === 'signin-back') { step = 'email'; signInError = ''; pendingCode = ''; drawSignIn({ focus: true }); return; }\n if (act === 'code') { requestCode(); return; }\n if (act === 'resend') { requestCode({ resent: true }); return; }\n if (act === 'confirm') { confirmCode(); return; }\n if (act === 'signout') {\n signInBusy = true; drawSignIn();\n try {\n await client.signOut();\n } finally {\n login.signedIn = null;\n signInBusy = false;\n // On a project that requires it, signing out puts the form away again\n // and asks — which is what applyGate does off the back of this.\n step = login.mode === 'required' ? 'email' : 'idle';\n drawSignIn({ focus: step !== 'idle' });\n }\n }\n });\n\n /** Re-read the project's setting and this browser's session, and redraw. */\n async function refreshLogin({ announce = false } = {}) {\n const state = await client.loginState();\n login = { mode: state.mode, signedIn: state.signedIn };\n client.lastLoginMode = state.mode;\n // Nothing to type on a project that requires sign-in and has nobody in it.\n if (mustSignIn() && step === 'idle') step = 'email';\n if (login.signedIn) step = 'idle';\n if (announce && state.signedOutReason) say(state.signedOutReason, 'info');\n drawSignIn();\n }\n\n refreshLogin({ announce: true }).catch(() => { /* a widget that can't ask stays open */ });\n\n callBtn.addEventListener('click', async () => {\n sending = true;\n say('');\n refreshSendState();\n callBtn.disabled = true;\n callBtn.innerHTML = '<span class=\"spinner\"></span>Ringing…';\n\n try {\n // No id: this files the report from what the page was doing and rings the\n // team about it, in one round trip.\n const handle = await client.startCall();\n showCall(handle);\n } catch (err) {\n sending = false;\n callBtn.disabled = false;\n callBtn.innerHTML = `${ICONS.phone}Talk to the team`;\n refreshSendState();\n // A report filed with no call is still a report. Saying \"the call\n // failed\" and nothing else would leave the reporter thinking their\n // context went nowhere, and pressing Send again would file it twice.\n if (err.filed) succeed(err.filed, { callFailed: err.reason || 'The call could not be started.' });\n else {\n say(err.message.replace('[BugCatch] ', ''));\n // The same follow-up the Send button does, because a call can fail for\n // the same reason a report can: this browser was signed out while the\n // panel sat open. Without it the message says sign in while the row\n // under it still names who signed in, which reads as a broken widget.\n if (login.mode !== 'off') refreshLogin().catch(() => {});\n }\n }\n });\n\n /**\n * The call, once the team is being rung.\n *\n * Replaces the panel for the same reason the history view does: it is the\n * same dialog answering a different question, and the reporter got here from\n * one launcher. There is no way back to the form — the report has already\n * been filed, and a Cancel that implied otherwise would be a lie.\n */\n function showCall(handle) {\n const panel = scrim.querySelector('.panel');\n const ref = handle?.bugId ? String(handle.bugId).slice(0, 8) : null;\n\n panel.innerHTML = `\n <div class=\"call\">\n <div class=\"call-head\">\n <span class=\"call-dot\" aria-hidden=\"true\"></span>\n <strong class=\"call-state\">Ringing…</strong>\n </div>\n <p class=\"call-note\">\n We're ringing the team now. Someone will pick up if they're free — your report${\n ref ? ` <code>${escapeHtml(ref)}</code>` : ''} is already filed either way, so nothing is lost if nobody answers.\n </p>\n <div class=\"foot\">\n <span class=\"spacer\"></span>\n <button class=\"primary\" type=\"button\" data-act=\"hangup\">Cancel</button>\n </div>\n </div>`;\n\n const stateEl = panel.querySelector('.call-state');\n const dot = panel.querySelector('.call-dot');\n\n // A call spends its first twenty seconds ringing, and can end by nobody\n // picking up — which is not a failure and must not read as one.\n const WORDING = {\n ringing: 'Ringing the team…',\n connecting: 'Someone answered — connecting…',\n connected: 'Connected',\n missed: 'Nobody was free',\n ended: 'Call ended',\n failed: 'The call dropped',\n };\n\n // `missed` is over too, and is the ordinary way a live call ends when the\n // team is busy. Left out of this list it would leave the dialog showing\n // \"Nobody was free\" above a Cancel button for a call that has already\n // stopped.\n const OVER = ['ended', 'failed', 'missed'];\n\n const offState = client.on('call', ({ state }) => {\n stateEl.textContent = WORDING[state] || state;\n dot.classList.toggle('off', OVER.includes(state));\n if (OVER.includes(state)) {\n panel.querySelector('[data-act=\"hangup\"]').textContent = 'Close';\n if (state === 'missed') {\n // Said plainly, and said as the non-event it is. The report is filed\n // and the thread already says the call went unanswered, so the one\n // thing left to tell them is that they have not lost anything.\n const note = panel.querySelector('.call-note');\n if (note) {\n note.textContent = 'Nobody was free to pick up just now. Your report is filed '\n + 'and the team will reply in the chat.';\n }\n }\n }\n });\n\n panel.querySelector('[data-act=\"hangup\"]').addEventListener('click', () => {\n offState();\n client.endCall();\n close();\n });\n }\n\n /**\n * The confirmation, and the one answer somebody actually wants from it.\n *\n * \"How long will this take\" is the first thing anybody thinks after reporting\n * a bug, and until now the answer was nothing at all — so the honest reading\n * of a confirmation screen was \"into the void\". The API computes the figure\n * on the same request that files the report, precisely so it can be said\n * here: an estimate that needed a second call would arrive after this panel\n * had closed itself.\n *\n * Absent whenever the project has decided not to tell reporters, or has\n * nothing to base a figure on. Nothing is drawn in that case — no \"unknown\",\n * no placeholder — because a confirmation that admits it cannot answer is\n * worse than one that does not raise the question.\n *\n * The dwell is longer when there is something to read. Two seconds is right\n * for one line and is not enough for two, and a panel that closes over the\n * sentence somebody started reading is the reason they open it again.\n */\n function succeed(res, { callFailed = null } = {}) {\n // The assistant: straight into the report's conversation, where the\n // investigation shows itself working (\"Analyzing the issue…\") and asks\n // what it needs. The plain \"Thanks\" below stays for when that view cannot\n // load — a report filed without a way to follow it.\n if (res?.id && !callFailed) {\n showReport(res.id, { fresh: true }).then((ok) => { if (!ok) doneScreen(res, { callFailed }); });\n return;\n }\n doneScreen(res, { callFailed });\n }\n\n function doneScreen(res, { callFailed = null } = {}) {\n const ref = res?.id ? String(res.id).slice(0, 8) : null;\n const eta = res?.estimate?.label ? String(res.estimate.label) : null;\n // A press of Call that filed the report and then could not ring anybody.\n // Shown as a plain \"Thanks\" it read as a call that had quietly worked, and\n // the reporter sat waiting for a phone that was never going to ring.\n const failed = callFailed\n ? `<p class=\"eta\">${escapeHtml(callFailed)} Your report is filed, and the team will reply in the chat.</p>`\n : '';\n scrim.querySelector('.panel').innerHTML = `\n <div class=\"done\">\n <div class=\"tick\">${ICONS.check}</div>\n <h3>Thanks — that's on its way.</h3>\n <p>${ref ? `Filed as <code>${escapeHtml(ref)}</code>.` : 'The team has what they need to reproduce it.'}</p>\n ${failed}\n ${eta ? `<p class=\"eta\">Reports like this usually take <strong>${escapeHtml(eta)}</strong>. We'll let you know when it moves.</p>` : ''}\n </div>`;\n setTimeout(close, failed ? 6000 : eta ? 4000 : 2000);\n }\n\n /**\n * The reporter's own history: what they have filed and where each one got to.\n *\n * Replaces the panel rather than opening a second surface — it is the same\n * dialog answering a different question, and the reporter came here from one\n * launcher.\n */\n async function showHistory() {\n const panel = scrim.querySelector('.panel');\n clearTimeout(assistantTimer);\n openReportId = null;\n panel.classList.remove('is-chat');\n panel.innerHTML = `\n ${headHtml({ title: 'Your reports', sub: \"Everything you've reported, and where each one got to.\" })}\n <div class=\"history\" aria-busy=\"true\">${skeletonRows(3)}</div>`;\n\n panel.querySelector('[data-act=\"close\"]').addEventListener('click', close);\n\n let reports = [];\n try {\n ({ reports } = await client.myReports());\n } catch {\n const failed = panel.querySelector('.history');\n if (failed) failed.innerHTML = emptyHtml('Couldn\\'t load your reports just now.', ICONS.alert);\n return;\n }\n\n // The panel can be closed while the request is in flight.\n const list = panel.querySelector('.history');\n if (!list) return;\n\n list.removeAttribute('aria-busy');\n list.innerHTML = reports.length\n ? reports.map((r, i) => `\n <button class=\"row\" type=\"button\" data-report=\"${escapeHtml(r.id)}\" style=\"animation-delay:${Math.min(i, 8) * 30}ms\">\n <span class=\"row-ico\" aria-hidden=\"true\">${ICONS.chat}</span>\n <span class=\"who\">\n <span class=\"ttl\">${escapeHtml(r.title)}</span>\n <span class=\"sub\">${escapeHtml(r.reference)} · ${escapeHtml(relativeDay(r.createdAt))}</span>\n </span>\n <span class=\"pill ${escapeHtml(String(r.status).toLowerCase())}\">${escapeHtml(statusWord(r.status))}</span>\n <span class=\"chev\" aria-hidden=\"true\">${ICONS.chevronRight}</span>\n </button>`).join('')\n : emptyHtml('You haven\\'t reported anything yet.', ICONS.inbox);\n\n for (const row of list.querySelectorAll('[data-report]')) {\n row.addEventListener('click', () => showReport(row.dataset.report));\n }\n }\n\n /**\n * One report as a conversation.\n *\n * This screen used to be a timeline: everything that had happened, read-only,\n * newest first. It answered \"what happened to my report\" and nothing else —\n * so when the team's reply was a question (\"which browser?\"), the person best\n * placed to answer it was looking straight at it with no way to.\n *\n * Now it is a thread. Status changes still appear, as the quiet centred lines\n * that mark events in any chat; the messages between the two sides are\n * bubbles either side of them; and there is a box at the bottom. Oldest at\n * the top, because that is the direction a conversation is read.\n */\n async function showReport(bugId, { fresh = false } = {}) {\n const panel = scrim.querySelector('.panel');\n clearTimeout(assistantTimer);\n panel.classList.add('is-chat');\n panel.innerHTML = `\n ${headHtml({\n back: true,\n title: fresh ? 'Looking into it' : 'Your report',\n sub: fresh ? 'Our assistant is on it — it may ask you a question here.' : 'Everything that has happened, and a way to answer.',\n })}\n <div class=\"history chatting\" aria-busy=\"true\"><div class=\"thread\">${skeletonBubbles()}</div></div>`;\n\n panel.querySelector('[data-act=\"close\"]').addEventListener('click', close);\n panel.querySelector('[data-act=\"back\"]').addEventListener('click', showHistory);\n\n let report = null;\n try {\n report = await client.myReport(bugId);\n } catch {\n report = null;\n }\n\n const body = panel.querySelector('.history');\n // The panel can be closed, or navigated away from, while this is in flight.\n if (!body || !panel.querySelector('[data-act=\"back\"]')) return false;\n\n if (!report) {\n if (fresh) { panel.classList.remove('is-chat'); return false; }\n body.removeAttribute('aria-busy');\n body.innerHTML = emptyHtml('Couldn\\'t load that report just now.', ICONS.alert);\n return false;\n }\n\n openReportId = bugId;\n // Reading the report is reading its updates. Leaving a \"your report was\n // fixed\" card up over the screen where they are reading that it was fixed\n // would be the notification arguing with itself.\n client.markReportRead(bugId);\n const drawn = threadHtml(report.timeline);\n body.classList.add('chatting');\n body.removeAttribute('aria-busy');\n body.innerHTML = `\n <div class=\"report-head\">\n <span class=\"ttl\">${escapeHtml(report.title)}</span>\n <span class=\"sub\">${escapeHtml(report.reference)} · ${escapeHtml(report.project || '')}</span>\n <span class=\"pill ${escapeHtml(String(report.status).toLowerCase())}\">${escapeHtml(statusWord(report.status))}</span>\n ${report.estimate?.label\n // The same figure they were given when they filed it, so coming back\n // to check is not a worse experience than not checking. Absent on a\n // report that has already been settled — the API stops sending it —\n // because a prediction beside a finished report is either redundant\n // or an argument.\n ? `<span class=\"eta-line\">Usually takes ${escapeHtml(String(report.estimate.label))}</span>`\n : ''}\n </div>\n <div class=\"thread\" role=\"log\" aria-live=\"polite\" aria-label=\"Conversation\">${drawn}</div>`;\n\n const thread = body.querySelector('.thread');\n threadDrawn.set(thread, drawn);\n scrollToLatest(thread);\n mountVerification(body, report);\n if (report.canReply) mountComposer(body, thread, bugId);\n mountAssistant(body, report, bugId);\n // Again, now the cards above it have taken their share of the height: the\n // latest message — and any quick answers under it — is what they came for.\n scrollToLatest(thread);\n return true;\n }\n\n /**\n * The assistant's live card: where the investigation is, and a gentle\n * refresh while it works. Analyzing polls every few seconds until the\n * outcome lands — a question, a found cause, or a hand-off — so the\n * reporter watches it happen rather than wondering whether anything is.\n */\n let assistantTimer = null;\n function mountAssistant(body, report, bugId) {\n clearTimeout(assistantTimer);\n const a = report.assistant;\n const thread = body.querySelector('.thread');\n let card = body.querySelector('.assistant');\n if (!a) {\n card?.remove();\n thread?.querySelector('.typing-row')?.remove();\n return;\n }\n\n // Updated in place rather than rebuilt: the card is polled every few\n // seconds while the assistant works, and a card that re-entered on every\n // poll would flicker — and re-announce itself to a screen reader — for\n // nothing. It only animates when the phase actually moves.\n const previous = card?.dataset.phase || null;\n if (!card) {\n card = document.createElement('div');\n card.setAttribute('role', 'status');\n card.setAttribute('aria-live', 'polite');\n body.querySelector('.report-head')?.after(card);\n }\n card.className = `assistant ${escapeHtml(a.phase)}`;\n card.dataset.phase = a.phase;\n\n const steps = a.steps || [];\n const done = steps.filter((st) => st.state === 'done').length;\n const active = steps.some((st) => st.state === 'active') ? 0.5 : 0;\n const pct = steps.length ? Math.round(((done + active) / steps.length) * 100) : 0;\n const content = `\n <div class=\"as-body${previous && previous !== a.phase ? ' entering' : ''}\">\n <div class=\"as-top\">\n <span class=\"as-orb\" aria-hidden=\"true\">${a.phase === 'analyzing' ? '<span class=\"as-spin\"></span>'\n : a.phase === 'calling' ? ICONS.phone\n : a.phase === 'resolved' ? ICONS.check : ICONS.sparkle}</span>\n <div class=\"as-text\">\n <div class=\"as-title\">${escapeHtml(a.title)}</div>\n <div class=\"as-detail\">${escapeHtml(a.detail)}</div>\n </div>\n </div>\n </div>\n ${steps.length ? `\n <div class=\"as-progress\" role=\"progressbar\" aria-label=\"Progress\" aria-valuemin=\"0\" aria-valuemax=\"100\" aria-valuenow=\"${pct}\"><i style=\"width:${pct}%\"></i></div>\n <ol class=\"as-steps\">${steps.map((st) => `<li class=\"${escapeHtml(st.state)}\"${st.state === 'active' ? ' aria-current=\"step\"' : ''}><span class=\"dot\"></span><span class=\"lbl\">${escapeHtml(st.label)}</span></li>`).join('')}</ol>` : ''}`;\n const key = JSON.stringify([a.phase, a.title, a.detail, steps]);\n if (card.dataset.key !== key) {\n const bar = card.querySelector('.as-progress i');\n const from = bar?.style.width;\n card.innerHTML = content;\n card.dataset.key = key;\n // Let the bar travel from where it was to where it is now.\n const next = card.querySelector('.as-progress i');\n if (next && from) {\n next.style.width = from;\n requestAnimationFrame(() => { next.style.width = `${pct}%`; });\n }\n }\n\n // While it works, a \"thinking\" bubble sits where its next message will land.\n // Never during a call: somebody is on the phone about this report, nothing\n // is being analysed (the analysis waits for the transcript), and a bubble\n // typing away underneath a live call is the assistant talking over them.\n if (thread) {\n const pinned = atLatest(thread);\n const typing = thread.querySelector('.typing-row');\n if (a.phase !== 'analyzing') typing?.remove();\n else if (!typing || typing !== thread.lastElementChild) {\n typing?.remove();\n thread.insertAdjacentHTML('beforeend', typingHtml());\n if (pinned) scrollToLatest(thread);\n }\n }\n\n if (a.phase === 'analyzing') {\n assistantTimer = setTimeout(() => refreshThread(bugId), 3000);\n } else if (a.phase === 'calling') {\n // Slower: the call's own card is what the reporter is watching, and this\n // only has to notice when the call ends and the analysis begins.\n assistantTimer = setTimeout(() => refreshThread(bugId), 10000);\n } else if (a.phase === 'needs_info' && previous !== 'needs_info') {\n body.querySelector('.composer textarea')?.focus();\n }\n }\n\n /**\n * Redraw the open thread in place, keeping whatever is half-typed.\n *\n * Rebuilding the whole screen on every incoming message would be simpler and\n * would also throw away a draft, a staged file and the scroll position — for\n * someone mid-sentence, a reply arriving would look like the widget crashed.\n */\n function mountVerification(body, report) {\n body.querySelector('.fix-verification')?.remove();\n if (!report.resolvedAt || !['RESOLVED', 'CLOSED'].includes(report.status)) return;\n const box = document.createElement('div'); box.className = 'fix-verification';\n if (report.verification) { box.textContent = report.verification === 'CONFIRMED' ? 'You confirmed that this fix works.' : 'Your feedback was recorded.'; }\n else {\n box.innerHTML = '<p>Does the fix work for you?</p><textarea maxlength=\"4000\" aria-label=\"Fix feedback\" placeholder=\"What happens when you try again? (optional)\"></textarea><button type=\"button\" data-result=\"CONFIRMED\">It works now</button> <button type=\"button\" data-result=\"STILL_HAPPENING\">Still happening</button><p class=\"feedback-status\" role=\"status\"></p>';\n box.querySelectorAll('[data-result]').forEach((btn) => { btn.onclick = async () => {\n box.querySelectorAll('button').forEach((b) => { b.disabled = true; });\n try { await client.verifyFix(report.id, { result: btn.dataset.result, resolvedAt: report.resolvedAt, note: box.querySelector('textarea').value }); await showReport(report.id); }\n catch (err) { box.querySelector('.feedback-status').textContent = err.message; box.querySelectorAll('button').forEach((b) => { b.disabled = false; }); }\n }; });\n }\n body.querySelector('.report-head')?.after(box);\n }\n\n async function refreshThread(bugId) {\n const body = scrim.querySelector('.history.chatting');\n const thread = body?.querySelector('.thread');\n if (!thread || openReportId !== bugId) return;\n\n let report = null;\n try {\n report = await client.myReport(bugId);\n } catch { /* a failed refresh leaves what is on screen, which is not wrong */ }\n if (!report || openReportId !== bugId) return;\n\n mountVerification(body, report);\n const pinned = atLatest(thread);\n const html = threadHtml(report.timeline);\n // Only rewritten when something changed, so a poll that brings nothing new\n // does not replay every bubble's entrance or re-announce the log.\n if (threadDrawn.get(thread) !== html) {\n thread.innerHTML = html;\n threadDrawn.set(thread, html);\n }\n mountAssistant(body, report, bugId);\n // Only follow the conversation down if they were already at the bottom.\n // Yanking someone back from something they scrolled up to read is worse\n // than making them scroll.\n if (pinned) scrollToLatest(thread);\n\n const pill = body.querySelector('.report-head .pill');\n if (pill) {\n pill.className = `pill ${String(report.status).toLowerCase()}`;\n pill.textContent = statusWord(report.status);\n }\n }\n\n /**\n * The box at the bottom: text, files, and a voice note.\n *\n * Files are uploaded as they are picked, not on send, so the wait happens\n * while the person is still writing rather than after they have pressed the\n * button — and so a failed upload is visible as a failed chip instead of a\n * message that never went.\n */\n function mountComposer(body, thread, bugId) {\n const composer = document.createElement('div');\n composer.className = 'composer';\n composer.innerHTML = `\n <div class=\"staged\" hidden></div>\n <div class=\"row\">\n <div class=\"tools\">\n ${cfg.attachments === false ? '' : `\n <button class=\"tool\" type=\"button\" data-act=\"attach\" title=\"Attach a file\" aria-label=\"Attach a file\">${ICONS.paperclip}</button>\n <button class=\"tool\" type=\"button\" data-act=\"voice\" title=\"Record a voice note\" aria-label=\"Record a voice note\">${ICONS.mic}</button>`}\n <button class=\"tool\" type=\"button\" data-act=\"livecall\" title=\"Call the team\" aria-label=\"Call the team\" hidden>${ICONS.phone}</button>\n </div>\n <textarea rows=\"1\" placeholder=\"Write a reply…\" aria-label=\"Write a reply\"></textarea>\n <button class=\"send\" type=\"button\" data-act=\"send\" title=\"Send\" aria-label=\"Send\" disabled>${ICONS.send}</button>\n </div>\n <div class=\"note\" hidden></div>\n <input type=\"file\" hidden multiple accept=\"${UPLOAD_MIMES.join(',')}\">`;\n body.appendChild(composer);\n\n const staged = composer.querySelector('.staged');\n const input = composer.querySelector('textarea');\n const sendBtn = composer.querySelector('[data-act=\"send\"]');\n const voiceBtn = composer.querySelector('[data-act=\"voice\"]');\n const liveBtn = composer.querySelector('[data-act=\"livecall\"]');\n const picker = composer.querySelector('input[type=file]');\n const note = composer.querySelector('.note');\n\n // Uploaded files waiting to ride on the next message.\n const files = [];\n let recorder = null;\n let sending = false;\n\n function say(text, kind = '') {\n note.hidden = !text;\n note.className = `note ${kind}`;\n note.textContent = text || '';\n }\n\n function refresh() {\n const ready = files.filter((f) => f.storageKey);\n sendBtn.disabled = sending || (!input.value.trim() && !ready.length);\n staged.hidden = !files.length;\n staged.innerHTML = files.map((f, i) => `\n <span class=\"chip ${f.storageKey ? '' : 'uploading'}\">\n <span class=\"nm\">${escapeHtml(f.name)}</span>\n <span class=\"sz\">${f.storageKey ? humanSize(f.size) : 'sending…'}</span>\n <button type=\"button\" data-drop=\"${i}\" aria-label=\"Remove ${escapeHtml(f.name)}\">${ICONS.close}</button>\n </span>`).join('');\n for (const btn of staged.querySelectorAll('[data-drop]')) {\n btn.addEventListener('click', () => {\n files.splice(Number(btn.dataset.drop), 1);\n refresh();\n });\n }\n }\n\n /** Upload one blob and stage it. Its chip is on screen for the whole trip. */\n async function stage(blob, mime, name) {\n const entry = { name, size: blob.size, storageKey: null };\n files.push(entry);\n refresh();\n try {\n const { storageKey, mime: stored } = await client.uploadAttachment(blob, mime);\n entry.storageKey = storageKey;\n entry.mime = stored;\n } catch (err) {\n files.splice(files.indexOf(entry), 1);\n say(err.message || 'That file could not be sent.', 'err');\n }\n refresh();\n }\n\n input.addEventListener('input', () => {\n // Grow with the text, up to the CSS cap. A reply about a bug is often\n // three lines, and a one-line box makes people write one line.\n input.style.height = 'auto';\n input.style.height = `${Math.min(input.scrollHeight, 120)}px`;\n refresh();\n });\n\n // Enter sends, Shift+Enter breaks the line — the chat convention. Anyone\n // who wants a paragraph gets it; nobody has to hunt for the button.\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter' && !e.shiftKey && !e.isComposing) {\n e.preventDefault();\n send();\n }\n });\n\n // Absent when the host set `attachments: false` — which the report form\n // has always honoured and this composer did not, so a host keeping files\n // and microphone audio outside its compliance perimeter still had both\n // offered in the follow-up thread.\n composer.querySelector('[data-act=\"attach\"]')?.addEventListener('click', () => picker.click());\n picker.addEventListener('change', () => {\n say('');\n for (const file of picker.files) {\n if (file.size > client.config.maxAttachmentBytes) {\n say(`${file.name} is larger than ${humanSize(client.config.maxAttachmentBytes)}.`, 'err');\n continue;\n }\n stage(file, file.type, file.name);\n }\n picker.value = '';\n });\n\n if (voiceBtn && !voiceSupported()) {\n voiceBtn.disabled = true;\n voiceBtn.title = 'This browser cannot record audio';\n }\n\n voiceBtn?.addEventListener('click', async () => {\n if (recorder) {\n const handle = recorder;\n recorder = null;\n voiceBtn.classList.remove('recording');\n voiceBtn.innerHTML = ICONS.mic;\n say('');\n const { blob, mime } = await handle.stop();\n if (blob.size) stage(blob, mime, 'Voice note');\n return;\n }\n try {\n say('');\n recorder = await startVoiceNote({\n maxMs: client.config.maxRecordingMs,\n onTick: (s) => say(`Recording — ${humanDuration(s)}. Tap the mic to stop.`),\n onAutoStop: () => voiceBtn.click(),\n });\n voiceBtn.classList.add('recording');\n voiceBtn.innerHTML = ICONS.stopSquare;\n } catch {\n recorder = null;\n say('The microphone is not available — check the browser permission.', 'err');\n }\n });\n\n /* ---------- calling the team ---------- */\n\n // Asked once, when a report is opened, and the button stays hidden unless\n // the answer is yes. Most workspaces have live calling off, and a phone\n // icon that apologises on press is worse than no phone icon.\n client.canCallTeam(bugId)\n .then((available) => { if (available) liveBtn.hidden = false; })\n .catch(() => { /* not available is the safe answer to every failure */ });\n\n liveBtn.addEventListener('click', async () => {\n liveBtn.disabled = true;\n say('');\n try {\n const handle = await client.startLiveCall(bugId);\n showLiveCall(body, thread, handle);\n } catch (err) {\n say(err.message.replace('[BugCatch] ', ''), 'err');\n } finally {\n liveBtn.disabled = false;\n }\n });\n\n sendBtn.addEventListener('click', send);\n\n // The confirm question's quick answers: the same as typing and sending.\n thread.addEventListener('click', (e) => {\n const quick = e.target.closest?.('[data-quick]');\n if (!quick || sending) return;\n input.value = quick.dataset.quick;\n send();\n });\n\n async function send() {\n const text = input.value.trim();\n const ready = files.filter((f) => f.storageKey);\n if (sending || (!text && !ready.length)) return;\n\n sending = true;\n refresh();\n say('');\n\n // Shown immediately, greyed, and replaced by the refresh below. Waiting\n // for the round trip before anything appears makes a slow network look\n // like a dropped message, which is when people send it twice.\n thread.insertAdjacentHTML('beforeend', messageHtml({\n from: 'reporter',\n body: text,\n at: new Date().toISOString(),\n attachments: ready.map((f) => ({ type: 'FILE', name: f.name, size: f.size })),\n }, 'pending'));\n scrollToLatest(thread, { smooth: true });\n\n try {\n await sendMessage({\n apiBase: client.config.apiBase,\n bugId,\n body: text,\n attachments: ready.map((f) => ({\n storageKey: f.storageKey, mime: f.mime, name: f.name, size: f.size,\n })),\n });\n input.value = '';\n input.style.height = 'auto';\n files.length = 0;\n await refreshThread(bugId);\n } catch (err) {\n thread.querySelector('.msg-row.pending')?.classList.replace('pending', 'failed');\n say(err.message || 'That could not be sent — try again.', 'err');\n } finally {\n sending = false;\n refresh();\n }\n }\n\n refresh();\n }\n\n /**\n * The call, on top of the report it is about.\n *\n * A bar rather than a screen of its own, unlike the one the composer's button\n * opens — and for a reason that only applies here. A call has nothing to show\n * while it runs, because nobody is listening to it but the person on the\n * other end, so what is worth looking at is the report both people are\n * talking about.\n */\n function showLiveCall(body, thread, handle) {\n const bar = document.createElement('div');\n bar.className = 'livecall';\n bar.innerHTML = `\n <span class=\"call-dot\" aria-hidden=\"true\"></span>\n <span class=\"who\" role=\"status\" aria-live=\"polite\">Calling the team…</span>\n <span class=\"secs\" hidden></span>\n ${handle?.recorded ? '<span class=\"call-kept\">This call is recorded.</span>' : ''}\n <button class=\"act\" type=\"button\" data-act=\"mute\" hidden>Mute</button>\n <button class=\"act danger\" type=\"button\" data-act=\"hangup\">Cancel</button>`;\n body.insertBefore(bar, thread);\n\n const who = bar.querySelector('.who');\n const secs = bar.querySelector('.secs');\n const dot = bar.querySelector('.call-dot');\n const muteBtn = bar.querySelector('[data-act=\"mute\"]');\n const hangBtn = bar.querySelector('[data-act=\"hangup\"]');\n\n let ticker = null;\n const stopTicking = () => { if (ticker) { clearInterval(ticker); ticker = null; } };\n\n /** Left up rather than removed, so somebody who looked away can see how it\n * ended — and dismissed by hand, or by the next call replacing it. */\n const settle = (text) => {\n stopTicking();\n dot.classList.add('off');\n bar.classList.add('over');\n who.textContent = text;\n secs.hidden = true;\n muteBtn.hidden = true;\n hangBtn.textContent = 'Dismiss';\n hangBtn.classList.remove('danger');\n };\n\n const off = client.on('call', ({ state }) => {\n if (state === 'ringing') who.textContent = 'Calling the team…';\n\n if (state === 'connecting') {\n who.textContent = 'Someone answered — connecting…';\n }\n\n if (state === 'connected') {\n who.textContent = \"You're on a call with the team\";\n muteBtn.hidden = false;\n secs.hidden = false;\n const from = Date.now();\n stopTicking();\n // Started on connect rather than on answer: the seconds a person is\n // charged for looking at are the ones they could hear.\n ticker = setInterval(() => {\n secs.textContent = humanDuration(Math.floor((Date.now() - from) / 1000));\n }, 1000);\n secs.textContent = '0s';\n }\n\n if (state === 'missed') {\n settle('Nobody was free. Send a message instead and the team will pick it up.');\n }\n if (state === 'ended') settle('Call ended.');\n if (state === 'failed') settle('The call dropped.');\n });\n\n muteBtn.addEventListener('click', () => {\n const muted = handle.setMuted(!handle.isMuted());\n muteBtn.textContent = muted ? 'Unmute' : 'Mute';\n // Announced rather than only shown: somebody who muted by accident finds\n // out when the other person stops replying, which is a long way in.\n who.textContent = muted ? \"You're muted\" : \"You're on a call with the team\";\n });\n\n hangBtn.addEventListener('click', () => {\n off();\n stopTicking();\n client.endCall();\n bar.remove();\n // The note about the call lands in the thread a moment after it ends, and\n // the reporter is looking straight at where it goes.\n refreshThread(openReportId);\n });\n }\n\n // The report currently open, if any. A live update for a different report\n // must not redraw the one being read.\n let openReportId = null;\n\n // Re-read the open thread when something happens to it. Without this the\n // reader is looking at a conversation that stopped being true while they read\n // it — the very screen where a stale view is most obvious, and now the screen\n // where the other side may be typing.\n stopListening = client.on('update', (event) => {\n if (!openReportId || event?.bugId !== openReportId) return;\n // Already on screen, so already read — and marking it here, before the\n // client draws its notifications, is what keeps a card from appearing over\n // the thread that is about to show the same thing.\n client.markReportRead(openReportId);\n refreshThread(openReportId);\n });\n\n root.appendChild(scrim);\n refreshSendState();\n titleInput.focus();\n\n return { destroy, close };\n}\n\n/**\n * The whole thread, oldest first.\n *\n * The API hands back one ordered list holding both kinds of entry, which is\n * what keeps a reply sitting under the status change that prompted it. Entries\n * carrying `from` are messages; the rest are things that happened.\n */\nfunction threadHtml(timeline) {\n const entries = timeline || [];\n if (!entries.length) return '';\n let day = null;\n let prev = null;\n return entries.map((e) => {\n // A rule with the date on it whenever the calendar day turns, the way a\n // chat marks that the next message came later.\n const d = dayKey(e.at);\n const sep = d && d !== day ? `<div class=\"day-sep\" role=\"separator\">${escapeHtml(dayLabel(e.at))}</div>` : '';\n if (sep) { day = d; prev = null; }\n if (!e.from) { prev = null; return sep + systemHtml(e); }\n const message = {\n from: e.from,\n author: e.reply?.author,\n automated: e.reply?.automated,\n body: e.reply?.body,\n at: e.at,\n attachments: e.reply?.attachments,\n };\n // Consecutive messages from the same voice within a few minutes are one\n // turn: one avatar, one name, the bubbles stacked under it.\n const who = speakerOf(message);\n const cont = prev && prev.who === who\n && Math.abs(new Date(e.at).getTime() - new Date(prev.at).getTime()) < 5 * 60_000;\n prev = { who, at: e.at };\n return sep + messageHtml(message, cont ? 'cont' : '');\n }).join('');\n}\n\n/** Which voice a message is in, for grouping: the reporter, the assistant, or a named person. */\nfunction speakerOf(message) {\n if (message.from === 'reporter') return 'me';\n if (message.automated) return 'assistant';\n return `team:${message.author || ''}`;\n}\n\n/**\n * Something that happened, rather than something someone said.\n *\n * `summary` is written by the API, which knows what changed and phrases it for\n * the person who reported it — so the wording here matches the toast and the\n * portal exactly rather than being a third attempt at the same sentence.\n */\nfunction systemHtml(entry) {\n const when = clockTime(entry.at);\n return `\n <div class=\"sys k-${escapeHtml(entry.kind)}\">\n <span class=\"dot\" aria-hidden=\"true\"></span>${escapeHtml(entry.summary || '')}${when ? `\n <span class=\"t\">· ${escapeHtml(when)}</span>` : ''}\n </div>`;\n}\n\n/**\n * One bubble. `state` marks a message that is still in flight, or failed, or\n * one that continues the turn above it.\n *\n * A reply that was sent automatically says so, above the words rather than\n * after them. The person reading it is deciding whether their problem has been\n * understood, and that decision is different when nobody has read the report —\n * so it is not a footnote, and it is not left to be inferred from the tone.\n */\nfunction messageHtml(message, state = '') {\n const mine = message.from === 'reporter';\n const body = message.body || '';\n const attachments = message.attachments || [];\n const automated = !mine && Boolean(message.automated);\n\n // The text goes in its own element rather than as a bare child of .bubble:\n // it is the only part that wants `white-space: pre-wrap`, and with that rule\n // on the bubble the template's own newlines and indentation render as a\n // blank line above and below every message.\n const text = body ? `<span class=\"msg-text\">${escapeHtml(body)}</span>` : '';\n const files = attachments.length\n ? `<div class=\"att\">${attachments.map(attachmentHtml).join('')}</div>`\n : '';\n\n // Named, or labelled, never both and never neither: an automated message has\n // no author to name, and naming one would be the lie the label exists to\n // prevent. The avatar says the same thing at a glance.\n let who = '';\n let avatar = '';\n if (automated) {\n who = '<span class=\"msg-who auto\">Assistant <span class=\"tag\">AI</span></span>';\n avatar = `<span class=\"av bot\" aria-hidden=\"true\">${ICONS.sparkle}</span>`;\n } else if (!mine) {\n const name = message.author || 'Support team';\n who = `<span class=\"msg-who\">${escapeHtml(name)}</span>`;\n avatar = `<span class=\"av\" aria-hidden=\"true\">${escapeHtml(initials(name))}</span>`;\n }\n if (state.includes('cont')) who = '';\n\n // The assistant's one answer ends by inviting a correction (check-rules\n // confirmMessage). One tap sends it, and what comes back goes to the person\n // who now owns the report rather than to another automated round — so the\n // wording is \"that's right\" and \"that's not it\", not a yes/no question.\n // The Sphoro Agent's answer ends by asking whether it settled things\n // (SOLVED_PROMPT). Yes closes the report; No puts it in front of a person.\n const confirm = automated && /it goes to the same person/i.test(body)\n ? `<div class=\"quick\" role=\"group\" aria-label=\"Answer\">\n <button type=\"button\" data-quick=\"Yes, this is the problem\">${ICONS.check}That's right</button>\n <button type=\"button\" data-quick=\"No, that is not the problem\">That's not it</button>\n </div>`\n : automated && SOLVED_PROMPT.test(body)\n ? `<div class=\"quick\" role=\"group\" aria-label=\"Did this answer your question?\">\n <button type=\"button\" data-quick=\"Yes, that answers it\">${ICONS.check}Yes, that answers it</button>\n <button type=\"button\" data-quick=\"No, I still need help\">No, I still need help</button>\n </div>`\n : '';\n\n return `\n <div class=\"msg-row ${mine ? 'mine' : 'theirs'} ${automated ? 'auto' : ''} ${state}\">\n ${avatar}\n ${who}\n <div class=\"bubble ${body ? '' : 'silent'}\">${text}${files}</div>\n ${confirm}\n <span class=\"msg-when\">${escapeHtml(clockTime(message.at) || relativeDay(message.at))}</span>\n </div>`;\n}\n\n/** The assistant, thinking: where its next message will appear. */\nfunction typingHtml() {\n return `\n <div class=\"typing-row\">\n <span class=\"av bot\" aria-hidden=\"true\">${ICONS.sparkle}</span>\n <span class=\"msg-who auto\">Assistant <span class=\"tag\">AI</span></span>\n <div class=\"bubble typing\" role=\"img\" aria-label=\"The assistant is working on it\"><i></i><i></i><i></i></div>\n </div>`;\n}\n\n/**\n * The panel's header, on every screen. The assistant's avatar carries the\n * identity; `back` adds the way out of a report to the list of them.\n */\nfunction headHtml({ title, sub, id = '', back = false }) {\n return `\n <div class=\"head\">\n ${back ? `<button class=\"icon-btn back\" type=\"button\" data-act=\"back\" aria-label=\"Back to your reports\">${ICONS.back}</button>` : ''}\n <div class=\"mark\" aria-hidden=\"true\">${ICONS.sparkle}</div>\n <div class=\"titles\">\n <h2${id ? ` id=\"${id}\"` : ''}>${escapeHtml(title)}</h2>\n <p>${escapeHtml(sub)}</p>\n </div>\n <button class=\"icon-btn\" type=\"button\" data-act=\"close\" aria-label=\"Close\">${ICONS.close}</button>\n </div>`;\n}\n\nfunction emptyHtml(text, icon) {\n return `<div class=\"empty\"><span class=\"empty-ico\" aria-hidden=\"true\">${icon}</span>${escapeHtml(text)}</div>`;\n}\n\n/** Placeholder rows while the list loads — the shape of what is coming. */\nfunction skeletonRows(n) {\n return Array.from({ length: n }, () => `\n <div class=\"row\" aria-hidden=\"true\">\n <span class=\"row-ico\"></span>\n <span class=\"who\"><span class=\"skel\" style=\"width:70%\"></span><span class=\"skel\" style=\"width:40%;height:9px\"></span></span>\n </div>`).join('') + '<span class=\"sr-only\">Loading…</span>';\n}\n\nfunction skeletonBubbles() {\n return `\n <div aria-hidden=\"true\" style=\"display:flex;flex-direction:column;gap:10px;padding-top:6px\">\n <span class=\"skel\" style=\"width:42%;height:34px;border-radius:16px;align-self:center\"></span>\n <span class=\"skel\" style=\"width:66%;height:52px;border-radius:18px\"></span>\n <span class=\"skel\" style=\"width:48%;height:38px;border-radius:18px;align-self:flex-end\"></span>\n </div>\n <span class=\"sr-only\">Loading…</span>`;\n}\n\n// What each open thread was last drawn from, so a poll that brings nothing new\n// leaves the DOM (and the reader's place in it) alone.\nconst threadDrawn = new WeakMap();\n\nfunction initials(name) {\n const parts = String(name).trim().split(/\\s+/).filter(Boolean);\n if (!parts.length) return '?';\n return (parts[0][0] + (parts.length > 1 ? parts[parts.length - 1][0] : '')).toUpperCase();\n}\n\nfunction dayKey(iso) {\n const d = new Date(iso);\n return Number.isFinite(d.getTime()) ? `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}` : null;\n}\n\n/** \"Today\" / \"Yesterday\" / \"Mon, 14 Sep\" — the label on a day separator. */\nfunction dayLabel(iso) {\n const d = new Date(iso);\n const today = new Date();\n const yesterday = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 1);\n if (dayKey(d) === dayKey(today)) return 'Today';\n if (dayKey(d) === dayKey(yesterday)) return 'Yesterday';\n try {\n return d.toLocaleDateString(undefined, {\n weekday: 'short', day: 'numeric', month: 'short',\n ...(d.getFullYear() !== today.getFullYear() ? { year: 'numeric' } : {}),\n });\n } catch {\n return relativeDay(iso);\n }\n}\n\n/** \"14:05\" or \"2:05 PM\", in the reader's own convention. */\nfunction clockTime(iso) {\n const d = new Date(iso);\n if (!Number.isFinite(d.getTime())) return '';\n try {\n return d.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' });\n } catch {\n return '';\n }\n}\n\n/**\n * One attachment, rendered as what it is.\n *\n * An image people can see, a voice note they can play and a document they can\n * download are three different things, and collapsing them all to a link is\n * how a chat stops feeling like one. A pending upload has no URL yet, so it\n * falls through to the file row with nothing to click.\n */\nfunction attachmentHtml(a) {\n const name = escapeHtml(a.name || 'attachment');\n if (!a.url) return `<span class=\"file\"><span class=\"nm\">${name}</span></span>`;\n const url = escapeHtml(a.url);\n\n if (a.type === 'SCREENSHOT') {\n return `<a href=\"${url}\" target=\"_blank\" rel=\"noopener noreferrer\"><img src=\"${url}\" alt=\"${name}\" loading=\"lazy\"></a>`;\n }\n if (a.type === 'AUDIO') {\n return `<audio controls preload=\"metadata\" src=\"${url}\"></audio>`;\n }\n if (a.type === 'VIDEO') {\n return `<video controls preload=\"metadata\" src=\"${url}\"></video>`;\n }\n return `\n <a class=\"file\" href=\"${url}\" target=\"_blank\" rel=\"noopener noreferrer\" download>\n <span class=\"nm\">${name}</span>\n ${a.size ? `<span class=\"sz\">${escapeHtml(humanSize(a.size))}</span>` : ''}\n </a>`;\n}\n\n/** Whether the reader is at the foot of the thread, within a line or two. */\nfunction atLatest(thread) {\n return thread.scrollHeight - thread.scrollTop - thread.clientHeight < 40;\n}\n\nfunction scrollToLatest(thread, { smooth = false } = {}) {\n const calm = typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;\n if (smooth && !calm && thread.scrollTo) thread.scrollTo({ top: thread.scrollHeight, behavior: 'smooth' });\n else thread.scrollTop = thread.scrollHeight;\n}\n\n/** Status as a word a reporter would use, not the enum the database stores. */\nfunction statusWord(status) {\n return {\n OPEN: 'Open',\n IN_PROGRESS: 'In progress',\n RESOLVED: 'Fixed',\n CLOSED: 'Closed',\n }[status] || String(status || '').toLowerCase();\n}\n\n/** \"today\" / \"3 days ago\" — enough for a history list, no library. */\nfunction relativeDay(iso) {\n const then = new Date(iso).getTime();\n if (!Number.isFinite(then)) return '';\n const days = Math.floor((Date.now() - then) / 86400000);\n if (days <= 0) return 'today';\n if (days === 1) return 'yesterday';\n if (days < 30) return `${days} days ago`;\n const months = Math.floor(days / 30);\n return months === 1 ? 'a month ago' : `${months} months ago`;\n}\n\nfunction escapeHtml(s) {\n return String(s).replace(/[&<>\"']/g, (c) => (\n { '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;' }[c]\n ));\n}\n","// Microphone to µ-law, and back.\n//\n// sphoro.voice speaks G.711 µ-law at 8 kHz in both directions — the format a\n// telephone carrier hands it, which is what makes a browser call and a phone\n// call indistinguishable to everything downstream. A browser has neither: it\n// captures 32-bit floats at whatever rate the sound card runs at. The gap\n// between those two is a codec and a resampler, and neither is available from\n// the platform, so both are here.\n//\n// This file exists rather than the two callers each having a copy. The agent\n// call sends µ-law because that is the whole conversation; a live call sends it\n// because sphoro.voice cannot record or transcribe a conversation it is not in\n// the middle of, and asks each browser for a copy of its own microphone. Two\n// implementations of G.711 in one package would be one implementation and one\n// that is subtly wrong on the day somebody fixes only the first.\n//\n// Nothing is imported to do it. The SDK runs on someone else's page and has no\n// dependencies, and that rule does not bend for audio.\n\n/** The wire's sample rate. Not negotiable — see the note above. */\nexport const WIRE_RATE = 8000;\n\n/**\n * An audio context, at the wire's rate where the browser will give us one.\n *\n * Asking for 8 kHz outright makes the resampler below dead code on most\n * machines. Some browsers refuse a rate their hardware does not like, so the\n * fallback is an ordinary context and the manual path.\n */\nexport function createContext(rate = WIRE_RATE) {\n const Ctor = window.AudioContext || window.webkitAudioContext;\n try {\n return new Ctor({ sampleRate: rate });\n } catch {\n return new Ctor();\n }\n}\n\n/**\n * Start a context that the browser has parked, if it will let us.\n *\n * Resolves either way. There is nothing useful to do with the rejection: it\n * means this browser wants a user gesture we are not inside, and the call is\n * still worth attempting — a context that stays suspended is a silent call,\n * which is what the caller finds out about from the states it is already being\n * sent.\n */\nexport async function resumeContext(context) {\n if (context.state !== 'suspended') return;\n try {\n await context.resume();\n } catch { /* no activation to spend */ }\n}\n\n/**\n * Drop a signal to the wire rate by averaging each source window.\n *\n * Exported for its own test: the browsers that refuse an 8 kHz context are the\n * ones that need this, and they are exactly the browsers a developer is least\n * likely to be looking at when they change it.\n *\n * Averaging rather than picking every nth sample: plain decimation aliases\n * everything above 4 kHz down into the speech band, and consonants are exactly\n * what lives up there. This is a crude low-pass, but it is the difference\n * between \"sixty\" and \"sixteen\" surviving the trip.\n */\nexport function downsample(input, fromRate, toRate) {\n const ratio = fromRate / toRate;\n const out = new Float32Array(Math.floor(input.length / ratio));\n for (let i = 0; i < out.length; i++) {\n const start = Math.floor(i * ratio);\n const end = Math.min(Math.floor((i + 1) * ratio), input.length);\n let sum = 0;\n for (let j = start; j < end; j++) sum += input[j];\n out[i] = end > start ? sum / (end - start) : 0;\n }\n return out;\n}\n\n/** G.711 µ-law: the bias and clip the standard specifies. */\nconst MU_BIAS = 0x84;\nconst MU_CLIP = 32635;\n\n/**\n * Float samples to µ-law bytes, ready to put on the wire.\n *\n * Exported for the codec test rather than for callers: this file is not the\n * package's entry point, and a hand-rolled G.711 that nothing checks is a\n * silent call nobody can debug from the outside.\n */\nexport function encodeMuLaw(samples) {\n const out = new Uint8Array(samples.length);\n for (let i = 0; i < samples.length; i++) {\n // Clamped before scaling: a float outside [-1, 1] is legal in Web Audio and\n // would wrap round to the opposite sign as an integer.\n const clamped = Math.max(-1, Math.min(1, samples[i]));\n let sample = Math.round(clamped * 32767);\n\n const sign = sample < 0 ? 0x80 : 0;\n if (sign) sample = -sample;\n if (sample > MU_CLIP) sample = MU_CLIP;\n sample += MU_BIAS;\n\n let exponent = 7;\n for (let mask = 0x4000; (sample & mask) === 0 && exponent > 0; exponent--, mask >>= 1);\n const mantissa = (sample >> (exponent + 3)) & 0x0F;\n out[i] = ~(sign | (exponent << 4) | mantissa) & 0xFF;\n }\n return out;\n}\n\n/** µ-law bytes back to float samples, ready for an AudioBuffer. */\nexport function decodeMuLaw(bytes) {\n const out = new Float32Array(bytes.length);\n for (let i = 0; i < bytes.length; i++) {\n const byte = ~bytes[i] & 0xFF;\n const sign = byte & 0x80;\n const exponent = (byte >> 4) & 0x07;\n const mantissa = byte & 0x0F;\n const magnitude = (((mantissa << 3) + MU_BIAS) << exponent) - MU_BIAS;\n out[i] = (sign ? -magnitude : magnitude) / 32768;\n }\n return out;\n}\n\n/**\n * How much audio rides in one frame.\n *\n * Measured in samples, and the context above runs at 8 kHz wherever the browser\n * allows it — so this is 64 ms of speech, not the 20-odd it would be at\n * hardware rate. That distinction is the whole reason for the number: at 2048\n * the same code sends a quarter of a second per frame, which is a quarter of a\n * second added to the start of every turn and to every barge-in, and it sounds\n * exactly like an agent that is slow to notice you talking.\n *\n * Not smaller than this. ScriptProcessorNode runs on the main thread, and a\n * block it cannot fill in time is a block it drops — which is a gap in the\n * middle of a word rather than a delay before one. Powers of two only.\n */\nexport const CAPTURE_BLOCK = 512;\n\n/**\n * Tap a microphone and hand each block over as µ-law bytes.\n *\n * ScriptProcessorNode is deprecated in favour of AudioWorklet, and is used\n * anyway. A worklet's processor has to be loaded from a URL, which on this SDK\n * means a blob: URL — and a blob: worklet is refused outright by the\n * Content-Security-Policy of a great many of the sites this code runs on. A\n * deprecated node that works on a customer's page beats a modern one that is\n * blocked by their headers.\n *\n * Returns a `stop` that lets go of the whole chain. The microphone track is\n * deliberately *not* stopped by it: on a live call the same track is carrying\n * the conversation, and stopping it to end a recording would end the call.\n *\n * @param {AudioContext} context\n * @param {MediaStream} stream\n * @param {(bytes: Uint8Array) => void} onFrame\n * @param {number} [wireRate]\n * @returns {{stop: () => void}}\n */\nexport function tapMicrophone(context, stream, onFrame, wireRate = WIRE_RATE) {\n const source = context.createMediaStreamSource(stream);\n const capture = context.createScriptProcessor(CAPTURE_BLOCK, 1, 1);\n\n capture.onaudioprocess = (event) => {\n const input = event.inputBuffer.getChannelData(0);\n const frames = context.sampleRate === wireRate\n ? input\n : downsample(input, context.sampleRate, wireRate);\n onFrame(encodeMuLaw(frames));\n };\n\n source.connect(capture);\n // A ScriptProcessorNode does not run unless it is connected to a destination,\n // even when nothing is meant to come out of it. Zero gain is what keeps the\n // speaker from hearing their own voice played back at them.\n const mute = context.createGain();\n mute.gain.value = 0;\n capture.connect(mute);\n mute.connect(context.destination);\n\n return {\n stop() {\n capture.onaudioprocess = null;\n try { source.disconnect(); } catch { /* already gone */ }\n try { capture.disconnect(); } catch { /* already gone */ }\n try { mute.disconnect(); } catch { /* already gone */ }\n },\n };\n}\n","// Two people, talking directly.\n//\n// The agent call next door is a microphone, a socket and a codec: the browser\n// sends µ-law up to sphoro.voice, a model answers, and every sample passes\n// through a server. This is the opposite arrangement. The audio here goes\n// straight from one browser to the other over WebRTC, and sphoro.voice's part is\n// only to introduce them — it passes an offer, an answer and a handful of ICE\n// candidates between the two sockets and then has nothing left to do.\n//\n// Which means there is no codec in this file, no resampler, and no audio path\n// at all. The browser's own WebRTC stack owns those, and it is better at them\n// than anything hand-written: Opus at whatever rate the two ends agree on,\n// echo cancellation, jitter buffering and packet loss concealment, none of\n// which the µ-law path can have because a telephone carrier cannot receive\n// them. A live call sounds better than the agent call, and costs nothing per\n// minute, because nobody is in the middle of it.\n//\n// # Shared with the dashboard\n//\n// Both ends of a live call run this file: the reporter's widget and the member\n// who answers in the portal, which imports it from here rather than keeping a\n// second copy. The two sides differ by one flag — who makes the offer — and it\n// arrives from the server rather than being decided here, so the two ends\n// cannot both sit waiting for the other to go first.\n//\n// # The one exception: the copy that gets kept\n//\n// An agent that is set to record or transcribe cannot do either from here —\n// the audio it would need never reaches it. So sphoro.voice asks instead: it\n// names, in `session.created`, what this call is keeping and in what format,\n// and each browser uplinks a copy of *its own* microphone over the signaling\n// socket it already has open. That copy is the only way a recording or a\n// transcript of a peer-to-peer call can exist at all, and it is why the codec\n// this file said it did not have is imported below.\n//\n// It is a copy and not the call. The conversation still runs browser to\n// browser; an uplink that stalls or is refused costs a recording, never a call.\n//\n// Nothing outside the package is imported to do any of it. The SDK runs on\n// someone else's page and has no dependencies, and that rule does not bend for\n// a peer connection.\n\nimport { createContext, resumeContext, tapMicrophone } from './audio.js';\n\n/**\n * sphoro.voice's realtime protocol, sent as the WebSocket subprotocol. The same\n * one the agent call speaks — a bridged call is a different conversation over\n * the same envelope, not a different protocol.\n */\nconst SUBPROTOCOL = 'voiceai.realtime.v1';\n\n/**\n * Where a browser looks to find its way to the other one, when the server names\n * nowhere.\n *\n * The server almost always does name somewhere — it is configured per\n * deployment, because a network behind symmetric NAT needs a TURN relay and\n * that costs money. This is the floor: a public STUN server, which is enough\n * for most home and office networks and free.\n */\nconst FALLBACK_ICE = [{ urls: 'stun:stun.l.google.com:19302' }];\n\n/**\n * Ask for the microphone.\n *\n * Separate from joining, and deliberately. The permission prompt takes as long\n * as the person takes to read it, and both ends have something they should not\n * do until it is answered: the reporter should not make a room full of\n * dashboards ring before knowing they can speak, and whoever answers should not\n * claim a call they then cannot take. So both ask first and act second.\n *\n * @returns {Promise<MediaStream>}\n */\nexport function openMicrophone() {\n return navigator.mediaDevices.getUserMedia({\n audio: {\n // Both ends are on speakers as often as not. Without cancellation each\n // hears themselves back through the other, half a second late, which is\n // the single most effective way to make a conversation impossible.\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n channelCount: 1,\n },\n });\n}\n\n/** Whether this browser can be on a live call at all. */\nexport function liveCallSupported() {\n return typeof window !== 'undefined'\n && typeof window.RTCPeerConnection === 'function'\n && Boolean(navigator.mediaDevices?.getUserMedia);\n}\n\n/**\n * Join a live call and stay on it until somebody hangs up.\n *\n * @param {object} opts\n * @param {string} opts.realtimeUrl From the API, carrying its own credential\n * and its own seat. Never log it and never build one by hand.\n * @param {MediaStream} [opts.stream] A microphone already opened by\n * `openMicrophone`. One is opened here if not, which is the wrong order for\n * anything that had a decision to make first.\n * @param {string} [opts.ringsUntil] ISO-8601, on the caller's side only: when\n * the ringing stops. The browser is the thing that decides it has, because it\n * is the one client guaranteed to still be listening at that moment.\n * @param {(state: string, detail?: object) => void} [opts.onState]\n * 'ringing' | 'connecting' | 'connected' | 'reconnecting' | 'ended' | 'missed'\n * | 'failed'. 'reconnecting' is not a failure and never ends a call — see\n * `routeChanged`.\n * @param {(blocked: boolean) => void} [opts.onAudioBlocked] The browser refused\n * to play the other person, or has since started. Its own callback rather\n * than a state, because it is orthogonal to every one of them: the call is\n * connected and working, and one end cannot hear it. See `playRemote`.\n * @param {(live: boolean) => void} [opts.onMicrophone] Whether this person's\n * microphone is still reaching the call. Also orthogonal, and for the same\n * reason: the call is up, the other end is audible, and nothing this person\n * says is going anywhere. See `watchMicrophone`.\n * @returns {Promise<{hangUp: () => void, setMuted: (muted: boolean) => boolean,\n * isMuted: () => boolean}>}\n */\nexport async function joinLiveCall({\n realtimeUrl, stream, ringsUntil, onState, onAudioBlocked, onMicrophone,\n}) {\n const emit = (state, detail) => { try { onState?.(state, detail); } catch { /* theirs */ } };\n const sayBlocked = (blocked) => {\n try { onAudioBlocked?.(blocked); } catch { /* theirs */ }\n };\n const sayMicrophone = (live) => {\n try { onMicrophone?.(live); } catch { /* theirs */ }\n };\n\n if (!liveCallSupported()) {\n const err = new Error('[BugCatch] this browser cannot make live calls.');\n emit('failed', { reason: 'unsupported' });\n throw err;\n }\n\n let microphone = stream;\n if (!microphone) {\n try {\n microphone = await openMicrophone();\n } catch (err) {\n emit('failed', { reason: 'microphone', error: String(err?.name || err) });\n throw err;\n }\n }\n\n let socket;\n try {\n socket = await openSocket(realtimeUrl);\n } catch (err) {\n stopStream(microphone);\n emit('failed', { reason: 'connect' });\n throw err;\n }\n\n let peer = null;\n let remoteAudio = null;\n let finished = false;\n let ringTimer = null;\n // What sphoro.voice said this call is keeping, and the tap feeding it. Both\n // stay null on the ordinary call, which keeps nothing and uplinks nothing.\n let capture = null;\n let uplink = null;\n // Whether the browser is currently refusing to play the other person, and the\n // teardown for the gesture listeners armed to try again. See playRemote.\n let blocked = false;\n let disarmGesture = null;\n /** Teardown for the microphone watch, armed once the peer connection is built. */\n let unwatchMicrophone = null;\n // Claimed synchronously, before the first await below, because the seat that\n // answers is told the call has started twice — once in its own\n // `session.created`, which says somebody is already waiting, and again in the\n // `peer.joined` that goes to both ends a moment later. Two starts that both\n // check `uplink` before either has set it are two taps on one microphone, and\n // two copies of every frame interleaved into one recording.\n let starting = false;\n // Candidates that arrived before there was anywhere to put them.\n //\n // The offer and the first candidates are sent within a millisecond of each\n // other and arrive in that order, but `setRemoteDescription` is a promise —\n // so a candidate can be in hand while the description it belongs to is still\n // being applied. Adding one then throws, and the call comes up with a worse\n // route than it could have had, or none.\n const pending = [];\n\n // --- surviving a change of network ------------------------------------------------\n //\n // A phone call moves. Somebody walks out of the office and the handset hands the\n // connection from Wi-Fi to cellular; a lift takes the signal for four seconds; a laptop\n // wakes on a different access point. Every one of those changes the local address the\n // call was negotiated against, and WebRTC's answer to all of them is the same: the ICE\n // agent reports `disconnected`, and unless somebody restarts it, `failed` a moment later.\n //\n // Before this, `failed` ended the call and `disconnected` was not handled at all — so a\n // call that would have recovered on its own in two seconds was hung up on, and one that\n // needed a restart was hung up on with no attempt made. On a desk that is rare. On the\n // phone this app also ships as, it is what happens when you stand up.\n //\n // What replaces it is a grace period, then an ICE restart, then a deadline. Each is\n // there for a distinct reason:\n //\n // the grace `disconnected` is frequently transient — a couple of lost packets on a\n // route that is still fine. Restarting immediately would tear down a\n // working connection to rebuild the same one, and take audio with it.\n //\n // the restart Renegotiation from scratch over the signaling socket, which is still\n // open: it never depended on the media route. This is the part that\n // actually crosses a network change.\n //\n // the deadline Reconnecting forever is its own failure. Somebody staring at a card\n // that says \"reconnecting\" for five minutes has been abandoned more\n // thoroughly than somebody told the call dropped.\n //\n // Only one side offers, and it is the side the server nominated at the start. Both ends\n // see the drop, so if the two sides both restarted there would be two offers crossing —\n // a glare condition WebRTC resolves badly. The other side asks instead, over the\n // signaling socket, and its request is best-effort: the offerer's own detection is what\n // this actually rests on.\n\n /** Which side was told to make the offer. Set from `peer.joined`, for both ends. */\n let shouldOffer = false;\n /** Whether the two ends have ever been connected. A drop before that is a failed setup. */\n let connectedOnce = false;\n let reconnecting = false;\n let recoverTimer = null;\n let giveUpTimer = null;\n let restarts = 0;\n\n /** How long `disconnected` is allowed to resolve itself before a restart is attempted. */\n const RECOVER_GRACE_MS = 2000;\n /** How long between one restart and the next, when the first did not take. */\n const RECOVER_RETRY_MS = 5000;\n /** How long a call may spend off the air in total before it is called dropped. */\n const RECOVER_DEADLINE_MS = 30000;\n const MAX_RESTARTS = 4;\n\n /** Everything ends here, whatever ended it, exactly once. */\n const finish = (state, detail) => {\n if (finished) return;\n finished = true;\n if (ringTimer) { clearTimeout(ringTimer); ringTimer = null; }\n // Before the peer is closed, and before the stream is stopped. Closing a connection\n // moves it to `closed` and stopping a track fires `ended`, and both would otherwise\n // reach handlers that would report a dropped call and a dead microphone to somebody\n // who has just hung up.\n clearRecovery();\n unwatchMicrophone?.();\n unwatchMicrophone = null;\n stopUplink();\n // Before anything else is torn down: these are listeners on `window`, and a\n // call that left them behind would keep retrying playback on an element\n // that is about to be removed — once per click, for the rest of the session.\n disarmGesture?.();\n disarmGesture = null;\n try { peer?.close(); } catch { /* already gone */ }\n stopStream(microphone);\n if (remoteAudio) {\n remoteAudio.srcObject = null;\n remoteAudio.remove();\n remoteAudio = null;\n }\n try {\n if (socket.readyState === WebSocket.OPEN) socket.close(1000, 'call over');\n } catch { /* already closing */ }\n emit(state, detail);\n };\n\n const send = (message) => {\n if (socket.readyState === WebSocket.OPEN) socket.send(JSON.stringify(message));\n };\n\n /**\n * Start sending sphoro.voice a copy of this person's microphone.\n *\n * Only when it asked, and only once both people are on the call. The server\n * throws away anything that arrives while the phone is still ringing — a\n * recording is timed from the moment the second person joined, and a minute\n * of somebody waiting alone at the head of the file is a timeline that\n * disagrees with the call's own duration. Sending it anyway would just be\n * bandwidth spent on audio that gets dropped, on the same network carrying\n * the call.\n *\n * Every failure here is survivable and none of them ends the call. A browser\n * with no Web Audio, a context the autoplay policy will not start, a format\n * this version does not know how to produce — each costs the recording and\n * leaves two people talking, which is the right way round. It is said out\n * loud, though: an agent set to record whose calls silently arrive with no\n * audio is the failure that is impossible to diagnose from the outside.\n */\n const startUplink = async () => {\n if (starting || uplink || finished || !capture) return;\n if (!capture.audio && !capture.transcript) return;\n starting = true;\n\n // The server names the format rather than assuming one, so a client that\n // does not recognise it sends nothing rather than sending µ-law into a\n // recording that expected something else — which would store a file of\n // noise and read as a working recording to everything downstream.\n const encoding = capture.encoding || 'mulaw';\n if (encoding !== 'mulaw') {\n warn(`this call is kept as ${encoding}, which this SDK cannot produce.`);\n return;\n }\n\n\n const rate = Number(capture.sample_rate) || 8000;\n try {\n const context = createContext(rate);\n // A context built without a user gesture starts suspended, and a\n // suspended context's ScriptProcessorNode never fires — so nothing would\n // be uplinked and nothing would say why. Both ends granted microphone\n // access a moment ago, which is the activation this spends.\n await resumeContext(context);\n if (finished) {\n try { context.close(); } catch { /* already closed */ }\n return;\n }\n\n const tap = tapMicrophone(context, microphone, (bytes) => {\n // Binary, not the base64 envelope the protocol also accepts: this is a\n // second copy of audio going up a network that is already carrying the\n // call, and base64 would make it a third larger for nothing.\n if (socket.readyState === WebSocket.OPEN) socket.send(bytes);\n }, rate);\n\n uplink = { context, tap };\n } catch (err) {\n warn(`could not send the copy this call is keeping: ${String(err?.message || err)}`);\n }\n };\n\n /**\n * Stop sending it.\n *\n * The microphone track is deliberately left alone — `finish` stops the whole\n * stream a moment later, and stopping it here would cut the conversation off\n * to end a recording.\n */\n function stopUplink() {\n starting = false;\n if (!uplink) return;\n const { context, tap } = uplink;\n uplink = null;\n try { tap.stop(); } catch { /* already gone */ }\n try { context.close(); } catch { /* already closed */ }\n }\n\n /**\n * Play the other person, and do something about it when the browser says no.\n *\n * The refusal used to be swallowed: `play().catch(() => {})`, on the reasoning\n * that both ends had granted microphone access a moment earlier and that\n * satisfies every autoplay policy. It does not always. A call answered from a\n * notification arrives with no user gesture on the page at all — the tap\n * happened on a system notification, which is not this document — and inside\n * an Android WebView that is exactly the case that fails.\n *\n * What that produced is the worst shape a bug can have: the call connects,\n * every state says `connected`, both ends are billed for it, and one person\n * hears nothing. Nothing is logged, because the rejection was caught and\n * dropped, so there is no evidence anywhere that it happened.\n *\n * So now it is reported and it is retried. The retry is armed on the next\n * gesture anywhere on the page, which is the one thing that reliably lifts an\n * autoplay block, and it is armed in capture so a handler calling\n * `stopPropagation` cannot hide the gesture from it. Whoever is on the call is\n * about to touch something — the mute button, the hang-up button, the report\n * they are talking about — and the sound comes up when they do, with the\n * caller free to say so on screen in the meantime.\n */\n function playRemote() {\n if (!remoteAudio || finished) return;\n let attempt;\n try {\n attempt = remoteAudio.play?.();\n } catch {\n // A synchronous throw, which some engines do instead of rejecting.\n markBlocked();\n return;\n }\n if (!attempt || typeof attempt.then !== 'function') {\n // No promise: an older engine, or a stub. Nothing to wait on and nothing\n // to report — it either made a sound or it did not.\n markPlaying();\n return;\n }\n attempt.then(markPlaying, markBlocked);\n }\n\n function markPlaying() {\n disarmGesture?.();\n disarmGesture = null;\n if (!blocked) return;\n blocked = false;\n sayBlocked(false);\n }\n\n function markBlocked() {\n if (finished) return;\n if (!blocked) {\n blocked = true;\n sayBlocked(true);\n }\n armGesture();\n }\n\n /** Try again on the next thing the person touches. Armed once. */\n function armGesture() {\n if (disarmGesture) return;\n // Guarded rather than assumed: this file is also loaded by a host page's\n // bundler into environments that define a partial `window`, and a retry\n // that cannot be armed is a quieter failure than one that throws inside a\n // promise nobody is awaiting.\n if (typeof window === 'undefined' || typeof window.addEventListener !== 'function') return;\n const retry = () => playRemote();\n const events = ['pointerdown', 'keydown', 'touchstart'];\n for (const name of events) {\n window.addEventListener(name, retry, { capture: true, passive: true });\n }\n disarmGesture = () => {\n for (const name of events) {\n try { window.removeEventListener(name, retry, { capture: true }); } catch { /* gone */ }\n }\n };\n }\n\n /**\n * Build the peer connection.\n *\n * Made on `session.created` rather than lazily on the first signal, because\n * the ICE gathering it starts is the slowest part of connecting and the whole\n * of it can happen while the phone is still ringing. Waiting until somebody\n * answers would add that delay to the exact moment a person says hello, which\n * is the one moment on this call where it is audible.\n */\n const build = (iceServers) => {\n peer = new RTCPeerConnection({ iceServers: iceServers?.length ? iceServers : FALLBACK_ICE });\n\n for (const track of microphone.getAudioTracks()) peer.addTrack(track, microphone);\n\n // Armed here rather than at the top, so a call that never got a peer connection has\n // nothing to tear down — and so the watch covers exactly the span in which being\n // unheard is a problem worth telling somebody about.\n unwatchMicrophone = watchMicrophone();\n\n peer.onicecandidate = (event) => {\n if (!event.candidate) return;\n send({ type: 'signal', signal: { kind: 'candidate', payload: event.candidate.toJSON() } });\n };\n\n peer.ontrack = (event) => {\n const [incoming] = event.streams;\n if (!incoming) return;\n // An element rather than the Web Audio API. Audio routed through a\n // MediaStreamAudioSourceNode loses the browser's own echo cancellation on\n // several engines, and this call has a live microphone next to it.\n if (!remoteAudio) {\n remoteAudio = document.createElement('audio');\n remoteAudio.autoplay = true;\n // Inline, for a WebView that would otherwise hand a media element to a\n // full-screen player — which on a call is a black rectangle over the\n // dashboard.\n remoteAudio.playsInline = true;\n remoteAudio.setAttribute?.('playsinline', '');\n // Off-screen rather than display:none — a hidden media element is\n // treated as invisible by some autoplay heuristics and refused.\n remoteAudio.style.cssText = 'position:fixed;width:0;height:0;opacity:0;pointer-events:none';\n document.body.appendChild(remoteAudio);\n }\n remoteAudio.srcObject = incoming;\n playRemote();\n };\n\n peer.onconnectionstatechange = routeChanged;\n // The same signal, read the other way, for the engines that do not give a reliable\n // `connectionState`. WebKit — which is every browser on iOS and the WebView this app\n // ships inside there — has historically fired `iceconnectionstatechange` and not the\n // aggregate one. Both are wired to the same handler, which reads whichever of the two\n // the engine actually maintains, so a duplicate event costs one comparison.\n peer.oniceconnectionstatechange = routeChanged;\n };\n\n /**\n * What the media route is doing, and what to do about it.\n *\n * Reads whichever state the engine keeps up to date, and treats `completed` — which only\n * the ICE-level state has — as connected, because it is: it means a route was found and\n * checking has stopped.\n */\n function routeChanged() {\n if (!peer || finished) return;\n const state = peer.connectionState || peer.iceConnectionState;\n\n if (state === 'connected' || state === 'completed') {\n connectedOnce = true;\n restarts = 0;\n clearRecovery();\n // Emitted whether or not this is a reconnection. The caller renders one state, and a\n // card that came back from \"reconnecting\" has to be told the call is up again.\n reconnecting = false;\n emit('connected');\n return;\n }\n\n if (state === 'disconnected') {\n // Before the two ends ever met, a `disconnected` is ICE still working — it goes\n // there between checking one candidate pair and the next. Only `failed` is final at\n // that stage, and it is handled below.\n if (!connectedOnce) return;\n enterRecovery(RECOVER_GRACE_MS);\n return;\n }\n\n if (state === 'failed') {\n if (!connectedOnce) {\n // The two browsers never found a route to each other at all. Almost always a\n // network that needs a TURN relay the deployment has not configured; reported as\n // its own reason so that is diagnosable from a support ticket rather than from a\n // packet capture. A restart would try the same candidates again.\n finish('failed', { reason: 'ice' });\n return;\n }\n // A route that worked and then stopped. That is a network change, and a restart is\n // exactly the remedy — no grace, because `failed` is not going to resolve itself.\n enterRecovery(0);\n }\n }\n\n /** Say the call is off the air, and line up an attempt to bring it back. */\n function enterRecovery(delay) {\n if (finished) return;\n if (!reconnecting) {\n reconnecting = true;\n emit('reconnecting');\n giveUpTimer = setTimeout(() => finish('failed', { reason: 'network' }), RECOVER_DEADLINE_MS);\n }\n // One attempt in flight at a time. `disconnected` and `failed` both arrive on the way\n // down, and two timers would mean two offers crossing on one connection.\n if (recoverTimer) return;\n recoverTimer = setTimeout(recover, delay);\n }\n\n function clearRecovery() {\n if (recoverTimer) { clearTimeout(recoverTimer); recoverTimer = null; }\n if (giveUpTimer) { clearTimeout(giveUpTimer); giveUpTimer = null; }\n }\n\n /**\n * Renegotiate the media route over the signaling socket, which never left.\n *\n * `createOffer({ iceRestart: true })` rather than `restartIce()`: the latter is the\n * modern spelling and does the same thing by firing `negotiationneeded`, but it is\n * missing from engines this SDK is expected to run in and the explicit form works\n * everywhere. It also keeps the offer on the same path as the original one, so there is a\n * single place where an offer is made.\n */\n async function recover() {\n recoverTimer = null;\n if (finished || !peer) return;\n // It came back on its own while the grace period was running, which is the common case\n // and the reason the grace period exists.\n const state = peer.connectionState || peer.iceConnectionState;\n if (state === 'connected' || state === 'completed') return;\n\n if (!shouldOffer) {\n // Not this side's to restart. Ask, and keep asking — the offerer sees the same drop\n // and is very likely already doing it, so this is the belt to that braces.\n send({ type: 'signal', signal: { kind: 'restart', payload: {} } });\n recoverTimer = setTimeout(recover, RECOVER_RETRY_MS);\n return;\n }\n\n if (restarts >= MAX_RESTARTS) return; // the deadline is what ends it\n restarts += 1;\n await restartIce();\n recoverTimer = setTimeout(recover, RECOVER_RETRY_MS);\n }\n\n /** The restart itself, also reachable from the other side's request. */\n async function restartIce() {\n if (finished || !peer) return;\n try {\n const description = await peer.createOffer({ iceRestart: true });\n await peer.setLocalDescription(description);\n send({ type: 'signal', signal: { kind: 'offer', payload: peer.localDescription.toJSON() } });\n } catch {\n // Nothing to do here but let the next attempt or the deadline decide. Ending the\n // call on one refused renegotiation would throw away the retries that follow.\n }\n }\n\n /**\n * Whether this person is still being heard.\n *\n * A track's `muted` is not the mute button — that is `enabled`, which this file sets and\n * therefore already knows about. `muted` is the *source* stopping: the OS handed the\n * microphone to an incoming phone call, a headset was unplugged, or — the case this app\n * meets daily — Android revoked microphone access because the app went to the background.\n *\n * Which produces the same shape as every other bug on this path: the call is up, the\n * other person is audible, both ends are billed, and nothing this person says goes\n * anywhere. They find out when the other person says \"hello? are you there?\".\n *\n * The loss is reported on a short delay and the recovery immediately. Several engines\n * flick `muted` on for a few frames while a track settles, and a card that said \"your\n * microphone was cut off\" for 200ms at the start of every call would be noise — while a\n * microphone that came back and was still reported lost is the error saying the wrong\n * thing.\n */\n function watchMicrophone() {\n const SETTLE_MS = 600;\n let settle = null;\n let lost = false;\n\n const say = (live) => {\n if (settle) { clearTimeout(settle); settle = null; }\n if (live) {\n if (!lost) return;\n lost = false;\n sayMicrophone(true);\n return;\n }\n if (lost) return;\n settle = setTimeout(() => {\n settle = null;\n if (finished) return;\n lost = true;\n sayMicrophone(false);\n }, SETTLE_MS);\n };\n\n for (const track of microphone.getAudioTracks()) {\n track.onmute = () => say(false);\n track.onunmute = () => say(true);\n // An ended track never comes back — the device is gone. Said at once rather than\n // after the settle, because there is nothing to wait for.\n track.onended = () => {\n if (settle) { clearTimeout(settle); settle = null; }\n if (lost) return;\n lost = true;\n sayMicrophone(false);\n };\n }\n return () => {\n if (settle) { clearTimeout(settle); settle = null; }\n for (const track of microphone.getAudioTracks()) {\n track.onmute = null;\n track.onunmute = null;\n track.onended = null;\n }\n };\n }\n\n const offer = async () => {\n try {\n const description = await peer.createOffer();\n await peer.setLocalDescription(description);\n send({ type: 'signal', signal: { kind: 'offer', payload: peer.localDescription.toJSON() } });\n } catch {\n finish('failed', { reason: 'negotiation' });\n }\n };\n\n const accept = async (payload) => {\n try {\n await peer.setRemoteDescription(new RTCSessionDescription(payload));\n const answer = await peer.createAnswer();\n await peer.setLocalDescription(answer);\n send({ type: 'signal', signal: { kind: 'answer', payload: peer.localDescription.toJSON() } });\n await drainCandidates();\n } catch {\n finish('failed', { reason: 'negotiation' });\n }\n };\n\n const drainCandidates = async () => {\n while (pending.length) {\n const candidate = pending.shift();\n try {\n await peer.addIceCandidate(new RTCIceCandidate(candidate));\n } catch {\n // One unusable candidate is not a failed call — there are usually\n // several, and the connection needs only one of them to work.\n }\n }\n };\n\n /**\n * One message at a time, in the order they arrived.\n *\n * Handling is asynchronous — `setRemoteDescription` and `createAnswer` are\n * promises — and the socket does not wait. Without this chain a candidate\n * arriving mid-answer is examined while the description it belongs to is\n * still being applied, so it goes on the pending list a moment after the\n * pending list was drained, and stays there for the rest of the call.\n */\n let handling = Promise.resolve();\n socket.onmessage = (event) => {\n handling = handling.then(() => handle(event)).catch(() => { /* one bad frame */ });\n };\n\n const handle = async (event) => {\n if (typeof event.data !== 'string') return;\n let message;\n try { message = JSON.parse(event.data); } catch { return; }\n\n switch (message.type) {\n case 'session.created': {\n build(message.data?.ice_servers);\n // What this call keeps, and in what format. Held rather than acted on:\n // nothing is uplinked until somebody is actually on the other end.\n capture = message.data?.capture || null;\n // Ringing, until somebody is on the other side. Whoever answers finds\n // the caller already waiting and skips straight past it — the\n // `peer.joined` that follows a moment later says the same thing, and\n // this is what keeps the answerer's first frame from being silence.\n if (message.data?.peer_present) {\n emit('connecting');\n void startUplink();\n break;\n }\n emit('ringing');\n armRingTimer();\n break;\n }\n\n case 'peer.joined': {\n if (ringTimer) { clearTimeout(ringTimer); ringTimer = null; }\n emit('connecting');\n // Told, not inferred. Both ends learn somebody arrived in the same\n // frame, and only one of them is asked to go first.\n //\n // Remembered as well as acted on: it is also the answer to \"whose job is it to\n // renegotiate when the network changes\", and that question is asked later, when\n // the frame that answered it is long gone. See `recover`.\n shouldOffer = Boolean(message.data?.should_offer);\n if (shouldOffer) await offer();\n // After the offer, and not awaited: opening an audio context is\n // milliseconds, but they would be milliseconds added to the moment\n // somebody says hello, which is the one place on this call where they\n // are audible.\n void startUplink();\n break;\n }\n\n case 'signal': {\n if (!peer) break;\n const { kind, payload } = message.data || {};\n if (kind === 'offer') {\n await accept(payload);\n } else if (kind === 'answer') {\n try {\n await peer.setRemoteDescription(new RTCSessionDescription(payload));\n await drainCandidates();\n } catch {\n finish('failed', { reason: 'negotiation' });\n }\n } else if (kind === 'candidate') {\n if (peer.remoteDescription) {\n try { await peer.addIceCandidate(new RTCIceCandidate(payload)); } catch { /* see above */ }\n } else {\n pending.push(payload);\n }\n } else if (kind === 'restart') {\n // The other side lost the route and cannot renegotiate itself. Honoured only by\n // the side that was nominated to offer, so a request arriving at both ends of a\n // mis-signalled call cannot produce two offers crossing.\n if (shouldOffer) await restartIce();\n }\n break;\n }\n\n case 'peer.left':\n finish('ended', { reason: 'peer_left' });\n break;\n\n case 'error':\n finish('failed', { reason: 'server', detail: message.error?.detail });\n break;\n\n case 'session.ended':\n finish('ended', { reason: 'server' });\n break;\n\n default:\n // Every message the agent conversation has — audio deltas, transcripts,\n // state changes. None of them happen on a bridged call, and one\n // arriving is not worth ending a working call over.\n break;\n }\n };\n\n socket.onclose = () => finish('ended', { reason: 'disconnected' });\n socket.onerror = () => finish('failed', { reason: 'socket' });\n\n /**\n * Stop waiting.\n *\n * The deadline comes from the server, which wrote it down when the call was\n * created — so a browser with a slow clock waits a little longer rather than\n * hanging up on somebody mid-answer, and the server refuses a give-up that\n * arrives early in any case.\n */\n function armRingTimer() {\n if (!ringsUntil) return;\n const remaining = new Date(ringsUntil).getTime() - Date.now();\n if (!Number.isFinite(remaining)) return;\n ringTimer = setTimeout(() => finish('missed'), Math.max(0, remaining));\n }\n\n return {\n hangUp() {\n // Said out loud before the socket closes. A close on its own would end\n // the call too — sphoro.voice ends a bridge the moment one side leaves —\n // but saying so first is what distinguishes hanging up from a tunnel.\n send({ type: 'session.end' });\n finish('ended', { reason: 'hung_up' });\n },\n setMuted(muted) {\n for (const track of microphone.getAudioTracks()) track.enabled = !muted;\n return muted;\n },\n isMuted() {\n return microphone.getAudioTracks().every((track) => !track.enabled);\n },\n };\n}\n\n/** Open the socket, resolving only once it is actually open. */\nfunction openSocket(url) {\n return new Promise((resolve, reject) => {\n let socket;\n try {\n socket = new WebSocket(url, SUBPROTOCOL);\n } catch (err) {\n reject(err);\n return;\n }\n socket.onopen = () => {\n socket.onopen = null;\n socket.onerror = null;\n resolve(socket);\n };\n socket.onerror = () => {\n socket.onopen = null;\n socket.onerror = null;\n reject(new Error('[BugCatch] could not connect the call.'));\n };\n });\n}\n\n/**\n * Say something went wrong with the copy, without ending anything.\n *\n * `console.warn` and not a state: every caller of this file renders the states\n * to somebody who is mid-conversation, and \"the recording did not start\" is not\n * news to the two people talking. It is news to whoever turned recording on,\n * and the console is where they will look.\n */\nfunction warn(message) {\n try { console.warn(`[BugCatch] ${message}`); } catch { /* no console */ }\n}\n\nfunction stopStream(stream) {\n for (const track of stream?.getTracks?.() ?? []) {\n try { track.stop(); } catch { /* already stopped */ }\n }\n}\n","// The reporter's telephone.\n//\n// Everything else this SDK draws is a consequence of something the person on\n// the page did: they pressed the widget, they filed a report, they asked to\n// talk it through. This is the one thing that arrives unasked — a member of the\n// team has a question about a report and is ringing to ask it — and that makes\n// it the piece with the most rules around it.\n//\n// # What it is allowed to do\n//\n// Appear, and make a noise. That is the whole of it. No microphone is opened,\n// no audio moves and nothing is connected until the person presses Answer, and\n// if they press nothing at all it takes itself away when the ringing stops. The\n// card is an offer; the SDK does not put a customer on a call because somebody\n// at the vendor clicked a button.\n//\n// # And what it says\n//\n// A name, a product and what the call is about, because a card that says\n// \"someone is calling about a report\" is indistinguishable from the thing every\n// bank tells people to hang up on. And whether the call would be recorded,\n// stated before Answer rather than after — on a peer-to-peer call the copy that\n// gets kept is uplinked by this browser, so telling somebody afterwards is\n// telling them about something that already happened.\n//\n// # The noise\n//\n// Synthesised, for the reason the dashboard's is: a ringtone as an audio file\n// is a binary in a bundle that runs on somebody else's site, an extra request\n// on their page, and a thing that can go missing behind a cache header. Four\n// oscillators and a table of numbers cost none of that.\n//\n// It is also softer and shorter than the dashboard's. A phone on a support\n// desk is meant to interrupt; this one is ringing in the middle of a stranger's\n// shopping cart, and a page that suddenly plays a loud telephone at somebody is\n// a page they close. If the browser refuses to play it at all — no interaction\n// yet, which is its rule and not ours — the card rings silently rather than not\n// appearing.\n\n/**\n * A quiet double-pulse, once every three seconds.\n *\n * Two notes rather than one so it reads as a telephone rather than as an alert,\n * and a fifth apart because that interval is the one people hear as \"ringing\"\n * across every phone system anybody has used.\n */\nconst RING = {\n cycleMs: 3000,\n peak: 0.045,\n bursts: [\n { at: 0, seconds: 0.4, hz: [440, 660] },\n { at: 0.55, seconds: 0.4, hz: [440, 660] },\n ],\n};\n\n/**\n * Whether anybody has touched this page yet.\n *\n * Tracked rather than asked, because there is nothing to ask: a browser will\n * tell you an AudioContext is suspended, but only after you have built one —\n * and building one on a page with no interaction is what writes\n *\n * The AudioContext was not allowed to start. It must be resumed (or\n * created) after a user gesture on the page.\n *\n * into the console. This SDK runs on somebody else's site, and that console\n * belongs to whoever is trying to debug their own product. A warning we cannot\n * act on, for a sound that was never going to play, is not ours to put there.\n */\nlet touched = false;\nif (typeof window !== 'undefined' && window.addEventListener) {\n for (const evt of ['pointerdown', 'keydown', 'touchstart']) {\n // Capture, and it matters more here than it would in our own app: this runs\n // on somebody else's page, whose handlers we have never seen and one of\n // which may well stop a click before it reaches the window.\n window.addEventListener(evt, () => { touched = true; }, {\n once: true, capture: true, passive: true,\n });\n }\n}\n\n/**\n * The same question asked of the browser rather than of ourselves.\n *\n * Chrome and Edge implement this and are the browsers that enforce the policy\n * in the first place, so where it matters it answers. It is the recovery for a\n * host page that swallowed every gesture before we saw it — better to make a\n * quiet noise the person expects than to stay silent because of somebody else's\n * event handler.\n */\nfunction activatedByBrowser() {\n try {\n return Boolean(navigator.userActivation?.hasBeenActive);\n } catch {\n return false;\n }\n}\n\n/**\n * Start ringing, and give back the way to stop.\n *\n * Returns a no-op teardown on every browser that will not do it, which is a\n * real and ordinary case: a page nobody has clicked on yet has no audio\n * permission, and the correct behaviour there is a card that appears in silence\n * rather than no card.\n */\nfunction startRinging() {\n if (!touched && !activatedByBrowser()) return () => {};\n\n const Ctx = typeof window !== 'undefined' && (window.AudioContext || window.webkitAudioContext);\n if (!Ctx) return () => {};\n\n let context;\n try {\n context = new Ctx();\n } catch {\n return () => {};\n }\n\n let stopped = false;\n let timer = null;\n const voices = [];\n\n const burst = ({ at, seconds, hz }) => {\n const start = context.currentTime + at;\n const gain = context.createGain();\n gain.connect(context.destination);\n // Shaped rather than switched. A square edge on a sine is a click, and a\n // click every three seconds is more irritating than the tone it introduces.\n gain.gain.setValueAtTime(0, start);\n gain.gain.linearRampToValueAtTime(RING.peak, start + 0.02);\n gain.gain.setValueAtTime(RING.peak, start + seconds - 0.06);\n gain.gain.linearRampToValueAtTime(0, start + seconds);\n\n for (const frequency of hz) {\n const osc = context.createOscillator();\n osc.type = 'sine';\n osc.frequency.value = frequency;\n osc.connect(gain);\n osc.start(start);\n osc.stop(start + seconds + 0.02);\n voices.push(osc);\n }\n };\n\n const cycle = () => {\n if (stopped) return;\n try {\n RING.bursts.forEach(burst);\n } catch { /* the card is still on screen, which is the part that matters */ }\n };\n\n const begin = () => {\n if (stopped) return;\n cycle();\n timer = setInterval(cycle, RING.cycleMs);\n };\n\n if (context.state === 'running') begin();\n else context.resume().then(begin).catch(() => {});\n\n return () => {\n stopped = true;\n if (timer) clearInterval(timer);\n // Stopped rather than left to finish. Answered, this is cut off by the\n // microphone opening a fraction of a second later, and a ring still sounding\n // over the first word of a conversation is the thing people apologise for.\n for (const osc of voices) {\n try { osc.stop(); } catch { /* already stopped on its own schedule */ }\n }\n try { context.close(); } catch { /* nothing left to close */ }\n };\n}\n\n/** Seconds left until a deadline, floored at zero. */\nfunction secondsLeft(ringsUntil) {\n const until = Date.parse(ringsUntil || '');\n if (Number.isNaN(until)) return 0;\n return Math.max(0, Math.round((until - Date.now()) / 1000));\n}\n\nconst BUTTON = [\n 'flex:1',\n 'padding:9px 12px',\n 'border:0',\n 'border-radius:8px',\n 'font:600 13px/1.4 inherit',\n 'cursor:pointer',\n].join(';');\n\n/**\n * Put the card on screen and ring until something happens to it.\n *\n * @param {object} opts\n * @param {object} opts.call The `incomingCall` object off the update stream —\n * `callId`, `from`, `project`, `reference`, `ringsUntil`, `recorded`.\n * @param {string} opts.accent The integration's own colour, for Answer.\n * @param {string} [opts.position] Same two words the toast takes.\n * @param {() => void} opts.onAnswer Pressed Answer.\n * @param {() => void} opts.onDecline Pressed Decline.\n * @param {() => void} [opts.onExpire] Nobody pressed anything in time.\n * @returns {(reason?: string) => void} Take it away. Called with no reason on\n * an ordinary dismissal; the caller passes one only for its own logging.\n */\nexport function showIncomingCall({\n call, accent = '#5b6ef5', position = 'bottom-right',\n onAnswer, onDecline, onExpire,\n}) {\n if (typeof document === 'undefined' || !document.body) return () => {};\n\n const [vertical, horizontal] = String(position).split('-');\n const el = document.createElement('div');\n el.setAttribute('role', 'alertdialog');\n el.setAttribute('aria-live', 'assertive');\n el.setAttribute('aria-label', `${call.from || 'Support'} is calling about ${call.reference}`);\n el.style.cssText = [\n 'position:fixed',\n `${vertical === 'top' ? 'top' : 'bottom'}:20px`,\n `${horizontal === 'left' ? 'left' : 'right'}:20px`,\n // Above the update toast, which uses the maximum and may be on screen at\n // the same moment — a status line must not sit on top of a ringing phone.\n 'z-index:2147483647',\n 'width:300px',\n 'max-width:calc(100vw - 40px)',\n 'padding:14px',\n 'border-radius:12px',\n 'background:#1b1e26',\n 'color:#eceef2',\n 'font:13px/1.5 -apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif',\n 'box-shadow:0 18px 44px rgba(0,0,0,0.5)',\n `border:1px solid ${accent}`,\n 'opacity:0',\n 'transform:translateY(8px)',\n 'transition:opacity .2s ease,transform .2s ease',\n ].join(';');\n\n const heading = document.createElement('div');\n heading.style.cssText = 'font-weight:600;font-size:14px;margin-bottom:2px';\n // textContent throughout: every string on this card came from a person typing\n // it, and it is being written into somebody else's page.\n heading.textContent = `${call.from || 'Support'} is calling`;\n\n const about = document.createElement('div');\n about.style.cssText = 'color:#a4abba;margin-bottom:10px';\n about.textContent = call.project\n ? `About your report ${call.reference} · ${call.project}`\n : `About your report ${call.reference}`;\n\n const countdown = document.createElement('div');\n countdown.style.cssText = 'color:#7d8598;font-size:12px;margin-bottom:10px';\n\n const answer = document.createElement('button');\n answer.type = 'button';\n answer.textContent = 'Answer';\n answer.style.cssText = `${BUTTON};background:${accent};color:#fff`;\n\n const decline = document.createElement('button');\n decline.type = 'button';\n decline.textContent = 'Not now';\n decline.style.cssText = `${BUTTON};background:#2b2f3a;color:#c8cedb`;\n\n const actions = document.createElement('div');\n actions.style.cssText = 'display:flex;gap:8px';\n actions.append(answer, decline);\n\n el.append(heading, about, countdown);\n if (call.recorded) {\n const kept = document.createElement('div');\n kept.style.cssText = 'color:#7d8598;font-size:12px;margin-bottom:10px';\n kept.textContent = 'This call will be recorded.';\n el.append(kept);\n }\n el.append(actions);\n document.body.appendChild(el);\n\n const stopRinging = startRinging();\n let closed = false;\n let ticker = null;\n\n const close = () => {\n if (closed) return;\n closed = true;\n if (ticker) clearInterval(ticker);\n stopRinging();\n el.style.opacity = '0';\n el.style.transform = 'translateY(8px)';\n setTimeout(() => el.remove(), 250);\n };\n\n const tick = () => {\n const left = secondsLeft(call.ringsUntil);\n countdown.textContent = left\n ? `Ringing — ${left}s`\n : 'The call has stopped ringing.';\n if (left) return;\n // The deadline is the server's, not this browser's arithmetic, for the\n // reason the API writes it down: two clocks disagreeing about when a phone\n // stopped ringing is how somebody gets cut off mid-answer.\n close();\n try { onExpire?.(); } catch { /* theirs */ }\n };\n tick();\n ticker = setInterval(tick, 1000);\n\n answer.addEventListener('click', () => {\n // Stopped before the handler runs, not after: opening a microphone takes a\n // moment, and a phone still ringing during it sounds like the button\n // did nothing.\n close();\n try { onAnswer?.(); } catch { /* theirs */ }\n });\n\n decline.addEventListener('click', () => {\n close();\n try { onDecline?.(); } catch { /* theirs */ }\n });\n\n requestAnimationFrame(() => {\n el.style.opacity = '1';\n el.style.transform = 'translateY(0)';\n });\n\n return close;\n}\n","/**\n * Who is reporting, what they are looking at, and whether init() was given\n * something that can work — shaped here, before anything is sent.\n *\n * Pure and import-free so the unit tests load it directly. Every function takes\n * a `warn(message)` callback instead of calling console itself: the client\n * decides whether (and how often) a warning is printed, and the tests collect\n * them.\n *\n * WHY THE BUSINESS IDS HAVE NAMES. A lending app knows exactly which customer is\n * signed in and which loan is on screen. Passed as `loanId`, `applicationId` or\n * `customerNumber`, the API reads them as identifiers the app itself asserted\n * (reference source `identify`), which is what lets bugcatch look the customer\n * up in the lender's own system over MCP without asking the reporter — and lets\n * that lookup count as a trusted join. Passed under any other name they are\n * still sent, but only as metadata a person can read.\n *\n * WHERE THEY GO. The signed-in user's fields travel as `metadata.user`, which is\n * where the API has always read `id` and where the ownership check reads `phone`\n * and `email`. The current screen travels as `metadata.context`. Both shapes are\n * mirrored by bugcatch-api/src/settings/reference-extract.ts identifyReferences.\n */\n\n/** Longest identifier the API keeps; anything longer is dropped there too. */\nexport const MAX_ID_CHARS = 120;\n/** Longest free-form string value kept on user or context. */\nexport const MAX_TEXT_CHARS = 500;\n/** Most extra (non-standard) keys kept on user or context. */\nexport const MAX_EXTRA_KEYS = 30;\n/** Largest nested object or array kept, as JSON. */\nconst MAX_NESTED_JSON = 1000;\n\n/**\n * The identifier fields bugcatch understands, and the names each accepts.\n * The first name is canonical; the rest are accepted and rewritten to it so the\n * API sees one spelling.\n */\nexport const ID_FIELDS = {\n loanId: ['loanId', 'loanNumber'],\n applicationId: ['applicationId'],\n customerNumber: ['customerNumber', 'customerId'],\n};\n\nconst CONTROL = /[\\u0000-\\u001f\\u007f]/;\n\n/** An identifier: a non-empty string or finite number, as a trimmed string, or null. */\nexport function idValue(raw) {\n if (typeof raw === 'number') return Number.isFinite(raw) ? String(raw) : null;\n if (typeof raw !== 'string') return null;\n const v = raw.trim();\n if (!v || v.length > MAX_ID_CHARS || CONTROL.test(v)) return null;\n return v;\n}\n\nfunction describe(v) {\n if (v === null) return 'null';\n if (Array.isArray(v)) return 'an array';\n return typeof v === 'object' ? 'an object' : `a ${typeof v}`;\n}\n\n/** Read one identifier field (under any of its names) off `src`, warning on a bad value. */\nfunction readId(src, canonical, names, where, warn) {\n for (const name of names) {\n if (!(name in src) || src[name] === undefined || src[name] === null || src[name] === '') continue;\n const v = idValue(src[name]);\n if (v) return v;\n const raw = src[name];\n warn(typeof raw === 'string' && raw.trim().length > MAX_ID_CHARS\n ? `${where}.${name} is longer than ${MAX_ID_CHARS} characters and was dropped — pass the identifier itself, not a sentence or a URL.`\n : `${where}.${name} should be a string or number, got ${describe(raw)} — it was dropped.`);\n }\n return null;\n}\n\n/** An extra field worth keeping: a primitive, or a small plain object / array. */\nfunction extraValue(v) {\n if (v === null || typeof v === 'boolean') return { ok: true, value: v };\n if (typeof v === 'number') return Number.isFinite(v) ? { ok: true, value: v } : { ok: false };\n if (typeof v === 'string') return { ok: true, value: v.slice(0, MAX_TEXT_CHARS) };\n if (typeof v === 'object') {\n try {\n const json = JSON.stringify(v);\n if (json && json.length <= MAX_NESTED_JSON) return { ok: true, value: JSON.parse(json) };\n } catch { /* cyclic or otherwise unserialisable */ }\n }\n return { ok: false };\n}\n\nconst USER_TEXT = ['email', 'name', 'phone'];\nconst RESERVED_USER = new Set(['id', 'userId', ...USER_TEXT, ...Object.values(ID_FIELDS).flat()]);\n\n/**\n * identify()'s argument, as it will be sent under `metadata.user`.\n *\n * Returns null for null/undefined (sign-out) and for anything that is not a\n * plain object, with a warning for the latter. Never throws.\n *\n * id string | number the customer's own account id in your system\n * email string threads their reports; where \"resolved\" mail goes\n * name string\n * phone string | number used, with email, to confirm the reporter owns\n * the record bugcatch looked up\n * loanId string | number (alias loanNumber)\n * applicationId string | number\n * customerNumber string | number (alias customerId)\n *\n * Anything else is kept as readable metadata, within limits.\n */\nexport function normalizeUser(user, warn = () => {}) {\n if (user === null || user === undefined) return null;\n if (typeof user !== 'object' || Array.isArray(user)) {\n warn(`identify() takes an object like { id, email, name } or null, got ${describe(user)} — ignored.`);\n return null;\n }\n const out = {};\n try {\n const id = readId(user, 'id', ['id', 'userId'], 'identify()', warn);\n if (id) out.id = id;\n for (const field of USER_TEXT) {\n const raw = user[field];\n if (raw === undefined || raw === null || raw === '') continue;\n if (field === 'phone' && typeof raw === 'number' && Number.isFinite(raw)) { out.phone = String(raw); continue; }\n if (typeof raw !== 'string') {\n warn(`identify().${field} should be a string, got ${describe(raw)} — it was dropped.`);\n continue;\n }\n const v = raw.trim().slice(0, field === 'name' ? 200 : MAX_ID_CHARS);\n if (!v) continue;\n if (field === 'email' && !v.includes('@')) {\n warn(`identify().email \"${v}\" does not look like an email address — reports cannot be threaded or followed up without one.`);\n }\n out[field] = v;\n }\n for (const [canonical, names] of Object.entries(ID_FIELDS)) {\n const v = readId(user, canonical, names, 'identify()', warn);\n if (v) out[canonical] = v;\n }\n let extras = 0;\n for (const [key, raw] of Object.entries(user)) {\n if (RESERVED_USER.has(key) || raw === undefined || typeof raw === 'function') continue;\n if (extras >= MAX_EXTRA_KEYS) { warn(`identify() was given more than ${MAX_EXTRA_KEYS} extra fields; the rest were dropped.`); break; }\n const kept = extraValue(raw);\n if (!kept.ok) { warn(`identify().${key} could not be sent (${describe(raw)} too large or not serialisable) — dropped.`); continue; }\n out[key] = kept.value;\n extras += 1;\n }\n } catch {\n // A getter that throws, a proxy — whatever it was, the report goes out with\n // what was read before it.\n }\n if (!out.id && !out.email) {\n warn('identify() was called without an id or email — reports will not be attributed to anyone, and nothing can be looked up by user.');\n }\n return out;\n}\n\n/**\n * setContext()'s argument merged over what was already set, as it will be sent\n * under `metadata.context`.\n *\n * `null` for the patch clears everything; a key set to null/undefined removes\n * that key. Identifier fields are validated exactly as identify()'s are and\n * rewritten to their canonical names; everything else (screen, route, feature\n * flag…) is kept within limits. Never throws.\n */\nexport function mergeContext(prev, patch, warn = () => {}) {\n if (patch === null) return {};\n const out = { ...(prev || {}) };\n if (patch === undefined) return out;\n if (typeof patch !== 'object' || Array.isArray(patch)) {\n warn(`setContext() takes an object like { loanId, screen } or null, got ${describe(patch)} — ignored.`);\n return out;\n }\n try {\n const aliases = new Map();\n for (const [canonical, names] of Object.entries(ID_FIELDS)) for (const n of names) aliases.set(n, canonical);\n for (const [key, raw] of Object.entries(patch)) {\n const canonical = aliases.get(key);\n if (raw === null || raw === undefined) { delete out[canonical || key]; continue; }\n if (canonical) {\n const v = readId({ [key]: raw }, canonical, [key], 'setContext()', warn);\n if (v) out[canonical] = v; else delete out[canonical];\n continue;\n }\n if (typeof raw === 'function') continue;\n if (!(key in out) && Object.keys(out).length >= MAX_EXTRA_KEYS) {\n warn(`setContext() holds at most ${MAX_EXTRA_KEYS} keys; \"${key}\" was dropped.`);\n continue;\n }\n const kept = extraValue(raw);\n if (!kept.ok) { warn(`setContext().${key} could not be sent (${describe(raw)} too large or not serialisable) — dropped.`); continue; }\n out[key] = kept.value;\n }\n } catch { /* keep what was merged so far */ }\n return out;\n}\n\n/** Every init() option this SDK reads. Anything else is probably a typo. */\nexport const KNOWN_OPTIONS = [\n 'apiBase', 'apiKey', 'projectId', 'consoleLimit', 'networkLimit', 'captureConsole',\n 'captureNetwork', 'captureErrors', 'widget', 'widgetPosition', 'theme', 'accent',\n 'attachments', 'maxAttachments', 'maxAttachmentBytes', 'maxRecordingMs', 'beforeSend',\n 'user', 'context', 'updates', 'updateToast', 'onUpdate', 'voice', 'onCallState',\n 'onIncomingCall', 'onReportSubmitted', 'onAssistantPhase', 'onError', 'debug',\n 'drafts', 'knownIssues', 'agentIssues', 'release', 'environment', 'buildId',\n];\n\nconst CALLBACK_OPTIONS = [\n 'beforeSend', 'onUpdate', 'onCallState', 'onIncomingCall', 'onReportSubmitted',\n 'onAssistantPhase', 'onError',\n];\n\n/** The closest known option to a misspelt one, when it is close enough to suggest. */\nfunction suggest(name) {\n const lower = name.toLowerCase().replace(/[-_]/g, '');\n return KNOWN_OPTIONS.find((k) => k.toLowerCase() === lower) || null;\n}\n\nconst PLACEHOLDER = /^(your[_-]|<|xxx|todo|changeme)/i;\nconst UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/**\n * What is wrong with init()'s options, as two lists: `errors` stop init (it\n * throws the first), `warnings` are printed and init carries on.\n *\n * `pageHost` is the page's hostname, passed in so this stays pure; it is what\n * tells a production page still pointed at the localhost default apart from a\n * developer's machine.\n */\nexport function checkOptions(options, { pageHost = '' } = {}) {\n const errors = [];\n const warnings = [];\n if (!options || typeof options !== 'object') {\n errors.push('init() takes an options object: BugCatch.init({ apiBase, apiKey, projectId }).');\n return { errors, warnings };\n }\n const { apiKey, projectId, apiBase } = options;\n\n if (!apiKey) errors.push('apiKey is required — create one under Workspace Settings → API Keys.');\n else if (typeof apiKey !== 'string') errors.push(`apiKey must be a string, got ${describe(apiKey)}.`);\n else if (PLACEHOLDER.test(apiKey)) errors.push(`apiKey is still the placeholder \"${apiKey}\" — paste the key you saved when you created it.`);\n else if (UUID.test(apiKey)) warnings.push('apiKey looks like a project id — did you swap apiKey and projectId?');\n else if (!apiKey.startsWith('bc_')) warnings.push('apiKey does not look like a BugCatch key (they start with \"bc_live_\") — reports will be refused with HTTP 401.');\n\n if (!projectId) errors.push('projectId is required — copy it from Project Settings → Web SDK.');\n else if (typeof projectId !== 'string') errors.push(`projectId must be a string, got ${describe(projectId)}.`);\n else if (PLACEHOLDER.test(projectId)) errors.push(`projectId is still the placeholder \"${projectId}\".`);\n else if (projectId.startsWith('bc_')) warnings.push('projectId looks like an API key — did you swap apiKey and projectId?');\n else if (/\\s/.test(projectId)) warnings.push('projectId contains whitespace — check it was copied cleanly.');\n\n if (apiBase === undefined || apiBase === null) {\n const local = !pageHost || /^(localhost|127\\.|0\\.0\\.0\\.0|\\[::1\\])/.test(pageHost);\n if (!local) warnings.push('apiBase is not set, so reports go to http://localhost:3000 — set apiBase to your BugCatch API URL.');\n } else if (typeof apiBase !== 'string' || !/^https?:\\/\\//.test(apiBase)) {\n warnings.push(`apiBase should be an absolute http(s) URL, got ${JSON.stringify(apiBase)}.`);\n }\n\n for (const key of Object.keys(options)) {\n if (KNOWN_OPTIONS.includes(key)) continue;\n const near = suggest(key);\n warnings.push(near\n ? `unknown option \"${key}\" — did you mean \"${near}\"?`\n : `unknown option \"${key}\" is ignored.`);\n }\n for (const key of CALLBACK_OPTIONS) {\n const v = options[key];\n if (v !== undefined && v !== null && typeof v !== 'function') {\n warnings.push(`${key} should be a function, got ${describe(v)} — it will not be called.`);\n }\n }\n return { errors, warnings };\n}\n\n/** Drop a trailing slash so `${apiBase}/ingest/bugs` never has two. */\nexport function cleanApiBase(apiBase) {\n return typeof apiBase === 'string' ? apiBase.replace(/\\/+$/, '') : apiBase;\n}\n","import {\n createRingBuffer, installConsoleCapture, installNetworkCapture,\n installErrorCapture, collectEnvironment, collectEnvironmentSync,\n} from './collectors.js';\nimport { mountWidget } from './widget.js';\nimport { uploadAttachment, normalizeMime, REPORT_MIMES } from './capture.js';\nimport {\n watchUpdates, createUpdateNotifier, fetchMyReports, fetchReport, writeToken, readToken,\n clearToken, scopeReports, sendMessage, verifyFix, callKindsForProject,\n reportByCall, liveCallAvailable, requestLiveCall, giveUpLiveCall,\n answerIncomingCall, declineIncomingCall,\n readUnread, rememberUpdate, forgetUpdate, forgetReportUpdates, forgetAllUpdates,\n fetchKnownIssues, matchKnownIssues, joinKnownIssue,\n UNREAD_KEY,\n} from './updates.js';\nimport {\n loginState, startSignIn, confirmSignIn, endSignIn, readSession, clearSession,\n} from './session.js';\nimport { joinLiveCall, liveCallSupported, openMicrophone } from './live-call.js';\nimport { showIncomingCall } from './incoming-call.js';\nimport { referenceFields } from './references.js';\nimport { fetchAgentIssues } from './agent-issues.js';\nimport { normalizeUser, mergeContext, checkOptions, cleanApiBase } from './identity.js';\n\n// Replaced at build time from package.json. The guard is for anyone importing\n// src/ directly, where the define never runs.\nconst SDK_IDENTITY = {\n name: typeof __SDK_NAME__ === 'string' ? __SDK_NAME__ : '@sphoro/bugcatch-web-sdk',\n version: typeof __SDK_VERSION__ === 'string' ? __SDK_VERSION__ : '0.0.0-dev',\n};\n\nconst DEFAULTS = {\n apiBase: 'http://localhost:3000',\n apiKey: null,\n projectId: null,\n consoleLimit: 100,\n networkLimit: 50,\n captureConsole: true,\n captureNetwork: true,\n captureErrors: true,\n // Shows the floating \"Report a bug\" button. Turn off to drive it yourself.\n widget: true,\n widgetPosition: 'bottom-right',\n // 'auto' follows the visitor's system setting; 'light' / 'dark' pin it.\n theme: 'auto',\n accent: '#4F46E5',\n // Screenshot / screen recording / file attach. Capture needs getDisplayMedia,\n // so it is offered only where the browser has it — file attach always works.\n attachments: true,\n maxAttachments: 5,\n maxAttachmentBytes: 25 * 1024 * 1024,\n maxRecordingMs: 120_000,\n // Called before send; return false to cancel, or mutate the payload to scrub.\n beforeSend: null,\n user: null,\n // Tell the reporter when a bug they filed changes status — live, in your\n // app, where they already are. Off means no connection is opened at all.\n updates: true,\n // Show the built-in notification when an update arrives. It stays until the\n // reporter marks it read — including across a reload — so an update is not\n // lost to whoever happened to be looking at the tab. Turn it off and drive\n // the notification yourself from onUpdate and unreadUpdates().\n updateToast: true,\n // Called with { id, bugId, title, status, at } whenever one of this browser's\n // reports moves. Runs whether or not the built-in notification is on; `id` is\n // what markUpdateRead() takes.\n onUpdate: null,\n // Offer a call to the team when a report is picked up, if the workspace has\n // sphoro.voice configured. Nothing is dialled and no permission is asked for\n // until somebody presses the button — see startCall().\n voice: true,\n // Called as a call moves: 'ringing', 'connecting', 'connected', 'missed',\n // 'ended', 'failed'. The SDK shows nothing of its own beyond the offer, so an\n // integration that wants a call UI builds it from this.\n onCallState: null,\n // Called with { bugId, callId, from, project, reference, ringsUntil,\n // recorded } when somebody on the team rings this page about a report filed\n // from it. The SDK has already drawn the card by then — this is for a host\n // application that wants to duck its own audio or pause a video, not a hook\n // that can suppress a call the person can see ringing. Set `voice: false` to\n // switch the whole thing off.\n onIncomingCall: null,\n // Called with { bugId, title, via: 'form' | 'call', result } once a report\n // has been accepted — from the widget, from report(), or from pressing Call.\n // The same thing is emitted as the 'report' event.\n onReportSubmitted: null,\n // Called with { bugId, phase, title, detail } when the AI assistant working\n // a report moves: 'analyzing' → 'needs_info' | 'confirming' | 'reviewing' |\n // 'resolving' | 'resolved'. Seen whenever the report is fetched — the widget\n // does while it is open; myReport(id) does for an integration's own view.\n onAssistantPhase: null,\n // Called with { action, error } when something the SDK did on the page's\n // behalf failed (a report that was refused, a listener that threw). Also the\n // 'error' event.\n onError: null,\n // Business context for the screen the SDK starts on — see setContext().\n context: null,\n // Log what the SDK is doing (identify, context, each report's shape) to the\n // console. Off in production; the warnings about misconfiguration are\n // printed either way.\n debug: false,\n};\n\nclass BugCatchClient {\n constructor() {\n this.config = { ...DEFAULTS };\n this.started = false;\n this.teardown = [];\n this.consoleBuffer = createRingBuffer(DEFAULTS.consoleLimit);\n this.networkBuffer = createRingBuffer(DEFAULTS.networkLimit);\n this.stopWatching = null;\n // The built-in notification stack, built on first use: an integration that\n // renders its own never has one, and neither does a visitor who has nothing\n // unread.\n this.notifier = null;\n // The call in progress, if any, and whether one is being connected. One at\n // a time: a second would be two microphones on one page talking over each\n // other.\n this.call = null;\n this.callStarting = false;\n // The card on screen for a call the team is placing to this page, if any.\n // Distinct from `call` above, which is a conversation in progress: this is\n // a telephone that has not been picked up, and the two exist at different\n // moments — a call cannot be ringing and connected at once.\n this.ringing = null;\n // event -> Set<handler>. Several parts of a host app can listen without\n // having to share the single `onUpdate` config callback between them.\n this.listeners = new Map();\n // What setContext() has said about the screen the reporter is on. Its own\n // field rather than part of config, so init() — which rebuilds config —\n // cannot wipe a context set before it ran.\n this.context = {};\n // Warnings already printed, so a misconfiguration in a render loop says so\n // once rather than a thousand times.\n this.warned = new Set();\n // The last assistant phase seen per report, so onAssistantPhase fires on a\n // change rather than on every poll.\n this.phases = new Map();\n }\n\n /** A misconfiguration, said once. Always printed — it is what saves a day of guessing. */\n warn(message) {\n if (this.warned.has(message)) return;\n this.warned.add(message);\n // eslint-disable-next-line no-console\n console.warn(`[BugCatch] ${message}`);\n }\n\n /** What the SDK is doing, for `debug: true`. */\n debugLog(...args) {\n // eslint-disable-next-line no-console\n if (this.config.debug) console.info('[BugCatch]', ...args);\n }\n\n /** Call a host callback without letting it break the SDK. */\n #callback(name, payload) {\n const fn = this.config[name];\n if (typeof fn !== 'function') return;\n try { fn(payload); } catch (err) {\n // eslint-disable-next-line no-console\n console.error(`[BugCatch] your ${name} callback threw:`, err);\n }\n }\n\n init(options = {}) {\n if (this.started) {\n console.warn('[BugCatch] init() called twice — ignoring the second call.');\n return this;\n }\n const { errors, warnings } = checkOptions(options, {\n pageHost: typeof location !== 'undefined' ? location.hostname : '',\n });\n if (errors.length) throw new Error(`[BugCatch] ${errors[0]}`);\n for (const w of warnings) this.warn(w);\n\n // identify() and setContext() may run before init() — an app that knows its\n // user before it has loaded its config is ordinary. Rebuilding config here\n // used to throw that user away; it is carried over unless init names one.\n const earlier = this.config.user;\n this.config = { ...DEFAULTS, ...options, apiBase: cleanApiBase(options.apiBase ?? DEFAULTS.apiBase) };\n this.config.user = options.user !== undefined\n ? normalizeUser(options.user, (m) => this.warn(m.replace('identify()', 'init({ user })')))\n : earlier || null;\n if (options.context) this.setContext(options.context);\n\n this.consoleBuffer = createRingBuffer(this.config.consoleLimit);\n this.networkBuffer = createRingBuffer(this.config.networkLimit);\n\n if (this.config.captureConsole) this.teardown.push(installConsoleCapture(this.consoleBuffer));\n if (this.config.captureNetwork) this.teardown.push(installNetworkCapture(this.networkBuffer));\n if (this.config.captureErrors) this.teardown.push(installErrorCapture(this.consoleBuffer));\n // Before anything reads the watch token: it is kept, and asked about, per\n // project. See scopeReports.\n scopeReports(this.config.projectId);\n if (this.config.widget) this.teardown.push(mountWidget(this));\n\n // Only if this browser has actually filed something. A visitor who has\n // never reported has nothing to be told about, and opening a connection\n // for them would be a request on every page load that can never deliver.\n if (this.config.updates && readToken()) {\n this.startWatching();\n // Whatever was still unread when they last closed the tab. The stream\n // replays nothing on connect, so without this an update that arrived\n // while somebody was away would be gone by the time they came back.\n //\n // A tick late, so the count reaches a listener registered on the line\n // after init() — which is where every integration registers one, and\n // where a synchronous emit would fire into an empty room.\n queueMicrotask(() => this.#drawUnread());\n }\n\n this.started = true;\n this.#sayHello();\n this.debugLog('initialised', {\n version: SDK_IDENTITY.version, apiBase: this.config.apiBase, projectId: this.config.projectId,\n user: this.config.user, context: this.context,\n });\n return this;\n }\n\n /**\n * Tell the project this site has the SDK — once a day per browser, a few\n * seconds after init so identify() and setContext() have had their chance.\n *\n * What the Web SDK settings page shows under \"Installed on\": which site,\n * which version, whether identify() is wired. Nothing personal goes: the\n * origin comes from the browser's own Origin header, and the rest is a\n * version string and four booleans. Every failure is silent — this must\n * never cost a page anything but one small request a day.\n */\n #sayHello() {\n if (typeof window === 'undefined' || typeof fetch !== 'function') return;\n const mark = `bugcatch:hello:${this.config.projectId}`;\n const today = new Date().toISOString().slice(0, 10);\n try { if (window.localStorage.getItem(mark) === today) return; } catch { /* storage blocked: say it anyway */ }\n const timer = setTimeout(() => {\n try { window.localStorage.setItem(mark, today); } catch { /* fine */ }\n fetch(`${this.config.apiBase}/ingest/hello`, {\n method: 'POST',\n keepalive: true,\n headers: { 'Content-Type': 'application/json', 'X-BugCatch-Key': this.config.apiKey },\n body: JSON.stringify({\n projectId: this.config.projectId,\n version: SDK_IDENTITY.version,\n identify: Boolean(this.config.user),\n context: Boolean(this.context && Object.keys(this.context).length),\n widget: Boolean(this.config.widget),\n environment: this.config.environment || null,\n release: this.config.release || null,\n }),\n }).catch(() => {});\n }, 5000);\n this.teardown.push(() => clearTimeout(timer));\n }\n\n /**\n * Open the update stream. Idempotent — init() and a first report both reach\n * here, and a second connection would double every notification.\n */\n startWatching() {\n if (this.stopWatching) return this;\n this.stopWatching = watchUpdates({\n apiBase: this.config.apiBase,\n onUpdate: (event) => {\n // A telephone, not an update — taken first and returned on, so it\n // neither queues behind a status line nor lands in the unread store. A\n // call is worthless thirty seconds after it is sent, and an unread\n // badge that still says \"someone called\" tomorrow morning is a badge\n // nobody trusts.\n if (event?.incomingCall) { this.#ring(event); return; }\n if (event?.callWithdrawn) { this.#stopRinging(event.callWithdrawn.callId); return; }\n\n // Held before it is shown, and held until it is read. The card on\n // screen is a view of this store rather than the notification itself,\n // which is what lets a reload, a second tab, or the widget marking it\n // read all agree about what is still outstanding.\n const update = rememberUpdate(event);\n if (typeof this.config.onUpdate === 'function') this.config.onUpdate(update);\n // Listeners registered with on('update') get the same frame. The\n // widget uses this to refresh a report the reader has open, so a\n // change lands on screen rather than waiting for them to reopen it —\n // and to mark it read, which is why this runs before the redraw.\n this.emit('update', update);\n this.#drawUnread();\n },\n });\n this.teardown.push(() => this.stopWatching?.());\n // And a card left ringing when the SDK is torn down is a card that rings\n // forever: the stream that would withdraw it has just been closed.\n this.teardown.push(() => this.#stopRinging());\n\n // Another tab of the same app reads one. They share the store, so they\n // share what is outstanding — without this, a card marked read in one tab\n // sits there in the other, over an app that already knows about it.\n const onStorage = (event) => {\n // A null key is localStorage.clear(), which takes the store with it.\n if (event.key === UNREAD_KEY || event.key === null) this.#drawUnread();\n };\n window.addEventListener('storage', onStorage);\n this.teardown.push(() => window.removeEventListener('storage', onStorage));\n return this;\n }\n\n /**\n * Subscribe to updates on this browser's reports. Returns an unsubscribe.\n *\n * const off = BugCatch.on('update', (u) => console.log(u.summary));\n *\n * Alongside the `onUpdate` config option rather than replacing it: that one\n * is the single handler an integration sets at init, this one lets several\n * parts of an app listen independently — a badge, a toast of your own, an\n * open report view — without them having to agree on one callback.\n */\n on(event, handler) {\n if (typeof handler !== 'function') return () => {};\n const set = this.listeners.get(event) || new Set();\n set.add(handler);\n this.listeners.set(event, set);\n return () => this.off(event, handler);\n }\n\n off(event, handler) {\n this.listeners.get(event)?.delete(handler);\n return this;\n }\n\n /** Notify listeners; one that throws must not stop the rest. */\n emit(event, payload) {\n for (const fn of this.listeners.get(event) || []) {\n try {\n fn(payload);\n } catch (err) {\n // Someone else's handler, on someone else's page.\n // eslint-disable-next-line no-console\n console.error(`[BugCatch] a '${event}' listener threw:`, err);\n }\n }\n }\n\n /**\n * Updates this browser has been told about and nobody has read yet, oldest\n * first.\n *\n * BugCatch.unreadUpdates().length // a badge on your own bell\n *\n * Survives a reload: the stream replays nothing when it reconnects, so an\n * update that arrived while the tab was closed would otherwise be gone.\n * Each carries the `id` the mark-read calls take.\n */\n unreadUpdates() {\n return readUnread();\n }\n\n /**\n * Mark one update read. It stops being shown, here and on the next load.\n *\n * BugCatch.on('update', (u) => myBell.add(u, () => BugCatch.markUpdateRead(u.id)));\n *\n * Read is something the reporter says, not something a timer decides — which\n * is the whole reason these are held rather than shown for eight seconds and\n * dropped. An integration rendering its own notifications is the one that\n * knows when its reader has seen one, so it has to say.\n */\n markUpdateRead(id) {\n forgetUpdate(id);\n this.#drawUnread();\n return this;\n }\n\n /**\n * Mark everything about one report read.\n *\n * Opening the report is reading its updates, so the widget calls this when\n * the thread opens; an integration with its own report view should too.\n */\n markReportRead(bugId) {\n forgetReportUpdates(bugId);\n this.#drawUnread();\n return this;\n }\n\n /** Mark everything read. The \"clear all\" behind your own notification list. */\n markUpdatesRead() {\n forgetAllUpdates();\n this.#drawUnread();\n return this;\n }\n\n /**\n * Redraw the built-in notifications, and tell anyone counting.\n *\n * The single path out of the unread store: every add and every mark-read ends\n * here, so the cards on screen and a host app's own badge cannot disagree\n * about what is outstanding.\n */\n #drawUnread() {\n const unread = readUnread();\n // Emitted whether or not the built-in cards are on — this is the number a\n // host app puts on its own bell.\n this.emit('unread', { count: unread.length, updates: unread });\n\n if (!this.config.updateToast) return;\n if (!this.notifier) {\n if (!unread.length) return;\n this.notifier = createUpdateNotifier({\n accent: this.config.accent,\n position: this.config.widgetPosition,\n onRead: (id) => this.markUpdateRead(id),\n onReadAll: () => this.markUpdatesRead(),\n // The call button appears only where the API said the offer stands and\n // this page has calling switched on. Pressing it reads the update —\n // acting on a notification is the clearest way there is of having seen\n // it — and `startCall` does the rest.\n onCall: this.config.voice\n ? (update) => {\n this.markUpdateRead(update.id);\n void this.startCall(update.bugId);\n }\n : null,\n });\n this.teardown.push(() => this.notifier?.destroy());\n }\n this.notifier.render(unread);\n }\n\n /**\n * One report in full, with its timeline — every step from filing to now.\n *\n * const report = await BugCatch.myReport(id);\n * report.timeline // [{ kind: 'filed' | 'status' | 'severity' | ... , summary, at }]\n *\n * Resolves to null when this browser cannot prove it filed that report.\n */\n async verifyFix(bugId, { result, resolvedAt, note }) {\n const context = result === 'STILL_HAPPENING' ? await this.#payload({}) : {};\n return verifyFix({ apiBase: this.config.apiBase, bugId, result, resolvedAt, note, metadata: context.metadata, logs: context.logs });\n }\n\n async myReport(bugId) {\n const report = await fetchReport({ apiBase: this.config.apiBase, bugId });\n this.#notePhase(bugId, report?.assistant);\n return report;\n }\n\n /**\n * Tell the host app the assistant moved, once per change.\n *\n * The phase is only known when a report is fetched, which the widget does\n * every few seconds while its thread is open and an integration does through\n * myReport(). The first sighting counts as a change: a host that has just\n * opened a report wants to know it is being analysed.\n */\n #notePhase(bugId, assistant) {\n const phase = assistant?.phase;\n if (!bugId || typeof phase !== 'string') return;\n if (this.phases.get(bugId) === phase) return;\n this.phases.set(bugId, phase);\n const detail = { bugId, phase, title: assistant.title ?? null, detail: assistant.detail ?? null };\n this.debugLog('assistant', detail);\n this.#callback('onAssistantPhase', detail);\n this.emit('assistant', detail);\n }\n\n /** A report was accepted: tell the host app, however it was filed. */\n #submitted(result, { title, via }) {\n if (!result?.id) return;\n const detail = { bugId: result.id, title: title ?? null, via, result };\n this.debugLog('report accepted', detail);\n this.#callback('onReportSubmitted', detail);\n this.emit('report', detail);\n }\n\n /** Something done on the page's behalf failed. */\n #failed(action, error) {\n this.#callback('onError', { action, error });\n this.emit('error', { action, error });\n }\n\n /**\n * Write back on a report this browser filed.\n *\n * const key = await BugCatch.uploadAttachment(file, file.type);\n * await BugCatch.replyToReport(id, {\n * body: 'Still happening on Safari',\n * attachments: [{ ...key, name: file.name, size: file.size }],\n * });\n *\n * The widget's own conversation view calls this; it is public so an\n * integration rendering its own can too. Resolves to the stored message, so\n * the caller renders what the server kept rather than what it hoped it sent.\n */\n replyToReport(bugId, { body, attachments } = {}) {\n return sendMessage({ apiBase: this.config.apiBase, bugId, body, attachments });\n }\n\n /**\n * Everything a report carries, however it was started.\n *\n * Shared by `report()` and by pressing Call, because the two file the same\n * thing — the difference is only whether a person typed a title first. Two\n * copies of this would drift, and the half that drifted would be the one that\n * stopped attaching the console.\n */\n async #payload({ title, description = '', priority = 'MEDIUM', metadata = {}, media = [], references = [], context = null, issue = null }) {\n // This report's own context over the screen's, without keeping it.\n const ctx = context ? mergeContext(this.context, context, (m) => this.warn(m.replace('setContext()', 'report({ context })'))) : this.context;\n return {\n projectId: this.config.projectId,\n ...(title ? { title } : {}),\n description,\n priority,\n url: window.location.href,\n reporterEmail: this.config.user?.email,\n // Sent alongside the address so the dashboard can show a person rather\n // than a mailbox, and so the resolution email can open with their name.\n reporterName: this.config.user?.name,\n metadata: {\n ...(await collectEnvironment()),\n release: this.config.release,\n environment: this.config.environment,\n buildId: this.config.buildId,\n ...metadata,\n // Who is signed in, as identify() shaped it: `id`, `email` and `phone`\n // are where the API and the ownership check read them, and `loanId`,\n // `applicationId`, `customerNumber` become trusted lookup keys.\n user: this.config.user || undefined,\n // The screen the report came from — setContext(). Its identifiers\n // outrank the same kind on `user`.\n ...(Object.keys(ctx).length ? { context: ctx } : {}),\n sdk: SDK_IDENTITY,\n },\n logs: {\n console: { entries: this.consoleBuffer.all() },\n network: { entries: this.networkBuffer.all() },\n },\n // Omitted rather than sent empty: the field is optional server-side, and\n // an empty array in every payload makes beforeSend hooks harder to read.\n ...(media.length ? { attachments: media } : {}),\n // What the reporter answered when asked which record this is about. Only\n // ever what they typed into the box — never what this browser matched in\n // their own text, which the API reads for itself out of the title and\n // description it is being sent anyway. A payload is a claim, and there is\n // no reason to make the join key one when the source text is right here.\n ...(references.length ? { references } : {}),\n // A predefined issue the reporter picked (agentIssues()). The API keeps\n // it only if this project offers it.\n ...(issue ? { issue } : {}),\n };\n }\n\n /**\n * File a report because somebody pressed Call instead of filling the form in.\n *\n * Filing is the half a call shares with the form, and it is the half that has\n * to say who is filing. Held in one place after a session header went missing\n * from this route while `report()` kept sending it: nothing looked wrong until\n * a project switched sign-in on, and then a signed-in reporter pressing Call\n * was told to sign in.\n */\n /**\n * The host's `beforeSend`, applied to anything about to leave the browser.\n *\n * Its own method because it was written into `report()` alone, and a\n * reporter pressing Call files a report by another route — carrying the same\n * console buffer, the same network buffer and the same identify() fields,\n * past a hook whose whole purpose is to strip them. A host that set this to\n * scrub PII was scrubbed on one button and not the other.\n *\n * Returns the payload to send, or false when the host cancelled.\n */\n #screen(payload) {\n if (typeof this.config.beforeSend !== 'function') return payload;\n let result;\n try {\n result = this.config.beforeSend(payload);\n } catch (err) {\n // A throwing hook is not permission to send what it was meant to clean.\n this.warn(`beforeSend threw, so nothing was sent: ${err?.message || err}`);\n return false;\n }\n if (result === false) return false;\n return result && typeof result === 'object' ? result : payload;\n }\n\n async #fileByCall() {\n const session = readSession(this.config.projectId);\n\n // The same hook the form honours. Cancelling here cancels the call: there\n // is nothing to ring about once the report has been refused.\n const payload = this.#screen(await this.#payload({}));\n if (payload === false) {\n this.debugLog('call cancelled by beforeSend');\n return { cancelled: true };\n }\n\n let filed;\n try {\n filed = await reportByCall({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n token: session?.token,\n payload,\n });\n } catch (err) {\n // A session the server no longer recognises, dropped exactly as\n // `report()` drops it: the widget re-reads state every time it opens, so\n // forgetting it now is what makes it draw the sign-in step instead.\n if (err.status === 401 && session) clearSession(this.config.projectId);\n this.#failed('call', err);\n throw err;\n }\n this.#submitted(filed, { title: null, via: 'call' });\n\n // The same token `report()` keeps, and for the same reason: it is what lets\n // this browser hear what happens to the report afterwards.\n if (filed.watchToken) {\n writeToken(filed.watchToken);\n if (this.config.updates) this.startWatching();\n }\n return filed;\n }\n\n /**\n * Whether this visitor can report something by talking about it instead.\n *\n * if (await BugCatch.canCallNow()) showYourOwnCallButton();\n *\n * Two conditions, and both have to hold before anything is drawn. The project\n * has to have calling configured — false on most deployments — and this\n * browser has to know who the visitor is, because a call with nobody to\n * attribute it to has no watch token to follow it with and nobody to tell\n * afterwards what happened to the report. That is the same limit every other\n * follow-up feature in this SDK has: it all hangs off `identify()`.\n */\n async canCallNow() {\n return Boolean(await this.callKind());\n }\n\n /**\n * Which kind of call this visitor would get: 'live', or null for none.\n *\n * const kind = await BugCatch.callKind();\n * // 'live' — the team's dashboards ring and a colleague picks up\n *\n * One kind, and this says whether it is on offer. It is still worth asking\n * rather than assuming: on most deployments the answer is null, and a button\n * promising a call nobody can place is worse than no button.\n *\n * The answer is remembered for the life of the page. It is a property of the\n * deployment's configuration rather than of this visitor, and re-asking on\n * every dialog open is a request per page view for something that changes\n * about once a year.\n */\n async callKind() {\n const kinds = await this.callKinds();\n return kinds.live ? 'live' : null;\n }\n\n /**\n * The same answer as booleans: `{ agent, live }`.\n *\n * const { live } = await BugCatch.callKinds();\n * if (live) drawCallTheTeam();\n *\n * `agent` is always false. It was a triage agent that answered instantly\n * instead of a person, it is gone, and the key stays only so an integration\n * that reads it does not get `undefined` and draw a button on it.\n *\n * Same round trip and same page-lifetime cache as `callKind()`. False on a\n * deployment without sphoro.voice, on a workspace that has switched calling\n * off, and on a visitor this SDK has no address for — a call needs somebody\n * to attribute it to.\n */\n async callKinds() {\n const none = { agent: false, live: false };\n if (!this.config.voice) return none;\n if (!this.config.user?.email) return none;\n if (this.kinds !== undefined) return this.kinds;\n this.kinds = await callKindsForProject({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n });\n return this.kinds;\n }\n\n /**\n * Whether a report can be talked through right now.\n *\n * if (await BugCatch.canCall(id)) showYourOwnButton();\n *\n * False on every deployment without sphoro.voice configured, on every\n * workspace that has not switched calling on, and on a report that has had\n * its allowance of calls — so an integration rendering its own affordance can\n * ask rather than guess.\n *\n * The same question as `canCallTeam`, which is the name the widget uses. Both\n * are kept because both are documented, and there is one kind of call left\n * for either of them to be asking about.\n */\n canCall(bugId) {\n if (!this.config.voice) return Promise.resolve(false);\n return liveCallAvailable({ apiBase: this.config.apiBase, bugId });\n }\n\n /**\n * Ring the team.\n *\n * await BugCatch.startCall(id); // about a report already filed\n * await BugCatch.startCall(); // instead of filling the form in\n *\n * With a report id, it rings about that report. With none, it files one first\n * — carrying the console, network and environment this SDK is already holding\n * — and rings about that. The report is written before the phones ring either\n * way, so a call nobody answers still leaves the context behind; the handle\n * carries `bugId` so the caller can say what it was filed as.\n *\n * Asks for the microphone, places the call, and resolves once it is\n * *ringing* — not once somebody answers. The handle's `onState` is how the\n * caller follows what happens after that:\n *\n * 'ringing' the team's dashboards are lighting up\n * 'connecting' somebody answered; the two browsers are finding each other\n * 'connected' they can hear each other\n * 'missed' nobody picked up before the ringing stopped\n * 'ended' it is over\n *\n * What comes back is a conversation with a colleague: the audio travels\n * directly between the two browsers over WebRTC and never reaches bugcatch or\n * sphoro.voice, so it is not transcribed. The thread gets a note saying the\n * call happened and who took it.\n *\n * Rejects if the microphone is refused, if calling is not available, or if\n * one is already running. There is deliberately no way to start a second: two\n * microphones on one page talk over each other. A rejection carrying `.filed`\n * means the report was written and only the call failed.\n */\n async startCall(bugId) {\n if (!this.config.voice) throw new Error('[BugCatch] calling is switched off.');\n // Two checks, because there are two ways to already be on a call: one that\n // has connected, and one still asking for the microphone. A double-click\n // races past a check on `this.call` alone — the second press gets through\n // while the first is still awaiting, and the page ends up with two\n // microphones in one conversation.\n if (this.call || this.callStarting) {\n throw new Error('[BugCatch] a call is already running.');\n }\n this.callStarting = true;\n\n try {\n // Whether this deployment can place a call at all, asked before the\n // microphone prompt and before anything is filed. Without it a press from\n // a visitor with no address, or on a project with calling off, filed a\n // report anyway — and the reporter, promised a call, got a ticket and a\n // phone that never rang.\n if (!await this.callKind()) {\n throw new Error(this.config.user?.email\n ? '[BugCatch] calling is not available on this project.'\n : '[BugCatch] a call needs to know who is calling — call identify() with an email first.');\n }\n return await this.#ringTheTeam(bugId);\n } finally {\n this.callStarting = false;\n }\n }\n\n /**\n * Ring the team, under the name an integration may already be calling.\n *\n * await BugCatch.startLiveCall(id);\n *\n * Exactly `startCall`, which is now the only call there is. The id is\n * optional here too: it was required while this was one of two kinds, because\n * the other one was the only way to call without a report to call about.\n */\n startLiveCall(bugId) {\n return this.startCall(bugId);\n }\n\n /** The body of startCall, so the in-flight flag clears on every path. */\n async #ringTheTeam(bugId) {\n // Before the call is placed, not after. A permission prompt takes as long\n // as the person takes to read it, and asking afterwards would have a room\n // full of dashboards ringing about a call its own caller may yet refuse to\n // be on. That ordering is why the report below is filed *after* the\n // microphone too, rather than first as the form does: filing is what makes\n // the phones ring.\n if (!liveCallSupported()) {\n throw new Error('[BugCatch] this browser cannot make live calls.');\n }\n const microphone = await openMicrophone();\n\n let started;\n let filed = null;\n try {\n if (bugId) {\n started = await requestLiveCall({ apiBase: this.config.apiBase, bugId });\n } else {\n // Nobody filled the form in. One round trip writes what the page was\n // doing and rings the team about it, so a call nobody answers still\n // leaves the context behind.\n filed = await this.#fileByCall();\n bugId = filed.id;\n started = filed.live_call;\n if (!started?.realtime_url) {\n // Filed, but nobody rang. Reported as such rather than thrown, so the\n // caller can say what the report is called instead of losing it.\n const err = new Error(`[BugCatch] the report was filed, but the team could not be rung${\n filed.call_error ? `: ${filed.call_error}` : '.'}`);\n err.filed = filed;\n err.reason = filed.call_error || null;\n throw err;\n }\n }\n } catch (err) {\n for (const track of microphone.getTracks()) track.stop();\n throw err;\n }\n\n const handle = await joinLiveCall({\n realtimeUrl: started.realtime_url,\n stream: microphone,\n ringsUntil: started.rings_until,\n onState: (state, detail) => {\n if (state === 'ended' || state === 'failed' || state === 'missed') this.call = null;\n // Nobody picked up, as decided by the one client that was listening to\n // it. Told to the API so the thread says so; the reporter has already\n // been shown it, and nothing here waits for the answer.\n if (state === 'missed') {\n giveUpLiveCall({ apiBase: this.config.apiBase, bugId, callId: started.call_id });\n }\n if (typeof this.config.onCallState === 'function') {\n this.config.onCallState(state, { bugId, live: true, ...detail });\n }\n this.emit('call', { bugId, live: true, state, ...detail });\n },\n });\n\n // Set after the await, not before: a call that never connected is not one\n // this page is on, and leaving the slot taken would block the retry.\n this.call = handle;\n // Whether the workspace keeps this call. The widget says so before anybody\n // speaks — the team's incoming card has always been told; the person\n // placing the call was not, which is the wrong way round.\n return Object.assign(handle, {\n bugId, callId: started.call_id, live: true, filed, recorded: Boolean(started.recorded),\n });\n }\n\n /**\n * Whether this report can be talked through with a person.\n *\n * For a host application drawing its own button. The widget asks this itself.\n */\n canCallTeam(bugId) {\n if (!this.config.voice) return Promise.resolve(false);\n return liveCallAvailable({ apiBase: this.config.apiBase, bugId });\n }\n\n // --- being called ------------------------------------------------------------\n\n /**\n * Somebody on the team is ringing this page.\n *\n * The only thing in this SDK that starts without the person on the page\n * asking for it, which is why what it does is so much narrower than what the\n * outgoing calls do: it draws a card and makes a soft noise. No microphone is\n * opened and nothing is connected until Answer is pressed, and if nothing is\n * pressed the card takes itself away when the ringing stops.\n *\n * Silently ignored when calling is switched off in this integration's config\n * or the browser cannot hold a peer connection. A host application that has\n * said `voice: false` has said its customers are not to be rung, and a card\n * offering a call this page could not join would be worse than none.\n */\n #ring(event) {\n const call = event.incomingCall;\n if (!this.config.voice || !liveCallSupported()) return;\n // Already ringing about this one — a reconnect replaying the frame, or a\n // second delivery. One card either way.\n if (this.ringing?.callId === call.callId) return;\n // One microphone on this page, so one call. Somebody already talking is not\n // interrupted by a card offering them a second conversation.\n if (this.call || this.callStarting) return;\n\n this.#stopRinging();\n\n const dismiss = showIncomingCall({\n call,\n accent: this.config.accent,\n position: this.config.widgetPosition,\n onAnswer: () => { void this.#answer(event.bugId, call); },\n onDecline: () => {\n this.ringing = null;\n declineIncomingCall({\n apiBase: this.config.apiBase, bugId: event.bugId, callId: call.callId,\n });\n this.emit('incoming-call', { bugId: event.bugId, callId: call.callId, state: 'declined' });\n },\n // Nothing is reported here. The member who placed it is watching it ring\n // on their own screen and their browser tells the API when it stops —\n // the same rule as the other direction, where whoever placed the call is\n // the one that reports nobody answered.\n onExpire: () => {\n this.ringing = null;\n this.emit('incoming-call', { bugId: event.bugId, callId: call.callId, state: 'missed' });\n },\n });\n\n this.ringing = { callId: call.callId, dismiss };\n if (typeof this.config.onIncomingCall === 'function') {\n // After the card, not instead of it. This is for a host application that\n // wants to know — to duck its own audio, to pause a video — and not a\n // hook that can suppress a call the person can see ringing.\n try { this.config.onIncomingCall({ bugId: event.bugId, ...call }); } catch { /* theirs */ }\n }\n this.emit('incoming-call', { bugId: event.bugId, callId: call.callId, state: 'ringing' });\n }\n\n /** Take the card away, whatever it is showing. */\n #stopRinging(callId) {\n if (!this.ringing) return;\n if (callId && this.ringing.callId !== callId) return;\n try { this.ringing.dismiss(); } catch { /* already gone */ }\n this.ringing = null;\n }\n\n /**\n * Answer it: microphone first, then the claim, then the socket.\n *\n * That order is the same one every other call in this file uses and it\n * matters most here. The permission prompt takes as long as the person takes\n * to read it, and claiming the call before they have answered it would put\n * somebody on a conversation they may yet decline to be able to speak on —\n * with a member of the team on the other end saying hello to a muted browser.\n */\n async #answer(bugId, call) {\n this.ringing = null;\n if (this.call || this.callStarting) return;\n this.callStarting = true;\n\n let microphone;\n try {\n microphone = await openMicrophone();\n } catch (err) {\n this.callStarting = false;\n // Refused the prompt, or no microphone on this machine. The call is still\n // ringing at the other end, so it is declined rather than left — the\n // member calling should not wait out the deadline for somebody who\n // cannot speak to them.\n declineIncomingCall({ apiBase: this.config.apiBase, bugId, callId: call.callId });\n this.emit('incoming-call', { bugId, callId: call.callId, state: 'failed', error: err });\n return;\n }\n\n let answered;\n try {\n answered = await answerIncomingCall({\n apiBase: this.config.apiBase, bugId, callId: call.callId,\n });\n } catch (err) {\n for (const track of microphone.getTracks()) track.stop();\n this.callStarting = false;\n // 409 is the ordinary one: it stopped ringing while Answer was being\n // pressed. Reported as a state rather than thrown, because nobody called\n // this — the page did, on somebody's behalf, and there is no promise\n // waiting to reject.\n this.emit('incoming-call', { bugId, callId: call.callId, state: 'missed', error: err });\n return;\n }\n\n try {\n const handle = await joinLiveCall({\n realtimeUrl: answered.realtime_url,\n stream: microphone,\n // No deadline on this side. The ringing is over — it ended when this\n // browser claimed the call — and a countdown here would hang up on a\n // conversation that has already started.\n onState: (state, detail) => {\n if (state === 'ended' || state === 'failed' || state === 'missed') this.call = null;\n if (typeof this.config.onCallState === 'function') {\n this.config.onCallState(state, { bugId, live: true, incoming: true, ...detail });\n }\n this.emit('call', { bugId, live: true, incoming: true, state, ...detail });\n },\n });\n this.call = handle;\n this.emit('incoming-call', { bugId, callId: call.callId, state: 'answered' });\n } catch (err) {\n for (const track of microphone.getTracks()) track.stop();\n this.emit('incoming-call', { bugId, callId: call.callId, state: 'failed', error: err });\n } finally {\n this.callStarting = false;\n }\n }\n\n /**\n * End the call in progress, of either kind. Does nothing when there is not\n * one — there is a single slot, because there is a single microphone.\n */\n endCall() {\n this.call?.hangUp();\n this.call = null;\n return this;\n }\n\n /**\n * Everything this browser has reported, newest first, with the status of\n * each — the history a reporter normally has no way to see.\n *\n * const { reports } = await BugCatch.myReports();\n * // [{ reference: '3F2A9B01', title, status, priority, createdAt, ... }]\n *\n * Resolves to an empty history rather than throwing when nothing has been\n * filed from this browser, or when identify() supplied no email — there is\n * nothing to look up in either case.\n */\n myReports() {\n return fetchMyReports({ apiBase: this.config.apiBase });\n }\n\n /** True when this browser has filed something, so a \"your reports\" entry\n * point can be hidden until it would show anything. */\n hasReports() {\n return Boolean(readToken());\n }\n\n /**\n * Forget this browser's reports — call on sign-out, or the next person at\n * this machine inherits the previous one's notifications.\n */\n forgetReports() {\n this.stopWatching?.();\n this.stopWatching = null;\n // Ended, not just forgotten. This is the sign-out path, and a call left\n // running on it would keep a microphone open on behalf of somebody who has\n // just left — and go on transcribing them into a report they can no longer\n // prove is theirs.\n this.endCall();\n // Drops the unread notifications with the token — see clearToken — so the\n // next person at a shared machine does not find the last one's bug reports\n // stacked in the corner.\n clearToken();\n this.#drawUnread();\n return this;\n }\n\n /**\n * Attach the signed-in user so reports say who hit the bug.\n *\n * `email` is the field that matters beyond display: it threads every report\n * from the same person together in the dashboard, and it is where the team's\n * \"your issue is resolved\" email goes. Because your app has already\n * authenticated this person, an address sent here is treated as reachable —\n * unlike one typed into a public recording-link form, which stays unusable\n * for outbound mail until the reporter answers a confirmation code.\n *\n * BugCatch.identify({ id: 'u_42', email: 'alex@acme.com', name: 'Alex' });\n *\n * Pass null to forget them again on sign-out.\n */\n identify(user) {\n const shaped = normalizeUser(user, (m) => this.warn(m));\n // Anything but null/undefined that yielded nothing usable is a mistake, not\n // a sign-out: keep who was there rather than silently forgetting them.\n if (user !== null && user !== undefined && !shaped) return this;\n this.config.user = shaped;\n this.debugLog('identify', shaped);\n return this;\n }\n\n /**\n * Say what the reporter is looking at — the loan, application or customer on\n * this screen — so every report filed from it carries that record.\n *\n * BugCatch.setContext({ loanId: 'L26090028459', screen: 'repayment' });\n * BugCatch.setContext({ loanId: null }); // left the loan screen\n * BugCatch.setContext(null); // clear everything\n *\n * Merged over what was set before and kept until changed — for the life of\n * the page, not across reloads. `loanId`, `applicationId` and\n * `customerNumber` are read by bugcatch as identifiers your app asserted, and\n * outrank the same field on identify(); anything else is readable metadata.\n */\n setContext(context) {\n this.context = mergeContext(this.context, context, (m) => this.warn(m));\n this.debugLog('context', this.context);\n return this;\n }\n\n /** What setContext() currently holds (a copy). */\n getContext() {\n return { ...this.context };\n }\n\n /**\n * Open the report panel, as if the floating button had been pressed — for a\n * \"Report a problem\" item in your own menu.\n *\n * Needs the built-in widget (`widget: true`, the default). With `widget:\n * false` there is no panel to open: draw your own form and call report().\n */\n open() {\n if (!this.started) { this.warn('open() was called before init() — nothing to open yet.'); return this; }\n if (!this.widgetControls) { this.warn('open() needs the built-in widget; it was turned off with widget: false.'); return this; }\n this.widgetControls.open();\n return this;\n }\n\n /** Close the report panel if it is open. */\n close() {\n this.widgetControls?.close();\n return this;\n }\n\n /** Whether the report panel is open. */\n isOpen() {\n return Boolean(this.widgetControls?.isOpen());\n }\n\n /**\n * Upload a screenshot, recording or file and get back the storage key that\n * `report({ attachments })` references.\n *\n * The blob goes straight to storage on a presigned URL — it never passes\n * through the API — so this is also what you call when you drive the SDK\n * yourself with `widget: false`.\n */\n async uploadAttachment(blob, mime, onProgress) {\n if (!this.started) throw new Error('[BugCatch] call init() first.');\n return uploadAttachment({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n blob,\n mime: mime || blob?.type,\n onProgress,\n });\n }\n\n /** File a bug with everything captured so far. */\n async report({\n title, description = '', priority = 'MEDIUM', metadata = {}, attachments = [],\n references = [], context = null, issue = null,\n } = {}) {\n if (!this.started) throw new Error('[BugCatch] call init() first.');\n if (typeof title !== 'string' || !title.trim()) throw new Error('[BugCatch] a title is required.');\n if (!['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'].includes(String(priority).toUpperCase())) {\n this.warn(`priority \"${priority}\" is not one of LOW, MEDIUM, HIGH, CRITICAL — sent as MEDIUM.`);\n priority = 'MEDIUM';\n }\n\n // Drop anything the API would reject outright, so one bad entry can't cost\n // the reporter the whole report.\n const media = (attachments || [])\n // REPORT_MIMES, not the full upload set: a report's attachments are typed\n // SCREENSHOT or VIDEO, so a document reaching here would be filed as a\n // screenshot. Documents and voice notes belong on a message.\n .filter((a) => a?.storageKey && normalizeMime(a.mime, REPORT_MIMES))\n .map((a) => ({\n type: a.type === 'VIDEO' ? 'VIDEO' : 'SCREENSHOT',\n storageKey: a.storageKey,\n mime: normalizeMime(a.mime, REPORT_MIMES),\n }));\n\n // Only well-formed entries: one malformed reference must not cost the\n // reporter the report (the API refuses the whole body on a bad one).\n const refs = (Array.isArray(references) ? references : [])\n .filter((r) => r && typeof r.key === 'string' && r.key && (typeof r.value === 'string' || typeof r.value === 'number'))\n .map((r) => ({ key: r.key.slice(0, 64), value: String(r.value).trim().slice(0, 120) }))\n .filter((r) => r.value);\n if (Array.isArray(references) && refs.length < references.length) {\n this.warn('report({ references }) takes [{ key, value }] with string values; malformed entries were dropped.');\n }\n\n let payload = await this.#payload({\n title: title.trim(), description: typeof description === 'string' ? description : String(description ?? ''),\n priority: String(priority).toUpperCase(), metadata, media, references: refs, context,\n issue: typeof issue === 'string' && issue ? issue.slice(0, 32) : null,\n });\n\n const screened = this.#screen(payload);\n if (screened === false) return { cancelled: true };\n payload = screened;\n\n this.debugLog('sending report', {\n title: payload.title, user: payload.metadata?.user, context: payload.metadata?.context,\n references: payload.references, console: payload.logs?.console?.entries?.length,\n network: payload.logs?.network?.entries?.length,\n });\n const session = readSession(this.config.projectId);\n let res;\n try {\n res = await fetch(`${this.config.apiBase}/ingest/bugs`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-BugCatch-Key': this.config.apiKey,\n // Present only where the reporter signed in. The server takes the\n // address from here rather than from the payload when it is — on a\n // project that requires sign-in, letting the page name a different\n // reporter would undo the requirement.\n ...(session?.token ? { 'X-BugCatch-Session': session.token } : {}),\n },\n body: JSON.stringify(payload),\n });\n } catch (cause) {\n // No response at all: the wrong apiBase, the API down, or CORS refusing\n // this origin. The browser's own \"Failed to fetch\" names none of them.\n const err = new Error(`[BugCatch] could not reach ${this.config.apiBase} — check apiBase, and that this site's origin is allowed by the API.`);\n err.cause = cause;\n this.#failed('report', err);\n throw err;\n }\n\n // A session the server no longer recognises — expired, or ended by a\n // sign-in somewhere else. Dropped here rather than left to fail again on\n // the next report: the widget re-reads state every time it opens, so\n // forgetting it now is what makes it draw the sign-in step instead.\n if (res.status === 401 && session) clearSession(this.config.projectId);\n\n if (!res.ok) {\n let detail = '';\n try {\n const body = await res.json();\n detail = Array.isArray(body.message) ? body.message.join(', ') : body.message || '';\n } catch { /* non-JSON error */ }\n // The two refusals an integrator hits first, said in their terms.\n if (res.status === 401 && !session) detail ||= 'the apiKey was not accepted';\n if (res.status === 403) detail ||= 'the apiKey is not allowed to report to this projectId';\n const err = new Error(`[BugCatch] report failed (HTTP ${res.status})${detail ? ': ' + detail : ''}`);\n err.status = res.status;\n this.#failed('report', err);\n throw err;\n }\n\n const result = await res.json();\n this.#submitted(result, { title: payload.title ?? title.trim(), via: 'form' });\n\n // The API returns a watch token only when it could attribute the report to\n // someone — which means identify() was called with an email. Without one\n // there is nobody to notify, and none comes back.\n if (result.watchToken) {\n writeToken(result.watchToken);\n if (this.config.updates) this.startWatching();\n }\n\n return result;\n }\n\n /**\n * What this project asks of the person reporting, and who this browser is\n * signed in as right now.\n *\n * const { mode, signedIn } = await BugCatch.loginState();\n * // mode: 'off' | 'optional' | 'required'\n *\n * The widget calls this itself every time it opens. It is here for\n * integrations running with `widget: false`, which have to draw their own\n * sign-in and need the same answer to know whether to draw one at all.\n *\n * The mode is the project's setting, changed in BugCatch under Project\n * Settings → General, not in this config. A widget that could opt out of a\n * required sign-in would not be a requirement.\n */\n loginState() {\n return loginState({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n token: readSession(this.config.projectId)?.token,\n });\n }\n\n /**\n * What this project needs in order to look a report up, and how it asks.\n *\n * const { policy, fields } = await BugCatch.referenceFields();\n * // policy: 'off' | 'optional' | 'required'\n *\n * The widget calls this itself when it opens, and caches the answer for the\n * life of the page: unlike the sign-in state, this cannot change underneath a\n * reporter in a way that matters — an admin editing the field list mid-report\n * costs one stale question, not a failed send.\n *\n * Here for the same reason loginState() is: an integration running with\n * `widget: false` draws its own form and needs the same answer to know\n * whether that form should have an identifier box on it.\n */\n referenceFields() {\n if (this.refFields) return this.refFields;\n this.refFields = referenceFields({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n });\n return this.refFields;\n }\n\n /**\n * The questions this project's assistant can answer and close by itself —\n * \"When is my next EMI due?\", \"Is my KYC complete?\" — for a form to offer\n * as one-tap choices.\n *\n * const issues = await BugCatch.agentIssues();\n * // [{ key: 'emi', title: 'When is my next EMI due?', hint: 'The date and the amount' }]\n * BugCatch.report({ title: issues[0].title, issue: issues[0].key });\n *\n * The assistant looks the reporter up (identify() makes that instant),\n * answers from their record, and asks whether that settled it: yes resolves\n * the report, no hands it to the team. Empty unless the project has turned\n * this on and connected a data source that can answer. The widget calls it\n * itself; cached for the life of the page.\n */\n agentIssues() {\n if (this.agentIssueList) return this.agentIssueList;\n this.agentIssueList = fetchAgentIssues({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n });\n return this.agentIssueList;\n }\n\n /**\n * Who this browser is signed in as on this project, from storage alone.\n *\n * BugCatch.signedInAs() // { email, name } | null\n *\n * Synchronous, and therefore only as current as the last round trip: a\n * session ended elsewhere still reads as signed in here until something asks\n * the server. Use loginState() where that matters.\n */\n signedInAs() {\n const session = readSession(this.config.projectId);\n return session ? { email: session.email, name: session.name } : null;\n }\n\n /**\n * Step one of signing in: mail a code to any address.\n *\n * await BugCatch.signIn('someone@example.com');\n * await BugCatch.confirmSignIn('someone@example.com', '481920');\n *\n * Any address, deliberately — there is no list of people who may report a\n * bug in somebody's product, and asking for one would be asking the customer\n * to maintain it. What the code proves is that whoever typed it can read the\n * address, which is all a reply-to has ever needed to be worth.\n */\n signIn(email, { name } = {}) {\n return startSignIn({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n email,\n // The name the host app already knows beats asking for one again.\n name: name ?? this.config.user?.name ?? null,\n });\n }\n\n /**\n * Step two: spend the code and hold the session.\n *\n * Signing in signs this address out of this project in every other browser —\n * one live session per address per project. Somebody who signs in on their\n * phone has signed their laptop out, on purpose.\n */\n async confirmSignIn(email, code) {\n const session = await confirmSignIn({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n email,\n code,\n });\n\n // A signed-in reporter is an identity, so reports carry it the same way\n // identify() makes them — but never over one the host application set:\n // that address is its own claim about its own user, and this SDK is not\n // the thing that decides it was wrong.\n if (!this.config.user?.email) {\n this.config.user = { email: session.email, name: session.name || undefined };\n }\n this.emit('signin', { email: session.email, name: session.name });\n return { email: session.email, name: session.name };\n }\n\n /**\n * Sign out of this project.\n *\n * Ends the session on the server too, so the token cannot be replayed from a\n * copy of this browser's storage. Local state goes whatever the network\n * says — sign-out on a shared machine has to mean signed out on it.\n */\n async signOut() {\n const session = readSession(this.config.projectId);\n const result = await endSignIn({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n token: session?.token,\n });\n // Only the identity this session put there. One the host application set\n // through identify() is theirs, and outlives a widget sign-out.\n if (session?.email && this.config.user?.email === session.email) this.config.user = null;\n this.emit('signout', { email: session?.email || null });\n return result;\n }\n\n /**\n * What this project is already known to be having trouble with.\n *\n * The widget asks this itself when it opens; it is public for a host\n * application drawing its own help surface with `widget: false`. Resolves to\n * an empty list on every project that has not switched the feature on, which\n * is the default — draw nothing rather than an empty heading.\n */\n knownIssues() {\n return fetchKnownIssues({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n sessionToken: readSession(this.config.projectId)?.token,\n });\n }\n\n /**\n * The known issues that look like a description somebody is writing.\n *\n * Pass an AbortSignal when calling this on a keystroke, or a fast typist gets\n * the results of whichever request happens to return last.\n */\n matchKnownIssues(query, { signal } = {}) {\n return matchKnownIssues({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n q: query || '',\n sessionToken: readSession(this.config.projectId)?.token,\n signal,\n });\n }\n\n /**\n * Say a known issue is happening to you too, instead of filing a second\n * report about it.\n *\n * Sends the same capture a report would — this browser's console, network and\n * environment — because that is what the team gets out of somebody joining\n * rather than filing: one report with everybody's environment on it.\n *\n * Needs an address. A signed-in widget has one, and identify() supplies one\n * otherwise; pass `email` for the case where neither did. Joining means \"tell\n * me when this is fixed\", and there is no way to keep that promise to\n * somebody anonymous.\n */\n async meToo(bugId, { email, name } = {}) {\n const session = readSession(this.config.projectId);\n const environment = await collectEnvironment();\n return joinKnownIssue({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n bugId,\n reporterEmail: email || this.config.user?.email,\n reporterName: name || this.config.user?.name,\n metadata: {\n ...environment,\n user: this.config.user || undefined,\n ...(Object.keys(this.context).length ? { context: this.context } : {}),\n sdk: SDK_IDENTITY,\n },\n logs: {\n console: { entries: this.consoleBuffer.all() },\n network: { entries: this.networkBuffer.all() },\n },\n sessionToken: session?.token,\n });\n }\n\n /** What would be sent right now — useful for debugging integrations. */\n snapshot() {\n return {\n user: this.config.user || null,\n context: { ...this.context },\n console: this.consoleBuffer.all(),\n network: this.networkBuffer.all(),\n environment: collectEnvironmentSync(),\n };\n }\n\n clear() {\n this.consoleBuffer.clear();\n this.networkBuffer.clear();\n }\n\n /** Restore every patched global and remove the widget. */\n destroy() {\n // The call first. Everything else here is listeners and buffers; this one\n // holds a live microphone, and a page that tore the SDK down without\n // releasing it would keep the tab's recording indicator lit.\n this.endCall();\n this.teardown.forEach((fn) => { try { fn(); } catch { /* ignore */ } });\n this.teardown = [];\n // Torn down with everything else, so a later init() builds a fresh one\n // rather than rendering into a stack that has been removed.\n this.notifier = null;\n this.started = false;\n }\n}\n\nconst BugCatch = new BugCatchClient();\n\n// Exposed on the singleton rather than as a second named export: with a UMD\n// build, a named export alongside the default turns `window.BugCatch` into a\n// namespace object, so `BugCatch.init(...)` from a <script> tag would break.\nBugCatch.Client = BugCatchClient;\n// For support tickets and `console.log(BugCatch.version)`.\nBugCatch.version = SDK_IDENTITY.version;\n\nexport default BugCatch;\n"],"names":["PLACEHOLDER","ALWAYS","MARKERS","AMBIGUOUS","SHAPES","BEARER","USERINFO","safeDecode","value","normalizeKey","rawKey","looksSecret","re","isSensitiveKey","rawValue","key","marker","redactPairs","query","pair","eq","redactShapes","text","out","redactUrl","raw","url","hashAt","head","hash","queryAt","body","innerQuery","redactText","m","createRingBuffer","limit","items","item","LEVELS","installConsoleCapture","buffer","levels","originals","level","args","stringify","installNetworkCapture","slowMs","originalFetch","originalOpen","originalSend","started","_a","method","_b","_c","res","record","err","rest","status","ms","error","installErrorCapture","onError","event","message","where","onRejection","collectEnvironment","base","collectEnvironmentSync","b","nav","_d","_e","_g","_f","_h","_i","nativeFetch","nativeXhrOpen","nativeXhrSend","UPLOAD_MIMES","REPORT_MIMES","voiceSupported","captureSupported","screenshotSupported","normalizeMime","mime","allowed","captureScreenshot","stream","track","video","resolve","width","height","canvas","blob","t","startRecording","maxMs","onTick","onAutoStop","recorder","chunks","e","startedAt","timer","cleanup","capTimer","finished","endEarly","startVoiceNote","uploadAttachment","apiBase","apiKey","onProgress","type","uploadUrl","put","reject","xhr","humanSize","bytes","humanDuration","seconds","s","MAX_AGE","database","open","request","draftKey","config","email","draftOperation","db","tx","store","readDraft","KEY_PREFIX","storageKey","projectId","inMemory","readSession","parsed","writeSession","session","held","clearSession","headers","token","readError","loginState","off","startSignIn","name","confirmSignIn","code","endSignIn","TOKEN_KEY","BACKOFF_MS","STABLE_MS","project","scopeReports","tokenKey","projectQuery","readToken","writeToken","clearToken","forgetAllUpdates","UNREAD_KEY","updateId","readUnread","list","u","writeUnread","rememberUpdate","forgetUpdate","id","forgetReportUpdates","bugId","drain","onFrame","split","frame","data","line","fetchMyReports","fetchReport","sendMessage","attachments","detail","problem","callKindsForProject","none","reportByCall","payload","liveCallAvailable","requestLiveCall","giveUpLiveCall","callId","answerIncomingCall","declineIncomingCall","watchUpdates","onUpdate","running","attempt","controller","connect","openedAt","reader","decoder","done","wait","STATUS_COPY","KIND_ACCENT","updateHeadline","updateDetail","agoLabel","at","then","mins","hours","days","dockOffset","host","dock","createUpdateNotifier","accent","position","onRead","onReadAll","onCall","vertical","horizontal","atTop","atLeft","cards","stack","more","ensureStack","drop","el","button","label","primary","card","title","actions","when","read","talk","moreRow","count","all","unread","visible","shown","root","fresh","ordered","hidden","fetchKnownIssues","sessionToken","knownIssueHeaders","matchKnownIssues","q","signal","joinKnownIssue","reporterEmail","reporterName","metadata","logs","readJoinError","verifyFix","feedback","MAX_SCAN_CHARS","MAX_VALUE_CHARS","MAX_MATCHES","compile","source","tidy","normalizeValue","field","how","digits","valueOf","match","labelWeight","hit","LOOSE_ID","WORDISH","readReferences","fields","texts","scannable","hits","loose","blocks","block","order","guard","valueStart","h","survivors","o","byValue","entry","found","conflicts","settledKey","kinds","a","r","seen","taken","before","after","f","validateReference","toEnd","atEdge","referenceFields","policy","SOLVED_PROMPT","fetchAgentIssues","i","POSITIONS","PRIORITIES","ICONS","BRAND","THEMES","THEME_KEY","readStoredTheme","writeStoredTheme","css","mountWidget","client","theme","themeValue","themeClass","style","themer","btn","launcher","openPanel","onClose","cfg","canCapture","canShoot","canRecord","priority","sending","scrim","headHtml","p","$","sel","titleInput","descInput","slot","thumbs","filePicker","sendBtn","callBtn","note","compose","whoami","refsBox","known","picks","historyBtn","draftStatus","savedDraftKey","draftLoaded","draftSubmitted","draftWrites","draftTimer","persistDraft","kind","scheduleDraft","onPageHide","reconnectUploads","it","upload","draft","closed","addItem","previouslyFocused","rootStyle","previousOverflow","stopListening","destroy","onKeydown","close","stopRecording","focusable","first","last","active","say","escapeHtml","titleErr","flagTitle","refreshSendState","busy","max","drawThumb","preview","veil","removeItem","fill","normalized","withWidgetHidden","fn","shot","room","file","bar","keep","rec","clip","refConfig","refOpen","refChoice","refValue","refError","refCandidates","refMode","refSettled","refWarned","showFound","readText","textAnswers","answeredReference","foundNote","foundTimer","shortId","drawRefs","queueFound","candidateFor","v","c","takeCandidate","k","optional","current","sharedKind","many","chip","openRefs","needsReference","original","pickedIssue","succeed","login","refreshLogin","showHistory","issues","issueError","askingFor","matchAbort","loadKnownIssues","rows","drawKnown","matchTimer","matchKnown","issue","knownMeta","state","others","relativeDay","start","confirm","result","agentIssues","drawPicks","titleWasPick","callOffered","offerCall","mustSignIn","step","pendingEmail","pendingCode","signInBusy","signInError","applyGate","shut","drawWhoami","claimed","drawSignIn","focus","requestCode","confirmCode","resent","who","act","announce","handle","showCall","panel","ref","stateEl","dot","WORDING","OVER","offState","callFailed","showReport","ok","doneScreen","eta","failed","assistantTimer","openReportId","skeletonRows","reports","emptyHtml","statusWord","row","skeletonBubbles","report","drawn","threadHtml","thread","threadDrawn","scrollToLatest","mountVerification","mountComposer","mountAssistant","previous","steps","st","pct","content","from","next","pinned","atLatest","typing","typingHtml","refreshThread","box","html","pill","composer","staged","input","voiceBtn","liveBtn","picker","files","refresh","ready","stage","stored","send","available","showLiveCall","quick","messageHtml","secs","muteBtn","hangBtn","ticker","stopTicking","settle","muted","timeline","entries","day","prev","d","dayKey","sep","dayLabel","systemHtml","speakerOf","cont","clockTime","mine","automated","attachmentHtml","avatar","initials","sub","back","icon","n","parts","iso","today","yesterday","smooth","calm","months","WIRE_RATE","createContext","rate","Ctor","resumeContext","context","downsample","fromRate","toRate","ratio","end","sum","j","MU_BIAS","MU_CLIP","encodeMuLaw","samples","clamped","sample","sign","exponent","mask","mantissa","CAPTURE_BLOCK","tapMicrophone","wireRate","capture","frames","mute","SUBPROTOCOL","FALLBACK_ICE","openMicrophone","liveCallSupported","joinLiveCall","realtimeUrl","ringsUntil","onState","onAudioBlocked","onMicrophone","emit","sayBlocked","blocked","sayMicrophone","live","microphone","socket","openSocket","stopStream","peer","remoteAudio","ringTimer","uplink","disarmGesture","unwatchMicrophone","starting","pending","shouldOffer","connectedOnce","reconnecting","recoverTimer","giveUpTimer","restarts","RECOVER_GRACE_MS","RECOVER_RETRY_MS","RECOVER_DEADLINE_MS","MAX_RESTARTS","finish","clearRecovery","stopUplink","startUplink","encoding","warn","tap","playRemote","markBlocked","markPlaying","armGesture","retry","events","build","iceServers","watchMicrophone","incoming","routeChanged","enterRecovery","delay","recover","restartIce","description","lost","offer","accept","answer","drainCandidates","candidate","handling","armRingTimer","remaining","RING","touched","evt","activatedByBrowser","startRinging","Ctx","stopped","voices","burst","hz","gain","frequency","osc","cycle","begin","secondsLeft","until","BUTTON","showIncomingCall","call","onAnswer","onDecline","onExpire","heading","about","countdown","decline","kept","stopRinging","tick","left","MAX_ID_CHARS","MAX_TEXT_CHARS","MAX_EXTRA_KEYS","MAX_NESTED_JSON","ID_FIELDS","CONTROL","idValue","describe","readId","src","canonical","names","extraValue","json","USER_TEXT","RESERVED_USER","normalizeUser","user","extras","mergeContext","patch","aliases","KNOWN_OPTIONS","CALLBACK_OPTIONS","suggest","lower","UUID","checkOptions","options","pageHost","errors","warnings","near","cleanApiBase","SDK_IDENTITY","DEFAULTS","BugCatchClient","__privateAdd","_BugCatchClient_instances","w","earlier","__privateMethod","drawUnread_fn","sayHello_fn","ring_fn","stopRinging_fn","update","onStorage","handler","set","resolvedAt","payload_fn","notePhase_fn","ringTheTeam_fn","shaped","references","media","refs","screened","screen_fn","cause","failed_fn","submitted_fn","environment","callback_fn","mark","assistant","phase","via","action","ctx","fileByCall_fn","filed","dismiss","answer_fn","answered","BugCatch"],"mappings":"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AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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"}