makethisbetter 1.28.1 → 1.28.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/makethisbetter.cjs +6 -6
- package/dist/makethisbetter.cjs.map +1 -1
- package/dist/makethisbetter.esm.js +30 -35
- package/dist/makethisbetter.esm.js.map +1 -1
- package/dist/makethisbetter.js +2 -2
- package/dist/makethisbetter.js.map +1 -1
- package/dist/makethisbetter.standalone.js +9 -14
- package/dist/makethisbetter.standalone.js.map +1 -1
- package/dist/popup/chat-popup.d.ts.map +1 -1
- package/dist/widget/tab.d.ts +1 -0
- package/dist/widget/tab.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/popup/chat-popup.ts +12 -14
- package/src/widget/tab.ts +6 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"makethisbetter.cjs","sources":["../src/context/dom-utils.ts","../src/widget/shadow.ts","../src/widget/tab.ts","../src/widget/frustration-prompt.ts","../src/widget/dim-overlay.ts","../src/widget/pointer-kind.ts","../src/widget/page-offset.ts","../src/widget/scroll-lock.ts","../src/annotate/toolbar.ts","../src/annotate/overlay.ts","../src/annotate/highlighter.ts","../src/screenshot/geometry.ts","../src/annotate/pin.ts","../src/styles.ts","../src/annotate/draw.ts","../src/privacy/dom.ts","../src/annotate/session.ts","../src/popup/keyboard-inset.ts","../src/popup/sheet.ts","../src/annotate/draw-note-bar.ts","../src/popup/position.ts","../src/popup/focus-trap.ts","../src/popup/chat-popup.ts","../src/popup/receipt.ts","../src/privacy/sanitize.ts","../src/privacy/fields.ts","../src/privacy/text-context.ts","../src/screenshot/image.ts","../src/screenshot/svg-path.ts","../src/screenshot/capture.ts","../src/context/error-source.ts","../src/context/error-summary.ts","../src/context/console.ts","../src/context/history-patch.ts","../src/context/breadcrumbs.ts","../src/context/frustration-state.ts","../src/context/frustration.ts","../src/api/client.ts","../src/widget/anon-id.ts","../src/i18n/en.ts","../src/i18n/zh-CN.ts","../src/i18n/ja.ts","../src/i18n/ko.ts","../src/i18n/es.ts","../src/i18n/fr.ts","../src/i18n/de.ts","../src/i18n/index.ts","../src/record/session.ts","../src/record/control-bar.ts","../src/widget/recording-manager.ts","../src/context/collector.ts","../src/widget/payload.ts","../src/widget/submission-lifecycle.ts","../src/widget/mode-states.ts","../src/widget/brand-colors.ts","../src/widget/controller.ts","../src/index.ts"],"sourcesContent":["export const WIDGET_HOST_ID = 'mtb-widget-host'\n\nexport function isWidgetElement(el: Element): boolean {\n const host = document.getElementById(WIDGET_HOST_ID)\n if (!host) return false\n return host === el || host.contains(el)\n}\n\n// Skips the widget host via elementsFromPoint instead of toggling its\n// display — hiding the host on every mousemove flickered the toolbar.\nexport function elementUnderPoint(x: number, y: number): Element | null {\n return document.elementsFromPoint?.(x, y).find(el => el.id !== WIDGET_HOST_ID) ?? null\n}\n\nexport function escapeHtml(text: string): string {\n return text\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n}\n\n// A keydown fired mid-IME-composition confirms a candidate, not the widget\n// action bound to the key — acting on it would eat what a reporter typing\n// Chinese or Japanese meant as text. Some browsers reset isComposing before\n// the keydown reaches a capture handler but retain the legacy IME sentinel\n// in keyCode.\nexport function isImeConfirmKeydown(event: KeyboardEvent): boolean {\n return event.isComposing || event.keyCode === 229\n}\n\n// textContent counts the source of <style> and <script> children as text. A\n// page with an inline stylesheet in its header therefore names that header\n// after its own CSS, and that name travels into the pin, the report, and the\n// triage prompt. Only what a reader would see counts.\nconst UNRENDERED = /^(script|style|noscript|template)$/i\n\nfunction renderedText(el: Element, limit = 80): string {\n let out = ''\n for (const node of Array.from(el.childNodes)) {\n if (out.length >= limit) break\n\n if (node.nodeType === 3) {\n out += node.nodeValue ?? ''\n } else if (node instanceof Element && !UNRENDERED.test(node.tagName)) {\n out += renderedText(node, limit - out.length)\n }\n }\n return out\n}\n\nexport function getElementName(el: Element): string {\n if (el instanceof HTMLElement) {\n const label = el.getAttribute('aria-label')\n if (label) return label\n\n const title = el.getAttribute('title')\n if (title) return title\n\n const text = renderedText(el).trim().slice(0, 40)\n if (text) return text\n\n const placeholder = (el as HTMLInputElement).placeholder\n if (placeholder) return placeholder\n\n const alt = (el as HTMLImageElement).alt\n if (alt) return alt\n\n const dataMark = el.getAttribute('data-mark')\n if (dataMark) return dataMark\n }\n return el.tagName.toLowerCase()\n}\n\nexport function getCssSelector(el: Element): string {\n const parts: string[] = []\n let current: Element | null = el\n while (current && current !== document.body && parts.length < 5) {\n let selector = current.tagName.toLowerCase()\n if (current.id) {\n selector = `#${current.id}`\n parts.unshift(selector)\n break\n }\n const classNames = Array.from(current.classList)\n .filter(c => !c.startsWith('mtb-'))\n .slice(0, 2)\n .join('.')\n if (classNames) selector += `.${classNames}`\n\n const parent = current.parentElement\n if (parent) {\n const siblings = Array.from(parent.children).filter(\n c => c.tagName === current!.tagName,\n )\n if (siblings.length > 1) {\n const idx = siblings.indexOf(current) + 1\n selector += `:nth-of-type(${idx})`\n }\n }\n\n parts.unshift(selector)\n current = current.parentElement\n }\n return parts.join(' > ')\n}\n","import cssText from '../styles/widget.css?inline'\nimport { WIDGET_HOST_ID } from '../context/dom-utils'\nimport type { MakeThisBetterConfig } from '../types'\nimport type { WidgetBrandColors } from './brand-colors'\n\nexport class ShadowContainer {\n // Click-away layers listen at different points in a mouse or touch sequence.\n // Keep discrete widget interactions private, but leave pointermove composed\n // because toolbar dragging intentionally tracks movement on document.\n private static readonly HOST_INTERACTION_EVENTS = [\n 'pointerdown',\n 'pointerup',\n 'pointercancel',\n 'mousedown',\n 'mouseup',\n 'touchstart',\n 'touchend',\n 'touchcancel',\n 'click',\n ] as const\n\n // Readable so a component can mark the host with its own presence and have\n // :host(...) rules react — the only way CSS inside the shadow tree can be\n // told which components are currently mounted.\n readonly host: HTMLElement\n readonly root: ShadowRoot\n // Only a host we made is ours to take away. One the page supplied has to\n // outlive us, or a framework counting on it being there — Turbo pairs its\n // permanent elements by id — would find it gone after the first teardown.\n private readonly ownsHost: boolean\n // A borrowed host's inline style and theme attribute belong to the page.\n // Snapshot the whole attribute values, not just the properties we write:\n // cssText assignment below replaces everything the customer had inline, so\n // only the full original string can put it back. null means the attribute\n // did not exist, and restoring must remove it rather than leave ''.\n private readonly originalInlineStyle: string | null\n private readonly originalTheme: string | null\n\n constructor(\n theme: MakeThisBetterConfig['theme'] = 'auto',\n brandColors?: WidgetBrandColors,\n ) {\n // A page that renders its own host controls where the widget lives, and\n // that is the only way to survive a framework which replaces <body>\n // wholesale on navigation: Turbo carries an element marked\n // data-turbo-permanent across the swap, shadow root and all, so a\n // recording in progress is not thrown away by a link click.\n const provided = document.getElementById(WIDGET_HOST_ID)\n this.ownsHost = !provided\n\n this.host = provided ?? document.createElement('div')\n // Attach before any host mutation: a supplied element that cannot take a\n // shadow root fails right here, while the page's element is still exactly\n // as the page left it — there is no partially-styled host to unwind.\n this.root = this.host.shadowRoot ?? this.host.attachShadow({ mode: 'open' })\n this.root.replaceChildren()\n this.originalInlineStyle = this.host.getAttribute('style')\n this.originalTheme = this.host.getAttribute('data-mtb-theme')\n this.host.id = WIDGET_HOST_ID\n this.host.style.cssText = 'position:fixed;top:0;left:0;width:0;height:0;overflow:visible;z-index:2147483646;'\n if (brandColors) {\n const rgb = [1, 3, 5].map((offset) => parseInt(brandColors.primary.slice(offset, offset + 2), 16))\n const onPrimaryRgb = [1, 3, 5].map((offset) => parseInt(brandColors.onPrimary.slice(offset, offset + 2), 16))\n this.host.style.setProperty('--mtb-brand-primary', brandColors.primary.toLowerCase())\n this.host.style.setProperty('--mtb-brand-hover', brandColors.hover.toLowerCase())\n this.host.style.setProperty('--mtb-brand-active', brandColors.active.toLowerCase())\n this.host.style.setProperty('--mtb-brand-on-primary', brandColors.onPrimary.toLowerCase())\n this.host.style.setProperty('--mtb-brand-rgb', rgb.join(', '))\n this.host.style.setProperty('--mtb-brand-on-primary-rgb', onPrimaryRgb.join(', '))\n this.host.style.setProperty('--mtb-brand-emphasis', brandColors.primary.toLowerCase())\n this.host.style.setProperty('--mtb-brand-emphasis-rgb', rgb.join(', '))\n this.host.style.setProperty('--mtb-brand-gradient-start', brandColors.primary.toLowerCase())\n this.host.style.setProperty('--mtb-brand-gradient-end', brandColors.hover.toLowerCase())\n }\n this.host.setAttribute('data-mtb-theme', theme)\n if (this.ownsHost) document.body.appendChild(this.host)\n\n for (const eventType of ShadowContainer.HOST_INTERACTION_EVENTS) {\n this.root.addEventListener(eventType, this.stopHostInteraction)\n }\n\n const style = document.createElement('style')\n style.textContent = cssText\n this.root.appendChild(style)\n }\n\n el<T extends HTMLElement = HTMLElement>(tag: string, className?: string): T {\n const el = document.createElement(tag) as T\n if (className) el.className = className\n return el\n }\n\n append(...nodes: Node[]): void {\n for (const n of nodes) this.root.appendChild(n)\n }\n\n remove(...nodes: Node[]): void {\n for (const n of nodes) {\n if (n.parentNode === this.root) this.root.removeChild(n)\n }\n }\n\n private stopHostInteraction = (event: Event): void => {\n event.stopPropagation()\n }\n\n destroy(): void {\n for (const eventType of ShadowContainer.HOST_INTERACTION_EVENTS) {\n this.root.removeEventListener(eventType, this.stopHostInteraction)\n }\n\n if (!this.ownsHost) {\n // Borrowed. Empty it and leave it standing for whoever put it there.\n this.root.replaceChildren()\n this.host.removeAttribute('data-mtb-toolbar')\n if (this.originalInlineStyle === null) this.host.removeAttribute('style')\n else this.host.setAttribute('style', this.originalInlineStyle)\n if (this.originalTheme === null) this.host.removeAttribute('data-mtb-theme')\n else this.host.setAttribute('data-mtb-theme', this.originalTheme)\n return\n }\n\n // host.remove() is a safe no-op if the host is already detached — e.g.\n // when Turbo swapped document.body on navigation. removeChild(host) would\n // throw NotFoundError there, aborting re-init and dropping the widget.\n this.host.remove()\n }\n}\n","import type { ShadowContainer } from './shadow'\nimport type { I18nMessages } from '../i18n'\n\nexport class FeedbackTab {\n private el: HTMLButtonElement\n private onClick: () => void\n private idleLabel: string\n private activeLabel: string\n private customText?: string\n private active = false\n\n constructor(\n shadow: ShadowContainer,\n messages: I18nMessages,\n position: 'left' | 'right',\n onClick: () => void,\n customText?: string,\n ) {\n this.onClick = onClick\n this.customText = customText\n this.el = shadow.el<HTMLButtonElement>('button', 'mtb-tab')\n if (position === 'left') this.el.classList.add('left')\n this.idleLabel = customText ?? messages.tab\n this.activeLabel = messages.toolbar.exit\n this.setLabel(this.idleLabel)\n this.el.addEventListener('click', onClick)\n shadow.append(this.el)\n }\n\n private setLabel(label: string): void {\n this.el.textContent = label\n this.el.setAttribute('aria-label', label)\n }\n\n setMessages(messages: I18nMessages): void {\n this.idleLabel = this.customText ?? messages.tab\n this.activeLabel = messages.toolbar.exit\n this.setLabel(this.active ? this.activeLabel : this.idleLabel)\n }\n\n setActive(active: boolean): void {\n this.active = active\n this.el.classList.toggle('active', active)\n this.setLabel(active ? this.activeLabel : this.idleLabel)\n }\n\n destroy(): void {\n this.el.removeEventListener('click', this.onClick)\n this.el.remove()\n }\n}\n","import type { ShadowContainer } from './shadow'\nimport type { I18nMessages } from '../i18n'\nimport { escapeHtml } from '../context/dom-utils'\n\nconst FRUSTRATION_DISMISS_MS = 8000\n\nexport interface FrustrationPromptHandlers {\n onTell: () => void\n // The reporter clicked Dismiss — a deliberate refusal.\n onDismiss: () => void\n // Nobody touched the card and it faded out on its own. Kept separate from\n // onDismiss because \"did not notice\" is not \"said no\": treating the two the\n // same suppressed every later prompt for the session.\n onAutoHide: () => void\n}\n\nexport class FrustrationPromptCard {\n private el: HTMLDivElement\n private autoHideTimer: ReturnType<typeof setTimeout> | null = null\n\n constructor(\n shadow: ShadowContainer,\n messages: I18nMessages,\n handlers: FrustrationPromptHandlers,\n ) {\n this.el = shadow.el<HTMLDivElement>('div', 'mtb-frustration-prompt')\n this.el.innerHTML = `\n <div class=\"mtb-frustration-icon\">⚠️</div>\n <span class=\"mtb-frustration-text\">${escapeHtml(messages.frustration.prompt)}</span>\n <div class=\"mtb-frustration-actions\">\n <button class=\"mtb-frustration-tell\">${escapeHtml(messages.frustration.action)}</button>\n <button class=\"mtb-frustration-dismiss\">${escapeHtml(messages.frustration.dismiss)}</button>\n </div>\n `\n\n this.el.querySelector('.mtb-frustration-tell')!.addEventListener('click', handlers.onTell)\n this.el.querySelector('.mtb-frustration-dismiss')!.addEventListener('click', handlers.onDismiss)\n\n shadow.append(this.el)\n this.autoHideTimer = setTimeout(handlers.onAutoHide, FRUSTRATION_DISMISS_MS)\n }\n\n destroy(): void {\n // Clearing matters on the engagement path too: without it, a card destroyed\n // by \"Tell us\" still fired its auto-hide 8s later.\n if (this.autoHideTimer) {\n clearTimeout(this.autoHideTimer)\n this.autoHideTimer = null\n }\n this.el.remove()\n }\n}\n","import type { ShadowContainer } from './shadow'\n\n/**\n * A touch browser replays the tap that opened the popup as a compatibility\n * click a moment after pointerup — and by then this scrim exists underneath,\n * so that click dismissed the popup the same gesture had just opened. On a\n * phone it read as the sheet flashing and vanishing, or as tapping doing\n * nothing at all. Cancelling pointerdown does not prevent it: WebKit still\n * dispatches the click (observed on iOS 18.7).\n *\n * So the scrim ignores anything that arrives before it could plausibly be a\n * new gesture. A deliberate dismissal is a separate press, well past this.\n */\nconst SPAWN_CLICK_GRACE_MS = 400\n\n// Full-screen scrim shown behind the comment popup. Clicking it resets the\n// widget, mirroring the design's dim-to-dismiss behaviour.\nexport class DimOverlay {\n private el: HTMLDivElement\n private bornAt = Date.now()\n private onClick: (e: Event) => void\n\n constructor(shadow: ShadowContainer, onDismiss: () => void, now: () => number = () => Date.now()) {\n this.bornAt = now()\n this.onClick = () => {\n if (now() - this.bornAt < SPAWN_CLICK_GRACE_MS) return\n onDismiss()\n }\n this.el = shadow.el<HTMLDivElement>('div', 'mtb-dim mtb-dim-clickable')\n this.el.addEventListener('click', this.onClick)\n shadow.append(this.el)\n }\n\n destroy(): void {\n this.el.removeEventListener('click', this.onClick)\n this.el.remove()\n }\n}\n","/**\n * Whether a finger is driving, rather than a mouse.\n *\n * Several behaviours fork on this — freezing the host page, which input\n * surface collects a note — and they have to agree, so the question is asked\n * in exactly one place.\n *\n * No matchMedia means no evidence of a touch device: assume a mouse and leave\n * the desktop path alone, which is the one that already worked.\n */\nexport function isTouchPointer(): boolean {\n return window.matchMedia?.('(pointer: coarse)').matches ?? false\n}\n","/**\n * Reserves room at the top of the host page for the annotation toolbar, so the\n * bar occupies space of its own instead of sitting on the customer's content.\n *\n * Without this the bar is roughly 100px of dead zone on a phone — 44px plus\n * the notch inset — covering exactly the strip most sites put their header in,\n * and taps there do nothing at all with no explanation. Measured on a device:\n * everything below responded, the whole top band did not.\n *\n * Known limit, and it is not small: `position: fixed` and `sticky` elements do\n * not move with padding. A site whose header is fixed keeps it where it is,\n * still underneath the bar. This buys back normal-flow content, which is most\n * of the page, and nothing else.\n *\n * The host page is not ours, so the previous inline value is put back verbatim\n * — writing '' would delete a padding the page had set for its own reasons.\n */\nexport class PageOffset {\n private applied = false\n private previousPadding = ''\n private previousTransition = ''\n\n reserve(px: number): void {\n if (this.applied || px <= 0) return\n this.applied = true\n\n const style = document.body.style\n this.previousPadding = style.paddingTop\n this.previousTransition = style.transition\n\n const current = parseFloat(getComputedStyle(document.body).paddingTop) || 0\n // Suppress any transition the page has on padding: the bar appears at once,\n // and a site animating its own layout in response looks like a glitch.\n style.transition = 'none'\n style.paddingTop = `${current + px}px`\n }\n\n release(): void {\n if (!this.applied) return\n this.applied = false\n\n const style = document.body.style\n style.paddingTop = this.previousPadding\n style.transition = this.previousTransition\n }\n\n isApplied(): boolean {\n return this.applied\n }\n}\n","/**\n * Holds the host page still while strokes or a note card are on screen.\n *\n * Annotations are positioned in viewport coordinates; scrolling moves the page\n * out from under them. The lock engages when drawing starts or a note card\n * opens, and releases when the surface tears down or the flow ends (exitAll).\n *\n * Nothing on the host page is mutated. Cancelling the gestures that scroll\n * leaves the page's own styles untouched, so there is nothing to restore and\n * nothing to strand if teardown is ever missed.\n */\nexport interface ScrollLock {\n /** Let the page scroll again. Safe to call more than once. */\n release(): void\n}\n\nconst POINTER_GESTURES = ['wheel', 'touchmove'] as const\n\nconst SCROLL_KEYS = new Set([\n 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight',\n 'PageUp', 'PageDown', 'Home', 'End', ' ',\n])\n\nfunction scrollsInternally(event: Event): boolean {\n for (const node of event.composedPath()) {\n if (!(node instanceof Element)) continue\n const style = getComputedStyle(node)\n const scrollable = /auto|scroll/.test(`${style.overflowY} ${style.overflowX}`)\n if (!scrollable) continue\n if (node.scrollHeight > node.clientHeight || node.scrollWidth > node.clientWidth) return true\n }\n return false\n}\n\nfunction consumesScrollKey(el: Element, key: string): boolean {\n const tag = el.tagName\n if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return true\n if (el instanceof HTMLElement && el.isContentEditable) return true\n if (key === ' ' && (tag === 'BUTTON' || tag === 'SUMMARY')) return true\n return false\n}\n\nexport function lockPageScroll(): ScrollLock {\n const blockGesture = (event: Event): void => {\n if (scrollsInternally(event)) return\n if (event.cancelable) event.preventDefault()\n }\n\n const blockKey = (event: Event): void => {\n if (!(event instanceof KeyboardEvent)) return\n if (!SCROLL_KEYS.has(event.key)) return\n const origin = event.composedPath()[0]\n if (origin instanceof Element && consumesScrollKey(origin, event.key)) return\n if (event.cancelable) event.preventDefault()\n }\n\n for (const gesture of POINTER_GESTURES) {\n window.addEventListener(gesture, blockGesture, { capture: true, passive: false })\n }\n window.addEventListener('keydown', blockKey, { capture: true })\n\n return {\n release(): void {\n for (const gesture of POINTER_GESTURES) {\n window.removeEventListener(gesture, blockGesture, { capture: true })\n }\n window.removeEventListener('keydown', blockKey, { capture: true })\n },\n }\n}\n","import type { ShadowContainer } from '../widget/shadow'\nimport type { I18nMessages } from '../i18n'\nimport { isTouchPointer } from '../widget/pointer-kind'\nimport { escapeHtml } from '../context/dom-utils'\n\nexport type ToolbarMode = 'markup' | 'record'\n\n// Mirrors the `height` the max-width: 480px block gives .mtb-toolbar. The\n// safe-area inset stacked on top of it can only be measured, not predicted.\nconst MOBILE_BAR_HEIGHT_PX = 44\n\nexport class AnnotationToolbar {\n private el: HTMLDivElement\n // The capsule, which is what gets measured and dimmed. hintTextEl is only the\n // words inside it — dimming that instead would leave the capsule around them\n // at full strength, and shrink the region counted as \"under the pointer\".\n private hintBarEl: HTMLDivElement\n private hintTextEl!: HTMLSpanElement\n private host: HTMLElement\n private markupBtn!: HTMLButtonElement\n private recordBtn!: HTMLButtonElement\n private messages: I18nMessages\n private currentMode: ToolbarMode = 'markup'\n private readonly touch: boolean\n\n // The hint sits over the page and carries nothing to click, so it steps back\n // while the pointer is on top of it and comes forward again the moment the\n // pointer leaves. Nothing else about the page's movement affects it.\n //\n // This is a hover affordance, and touch has no hover — but a tap still emits\n // a compatibility move wherever the finger landed, which would dim the hint\n // for no reason. Restricting to a real mouse makes it a no-op on a phone.\n private handlePointerMove = (e: PointerEvent): void => {\n if (e.pointerType !== 'mouse') return\n\n const rect = this.hintBarEl.getBoundingClientRect()\n const inside =\n e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom\n\n this.hintBarEl.classList.toggle('mtb-hint-bar--under-pointer', inside)\n }\n\n // Tracks whether the touch-hint dismiss listener is currently attached, so\n // add and remove stay symmetric: it is only ever added on touch with the\n // hint showing, and destroy() must not issue a remove for an add that never\n // happened.\n private touchHintDismissListening = false\n\n private dismissTouchHint = (): void => {\n this.hintBarEl.classList.add('mtb-hint-bar--dismissed')\n this.stopTouchHintDismissListening()\n }\n\n private stopTouchHintDismissListening(): void {\n if (!this.touchHintDismissListening) return\n this.touchHintDismissListening = false\n document.removeEventListener('pointerdown', this.dismissTouchHint, true)\n }\n\n constructor(\n shadow: ShadowContainer,\n messages: I18nMessages,\n onExit: () => void,\n onModeChange?: (mode: ToolbarMode) => void,\n position: 'left' | 'right' = 'right',\n showTouchHint = true,\n ) {\n this.messages = messages\n this.touch = isTouchPointer()\n\n // Two siblings, not one element. A fixed-position child is laid out against\n // its nearest transformed ancestor rather than the viewport, and the bar is\n // transformed both at rest and by its entry animation — so a hint nested\n // inside it could never be centred on the page.\n this.hintBarEl = shadow.el<HTMLDivElement>('div', 'mtb-hint-bar')\n // \"Click to comment\" is a lie on a phone, where a tap is deliberately inert\n // — the hint has to describe the one gesture that does anything.\n this.hintBarEl.innerHTML = `<span class=\"mtb-toolbar-hint\">${escapeHtml(this.idleHint())}</span>`\n if (this.touch && !showTouchHint) this.hintBarEl.classList.add('mtb-hint-bar--dismissed')\n\n this.el = shadow.el<HTMLDivElement>('div', 'mtb-toolbar')\n if (position === 'left') this.el.classList.add('left')\n this.el.innerHTML = this.buildHTML(messages)\n shadow.append(this.hintBarEl, this.el)\n\n // Lets the CSS retire the tab for exactly as long as this bar is up. Keyed\n // to the bar's own lifetime rather than to the tab's active state: the tab\n // is still active while a clarification card is open, and by then the bar\n // is gone — hiding it there would leave no way out at all.\n this.host = shadow.host\n this.host.setAttribute('data-mtb-toolbar', '')\n\n this.hintTextEl = this.hintBarEl.querySelector('.mtb-toolbar-hint')!\n this.markupBtn = this.el.querySelector('.mtb-toolbar-mode-markup')!\n this.recordBtn = this.el.querySelector('.mtb-toolbar-mode-record')!\n\n this.markupBtn.addEventListener('click', () => {\n this.setMode('markup')\n onModeChange?.('markup')\n })\n this.recordBtn.addEventListener('click', () => {\n this.setMode('record')\n onModeChange?.('record')\n })\n this.el.querySelector('.mtb-exit-btn')!.addEventListener('click', onExit)\n\n document.addEventListener('pointermove', this.handlePointerMove as EventListener)\n if (this.touch && showTouchHint) {\n document.addEventListener('pointerdown', this.dismissTouchHint, true)\n this.touchHintDismissListening = true\n }\n }\n\n private buildHTML(m: I18nMessages): string {\n return `\n <div class=\"mtb-toolbar-modes\">\n <button class=\"mtb-toolbar-mode-btn mtb-toolbar-mode-markup active\" aria-label=\"${escapeHtml(m.toolbar.markup)}\" title=\"${escapeHtml(m.toolbar.markup)}\">\n <svg width=\"16\" height=\"16\" 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\"/><line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"7\"/><line x1=\"12\" y1=\"17\" x2=\"12\" y2=\"21\"/><line x1=\"3\" y1=\"12\" x2=\"7\" y2=\"12\"/><line x1=\"17\" y1=\"12\" x2=\"21\" y2=\"12\"/></svg><span class=\"mtb-toolbar-label\">${escapeHtml(m.toolbar.markup)}</span>\n </button>\n <button class=\"mtb-toolbar-mode-btn mtb-toolbar-mode-record\" aria-label=\"${escapeHtml(m.toolbar.record)}\" title=\"${escapeHtml(m.toolbar.record)}\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"2\" y=\"6\" width=\"13\" height=\"12\" rx=\"2.5\"/><path d=\"M15 10.5 21.2 7.4a.6.6 0 0 1 .8.5v8.2a.6.6 0 0 1-.8.5L15 13.5z\"/></svg><span class=\"mtb-toolbar-label\">${escapeHtml(m.toolbar.record)}</span>\n </button>\n </div>\n <span class=\"mtb-toolbar-sep\"></span>\n <button class=\"mtb-exit-btn\" aria-label=\"${escapeHtml(m.toolbar.exit)}\" title=\"${escapeHtml(m.toolbar.exit)}\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 6L6 18M6 6l12 12\"/>\n </svg><span class=\"mtb-toolbar-label\">${escapeHtml(m.toolbar.exit)}</span>\n </button>\n `\n }\n\n private idleHint(): string {\n return this.touch ? this.messages.toolbar.hintTouch : this.messages.toolbar.hint\n }\n\n setMode(mode: ToolbarMode): void {\n this.currentMode = mode\n if (mode === 'markup') {\n this.markupBtn.classList.add('active')\n this.recordBtn.classList.remove('active')\n this.hintTextEl.textContent = this.idleHint()\n } else {\n this.markupBtn.classList.remove('active')\n this.recordBtn.classList.add('active')\n this.hintTextEl.textContent = this.messages.toolbar.hintRecord\n }\n }\n\n getMode(): ToolbarMode {\n return this.currentMode\n }\n\n /**\n * How much vertical room the bar occupies, including any safe-area inset.\n *\n * Falls back to the height the stylesheet declares when measurement returns\n * nothing — which happens before first paint, and in any environment without\n * layout. Reserving the wrong amount is recoverable; reserving none means\n * the bar covers the page again, which is the defect this exists to fix.\n */\n height(): number {\n return this.el.getBoundingClientRect().height || MOBILE_BAR_HEIGHT_PX\n }\n\n destroy(): void {\n document.removeEventListener('pointermove', this.handlePointerMove as EventListener)\n this.stopTouchHintDismissListening()\n this.host.removeAttribute('data-mtb-toolbar')\n this.hintBarEl.remove()\n this.el.remove()\n }\n}\n","import type { ShadowContainer } from '../widget/shadow'\n\nexport interface OverlayHandlers {\n onPointerDown: (e: PointerEvent) => void\n onPointerMove: (e: PointerEvent) => void\n onPointerUp: (e: PointerEvent) => void\n onPointerCancel: (e: PointerEvent) => void\n}\n\n/**\n * The annotation surface. Runs on pointer events only — never mouse events.\n *\n * Mouse listeners are deliberately absent. Touch browsers replay a tap as\n * pointerdown/up followed by compatibility mousedown/up/click, so listening to\n * both would run the annotation logic twice for one tap and drop two pins.\n * Pointer events already cover mouse and stylus, so the desktop loses nothing.\n */\nexport class AnnotationOverlay {\n private overlayEl: HTMLDivElement\n private dimEl: HTMLDivElement\n private handlers: OverlayHandlers\n private onPointerDown: (e: Event) => void\n private onPointerMove: (e: Event) => void\n private onPointerUp: (e: Event) => void\n private onPointerCancel: (e: Event) => void\n\n constructor(shadow: ShadowContainer, handlers: OverlayHandlers) {\n this.handlers = handlers\n\n this.overlayEl = shadow.el<HTMLDivElement>('div', 'mtb-overlay')\n\n this.onPointerDown = (e) => {\n const pe = e as PointerEvent\n if (!pe.isPrimary) return\n // Capture keeps a stroke alive when the finger crosses the overlay edge.\n // Without it, a drag that leaves the surface silently stops mid-line.\n try {\n this.overlayEl.setPointerCapture(pe.pointerId)\n } catch {\n // Thrown when the pointer is already gone. The stroke can still run\n // uncaptured, so this is not worth aborting the gesture over.\n }\n this.handlers.onPointerDown(pe)\n }\n\n this.onPointerMove = (e) => {\n const pe = e as PointerEvent\n if (!pe.isPrimary) return\n this.handlers.onPointerMove(pe)\n }\n\n this.onPointerUp = (e) => {\n const pe = e as PointerEvent\n if (!pe.isPrimary) return\n this.releaseCapture(pe.pointerId)\n this.handlers.onPointerUp(pe)\n }\n\n // Replaces the old mouseleave path. The browser fires this when it takes\n // the gesture away from us — a system gesture, or the page going\n // background — which is exactly when a half-drawn stroke should end.\n this.onPointerCancel = (e) => {\n const pe = e as PointerEvent\n // A palm or second finger can be cancelled on its own. That pointer never\n // reached the handlers, so acting on its cancel would cut short a stroke\n // the primary finger is still drawing.\n if (!pe.isPrimary) return\n this.releaseCapture(pe.pointerId)\n this.handlers.onPointerCancel(pe)\n }\n\n this.overlayEl.addEventListener('pointerdown', this.onPointerDown)\n this.overlayEl.addEventListener('pointermove', this.onPointerMove)\n this.overlayEl.addEventListener('pointerup', this.onPointerUp)\n this.overlayEl.addEventListener('pointercancel', this.onPointerCancel)\n shadow.append(this.overlayEl)\n\n this.dimEl = shadow.el<HTMLDivElement>('div', 'mtb-dim')\n shadow.append(this.dimEl)\n }\n\n private releaseCapture(pointerId: number): void {\n try {\n this.overlayEl.releasePointerCapture(pointerId)\n } catch {\n // Already released, or the pointer is gone. Nothing to undo.\n }\n }\n\n destroy(): void {\n this.overlayEl.removeEventListener('pointerdown', this.onPointerDown)\n this.overlayEl.removeEventListener('pointermove', this.onPointerMove)\n this.overlayEl.removeEventListener('pointerup', this.onPointerUp)\n this.overlayEl.removeEventListener('pointercancel', this.onPointerCancel)\n this.overlayEl.remove()\n this.dimEl.remove()\n }\n}\n","import type { ShadowContainer } from '../widget/shadow'\nimport { getElementName } from '../context/dom-utils'\n\nexport class ElementHighlighter {\n private highlightEl: HTMLDivElement\n private tooltipEl: HTMLDivElement\n\n constructor(shadow: ShadowContainer) {\n this.highlightEl = shadow.el<HTMLDivElement>('div', 'mtb-highlight')\n this.tooltipEl = shadow.el<HTMLDivElement>('div', 'mtb-highlight-tooltip')\n this.hide()\n shadow.append(this.highlightEl, this.tooltipEl)\n }\n\n highlight(el: Element): void {\n const rect = el.getBoundingClientRect()\n this.highlightEl.style.top = `${rect.top - 2}px`\n this.highlightEl.style.left = `${rect.left - 2}px`\n this.highlightEl.style.width = `${rect.width + 4}px`\n this.highlightEl.style.height = `${rect.height + 4}px`\n this.highlightEl.style.display = 'block'\n this.showTooltip(el, rect)\n }\n\n private showTooltip(el: Element, rect: DOMRect): void {\n this.tooltipEl.textContent = getElementName(el)\n const below = rect.top < 30\n this.tooltipEl.classList.toggle('below', below)\n this.tooltipEl.style.left = `${rect.left + rect.width / 2}px`\n this.tooltipEl.style.top = below ? `${rect.bottom + 8}px` : `${rect.top - 8}px`\n this.tooltipEl.style.display = 'block'\n }\n\n select(): void {\n this.highlightEl.classList.add('selected')\n this.tooltipEl.style.display = 'none'\n }\n\n hide(): void {\n this.highlightEl.style.display = 'none'\n this.highlightEl.classList.remove('selected')\n this.tooltipEl.style.display = 'none'\n }\n\n destroy(): void {\n this.highlightEl.remove()\n this.tooltipEl.remove()\n }\n}\n","// The capture pipeline works in clone-space: viewport rects corrected for\n// nested-scroll drift, i.e. where html-to-image renders each element in the\n// captured bitmap. Capture metrics and privacy covers are the same kind of\n// rectangle in that one space, so they share this spelling. Crop rectangles\n// (viewport / canvas pixels) stay a separate type on purpose — the space\n// difference is information.\nexport interface Rect {\n left: number\n top: number\n width: number\n height: number\n}\n\n// Owns the offset+scale math from clone-space onto the rendered canvas, so\n// callers stop hand-writing (v - left) * scaleX at every layer. left/top is\n// the clone origin inside the capture; scale is capture CSS px → canvas px.\nexport interface CaptureTransform {\n left: number\n top: number\n scaleX: number\n scaleY: number\n}\n\nexport function captureTransform(region: Rect, canvasWidth: number, canvasHeight: number): CaptureTransform {\n return {\n left: region.left,\n top: region.top,\n scaleX: canvasWidth / region.width,\n scaleY: canvasHeight / region.height,\n }\n}\n\nexport function toCanvasX(transform: CaptureTransform, x: number): number {\n return (x - transform.left) * transform.scaleX\n}\n\nexport function toCanvasY(transform: CaptureTransform, y: number): number {\n return (y - transform.top) * transform.scaleY\n}\n\nexport function rectToCanvas(transform: CaptureTransform, rect: Rect): Rect {\n return {\n left: toCanvasX(transform, rect.left),\n top: toCanvasY(transform, rect.top),\n width: rect.width * transform.scaleX,\n height: rect.height * transform.scaleY,\n }\n}\n\nexport interface ScrollOffset {\n x: number\n y: number\n}\n\n/**\n * The capture region: the visual viewport, and nothing else. html-to-image\n * renders document.body shifted by the page scroll so the on-screen region\n * lands at the canvas origin — which makes this rect both the capture request\n * and the deliverable. There is no crop step: cropping a clone-space bitmap\n * was only ever needed when the capture was the whole document.\n */\nexport function viewportCaptureRect(): Rect {\n return { left: 0, top: 0, width: window.innerWidth, height: window.innerHeight }\n}\n\n/**\n * How far an anchored annotation must move to land in a capture's frame.\n *\n * The anchor is the annotation's scroll debt at creation (page + nested\n * scrollers); the capture's pageScroll is the page half of that debt at capture\n * time. Adding the anchor lifts the annotation into clone space, subtracting\n * the capture scroll brings it back down into the viewport bitmap.\n */\nexport function anchorScreenshotOffset(\n anchor: ScrollOffset,\n pageScroll: ScrollOffset,\n): ScrollOffset {\n return { x: anchor.x - pageScroll.x, y: anchor.y - pageScroll.y }\n}\n\n// How far a viewport measurement has to move to reach clone space: every\n// scroller between the element and the body has pushed its content out of view\n// by its own scrollLeft/scrollTop, and html-to-image renders it back.\nexport function nestedScrollOffset(target: Element | null): ScrollOffset {\n let x = 0\n let y = 0\n let current = target\n\n while (current && current !== document.body) {\n if (current instanceof HTMLElement) {\n x += current.scrollLeft\n y += current.scrollTop\n }\n current = parentElement(current)\n }\n\n return { x, y }\n}\n\nexport function parentElement(element: Element): Element | null {\n if (element.parentElement) return element.parentElement\n\n const root = element.getRootNode()\n return root instanceof ShadowRoot ? root.host : null\n}\n\n/**\n * The full offset from a viewport measurement to clone space, page scroll\n * included — the anchor an annotation records so that later scrolling cannot\n * move it.\n *\n * nestedScrollOffset stops at the body because the capture's own metrics carry\n * the page scroll for anything measured at capture time. An annotation is not\n * measured at capture time, so it has to carry that half itself.\n */\nexport function captureAnchorOffset(target: Element | null): ScrollOffset {\n const nested = nestedScrollOffset(target)\n return { x: nested.x + window.scrollX, y: nested.y + window.scrollY }\n}\n","import type { ShadowContainer } from '../widget/shadow'\n\nexport class PinMarker {\n private pins: HTMLDivElement[] = []\n private shadow: ShadowContainer\n\n constructor(shadow: ShadowContainer) {\n this.shadow = shadow\n }\n\n addPin(x: number, y: number): void {\n const pin = this.shadow.el<HTMLDivElement>('div', 'mtb-pin')\n pin.style.top = `${y}px`\n pin.style.left = `${x}px`\n this.shadow.append(pin)\n this.pins.push(pin)\n }\n\n clearAll(): void {\n for (const pin of this.pins) pin.remove()\n this.pins = []\n }\n\n destroy(): void {\n this.clearAll()\n }\n}\n","export const BRAND_COLOR = '#059669'\n// Same green as BRAND_COLOR, in the form shadows need. Derived once so a brand\n// change cannot leave the shadows on the old colour.\nconst BRAND_RGB = '5,150,105'\n\nexport function brandAlpha(alpha: number): string {\n return `rgba(${BRAND_RGB},${alpha})`\n}\n\nexport const FONT_STACK = '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif'\n\nexport const DRAW_STROKE = {\n color: 'var(--mtb-brand-primary)',\n width: '3.5',\n linecap: 'round',\n linejoin: 'round',\n filter: 'drop-shadow(0 0 1px rgba(255, 255, 255, 0.95)) drop-shadow(0 1px 2px rgba(15, 23, 42, 0.72))',\n} as const\n","import type { ShadowContainer } from '../widget/shadow'\nimport type { Annotation } from '../types'\nimport { DRAW_STROKE } from '../styles'\nimport { elementUnderPoint } from '../context/dom-utils'\nimport { nestedScrollOffset } from '../screenshot/geometry'\nimport type { ScrollOffset } from '../screenshot/geometry'\n\ntype Point = [number, number]\n\ninterface StrokeAnchor {\n /** Page scroll plus enclosing-container scroll: the offset to clone space. */\n offset: ScrollOffset\n /** The page-scroll half on its own, kept so viewportDrift can undo it. */\n pageScroll: ScrollOffset\n}\n\ninterface Stroke {\n path: SVGPathElement\n d: string\n points: Point[]\n anchor: StrokeAnchor\n}\n\nexport class DrawLayer {\n private svg: SVGSVGElement\n private currentPath: SVGPathElement | null = null\n private currentPoints: Point[] = []\n // Sampled when the stroke starts rather than when the drawing is handed over,\n // so a page or container scroll partway through a multi-stroke drawing moves\n // neither the strokes already made nor the ones still to come.\n private currentAnchor: StrokeAnchor = emptyAnchor()\n private isDrawing = false\n private committed: Stroke[] = []\n private redoStack: { d: string; points: Point[]; anchor: StrokeAnchor }[] = []\n\n constructor(shadow: ShadowContainer) {\n this.svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')\n this.svg.setAttribute('class', 'mtb-draw-svg')\n shadow.append(this.svg)\n }\n\n startDraw(x: number, y: number): void {\n this.isDrawing = true\n this.currentPoints = [[x, y]]\n this.currentAnchor = strokeAnchor(x, y)\n this.currentPath = this.createPath()\n this.svg.appendChild(this.currentPath)\n }\n\n continueDraw(x: number, y: number): void {\n if (!this.isDrawing || !this.currentPath) return\n this.currentPoints.push([x, y])\n this.currentPath.setAttribute('d', buildPathD(this.currentPoints))\n }\n\n /**\n * How far the pointer has wandered from where it went down, in pixels.\n *\n * This is what separates a tap from a stroke. Counting points cannot: a\n * mouse click reports no movement at all, while a finger reports a burst of\n * sub-pixel jitter, so any point-based threshold reads every tap on a phone\n * as a drawing.\n */\n getCurrentSpan(): number {\n const [start] = this.currentPoints\n if (!start) return 0\n\n let max = 0\n for (const [x, y] of this.currentPoints) {\n max = Math.max(max, Math.hypot(x - start[0], y - start[1]))\n }\n return max\n }\n\n // Commits the in-progress stroke to the undo stack and clears the redo stack.\n // Callers decide whether the stroke has enough points before committing.\n commitStroke(): boolean {\n if (!this.isDrawing || !this.currentPath) return false\n this.isDrawing = false\n this.committed.push({\n path: this.currentPath,\n d: buildPathD(this.currentPoints),\n points: this.currentPoints,\n anchor: this.currentAnchor,\n })\n this.redoStack = []\n this.currentPath = null\n this.currentPoints = []\n return true\n }\n\n cancelCurrentDraw(): void {\n this.currentPath?.remove()\n this.currentPath = null\n this.isDrawing = false\n this.currentPoints = []\n }\n\n undo(): boolean {\n const stroke = this.committed.pop()\n if (!stroke) return false\n stroke.path.remove()\n this.redoStack.push({ d: stroke.d, points: stroke.points, anchor: stroke.anchor })\n return true\n }\n\n redo(): boolean {\n const entry = this.redoStack.pop()\n if (!entry) return false\n const path = this.createPath()\n path.setAttribute('d', entry.d)\n this.svg.appendChild(path)\n // The stroke comes back in the frame it was drawn in, not the frame the\n // page happens to be scrolled to when redo is pressed.\n this.committed.push({ path, d: entry.d, points: entry.points, anchor: entry.anchor })\n return true\n }\n\n canUndo(): boolean {\n return this.committed.length > 0\n }\n\n canRedo(): boolean {\n return this.redoStack.length > 0\n }\n\n hasStrokes(): boolean {\n return this.committed.length > 0\n }\n\n getAnnotation(): Annotation | null {\n const [first] = this.committed\n if (!first) return null\n\n // Strokes made either side of a scroll were recorded against different\n // viewports. Restate them all in the frame the first stroke was drawn in so\n // the drawing is one shape, and hand that frame's anchor to the caller.\n const strokes = this.committed.map(stroke => shiftPoints(stroke.points, {\n x: stroke.anchor.offset.x - first.anchor.offset.x,\n y: stroke.anchor.offset.y - first.anchor.offset.y,\n }))\n const [cx, cy] = center(strokes.flat())\n return {\n type: 'draw',\n x: cx,\n y: cy,\n drawPath: strokes.map(buildPathD).join(' '),\n captureOffsetX: first.anchor.offset.x,\n captureOffsetY: first.anchor.offset.y,\n }\n }\n\n /**\n * How far the drawing's frame has drifted from the live viewport, so a caller\n * can map a point in the drawing back onto the page as it stands now — which\n * is what hit-testing the drawing's target needs.\n */\n viewportDrift(): ScrollOffset {\n const [first] = this.committed\n if (!first) return { x: 0, y: 0 }\n return {\n x: window.scrollX - first.anchor.pageScroll.x,\n y: window.scrollY - first.anchor.pageScroll.y,\n }\n }\n\n clearAll(): void {\n while (this.svg.firstChild) this.svg.removeChild(this.svg.firstChild)\n this.currentPath = null\n this.isDrawing = false\n this.currentPoints = []\n this.committed = []\n this.redoStack = []\n }\n\n isCurrentlyDrawing(): boolean {\n return this.isDrawing\n }\n\n destroy(): void {\n this.svg.remove()\n }\n\n private createPath(): SVGPathElement {\n const path = document.createElementNS('http://www.w3.org/2000/svg', 'path')\n path.setAttribute('fill', 'none')\n path.setAttribute('stroke', DRAW_STROKE.color)\n path.setAttribute('stroke-width', DRAW_STROKE.width)\n path.setAttribute('stroke-linecap', DRAW_STROKE.linecap)\n path.setAttribute('stroke-linejoin', DRAW_STROKE.linejoin)\n path.setAttribute('style', `filter:${DRAW_STROKE.filter}`)\n return path\n }\n}\n\nfunction buildPathD(points: Point[]): string {\n if (points.length === 0) return ''\n const [fx, fy] = points[0]\n let d = `M ${fx} ${fy}`\n for (let i = 1; i < points.length; i++) {\n const [x, y] = points[i]\n d += ` L ${x} ${y}`\n }\n return d\n}\n\nfunction emptyAnchor(): StrokeAnchor {\n return { offset: { x: 0, y: 0 }, pageScroll: { x: 0, y: 0 } }\n}\n\nfunction strokeAnchor(x: number, y: number): StrokeAnchor {\n const pageScroll = { x: window.scrollX, y: window.scrollY }\n const nested = nestedScrollOffset(elementUnderPoint(x, y))\n return {\n offset: { x: nested.x + pageScroll.x, y: nested.y + pageScroll.y },\n pageScroll,\n }\n}\n\nfunction shiftPoints(points: Point[], offset: ScrollOffset): Point[] {\n if (offset.x === 0 && offset.y === 0) return points\n return points.map(([x, y]) => [x + offset.x, y + offset.y])\n}\n\nfunction center(points: Point[]): Point {\n const xs = points.map(p => p[0])\n const ys = points.map(p => p[1])\n const cx = (Math.min(...xs) + Math.max(...xs)) / 2\n const cy = (Math.min(...ys) + Math.max(...ys)) / 2\n return [cx, cy]\n}\n","// rrweb's own defaults for opting an element out of a replay. Named here so the\n// README can document them and so the values stay in one place.\nexport const BLOCK_CLASS = 'rr-block'\nexport const MASK_TEXT_CLASS = 'rr-mask'\n\nexport const PRIVACY_SELECTOR = `.${BLOCK_CLASS}, .${MASK_TEXT_CLASS}`\n\nexport function isPrivacyProtected(element: Element): boolean {\n return element.closest(PRIVACY_SELECTOR) !== null\n}\n\nexport function structuralElementName(element: Element): string {\n const role = element.getAttribute('role')?.trim().split(/\\s+/)[0]\n if (role && /^[a-z][a-z0-9-]*$/i.test(role)) return role\n return element.tagName.toLowerCase()\n}\n\nexport function structuralElementSelector(element: Element): string {\n return element.tagName.toLowerCase()\n}\n","import type { ShadowContainer } from '../widget/shadow'\nimport type { I18nMessages } from '../i18n'\nimport type { Annotation } from '../types'\nimport { AnnotationOverlay } from './overlay'\nimport { ElementHighlighter } from './highlighter'\nimport { getElementName, getCssSelector, elementUnderPoint } from '../context/dom-utils'\nimport { captureAnchorOffset } from '../screenshot/geometry'\nimport { PinMarker } from './pin'\nimport { DrawLayer } from './draw'\nimport { isTouchPointer } from '../widget/pointer-kind'\nimport { isPrivacyProtected, structuralElementName, structuralElementSelector } from '../privacy/dom'\n\n// How far the pointer must travel before the gesture counts as drawing rather\n// than pointing. Roughly the slop browsers themselves allow between mousedown\n// and click, and comfortably above the jitter a resting finger produces.\nconst DRAW_MIN_TRAVEL_PX = 10\n\ninterface SessionHandlers {\n onPin: (annotation: Annotation, targetName: string) => void\n onDrawStroke: () => void\n}\n\nfunction annotationTarget(element: Element): { name: string; text: string; selector: string } {\n if (isPrivacyProtected(element)) {\n return {\n name: structuralElementName(element),\n text: '',\n selector: structuralElementSelector(element),\n }\n }\n\n return {\n name: getElementName(element),\n text: element.textContent?.trim().slice(0, 200) ?? '',\n selector: getCssSelector(element),\n }\n}\n\nexport class AnnotationSession {\n private overlay: AnnotationOverlay\n private highlighter: ElementHighlighter\n private pinMarker: PinMarker\n private drawLayer: DrawLayer\n private interactionActive = true\n private pendingHighlightFrame: number | null = null\n private lastMoveX = 0\n private lastMoveY = 0\n // Read once. A pointer kind does not change mid-session, and re-querying it\n // per gesture would let a mid-stroke media-query flip strand a stroke.\n private readonly touch = isTouchPointer()\n\n constructor(\n shadow: ShadowContainer,\n private messages: I18nMessages,\n private handlers: SessionHandlers,\n ) {\n this.highlighter = new ElementHighlighter(shadow)\n this.pinMarker = new PinMarker(shadow)\n this.drawLayer = new DrawLayer(shadow)\n this.overlay = new AnnotationOverlay(shadow, {\n onPointerDown: (e) => this.handlePointerDown(e),\n onPointerMove: (e) => this.handlePointerMove(e),\n onPointerUp: (e) => this.handlePointerUp(e),\n // Previously mouseleave. The overlay now captures the pointer, so a\n // stroke no longer ends by wandering off the surface — only by the\n // browser taking the gesture away, which is what pointercancel means.\n onPointerCancel: () => {\n if (this.drawLayer.isCurrentlyDrawing()) this.finishStroke()\n this.cancelPendingHighlight()\n this.highlighter.hide()\n },\n })\n }\n\n private handlePointerDown(e: PointerEvent): void {\n e.preventDefault()\n this.drawLayer.startDraw(e.clientX, e.clientY)\n }\n\n private handlePointerMove(e: PointerEvent): void {\n if (this.drawLayer.isCurrentlyDrawing()) {\n this.cancelPendingHighlight()\n this.drawLayer.continueDraw(e.clientX, e.clientY)\n this.highlighter.hide()\n return\n }\n\n this.lastMoveX = e.clientX\n this.lastMoveY = e.clientY\n if (this.pendingHighlightFrame !== null) return\n this.pendingHighlightFrame = requestAnimationFrame(() => {\n this.pendingHighlightFrame = null\n this.updateHighlight()\n })\n }\n\n private updateHighlight(): void {\n if (!this.interactionActive || this.drawLayer.isCurrentlyDrawing() || this.drawSessionActive()) return\n const underEl = elementUnderPoint(this.lastMoveX, this.lastMoveY)\n if (underEl) this.highlighter.highlight(underEl)\n else this.highlighter.hide()\n }\n\n private cancelPendingHighlight(): void {\n if (this.pendingHighlightFrame === null) return\n cancelAnimationFrame(this.pendingHighlightFrame)\n this.pendingHighlightFrame = null\n }\n\n private handlePointerUp(e: PointerEvent): void {\n if (!this.drawLayer.isCurrentlyDrawing()) return\n this.finishStroke(e)\n }\n\n // A drag that travels far enough becomes a freehand stroke and opens the\n // draw note bar; a tap on an element becomes a pin. Stray taps while a\n // drawing already exists are ignored so they don't hijack the draw flow.\n private finishStroke(e?: PointerEvent): void {\n if (this.drawLayer.getCurrentSpan() >= DRAW_MIN_TRAVEL_PX) {\n this.drawLayer.commitStroke()\n this.highlighter.hide()\n this.handlers.onDrawStroke()\n return\n }\n\n this.drawLayer.cancelCurrentDraw()\n // A finger draws; it does not point. Hitting a 40px control on a phone is\n // fiddly, and the habit people already have is to scribble over what is\n // wrong rather than tap it precisely. So on touch a tap means nothing at\n // all — which also retires a whole family of defects, since every \"the tap\n // did nothing\" and \"a stray dot appeared\" report was a tap that carried\n // meaning it should never have carried. The element identity a pin used to\n // supply is recovered from the stroke instead, in getDrawAnnotation().\n if (this.touch) return\n if (this.drawLayer.hasStrokes() || !e) return\n\n const underEl = elementUnderPoint(e.clientX, e.clientY)\n const target = underEl ? annotationTarget(underEl) : null\n const targetName = target?.name ?? this.messages.annotation.element\n const rect = underEl?.getBoundingClientRect()\n // Sampled here, alongside the coordinates it belongs to. Read any later and\n // it would describe wherever the reporter has scrolled to since.\n const anchor = captureAnchorOffset(underEl)\n const annotation: Annotation = {\n type: 'pin',\n x: e.clientX,\n y: e.clientY,\n targetSelector: target?.selector,\n targetText: target?.text ?? '',\n targetName,\n targetRect: rect ? { top: rect.top, left: rect.left, width: rect.width, height: rect.height, bottom: rect.bottom } : undefined,\n captureOffsetX: anchor.x,\n captureOffsetY: anchor.y,\n }\n this.pinMarker.addPin(e.clientX, e.clientY)\n this.highlighter.select()\n this.handlers.onPin(annotation, targetName)\n }\n\n undoDraw(): void {\n this.drawLayer.undo()\n }\n\n redoDraw(): void {\n this.drawLayer.redo()\n }\n\n canUndoDraw(): boolean {\n return this.drawLayer.canUndo()\n }\n\n canRedoDraw(): boolean {\n return this.drawLayer.canRedo()\n }\n\n hasDrawStrokes(): boolean {\n return this.drawLayer.hasStrokes()\n }\n\n private drawSessionActive(): boolean {\n return this.drawLayer.hasStrokes() || this.drawLayer.canRedo()\n }\n\n // The stroke carries the element identity a pin used to supply. Without it a\n // touch report would say only \"the user drew somewhere\", where the board has\n // always been able to say which control they meant — that identity is what\n // makes a report actionable, and it should not be the price of not making\n // people aim. Resolved from the stroke's centre, so a circle drawn around a\n // button lands inside it. Best effort by design: an underline sits below its\n // target and will miss, which is why the drawing itself is still the record.\n getDrawAnnotation(): Annotation | null {\n const annotation = this.drawLayer.getAnnotation()\n if (!annotation) return null\n\n // The drawing's coordinates are in the frame it was drawn in; hit-testing\n // reads the page as it is now, so the centre has to make that trip first.\n const drift = this.drawLayer.viewportDrift()\n const underEl = elementUnderPoint(annotation.x - drift.x, annotation.y - drift.y)\n if (!underEl) return annotation\n\n // The rect and its own anchor are read in the same breath, so together they\n // say where this element sits in the capture; subtracting the drawing's\n // anchor restates that in the frame the rest of the annotation uses.\n const rect = underEl.getBoundingClientRect()\n const anchor = captureAnchorOffset(underEl)\n const dx = anchor.x - (annotation.captureOffsetX ?? 0)\n const dy = anchor.y - (annotation.captureOffsetY ?? 0)\n const target = annotationTarget(underEl)\n return {\n ...annotation,\n targetSelector: target.selector,\n targetText: target.text,\n targetName: target.name,\n targetRect: {\n top: rect.top + dy,\n left: rect.left + dx,\n width: rect.width,\n height: rect.height,\n bottom: rect.bottom + dy,\n },\n }\n }\n\n clearDraw(): void {\n this.drawLayer.clearAll()\n }\n\n dismissInteraction(): void {\n if (!this.interactionActive) return\n this.interactionActive = false\n this.cancelPendingHighlight()\n this.overlay.destroy()\n }\n\n hideHighlight(): void {\n this.highlighter.hide()\n }\n\n destroy(): void {\n this.dismissInteraction()\n this.highlighter.destroy()\n this.pinMarker.destroy()\n this.drawLayer.destroy()\n }\n}\n","/**\n * How much of the window the software keyboard is covering.\n *\n * A `position: fixed` sheet gets no help from the browser here — the built-in\n * \"scroll the focused field into view\" only moves elements that are in the\n * document flow, so on iOS the keyboard simply covers the sheet.\n *\n * The two platforms hide the keyboard in different places, and this one\n * formula covers both. iOS keeps the layout viewport at full height and\n * shrinks (and shifts) the visual viewport, so the difference is the keyboard.\n * Android resizes the layout viewport itself, so `innerHeight` shrinks too and\n * the difference falls out to zero — which is the right answer there, because\n * the bottom of the window is already above the keyboard.\n *\n * `offsetTop` matters as much as `height`: iOS shifts the visual viewport up\n * as well as shrinking it, and ignoring the shift leaves the sheet floating\n * exactly that far off, with a strip of page showing beneath it.\n */\n\n/**\n * The keyboard slides in, and iOS reports the viewport continuously while it\n * moves. Measured on an iOS 18.7 device over 130ms: 660 -> 323 -> 95 -> 660.\n * Acting on each of those walked the sheet up to 337px, then 565px — off the\n * top of the screen — and back down. Waiting for the numbers to stop moving\n * turns that into one placement.\n */\nconst SETTLE_MS = 140\n\nexport class KeyboardInset {\n private onChange: ((inset: number) => void) | null = null\n private timer: ReturnType<typeof setTimeout> | null = null\n private lastReading: number | null = null\n\n private handler = (): void => {\n if (this.timer !== null) clearTimeout(this.timer)\n this.timer = setTimeout(() => {\n this.timer = null\n const reading = this.current()\n // Take it only once the viewport says the same thing twice. Mid-slide\n // iOS reports values that are not measurements — -122 and 95 on a 660px\n // window were both seen — and a single quiet moment is not proof the\n // animation has finished, only that events paused. Two agreeing reads\n // are, and cost one more settle interval.\n if (reading === this.lastReading) {\n this.lastReading = null\n this.onChange?.(reading)\n return\n }\n this.lastReading = reading\n this.handler()\n }, SETTLE_MS)\n }\n\n current(): number {\n const vp = window.visualViewport\n if (!vp) return 0\n\n // Observed on iOS 18.7 mid-keyboard-animation: height reported as -122.\n // A viewport has no negative height — that is not a small measurement, it\n // is not a measurement. Deriving an inset from it gave 782px on a 660px\n // window, and only the settle window happened to discard it that time.\n if (vp.height <= 0) return 0\n\n const inset = Math.round(window.innerHeight - (vp.height + vp.offsetTop))\n // The keyboard cannot hide more than the window contains.\n return Math.min(Math.max(0, inset), window.innerHeight)\n }\n\n observe(onChange: (inset: number) => void): void {\n const vp = window.visualViewport\n if (!vp) return\n this.onChange = onChange\n vp.addEventListener('resize', this.handler)\n // Scroll fires for the shift-up on iOS, which resize alone does not report.\n vp.addEventListener('scroll', this.handler)\n }\n\n stop(): void {\n const vp = window.visualViewport\n this.onChange = null\n this.lastReading = null\n if (this.timer !== null) {\n clearTimeout(this.timer)\n this.timer = null\n }\n if (!vp) return\n vp.removeEventListener('resize', this.handler)\n vp.removeEventListener('scroll', this.handler)\n }\n}\n","import { KeyboardInset } from './keyboard-inset'\n\n/**\n * Matches the `max-width: 480px` block in widget.css, where every panel that\n * holds a text field stops being a card anchored near the annotation and\n * becomes a bottom sheet.\n */\nexport const SHEET_MAX_WIDTH = 480\n\nexport function isSheetViewport(): boolean {\n return window.innerWidth <= SHEET_MAX_WIDTH\n}\n\n/**\n * Hands a panel's geometry to the stylesheet on narrow screens, and keeps it\n * clear of the software keyboard.\n *\n * Two jobs that have to live together. The panels each position themselves\n * with inline `left`/`top`, which would beat the media query and drag the sheet\n * back into a floating card — so the inline values have to be cleared, not just\n * overridden. And a `position: fixed` sheet gets no help from the browser's\n * scroll-into-view, which only moves elements in the document flow, so the\n * keyboard simply covers it unless something lifts it.\n *\n * Shared by the comment popup, the clarify card, and the draw note bar: all\n * three carry a text field, and all three are the last thing between a reporter\n * and a submitted report.\n */\nexport class SheetLayout {\n private inset = new KeyboardInset()\n\n constructor(private el: HTMLElement) {}\n\n /** Returns true when this took over layout, i.e. the caller should not position. */\n apply(): boolean {\n if (!isSheetViewport()) return false\n\n this.el.style.left = ''\n this.el.style.top = ''\n this.el.style.maxHeight = ''\n this.lift(this.inset.current())\n this.inset.observe((px) => this.lift(px))\n return true\n }\n\n release(): void {\n this.inset.stop()\n }\n\n /**\n * Lifting is clamped so the panel's own top edge stays on screen.\n *\n * The rule is geometric rather than a guess about how tall a keyboard gets:\n * an earlier version refused any inset above 60% of the window, a number\n * calibrated from a single reading on a single device. What actually matters\n * is that the reporter can still see and reach the panel, and that is a\n * question about this panel's height, which is known right here.\n */\n private lift(inset: number): void {\n const headroom = Math.max(0, window.innerHeight - this.el.offsetHeight)\n const applied = Math.min(inset, headroom)\n this.el.style.bottom = applied > 0 ? `${applied}px` : ''\n }\n}\n","import type { ShadowContainer } from '../widget/shadow'\nimport type { I18nMessages } from '../i18n'\nimport { escapeHtml, isImeConfirmKeydown } from '../context/dom-utils'\nimport { SheetLayout } from '../popup/sheet'\n\ninterface DrawNoteBarOptions {\n messages: I18nMessages\n onUndo: () => void\n onRedo: () => void\n onCancel: () => void\n onSubmit: (note: string) => void\n // Fired on every keystroke; the owner uses it to defer heavy capture work\n // until the reporter pauses.\n onActivity?: () => void\n}\n\nexport class DrawNoteBar {\n private el: HTMLDivElement\n private sheet: SheetLayout\n private input!: HTMLInputElement\n private undoBtn!: HTMLButtonElement\n private redoBtn!: HTMLButtonElement\n private submitBtn!: HTMLButtonElement\n private onSubmit: (note: string) => void\n private submitLabel: string\n private messages: I18nMessages\n\n constructor(shadow: ShadowContainer, opts: DrawNoteBarOptions) {\n this.onSubmit = opts.onSubmit\n this.submitLabel = opts.messages.draw.submit\n this.messages = opts.messages\n\n this.el = shadow.el<HTMLDivElement>('div', 'mtb-draw-bar')\n this.el.innerHTML = this.buildHTML(opts.messages)\n shadow.append(this.el)\n // The bar carries a text field and sits flush to the bottom on a phone,\n // which is exactly where the keyboard lands.\n this.sheet = new SheetLayout(this.el)\n this.sheet.apply()\n\n this.input = this.el.querySelector('.mtb-draw-input')!\n this.undoBtn = this.el.querySelector('.mtb-draw-undo')!\n this.redoBtn = this.el.querySelector('.mtb-draw-redo')!\n this.submitBtn = this.el.querySelector('.mtb-draw-submit')!\n\n this.undoBtn.addEventListener('click', opts.onUndo)\n this.redoBtn.addEventListener('click', opts.onRedo)\n this.el.querySelector('.mtb-draw-cancel')!.addEventListener('click', opts.onCancel)\n this.submitBtn.addEventListener('click', () => this.submit())\n if (opts.onActivity) this.input.addEventListener('input', opts.onActivity)\n this.input.addEventListener('keydown', (e: KeyboardEvent) => {\n if (isImeConfirmKeydown(e)) return\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault()\n this.submit()\n }\n })\n\n this.input.focus({ preventScroll: true })\n }\n\n private buildHTML(m: I18nMessages): string {\n return `\n <input class=\"mtb-draw-input\" type=\"text\" placeholder=\"${escapeHtml(m.draw.note_placeholder)}\" />\n <button class=\"mtb-draw-icon-btn mtb-draw-undo\" type=\"button\" title=\"${escapeHtml(m.draw.undo)}\" aria-label=\"${escapeHtml(m.draw.undo)}\" disabled>\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M9 14L4 9l5-5\"/><path d=\"M4 9h11a5 5 0 0 1 0 10h-1\"/></svg>\n </button>\n <button class=\"mtb-draw-icon-btn mtb-draw-redo\" type=\"button\" title=\"${escapeHtml(m.draw.redo)}\" aria-label=\"${escapeHtml(m.draw.redo)}\" disabled>\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M15 14l5-5-5-5\"/><path d=\"M20 9H9a5 5 0 0 0 0 10h1\"/></svg>\n </button>\n <label class=\"mtb-draw-shot\" title=\"${escapeHtml(m.popup.screenshot_locked_hint)}\">\n <input type=\"checkbox\" checked disabled><span>${escapeHtml(m.popup.screenshot_short)}</span>\n </label>\n <span class=\"mtb-draw-sep\"></span>\n <button class=\"mtb-draw-cancel\" type=\"button\">${escapeHtml(m.draw.cancel)}</button>\n <button class=\"mtb-draw-submit\" type=\"button\">${escapeHtml(m.draw.submit)}</button>\n `\n }\n\n setLoading(loading: boolean): void {\n if (loading) {\n this.submitBtn.disabled = true\n this.submitBtn.classList.add('mtb-btn-loading')\n this.submitBtn.textContent = this.messages.popup.sending\n this.input.disabled = true\n } else {\n this.submitBtn.disabled = false\n this.submitBtn.classList.remove('mtb-btn-loading')\n this.submitBtn.textContent = this.submitLabel\n this.input.disabled = false\n }\n }\n\n setUndoRedo(canUndo: boolean, canRedo: boolean): void {\n this.undoBtn.disabled = !canUndo\n this.redoBtn.disabled = !canRedo\n }\n\n private submit(): void {\n this.onSubmit(this.input.value.trim())\n }\n\n destroy(): void {\n this.sheet.release()\n this.el.remove()\n }\n}\n\n","// Shared floating-panel geometry for the chat card and its kin. Panels anchor\n// a fixed panel inside the viewport while keeping clear of the launcher tab;\n// the margin and tab reserve used to be duplicated in each class and had to\n// stay hand-synced, so they are named once here.\n\n/** Gap kept between a floating panel and every viewport edge. */\nexport const VIEWPORT_MARGIN = 12\n/** Horizontal space reserved for the launcher tab on its side of the screen. */\nexport const TAB_WIDTH = 32\n\n/**\n * The horizontal band a floating panel may occupy: the viewport minus the edge\n * margin, minus the launcher tab on whichever side it sits.\n */\nexport interface ViewportFrame {\n vw: number\n vh: number\n minLeft: number\n maxRight: number\n}\n\nexport function measureViewportFrame(tabPosition: 'left' | 'right'): ViewportFrame {\n const vw = window.innerWidth\n const vh = window.innerHeight\n return {\n vw,\n vh,\n minLeft: VIEWPORT_MARGIN + (tabPosition === 'left' ? TAB_WIDTH : 0),\n maxRight: vw - VIEWPORT_MARGIN - (tabPosition === 'right' ? TAB_WIDTH : 0),\n }\n}\n\n/** Widest a panel can be without crossing the edge margin or the launcher tab. */\nexport function panelWidth(preferred: number, frame: ViewportFrame): number {\n return Math.min(preferred, frame.vw - 2 * VIEWPORT_MARGIN - TAB_WIDTH)\n}\n","/**\n * Keeps keyboard focus inside an open card, and remembers who opened it.\n *\n * The cards float above a dimmed host page, but the dim is only visual: Tab\n * happily walks out of the card and into controls the reporter can no longer\n * see or reach with a pointer. Cycling within the card is what the dialog role\n * promises assistive tech, and restoring the opener afterwards puts a keyboard\n * user back where they were instead of at the top of the document.\n *\n * The Tab handler lives on the card element, not on document or window: it can\n * only ever see keys typed while focus is inside the card, and removing the\n * card's own listener is a teardown the host page cannot be stranded by.\n */\nexport interface FocusTrap {\n /**\n * Detach the Tab handler without moving focus — for when the element is\n * handed to a successor (the clarify continuation) that installs its own\n * trap and must not have focus yanked out from under it.\n */\n release(): void\n /** Put focus back on whatever had it when the trap was installed. */\n restoreFocus(): void\n}\n\nconst TABBABLE_SELECTOR = [\n 'button:not([disabled])',\n 'input:not([disabled])',\n 'textarea:not([disabled])',\n 'select:not([disabled])',\n 'a[href]',\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(', ')\n\nexport function trapFocus(card: HTMLElement): FocusTrap {\n // Captured before any card control gets autofocused, so the trap remembers\n // the host page's element, not the card's own field.\n const opener = document.activeElement instanceof HTMLElement ? document.activeElement : null\n\n const handleKeydown = (event: KeyboardEvent): void => {\n if (event.key !== 'Tab') return\n const tabbables = Array.from(card.querySelectorAll<HTMLElement>(TABBABLE_SELECTOR))\n .filter(el => !el.hidden)\n if (tabbables.length === 0) return\n\n const first = tabbables[0]\n const last = tabbables[tabbables.length - 1]\n // Inside a shadow tree the document only reports the host element; the\n // shadow root knows which control actually holds focus. Fall back to the\n // document for environments that leave ShadowRoot.activeElement null.\n const root = card.getRootNode() as Document | ShadowRoot\n const active = root.activeElement ?? document.activeElement\n\n if (event.shiftKey) {\n if (active === first || !card.contains(active)) {\n event.preventDefault()\n last.focus({ preventScroll: true })\n }\n } else if (active === last || !card.contains(active)) {\n event.preventDefault()\n first.focus({ preventScroll: true })\n }\n }\n\n card.addEventListener('keydown', handleKeydown)\n\n return {\n release(): void {\n card.removeEventListener('keydown', handleKeydown)\n },\n restoreFocus(): void {\n // A detached opener would silently drop focus on <body>, so only reach\n // for one that still exists. preventScroll for the same reason every\n // panel uses it: the widget must never scroll the host page.\n if (opener?.isConnected) opener.focus({ preventScroll: true })\n },\n }\n}\n","import type { ShadowContainer } from '../widget/shadow'\nimport type { I18nMessages } from '../i18n'\nimport type { ClarifyStreamResult } from '../api/client'\nimport type { SubmissionSessionResponse, TargetRect } from '../types'\nimport { escapeHtml, isImeConfirmKeydown } from '../context/dom-utils'\nimport { SheetLayout } from './sheet'\nimport { measureViewportFrame, panelWidth, VIEWPORT_MARGIN } from './position'\nimport { trapFocus, type FocusTrap } from './focus-trap'\n\ntype ChatState = 'idle' | 'sending' | 'clarifying' | 'answering' | 'done'\n\ninterface ChatPopupOptions {\n targetName: string\n x: number\n y: number\n targetRect?: TargetRect\n messages: I18nMessages\n position?: 'left' | 'right'\n screenshotEnabled?: boolean\n onScreenshotToggle?: (enabled: boolean) => void\n onSubmit: (description: string) => Promise<SubmissionSessionResponse | null>\n onClarify: (sessionId: string, token: string, onDelta: (text: string) => void, signal?: AbortSignal) => Promise<ClarifyStreamResult>\n onAnswer: (sessionId: string, token: string, message: string) => Promise<unknown>\n onFinalize: () => Promise<void> | void\n onCancel: () => void\n /** A question is on screen: the card now holds an uploaded submission. */\n onConversationOpen?: () => void\n onActivity?: () => void\n recordingSeconds?: number\n // Resolved when the success view renders, not at mount: both depend on the\n // handoff the finalize returns, which does not exist while the reporter is\n // still typing.\n resolveBoardLink?: () => (() => void) | undefined\n resolveEmailCapture?: () => { onSubmit: (email: string) => Promise<boolean> } | undefined\n}\n\nconst CARD_WIDTH = 320\nconst AUTO_CLOSE_MS = 5000\n\nconst BRAND_URL = 'https://makethisbetter.dev'\n\nconst BRAND_ICON = '<svg viewBox=\"0 0 512 512\" fill=\"none\"><defs><linearGradient id=\"mtb-bg\" x1=\".3\" y1=\"0\" x2=\".7\" y2=\"1\"><stop offset=\"0%\" stop-color=\"var(--mtb-chat-logo-start, #10b981)\"/><stop offset=\"100%\" stop-color=\"var(--mtb-chat-logo-end, #059669)\"/></linearGradient></defs><rect width=\"512\" height=\"512\" rx=\"96\" fill=\"url(#mtb-bg)\"/><g transform=\"translate(89,89) scale(13.9)\" stroke=\"var(--mtb-chat-logo-fg, #fff)\" stroke-width=\"2.1\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 2v6h-6\"/><path d=\"M21 13a9 9 0 1 1-3-7.7L21 8\"/><circle cx=\"12\" cy=\"12\" r=\"2.2\" fill=\"var(--mtb-chat-logo-fg, #fff)\" stroke=\"none\"/></g></svg>'\nconst CLOSE_ICON = '<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"currentColor\"><path d=\"M8.9 8l3.3-3.3-.9-.9L8 7.1 4.7 3.8l-.9.9L7.1 8l-3.3 3.3.9.9L8 8.9l3.3 3.3.9-.9z\"/></svg>'\nconst CAMERA_ICON = '<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentColor\"><path d=\"M6.2 2.6h3.6l.9 1.4H14a1 1 0 011 1v7.4a1 1 0 01-1 1H2a1 1 0 01-1-1V5a1 1 0 011-1h3.3l.9-1.4z\"/><circle cx=\"8\" cy=\"9\" r=\"2.5\" class=\"mtb-chat-icon-lens\"/></svg>'\nconst RECORD_ICON = '<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentColor\"><circle cx=\"8\" cy=\"8\" r=\"6.6\"/><polygon points=\"6.4,5.2 11,8 6.4,10.8\" class=\"mtb-chat-icon-lens\"/></svg>'\nconst CHECK_ICON = '<svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path d=\"M8.1 14.6L3.8 10.3l1.5-1.5 2.8 2.8 6.6-6.6 1.5 1.5z\"/></svg>'\n\nexport class ChatPopup {\n private el: HTMLDivElement\n private sheet: SheetLayout\n private focusTrap: FocusTrap\n private messagesEl!: HTMLDivElement\n private composerEl!: HTMLDivElement\n private actionsEl!: HTMLDivElement\n private inputEl!: HTMLTextAreaElement\n private sendBtn!: HTMLButtonElement\n private quickRepliesEl!: HTMLDivElement\n private state: ChatState = 'idle'\n private i18n: I18nMessages\n private opts: ChatPopupOptions\n private autoCloseTimer: ReturnType<typeof setTimeout> | null = null\n private session: SubmissionSessionResponse | null = null\n private clarifyAbort: AbortController | null = null\n\n constructor(shadow: ShadowContainer, opts: ChatPopupOptions) {\n this.opts = opts\n this.i18n = opts.messages\n\n this.el = shadow.el<HTMLDivElement>('div', 'mtb-chat')\n this.el.setAttribute('role', 'dialog')\n this.el.setAttribute('aria-modal', 'true')\n this.el.setAttribute('aria-label', opts.targetName)\n this.el.innerHTML = this.buildHTML()\n shadow.append(this.el)\n this.focusTrap = trapFocus(this.el)\n this.sheet = new SheetLayout(this.el)\n if (!this.sheet.apply()) {\n this.positionCard(opts.x, opts.y, opts.targetRect, opts.position ?? 'right')\n }\n\n this.messagesEl = this.el.querySelector('.mtb-chat-messages')!\n this.composerEl = this.el.querySelector('.mtb-chat-composer')!\n this.actionsEl = this.el.querySelector('.mtb-chat-actions')!\n this.inputEl = this.el.querySelector('.mtb-chat-input')!\n this.sendBtn = this.el.querySelector('.mtb-chat-send')!\n this.quickRepliesEl = this.el.querySelector('.mtb-chat-quick-replies')!\n\n this.bindEvents()\n this.handleEscape = this.handleEscape.bind(this)\n window.addEventListener('keydown', this.handleEscape)\n this.inputEl.focus({ preventScroll: true })\n }\n\n private handleEscape(e: KeyboardEvent): void {\n if (e.key !== 'Escape') return\n e.preventDefault()\n e.stopPropagation()\n if (this.state === 'sending') return\n if (this.state === 'done') {\n this.destroy()\n return\n }\n this.opts.onCancel()\n }\n\n private buildHTML(): string {\n const m = this.i18n\n const opts = this.opts\n const quickOpts = m.popup.quickOptions\n .map(o => `<button class=\"mtb-chat-qr\" type=\"button\" data-quick=\"${escapeHtml(o.label)}\" data-value=\"${escapeHtml(o.value ?? o.label)}\">${escapeHtml(o.emoji)} ${escapeHtml(o.label)}</button>`)\n .join('')\n\n const capture = opts.recordingSeconds != null\n ? `<span class=\"mtb-chat-attachment\" title=\"${escapeHtml(m.popup.recording_locked_hint)}\">${RECORD_ICON}<span class=\"mtb-chat-capture-label\">${Math.round(opts.recordingSeconds)}s</span></span>`\n : opts.onScreenshotToggle\n ? `<button class=\"mtb-chat-screenshot\" type=\"button\" title=\"${escapeHtml(m.popup.screenshot_hint)}\" aria-pressed=\"${opts.screenshotEnabled !== false}\">${CAMERA_ICON}<span class=\"mtb-chat-capture-label\">${escapeHtml(m.popup.screenshot_short)}</span></button>`\n : ''\n\n return `\n <div class=\"mtb-chat-header\">\n <div class=\"mtb-chat-handle\"></div>\n <div class=\"mtb-chat-header-row\">\n <a class=\"mtb-chat-brand\" href=\"${BRAND_URL}\" target=\"_blank\" rel=\"noopener\">\n <span class=\"mtb-chat-logo\">${BRAND_ICON}</span>\n <span class=\"mtb-chat-brand-name\">Make This Better</span>\n </a>\n <span class=\"mtb-chat-header-gap\"></span>\n <button class=\"mtb-chat-close\" type=\"button\" aria-label=\"${escapeHtml(m.popup.close)}\">${CLOSE_ICON}</button>\n </div>\n </div>\n <div class=\"mtb-chat-messages\"></div>\n <div class=\"mtb-chat-quick-replies\">${quickOpts}</div>\n <div class=\"mtb-chat-composer\">\n <textarea class=\"mtb-chat-input\" rows=\"1\" placeholder=\"${escapeHtml(m.popup.placeholder)}\"></textarea>\n <div class=\"mtb-chat-actions\">\n ${capture}\n <span class=\"mtb-chat-actions-gap\"></span>\n <button class=\"mtb-chat-send\" type=\"button\" disabled>${escapeHtml(m.clarify.send)}</button>\n </div>\n </div>\n `\n }\n\n private bindEvents(): void {\n this.el.querySelector('.mtb-chat-close')!.addEventListener('click', () => {\n this.opts.onCancel()\n })\n\n this.quickRepliesEl.querySelectorAll('.mtb-chat-qr').forEach(btn => {\n btn.addEventListener('click', () => {\n const value = (btn as HTMLElement).dataset['value'] ?? (btn as HTMLElement).dataset['quick'] ?? ''\n this.inputEl.value = value\n this.sendMessage(value)\n })\n })\n\n this.inputEl.addEventListener('input', () => {\n this.sendBtn.disabled = !this.inputEl.value.trim()\n this.opts.onActivity?.()\n })\n\n this.inputEl.addEventListener('keydown', (e: KeyboardEvent) => {\n if (isImeConfirmKeydown(e)) return\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault()\n const text = this.inputEl.value.trim()\n if (text) {\n this.sendMessage(text)\n } else if (this.state === 'clarifying') {\n this.skipAndSend()\n }\n }\n })\n\n this.sendBtn.addEventListener('click', () => {\n const text = this.inputEl.value.trim()\n if (text) this.sendMessage(text)\n })\n\n const screenshotBtn = this.el.querySelector('.mtb-chat-screenshot')\n if (screenshotBtn && this.opts.onScreenshotToggle) {\n screenshotBtn.addEventListener('click', () => {\n const pressed = screenshotBtn.getAttribute('aria-pressed') === 'true'\n screenshotBtn.setAttribute('aria-pressed', String(!pressed))\n screenshotBtn.classList.toggle('mtb-chat-screenshot-off', pressed)\n this.opts.onScreenshotToggle!(!pressed)\n })\n }\n }\n\n private async sendMessage(text: string): Promise<void> {\n if (this.state === 'sending' || this.state === 'answering') return\n\n if (this.state === 'clarifying') {\n this.handleAnswer(text)\n return\n }\n\n this.state = 'sending'\n this.appendUserBubble(text)\n this.quickRepliesEl.style.display = 'none'\n this.hideCapture()\n this.showThinking()\n this.setComposerDisabled(true)\n\n let session: SubmissionSessionResponse | null\n try {\n session = await this.opts.onSubmit(text)\n } catch {\n session = null\n }\n\n if (!session) {\n this.removeThinking()\n this.setComposerDisabled(false)\n this.showRetryableError(this.i18n.error.submit, () => {\n this.clearError()\n void this.sendMessage(text)\n })\n this.state = 'idle'\n return\n }\n\n this.session = session\n\n if (!session.ai_clarify_available) {\n this.removeThinking()\n void this.showDone()\n return\n }\n\n let result: ClarifyStreamResult | null = null\n const bufferedDeltas: string[] = []\n this.clarifyAbort = new AbortController()\n try {\n result = await this.opts.onClarify(session.id, session.token, (delta) => {\n bufferedDeltas.push(delta)\n }, this.clarifyAbort.signal)\n } catch {\n this.removeThinking()\n this.showDone()\n return\n }\n\n this.removeThinking()\n\n if (result.done || !result.messages.length) {\n void this.showDone()\n return\n }\n\n if (bufferedDeltas.length) {\n this.startStreamingBubble()\n for (const delta of bufferedDeltas) {\n this.appendStreamDelta(delta)\n await new Promise<void>(r => setTimeout(r, 15))\n }\n this.endStreamingBubble()\n }\n\n const aiMessage = result.messages.find(msg => msg.role === 'assistant')\n if (!bufferedDeltas.length && aiMessage) {\n this.appendAiBubble(aiMessage.content)\n }\n this.renderSuggestions(result.suggestions)\n this.state = 'clarifying'\n this.setComposerDisabled(false)\n this.inputEl.value = ''\n this.inputEl.placeholder = this.i18n.clarify.placeholder\n this.sendBtn.textContent = this.i18n.clarify.submit ?? this.i18n.clarify.send\n this.sendBtn.disabled = true\n this.showSkip()\n this.inputEl.focus({ preventScroll: true })\n this.opts.onConversationOpen?.()\n }\n\n private handleAnswer(text: string): void {\n const session = this.session\n if (!session) return\n this.state = 'done'\n this.appendUserBubble(text)\n this.clearSuggestions()\n this.hideSkip()\n this.setComposerDisabled(true)\n this.renderSuccess()\n // The answer has to reach the server before finalize runs: finalizing\n // deactivates the session, after which the server rejects the answer as\n // terminal. A failed answer still finalizes — losing the follow-up answer\n // must never lose the feedback itself.\n Promise.resolve(this.opts.onAnswer(session.id, session.token, text))\n .catch(() => {})\n .then(() => this.finalizeInBackground())\n }\n\n /**\n * The receipt goes up the moment the reporter is done rather than waiting on\n * the finalize round trip; the hand-off it returns is patched into the card\n * when it lands. A failed finalize replaces this receipt with an in-card\n * retry state so the reporter stays in the conversation they used.\n */\n private showDone(): void {\n this.state = 'done'\n this.hideSkip()\n this.setComposerDisabled(true)\n this.renderSuccess()\n this.finalizeInBackground()\n }\n\n /**\n * The success view takes over everything under the header — the transcript,\n * the quick replies and the composer are all gone, so the card reads as a\n * receipt rather than a conversation the reporter could keep typing into.\n */\n private renderSuccess(): void {\n const m = this.i18n.success\n this.messagesEl.remove()\n this.quickRepliesEl.remove()\n this.composerEl.remove()\n\n const view = this.el.querySelector<HTMLDivElement>('.mtb-chat-success') ?? document.createElement('div')\n view.className = 'mtb-chat-success mtb-chat-success-simple'\n view.replaceChildren()\n\n const icon = document.createElement('div')\n icon.className = 'mtb-chat-success-icon'\n icon.innerHTML = CHECK_ICON\n view.appendChild(icon)\n\n const title = document.createElement('div')\n title.className = 'mtb-chat-success-title'\n title.textContent = this.session?.ai_clarify_available === false ? m.title_no_ai : m.title\n view.appendChild(title)\n\n const link = document.createElement('a')\n link.className = 'mtb-chat-success-link mtb-chat-success-link-pending'\n link.href = '#'\n link.textContent = m.track_feedback\n link.addEventListener('click', (e: MouseEvent) => e.preventDefault())\n view.appendChild(link)\n\n if (!view.parentElement) this.el.appendChild(view)\n this.attachHandoff()\n }\n\n /** Keep a failed background finalize in the conversation the reporter used. */\n showFinalizeFailure(onRetry?: () => void): void {\n if (this.autoCloseTimer) {\n clearTimeout(this.autoCloseTimer)\n this.autoCloseTimer = null\n }\n\n let view = this.el.querySelector<HTMLDivElement>('.mtb-chat-success')\n if (!view) {\n this.messagesEl.remove()\n this.quickRepliesEl.remove()\n this.composerEl.remove()\n view = document.createElement('div')\n this.el.appendChild(view)\n }\n\n view.className = 'mtb-chat-success mtb-chat-finalize-failure'\n view.innerHTML = `\n <div class=\"mtb-chat-success-icon mtb-chat-failure-icon\">!</div>\n <div class=\"mtb-chat-success-title\">${escapeHtml(this.i18n.error.finalize_title)}</div>\n <div class=\"mtb-chat-finalize-message\">${escapeHtml(this.i18n.error.finalize_message)}</div>\n ${onRetry ? `<button class=\"mtb-chat-finalize-retry\" type=\"button\">${escapeHtml(this.i18n.clarify.retry)}</button>` : ''}\n <button class=\"mtb-chat-finalize-close\" type=\"button\">${escapeHtml(this.i18n.success.close)}</button>\n `\n\n if (onRetry) {\n view.querySelector<HTMLButtonElement>('.mtb-chat-finalize-retry')!.addEventListener('click', () => {\n const retry = view.querySelector<HTMLButtonElement>('.mtb-chat-finalize-retry')!\n retry.disabled = true\n retry.innerHTML = '<span class=\"mtb-chat-finalize-spinner\" aria-label=\"Retrying\"></span>'\n onRetry()\n })\n }\n view.querySelector<HTMLButtonElement>('.mtb-chat-finalize-close')!.addEventListener('click', () => this.opts.onCancel())\n }\n\n /** Restore the receipt after a retry reaches the final endpoint. */\n showFinalizeSuccess(): void {\n this.state = 'done'\n this.renderSuccess()\n }\n\n /**\n * The board link and the email form are both built from the hand-off that\n * finalize returns, but the receipt renders the moment the reporter is done\n * rather than waiting on that round trip — so this runs twice: once against\n * whatever is already known, and again when finalize lands.\n */\n private attachHandoff(): void {\n const view = this.el.querySelector<HTMLDivElement>('.mtb-chat-success')\n if (!view) return\n const m = this.i18n.success\n\n const openBoard = this.opts.resolveBoardLink?.()\n const pendingLink = view.querySelector<HTMLAnchorElement>('.mtb-chat-success-link-pending')\n if (openBoard && pendingLink) {\n pendingLink.classList.remove('mtb-chat-success-link-pending')\n pendingLink.addEventListener('click', (e: MouseEvent) => {\n e.preventDefault()\n openBoard()\n })\n }\n\n const emailCapture = this.opts.resolveEmailCapture?.()\n if (emailCapture && !view.querySelector('.mtb-chat-email-card')) {\n if (this.autoCloseTimer) {\n clearTimeout(this.autoCloseTimer)\n this.autoCloseTimer = null\n }\n view.classList.remove('mtb-chat-success-simple')\n view.appendChild(this.buildEmailCard(emailCapture))\n } else if (!emailCapture && !this.autoCloseTimer && !view.querySelector('.mtb-chat-email-card')) {\n this.autoCloseTimer = setTimeout(() => this.destroy(), AUTO_CLOSE_MS)\n }\n }\n\n private finalizeInBackground(): void {\n Promise.resolve(this.opts.onFinalize())\n .then(() => this.attachHandoff())\n .catch(() => {})\n }\n\n private buildEmailCard(capture: { onSubmit: (email: string) => Promise<boolean> }): HTMLDivElement {\n const m = this.i18n.success\n const card = document.createElement('div')\n card.className = 'mtb-chat-email-card'\n\n const row = document.createElement('div')\n row.className = 'mtb-chat-email-row'\n\n const field = document.createElement('div')\n field.className = 'mtb-chat-email-field'\n const input = document.createElement('input')\n input.className = 'mtb-chat-email-input'\n input.type = 'email'\n input.placeholder = m.email_cta\n field.appendChild(input)\n\n const btn = document.createElement('button')\n btn.className = 'mtb-chat-email-submit'\n btn.type = 'button'\n btn.textContent = m.notify\n btn.disabled = true\n\n const submitEmail = async () => {\n const email = input.value.trim()\n if (!email) return\n btn.disabled = true\n input.disabled = true\n const saved = await capture.onSubmit(email)\n if (saved) {\n const note = document.createElement('div')\n note.className = 'mtb-chat-email-note'\n note.textContent = m.email_saved\n card.replaceChildren(note)\n this.autoCloseTimer = setTimeout(() => this.destroy(), AUTO_CLOSE_MS)\n return\n }\n input.disabled = false\n btn.disabled = false\n const error = card.querySelector('.mtb-chat-email-error') ?? document.createElement('div')\n error.className = 'mtb-chat-email-error'\n error.textContent = m.email_error\n card.appendChild(error)\n }\n\n input.addEventListener('input', () => { btn.disabled = !input.value.trim() })\n input.addEventListener('keydown', (e: KeyboardEvent) => {\n if (e.key === 'Enter' && input.value.trim()) void submitEmail()\n })\n btn.addEventListener('click', () => void submitEmail())\n\n row.appendChild(field)\n row.appendChild(btn)\n card.appendChild(row)\n return card\n }\n\n private appendUserBubble(text: string): void {\n const bubble = document.createElement('div')\n bubble.className = 'mtb-chat-bubble mtb-chat-bubble-user'\n bubble.textContent = text\n this.messagesEl.appendChild(bubble)\n this.messagesEl.scrollTop = this.messagesEl.scrollHeight\n this.inputEl.value = ''\n this.sendBtn.disabled = true\n }\n\n private streamingBubbleEl: HTMLDivElement | null = null\n\n private startStreamingBubble(): void {\n const row = document.createElement('div')\n row.className = 'mtb-chat-ai-row'\n row.innerHTML = `<div class=\"mtb-chat-avatar\">${BRAND_ICON}</div><div class=\"mtb-chat-bubble mtb-chat-bubble-ai\"></div>`\n this.messagesEl.appendChild(row)\n this.streamingBubbleEl = row.querySelector('.mtb-chat-bubble-ai')!\n }\n\n private appendStreamDelta(text: string): void {\n if (!this.streamingBubbleEl) return\n this.streamingBubbleEl.textContent = (this.streamingBubbleEl.textContent ?? '') + text\n this.messagesEl.scrollTop = this.messagesEl.scrollHeight\n }\n\n private endStreamingBubble(): void {\n this.streamingBubbleEl = null\n }\n\n private appendAiBubble(text: string): void {\n const row = document.createElement('div')\n row.className = 'mtb-chat-ai-row'\n row.innerHTML = `<div class=\"mtb-chat-avatar\">${BRAND_ICON}</div><div class=\"mtb-chat-bubble mtb-chat-bubble-ai\"></div>`\n row.querySelector('.mtb-chat-bubble-ai')!.textContent = text\n this.messagesEl.appendChild(row)\n this.messagesEl.scrollTop = this.messagesEl.scrollHeight\n }\n\n private renderSuggestions(suggestions: string[] | undefined): void {\n this.clearSuggestions()\n if (!suggestions?.length) return\n\n const row = document.createElement('div')\n row.className = 'mtb-chat-suggestions'\n for (const suggestion of suggestions) {\n const pill = document.createElement('button')\n pill.className = 'mtb-chat-sg'\n pill.type = 'button'\n pill.textContent = suggestion\n pill.addEventListener('click', () => {\n this.inputEl.value = suggestion\n this.sendBtn.disabled = false\n this.inputEl.focus({ preventScroll: true })\n })\n row.appendChild(pill)\n }\n this.messagesEl.appendChild(row)\n this.messagesEl.scrollTop = this.messagesEl.scrollHeight\n }\n\n private clearSuggestions(): void {\n this.messagesEl.querySelector('.mtb-chat-suggestions')?.remove()\n }\n\n private showThinking(): void {\n if (this.messagesEl.querySelector('.mtb-chat-thinking')) return\n const row = document.createElement('div')\n row.className = 'mtb-chat-ai-row mtb-chat-thinking'\n row.innerHTML = `<div class=\"mtb-chat-avatar\">${BRAND_ICON}</div><div class=\"mtb-chat-dots\"><span></span><span></span><span></span></div>`\n this.messagesEl.appendChild(row)\n this.messagesEl.scrollTop = this.messagesEl.scrollHeight\n }\n\n private removeThinking(): void {\n this.messagesEl.querySelector('.mtb-chat-thinking')?.remove()\n }\n\n private showRetryableError(text: string, onRetry: () => void): void {\n this.clearError()\n const row = document.createElement('div')\n row.className = 'mtb-chat-error-row'\n const msg = document.createElement('span')\n msg.className = 'mtb-chat-error-text'\n msg.textContent = text\n const retry = document.createElement('button')\n retry.className = 'mtb-chat-error-retry'\n retry.type = 'button'\n retry.textContent = this.i18n.clarify.retry\n retry.addEventListener('click', onRetry)\n row.appendChild(msg)\n row.appendChild(retry)\n this.messagesEl.appendChild(row)\n this.messagesEl.scrollTop = this.messagesEl.scrollHeight\n }\n\n private clearError(): void {\n this.messagesEl.querySelectorAll('.mtb-chat-error-row').forEach(el => el.remove())\n }\n\n private showSkip(): void {\n if (this.actionsEl.querySelector('.mtb-chat-skip')) return\n const skip = document.createElement('button')\n skip.className = 'mtb-chat-skip'\n skip.type = 'button'\n skip.title = this.i18n.clarify.skip\n skip.textContent = this.i18n.clarify.skip_short\n skip.addEventListener('click', () => this.skipAndSend())\n this.actionsEl.insertBefore(skip, this.sendBtn)\n }\n\n private skipAndSend(): void {\n this.hideSkip()\n this.state = 'done'\n this.setComposerDisabled(true)\n this.renderSuccess()\n this.finalizeInBackground()\n }\n\n private hideSkip(): void {\n this.actionsEl?.querySelector('.mtb-chat-skip')?.remove()\n }\n\n private hideCapture(): void {\n this.actionsEl.querySelector('.mtb-chat-screenshot')?.remove()\n this.actionsEl.querySelector('.mtb-chat-attachment')?.remove()\n }\n\n private setComposerDisabled(disabled: boolean): void {\n this.inputEl.disabled = disabled\n this.sendBtn.disabled = disabled\n this.composerEl.classList.toggle('mtb-chat-composer-disabled', disabled)\n }\n\n private positionCard(x: number, y: number, targetRect: TargetRect | undefined, tabPosition: 'left' | 'right'): void {\n const frame = measureViewportFrame(tabPosition)\n const { vw, vh, minLeft, maxRight } = frame\n const margin = VIEWPORT_MARGIN\n const gap = 8\n const cardW = panelWidth(CARD_WIDTH, frame)\n const measured = this.el.offsetHeight || 280\n const cardH = measured + 80\n\n let left: number\n let top: number\n\n const anchorIsInformative =\n targetRect && targetRect.width < vw * 0.6 && targetRect.height < vh * 0.6\n\n if (targetRect && anchorIsInformative) {\n left = targetRect.left\n top = targetRect.bottom + gap\n if (top + cardH > vh - margin) top = targetRect.top - cardH - gap\n if (top < margin) top = Math.min(targetRect.bottom + gap, vh - cardH - margin)\n } else {\n left = x + 12\n top = y + 12\n }\n\n if (left + cardW > maxRight) left = maxRight - cardW\n if (left < minLeft) left = minLeft\n if (top < margin) top = margin\n\n if (top + cardH > vh - margin) top = vh - cardH - margin\n if (top < margin) top = margin\n\n this.el.style.left = `${left}px`\n this.el.style.top = `${top}px`\n this.el.style.maxHeight = `${vh - top - margin}px`\n }\n\n submit(text: string): void {\n void this.sendMessage(text)\n }\n\n /** Which submission this card holds — null while the reporter is still typing. */\n sessionId(): string | null {\n return this.session?.id ?? null\n }\n\n destroy(): void {\n this.clarifyAbort?.abort()\n if (this.autoCloseTimer) clearTimeout(this.autoCloseTimer)\n window.removeEventListener('keydown', this.handleEscape)\n this.sheet.release()\n this.focusTrap.release()\n this.el.remove()\n this.focusTrap.restoreFocus()\n }\n}\n","import type { ShadowContainer } from '../widget/shadow'\nimport type { I18nMessages } from '../i18n'\nimport { trapFocus, type FocusTrap } from './focus-trap'\n\nconst AUTO_CLOSE_MS = 5000\n\nconst CHECK_ICON = '<svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path d=\"M8.1 14.6L3.8 10.3l1.5-1.5 2.8 2.8 6.6-6.6 1.5 1.5z\"/></svg>'\n\nexport interface ReceiptCardOptions {\n skipFollowup?: boolean\n onViewFeedback?: () => void\n}\n\n/**\n * The standalone receipt for a submission whose chat card is already gone —\n * today that is one path: a background finalize failed, its failure card was\n * dismissed by the retry, and the retry then succeeded. Renders with the chat\n * card's own receipt classes so the two ways of saying \"sent\" stay one design.\n */\nexport class ReceiptCard {\n private el: HTMLDivElement\n private focusTrap: FocusTrap\n private autoCloseTimer: ReturnType<typeof setTimeout>\n\n constructor(shadow: ShadowContainer, messages: I18nMessages, onClose: () => void, options?: ReceiptCardOptions) {\n const m = messages.success\n const title = options?.skipFollowup ? m.title_no_ai : m.title\n\n this.el = shadow.el<HTMLDivElement>('div', 'mtb-chat mtb-chat-receipt')\n this.el.setAttribute('role', 'dialog')\n this.el.setAttribute('aria-modal', 'true')\n this.el.setAttribute('aria-label', title)\n\n const view = document.createElement('div')\n view.className = 'mtb-chat-success mtb-chat-success-simple'\n\n const icon = document.createElement('div')\n icon.className = 'mtb-chat-success-icon'\n icon.innerHTML = CHECK_ICON\n view.appendChild(icon)\n\n const titleEl = document.createElement('div')\n titleEl.className = 'mtb-chat-success-title'\n titleEl.textContent = title\n view.appendChild(titleEl)\n\n if (options?.onViewFeedback) {\n const link = document.createElement('a')\n link.className = 'mtb-chat-success-link'\n link.href = '#'\n link.textContent = m.track_feedback\n link.addEventListener('click', (e: MouseEvent) => {\n e.preventDefault()\n options.onViewFeedback!()\n })\n view.appendChild(link)\n }\n\n this.el.appendChild(view)\n shadow.append(this.el)\n this.focusTrap = trapFocus(this.el)\n this.autoCloseTimer = setTimeout(onClose, AUTO_CLOSE_MS)\n }\n\n destroy(): void {\n clearTimeout(this.autoCloseTimer)\n this.focusTrap.release()\n this.el.remove()\n this.focusTrap.restoreFocus()\n }\n}\n","export const FILTERED_VALUE = '[Filtered]'\n\nconst PRIVATE_KEY_PATTERN = /-----BEGIN ((?:[A-Z0-9]+ )*PRIVATE KEY)-----[\\s\\S]*?-----END \\1-----/g\nconst JWT_PATTERN = /\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\b/g\nconst API_KEY_PATTERNS = [\n /\\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\\b/g,\n /\\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{20,}\\b/g,\n /\\bgh[pousr]_[A-Za-z0-9]{30,}\\b/g,\n /\\bAKIA[0-9A-Z]{16}\\b/g,\n /\\bxox[baprs]-[A-Za-z0-9-]{20,}\\b/g,\n]\nconst CARD_CANDIDATE_PATTERN = /\\b(?:\\d[ -]?){12,18}\\d\\b/g\nconst HTTP_URL_PATTERN = /\\bhttps?:\\/\\/[^\\s<>\"']+/gi\nconst FIELD_SEMANTIC_KEYS = new Set(['type', 'autocomplete', 'name', 'id', 'aria_label', 'placeholder'])\nconst VALUE_ATTRIBUTE_KEYS = new Set(['value', 'content'])\nconst SENSITIVE_INPUT_TYPES = new Set(['password', 'hidden', 'file'])\nconst SENSITIVE_AUTOCOMPLETE = new Set([\n 'current_password',\n 'new_password',\n 'one_time_code',\n 'cc_number',\n 'cc_csc',\n 'cc_exp',\n 'cc_exp_month',\n 'cc_exp_year',\n])\nconst SENSITIVE_KEY_PATTERN = /(?:^|_)(?:password|passwd|passcode|card_(?:number|no)(?:_[0-9]+)?|credit_card|cc_(?:number|num|csc|cvv|cvc|exp|expiry)|cvv|cvc|csc|security_code|one_time_(?:code|password)|otp(?:_code)?|verification_code|auth(?:entication)?_token|access_token|refresh_token|id_token|token|api_key|client_secret|secret|authorization|cookie|set_cookie|credential|private_key|hidden_value|file_name|filename)(?:$|_(?:value|field|input|confirmation|confirm|header|data|pem)$)/\n\nexport function filterSensitiveText(value: string): string {\n let filtered = value\n .replace(PRIVATE_KEY_PATTERN, FILTERED_VALUE)\n .replace(JWT_PATTERN, FILTERED_VALUE)\n\n for (const pattern of API_KEY_PATTERNS) {\n filtered = filtered.replace(pattern, FILTERED_VALUE)\n }\n\n filtered = filtered.replace(CARD_CANDIDATE_PATTERN, candidate => (\n passesLuhn(candidate) ? FILTERED_VALUE : candidate\n ))\n\n return filtered.replace(HTTP_URL_PATTERN, candidate => sanitizeUrl(candidate) || FILTERED_VALUE)\n}\n\nexport function sanitizeUrl(value: string): string {\n try {\n const url = new URL(value)\n return `${url.origin}${url.pathname}`\n } catch {\n return ''\n }\n}\n\nexport function filterSensitiveValue<T>(value: T, key?: string): T {\n if (key && isSensitiveKey(key) && value != null) return FILTERED_VALUE as T\n if (typeof value === 'string') return filterSensitiveText(value) as T\n if (Array.isArray(value)) return value.map(item => filterSensitiveValue(item)) as T\n if (value && typeof value === 'object') {\n const sensitiveField = isSensitiveFieldAttributes(value as Record<string, unknown>)\n const filtered = Object.fromEntries(\n Object.entries(value).map(([nestedKey, item]) => {\n const filteredItem = sensitiveField && VALUE_ATTRIBUTE_KEYS.has(normalizeIdentifier(nestedKey)) && item != null\n ? FILTERED_VALUE\n : filterSensitiveValue(item, nestedKey)\n return [filterSensitiveText(nestedKey), filteredItem]\n }),\n )\n return filtered as T\n }\n return value\n}\n\nexport function isSensitiveKey(value: string): boolean {\n return SENSITIVE_KEY_PATTERN.test(normalizeIdentifier(value))\n}\n\nexport function isSensitiveFieldAttributes(attributes: Record<string, unknown>): boolean {\n return Object.entries(attributes).some(([key, value]) => {\n if (typeof value !== 'string') return false\n\n const normalizedKey = normalizeIdentifier(key)\n if (!FIELD_SEMANTIC_KEYS.has(normalizedKey)) return false\n if (normalizedKey === 'type') return SENSITIVE_INPUT_TYPES.has(normalizeIdentifier(value))\n if (normalizedKey === 'autocomplete') {\n return value.split(/\\s+/).some(token => SENSITIVE_AUTOCOMPLETE.has(normalizeIdentifier(token)))\n }\n return isSensitiveKey(value)\n })\n}\n\nfunction normalizeIdentifier(value: string): string {\n return value\n .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '_')\n .replace(/^_+|_+$/g, '')\n}\n\nfunction passesLuhn(candidate: string): boolean {\n const digits = candidate.replace(/\\D/g, '')\n if (digits.length < 13 || digits.length > 19) return false\n\n let sum = 0\n let doubleDigit = false\n for (let index = digits.length - 1; index >= 0; index -= 1) {\n let digit = Number(digits[index])\n if (doubleDigit) {\n digit *= 2\n if (digit > 9) digit -= 9\n }\n sum += digit\n doubleDigit = !doubleDigit\n }\n return sum % 10 === 0\n}\n","import { FILTERED_VALUE, filterSensitiveText, isSensitiveFieldAttributes } from './sanitize'\n\nexport function isSensitiveField(element: Element): boolean {\n return isSensitiveFieldAttributes({\n type: element.getAttribute('type'),\n autocomplete: element.getAttribute('autocomplete'),\n name: element.getAttribute('name'),\n id: element.id,\n 'aria-label': element.getAttribute('aria-label'),\n placeholder: element.getAttribute('placeholder'),\n })\n}\n\nexport function filterInputValue(value: string, element: Element): string {\n if (!value) return value\n if (isSensitiveField(element)) return FILTERED_VALUE\n return filterSensitiveText(value)\n}\n","import { filterSensitiveText } from './sanitize'\n\nconst MAX_GROUPED_TEXT_LENGTH = 512\ntype SensitiveTextState = 'none' | 'sensitive' | 'contains-sensitive-child' | 'too-large'\nexport type SensitiveTextContext = WeakMap<Element, SensitiveTextState>\n\nexport function createSensitiveTextContext(): SensitiveTextContext {\n return new WeakMap()\n}\n\nexport function hasSensitiveTextContext(node: Node, context: SensitiveTextContext): boolean {\n let element = node.parentElement\n while (element && element !== document.body && element !== document.documentElement) {\n const state = sensitiveTextState(element, context)\n if (state === 'sensitive') return true\n if (state === 'contains-sensitive-child' || state === 'too-large') return false\n element = element.parentElement\n }\n return false\n}\n\nfunction sensitiveTextState(element: Element, context: SensitiveTextContext): SensitiveTextState {\n const cached = context.get(element)\n if (cached) return cached\n\n const text = element.textContent ?? ''\n let state: SensitiveTextState = 'none'\n if (text.length > MAX_GROUPED_TEXT_LENGTH) {\n state = 'too-large'\n } else if (filterSensitiveText(text) !== text) {\n const childContainsSensitiveText = Array.from(element.children).some((child) => {\n const childState = sensitiveTextState(child, context)\n return childState === 'sensitive' || childState === 'contains-sensitive-child'\n })\n state = childContainsSensitiveText ? 'contains-sensitive-child' : 'sensitive'\n }\n context.set(element, state)\n return state\n}\n","export function loadImage(dataUrl: string): Promise<HTMLImageElement | null> {\n return new Promise(resolve => {\n const image = new Image()\n image.onload = () => resolve(image)\n image.onerror = () => resolve(null)\n image.src = dataUrl\n })\n}\n","// The draw layer only ever emits M/L polyline commands (see buildPathD in\n// annotate/draw.ts); anything else in the string is ignored.\nexport function parsePathPolylines(drawPath: string): Array<Array<[number, number]>> {\n const polylines: Array<Array<[number, number]>> = []\n let current: Array<[number, number]> | null = null\n const tokens = drawPath.match(/[ML]\\s*[-\\d.]+\\s+[-\\d.]+/g) ?? []\n for (const token of tokens) {\n const [x, y] = token.slice(1).trim().split(/\\s+/).map(Number)\n if (Number.isNaN(x) || Number.isNaN(y)) continue\n if (token[0] === 'M') {\n current = [[x, y]]\n polylines.push(current)\n } else if (current) {\n current.push([x, y])\n }\n }\n return polylines\n}\n\n// Rebuilt from the parsed polylines so this module stays the single reader of\n// the path grammar. Tokens the parser ignores are dropped, which is safe\n// because every consumer reads the translated path through that same parser.\nexport function translatePathD(drawPath: string, dx: number, dy: number): string {\n return parsePathPolylines(drawPath)\n .map(points =>\n points.map(([x, y], index) => `${index === 0 ? 'M' : 'L'} ${x + dx} ${y + dy}`).join(' '),\n )\n .join(' ')\n}\n","import type { Annotation, WidgetBrandColors } from '../types'\nimport { BLOCK_CLASS, MASK_TEXT_CLASS } from '../privacy/dom'\nimport { WIDGET_HOST_ID } from '../context/dom-utils'\nimport { filterInputValue, isSensitiveField } from '../privacy/fields'\nimport { filterSensitiveText } from '../privacy/sanitize'\nimport { createSensitiveTextContext, hasSensitiveTextContext } from '../privacy/text-context'\nimport { BRAND_COLOR } from '../styles'\nimport { anchorScreenshotOffset, captureTransform, nestedScrollOffset, parentElement, rectToCanvas, toCanvasX, toCanvasY, viewportCaptureRect } from './geometry'\nimport type { CaptureTransform, Rect, ScrollOffset } from './geometry'\nimport { loadImage } from './image'\nimport { parsePathPolylines, translatePathD } from './svg-path'\n\n// Capture at CSS pixels (pixelRatio 1) as JPEG to keep the upload small: on\n// retina screens the default devicePixelRatio would quadruple the pixel count,\n// and the model downscales past ~1568px anyway. The capture covers only the\n// viewport; capturePixelRatio stays as a guard for a viewport that would ever\n// exceed the browser's canvas ceiling.\n//\n// Annotations are baked onto the captured bitmap with canvas 2D afterwards —\n// never mounted into the live page — so the capture leaves the page visually\n// untouched no matter how long the DOM snapshot takes.\nexport interface BaseScreenshot {\n dataUrl: string\n metrics: Rect\n redactions: Rect[]\n /**\n * Where the page was scrolled when the bitmap was measured. The bake happens\n * later — the reporter is still typing — so it cannot read this itself, and\n * annotations that carry their own anchor need it to come back to this\n * capture's frame.\n */\n pageScroll: ScrollOffset\n}\n\n// html-to-image is the largest dependency in the bundle, and most sessions\n// never open the widget, let alone capture a screenshot. Loading it lazily\n// keeps it out of the first-injection payload for ESM/CJS consumers (the IIFE\n// build inlines the import back into its single file). warmFontEmbedCss is the\n// first caller on every capture path, so the popup-open warm-up that prefetches\n// the font CSS also prefetches this module and submit latency does not regress.\ntype HtmlToImageModule = typeof import('html-to-image')\n\nlet htmlToImageLoaded: Promise<HtmlToImageModule> | null = null\n\nfunction loadHtmlToImage(): Promise<HtmlToImageModule> {\n if (htmlToImageLoaded) return htmlToImageLoaded\n // A rejection must not stay cached: one flaky-network failure would otherwise\n // make every later capture fail instantly until the page reloads. Same\n // reset-on-failure as record/session.ts loadRrweb.\n htmlToImageLoaded = import('html-to-image').catch((err) => {\n htmlToImageLoaded = null\n throw err\n })\n return htmlToImageLoaded\n}\n\n// Embedding webfonts is the expensive half of a capture: html-to-image walks\n// every stylesheet and fetches each referenced font file — a hundred-odd\n// requests on a Google-Fonts page — on every capture. The font set does not\n// change while the page lives, so compute the embed CSS once and reuse it.\nlet fontEmbedCss: string | undefined\nlet fontEmbedCssRequest: Promise<string | undefined> | null = null\n\nexport function warmFontEmbedCss(): Promise<string | undefined> {\n if (fontEmbedCss !== undefined) return Promise.resolve(fontEmbedCss)\n // On failure fall back to undefined so toJpeg embeds fonts itself, and allow\n // a later capture to try warming again.\n fontEmbedCssRequest ??= loadHtmlToImage()\n .then(({ getFontEmbedCSS }) => getFontEmbedCSS(document.body))\n .then((css) => {\n fontEmbedCss = css\n return css\n })\n .catch(() => {\n fontEmbedCssRequest = null\n return undefined\n })\n return fontEmbedCssRequest\n}\n\n// The rasterized page without annotations. Annotations are baked afterwards\n// from data, so this half can run early — during a pause in the reporter's\n// typing — and the submit path only pays for the cheap bake.\n//\n// Everything below this margin past the viewport is culled from the clone and\n// never rasterized. A full-page capture rasterized a tall page — sometimes 10x\n// the pixels and DOM nodes — only for the bake to crop it back to roughly a\n// viewport, and that wasted work is what froze typing on heavy pages.\n//\n// Only content BELOW the viewport can go: removing in-flow content above it\n// collapses the clone's layout upward and breaks the scroll alignment the\n// transform relies on (verified: culling above + translate rendered an\n// entirely blank capture).\nconst CULL_MARGIN_PX = 200\n// The browser cannot read cross-origin images whose server omits CORS headers.\n// Replace only that resource in html-to-image's clone so one inaccessible image\n// cannot discard the rest of the privacy-filtered screenshot.\nconst TRANSPARENT_IMAGE_PLACEHOLDER = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'\n\nexport type ScreenshotResourceUrlResolver = (\n resourceUrl: string,\n contentType?: string,\n) => string | undefined | Promise<string | undefined>\n\nexport async function captureBaseScreenshot(\n resourceUrlResolver?: ScreenshotResourceUrlResolver,\n signal?: AbortSignal,\n): Promise<BaseScreenshot | null> {\n try {\n if (signal?.aborted) return null\n // The module load rides alongside the warm-up (which itself needs the\n // module); a load failure rejects here and fails the capture closed to\n // null, same as any other capture error.\n const [{ toJpeg }, fontEmbedCSS] = await Promise.all([loadHtmlToImage(), warmFontEmbedCss()])\n // Measure only after the font warm-up settles: a cold warm-up is long\n // enough (a hundred-odd requests) for late-arriving fonts to reflow the\n // page, and a cover measured before that shift lands on the wrong pixels\n // with no fail-closed signal.\n const bodyRect = document.body.getBoundingClientRect()\n const metrics = viewportCaptureRect()\n const pageScroll = { x: window.scrollX, y: window.scrollY }\n const redactions = collectPrivacyRedactions(document.body)\n const dataUrl = await toJpeg(document.body, {\n quality: 0.85,\n imagePlaceholder: TRANSPARENT_IMAGE_PLACEHOLDER,\n resourceUrlResolver,\n fetchRequestInit: signal ? { signal } : undefined,\n width: metrics.width,\n height: metrics.height,\n pixelRatio: capturePixelRatio(metrics),\n backgroundColor: '#ffffff',\n skipFonts: false,\n skipAutoScale: false,\n fontEmbedCSS,\n style: {\n // The clone lays out from the document top; shifting it by the body's\n // current viewport offset puts the scrolled-to region at the canvas\n // origin, so live viewport measurements map onto the bitmap 1:1.\n transform: `translate(${bodyRect.left}px, ${bodyRect.top}px)`,\n transformOrigin: 'top left',\n },\n filter: (node) => {\n if (node instanceof HTMLElement && node.id === WIDGET_HOST_ID) return false\n if (!(node instanceof Element)) return true\n const rect = node.getBoundingClientRect()\n // Zero-sized nodes carry no pixels but often carry styles or heads of\n // rendered subtrees (style/script/positioned wrappers) — keep them.\n if (rect.width === 0 && rect.height === 0) return true\n // The clone walk is asynchronous, so the page can scroll while it\n // runs — and a live rect.top is measured against wherever the page is\n // by then. Re-expressing it against the scroll this capture was framed\n // at keeps the cull decision stable however late the walk reads it.\n const topInCapture = rect.top + window.scrollY - pageScroll.y\n return topInCapture <= metrics.height + CULL_MARGIN_PX\n },\n })\n if (signal?.aborted) return null\n return { dataUrl, metrics, redactions, pageScroll }\n } catch {\n return null\n }\n}\n\nexport async function bakeAnnotatedScreenshot(\n base: BaseScreenshot,\n annotations: Annotation[] = [],\n brandColors?: WidgetBrandColors,\n): Promise<Blob | null> {\n try {\n const screenshotAnnotations = annotations.map(\n annotation => translateAnnotationForScreenshot(annotation, base.pageScroll),\n )\n return await bakeScreenshot(base.dataUrl, screenshotAnnotations, base.metrics, base.redactions, brandColors)\n } catch {\n return null\n }\n}\n\nexport async function captureScreenshot(\n annotations: Annotation[] = [],\n brandColors?: WidgetBrandColors,\n): Promise<Blob | null> {\n const base = await captureBaseScreenshot()\n if (!base) return null\n return bakeAnnotatedScreenshot(base, annotations, brandColors)\n}\n\n// html-to-image clones an element's children at their unscrolled positions.\n// Annotations, however, are recorded in viewport coordinates. Translate the\n// screenshot-only copy so it remains attached to its target in that clone;\n// the original annotation is still sent to the API unchanged.\n//\n// The capture is one viewport — the one on screen at capture time, which the\n// prime-at-annotation flow makes the viewport the first annotation was made in.\n// An annotation whose anchor puts it outside that rect simply is not in the\n// shot: its strokes land off-canvas and the canvas clips them silently. That is\n// the honest picture (the marked content was not captured), and the wire\n// annotation data still carries the mark for the board.\nfunction translateAnnotationForScreenshot(annotation: Annotation, pageScroll: ScrollOffset): Annotation {\n const offset = screenshotOffset(annotation, pageScroll)\n if (offset.x === 0 && offset.y === 0) return annotation\n\n return {\n ...annotation,\n x: annotation.x + offset.x,\n y: annotation.y + offset.y,\n targetRect: annotation.targetRect && {\n ...annotation.targetRect,\n top: annotation.targetRect.top + offset.y,\n left: annotation.targetRect.left + offset.x,\n bottom: annotation.targetRect.bottom + offset.y,\n },\n drawPath: annotation.drawPath && translatePathD(annotation.drawPath, offset.x, offset.y),\n }\n}\n\n/**\n * How far to move an annotation to land where the capture rendered its target.\n *\n * An annotation that recorded its own anchor already knows where it belongs in\n * clone space, so all that is left is coming back to the frame this capture was\n * measured in — no live DOM read, and therefore nothing that a scroll between\n * the annotation and the capture can spoil.\n *\n * Without that anchor (an older client, or replayed data) the only thing left to\n * do is measure the target now and hope nothing moved, which is what every\n * annotation used to do.\n */\nfunction screenshotOffset(annotation: Annotation, pageScroll: ScrollOffset): ScrollOffset {\n if (annotation.captureOffsetX !== undefined && annotation.captureOffsetY !== undefined) {\n return anchorScreenshotOffset(\n { x: annotation.captureOffsetX, y: annotation.captureOffsetY },\n pageScroll,\n )\n }\n\n return nestedScrollOffset(findAnnotationTarget(annotation))\n}\n\nfunction findAnnotationTarget(annotation: Annotation): Element | null {\n if (annotation.targetSelector) {\n try {\n const target = document.querySelector(annotation.targetSelector)\n if (target) return target\n } catch {\n // Selectors are captured from the page, so a later DOM change can make\n // one invalid. Fall back to the element beneath the annotation instead.\n }\n }\n\n return document.elementsFromPoint?.(annotation.x, annotation.y)[0] ?? null\n}\n\nasync function bakeScreenshot(\n dataUrl: string,\n annotations: Annotation[],\n captureMetrics: Rect,\n redactions: Rect[],\n brandColors?: WidgetBrandColors,\n): Promise<Blob | null> {\n const canvas = document.createElement('canvas')\n const ctx = canvas.getContext('2d')\n if (!ctx) return null\n\n const image = await loadImage(dataUrl)\n if (!image) return null\n\n canvas.width = image.naturalWidth || window.innerWidth\n canvas.height = image.naturalHeight || window.innerHeight\n ctx.drawImage(image, 0, 0)\n const transform = captureTransform(captureMetrics, canvas.width, canvas.height)\n\n ctx.fillStyle = '#e5e7eb'\n for (const rect of redactions) {\n const cover = rectToCanvas(transform, rect)\n ctx.fillRect(cover.left, cover.top, cover.width, cover.height)\n }\n\n for (const annotation of annotations) {\n if (annotation.type === 'draw' && annotation.drawPath) {\n drawStroke(ctx, annotation.drawPath, transform, brandColors?.primary)\n } else if (annotation.type === 'pin') {\n drawPin(\n ctx,\n toCanvasX(transform, annotation.x),\n toCanvasY(transform, annotation.y),\n brandColors,\n )\n }\n }\n\n // No crop step: the capture rect is the viewport, which is exactly the\n // deliverable. Cropping this bitmap could only ever remove captured pixels —\n // and cropping content above the visible band was the bug class that shifted\n // the remaining content up and blanked the shot.\n return canvasToJpegBlob(canvas)\n}\n\nfunction collectPrivacyRedactions(root: HTMLElement): Rect[] {\n const redactions: Rect[] = []\n for (const element of elementsIncludingRoot(root, `.${BLOCK_CLASS}`)) {\n addRedaction(redactions, element.getBoundingClientRect(), cloneScrollOffset(element))\n }\n\n for (const element of root.querySelectorAll('input, textarea, select')) {\n if (isWidgetOrBlocked(element)) continue\n const value = element instanceof HTMLInputElement\n || element instanceof HTMLTextAreaElement\n || element instanceof HTMLSelectElement\n ? element.value\n : ''\n const isMasked = element.closest(`.${MASK_TEXT_CLASS}`) !== null\n const hasSensitiveValue = isSensitiveField(element) || filterInputValue(value, element) !== value\n if (!isMasked && !hasSensitiveValue) continue\n addRedaction(redactions, element.getBoundingClientRect(), cloneScrollOffset(element), REDACTION_PADDING_PX)\n }\n\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT)\n const textContext = createSensitiveTextContext()\n let node = walker.nextNode()\n while (node) {\n const parent = node.parentElement\n const text = node.textContent ?? ''\n const isMasked = parent?.closest(`.${MASK_TEXT_CLASS}`) !== null\n const hasSensitiveText = filterSensitiveText(text) !== text || hasSensitiveTextContext(node, textContext)\n if (parent && !isWidgetOrBlocked(parent) && (isMasked || hasSensitiveText)) {\n // Text is the parent's content, so the parent's own scrollTop moves it\n // too — start the offset walk at the parent itself, not its ancestors.\n const offset = nestedScrollOffset(parent)\n const range = document.createRange()\n range.selectNodeContents(node)\n for (const rect of Array.from(range.getClientRects())) {\n addRedaction(redactions, rect, offset, REDACTION_PADDING_PX)\n }\n }\n node = walker.nextNode()\n }\n\n return redactions\n}\n\n// html-to-image clones a scroller's children back at their unscrolled\n// positions, so a cover measured from the live viewport rect of an element\n// inside a scrolled container would land away from the secret in the export\n// and leave it visible — the same drift the annotation path compensates in\n// translateAnnotationForScreenshot. A border box moves with ancestor scroll\n// only (an element's own scrollTop shifts its content, not its box), so the\n// walk starts at the parent; page scroll stays out because nestedScrollOffset\n// stops at body, whose offset captureMetrics already carries.\nfunction cloneScrollOffset(element: Element): { x: number; y: number } {\n return nestedScrollOffset(parentElement(element))\n}\n\nfunction elementsIncludingRoot(root: HTMLElement, selector: string): Element[] {\n const descendants = Array.from(root.querySelectorAll(selector))\n return root.matches(selector) ? [root, ...descendants] : descendants\n}\n\nfunction isWidgetOrBlocked(element: Element): boolean {\n return element.closest(`#${WIDGET_HOST_ID}`) !== null || element.closest(`.${BLOCK_CLASS}`) !== null\n}\n\nfunction addRedaction(\n redactions: Rect[],\n rect: DOMRect,\n offset: { x: number; y: number },\n padding = 0,\n): void {\n if (![rect.left, rect.top, rect.width, rect.height].every(Number.isFinite)) {\n throw new Error('Invalid privacy redaction geometry')\n }\n if (rect.width <= 0 || rect.height <= 0) return\n redactions.push({\n left: rect.left + offset.x - padding,\n top: rect.top + offset.y - padding,\n width: rect.width + padding * 2,\n height: rect.height + padding * 2,\n })\n}\n\nfunction drawStroke(\n ctx: CanvasRenderingContext2D,\n drawPath: string,\n transform: CaptureTransform,\n color = BRAND_COLOR,\n): void {\n ctx.save()\n ctx.lineCap = 'round'\n ctx.lineJoin = 'round'\n for (const points of parsePathPolylines(drawPath)) {\n if (points.length === 0) continue\n ctx.beginPath()\n ctx.moveTo(\n toCanvasX(transform, points[0][0]),\n toCanvasY(transform, points[0][1]),\n )\n for (let i = 1; i < points.length; i++) {\n ctx.lineTo(\n toCanvasX(transform, points[i][0]),\n toCanvasY(transform, points[i][1]),\n )\n }\n ctx.shadowColor = 'transparent'\n ctx.strokeStyle = 'rgba(15, 23, 42, 0.72)'\n ctx.lineWidth = 7\n ctx.stroke()\n ctx.strokeStyle = '#ffffff'\n ctx.lineWidth = 5.5\n ctx.stroke()\n ctx.strokeStyle = color\n ctx.lineWidth = 3.5\n ctx.stroke()\n }\n ctx.restore()\n}\n\n// Safari refuses to back a canvas larger than roughly 16.7M pixels and hands\n// back a blank one instead, and the caller's catch turns that into a silent\n// screenshot-less submission. A viewport should never get near the ceiling,\n// but the guard costs nothing and keeps an absurd embedder viewport rendered\n// smaller instead of not at all; bakeScreenshot derives its scale from the\n// returned bitmap, so annotations follow along.\nconst MAX_CAPTURE_PIXELS = 16_000_000\nconst REDACTION_PADDING_PX = 2\n\nfunction capturePixelRatio(metrics: Rect): number {\n const total = metrics.width * metrics.height\n if (!Number.isFinite(total) || total <= MAX_CAPTURE_PIXELS) return 1\n return Math.sqrt(MAX_CAPTURE_PIXELS / total)\n}\n\nfunction drawPin(\n ctx: CanvasRenderingContext2D,\n x: number,\n y: number,\n brandColors?: WidgetBrandColors,\n): void {\n const primary = brandColors?.primary ?? BRAND_COLOR\n ctx.save()\n ctx.shadowColor = 'rgba(15, 23, 42, 0.68)'\n ctx.shadowOffsetY = 2\n ctx.shadowBlur = 8\n ctx.fillStyle = primary\n ctx.beginPath()\n ctx.arc(x, y, 11, 0, Math.PI * 2)\n ctx.fill()\n ctx.shadowColor = 'transparent'\n ctx.lineWidth = 2\n ctx.strokeStyle = '#ffffff'\n ctx.stroke()\n ctx.restore()\n}\n\nfunction canvasToJpegBlob(canvas: HTMLCanvasElement): Promise<Blob | null> {\n return new Promise(resolve => {\n try {\n canvas.toBlob(blob => resolve(blob), 'image/jpeg', 0.85)\n } catch {\n resolve(null)\n }\n })\n}\n","export interface ErrorSourceEvent {\n name?: string\n message: string\n source?: string\n line?: number\n col?: number\n error?: unknown\n}\n\ntype ErrorListener = (event: ErrorSourceEvent) => void\n\nclass ErrorSource {\n private listeners = new Set<ErrorListener>()\n private originalOnError: OnErrorEventHandler | null = null\n private installedOnError: OnErrorEventHandler | null = null\n private rejectionHandler: ((e: PromiseRejectionEvent) => void) | null = null\n private hooked = false\n\n subscribe(listener: ErrorListener): () => void {\n this.listeners.add(listener)\n this.hook()\n return () => {\n this.listeners.delete(listener)\n if (this.listeners.size === 0) this.unhook()\n }\n }\n\n private hook(): void {\n if (this.hooked) return\n this.hooked = true\n\n this.originalOnError = window.onerror\n this.installedOnError = (msg, source, line, col, error) => {\n this.emit({ name: errorName(error), message: String(msg), source, line, col, error })\n if (this.originalOnError) {\n return this.originalOnError.call(window, msg, source, line, col, error)\n }\n return false\n }\n window.onerror = this.installedOnError\n\n this.rejectionHandler = (e: PromiseRejectionEvent) => {\n const reason = e.reason instanceof Error ? e.reason.message : String(e.reason)\n const name = e.reason instanceof Error ? e.reason.name : 'UnhandledRejection'\n this.emit({ name, message: `Unhandled rejection: ${reason}` })\n }\n window.addEventListener('unhandledrejection', this.rejectionHandler)\n }\n\n private unhook(): void {\n if (!this.hooked) return\n this.hooked = false\n // Only restore if nothing replaced our handler meanwhile — restoring\n // the stale snapshot would clobber a handler the page installed later.\n if (window.onerror === this.installedOnError) {\n window.onerror = this.originalOnError\n }\n this.installedOnError = null\n this.originalOnError = null\n if (this.rejectionHandler) {\n window.removeEventListener('unhandledrejection', this.rejectionHandler)\n this.rejectionHandler = null\n }\n }\n\n private emit(event: ErrorSourceEvent): void {\n for (const listener of this.listeners) listener(event)\n }\n}\n\nexport const errorSource = new ErrorSource()\n\nfunction errorName(error: unknown): string | undefined {\n if (!error || typeof error !== 'object' || !('name' in error)) return undefined\n const name = String(error.name)\n return name || undefined\n}\n","import type { ErrorSourceEvent } from './error-source'\n\nexport function summarizeError(event: ErrorSourceEvent): string {\n const name = validErrorName(event.name) || inferErrorName(event.message)\n const location = errorLocation(event)\n return location ? `${name} (${location})` : name\n}\n\nfunction validErrorName(name: string | undefined): string {\n return name && /^[A-Za-z][A-Za-z0-9]*(?:Error|Exception|Rejection)$/.test(name) ? name : ''\n}\n\nfunction inferErrorName(message: string): string {\n return message.match(/^([A-Za-z][A-Za-z0-9]*(?:Error|Exception))\\b/)?.[1] ?? 'Error'\n}\n\nfunction errorLocation(event: ErrorSourceEvent): string {\n if (!event.source) return ''\n\n try {\n const pathname = new URL(event.source, window.location.href).pathname\n if (event.line == null) return pathname\n return `${pathname}:${event.line}:${event.col ?? 0}`\n } catch {\n return ''\n }\n}\n","import { errorSource, type ErrorSourceEvent } from './error-source'\nimport { summarizeError } from './error-summary'\n\nconst MAX_CONSOLE_ERRORS = 20\n\nexport class ConsoleErrorCollector {\n private errors: string[] = []\n private unsubscribe: (() => void) | null = null\n\n start(): void {\n if (this.unsubscribe) return\n this.unsubscribe = errorSource.subscribe((e) => this.record(e))\n }\n\n private record(e: ErrorSourceEvent): void {\n this.addError(summarizeError(e))\n }\n\n private addError(msg: string): void {\n if (this.errors.length >= MAX_CONSOLE_ERRORS) return\n if (!this.errors.includes(msg)) {\n this.errors.push(msg)\n }\n }\n\n getErrors(): string[] {\n return [...this.errors]\n }\n\n stop(): void {\n this.unsubscribe?.()\n this.unsubscribe = null\n }\n}\n","// Single owner of the history.pushState / replaceState patch.\n//\n// Two collectors (breadcrumbs and frustration) both need to know when an SPA\n// navigates. When each wrapped history itself, teardown could only restore the\n// native function if it happened to be the outermost wrapper — so tearing them\n// down in install order (FIFO teardown of a LIFO stack) left one dead wrapper\n// behind per cycle, and Turbo Drive reinstalls on every visit. Refcounting one\n// wrapper here makes the install order irrelevant: the patch goes on when the\n// first listener subscribes and comes off when the last one unsubscribes.\n\nexport interface HistoryNavigationEvent {\n type: 'push' | 'replace' | 'pop'\n fromUrl: string\n toUrl: string\n}\n\ntype NavListener = (event: HistoryNavigationEvent) => void\n\nconst listeners = new Set<NavListener>()\n\nlet nativePushState: History['pushState'] | null = null\nlet nativeReplaceState: History['replaceState'] | null = null\nlet installedPushState: History['pushState'] | null = null\nlet installedReplaceState: History['replaceState'] | null = null\nlet popstateHandler: (() => void) | null = null\nlet lastUrl: string | null = null\n\n// Subscribes to pushState, replaceState and popstate. Returns an unsubscribe\n// function; calling it twice is harmless.\nexport function onHistoryNavigation(listener: NavListener): () => void {\n listeners.add(listener)\n install()\n\n let released = false\n return () => {\n if (released) return\n released = true\n listeners.delete(listener)\n if (listeners.size === 0) uninstall()\n }\n}\n\nfunction notify(type: HistoryNavigationEvent['type'], fromUrl: string): void {\n const event = { type, fromUrl, toUrl: location.href }\n lastUrl = event.toUrl\n // Copy first: a listener may unsubscribe (or subscribe) while being notified.\n for (const listener of [...listeners]) {\n try { listener(event) } catch { /* never propagate into the host page */ }\n }\n}\n\nfunction install(): void {\n if (installedPushState) return\n\n lastUrl = location.href\n popstateHandler = () => notify('pop', lastUrl ?? location.href)\n window.addEventListener('popstate', popstateHandler)\n\n // Snapshot the raw functions and invoke them with .call — binding would hand\n // back a different function object on uninstall, so the host page would never\n // get its own pushState identity back and each cycle would add a bind layer.\n nativePushState = history.pushState\n nativeReplaceState = history.replaceState\n const pushOrig = nativePushState\n const replaceOrig = nativeReplaceState\n\n installedPushState = function (data: unknown, unused: string, url?: string | URL | null) {\n const fromUrl = location.href\n pushOrig.call(history, data, unused, url)\n notify('push', fromUrl)\n }\n installedReplaceState = function (data: unknown, unused: string, url?: string | URL | null) {\n const fromUrl = location.href\n replaceOrig.call(history, data, unused, url)\n notify('replace', fromUrl)\n }\n history.pushState = installedPushState\n history.replaceState = installedReplaceState\n}\n\nfunction uninstall(): void {\n if (popstateHandler) {\n window.removeEventListener('popstate', popstateHandler)\n popstateHandler = null\n }\n // Only restore if nothing (e.g. an SPA router) re-wrapped the functions\n // after us — restoring the stale snapshot would discard their wrapper.\n if (nativePushState && history.pushState === installedPushState) {\n history.pushState = nativePushState\n }\n if (nativeReplaceState && history.replaceState === installedReplaceState) {\n history.replaceState = nativeReplaceState\n }\n nativePushState = null\n nativeReplaceState = null\n installedPushState = null\n installedReplaceState = null\n lastUrl = null\n}\n","import { errorSource, type ErrorSourceEvent } from './error-source'\nimport { summarizeError } from './error-summary'\nimport { getCssSelector, getElementName, isWidgetElement } from './dom-utils'\nimport { onHistoryNavigation } from './history-patch'\nimport { isPrivacyProtected, structuralElementName, structuralElementSelector } from '../privacy/dom'\nimport { sanitizeUrl } from '../privacy/sanitize'\nimport type { Breadcrumb } from '../types'\n\nconst MAX_BREADCRUMBS = 20\n// A click breadcrumb names the element by its on-screen text, which on a page\n// showing an order total or a patient name is that value verbatim. The replay\n// recorder already honours rr-block/rr-mask for exactly this; a customer who\n// marked a region has marked it for the whole SDK, not for replays only. Inside\n// one, fall back to what the element is rather than what it says.\nfunction describeTarget(el: Element): string {\n if (isPrivacyProtected(el)) return structuralElementName(el)\n return getElementName(el)\n}\n\nfunction describeSelector(el: Element): string {\n return isPrivacyProtected(el) ? structuralElementSelector(el) : getCssSelector(el)\n}\n\nexport class BreadcrumbCollector {\n private breadcrumbs: Breadcrumb[] = []\n private active = false\n private unsubscribeErrors: (() => void) | null = null\n private boundClickHandler: (e: MouseEvent) => void\n private boundChangeHandler: (e: Event) => void\n private unsubscribeNavigation: (() => void) | null = null\n private lastUrl = ''\n\n constructor() {\n this.boundClickHandler = (e: MouseEvent) => this.handleClick(e)\n this.boundChangeHandler = (e: Event) => this.handleChange(e)\n }\n\n start(): void {\n if (this.active) return\n this.active = true\n this.lastUrl = location.href\n document.addEventListener('click', this.boundClickHandler, { capture: true, passive: true })\n document.addEventListener('change', this.boundChangeHandler, { capture: true, passive: true })\n this.unsubscribeErrors = errorSource.subscribe((e) => this.recordError(e))\n this.unsubscribeNavigation = onHistoryNavigation(() => this.handleNavigation())\n }\n\n stop(): void {\n if (!this.active) return\n this.active = false\n document.removeEventListener('click', this.boundClickHandler, true)\n document.removeEventListener('change', this.boundChangeHandler, true)\n this.unsubscribeErrors?.()\n this.unsubscribeErrors = null\n this.unsubscribeNavigation?.()\n this.unsubscribeNavigation = null\n }\n\n getBreadcrumbs(): Breadcrumb[] {\n return [...this.breadcrumbs]\n }\n\n private add(crumb: Breadcrumb): void {\n this.breadcrumbs.push(crumb)\n if (this.breadcrumbs.length > MAX_BREADCRUMBS) {\n this.breadcrumbs.shift()\n }\n }\n\n private handleClick(e: MouseEvent): void {\n const target = e.target as Element | null\n if (!target) return\n if (isWidgetElement(target)) return\n\n try {\n this.add({\n type: 'ui',\n category: 'ui.click',\n timestamp: Date.now(),\n message: describeTarget(target),\n data: { selector: describeSelector(target) },\n })\n } catch { /* never propagate into host page */ }\n }\n\n private handleChange(e: Event): void {\n const target = e.target as Element | null\n if (!target) return\n if (isWidgetElement(target)) return\n\n try {\n const input = target as HTMLInputElement\n const field = isPrivacyProtected(target)\n ? structuralElementName(target)\n : input.name || input.placeholder || target.getAttribute('aria-label') || target.tagName\n this.add({\n type: 'ui',\n category: 'ui.input',\n timestamp: Date.now(),\n message: field,\n data: { field },\n })\n } catch { /* never propagate into host page */ }\n }\n\n private recordError(e: ErrorSourceEvent): void {\n const summary = summarizeError(e)\n this.add({\n type: 'console',\n category: 'console.error',\n timestamp: Date.now(),\n message: summary,\n data: { message: summary },\n })\n }\n\n private handleNavigation(): void {\n if (!this.active) return\n const from = this.lastUrl\n const to = location.href\n if (from === to) return\n this.lastUrl = to\n const sanitizedFrom = sanitizeUrl(from)\n const sanitizedTo = sanitizeUrl(to)\n this.add({\n type: 'navigation',\n category: 'navigation',\n timestamp: Date.now(),\n message: `${sanitizedFrom} → ${sanitizedTo}`,\n data: { from: sanitizedFrom, to: sanitizedTo },\n })\n }\n\n}\n","// Frustration state that must outlive a single page visit.\n//\n// The detector and the widget controller are both rebuilt from scratch on every\n// Turbo Drive navigation (see src/index.ts), which used to reset all of this.\n// The consequences were: `rapid_navigation` could never fire (it needs 3\n// timestamps within 5s but each visit contributed at most one), the 60s cooldown\n// was bypassed by navigating, an already-reported error page re-prompted on\n// revisit, and a reporter's explicit \"Dismiss\" did not survive one visit.\n//\n// sessionStorage is the right scope — this is per-tab, per-visit-series state,\n// not a long-lived identity like mtb_anon_id. Writes are best-effort: Safari\n// private mode and storage-partitioned iframes throw, so an in-memory copy backs\n// every read (it covers the current page at least).\n\nconst STORAGE_KEY = 'mtb_frustration_state'\n// Keeps a long browsing session from growing the stored payload without bound.\nconst MAX_ERROR_PAGE_URLS = 20\n\nexport interface FrustrationState {\n navTimestamps: number[]\n cooldownUntil: number\n errorPageUrls: string[]\n dismissedByUser: boolean\n}\n\nlet memoryState: FrustrationState | null = null\n\n// Every return path goes through normalize(), which rebuilds the arrays. Callers\n// own their copy and mutate it in place (the detector pushes onto navTimestamps),\n// so handing out a shared array would let one caller corrupt the stored state.\nexport function loadFrustrationState(): FrustrationState {\n try {\n const raw = window.sessionStorage.getItem(STORAGE_KEY)\n if (!raw) return normalize(memoryState)\n return normalize(JSON.parse(raw))\n } catch {\n return normalize(memoryState)\n }\n}\n\nexport function saveFrustrationState(state: FrustrationState): void {\n const normalized = normalize(state)\n memoryState = normalized\n try {\n window.sessionStorage.setItem(STORAGE_KEY, JSON.stringify(normalized))\n } catch {\n // sessionStorage unavailable — the in-memory copy covers this page\n }\n}\n\nexport function isFrustrationDismissed(): boolean {\n return loadFrustrationState().dismissedByUser\n}\n\n// Records a real refusal (the reporter clicked Dismiss), which suppresses\n// proactive prompts for the rest of the tab session. An auto-hide must never\n// call this — the 60s cooldown is what rate-limits an ignored prompt.\nexport function markFrustrationDismissed(): void {\n saveFrustrationState({ ...loadFrustrationState(), dismissedByUser: true })\n}\n\nexport function clearFrustrationState(): void {\n memoryState = null\n try {\n window.sessionStorage.removeItem(STORAGE_KEY)\n } catch {\n // nothing to clear\n }\n}\n\nfunction normalize(value: unknown): FrustrationState {\n const raw = (value ?? {}) as Partial<Record<keyof FrustrationState, unknown>>\n return {\n navTimestamps: Array.isArray(raw.navTimestamps)\n ? raw.navTimestamps.filter((t): t is number => typeof t === 'number')\n : [],\n cooldownUntil: typeof raw.cooldownUntil === 'number' ? raw.cooldownUntil : 0,\n errorPageUrls: Array.isArray(raw.errorPageUrls)\n ? raw.errorPageUrls.filter((u): u is string => typeof u === 'string').slice(-MAX_ERROR_PAGE_URLS)\n : [],\n dismissedByUser: raw.dismissedByUser === true,\n }\n}\n","import { errorSource } from './error-source'\nimport { isWidgetElement } from './dom-utils'\nimport { onHistoryNavigation } from './history-patch'\nimport type { HistoryNavigationEvent } from './history-patch'\nimport { loadFrustrationState, saveFrustrationState } from './frustration-state'\n\nexport type FrustrationSignal =\n | 'rage_click'\n | 'dead_click'\n | 'rapid_navigation'\n | 'form_failure'\n | 'error_page'\n | 'dead_click_dom'\n\nexport interface FrustrationEvent {\n signal: FrustrationSignal\n target?: string\n detail?: string\n timestamp: number\n}\n\ninterface ClickRecord {\n target: EventTarget | null\n time: number\n}\n\nconst RAGE_CLICK_THRESHOLD = 4\nconst RAGE_CLICK_WINDOW_MS = 1500\nconst RAPID_NAV_THRESHOLD = 3\nconst RAPID_NAV_WINDOW_MS = 5000\nconst FORM_CHECK_DELAY_MS = 500\nconst FORM_FAILURE_THRESHOLD = 2\nconst FORM_FAILURE_WINDOW_MS = 30_000\nconst INTERACTION_ERROR_WINDOW_MS = 2000\nconst DEAD_CLICK_DOM_DELAY_MS = 1000\nconst ERROR_PAGE_CHECK_DELAY_MS = 300\n\nconst INTERACTIVE_TAGS = new Set([\n 'A', 'BUTTON', 'INPUT', 'SELECT', 'TEXTAREA', 'LABEL', 'SUMMARY', 'DETAILS',\n])\n\nconst INTERACTIVE_ROLES = new Set([\n 'button', 'link', 'checkbox', 'radio', 'tab', 'menuitem', 'option',\n 'switch', 'textbox', 'combobox', 'listbox', 'slider', 'spinbutton',\n])\n\nconst COMMAND_TAGS = new Set(['A', 'BUTTON', 'SUMMARY'])\nconst COMMAND_ROLES = new Set(['button', 'link', 'tab', 'menuitem'])\n\n// Matched as whole words, never as substrings. Substring matching read the\n// '500' in \"Top 500 SaaS Tools\" as an outage and put the high-priority prompt\n// in front of every visitor on load.\n//\n// A bare status code stays ambiguous even with word boundaries — it is a\n// listicle count, a company name, a price — so it only counts when the title is\n// nothing but the code, or an explicit error word stands next to it. The\n// phrases are error-shaped on their own and carry themselves.\nconst ERROR_PAGE_PATTERNS = [\n /\\bnot found\\b/,\n /\\bserver error\\b/,\n /^\\s*(?:http\\s*)?[45]\\d{2}\\s*$/,\n /\\b(?:error|http|status)\\s*[:-]?\\s*[45]\\d{2}\\b/,\n /\\b[45]\\d{2}\\s*[:-]?\\s*error\\b/,\n]\n\nexport class FrustrationDetector {\n private clickHistory: ClickRecord[] = []\n private onFrustration: (event: FrustrationEvent) => void\n private boundClickHandler: (e: MouseEvent) => void\n private unsubscribeErrors: (() => void) | null = null\n private active = false\n private lastInteraction: { target: Element; time: number } | null = null\n private cooldownUntil = 0\n private static readonly COOLDOWN_MS = 60_000\n\n private navTimestamps: number[] = []\n private unsubscribeNavigation: (() => void) | null = null\n private boundPageLeaveHandler: (() => void) | null = null\n private boundTurboNavigationHandler: (() => void) | null = null\n private lastNavigationAt = -1\n private boundSubmitHandler: ((e: Event) => void) | null = null\n private boundInvalidHandler: ((e: Event) => void) | null = null\n private recentSubmitForms = new WeakSet<HTMLFormElement>()\n private recentFailedForms = new WeakSet<HTMLFormElement>()\n private formFailureTimestamps = new WeakMap<HTMLFormElement, number[]>()\n private errorPageUrls = new Set<string>()\n private domObserver: MutationObserver | null = null\n private pendingDomChecks = 0\n private lastMutationAt = -1\n\n // Every deferred check outlives the interaction that scheduled it, so stop()\n // has to cancel the ones still waiting. Two reasons: a dead-click timer holds\n // the clicked host element for a second past teardown, and a start() inside\n // that window would let the previous session's click emit against the new one.\n private pendingTimers = new Set<ReturnType<typeof setTimeout>>()\n\n constructor(onFrustration: (event: FrustrationEvent) => void) {\n this.onFrustration = onFrustration\n this.boundClickHandler = (e: MouseEvent) => this.handleClick(e)\n }\n\n start(): void {\n if (this.active) return\n this.active = true\n this.restoreState()\n document.addEventListener('click', this.boundClickHandler, true)\n this.unsubscribeErrors = errorSource.subscribe(() => {\n const now = Date.now()\n if (!this.lastInteraction || now - this.lastInteraction.time > INTERACTION_ERROR_WINDOW_MS) return\n\n const interaction = this.lastInteraction\n this.lastInteraction = null\n this.emitIfReady({\n signal: 'dead_click',\n target: describeElement(interaction.target),\n detail: 'Uncaught error after interaction',\n timestamp: now,\n })\n })\n\n this.startNavigationTracking()\n this.startFormTracking()\n this.checkErrorPage()\n }\n\n stop(): void {\n if (!this.active) return\n this.active = false\n document.removeEventListener('click', this.boundClickHandler, true)\n this.unsubscribeErrors?.()\n this.unsubscribeErrors = null\n this.clickHistory = []\n this.lastInteraction = null\n\n this.stopNavigationTracking()\n this.stopFormTracking()\n this.errorPageUrls.clear()\n\n this.clearPendingTimers()\n // The cancelled dead-click timers are the only thing that would have\n // decremented this, so the count has to come back to zero by hand or the\n // observer of a restarted detector never reaches its disconnect condition.\n this.pendingDomChecks = 0\n this.disconnectDomObserver()\n }\n\n private schedule(callback: () => void, delayMs: number): void {\n const timer = setTimeout(() => {\n this.pendingTimers.delete(timer)\n callback()\n }, delayMs)\n this.pendingTimers.add(timer)\n }\n\n private clearPendingTimers(): void {\n for (const timer of this.pendingTimers) clearTimeout(timer)\n this.pendingTimers.clear()\n }\n\n private startNavigationTracking(): void {\n this.unsubscribeNavigation = onHistoryNavigation((event) => this.handleNavigation(event))\n\n // A click that starts a full-page navigation (checkout, OAuth, external\n // link, download, window.location assignment) leaves the DOM untouched\n // while the browser tears the page down, which is indistinguishable from a\n // dead click by mutation alone. Recording when the page starts leaving lets\n // checkDeadClickDom acquit those clicks. Both events are needed: browsers\n // skip beforeunload when nothing registered an unload prompt, and Safari\n // reaches bfcache via pagehide only.\n this.boundPageLeaveHandler = () => this.noteNavigationStarted()\n window.addEventListener('beforeunload', this.boundPageLeaveHandler)\n window.addEventListener('pagehide', this.boundPageLeaveHandler)\n this.boundTurboNavigationHandler = () => this.noteNavigationStarted()\n document.addEventListener('turbo:before-visit', this.boundTurboNavigationHandler)\n document.addEventListener('turbo:submit-start', this.boundTurboNavigationHandler)\n document.addEventListener('turbo:before-fetch-request', this.boundTurboNavigationHandler)\n }\n\n private stopNavigationTracking(): void {\n this.unsubscribeNavigation?.()\n this.unsubscribeNavigation = null\n this.navTimestamps = []\n\n if (this.boundPageLeaveHandler) {\n window.removeEventListener('beforeunload', this.boundPageLeaveHandler)\n window.removeEventListener('pagehide', this.boundPageLeaveHandler)\n this.boundPageLeaveHandler = null\n }\n if (this.boundTurboNavigationHandler) {\n document.removeEventListener('turbo:before-visit', this.boundTurboNavigationHandler)\n document.removeEventListener('turbo:submit-start', this.boundTurboNavigationHandler)\n document.removeEventListener('turbo:before-fetch-request', this.boundTurboNavigationHandler)\n this.boundTurboNavigationHandler = null\n }\n this.lastNavigationAt = -1\n }\n\n private noteNavigationStarted(): void {\n this.lastNavigationAt = Date.now()\n }\n\n // Rehydrates the state a Turbo Drive visit would otherwise reset. Without this,\n // a fresh detector on every page starts with an empty nav history (so\n // `rapid_navigation` can never reach its 3-in-5s threshold), no cooldown, and no\n // memory of which error pages it already reported.\n private restoreState(): void {\n const stored = loadFrustrationState()\n this.navTimestamps = stored.navTimestamps\n this.cooldownUntil = stored.cooldownUntil\n this.errorPageUrls = new Set(stored.errorPageUrls)\n }\n\n private persistState(): void {\n const stored = loadFrustrationState()\n saveFrustrationState({\n ...stored,\n navTimestamps: this.navTimestamps,\n cooldownUntil: this.cooldownUntil,\n errorPageUrls: [...this.errorPageUrls],\n })\n }\n\n private startFormTracking(): void {\n this.boundSubmitHandler = (e: Event) => this.handleFormSubmit(e)\n this.boundInvalidHandler = (e: Event) => this.handleFormInvalid(e)\n document.addEventListener('submit', this.boundSubmitHandler, true)\n document.addEventListener('invalid', this.boundInvalidHandler, true)\n }\n\n private stopFormTracking(): void {\n if (this.boundSubmitHandler) {\n document.removeEventListener('submit', this.boundSubmitHandler, true)\n this.boundSubmitHandler = null\n }\n if (this.boundInvalidHandler) {\n document.removeEventListener('invalid', this.boundInvalidHandler, true)\n this.boundInvalidHandler = null\n }\n // Their expiry timers were just cancelled, so membership would otherwise be\n // permanent and a form left mid-submit here would be judged failed by a\n // later detector on the strength of an attempt from the previous one.\n this.recentSubmitForms = new WeakSet()\n this.recentFailedForms = new WeakSet()\n }\n\n private handleClick(e: MouseEvent): void {\n const target = e.target as Element | null\n if (!target) return\n\n if (isWidgetElement(target)) return\n this.trackFormSubmitIntent(target)\n this.trackNativeNavigationIntent(e, target)\n const interactionTarget = this.interactionTarget(target)\n\n const now = Date.now()\n this.lastInteraction = { target: interactionTarget, time: now }\n this.clickHistory.push({ target: interactionTarget, time: now })\n\n const cutoff = now - RAGE_CLICK_WINDOW_MS\n this.clickHistory = this.clickHistory.filter(r => r.time > cutoff)\n\n const sameTargetClicks = this.clickHistory.filter(r => r.target === interactionTarget)\n if (sameTargetClicks.length >= RAGE_CLICK_THRESHOLD) {\n this.clickHistory = []\n this.emitHighPriority({\n signal: 'rage_click',\n target: describeElement(interactionTarget),\n timestamp: now,\n })\n return\n }\n\n this.checkDeadClickDom(interactionTarget, now)\n }\n\n private handleNavigation(event: HistoryNavigationEvent): void {\n const now = Date.now()\n this.noteNavigationStarted()\n const meaningfulNavigation = event.type !== 'replace' && event.fromUrl !== event.toUrl\n // Successful forward navigation is ordinary browsing, even when several\n // pages are opened quickly. Repeated browser back/forward movement is the\n // narrower signal that the visitor may be struggling to find their way.\n if (meaningfulNavigation && event.type === 'pop') {\n this.navTimestamps.push(now)\n const cutoff = now - RAPID_NAV_WINDOW_MS\n this.navTimestamps = this.navTimestamps.filter(t => t > cutoff)\n if (this.navTimestamps.length >= RAPID_NAV_THRESHOLD) {\n this.navTimestamps = []\n this.emitIfReady({\n signal: 'rapid_navigation',\n timestamp: now,\n })\n }\n }\n this.persistState()\n this.schedule(() => {\n if (this.active) this.checkErrorPage()\n }, ERROR_PAGE_CHECK_DELAY_MS)\n }\n\n private handleFormSubmit(e: Event): void {\n const form = e.target\n if (!(form instanceof HTMLFormElement)) return\n if (isWidgetElement(form)) return\n\n this.rememberSubmitAttempt(form)\n\n this.schedule(() => {\n if (!this.active) return\n if (form.querySelector('[aria-invalid=\"true\"], .is-invalid')) {\n this.recordFormFailure(form)\n }\n }, FORM_CHECK_DELAY_MS)\n }\n\n private trackFormSubmitIntent(target: Element): void {\n const control = target.closest('button, input')\n if (!(control instanceof HTMLButtonElement || control instanceof HTMLInputElement)) return\n if (control.type !== 'submit' && control.type !== 'image') return\n if (!control.form || isWidgetElement(control.form)) return\n\n this.rememberSubmitAttempt(control.form)\n }\n\n private trackNativeNavigationIntent(event: MouseEvent, target: Element): void {\n if (!target.closest('a[href]')) return\n\n queueMicrotask(() => {\n if (this.active && !event.defaultPrevented) this.noteNavigationStarted()\n })\n }\n\n private rememberSubmitAttempt(form: HTMLFormElement): void {\n this.recentSubmitForms.add(form)\n this.schedule(() => {\n this.recentSubmitForms.delete(form)\n }, FORM_CHECK_DELAY_MS)\n }\n\n private recordFormFailure(form: HTMLFormElement): void {\n if (this.recentFailedForms.has(form)) return\n this.recentFailedForms.add(form)\n this.schedule(() => this.recentFailedForms.delete(form), FORM_CHECK_DELAY_MS)\n\n const now = Date.now()\n const cutoff = now - FORM_FAILURE_WINDOW_MS\n const failures = (this.formFailureTimestamps.get(form) ?? []).filter(timestamp => timestamp > cutoff)\n failures.push(now)\n this.formFailureTimestamps.set(form, failures)\n if (failures.length < FORM_FAILURE_THRESHOLD) return\n\n this.formFailureTimestamps.set(form, [])\n this.emitIfReady({\n signal: 'form_failure',\n target: describeElement(form),\n timestamp: now,\n })\n }\n\n private handleFormInvalid(e: Event): void {\n const el = e.target as Element | null\n if (!el) return\n if (isWidgetElement(el)) return\n const form = el.closest('form')\n if (!form || !this.recentSubmitForms.has(form)) return\n this.recordFormFailure(form)\n }\n\n private checkErrorPage(): void {\n const url = location.href\n if (this.errorPageUrls.has(url)) return\n\n const title = document.title.toLowerCase()\n if (ERROR_PAGE_PATTERNS.some(p => p.test(title))) {\n this.errorPageUrls.add(url)\n this.emitHighPriority({\n signal: 'error_page',\n detail: document.title,\n timestamp: Date.now(),\n })\n return\n }\n\n const candidates = document.querySelectorAll(\n 'h1, h2, [class*=\"error\"], [class*=\"not-found\"], [id*=\"error\"], [id*=\"not-found\"]'\n )\n for (const el of candidates) {\n const text = el.textContent ?? ''\n if (text.length < 200 && ERROR_PAGE_PATTERNS.some(p => p.test(text.toLowerCase()))) {\n this.errorPageUrls.add(url)\n this.emitHighPriority({\n signal: 'error_page',\n detail: text.trim().slice(0, 60),\n timestamp: Date.now(),\n })\n return\n }\n }\n }\n\n // One shared observer for all in-flight dead-click checks, connected only\n // while at least one check is pending — a full-page observer per click was\n // too heavy on mutation-busy pages.\n private checkDeadClickDom(target: Element, clickTime: number): void {\n if (!this.looksInteractive(target)) return\n\n this.pendingDomChecks++\n this.ensureDomObserver()\n\n this.schedule(() => {\n this.pendingDomChecks--\n const mutated = this.lastMutationAt >= clickTime\n // A navigation that began at or after the click means the click worked;\n // the page just left before it could mutate anything.\n const navigated = this.lastNavigationAt >= clickTime\n if (this.pendingDomChecks === 0) this.disconnectDomObserver()\n\n if (!mutated && !navigated && this.active) {\n this.emitIfReady({\n signal: 'dead_click_dom',\n target: describeElement(target),\n timestamp: clickTime,\n })\n }\n }, DEAD_CLICK_DOM_DELAY_MS)\n }\n\n private ensureDomObserver(): void {\n if (this.domObserver) return\n this.domObserver = new MutationObserver(() => {\n this.lastMutationAt = Date.now()\n })\n this.domObserver.observe(document.documentElement, {\n childList: true,\n subtree: true,\n attributes: true,\n characterData: true,\n })\n }\n\n private disconnectDomObserver(): void {\n this.domObserver?.disconnect()\n this.domObserver = null\n }\n\n private looksInteractive(el: Element): boolean {\n if (COMMAND_TAGS.has(el.tagName) || el.hasAttribute('onclick')) return true\n\n const role = el.getAttribute('role')\n if (role && COMMAND_ROLES.has(role)) return true\n\n const style = window.getComputedStyle(el)\n if (style.cursor === 'pointer') return true\n\n if (el.classList) {\n for (const cls of el.classList) {\n const lower = cls.toLowerCase()\n if (lower.includes('btn') || lower.includes('button')) return true\n }\n }\n\n return false\n }\n\n private interactionTarget(el: Element): Element {\n let current: Element | null = el\n while (current && current !== document.body) {\n if (this.isIntrinsicInteractive(current)) return current\n current = current.parentElement\n }\n\n let candidate = el\n current = el.parentElement\n while (current && current !== document.body && this.looksInteractive(current)) {\n candidate = current\n current = current.parentElement\n }\n return candidate\n }\n\n private isIntrinsicInteractive(el: Element): boolean {\n if (INTERACTIVE_TAGS.has(el.tagName)) return true\n\n const role = el.getAttribute('role')\n if (role && INTERACTIVE_ROLES.has(role)) return true\n\n if (el.hasAttribute('onclick') || el.hasAttribute('tabindex')) return true\n return (el as HTMLElement).contentEditable === 'true'\n }\n\n private emitIfReady(event: FrustrationEvent): void {\n const now = Date.now()\n if (now < this.cooldownUntil) return\n this.cooldownUntil = now + FrustrationDetector.COOLDOWN_MS\n this.persistState()\n this.onFrustration(event)\n }\n\n private emitHighPriority(event: FrustrationEvent): void {\n this.cooldownUntil = Date.now() + FrustrationDetector.COOLDOWN_MS\n this.persistState()\n this.onFrustration(event)\n }\n}\n\nfunction describeElement(el: Element): string {\n const tag = el.tagName.toLowerCase()\n const id = el.id ? `#${el.id}` : ''\n const cls = el.className && typeof el.className === 'string'\n ? '.' + el.className.trim().split(/\\s+/).slice(0, 2).join('.')\n : ''\n const text = el.textContent?.trim().slice(0, 30) ?? ''\n return `<${tag}${id}${cls}>${text ? ` \"${text}\"` : ''}`\n}\n","import type {\n FeedbackPayload,\n FeedbackResponse,\n SubmissionSessionResponse,\n ClarifyResponse,\n ClarifyMessage,\n} from '../types'\nimport { filterSensitiveText } from '../privacy/sanitize'\n\nexport interface ClarifyStreamResult {\n messages: ClarifyMessage[]\n suggestions?: string[]\n done: boolean\n failed?: boolean\n}\n\n// Carries the HTTP status so callers can tell a retryable failure from a terminal\n// one (e.g. 409 = the submission session is no longer accepting a finalize, which\n// retrying can never fix). The message stays `HTTP <status>` for compatibility.\nexport class ApiError extends Error {\n readonly status: number\n\n constructor(status: number) {\n super(`HTTP ${status}`)\n this.name = 'ApiError'\n this.status = status\n }\n}\n\nexport function isTerminalConflict(error: unknown): boolean {\n return error instanceof ApiError && error.status === 409\n}\n\n// A finalize the reporter can no longer watch must still end. Without a ceiling\n// a request that never settles leaves the widget mid-submission forever, with\n// the launcher inert and nothing on screen to explain why.\nexport const FINALIZE_TIMEOUT_MS = 25_000\n\nconst DEFAULT_API_URL = 'https://makethisbetter.dev/api/v1'\nconst CAPTURE_POLICY = 'sensitive-data-v1'\n\nexport interface ApiClientOptions {\n projectKey: string\n apiUrl?: string\n retryDelayMs?: number\n userToken?: string\n userTokenFn?: () => Promise<string>\n}\n\nexport class ApiClient {\n private apiUrl: string\n private projectKey: string\n private retryDelayMs: number\n private userToken?: string\n private userTokenFn?: () => Promise<string>\n\n constructor(options: ApiClientOptions) {\n this.projectKey = options.projectKey\n this.apiUrl = (options.apiUrl ?? DEFAULT_API_URL).replace(/\\/$/, '')\n this.retryDelayMs = options.retryDelayMs ?? 1000\n this.userToken = options.userToken\n this.userTokenFn = options.userTokenFn\n }\n\n async resolveUserToken(): Promise<string | undefined> {\n if (this.userTokenFn) return this.userTokenFn()\n return this.userToken\n }\n\n async resolveScreenshotResourceUrl(\n submissionSessionId: string,\n token: string,\n resourceUrl: string,\n contentType?: string,\n signal?: AbortSignal,\n ): Promise<string | undefined> {\n if (contentType && !contentType.startsWith('image/')) return undefined\n\n try {\n const source = new URL(resourceUrl, window.location.href)\n if (!['http:', 'https:'].includes(source.protocol)) return undefined\n if (source.origin === window.location.origin) return undefined\n\n const headers = await this.submissionHeaders(token, 'application/json')\n const res = await this.fetchWithRetry(\n `${this.apiUrl}/widget/feedback_submission_sessions/${submissionSessionId}/image_proxy_url`,\n {\n method: 'POST',\n headers,\n body: JSON.stringify({ image_proxy_url: { source: source.href } }),\n signal,\n },\n )\n const body = await res.json() as { url?: unknown }\n return typeof body.url === 'string' ? body.url : undefined\n } catch {\n return undefined\n }\n }\n\n private async authHeaders(contentType?: string): Promise<Record<string, string>> {\n const headers: Record<string, string> = { 'X-Project-Key': this.projectKey }\n const token = await this.resolveUserToken()\n if (token) headers['X-User-Token'] = token\n if (contentType) headers['Content-Type'] = contentType\n return headers\n }\n\n private async submissionHeaders(token: string, contentType?: string): Promise<Record<string, string>> {\n const headers = await this.authHeaders(contentType)\n headers['X-Submission-Token'] = token\n return headers\n }\n\n async createSubmissionSession(payload: FeedbackPayload, screenshot: Blob | null): Promise<SubmissionSessionResponse> {\n const submissionToken = generateSubmissionToken()\n const form = new FormData()\n form.append('feedback[description]', payload.description)\n form.append('feedback[page_url]', payload.page_url)\n form.append('feedback[user_agent]', payload.user_agent)\n form.append('feedback[browser]', payload.browser)\n form.append('feedback[os]', payload.os)\n form.append('feedback[screen_width]', String(payload.screen_width))\n form.append('feedback[screen_height]', String(payload.screen_height))\n form.append('feedback[console_errors]', JSON.stringify(payload.console_errors))\n form.append('feedback[annotations]', JSON.stringify(payload.annotations))\n\n if (payload.target_element) {\n form.append('feedback[target_element]', JSON.stringify(payload.target_element))\n }\n if (payload.user_id) form.append('feedback[user_id]', payload.user_id)\n if (payload.reporter_external_id) form.append('feedback[reporter_external_id]', payload.reporter_external_id)\n if (payload.reporter_email) form.append('feedback[reporter_email]', payload.reporter_email)\n if (payload.user_email) form.append('feedback[user_email]', payload.user_email)\n if (payload.user_name) form.append('feedback[user_name]', payload.user_name)\n\n if (payload.breadcrumbs && payload.breadcrumbs.length > 0) {\n form.append('feedback[breadcrumbs]', JSON.stringify(payload.breadcrumbs))\n }\n\n if (payload.recording_events && payload.recording_events.length > 0) {\n const json = JSON.stringify(payload.recording_events)\n const blob = new Blob([json], { type: 'application/json' })\n form.append('feedback[recording]', blob, 'interaction-replay.json')\n form.append('feedback[recording_duration]', String(payload.recording_duration ?? 0))\n }\n\n if (screenshot) {\n const ext = screenshot.type === 'image/jpeg' ? 'jpg' : 'png'\n form.append('feedback[screenshot]', screenshot, `screenshot.${ext}`)\n }\n\n const headers = await this.authHeaders()\n headers['X-Submission-Token'] = submissionToken\n headers['X-MTB-Capture-Policy'] = CAPTURE_POLICY\n // One key per submission, reused across retries: if the first POST\n // reached the server but the response was lost, the retry must not\n // create a second feedback.\n headers['X-Idempotency-Key'] = generateIdempotencyKey()\n const res = await this.fetchWithRetry(`${this.apiUrl}/widget/feedback_submission_sessions`, {\n method: 'POST',\n headers,\n body: form,\n })\n const body = await res.json() as {\n submission_session: Omit<SubmissionSessionResponse, 'token'> & { token?: string }\n }\n return { ...body.submission_session, token: submissionToken }\n }\n\n async createIdentityToken(reporterExternalId: string): Promise<{ identity_token: string; board_url: string } | null> {\n const headers = await this.authHeaders('application/json')\n try {\n const res = await this.fetchWithRetry(`${this.apiUrl}/widget/identity_tokens`, {\n method: 'POST',\n headers,\n body: JSON.stringify({ reporter_external_id: reporterExternalId }),\n })\n return await res.json()\n } catch {\n return null\n }\n }\n\n async updateReporter(feedbackId: string, email: string, identityToken: string): Promise<boolean> {\n const headers = await this.authHeaders('application/json')\n headers['X-Identity-Token'] = identityToken\n try {\n await this.fetchWithRetry(`${this.apiUrl}/widget/feedbacks/${feedbackId}/reporter`, {\n method: 'PATCH',\n headers,\n body: JSON.stringify({ reporter: { email } }),\n })\n return true\n } catch {\n return false\n }\n }\n\n // Records the reporter's answer to the AI follow-up question. Must run\n // before finalize: finalizing deactivates the session and the server then\n // rejects the answer as terminal.\n async answerClarification(submissionSessionId: string, token: string, message: string): Promise<ClarifyResponse> {\n return this.requestClarification(submissionSessionId, token, { message: filterSensitiveText(message) })\n }\n\n private async requestClarification(\n submissionSessionId: string,\n token: string,\n body: Record<string, string | boolean>,\n ): Promise<ClarifyResponse> {\n const headers = await this.submissionHeaders(token, 'application/json')\n const res = await this.fetchWithRetry(`${this.apiUrl}/widget/feedback_submission_sessions/${submissionSessionId}/clarification`, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n })\n const response = await res.json() as { clarification: ClarifyResponse }\n return response.clarification\n }\n\n // Streams a clarification turn over SSE. Calls onDelta for each token and\n // resolves with the committed result.\n async streamClarification(\n submissionSessionId: string,\n token: string,\n onDelta: (text: string) => void,\n signal?: AbortSignal,\n ): Promise<ClarifyStreamResult> {\n const headers = await this.submissionHeaders(token, 'application/json')\n headers['Accept'] = 'text/event-stream'\n\n const res = await fetch(`${this.apiUrl}/widget/feedback_submission_sessions/${submissionSessionId}/clarification`, {\n method: 'POST',\n headers,\n body: JSON.stringify({}),\n signal,\n })\n\n const contentType = res.headers.get('Content-Type') ?? ''\n if (!res.ok || !res.body || !contentType.includes('text/event-stream')) {\n throw new Error('clarify stream unavailable')\n }\n\n return consumeSse(res.body, onDelta)\n }\n\n // No X-Idempotency-Key here, unlike createSubmissionSession: the session id in\n // the path already is one. The server finalizes under a row lock and returns\n // the feedback it already created if the session is finalized, so the retry\n // fetchWithRetry makes after a 5xx cannot produce a second Feedback.\n async finalizeSubmissionSession(submissionSessionId: string, token: string): Promise<FeedbackResponse> {\n const headers = await this.submissionHeaders(token)\n const controller = new AbortController()\n // Covers both attempts, so the caller waits the ceiling once rather than\n // once per retry.\n const timer = setTimeout(() => controller.abort(), FINALIZE_TIMEOUT_MS)\n try {\n const res = await this.fetchWithRetry(`${this.apiUrl}/widget/feedback_submission_sessions/${submissionSessionId}/feedback`, {\n method: 'POST',\n headers,\n signal: controller.signal,\n })\n const response = await res.json() as { feedback: FeedbackResponse }\n return response.feedback\n } finally {\n clearTimeout(timer)\n }\n }\n\n async uploadScreenshot(\n submissionSessionId: string,\n token: string,\n screenshot: Blob,\n signal?: AbortSignal,\n ): Promise<void> {\n const headers = await this.submissionHeaders(token)\n delete (headers as Record<string, string>)['Content-Type']\n const body = new FormData()\n body.append('screenshot', screenshot, 'screenshot.jpg')\n await this.fetchWithRetry(`${this.apiUrl}/widget/feedback_submission_sessions/${submissionSessionId}/screenshot`, {\n method: 'PUT',\n headers,\n body,\n signal,\n })\n }\n\n async abandonSubmissionSession(submissionSessionId: string, token: string): Promise<void> {\n const headers = await this.submissionHeaders(token)\n await this.fetchWithRetry(`${this.apiUrl}/widget/feedback_submission_sessions/${submissionSessionId}`, {\n method: 'DELETE',\n headers,\n })\n }\n\n private async fetchWithRetry(url: string, init: RequestInit, retries = 1): Promise<Response> {\n let res: Response\n try {\n res = await fetch(url, init)\n } catch (err) {\n // An aborted request has spent its deadline; retrying only makes the\n // caller wait out the retry delay for a second guaranteed abort.\n if (retries > 0 && !init.signal?.aborted) {\n await delay(this.retryDelayMs)\n return this.fetchWithRetry(url, init, retries - 1)\n }\n throw err\n }\n\n if (res.ok) return res\n if (res.status >= 500 && retries > 0) {\n await delay(this.retryDelayMs)\n return this.fetchWithRetry(url, init, retries - 1)\n }\n throw new ApiError(res.status)\n }\n}\n\nfunction delay(ms: number): Promise<void> {\n if (ms <= 0) return Promise.resolve()\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n\nasync function consumeSse(\n body: ReadableStream<Uint8Array>,\n onDelta: (text: string) => void,\n): Promise<ClarifyStreamResult> {\n const reader = body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n let result: ClarifyStreamResult | null = null\n\n const flush = (raw: string): void => {\n // Each SSE record is \"event: <name>\\n data: <json>\". Parse event + data.\n const eventLine = raw.split('\\n').find(l => l.startsWith('event:'))\n const dataLine = raw.split('\\n').find(l => l.startsWith('data:'))\n if (!dataLine) return\n const event = eventLine?.slice(6).trim()\n const payload = JSON.parse(dataLine.slice(5).trim())\n if (event === 'delta' && typeof payload.text === 'string') {\n onDelta(payload.text)\n } else if (event === 'done') {\n result = {\n messages: Array.isArray(payload.messages) ? payload.messages : [],\n suggestions: Array.isArray(payload.suggestions)\n ? payload.suggestions.filter((s: unknown): s is string => typeof s === 'string')\n : [],\n done: payload.done !== false,\n failed: payload.error === true,\n }\n }\n }\n\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n buffer += decoder.decode(value, { stream: true })\n let sep: number\n while ((sep = buffer.indexOf('\\n\\n')) !== -1) {\n const record = buffer.slice(0, sep)\n buffer = buffer.slice(sep + 2)\n try { flush(record) } catch { /* skip malformed record */ }\n }\n }\n\n buffer += decoder.decode()\n if (buffer.trim()) {\n try { flush(buffer) } catch { /* handled by the missing done check below */ }\n }\n if (!result) throw new Error('clarify stream ended before done')\n\n return result\n}\n\nfunction generateIdempotencyKey(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID()\n }\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`\n}\n\nfunction generateSubmissionToken(): string {\n if (typeof crypto === 'undefined' || typeof crypto.getRandomValues !== 'function') {\n throw new Error('Secure random generation is unavailable')\n }\n\n const bytes = crypto.getRandomValues(new Uint8Array(32))\n return Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('')\n}\n","const STORAGE_KEY = 'mtb_anon_id'\nconst EMAIL_KEY = 'mtb_reporter_email'\nconst BOARD_URL_KEY = 'mtb_board_url'\n\nlet memoryId: string | null = null\nlet memoryEmail: string | null = null\nlet memoryBoardUrl: string | null = null\n\nexport function getAnonId(): string {\n try {\n const stored = window.localStorage.getItem(STORAGE_KEY)\n if (stored?.startsWith('anon_')) return stored\n const id = generateAnonId()\n window.localStorage.setItem(STORAGE_KEY, id)\n return id\n } catch {\n memoryId ??= generateAnonId()\n return memoryId\n }\n}\n\nexport function getStoredReporterEmail(): string | undefined {\n try {\n return window.localStorage.getItem(EMAIL_KEY) ?? undefined\n } catch {\n return memoryEmail ?? undefined\n }\n}\n\nexport function rememberReporterEmail(email: string): void {\n memoryEmail = email\n try {\n window.localStorage.setItem(EMAIL_KEY, email)\n } catch {\n // localStorage unavailable — the in-memory copy covers this session\n }\n}\n\nexport function getCachedBoardUrl(): string | undefined {\n try {\n return window.localStorage.getItem(BOARD_URL_KEY) ?? undefined\n } catch {\n return memoryBoardUrl ?? undefined\n }\n}\n\nexport function cacheBoardUrl(url: string): void {\n memoryBoardUrl = url\n try {\n window.localStorage.setItem(BOARD_URL_KEY, url)\n } catch {\n // localStorage unavailable — the in-memory copy covers this session\n }\n}\n\nfunction generateAnonId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return `anon_${crypto.randomUUID()}`\n }\n const random = `${Date.now().toString(16)}-${Math.random().toString(16).slice(2, 10)}`\n return `anon_${random}`\n}\n","export default {\n tab: 'Feedback',\n toolbar: {\n markup: 'Mark up',\n record: 'Record',\n hint: 'Click to comment, drag to draw',\n hintTouch: \"Drag to mark what's wrong\",\n hintRecord: 'Recording your screen',\n exit: 'Exit',\n },\n popup: {\n about: 'About',\n placeholder: 'Describe the issue or suggestion...',\n submit: 'Submit',\n sending: 'Sending...',\n cancel: 'Cancel',\n close: 'Close',\n my_feedback: 'My feedback',\n screenshot_label: 'Attach screenshot',\n screenshot_hint: 'Passwords and other sensitive fields are covered automatically.',\n screenshot_short: 'Screenshot',\n screenshot_locked_hint: 'Your drawing is baked onto the screenshot, so it is always attached. Sensitive fields are covered automatically.',\n recording_label: 'Recording',\n recording_locked_hint: 'A replay of what the page showed and how it was used — always included with a recording. No camera or microphone. Sensitive fields are covered automatically.',\n quickOptions: [\n { emoji: '🐛', label: 'Broken', value: \"Something's broken\" },\n { emoji: '💡', label: 'Suggestion', value: 'I have a suggestion' },\n { emoji: '🤔', label: 'Confused', value: \"I'm confused\" },\n ],\n },\n success: {\n title: 'Thanks, your feedback is in.',\n title_no_ai: \"Thanks! We've received your feedback.\",\n close: 'Close',\n email_saved: \"You'll get an email when it's resolved.\",\n email_error: \"Couldn't save your email. Please try again.\",\n track_feedback: 'Track your feedback',\n email_cta: 'Email me when it\\'s fixed',\n notify: 'Notify',\n },\n error: {\n submit: 'Failed to submit. Please try again.',\n finalize_title: \"We couldn't send this yet.\",\n finalize_message: 'Your feedback is still saved. Please try again.',\n },\n pet: {\n bubble: 'Need help?',\n },\n frustration: {\n prompt: 'Something not working? Tell us.',\n action: 'Annotate',\n dismiss: 'Dismiss',\n },\n annotation: {\n drawing: 'Drawing',\n element: 'element',\n },\n draw: {\n note_placeholder: 'Add a note to your drawing',\n undo: 'Undo',\n redo: 'Redo',\n cancel: 'Cancel',\n submit: 'Submit',\n },\n record: {\n stop: 'Stop',\n timer_label: 'Recording',\n max_reached: 'Maximum recording time reached',\n replay_label: 'screen-recording.json',\n play: 'Play recording',\n close: 'Close',\n preview_unavailable: 'Preview unavailable',\n },\n clarify: {\n title: 'Make This Better AI',\n subtitle: 'Sharpening your feedback',\n thinking: 'Thinking...',\n placeholder: 'Add details, then submit',\n send: 'Send',\n submit: 'Submit',\n send_feedback: 'Send feedback',\n skip: 'Skip & submit',\n skip_short: 'Skip',\n cancel: \"Cancel — don't send feedback\",\n retry: 'Retry',\n error: \"Couldn't load the question. Send your feedback as is.\",\n expired: 'This feedback session expired before it could be sent. Please close the widget and submit again.',\n },\n}\n","export default {\n tab: '反馈',\n toolbar: {\n markup: '标注',\n record: '录屏',\n hint: '点击标注,拖动绘制',\n hintTouch: '用手指圈出问题',\n hintRecord: '正在录制屏幕',\n exit: '退出',\n },\n popup: {\n about: '关于',\n placeholder: '描述问题或建议...',\n submit: '提交',\n sending: '发送中...',\n cancel: '取消',\n close: '关闭',\n my_feedback: '我的反馈',\n screenshot_label: '附带页面截图',\n screenshot_hint: '密码等敏感字段会自动打码。',\n screenshot_short: '截图',\n screenshot_locked_hint: '圈画会画在截图上,所以必定附带。敏感字段会自动打码。',\n recording_label: '录制',\n recording_locked_hint: '页面画面与操作过程的回放,录制内容必定随反馈提交。不含摄像头和麦克风,敏感字段会自动打码。',\n quickOptions: [\n { emoji: '🐛', label: '有问题', value: '出问题了' },\n { emoji: '💡', label: '建议', value: '我有一个建议' },\n { emoji: '🤔', label: '疑问', value: '我有点疑问' },\n ],\n },\n success: {\n title: '收到了,谢谢你的反馈。',\n title_no_ai: '感谢!我们已收到您的反馈。',\n close: '关闭',\n email_saved: '问题解决后您会收到邮件通知。',\n email_error: '邮箱保存失败,请重试。',\n track_feedback: '查看处理进度',\n email_cta: '解决后邮件通知我',\n notify: '通知我',\n },\n error: {\n submit: '提交失败,请重试。',\n finalize_title: '暂时无法发送。',\n finalize_message: '你的反馈仍已保存,请重试。',\n },\n pet: {\n bubble: '需要帮助吗?',\n },\n frustration: {\n prompt: '遇到问题了?告诉我们。',\n action: '标注',\n dismiss: '关闭',\n },\n annotation: {\n drawing: '绘制',\n element: '元素',\n },\n draw: {\n note_placeholder: '给你的绘制添加说明',\n undo: '撤销',\n redo: '重做',\n cancel: '取消',\n submit: '提交',\n },\n record: {\n stop: '停止',\n timer_label: '录制中',\n max_reached: '已达到最长录制时间',\n replay_label: 'screen-recording.json',\n play: '播放录屏',\n close: '关闭',\n preview_unavailable: '暂无法预览',\n },\n clarify: {\n title: 'Make This Better AI',\n subtitle: '正在完善你的反馈',\n thinking: '思考中...',\n placeholder: '补充说明后提交',\n send: '发送',\n submit: '提交',\n send_feedback: '发送反馈',\n skip: '跳过并提交',\n skip_short: '跳过',\n cancel: '取消并不发送',\n retry: '重试',\n error: '追问加载失败,可以直接发送反馈。',\n expired: '这条反馈在发送前已过期。请关闭组件后重新提交。',\n },\n}\n","export default {\n tab: 'フィードバック',\n toolbar: {\n markup: 'マークアップ',\n record: '録画',\n hint: 'クリックでコメント、ドラッグで描画',\n hintTouch: 'ドラッグして問題箇所を囲んでください',\n hintRecord: '画面を録画中',\n exit: '終了',\n },\n popup: {\n about: '概要',\n placeholder: '問題や提案を入力してください...',\n submit: '送信',\n sending: '送信中...',\n cancel: 'キャンセル',\n close: '閉じる',\n my_feedback: '自分のフィードバック',\n screenshot_label: 'スクリーンショットを添付',\n screenshot_hint: 'パスワードなどの機密項目は自動的に隠されます。',\n screenshot_short: 'スクリーンショット',\n screenshot_locked_hint: '描き込みはスクリーンショット上に焼き込まれるため、必ず添付されます。機密項目は自動的に隠されます。',\n recording_label: '記録',\n recording_locked_hint: 'ページの表示と操作の再現記録で、必ず添付されます。カメラやマイクは含まれず、機密項目は自動的に隠されます。',\n quickOptions: [\n { emoji: '🐛', label: '不具合', value: '不具合があります' },\n { emoji: '💡', label: '提案', value: '提案があります' },\n { emoji: '🤔', label: '不明点', value: 'わかりにくいです' },\n ],\n },\n success: {\n title: 'フィードバックを受け取りました。ありがとうございます。',\n title_no_ai: 'ありがとうございます!フィードバックを受け付けました。',\n close: '閉じる',\n email_saved: '解決次第メールでお知らせします。',\n email_error: 'メールアドレスを保存できませんでした。もう一度お試しください。',\n track_feedback: '対応状況を確認',\n email_cta: '解決したらメールで知らせる',\n notify: '通知',\n },\n error: {\n submit: '送信に失敗しました。もう一度お試しください。',\n finalize_title: 'まだ送信できませんでした。',\n finalize_message: 'フィードバックは保存されています。もう一度お試しください。',\n },\n pet: {\n bubble: 'お困りですか?',\n },\n frustration: {\n prompt: 'うまくいかないことがありますか?お知らせください。',\n action: '注釈',\n dismiss: '閉じる',\n },\n annotation: {\n drawing: '描画',\n element: '要素',\n },\n draw: {\n note_placeholder: '描画にメモを追加',\n undo: '元に戻す',\n redo: 'やり直す',\n cancel: 'キャンセル',\n submit: '送信',\n },\n record: {\n stop: '停止',\n timer_label: '録画中',\n max_reached: '最大記録時間に達しました',\n replay_label: 'screen-recording.json',\n play: '録画を再生',\n close: '閉じる',\n preview_unavailable: 'プレビューは利用できません',\n },\n clarify: {\n title: 'Make This Better AI',\n subtitle: 'フィードバックを整えています',\n thinking: '考え中...',\n placeholder: '詳細を追加して送信',\n send: '送信',\n submit: '送信する',\n send_feedback: 'フィードバックを送信',\n skip: 'スキップして送信',\n skip_short: 'スキップ',\n cancel: 'キャンセル — 送信しない',\n retry: '再試行',\n error: '質問を読み込めませんでした。そのままフィードバックを送信できます。',\n expired: 'このフィードバックは送信される前に有効期限が切れました。ウィジェットを閉じて、もう一度送信してください。',\n },\n}\n","export default {\n tab: '피드백',\n toolbar: {\n markup: '마크업',\n record: '녹화',\n hint: '클릭하여 댓글, 드래그하여 그리기',\n hintTouch: '드래그해서 문제가 있는 곳을 표시하세요',\n hintRecord: '화면 녹화 중',\n exit: '나가기',\n },\n popup: {\n about: '정보',\n placeholder: '문제 또는 제안 사항을 설명해 주세요...',\n submit: '제출',\n sending: '전송 중...',\n cancel: '취소',\n close: '닫기',\n my_feedback: '내 피드백',\n screenshot_label: '스크린샷 첨부',\n screenshot_hint: '비밀번호 등 민감한 입력란은 자동으로 가려집니다.',\n screenshot_short: '스크린샷',\n screenshot_locked_hint: '그린 표시는 스크린샷 위에 합쳐지므로 항상 첨부됩니다. 민감한 입력란은 자동으로 가려집니다.',\n recording_label: '기록',\n recording_locked_hint: '페이지 화면과 조작 과정의 재현 기록으로, 항상 첨부됩니다. 카메라와 마이크는 포함되지 않으며 민감한 입력란은 자동으로 가려집니다.',\n quickOptions: [\n { emoji: '🐛', label: '오류', value: '문제가 있어요' },\n { emoji: '💡', label: '제안', value: '제안이 있어요' },\n { emoji: '🤔', label: '혼란', value: '헷갈려요' },\n ],\n },\n success: {\n title: '피드백이 접수되었습니다. 감사합니다.',\n title_no_ai: '감사합니다! 피드백을 접수했습니다.',\n close: '닫기',\n email_saved: '해결되면 이메일로 알려드립니다.',\n email_error: '이메일을 저장하지 못했습니다. 다시 시도해 주세요.',\n track_feedback: '처리 상황 보기',\n email_cta: '해결되면 이메일로 알림',\n notify: '알림',\n },\n error: {\n submit: '제출에 실패했습니다. 다시 시도해 주세요.',\n finalize_title: '아직 전송하지 못했습니다.',\n finalize_message: '피드백은 저장되어 있습니다. 다시 시도해 주세요.',\n },\n pet: {\n bubble: '도움이 필요하신가요?',\n },\n frustration: {\n prompt: '문제가 있으신가요? 알려 주세요.',\n action: '주석',\n dismiss: '닫기',\n },\n annotation: {\n drawing: '그리기',\n element: '요소',\n },\n draw: {\n note_placeholder: '그림에 메모 추가',\n undo: '실행 취소',\n redo: '다시 실행',\n cancel: '취소',\n submit: '보내기',\n },\n record: {\n stop: '중지',\n timer_label: '녹화 중',\n max_reached: '최대 녹화 시간에 도달했습니다',\n replay_label: 'screen-recording.json',\n play: '녹화 재생',\n close: '닫기',\n preview_unavailable: '미리보기를 사용할 수 없습니다',\n },\n clarify: {\n title: 'Make This Better AI',\n subtitle: '피드백을 다듬는 중',\n thinking: '생각 중...',\n placeholder: '자세히 적은 뒤 제출',\n send: '보내기',\n submit: '제출',\n send_feedback: '피드백 보내기',\n skip: '건너뛰고 보내기',\n skip_short: '건너뛰기',\n cancel: '취소 — 보내지 않기',\n retry: '재시도',\n error: '질문을 불러오지 못했습니다. 피드백을 그대로 보내세요.',\n expired: '이 피드백 세션은 전송되기 전에 만료되었습니다. 위젯을 닫고 다시 보내 주세요.',\n },\n}\n","export default {\n tab: 'Comentarios',\n toolbar: {\n markup: 'Anotar',\n record: 'Grabar',\n hint: 'Clic para comentar, arrastrar para dibujar',\n hintTouch: 'Arrastra para marcar el problema',\n hintRecord: 'Grabando pantalla',\n exit: 'Salir',\n },\n popup: {\n about: 'Acerca de',\n placeholder: 'Describe el problema o sugerencia...',\n submit: 'Enviar',\n sending: 'Enviando...',\n cancel: 'Cancelar',\n close: 'Cerrar',\n my_feedback: 'Mis comentarios',\n screenshot_label: 'Adjuntar captura de pantalla',\n screenshot_hint: 'Las contraseñas y otros campos sensibles se ocultan automáticamente.',\n screenshot_short: 'Captura',\n screenshot_locked_hint: 'Tu dibujo se integra en la captura, por eso siempre se adjunta. Los campos sensibles se ocultan automáticamente.',\n recording_label: 'Grabación',\n recording_locked_hint: 'Una reproducción de lo que mostraba la página y cómo se usó; siempre se adjunta. Sin cámara ni micrófono. Los campos sensibles se ocultan automáticamente.',\n quickOptions: [\n { emoji: '🐛', label: 'Error', value: 'Algo no funciona' },\n { emoji: '💡', label: 'Sugerencia', value: 'Tengo una sugerencia' },\n { emoji: '🤔', label: 'Confuso', value: 'Estoy confundido' },\n ],\n },\n success: {\n title: 'Gracias, hemos recibido tu comentario.',\n title_no_ai: '¡Gracias! Hemos recibido tu comentario.',\n close: 'Cerrar',\n email_saved: 'Recibirás un correo cuando se resuelva.',\n email_error: 'No se pudo guardar tu correo. Inténtalo de nuevo.',\n track_feedback: 'Seguir mi comentario',\n email_cta: 'Avísame por correo cuando se arregle',\n notify: 'Avisarme',\n },\n error: {\n submit: 'Error al enviar. Por favor, inténtalo de nuevo.',\n finalize_title: 'Aún no pudimos enviarlo.',\n finalize_message: 'Tu comentario sigue guardado. Inténtalo de nuevo.',\n },\n pet: {\n bubble: '¿Necesitas ayuda?',\n },\n frustration: {\n prompt: '¿Algo no funciona? Cuéntanos.',\n action: 'Anotar',\n dismiss: 'Cerrar',\n },\n annotation: {\n drawing: 'Dibujo',\n element: 'elemento',\n },\n draw: {\n note_placeholder: 'Añade una nota a tu dibujo',\n undo: 'Deshacer',\n redo: 'Rehacer',\n cancel: 'Cancelar',\n submit: 'Enviar',\n },\n record: {\n stop: 'Detener',\n timer_label: 'Grabando',\n max_reached: 'Tiempo máximo de grabación alcanzado',\n replay_label: 'screen-recording.json',\n play: 'Reproducir grabación',\n close: 'Cerrar',\n preview_unavailable: 'Vista previa no disponible',\n },\n clarify: {\n title: 'Make This Better AI',\n subtitle: 'Afinando tus comentarios',\n thinking: 'Pensando...',\n placeholder: 'Añade detalles y envía',\n send: 'Enviar',\n submit: 'Enviar',\n send_feedback: 'Enviar comentario',\n skip: 'Omitir y enviar',\n skip_short: 'Omitir',\n cancel: 'Cancelar — no enviar',\n retry: 'Reintentar',\n error: 'No se pudo cargar la pregunta. Envía tus comentarios tal cual.',\n expired: 'Esta sesión de comentarios caducó antes de poder enviarse. Cierra el widget y envíalo de nuevo.',\n },\n}\n","export default {\n tab: 'Avis',\n toolbar: {\n markup: 'Annoter',\n record: 'Enregistrer',\n hint: 'Cliquer pour commenter, glisser pour dessiner',\n hintTouch: 'Faites glisser pour marquer le problème',\n hintRecord: \"Enregistrement de l'écran\",\n exit: 'Quitter',\n },\n popup: {\n about: 'À propos',\n placeholder: 'Décrivez le problème ou votre suggestion...',\n submit: 'Envoyer',\n sending: 'Envoi en cours...',\n cancel: 'Annuler',\n close: 'Fermer',\n my_feedback: 'Mes avis',\n screenshot_label: 'Joindre une capture d\\'écran',\n screenshot_hint: 'Les mots de passe et autres champs sensibles sont masqués automatiquement.',\n screenshot_short: 'Capture',\n screenshot_locked_hint: \"Votre dessin est intégré à la capture, elle est donc toujours jointe. Les champs sensibles sont masqués automatiquement.\",\n recording_label: 'Enregistrement',\n recording_locked_hint: \"Une relecture de ce que la page affichait et de son utilisation — toujours jointe. Ni caméra ni micro. Les champs sensibles sont masqués automatiquement.\",\n quickOptions: [\n { emoji: '🐛', label: 'Bug', value: 'Quelque chose ne fonctionne pas' },\n { emoji: '💡', label: 'Suggestion', value: \"J'ai une suggestion\" },\n { emoji: '🤔', label: 'Confus', value: 'Je suis perplexe' },\n ],\n },\n success: {\n title: 'Merci, votre retour est bien arrivé.',\n title_no_ai: 'Merci ! Nous avons bien reçu votre avis.',\n close: 'Fermer',\n email_saved: 'Vous recevrez un e-mail une fois résolu.',\n email_error: \"Impossible d'enregistrer votre e-mail. Veuillez réessayer.\",\n track_feedback: 'Suivre mon retour',\n email_cta: 'M’avertir par e-mail une fois corrigé',\n notify: 'Avertir',\n },\n error: {\n submit: \"Échec de l'envoi. Veuillez réessayer.\",\n finalize_title: \"Nous n'avons pas encore pu l'envoyer.\",\n finalize_message: 'Votre retour est toujours enregistré. Veuillez réessayer.',\n },\n pet: {\n bubble: \"Besoin d'aide ?\",\n },\n frustration: {\n prompt: 'Un problème ? Dites-le-nous.',\n action: 'Annoter',\n dismiss: 'Fermer',\n },\n annotation: {\n drawing: 'Dessin',\n element: 'élément',\n },\n draw: {\n note_placeholder: 'Ajoutez une note à votre dessin',\n undo: 'Annuler',\n redo: 'Rétablir',\n cancel: 'Annuler',\n submit: 'Envoyer',\n },\n record: {\n stop: 'Arrêter',\n timer_label: 'Enregistrement',\n max_reached: \"Durée maximale d'enregistrement atteinte\",\n replay_label: 'screen-recording.json',\n play: \"Lire l'enregistrement\",\n close: 'Fermer',\n preview_unavailable: 'Aperçu indisponible',\n },\n clarify: {\n title: 'Make This Better AI',\n subtitle: 'Affinement de votre retour',\n thinking: 'Réflexion...',\n placeholder: 'Ajoutez des détails, puis envoyez',\n send: 'Envoyer',\n submit: 'Soumettre',\n send_feedback: 'Envoyer le retour',\n skip: 'Ignorer et envoyer',\n skip_short: 'Ignorer',\n cancel: 'Annuler — ne pas envoyer',\n retry: 'Réessayer',\n error: 'Impossible de charger la question. Envoyez votre avis tel quel.',\n expired: 'Cette session de retour a expiré avant d\\'avoir pu être envoyée. Fermez le widget et renvoyez-la.',\n },\n}\n","export default {\n tab: 'Feedback',\n toolbar: {\n markup: 'Markieren',\n record: 'Aufnehmen',\n hint: 'Klicken zum Kommentieren, ziehen zum Zeichnen',\n hintTouch: 'Ziehen, um das Problem zu markieren',\n hintRecord: 'Bildschirm wird aufgenommen',\n exit: 'Beenden',\n },\n popup: {\n about: 'Info',\n placeholder: 'Beschreiben Sie das Problem oder Ihren Vorschlag...',\n submit: 'Absenden',\n sending: 'Wird gesendet...',\n cancel: 'Abbrechen',\n close: 'Schließen',\n my_feedback: 'Mein Feedback',\n screenshot_label: 'Screenshot anhängen',\n screenshot_hint: 'Passwörter und andere sensible Felder werden automatisch abgedeckt.',\n screenshot_short: 'Screenshot',\n screenshot_locked_hint: 'Deine Zeichnung wird in den Screenshot eingebrannt, daher wird er immer angehängt. Sensible Felder werden automatisch abgedeckt.',\n recording_label: 'Aufzeichnung',\n recording_locked_hint: 'Eine Wiedergabe dessen, was die Seite zeigte und wie sie bedient wurde — immer angehängt. Keine Kamera, kein Mikrofon. Sensible Felder werden automatisch abgedeckt.',\n quickOptions: [\n { emoji: '🐛', label: 'Fehler', value: 'Etwas funktioniert nicht' },\n { emoji: '💡', label: 'Vorschlag', value: 'Ich habe einen Vorschlag' },\n { emoji: '🤔', label: 'Unklar', value: 'Ich bin verwirrt' },\n ],\n },\n success: {\n title: 'Danke, Ihr Feedback ist angekommen.',\n title_no_ai: 'Vielen Dank! Wir haben Ihr Feedback erhalten.',\n close: 'Schließen',\n email_saved: 'Sie erhalten eine E-Mail, sobald es gelöst ist.',\n email_error: 'E-Mail konnte nicht gespeichert werden. Bitte versuchen Sie es erneut.',\n track_feedback: 'Feedback verfolgen',\n email_cta: 'E-Mail, sobald es behoben ist',\n notify: 'Melden',\n },\n error: {\n submit: 'Senden fehlgeschlagen. Bitte versuchen Sie es erneut.',\n finalize_title: 'Wir konnten dies noch nicht senden.',\n finalize_message: 'Ihr Feedback ist weiterhin gespeichert. Bitte versuchen Sie es erneut.',\n },\n pet: {\n bubble: 'Brauchen Sie Hilfe?',\n },\n frustration: {\n prompt: 'Etwas funktioniert nicht? Sagen Sie es uns.',\n action: 'Kommentieren',\n dismiss: 'Schließen',\n },\n annotation: {\n drawing: 'Zeichnung',\n element: 'Element',\n },\n draw: {\n note_placeholder: 'Fügen Sie Ihrer Zeichnung eine Notiz hinzu',\n undo: 'Rückgängig',\n redo: 'Wiederholen',\n cancel: 'Abbrechen',\n submit: 'Absenden',\n },\n record: {\n stop: 'Stopp',\n timer_label: 'Aufnahme',\n max_reached: 'Maximale Aufnahmedauer erreicht',\n replay_label: 'screen-recording.json',\n play: 'Aufnahme abspielen',\n close: 'Schließen',\n preview_unavailable: 'Vorschau nicht verfügbar',\n },\n clarify: {\n title: 'Make This Better AI',\n subtitle: 'Wir schärfen dein Feedback',\n thinking: 'Einen Moment...',\n placeholder: 'Details ergänzen, dann absenden',\n send: 'Senden',\n submit: 'Absenden',\n send_feedback: 'Feedback senden',\n skip: 'Überspringen & senden',\n skip_short: 'Überspringen',\n cancel: 'Abbrechen — nicht senden',\n retry: 'Erneut versuchen',\n error: 'Die Frage konnte nicht geladen werden. Senden Sie Ihr Feedback einfach so.',\n expired: 'Diese Feedback-Sitzung ist abgelaufen, bevor sie gesendet werden konnte. Bitte schließe das Widget und sende erneut.',\n },\n}\n","import en from './en'\nimport zhCN from './zh-CN'\nimport ja from './ja'\nimport ko from './ko'\nimport es from './es'\nimport fr from './fr'\nimport de from './de'\n\nexport type I18nMessages = typeof en\n\nconst locales: Record<string, I18nMessages> = {\n en,\n 'zh-CN': zhCN,\n ja,\n ko,\n es,\n fr,\n de,\n}\n\nexport function getMessages(locale: string): I18nMessages {\n if (locales[locale]) return locales[locale]\n const lang = locale.split('-')[0]\n if (locales[lang]) return locales[lang]\n return locales['en']\n}\n","import type { ShadowContainer } from '../widget/shadow'\nimport { BLOCK_CLASS, MASK_TEXT_CLASS } from '../privacy/dom'\nimport { filterInputValue } from '../privacy/fields'\nimport { FILTERED_VALUE, filterSensitiveValue } from '../privacy/sanitize'\nimport {\n createSensitiveTextContext,\n hasSensitiveTextContext,\n type SensitiveTextContext,\n} from '../privacy/text-context'\n\ntype RRWebEvent = Record<string, unknown>\ntype StopFn = () => void\n\ninterface RRWebRecordOptions {\n emit: (event: RRWebEvent) => void\n maskAllInputs: boolean\n maskInputOptions: Record<string, boolean>\n maskInputFn: (text: string, element: HTMLElement) => string\n maskTextFn: () => string\n blockClass: string\n maskTextClass: string\n}\n\ninterface RRWebRecordFn {\n (options: RRWebRecordOptions): StopFn\n addCustomEvent: (tag: string, payload: Record<string, unknown>) => void\n mirror: { getNode: (id: number) => Node | null }\n}\n\nexport interface RecordingResult {\n events: RRWebEvent[]\n duration: number\n}\n\nconst MAX_DURATION_S = 60\nconst VALUE_ATTRIBUTE_NAMES = ['value', 'content'] as const\nconst MASKED_INPUT_TYPES = Object.fromEntries([\n 'button',\n 'checkbox',\n 'color',\n 'date',\n 'datetime-local',\n 'email',\n 'file',\n 'hidden',\n 'image',\n 'month',\n 'number',\n 'password',\n 'radio',\n 'range',\n 'reset',\n 'search',\n 'select',\n 'submit',\n 'tel',\n 'text',\n 'textarea',\n 'time',\n 'url',\n 'week',\n].map(type => [type, true]))\n// The marker classes live with the rest of the privacy policy in privacy/dom;\n// re-exported here because this module has been their documented import point\n// since 1.x.\nexport { BLOCK_CLASS, MASK_TEXT_CLASS }\n// Pinned version + SRI: this script executes on customers' pages, so a\n// floating tag or unverified content would be a supply-chain hole. Bumping\n// the version requires recomputing the hash from the exact CDN file:\n// curl -sL <url> | openssl dgst -sha384 -binary | openssl base64 -A\n// Must stay on @rrweb/record's static /umd/ build: the `rrweb` package ships\n// no UMD in 2.x (its dist files are ESM and throw \"Unexpected token 'export'\"\n// in a classic script tag), and jsDelivr's auto-minified files are unsafe for\n// SRI because their hash changes when jsDelivr upgrades its minifier.\nconst RRWEB_CDN = 'https://cdn.jsdelivr.net/npm/@rrweb/record@2.1.0/umd/record.min.js'\nconst RRWEB_SRI = 'sha384-MrD66HBNSykaP2N95+6hQCFlF5oH2tvL3TD/zyvHNkP/sAFWZx98DX9MEDy8MdVT'\n\nlet rrwebLoaded: Promise<RRWebRecordFn> | null = null\n\n// The IIFE/CDN bundle runs as a classic script, where the bare\n// import('@rrweb/record') can never resolve — without this flag every CDN\n// user's first recording would pay a thrown TypeError (console noise on the\n// customer's page) plus a wasted round before the CDN fallback. The typeof\n// guard keeps the untransformed source (vitest) on the ESM path.\nfunction isIifeBuild(): boolean {\n return typeof __MTB_IIFE__ !== 'undefined' && __MTB_IIFE__\n}\n\nfunction loadRrweb(): Promise<RRWebRecordFn> {\n if (rrwebLoaded) return rrwebLoaded\n const load = isIifeBuild() ? loadViaCdn() : loadViaImport().catch(() => loadViaCdn())\n // A rejection must not stay cached: one flaky-network failure would otherwise\n // make every later Record tap fail instantly until the page reloads. Same\n // reset-on-failure as record/replay.ts.\n rrwebLoaded = load.catch((err) => {\n rrwebLoaded = null\n throw err\n })\n return rrwebLoaded\n}\n\nfunction filterReplayEvent(event: RRWebEvent, record: RRWebRecordFn): RRWebEvent {\n const filtered = filterSensitiveValue(event)\n filterReplayDomValues(filtered, record, createSensitiveTextContext())\n return filtered\n}\n\nfunction filterReplayDomValues(\n value: unknown,\n record: RRWebRecordFn,\n textContext: SensitiveTextContext,\n): void {\n if (Array.isArray(value)) {\n value.forEach(item => filterReplayDomValues(item, record, textContext))\n return\n }\n if (!isRecord(value)) return\n\n const attributes = isRecord(value.attributes) ? value.attributes : null\n const hasAttributeValue = attributes && VALUE_ATTRIBUTE_NAMES.some(name => typeof attributes[name] === 'string')\n const hasNodeValue = typeof value.text === 'string'\n || typeof value.textContent === 'string'\n || typeof value.value === 'string'\n || hasAttributeValue\n const node = hasNodeValue && typeof value.id === 'number' ? record.mirror.getNode(value.id) : null\n if (node instanceof Element) {\n if (typeof value.text === 'string') value.text = filterInputValue(value.text, node)\n if (attributes) {\n for (const name of VALUE_ATTRIBUTE_NAMES) {\n const attributeValue = attributes[name]\n if (typeof attributeValue === 'string') attributes[name] = filterInputValue(attributeValue, node)\n }\n }\n } else if (node?.nodeType === Node.TEXT_NODE && hasSensitiveTextContext(node, textContext)) {\n if (typeof value.textContent === 'string') value.textContent = FILTERED_VALUE\n if (typeof value.value === 'string') value.value = FILTERED_VALUE\n }\n\n Object.values(value).forEach(item => filterReplayDomValues(item, record, textContext))\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === 'object' && !Array.isArray(value)\n}\n\nasync function loadViaImport(): Promise<RRWebRecordFn> {\n const mod = await import('@rrweb/record')\n return mod.record as unknown as RRWebRecordFn\n}\n\ninterface RRWebGlobals {\n rrwebRecord?: { record: RRWebRecordFn }\n rrweb?: { record: RRWebRecordFn }\n}\n\nfunction findGlobalRecord(): RRWebRecordFn | undefined {\n const win = globalThis as unknown as Window & RRWebGlobals\n return win.rrwebRecord?.record ?? win.rrweb?.record\n}\n\nfunction loadViaCdn(): Promise<RRWebRecordFn> {\n return new Promise((resolve, reject) => {\n const existing = findGlobalRecord()\n if (existing) { resolve(existing); return }\n const script = document.createElement('script')\n script.src = RRWEB_CDN\n script.integrity = RRWEB_SRI\n script.crossOrigin = 'anonymous'\n script.onload = () => {\n const record = findGlobalRecord()\n if (record) resolve(record)\n else reject(new Error('rrweb record global not found after script load'))\n }\n script.onerror = () => reject(new Error('Failed to load rrweb from CDN'))\n document.head.appendChild(script)\n })\n}\n\nexport class RecordSession {\n private events: RRWebEvent[] = []\n private stopFn: StopFn | null = null\n private startedAt = 0\n private timer: ReturnType<typeof setTimeout> | null = null\n private clickHandler: ((e: MouseEvent) => void) | null = null\n private shadow: ShadowContainer\n private onMaxReached?: () => void\n private stopped = false\n private destroyed = false\n\n constructor(shadow: ShadowContainer, onMaxReached?: () => void) {\n this.shadow = shadow\n this.onMaxReached = onMaxReached\n }\n\n async start(): Promise<void> {\n const record = await loadRrweb()\n\n // A cold dynamic import takes hundreds of milliseconds, and the reporter can\n // cancel the replay inside that window. Without this guard the resumed start()\n // would attach a recorder, a document click listener and a 60s timer that no\n // live object holds a handle to — rrweb would then serialize every mutation of\n // the host page for the rest of the session, after an explicit cancel.\n if (this.destroyed) return\n\n this.startedAt = Date.now()\n this.stopped = false\n\n this.stopFn = record({\n emit: (event: RRWebEvent) => {\n if (this.stopped) return\n try {\n this.events.push(filterReplayEvent(event, record))\n } catch {\n // A malformed host event must not bypass filtering or stop recording.\n }\n },\n // Explicit options include hidden/file, which rrweb's maskAllInputs=true\n // preset omits. Every value is routed through the fixed policy, where\n // ordinary values are returned unchanged.\n maskAllInputs: false,\n maskInputOptions: MASKED_INPUT_TYPES,\n maskInputFn: (text, element) => (\n element.closest(`.${MASK_TEXT_CLASS}`) ? FILTERED_VALUE : filterInputValue(text, element)\n ),\n maskTextFn: () => FILTERED_VALUE,\n blockClass: BLOCK_CLASS,\n maskTextClass: MASK_TEXT_CLASS,\n })\n\n this.clickHandler = (e: MouseEvent) => {\n this.showClickRipple(e.clientX, e.clientY)\n record.addCustomEvent?.('mtb-click', {\n x: e.clientX,\n y: e.clientY,\n target: e.target instanceof HTMLElement ? e.target.tagName : '',\n })\n }\n document.addEventListener('click', this.clickHandler, true)\n\n this.timer = setTimeout(() => {\n this.onMaxReached?.()\n }, MAX_DURATION_S * 1000)\n }\n\n stop(): RecordingResult {\n this.stopped = true\n this.stopFn?.()\n this.stopFn = null\n this.cleanupTimer()\n this.cleanupClickHandler()\n\n return {\n events: this.events,\n duration: this.getDuration(),\n }\n }\n\n getDuration(): number {\n if (this.startedAt === 0) return 0\n return Math.max(1, Math.round((Date.now() - this.startedAt) / 1000))\n }\n\n destroy(): void {\n this.destroyed = true\n this.stopped = true\n this.stopFn?.()\n this.stopFn = null\n this.cleanupTimer()\n this.cleanupClickHandler()\n this.events = []\n this.startedAt = 0\n }\n\n private cleanupTimer(): void {\n if (this.timer) {\n clearTimeout(this.timer)\n this.timer = null\n }\n }\n\n private cleanupClickHandler(): void {\n if (this.clickHandler) {\n document.removeEventListener('click', this.clickHandler, true)\n this.clickHandler = null\n }\n }\n\n private showClickRipple(x: number, y: number): void {\n const ripple = this.shadow.el<HTMLDivElement>('div', 'mtb-click-ripple')\n ripple.style.left = `${x}px`\n ripple.style.top = `${y}px`\n this.shadow.append(ripple)\n ripple.addEventListener('animationend', () => ripple.remove())\n }\n}\n","import type { ShadowContainer } from '../widget/shadow'\nimport type { I18nMessages } from '../i18n'\nimport { escapeHtml } from '../context/dom-utils'\n\nexport class RecordControlBar {\n private el: HTMLDivElement\n private timerEl!: HTMLSpanElement\n private intervalId: ReturnType<typeof setInterval> | null = null\n private getDuration: () => number\n\n constructor(\n shadow: ShadowContainer,\n messages: I18nMessages,\n getDuration: () => number,\n onStop: () => void,\n ) {\n this.getDuration = getDuration\n\n this.el = shadow.el<HTMLDivElement>('div', 'mtb-record-bar')\n this.el.innerHTML = `\n <span class=\"mtb-record-dot\"></span>\n <span class=\"mtb-record-timer\">00:00</span>\n <button class=\"mtb-record-stop\" type=\"button\">${escapeHtml(messages.record.stop)}</button>\n `\n shadow.append(this.el)\n\n this.timerEl = this.el.querySelector('.mtb-record-timer')!\n this.el.querySelector('.mtb-record-stop')!.addEventListener('click', onStop)\n\n this.intervalId = setInterval(() => this.updateTimer(), 1000)\n }\n\n private updateTimer(): void {\n const secs = this.getDuration()\n const mm = String(Math.floor(secs / 60)).padStart(2, '0')\n const ss = String(secs % 60).padStart(2, '0')\n this.timerEl.textContent = `${mm}:${ss}`\n }\n\n destroy(): void {\n if (this.intervalId) {\n clearInterval(this.intervalId)\n this.intervalId = null\n }\n this.el.remove()\n }\n}\n","import { RecordSession } from '../record/session'\nimport type { RecordingResult } from '../record/session'\nimport { RecordControlBar } from '../record/control-bar'\nimport type { ShadowContainer } from './shadow'\nimport type { I18nMessages } from '../i18n'\n\nexport type { RecordingResult }\n\nexport class RecordingManager {\n private session: RecordSession | null = null\n private bar: RecordControlBar | null = null\n private result: RecordingResult | null = null\n\n async start(\n shadow: ShadowContainer,\n messages: I18nMessages,\n onStop: () => void,\n ): Promise<void> {\n this.session = new RecordSession(shadow, onStop)\n this.bar = new RecordControlBar(\n shadow,\n messages,\n () => this.session?.getDuration() ?? 0,\n onStop,\n )\n await this.session.start()\n }\n\n stop(): RecordingResult | null {\n if (!this.session) return null\n this.result = this.session.stop()\n this.teardownUI()\n return this.result\n }\n\n getRecording(): RecordingResult | null {\n return this.result\n }\n\n destroy(): void {\n this.teardownUI()\n this.result = null\n }\n\n private teardownUI(): void {\n this.session?.destroy()\n this.session = null\n this.bar?.destroy()\n this.bar = null\n }\n}\n","import { sanitizeUrl } from '../privacy/sanitize'\n\nexport interface PageContext {\n page_url: string\n user_agent: string\n browser: string\n os: string\n screen_width: number\n screen_height: number\n}\n\nexport function collectPageContext(): PageContext {\n return {\n page_url: sanitizeUrl(window.location.href),\n user_agent: navigator.userAgent,\n browser: detectBrowser(),\n os: detectOS(),\n screen_width: window.screen.width,\n screen_height: window.screen.height,\n }\n}\n\nfunction detectBrowser(): string {\n const ua = navigator.userAgent\n if (/Edg\\//.test(ua)) return `Edge ${ua.match(/Edg\\/(\\S+)/)?.[1] ?? ''}`\n if (/OPR\\//.test(ua)) return `Opera ${ua.match(/OPR\\/(\\S+)/)?.[1] ?? ''}`\n if (/Chrome\\//.test(ua)) return `Chrome ${ua.match(/Chrome\\/(\\S+)/)?.[1] ?? ''}`\n if (/Safari\\//.test(ua) && /Version\\//.test(ua)) return `Safari ${ua.match(/Version\\/(\\S+)/)?.[1] ?? ''}`\n if (/Firefox\\//.test(ua)) return `Firefox ${ua.match(/Firefox\\/(\\S+)/)?.[1] ?? ''}`\n return 'Unknown'\n}\n\nfunction detectOS(): string {\n const ua = navigator.userAgent\n if (/Windows NT 10/.test(ua)) return 'Windows 10/11'\n if (/Windows NT/.test(ua)) return 'Windows'\n if (/Mac OS X 10_15|macOS 1[56789]/.test(ua)) return 'macOS'\n if (/Mac OS X/.test(ua)) return 'macOS'\n if (/Android/.test(ua)) return `Android ${ua.match(/Android (\\d+)/)?.[1] ?? ''}`\n if (/iPhone|iPad|iPod/.test(ua)) return 'iOS'\n if (/Linux/.test(ua)) return 'Linux'\n return 'Unknown'\n}\n","import type { PageContext } from '../context/collector'\nimport { filterSensitiveValue, sanitizeUrl } from '../privacy/sanitize'\nimport type { Annotation, Breadcrumb, FeedbackPayload, MakeThisBetterConfig } from '../types'\nimport type { RecordingResult } from '../record/session'\n\nexport interface BuildPayloadOptions {\n description: string\n pageContext: PageContext\n consoleErrors: string[]\n annotation: Annotation | null\n user: MakeThisBetterConfig['user']\n recording?: RecordingResult\n anonId?: string\n anonEmail?: string\n breadcrumbs?: Breadcrumb[]\n}\n\nexport function buildPayload(options: BuildPayloadOptions): FeedbackPayload {\n const { description, pageContext, consoleErrors, annotation, user, recording, anonId, anonEmail, breadcrumbs } = options\n\n const capturedContext = filterSensitiveValue({\n description,\n ...pageContext,\n page_url: sanitizeUrl(pageContext.page_url),\n console_errors: consoleErrors,\n annotations: annotation ? [annotation] : [],\n target_element: annotation?.targetSelector\n ? {\n selector: annotation.targetSelector,\n text: annotation.targetText ?? '',\n name: annotation.targetName ?? '',\n }\n : undefined,\n recording_events: recording?.events,\n recording_duration: recording?.duration,\n breadcrumbs,\n })\n\n return {\n ...capturedContext,\n user_id: user?.id,\n user_email: user?.email,\n user_name: user?.name,\n // An identified user carries no anonymous identity: the host has vouched\n // for who the reporter is, so attaching the stored anon id/email would tie\n // that account to a stranger's earlier anonymous submissions.\n reporter_external_id: user ? undefined : anonId,\n reporter_email: user ? undefined : anonEmail,\n }\n}\n","import { bakeAnnotatedScreenshot, captureBaseScreenshot, warmFontEmbedCss } from '../screenshot/capture'\nimport { collectPageContext } from '../context/collector'\nimport { buildPayload } from './payload'\nimport { getAnonId, getStoredReporterEmail } from './anon-id'\nimport type { ApiClient } from '../api/client'\nimport type { ConsoleErrorCollector } from '../context/console'\nimport type { BreadcrumbCollector } from '../context/breadcrumbs'\nimport type { ShadowContainer } from './shadow'\nimport type { RecordingResult } from './recording-manager'\nimport type { WidgetBrandColors } from './brand-colors'\nimport type { getMessages } from '../i18n'\nimport type { MakeThisBetterConfig, Annotation, SubmissionSessionResponse, FeedbackResponse } from '../types'\n\nconst MIN_FINALIZE_LOADING_MS = 300\nexport const SCREENSHOT_WAIT_MS = 5_000\n\n/**\n * Mode/UI transitions the lifecycle needs from its owner. The controller owns\n * mode and the launcher tab; the lifecycle only reports when a submission\n * outcome forces a transition, so \"which UI is on screen\" stays decided in one\n * place.\n */\nexport interface SubmissionLifecycleHost {\n /** True once the conversation has transitioned to its final receipt state. */\n isBackground(): boolean\n /** Replace the current conversation receipt with its retryable failure state. */\n onFinalizeFailure(onRetry?: () => void): void\n /** Return to the background-finalizing state ahead of a retry. */\n onReenterBackground(): void\n /** A finalize completed: hand off the feedback and show success. */\n onFinalized(feedback: FeedbackResponse, submission: SubmissionSessionResponse): void\n}\n\n/**\n * Everything the submission pipeline reads from the widget. Getters where the\n * value can change after construction (locale swaps messages; shadow only\n * exists once startup succeeded).\n */\nexport interface SubmissionLifecycleDeps {\n apiClient: ApiClient\n consoleCollector: ConsoleErrorCollector\n breadcrumbCollector: BreadcrumbCollector\n brandColors: WidgetBrandColors | undefined\n shadow: () => ShadowContainer\n messages: () => ReturnType<typeof getMessages>\n user: () => MakeThisBetterConfig['user']\n}\n\n/**\n * The submission concurrency state machine: attempt versioning, stale-request\n * abandonment, the foreground/background finalize dual path, and the failure\n * card's retry loop. Extracted so the controller's mode/UI orchestration and\n * this bookkeeping cannot silently interleave.\n */\nexport class SubmissionLifecycle {\n private activeSubmission: SubmissionSessionResponse | null = null\n private pendingSubmissionRequest: Promise<SubmissionSessionResponse | null> | null = null\n private submissionAttempt = 0\n private pendingScreenshotTask: Promise<void> | null = null\n private screenshotAbortController: AbortController | null = null\n private screenshotEnabled = true\n\n constructor(\n private deps: SubmissionLifecycleDeps,\n private host: SubmissionLifecycleHost,\n ) {}\n\n /**\n * What the note surface must render. Reading it back rather than assuming\n * the default is what keeps the checkbox from claiming a screenshot is\n * attached while this object has already been told otherwise — the popup is\n * remounted mid-session (record mode returns through it), so \"freshly opened\"\n * does not imply \"freshly consented\".\n */\n isScreenshotEnabled(): boolean {\n return this.screenshotEnabled\n }\n\n /**\n * Back to the default for a fresh report. Unlike setScreenshotEnabled(true)\n * this starts no capture: the widget is idle at both call sites, and\n * rasterizing a page nobody is annotating would be work done for a report\n * that may never be written.\n */\n resetScreenshotConsent(): void {\n this.screenshotEnabled = true\n }\n\n /**\n * Consent, as reported by the note surface's checkbox.\n *\n * Turning it off prevents capture at submit. Turning it back on warms the\n * lazy screenshot dependency while the reporter is still composing.\n */\n setScreenshotEnabled(enabled: boolean): void {\n this.screenshotEnabled = enabled\n if (enabled) {\n this.warmScreenshotDeps()\n return\n }\n this.screenshotAbortController?.abort()\n }\n\n // Load the screenshot dependency and font CSS ahead of submit. Bitmap capture\n // starts alongside Session creation; only cross-origin fallback waits for it.\n warmScreenshotDeps(): void {\n if (!this.screenshotEnabled) return\n void warmFontEmbedCss()\n }\n\n start(\n description: string,\n annotation: Annotation | null,\n recording: RecordingResult | undefined,\n ): Promise<SubmissionSessionResponse | null> {\n const submissionAttempt = ++this.submissionAttempt\n const request = this.createSubmission(description, annotation, recording, submissionAttempt)\n this.pendingSubmissionRequest = request\n return request\n }\n\n private async createSubmission(\n description: string,\n annotation: Annotation | null,\n recording: RecordingResult | undefined,\n submissionAttempt: number,\n ): Promise<SubmissionSessionResponse | null> {\n await waitForPaint()\n if (submissionAttempt !== this.submissionAttempt) return null\n const pageContext = collectPageContext()\n\n const payload = buildPayload({\n description,\n pageContext,\n consoleErrors: this.deps.consoleCollector.getErrors(),\n annotation,\n user: this.deps.user(),\n recording,\n anonId: getAnonId(),\n anonEmail: getStoredReporterEmail(),\n breadcrumbs: this.deps.breadcrumbCollector.getBreadcrumbs(),\n })\n\n // Create the Session and capture concurrently. Directly readable resources\n // can finish without the Session; only proxy fallback and upload await it.\n let submissionRequest: Promise<SubmissionSessionResponse>\n try {\n submissionRequest = this.deps.apiClient.createSubmissionSession(payload, null)\n } catch {\n return null\n }\n\n let screenshotController: AbortController | null = null\n if (this.screenshotEnabled) {\n const controller = new AbortController()\n screenshotController = controller\n this.screenshotAbortController?.abort()\n this.screenshotAbortController = controller\n const screenshotTask = (async () => {\n try {\n const resolver = async (resourceUrl: string, contentType?: string) => {\n const submission = await submissionRequest\n if (controller.signal.aborted || submissionAttempt !== this.submissionAttempt) return undefined\n return this.deps.apiClient.resolveScreenshotResourceUrl(\n submission.id,\n submission.token,\n resourceUrl,\n contentType,\n controller.signal,\n )\n }\n const base = await captureBaseScreenshot(resolver, controller.signal)\n if (controller.signal.aborted || submissionAttempt !== this.submissionAttempt || !base) return\n const screenshot = await bakeAnnotatedScreenshot(base, annotation ? [annotation] : [], this.deps.brandColors)\n if (controller.signal.aborted || submissionAttempt !== this.submissionAttempt || !screenshot) return\n const submission = await submissionRequest\n if (controller.signal.aborted || submissionAttempt !== this.submissionAttempt) return\n await this.deps.apiClient.uploadScreenshot(submission.id, submission.token, screenshot, controller.signal)\n } catch { /* screenshot is best-effort */ }\n })()\n const trackedTask = screenshotTask.finally(() => {\n if (this.pendingScreenshotTask === trackedTask) this.pendingScreenshotTask = null\n if (this.screenshotAbortController === controller) this.screenshotAbortController = null\n })\n this.pendingScreenshotTask = trackedTask\n }\n\n let submission: SubmissionSessionResponse\n try {\n submission = await submissionRequest\n if (submissionAttempt !== this.submissionAttempt) {\n screenshotController?.abort()\n await this.deps.apiClient.abandonSubmissionSession(submission.id, submission.token).catch(() => {})\n return null\n }\n this.activeSubmission = submission\n } catch {\n screenshotController?.abort()\n return null\n }\n\n return submission\n }\n\n async finalize(): Promise<void> {\n const submissionAttempt = this.submissionAttempt\n // Consent is per submission and this one is decided — its screenshot was\n // captured (or declined) at session creation. Resetting here, when the\n // receipt renders, rather than when the finalize round trip lands, means a\n // reporter who starts their next report immediately gets a fresh default\n // instead of inheriting a decline through the in-flight window.\n this.resetScreenshotConsent()\n // The conversation has become a receipt on the skip path. A finalization\n // failure replaces that receipt with an in-card retry state.\n const background = this.host.isBackground()\n // Finalize can be asked for (skip button) while the session upload is still\n // in flight — wait for it rather than failing a healthy submission.\n const submission = this.activeSubmission ?? await (this.pendingSubmissionRequest ?? Promise.resolve(null))\n if (submissionAttempt !== this.submissionAttempt) return\n if (!submission) {\n if (background) return this.showFinalizeFailure({ canRetry: false })\n throw new Error('No active submission session')\n }\n\n await this.waitForScreenshot()\n if (submissionAttempt !== this.submissionAttempt) return\n\n // The loading floor protects the clarify card's busy state; on the\n // background path the card is gone, so waiting would only delay the result.\n const minimumLoading = background\n ? Promise.resolve()\n : new Promise<void>((resolve) => setTimeout(resolve, MIN_FINALIZE_LOADING_MS))\n let feedback: FeedbackResponse\n try {\n feedback = await this.deps.apiClient.finalizeSubmissionSession(submission.id, submission.token)\n } catch (error) {\n if (background) return this.showFinalizeFailure({ canRetry: true })\n throw error\n }\n await Promise.all([minimumLoading, waitForPaint()])\n if (submissionAttempt !== this.submissionAttempt || this.activeSubmission !== submission) return\n\n this.activeSubmission = null\n this.pendingSubmissionRequest = null\n this.host.onFinalized(feedback, submission)\n }\n\n private async waitForScreenshot(): Promise<void> {\n const task = this.pendingScreenshotTask\n if (!task) return\n\n let timer: ReturnType<typeof setTimeout> | undefined\n const completed = await Promise.race([\n task.then(() => true),\n new Promise<false>((resolve) => {\n timer = setTimeout(() => resolve(false), SCREENSHOT_WAIT_MS)\n }),\n ])\n if (timer !== undefined) clearTimeout(timer)\n if (!completed) {\n this.screenshotAbortController?.abort()\n if (this.pendingScreenshotTask === task) this.pendingScreenshotTask = null\n }\n }\n\n private showFinalizeFailure(options: {canRetry: boolean}): void {\n this.host.onFinalizeFailure(options.canRetry ? () => this.retryBackgroundFinalization() : undefined)\n }\n\n private retryBackgroundFinalization(): void {\n // Re-enter the background state so another failure comes back to this card\n // rather than throwing with nothing on screen.\n this.host.onReenterBackground()\n void this.finalize().catch(() => this.showFinalizeFailure({canRetry: true}))\n }\n\n /**\n * Invalidate every in-flight attempt and abandon what already reached the\n * server. Both exit and destroy route through here.\n */\n abandon(): void {\n this.submissionAttempt += 1\n this.screenshotAbortController?.abort()\n this.screenshotAbortController = null\n this.pendingScreenshotTask = null\n // Consent is per submission, not per visitor. A reporter who declined once\n // on a page with something private on it is not declining for every report\n // they ever file, and a team silently receiving no screenshots forever\n // would have no way to notice why.\n //\n // This covers the exit path only. A successful submit never routes through\n // exitAll, so the controller resets there as well.\n this.resetScreenshotConsent()\n const submission = this.activeSubmission\n this.activeSubmission = null\n this.pendingSubmissionRequest = null\n if (submission) {\n void this.deps.apiClient.abandonSubmissionSession(submission.id, submission.token).catch(() => {})\n }\n }\n}\n\nfunction waitForPaint(): Promise<void> {\n return new Promise<void>((r) => requestAnimationFrame(() => setTimeout(r, 0)))\n}\n","import { isTouchPointer } from './pointer-kind'\nimport type { ToolbarMode } from '../annotate/toolbar'\nimport type { TargetRect } from '../types'\n\nexport type ModeKind = 'idle' | 'annotating' | 'recording' | 'drawing' | 'popup' | 'clarifying' | 'submitting'\n\nexport interface NotePopupOptions {\n x: number\n y: number\n targetName: string\n targetRect?: TargetRect\n}\n\n/**\n * The controller-side verbs a mode may perform. Each verb owns one concrete\n * mount, unmount, or hand-off; the states own which verbs run for which input\n * in which mode. Kept as an interface (implemented by WidgetController) so the\n * coupling between the machine and its resources is written down in one place.\n */\nexport interface ModeHost {\n transitionTo(next: ModeState): void\n exitAll(): void\n dismissFrustrationPrompt(): void\n /** True once the annotation toolbar is mounted — the flow chrome exists. */\n hasAnnotationChrome(): boolean\n /** First entry into markup: font warmup, toolbar, drawing surface, mobile strip. */\n mountAnnotationChrome(): void\n ensureSession(): void\n destroySession(): void\n clearCurrentAnnotation(): void\n teardownDrawBar(): void\n destroyRecordingManager(): void\n startRecorder(): void\n stopRecordingIntoNote(): void\n cancelDraw(): void\n mountDrawBar(): void\n mountDrawSheet(): void\n refreshDrawBar(): void\n mountNotePopup(opts: NotePopupOptions): void\n /** Abandon the note popup without ending the flow: surface, scrim and scroll lock come down, annotating resumes. */\n dismissNotePopup(): void\n /** Dismiss the success card from the previous submission. */\n dismissSuccess(): void\n revertToolbarDuringClarify(): void\n releaseClarifyUi(): void\n}\n\n/**\n * One object per widget mode. The controller holds a single `state` reference\n * and delegates every mode-dependent input here; transitions run through\n * `transitionTo(next)`, which calls the outgoing state's exit() before the\n * incoming state's enter().\n *\n * enter() mounts what the mode itself needs. exit() is deliberately thin: the\n * toolbar and the annotation session span the whole flow (annotating through\n * clarifying), and several out-edges hand a resource to the next mode instead\n * of releasing it — the note popup's element becomes the clarify card, a\n * stopped recording becomes the submit payload. Those edges release or hand\n * off through their own named paths (cancelDraw, the submit teardown, exitAll),\n * and exitAll remains the universal release every abandon route ends in.\n */\nexport interface ModeState {\n readonly kind: ModeKind\n /** Whether the docked launcher renders lit while this mode is active. */\n readonly launcherLit: boolean\n /** Whether a frustration signal may surface its prompt in this mode. */\n readonly acceptsFrustrationPrompt: boolean\n enter(): void\n exit(): void\n /** Public API open(). Only idle acts; while open it is an idempotent no-op. */\n handleOpen(): void\n handleClose(): void\n handleEscape(event: KeyboardEvent): void\n handleTabClick(): void\n handleToolbarModeChange(mode: ToolbarMode): void\n /** A stroke landed on the annotation overlay. */\n handleDrawStroke(): void\n}\n\nfunction consume(event: KeyboardEvent): void {\n event.preventDefault()\n event.stopPropagation()\n}\n\nabstract class BaseModeState implements ModeState {\n abstract readonly kind: ModeKind\n readonly launcherLit: boolean = true\n readonly acceptsFrustrationPrompt: boolean = false\n\n constructor(protected host: ModeHost) {}\n\n enter(): void {}\n exit(): void {}\n handleOpen(): void {}\n handleClose(): void {\n this.host.exitAll()\n }\n handleEscape(_event: KeyboardEvent): void {}\n handleTabClick(): void {\n this.host.exitAll()\n }\n handleToolbarModeChange(_mode: ToolbarMode): void {}\n handleDrawStroke(): void {}\n}\n\nexport class IdleState extends BaseModeState {\n readonly kind = 'idle'\n override readonly launcherLit = false\n override readonly acceptsFrustrationPrompt = true\n\n override handleOpen(): void {\n this.host.dismissFrustrationPrompt()\n this.host.dismissSuccess()\n this.host.transitionTo(new AnnotatingState(this.host))\n }\n\n override handleClose(): void {\n // Already closed; nothing to exit.\n }\n\n // Escape falls through untouched (not consumed) so the host page's own\n // Escape handling still works while the widget shows nothing.\n\n override handleTabClick(): void {\n this.handleOpen()\n }\n}\n\nexport class AnnotatingState extends BaseModeState {\n readonly kind = 'annotating'\n\n override enter(): void {\n if (!this.host.hasAnnotationChrome()) {\n this.host.mountAnnotationChrome()\n return\n }\n // Resuming markup from recording, a cancelled drawing, or the note popup:\n // the toolbar survived, only the mode-specific surfaces change hands.\n this.host.destroyRecordingManager()\n this.host.teardownDrawBar()\n this.host.ensureSession()\n }\n\n // exit: the toolbar and session deliberately outlive this state — they span\n // the whole flow and are released by the flow-ending paths, not per mode.\n\n override handleEscape(event: KeyboardEvent): void {\n consume(event)\n this.host.exitAll()\n }\n\n override handleToolbarModeChange(mode: ToolbarMode): void {\n if (mode === 'record') {\n this.host.transitionTo(new RecordingState(this.host))\n } else {\n // Re-entering markup is idempotent: enter() finds the chrome and session\n // already in place and changes nothing.\n this.host.transitionTo(new AnnotatingState(this.host))\n }\n }\n\n override handleDrawStroke(): void {\n // The draw note bar packs a field, undo/redo, cancel and submit into one\n // pill at the bottom of the screen — which on a phone is exactly where the\n // keyboard lands, and none of it is thumb-sized. Touch collects the note in\n // the same bottom sheet the pin flow already uses.\n this.host.transitionTo(new DrawingState(this.host, isTouchPointer() ? 'sheet' : 'bar'))\n }\n}\n\nexport class RecordingState extends BaseModeState {\n readonly kind = 'recording'\n\n override enter(): void {\n // Recording exists to capture the user reproducing something, which the\n // pin and draw surfaces would sit on top of — they come down first.\n this.host.clearCurrentAnnotation()\n this.host.teardownDrawBar()\n this.host.destroySession()\n this.host.startRecorder()\n }\n\n override handleEscape(event: KeyboardEvent): void {\n // Escape here is the Stop button, not a cancel: the recording is kept and\n // the note popup opens, exactly as if the reporter had pressed Stop. The\n // recording itself is carried into the popup for the submit payload, so\n // this edge hands the manager off instead of destroying it.\n consume(event)\n this.host.stopRecordingIntoNote()\n }\n\n override handleToolbarModeChange(mode: ToolbarMode): void {\n // A second press of Record must not restart the session already running.\n if (mode === 'record') return\n this.host.transitionTo(new AnnotatingState(this.host))\n }\n}\n\nexport class DrawingState extends BaseModeState {\n readonly kind = 'drawing'\n\n // Which note surface collects the drawing's note: the inline bar on a\n // desktop, the bottom sheet on touch.\n constructor(host: ModeHost, private surface: 'bar' | 'sheet') {\n super(host)\n }\n\n override enter(): void {\n if (this.surface === 'sheet') {\n this.host.mountDrawSheet()\n } else {\n this.host.mountDrawBar()\n this.host.refreshDrawBar()\n }\n }\n\n override handleEscape(event: KeyboardEvent): void {\n // One Escape costs the stroke, not the session: back to annotating.\n consume(event)\n this.host.cancelDraw()\n }\n\n override handleToolbarModeChange(mode: ToolbarMode): void {\n if (mode === 'record') {\n this.host.transitionTo(new RecordingState(this.host))\n } else {\n this.host.transitionTo(new AnnotatingState(this.host))\n }\n }\n\n override handleDrawStroke(): void {\n // Additional strokes while the note surface is already up only need the\n // undo/redo affordances refreshed; the sheet has none.\n if (this.surface === 'bar') this.host.refreshDrawBar()\n }\n}\n\nexport class PopupState extends BaseModeState {\n readonly kind = 'popup'\n\n constructor(host: ModeHost, private opts: NotePopupOptions) {\n super(host)\n }\n\n override enter(): void {\n this.host.mountNotePopup(this.opts)\n }\n\n override handleEscape(event: KeyboardEvent): void {\n consume(event)\n this.host.exitAll()\n }\n\n override handleToolbarModeChange(mode: ToolbarMode): void {\n if (mode === 'record') {\n // The popup and its scrim deliberately stay up: stopping the recording\n // opens a fresh note popup whose mount replaces both, so exactly one\n // scrim exists throughout instead of an orphaned pair.\n this.host.transitionTo(new RecordingState(this.host))\n } else {\n // Markup has no successor surface to replace the popup, so it must come\n // down here. Left standing it is a zombie: a scrim and scroll lock the\n // reporter cannot see past, and a checkbox whose change listener keeps\n // writing screenshot consent for a note that was just abandoned.\n this.host.dismissNotePopup()\n this.host.transitionTo(new AnnotatingState(this.host))\n }\n }\n}\n\n/**\n * A question is open inside the chat card. The card is already mounted and\n * stays put, so entering mounts nothing — what changes is that the submission\n * is now on the server, which is why Escape stops being a cancel here.\n */\nexport class ChatClarifyingState extends BaseModeState {\n readonly kind = 'clarifying'\n\n // handleEscape stays a no-op here because the chat card owns its own Escape\n // and routes it to the same cancel its X does — routing it twice would\n // cancel an already-cancelled submission.\n\n override handleToolbarModeChange(_mode: ToolbarMode): void {\n // No new surface may open under the conversation; snap the toolbar's\n // visual back to the mode the submission came from.\n this.host.revertToolbarDuringClarify()\n }\n}\n\nexport class SubmittingState extends BaseModeState {\n readonly kind = 'submitting'\n override readonly launcherLit = false\n\n override enter(): void {\n // Both entries converge here: skipping the clarification drops its chrome,\n // and re-entering from a failure-card retry finds it already gone — every\n // release below is idempotent.\n this.host.releaseClarifyUi()\n }\n\n override handleTabClick(): void {\n // Deliberate no-op: the launcher must not start a second feedback on top\n // of the one still finalizing in the background.\n }\n\n // Escape falls through unconsumed: nothing is on screen to dismiss, and the\n // background finalize must not be cancellable by a stray keypress.\n}\n","import type { MakeThisBetterConfig, WidgetBrandColors } from '../types'\n\nexport type { WidgetBrandColors } from '../types'\n\nconst HEX_COLOR = /^#[0-9a-f]{6}$/i\n\nexport function resolveBrandColors(\n colors: MakeThisBetterConfig['brandColors'],\n): WidgetBrandColors | undefined {\n if (colors === undefined) return undefined\n\n if (typeof colors !== 'object' || colors === null) return rejectBrandColors()\n\n const values = [colors.primary, colors.hover, colors.active, colors.onPrimary]\n if (!values.every((color) => typeof color === 'string' && HEX_COLOR.test(color))) {\n return rejectBrandColors()\n }\n\n return {\n primary: colors.primary.toLowerCase(),\n hover: colors.hover.toLowerCase(),\n active: colors.active.toLowerCase(),\n onPrimary: colors.onPrimary.toLowerCase(),\n }\n}\n\nfunction rejectBrandColors(): undefined {\n console.warn(\n '[MakeThisBetter] brandColors requires primary, hover, active, and onPrimary as six-digit hex colors.',\n )\n return undefined\n}\n","import { ShadowContainer } from './shadow'\nimport { isImeConfirmKeydown } from '../context/dom-utils'\nimport { FeedbackTab } from './tab'\nimport { FrustrationPromptCard } from './frustration-prompt'\nimport { DimOverlay } from './dim-overlay'\nimport { isTouchPointer } from './pointer-kind'\nimport { PageOffset } from './page-offset'\nimport { lockPageScroll } from './scroll-lock'\nimport type { ScrollLock } from './scroll-lock'\nimport { AnnotationToolbar } from '../annotate/toolbar'\nimport { AnnotationSession } from '../annotate/session'\nimport { DrawNoteBar } from '../annotate/draw-note-bar'\nimport { ChatPopup } from '../popup/chat-popup'\nimport { ReceiptCard } from '../popup/receipt'\nimport { warmFontEmbedCss } from '../screenshot/capture'\nimport { ConsoleErrorCollector } from '../context/console'\nimport { BreadcrumbCollector } from '../context/breadcrumbs'\nimport { FrustrationDetector } from '../context/frustration'\nimport { isFrustrationDismissed, markFrustrationDismissed } from '../context/frustration-state'\nimport { ApiClient } from '../api/client'\nimport { cacheBoardUrl, getAnonId, getCachedBoardUrl, getStoredReporterEmail, rememberReporterEmail } from './anon-id'\nimport { getMessages } from '../i18n'\nimport { RecordingManager } from './recording-manager'\nimport { SubmissionLifecycle } from './submission-lifecycle'\nimport { AnnotatingState, ChatClarifyingState, IdleState, PopupState, SubmittingState } from './mode-states'\nimport type { ModeHost, ModeState, NotePopupOptions } from './mode-states'\nimport type { MakeThisBetterConfig, Annotation, SubmissionSessionResponse, FeedbackResponse } from '../types'\nimport { resolveBrandColors } from './brand-colors'\nimport type { WidgetBrandColors } from './brand-colors'\n\ntype Via = 'pin' | 'draw' | 'record'\n\n// A browser SDK has no build-time environment, so \"is a developer watching?\"\n// has to be inferred. Loopback and the .local/.test dev TLDs are where an\n// integrator runs; anything else is somebody's live site.\nfunction isLocalHost(): boolean {\n const { hostname } = window.location\n return (\n hostname === 'localhost' ||\n hostname === '127.0.0.1' ||\n hostname === '[::1]' ||\n hostname.endsWith('.localhost') ||\n hostname.endsWith('.local') ||\n hostname.endsWith('.test')\n )\n}\n\nexport class WidgetController implements ModeHost {\n private config: MakeThisBetterConfig\n private brandColors: WidgetBrandColors | undefined\n private shadow!: ShadowContainer\n private tab: FeedbackTab | null = null\n private toolbar: AnnotationToolbar | null = null\n private session: AnnotationSession | null = null\n private drawBar: DrawNoteBar | null = null\n private dim: DimOverlay | null = null\n private pageOffset = new PageOffset()\n private chatPopup: ChatPopup | null = null\n private scrollLock: ScrollLock | null = null\n private success: ReceiptCard | null = null\n private frustrationPrompt: FrustrationPromptCard | null = null\n private apiClient: ApiClient\n private consoleCollector: ConsoleErrorCollector\n private breadcrumbCollector: BreadcrumbCollector\n private frustrationDetector: FrustrationDetector | null = null\n private messages: ReturnType<typeof getMessages>\n // The mode machine. One state object per mode owns what mounts on entry and\n // how each input behaves there; the controller only delegates and keeps the\n // component references the states operate on (through the ModeHost verbs).\n private state: ModeState = new IdleState(this)\n private via: Via = 'pin'\n private currentAnnotation: Annotation | null = null\n private lastPopupPos: { x: number; y: number } | null = null\n private recordingManager: RecordingManager | null = null\n private lifecycle: SubmissionLifecycle\n private touchHintShown = false\n private lastHandoff: { feedbackId?: string; boardUrl?: string; identityToken?: string } = {}\n\n constructor(config: MakeThisBetterConfig) {\n this.config = config\n // A missing key still renders a fully working widget whose every submit\n // 401s — the reporter sees a generic failure and the integrator sees\n // nothing at all. Say it at init, where the integrator is looking.\n if (!config.projectKey) {\n console.warn('[MakeThisBetter] projectKey is missing. Feedback submission will be rejected.')\n }\n this.brandColors = resolveBrandColors(config.brandColors)\n this.messages = getMessages(config.locale ?? document.documentElement.lang ?? 'en')\n this.apiClient = new ApiClient({\n projectKey: config.projectKey,\n apiUrl: config.apiUrl,\n userToken: config.userToken,\n userTokenFn: config.userTokenFn,\n })\n this.consoleCollector = new ConsoleErrorCollector()\n this.consoleCollector.start()\n this.breadcrumbCollector = new BreadcrumbCollector()\n this.breadcrumbCollector.start()\n this.lifecycle = new SubmissionLifecycle(\n {\n apiClient: this.apiClient,\n consoleCollector: this.consoleCollector,\n breadcrumbCollector: this.breadcrumbCollector,\n brandColors: this.brandColors,\n shadow: () => this.shadow,\n messages: () => this.messages,\n user: () => this.config.user,\n },\n {\n isBackground: () => this.state.kind === 'submitting',\n onFinalizeFailure: (onRetry) => this.chatPopup?.showFinalizeFailure(onRetry),\n onReenterBackground: () => this.transitionTo(new SubmittingState(this)),\n onFinalized: (feedback, submission) => this.handleFinalized(feedback, submission),\n },\n )\n try {\n this.buildUI()\n } catch (error) {\n // A page-supplied host can be an element that refuses attachShadow. The\n // collectors above have already patched window.onerror and history, and\n // letting this throw into the customer's script would leave those\n // patches with no owner to release them — so roll back and go dark\n // instead of crashing code that is not ours.\n this.consoleCollector.stop()\n this.breadcrumbCollector.stop()\n console.warn('[MakeThisBetter] Widget failed to start:', error)\n return\n }\n\n if (config.frustrationDetection !== false) {\n this.frustrationDetector = new FrustrationDetector(() => this.handleFrustration())\n this.frustrationDetector.start()\n }\n\n document.addEventListener('keydown', this.handleKeydown, true)\n }\n\n /**\n * The single mutation point of the mode machine: the outgoing state exits,\n * the incoming state enters, and the launcher reflects the new mode.\n */\n transitionTo(next: ModeState): void {\n const previous = this.state\n this.state = next\n previous.exit()\n next.enter()\n this.tab?.setActive(next.launcherLit)\n }\n\n private handleKeydown = (event: KeyboardEvent): void => {\n if (event.key !== 'Escape') return\n // Escape during IME composition cancels the candidate list, not the widget.\n // Without this a reporter typing Chinese or Japanese loses the popup and\n // everything they had written by backing out of one wrong candidate.\n if (isImeConfirmKeydown(event)) return\n\n this.state.handleEscape(event)\n }\n\n private buildUI(): void {\n this.shadow = new ShadowContainer(this.config.theme ?? 'auto', this.brandColors)\n\n if (this.config.entryMode === 'api') {\n // Nothing is reachable until the host wires up open(), and silence would\n // look identical to a broken install. But 'api' is a supported setting,\n // so a warning on every production page load would be a third-party SDK\n // shouting at customers who did nothing wrong — say it only where an\n // integrator is watching.\n if (isLocalHost()) {\n console.warn(\n '[MakeThisBetter] entryMode: \"api\" renders no launcher. ' +\n 'Call MakeThisBetter.open() from your own UI.',\n )\n }\n return\n }\n\n // A phone gets no launcher of ours. The edge tab is 36x40 of someone\n // else's screen, and it is genuinely hard to find — its own author missed\n // it on a test page — while a narrow layout has no spare margin to dock\n // anything to. Where a feedback entry belongs on a phone is the host's\n // call, not a third-party script's, so touch renders nothing and waits for\n // open() from the host's own UI.\n if (isTouchPointer()) {\n if (isLocalHost()) {\n console.warn(\n '[MakeThisBetter] No launcher is rendered on touch devices. ' +\n 'Call MakeThisBetter.open() from your own mobile UI.',\n )\n }\n return\n }\n\n this.mountTab()\n }\n\n private mountTab(): void {\n // No shadow means startup was rolled back; there is nowhere to mount.\n if (this.tab || !this.shadow) return\n this.tab = new FeedbackTab(\n this.shadow,\n this.messages,\n this.config.position ?? 'right',\n () => this.state.handleTabClick(),\n this.config.tabText,\n )\n }\n\n /** Opens annotation mode. Idempotent: a second call while open does nothing. */\n open(): void {\n if (!this.shadow) return\n this.state.handleOpen()\n }\n\n close(): void {\n this.state.handleClose()\n }\n\n showLauncher(): void {\n if (isTouchPointer()) return\n this.mountTab()\n this.tab?.setActive(this.state.launcherLit)\n }\n\n hideLauncher(): void {\n this.tab?.destroy()\n this.tab = null\n }\n\n // Takes effect for anything opened afterwards. Components already on screen keep\n // the language they were opened in rather than being re-rendered underneath a\n // reporter who may be mid-sentence.\n setLocale(locale: string): void {\n this.config.locale = locale\n this.messages = getMessages(locale)\n this.tab?.setMessages(this.messages)\n }\n\n private handleFrustration(): void {\n if (!this.state.acceptsFrustrationPrompt || this.frustrationPrompt || isFrustrationDismissed()) return\n\n this.frustrationPrompt = new FrustrationPromptCard(this.shadow, this.messages, {\n onTell: () => {\n this.dismissFrustrationPrompt()\n this.state.handleOpen()\n },\n onDismiss: () => {\n markFrustrationDismissed()\n this.dismissFrustrationPrompt()\n },\n // An unnoticed card is not a refusal, so it must not suppress later\n // prompts. The detector's own 60s cooldown does the rate limiting.\n onAutoHide: () => this.dismissFrustrationPrompt(),\n })\n }\n\n dismissSuccess(): void {\n this.success?.destroy()\n this.success = null\n // The chat card ends as its own receipt, so it is the previous\n // submission's success card on that path.\n this.chatPopup?.destroy()\n this.chatPopup = null\n }\n\n dismissFrustrationPrompt(): void {\n this.frustrationPrompt?.destroy()\n this.frustrationPrompt = null\n }\n\n hasAnnotationChrome(): boolean {\n return this.toolbar !== null\n }\n\n mountAnnotationChrome(): void {\n // Network-only warmup: annotating usually ends in a capture, and the font\n // embed CSS is the slow, cacheable half of one.\n void warmFontEmbedCss()\n const touch = isTouchPointer()\n\n this.toolbar = new AnnotationToolbar(\n this.shadow,\n this.messages,\n () => this.exitAll(),\n (mode) => this.state.handleToolbarModeChange(mode),\n // The bar stands at whichever edge the tab does. Leaving it pinned right\n // while a left-configured tab sat opposite would split the widget across\n // both sides of the page.\n this.config.position ?? 'right',\n !touch || !this.touchHintShown,\n )\n if (touch) this.touchHintShown = true\n this.ensureSession()\n // The bar is a full-bleed strip at the top on a phone. Left overlapping,\n // it covers roughly 100px of the page — usually the site's own header —\n // and taps there do nothing, silently. Give it room of its own instead.\n if (touch && this.toolbar) this.pageOffset.reserve(this.toolbar.height())\n }\n\n ensureSession(): void {\n if (this.session) return\n this.session = new AnnotationSession(this.shadow, this.messages, {\n onPin: (annotation, targetName) => {\n this.currentAnnotation = annotation\n this.via = 'pin'\n this.transitionTo(new PopupState(this, {\n x: annotation.x,\n y: annotation.y,\n targetName,\n targetRect: annotation.targetRect,\n }))\n },\n onDrawStroke: () => this.state.handleDrawStroke(),\n })\n }\n\n destroySession(): void {\n this.session?.destroy()\n this.session = null\n }\n\n clearCurrentAnnotation(): void {\n this.currentAnnotation = null\n }\n\n startRecorder(): void {\n this.recordingManager = new RecordingManager()\n this.recordingManager.start(\n this.shadow,\n this.messages,\n this.stopRecordingIntoNote,\n ).catch((error) => {\n // rrweb failed to load (network / SRI mismatch) — fall back to markup\n console.warn('[MakeThisBetter] Interaction replay unavailable, falling back to markup:', error)\n // Staleness guard: the reporter may have left recording while the load\n // was still failing, and a stale fallback must not disturb wherever\n // they are now.\n if (this.state.kind !== 'recording') return\n this.toolbar?.setMode('markup')\n this.state.handleToolbarModeChange('markup')\n })\n }\n\n destroyRecordingManager(): void {\n this.recordingManager?.destroy()\n this.recordingManager = null\n }\n\n stopRecordingIntoNote = (): void => {\n if (!this.recordingManager) return\n this.recordingManager.stop()\n this.via = 'record'\n this.transitionTo(new PopupState(this, {\n x: window.innerWidth / 2,\n y: window.innerHeight / 2,\n targetName: this.messages.record.timer_label,\n }))\n }\n\n mountDrawBar(): void {\n this.engageScrollLock()\n this.drawBar = new DrawNoteBar(this.shadow, {\n messages: this.messages,\n onUndo: () => this.handleDrawUndoRedo('undo'),\n onRedo: () => this.handleDrawUndoRedo('redo'),\n onCancel: () => this.cancelDraw(),\n onSubmit: (note) => this.handleDrawSubmit(note),\n onActivity: () => this.lifecycle.warmScreenshotDeps(),\n })\n // Strokes are baked onto the screenshot, so this surface offers no opt-out\n // and must undo one made earlier in the session.\n this.lifecycle.setScreenshotEnabled(true)\n this.lifecycle.warmScreenshotDeps()\n }\n\n /**\n * The note sheet for a finished drawing on touch.\n *\n * Cancel here means \"throw the drawing away and keep annotating\", not \"close\n * the widget\" — the reporter is mid-task and a stray stroke should cost one\n * tap, not the whole session.\n */\n mountDrawSheet(): void {\n // No dismissInteraction and no scrim here, matching the desktop draw bar:\n // the overlay stays live so more strokes are still possible, and the scrim\n // sits below it anyway so it could never receive the tap.\n this.engageScrollLock()\n // Cancel means \"throw the drawing away\" only while the drawing is still the\n // reporter's; once the note is sent the card is a receipt and closing it\n // ends the session instead.\n let submitted = false\n this.chatPopup = new ChatPopup(this.shadow, {\n targetName: this.messages.annotation.drawing,\n x: window.innerWidth / 2,\n y: window.innerHeight / 2,\n messages: this.messages,\n position: this.config.position ?? 'right',\n onSubmit: async (description: string) => {\n const annotation = this.session?.getDrawAnnotation()\n if (!annotation) {\n this.cancelDraw()\n return null\n }\n submitted = true\n this.currentAnnotation = annotation\n this.via = 'draw'\n this.session?.dismissInteraction()\n this.teardownDrawBar()\n const recording = this.recordingManager?.getRecording() ?? undefined\n const session = await this.lifecycle.start(\n description || this.messages.annotation.drawing,\n annotation,\n recording,\n )\n if (session?.board_url) cacheBoardUrl(session.board_url)\n return session\n },\n onClarify: async (sessionId: string, token: string, onDelta: (text: string) => void, signal?: AbortSignal) => {\n return this.apiClient.streamClarification(sessionId, token, onDelta, signal)\n },\n onAnswer: (sessionId: string, token: string, message: string) =>\n this.apiClient.answerClarification(sessionId, token, message),\n onFinalize: () => {\n this.transitionTo(new SubmittingState(this))\n return this.lifecycle.finalize()\n },\n onCancel: () => (submitted ? this.exitAll() : this.cancelDraw()),\n onConversationOpen: () => this.transitionTo(new ChatClarifyingState(this)),\n onActivity: () => this.lifecycle.warmScreenshotDeps(),\n resolveBoardLink: () => this.buildViewFeedbackHandler() ?? this.buildMyFeedbackHandler(),\n resolveEmailCapture: () => this.buildEmailCapture(),\n })\n // Same rule as the desktop draw bar: no opt-out on a drawing.\n this.lifecycle.setScreenshotEnabled(true)\n this.lifecycle.warmScreenshotDeps()\n }\n\n private handleDrawUndoRedo(action: 'undo' | 'redo'): void {\n if (action === 'undo') this.session?.undoDraw()\n else this.session?.redoDraw()\n this.refreshDrawBar()\n }\n\n refreshDrawBar(): void {\n if (!this.session) return\n this.drawBar?.setUndoRedo(this.session.canUndoDraw(), this.session.canRedoDraw())\n }\n\n cancelDraw(): void {\n this.session?.clearDraw()\n this.releaseScrollLock()\n this.teardownDrawBar()\n // On touch the sheet is the note surface, so cancelling the drawing has to\n // take the sheet and its scrim with it — teardownDrawBar only knows the bar.\n this.chatPopup?.destroy()\n this.chatPopup = null\n this.dim?.destroy()\n this.dim = null\n this.transitionTo(new AnnotatingState(this))\n }\n\n teardownDrawBar(): void {\n this.drawBar?.destroy()\n this.drawBar = null\n }\n\n private handleDrawSubmit(note: string): void {\n const annotation = this.session?.getDrawAnnotation()\n if (!annotation) {\n this.cancelDraw()\n return\n }\n this.currentAnnotation = annotation\n this.via = 'draw'\n this.session?.dismissInteraction()\n const description = note || this.messages.annotation.drawing\n const recording = this.recordingManager?.getRecording() ?? undefined\n\n this.teardownDrawBar()\n this.releaseScrollLock()\n this.dim?.destroy()\n this.dim = null\n\n this.mountChatAfterSubmit(description, annotation, recording)\n }\n\n private mountChatAfterSubmit(description: string, annotation: Annotation | null, recording: import('./recording-manager').RecordingResult | undefined): void {\n this.dim = new DimOverlay(this.shadow, () => this.exitAll())\n this.engageScrollLock()\n\n this.chatPopup = new ChatPopup(this.shadow, {\n targetName: annotation?.targetName ?? this.messages.annotation.drawing,\n x: this.lastPopupPos?.x ?? window.innerWidth / 2,\n y: this.lastPopupPos?.y ?? window.innerHeight / 2,\n messages: this.messages,\n position: this.config.position ?? 'right',\n onSubmit: async () => {\n const session = await this.lifecycle.start(description, annotation, recording)\n if (session?.board_url) cacheBoardUrl(session.board_url)\n return session\n },\n onClarify: async (sessionId: string, token: string, onDelta: (text: string) => void, signal?: AbortSignal) => {\n return this.apiClient.streamClarification(sessionId, token, onDelta, signal)\n },\n onAnswer: (sessionId: string, token: string, message: string) =>\n this.apiClient.answerClarification(sessionId, token, message),\n onFinalize: () => {\n this.transitionTo(new SubmittingState(this))\n return this.lifecycle.finalize()\n },\n onCancel: () => this.exitAll(),\n onConversationOpen: () => this.transitionTo(new ChatClarifyingState(this)),\n resolveBoardLink: () => this.buildViewFeedbackHandler() ?? this.buildMyFeedbackHandler(),\n resolveEmailCapture: () => this.buildEmailCapture(),\n })\n this.chatPopup.submit(description)\n }\n\n mountNotePopup({ x, y, targetName, targetRect }: NotePopupOptions): void {\n this.lastPopupPos = { x, y }\n this.teardownDrawBar()\n this.session?.dismissInteraction()\n this.chatPopup?.destroy()\n this.chatPopup = null\n this.dim?.destroy()\n this.dim = null\n this.dim = new DimOverlay(this.shadow, () => this.exitAll())\n const recording = this.recordingManager?.getRecording()\n if (recording) this.lifecycle.setScreenshotEnabled(true)\n\n const annotation = this.currentAnnotation\n const recordingResult = this.recordingManager?.getRecording() ?? undefined\n\n this.chatPopup = new ChatPopup(this.shadow, {\n targetName,\n x,\n y,\n targetRect,\n messages: this.messages,\n position: this.config.position ?? 'right',\n onSubmit: async (description: string) => {\n this.currentAnnotation = annotation ?? this.currentAnnotation\n const session = await this.lifecycle.start(description, this.currentAnnotation, recordingResult)\n if (session?.board_url) cacheBoardUrl(session.board_url)\n return session\n },\n onClarify: async (sessionId: string, token: string, onDelta: (text: string) => void, signal?: AbortSignal) => {\n return this.apiClient.streamClarification(sessionId, token, onDelta, signal)\n },\n onAnswer: (sessionId: string, token: string, message: string) =>\n this.apiClient.answerClarification(sessionId, token, message),\n // The chat card turns into a receipt at this point: the pin, the scrim\n // and the scroll lock have nothing left to hold. A finalization failure\n // remains in this card as a retryable receipt state.\n onFinalize: () => {\n this.teardownSubmittingUi()\n this.transitionTo(new SubmittingState(this))\n return this.lifecycle.finalize()\n },\n onCancel: () => this.exitAll(),\n onConversationOpen: () => this.transitionTo(new ChatClarifyingState(this)),\n onActivity: () => this.lifecycle.warmScreenshotDeps(),\n resolveBoardLink: () => this.buildViewFeedbackHandler() ?? this.buildMyFeedbackHandler(),\n resolveEmailCapture: () => this.buildEmailCapture(),\n ...(recording\n ? { recordingSeconds: recording.duration }\n : {\n onScreenshotToggle: (enabled: boolean) => this.lifecycle.setScreenshotEnabled(enabled),\n screenshotEnabled: this.lifecycle.isScreenshotEnabled(),\n }),\n })\n this.engageScrollLock()\n this.lifecycle.warmScreenshotDeps()\n }\n\n /**\n * The Markup-button escape from a note popup. The popup's mount destroyed\n * the session's overlay (dismissInteraction), so the session goes with it —\n * AnnotatingState.enter() rebuilds a fresh one and the reporter can point\n * and draw again, rather than facing a dead overlay under a leftover scrim.\n */\n dismissNotePopup(): void {\n this.chatPopup?.destroy()\n this.chatPopup = null\n this.dim?.destroy()\n this.dim = null\n this.releaseScrollLock()\n this.clearCurrentAnnotation()\n this.destroySession()\n }\n\n private engageScrollLock(): void {\n // Re-entrant: record mode returns here and mounts a fresh popup over the\n // existing one, and a second lock would leave a listener behind.\n if (!this.scrollLock) this.scrollLock = lockPageScroll()\n }\n\n private releaseScrollLock(): void {\n this.scrollLock?.release()\n this.scrollLock = null\n }\n\n private buildMyFeedbackHandler(): (() => void) | undefined {\n if (this.config.user) return undefined\n if (!getCachedBoardUrl()) return undefined\n\n return async () => {\n const exchange = await this.apiClient.createIdentityToken(getAnonId())\n if (!exchange?.identity_token || !exchange.board_url) return\n cacheBoardUrl(exchange.board_url)\n window.open(`${exchange.board_url}?identity=${encodeURIComponent(exchange.identity_token)}`, '_blank')\n }\n }\n\n private teardownSubmittingUi(): void {\n // The highlight exists to hold the target visible under a pin. This is\n // where the session — and with it the pin — goes away, so the highlight\n // has to go with it rather than outliving what it pointed at.\n this.teardownDrawBar()\n // The pin goes away with the session below, so nothing anchored is left for\n // a scroll to strand — the conversation that follows scrolls freely.\n this.releaseScrollLock()\n this.dim?.destroy()\n this.dim = null\n this.session?.hideHighlight()\n this.session?.destroy()\n this.session = null\n this.recordingManager?.destroy()\n this.recordingManager = null\n }\n\n revertToolbarDuringClarify(): void {\n this.toolbar?.setMode(this.via === 'record' ? 'record' : 'markup')\n }\n\n releaseClarifyUi(): void {\n this.teardownToolbar()\n this.currentAnnotation = null\n // The card is not destroyed here: the skip path's card dismisses itself\n // after handing off, and every other entry into submitting finds it gone.\n }\n\n private handleFinalized(feedback: FeedbackResponse, submission: SubmissionSessionResponse): void {\n this.lastHandoff = {\n feedbackId: feedback.id,\n boardUrl: feedback.board_url,\n identityToken: feedback.identity_token,\n }\n if (feedback.board_url) cacheBoardUrl(feedback.board_url)\n\n // A finalize that lands after the reporter already started their next\n // report must not touch the screen: any toolbar, consent state or chat\n // card up now belongs to the new report, not to this submission. The\n // hand-off above is still recorded — it is about the finalized feedback,\n // not about what is on screen.\n if (this.chatPopup && this.chatPopup.sessionId() !== submission.id) return\n if (!this.chatPopup && this.state.kind !== 'submitting' && this.state.kind !== 'clarifying') return\n\n if (this.chatPopup) {\n this.chatPopup.showFinalizeSuccess()\n this.teardownSubmittingUi()\n this.teardownToolbar()\n this.lifecycle.resetScreenshotConsent()\n this.transitionTo(new IdleState(this))\n return\n }\n\n this.teardownToolbar()\n this.lifecycle.resetScreenshotConsent()\n this.showSuccess({ skipFollowup: submission.ai_clarify_available === false })\n }\n\n private showSuccess(options?: { skipFollowup?: boolean }): void {\n this.transitionTo(new IdleState(this))\n this.success = new ReceiptCard(this.shadow, this.messages, () => {\n this.success?.destroy()\n this.success = null\n }, { ...options, onViewFeedback: this.buildViewFeedbackHandler() })\n }\n\n private buildEmailCapture(): { onSubmit: (email: string) => Promise<boolean> } | undefined {\n if (this.config.user) return undefined\n const { feedbackId, identityToken } = this.lastHandoff\n if (!feedbackId || !identityToken) return undefined\n if (getStoredReporterEmail()) return undefined\n\n return {\n onSubmit: async (email: string) => {\n const saved = await this.apiClient.updateReporter(feedbackId, email, identityToken)\n if (saved) rememberReporterEmail(email)\n return saved\n },\n }\n }\n\n private buildViewFeedbackHandler(): (() => void) | undefined {\n const { boardUrl, identityToken } = this.lastHandoff\n if (!boardUrl) return undefined\n\n if (identityToken) {\n return () => {\n window.open(`${boardUrl}?identity=${encodeURIComponent(identityToken)}`, '_blank')\n }\n }\n\n if (this.config.userToken || this.config.userTokenFn) {\n return async () => {\n // resolveUserToken runs the host's own userTokenFn. A rejection there\n // is the customer's backend failing, not ours — swallowing it beats\n // handing their page an unhandled rejection from a click handler they\n // never wrote.\n try {\n const token = await this.apiClient.resolveUserToken()\n if (token) window.open(`${boardUrl}?identity=${encodeURIComponent(token)}`, '_blank')\n } catch {\n // The board link just does nothing; the feedback itself already sent.\n }\n }\n }\n\n return undefined\n }\n\n /**\n * The reserved strip belongs to the toolbar, so it goes back the moment the\n * toolbar does. Both teardown paths route through here: exit, and the\n * successful submit — which never passes through exitAll and used to leave\n * the host page padded for the rest of its life.\n */\n private teardownToolbar(): void {\n this.pageOffset.release()\n this.toolbar?.destroy()\n this.toolbar = null\n }\n\n exitAll(): void {\n this.lifecycle.abandon()\n this.via = 'pin'\n this.transitionTo(new IdleState(this))\n // Ahead of anything that can throw: a half-torn-down widget is survivable,\n // a host page left permanently padded is not.\n this.teardownToolbar()\n this.teardownDrawBar()\n this.releaseScrollLock()\n this.session?.hideHighlight()\n this.session?.destroy()\n this.session = null\n this.dim?.destroy()\n this.dim = null\n this.chatPopup?.destroy()\n this.chatPopup = null\n this.currentAnnotation = null\n this.recordingManager?.destroy()\n this.recordingManager = null\n }\n\n destroy(): void {\n document.removeEventListener('keydown', this.handleKeydown, true)\n this.exitAll()\n this.consoleCollector.stop()\n this.breadcrumbCollector.stop()\n this.frustrationDetector?.stop()\n this.dismissFrustrationPrompt()\n this.success?.destroy()\n this.tab?.destroy()\n this.recordingManager?.destroy()\n // Undefined when startup rolled back before the shadow container existed.\n this.shadow?.destroy()\n }\n}\n","import { WidgetController } from './widget/controller'\nimport { WIDGET_HOST_ID } from './context/dom-utils'\nimport type { MakeThisBetterConfig } from './types'\n\nlet instance: WidgetController | null = null\nlet lastConfig: MakeThisBetterConfig | null = null\n// A mount deferred until DOMContentLoaded. Held so a second init() — or a\n// destroy() — can call it off: otherwise each init before the document is ready\n// queues its own listener, and every controller but the last mounts with no\n// live reference, leaking its document listeners and detectors for good.\nlet pendingMount: (() => void) | null = null\n\nfunction cancelPendingMount(): void {\n if (!pendingMount) return\n document.removeEventListener('DOMContentLoaded', pendingMount)\n pendingMount = null\n}\n\nfunction replaceInstance(config?: MakeThisBetterConfig): void {\n cancelPendingMount()\n instance?.destroy()\n instance = config ? new WidgetController(config) : null\n}\n\nconst MakeThisBetter = {\n init(config: MakeThisBetterConfig): void {\n // SSR frameworks run the same component code on the server, where there is\n // no document at all — a widget cannot exist there, and throwing a\n // ReferenceError into the host's render pass is the one thing a\n // third-party SDK must never do.\n if (typeof document === 'undefined') return\n replaceInstance()\n lastConfig = config\n if (document.readyState === 'loading') {\n pendingMount = () => {\n pendingMount = null\n replaceInstance(config)\n }\n document.addEventListener('DOMContentLoaded', pendingMount, { once: true })\n } else {\n replaceInstance(config)\n }\n },\n\n destroy(): void {\n replaceInstance()\n lastConfig = null\n },\n\n setLocale(locale: string): void {\n if (lastConfig) lastConfig.locale = locale\n instance?.setLocale(locale)\n },\n\n /**\n * Opens annotation mode from the host's own UI. Pair with\n * `entryMode: 'api'` to replace the docked tab entirely, which is the only\n * good answer on a phone where a permanent floating control is in the way.\n */\n open(): void {\n instance?.open()\n },\n\n close(): void {\n instance?.close()\n },\n\n /** Shows the docked tab on non-touch devices. Mobile entry UI belongs to the host. */\n showLauncher(): void {\n instance?.showLauncher()\n },\n\n hideLauncher(): void {\n instance?.hideLauncher()\n },\n}\n\nexport { MakeThisBetter }\nexport type { MakeThisBetterConfig }\n\nif (typeof window !== 'undefined') {\n (window as Window & { MakeThisBetter?: typeof MakeThisBetter }).MakeThisBetter = MakeThisBetter\n\n document.addEventListener('turbo:load', () => {\n const host = document.getElementById(WIDGET_HOST_ID)\n if (lastConfig && (!host || !host.shadowRoot)) {\n host?.remove()\n replaceInstance(lastConfig)\n }\n })\n}\n"],"names":["WIDGET_HOST_ID","isWidgetElement","el","host","elementUnderPoint","x","y","escapeHtml","text","isImeConfirmKeydown","event","UNRENDERED","renderedText","limit","out","node","getElementName","label","title","placeholder","alt","dataMark","getCssSelector","parts","current","selector","classNames","c","parent","siblings","idx","_ShadowContainer","theme","brandColors","provided","rgb","offset","onPrimaryRgb","eventType","style","cssText","tag","className","nodes","n","ShadowContainer","FeedbackTab","shadow","messages","position","onClick","customText","active","FRUSTRATION_DISMISS_MS","FrustrationPromptCard","handlers","SPAWN_CLICK_GRACE_MS","DimOverlay","onDismiss","now","isTouchPointer","PageOffset","px","POINTER_GESTURES","SCROLL_KEYS","scrollsInternally","consumesScrollKey","key","lockPageScroll","blockGesture","blockKey","origin","gesture","MOBILE_BAR_HEIGHT_PX","AnnotationToolbar","onExit","onModeChange","showTouchHint","e","rect","inside","m","mode","AnnotationOverlay","pe","pointerId","ElementHighlighter","below","captureTransform","region","canvasWidth","canvasHeight","toCanvasX","transform","toCanvasY","rectToCanvas","viewportCaptureRect","anchorScreenshotOffset","anchor","pageScroll","nestedScrollOffset","target","parentElement","element","root","captureAnchorOffset","nested","PinMarker","pin","BRAND_COLOR","DRAW_STROKE","DrawLayer","emptyAnchor","strokeAnchor","buildPathD","start","max","stroke","entry","path","first","strokes","shiftPoints","cx","cy","center","points","fx","fy","d","i","xs","p","ys","BLOCK_CLASS","MASK_TEXT_CLASS","PRIVACY_SELECTOR","isPrivacyProtected","structuralElementName","role","structuralElementSelector","DRAW_MIN_TRAVEL_PX","annotationTarget","AnnotationSession","underEl","targetName","annotation","drift","dx","dy","SETTLE_MS","KeyboardInset","reading","vp","inset","onChange","SHEET_MAX_WIDTH","isSheetViewport","SheetLayout","headroom","applied","DrawNoteBar","opts","loading","canUndo","canRedo","VIEWPORT_MARGIN","TAB_WIDTH","measureViewportFrame","tabPosition","vw","vh","panelWidth","preferred","frame","TABBABLE_SELECTOR","trapFocus","card","opener","handleKeydown","tabbables","last","CARD_WIDTH","AUTO_CLOSE_MS","BRAND_URL","BRAND_ICON","CLOSE_ICON","CAMERA_ICON","RECORD_ICON","CHECK_ICON","ChatPopup","quickOpts","o","capture","btn","value","screenshotBtn","pressed","session","result","bufferedDeltas","delta","r","aiMessage","msg","view","icon","link","onRetry","retry","openBoard","pendingLink","emailCapture","row","field","input","submitEmail","email","note","error","bubble","suggestions","suggestion","pill","skip","disabled","targetRect","minLeft","maxRight","margin","gap","cardW","cardH","left","top","anchorIsInformative","ReceiptCard","onClose","options","titleEl","FILTERED_VALUE","PRIVATE_KEY_PATTERN","JWT_PATTERN","API_KEY_PATTERNS","CARD_CANDIDATE_PATTERN","HTTP_URL_PATTERN","FIELD_SEMANTIC_KEYS","VALUE_ATTRIBUTE_KEYS","SENSITIVE_INPUT_TYPES","SENSITIVE_AUTOCOMPLETE","SENSITIVE_KEY_PATTERN","filterSensitiveText","filtered","pattern","candidate","passesLuhn","sanitizeUrl","url","filterSensitiveValue","isSensitiveKey","item","sensitiveField","isSensitiveFieldAttributes","nestedKey","filteredItem","normalizeIdentifier","attributes","normalizedKey","token","digits","sum","doubleDigit","index","digit","isSensitiveField","filterInputValue","MAX_GROUPED_TEXT_LENGTH","createSensitiveTextContext","hasSensitiveTextContext","context","state","sensitiveTextState","cached","child","childState","loadImage","dataUrl","resolve","image","parsePathPolylines","drawPath","polylines","tokens","translatePathD","htmlToImageLoaded","loadHtmlToImage","err","fontEmbedCss","fontEmbedCssRequest","warmFontEmbedCss","getFontEmbedCSS","css","CULL_MARGIN_PX","TRANSPARENT_IMAGE_PLACEHOLDER","captureBaseScreenshot","resourceUrlResolver","signal","toJpeg","fontEmbedCSS","bodyRect","metrics","redactions","collectPrivacyRedactions","capturePixelRatio","bakeAnnotatedScreenshot","base","annotations","screenshotAnnotations","translateAnnotationForScreenshot","bakeScreenshot","screenshotOffset","findAnnotationTarget","captureMetrics","canvas","ctx","cover","drawStroke","drawPin","canvasToJpegBlob","elementsIncludingRoot","addRedaction","cloneScrollOffset","isWidgetOrBlocked","isMasked","hasSensitiveValue","REDACTION_PADDING_PX","walker","textContext","hasSensitiveText","range","descendants","padding","color","MAX_CAPTURE_PIXELS","total","primary","blob","ErrorSource","listener","source","line","col","errorName","reason","name","errorSource","summarizeError","validErrorName","inferErrorName","location","errorLocation","message","pathname","MAX_CONSOLE_ERRORS","ConsoleErrorCollector","listeners","nativePushState","nativeReplaceState","installedPushState","installedReplaceState","popstateHandler","lastUrl","onHistoryNavigation","install","released","uninstall","notify","type","fromUrl","pushOrig","replaceOrig","data","unused","MAX_BREADCRUMBS","describeTarget","describeSelector","BreadcrumbCollector","crumb","summary","from","to","sanitizedFrom","sanitizedTo","STORAGE_KEY","memoryState","loadFrustrationState","raw","normalize","saveFrustrationState","normalized","isFrustrationDismissed","markFrustrationDismissed","t","u","RAGE_CLICK_THRESHOLD","RAGE_CLICK_WINDOW_MS","RAPID_NAV_THRESHOLD","RAPID_NAV_WINDOW_MS","FORM_CHECK_DELAY_MS","FORM_FAILURE_THRESHOLD","FORM_FAILURE_WINDOW_MS","INTERACTION_ERROR_WINDOW_MS","DEAD_CLICK_DOM_DELAY_MS","ERROR_PAGE_CHECK_DELAY_MS","INTERACTIVE_TAGS","INTERACTIVE_ROLES","COMMAND_TAGS","COMMAND_ROLES","ERROR_PAGE_PATTERNS","_FrustrationDetector","onFrustration","interaction","describeElement","callback","delayMs","timer","stored","interactionTarget","cutoff","form","control","failures","timestamp","candidates","clickTime","mutated","navigated","cls","lower","FrustrationDetector","id","ApiError","status","FINALIZE_TIMEOUT_MS","DEFAULT_API_URL","CAPTURE_POLICY","ApiClient","submissionSessionId","resourceUrl","contentType","headers","body","payload","screenshot","submissionToken","generateSubmissionToken","json","ext","generateIdempotencyKey","reporterExternalId","feedbackId","identityToken","onDelta","res","consumeSse","controller","init","retries","delay","ms","reader","decoder","buffer","flush","eventLine","l","dataLine","s","done","sep","record","bytes","byte","EMAIL_KEY","BOARD_URL_KEY","memoryId","memoryEmail","memoryBoardUrl","getAnonId","generateAnonId","getStoredReporterEmail","rememberReporterEmail","getCachedBoardUrl","cacheBoardUrl","en","zhCN","ja","ko","es","fr","de","locales","getMessages","locale","lang","MAX_DURATION_S","VALUE_ATTRIBUTE_NAMES","MASKED_INPUT_TYPES","RRWEB_CDN","RRWEB_SRI","rrwebLoaded","loadRrweb","loadViaImport","loadViaCdn","filterReplayEvent","filterReplayDomValues","isRecord","hasAttributeValue","attributeValue","findGlobalRecord","win","reject","existing","script","RecordSession","onMaxReached","ripple","RecordControlBar","getDuration","onStop","secs","mm","ss","RecordingManager","collectPageContext","detectBrowser","detectOS","ua","buildPayload","description","pageContext","consoleErrors","user","recording","anonId","anonEmail","breadcrumbs","MIN_FINALIZE_LOADING_MS","SCREENSHOT_WAIT_MS","SubmissionLifecycle","deps","enabled","submissionAttempt","request","waitForPaint","submissionRequest","screenshotController","trackedTask","submission","background","minimumLoading","feedback","task","completed","consume","BaseModeState","_event","_mode","IdleState","AnnotatingState","RecordingState","DrawingState","surface","PopupState","ChatClarifyingState","SubmittingState","HEX_COLOR","resolveBrandColors","colors","rejectBrandColors","isLocalHost","hostname","WidgetController","config","next","previous","touch","submitted","sessionId","action","recordingResult","exchange","saved","boardUrl","instance","lastConfig","pendingMount","cancelPendingMount","replaceInstance","MakeThisBetter"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"makethisbetter.cjs","sources":["../src/context/dom-utils.ts","../src/widget/shadow.ts","../src/widget/tab.ts","../src/widget/frustration-prompt.ts","../src/widget/dim-overlay.ts","../src/widget/pointer-kind.ts","../src/widget/page-offset.ts","../src/widget/scroll-lock.ts","../src/annotate/toolbar.ts","../src/annotate/overlay.ts","../src/annotate/highlighter.ts","../src/screenshot/geometry.ts","../src/annotate/pin.ts","../src/styles.ts","../src/annotate/draw.ts","../src/privacy/dom.ts","../src/annotate/session.ts","../src/popup/keyboard-inset.ts","../src/popup/sheet.ts","../src/annotate/draw-note-bar.ts","../src/popup/position.ts","../src/popup/focus-trap.ts","../src/popup/chat-popup.ts","../src/popup/receipt.ts","../src/privacy/sanitize.ts","../src/privacy/fields.ts","../src/privacy/text-context.ts","../src/screenshot/image.ts","../src/screenshot/svg-path.ts","../src/screenshot/capture.ts","../src/context/error-source.ts","../src/context/error-summary.ts","../src/context/console.ts","../src/context/history-patch.ts","../src/context/breadcrumbs.ts","../src/context/frustration-state.ts","../src/context/frustration.ts","../src/api/client.ts","../src/widget/anon-id.ts","../src/i18n/en.ts","../src/i18n/zh-CN.ts","../src/i18n/ja.ts","../src/i18n/ko.ts","../src/i18n/es.ts","../src/i18n/fr.ts","../src/i18n/de.ts","../src/i18n/index.ts","../src/record/session.ts","../src/record/control-bar.ts","../src/widget/recording-manager.ts","../src/context/collector.ts","../src/widget/payload.ts","../src/widget/submission-lifecycle.ts","../src/widget/mode-states.ts","../src/widget/brand-colors.ts","../src/widget/controller.ts","../src/index.ts"],"sourcesContent":["export const WIDGET_HOST_ID = 'mtb-widget-host'\n\nexport function isWidgetElement(el: Element): boolean {\n const host = document.getElementById(WIDGET_HOST_ID)\n if (!host) return false\n return host === el || host.contains(el)\n}\n\n// Skips the widget host via elementsFromPoint instead of toggling its\n// display — hiding the host on every mousemove flickered the toolbar.\nexport function elementUnderPoint(x: number, y: number): Element | null {\n return document.elementsFromPoint?.(x, y).find(el => el.id !== WIDGET_HOST_ID) ?? null\n}\n\nexport function escapeHtml(text: string): string {\n return text\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n}\n\n// A keydown fired mid-IME-composition confirms a candidate, not the widget\n// action bound to the key — acting on it would eat what a reporter typing\n// Chinese or Japanese meant as text. Some browsers reset isComposing before\n// the keydown reaches a capture handler but retain the legacy IME sentinel\n// in keyCode.\nexport function isImeConfirmKeydown(event: KeyboardEvent): boolean {\n return event.isComposing || event.keyCode === 229\n}\n\n// textContent counts the source of <style> and <script> children as text. A\n// page with an inline stylesheet in its header therefore names that header\n// after its own CSS, and that name travels into the pin, the report, and the\n// triage prompt. Only what a reader would see counts.\nconst UNRENDERED = /^(script|style|noscript|template)$/i\n\nfunction renderedText(el: Element, limit = 80): string {\n let out = ''\n for (const node of Array.from(el.childNodes)) {\n if (out.length >= limit) break\n\n if (node.nodeType === 3) {\n out += node.nodeValue ?? ''\n } else if (node instanceof Element && !UNRENDERED.test(node.tagName)) {\n out += renderedText(node, limit - out.length)\n }\n }\n return out\n}\n\nexport function getElementName(el: Element): string {\n if (el instanceof HTMLElement) {\n const label = el.getAttribute('aria-label')\n if (label) return label\n\n const title = el.getAttribute('title')\n if (title) return title\n\n const text = renderedText(el).trim().slice(0, 40)\n if (text) return text\n\n const placeholder = (el as HTMLInputElement).placeholder\n if (placeholder) return placeholder\n\n const alt = (el as HTMLImageElement).alt\n if (alt) return alt\n\n const dataMark = el.getAttribute('data-mark')\n if (dataMark) return dataMark\n }\n return el.tagName.toLowerCase()\n}\n\nexport function getCssSelector(el: Element): string {\n const parts: string[] = []\n let current: Element | null = el\n while (current && current !== document.body && parts.length < 5) {\n let selector = current.tagName.toLowerCase()\n if (current.id) {\n selector = `#${current.id}`\n parts.unshift(selector)\n break\n }\n const classNames = Array.from(current.classList)\n .filter(c => !c.startsWith('mtb-'))\n .slice(0, 2)\n .join('.')\n if (classNames) selector += `.${classNames}`\n\n const parent = current.parentElement\n if (parent) {\n const siblings = Array.from(parent.children).filter(\n c => c.tagName === current!.tagName,\n )\n if (siblings.length > 1) {\n const idx = siblings.indexOf(current) + 1\n selector += `:nth-of-type(${idx})`\n }\n }\n\n parts.unshift(selector)\n current = current.parentElement\n }\n return parts.join(' > ')\n}\n","import cssText from '../styles/widget.css?inline'\nimport { WIDGET_HOST_ID } from '../context/dom-utils'\nimport type { MakeThisBetterConfig } from '../types'\nimport type { WidgetBrandColors } from './brand-colors'\n\nexport class ShadowContainer {\n // Click-away layers listen at different points in a mouse or touch sequence.\n // Keep discrete widget interactions private, but leave pointermove composed\n // because toolbar dragging intentionally tracks movement on document.\n private static readonly HOST_INTERACTION_EVENTS = [\n 'pointerdown',\n 'pointerup',\n 'pointercancel',\n 'mousedown',\n 'mouseup',\n 'touchstart',\n 'touchend',\n 'touchcancel',\n 'click',\n ] as const\n\n // Readable so a component can mark the host with its own presence and have\n // :host(...) rules react — the only way CSS inside the shadow tree can be\n // told which components are currently mounted.\n readonly host: HTMLElement\n readonly root: ShadowRoot\n // Only a host we made is ours to take away. One the page supplied has to\n // outlive us, or a framework counting on it being there — Turbo pairs its\n // permanent elements by id — would find it gone after the first teardown.\n private readonly ownsHost: boolean\n // A borrowed host's inline style and theme attribute belong to the page.\n // Snapshot the whole attribute values, not just the properties we write:\n // cssText assignment below replaces everything the customer had inline, so\n // only the full original string can put it back. null means the attribute\n // did not exist, and restoring must remove it rather than leave ''.\n private readonly originalInlineStyle: string | null\n private readonly originalTheme: string | null\n\n constructor(\n theme: MakeThisBetterConfig['theme'] = 'auto',\n brandColors?: WidgetBrandColors,\n ) {\n // A page that renders its own host controls where the widget lives, and\n // that is the only way to survive a framework which replaces <body>\n // wholesale on navigation: Turbo carries an element marked\n // data-turbo-permanent across the swap, shadow root and all, so a\n // recording in progress is not thrown away by a link click.\n const provided = document.getElementById(WIDGET_HOST_ID)\n this.ownsHost = !provided\n\n this.host = provided ?? document.createElement('div')\n // Attach before any host mutation: a supplied element that cannot take a\n // shadow root fails right here, while the page's element is still exactly\n // as the page left it — there is no partially-styled host to unwind.\n this.root = this.host.shadowRoot ?? this.host.attachShadow({ mode: 'open' })\n this.root.replaceChildren()\n this.originalInlineStyle = this.host.getAttribute('style')\n this.originalTheme = this.host.getAttribute('data-mtb-theme')\n this.host.id = WIDGET_HOST_ID\n this.host.style.cssText = 'position:fixed;top:0;left:0;width:0;height:0;overflow:visible;z-index:2147483646;'\n if (brandColors) {\n const rgb = [1, 3, 5].map((offset) => parseInt(brandColors.primary.slice(offset, offset + 2), 16))\n const onPrimaryRgb = [1, 3, 5].map((offset) => parseInt(brandColors.onPrimary.slice(offset, offset + 2), 16))\n this.host.style.setProperty('--mtb-brand-primary', brandColors.primary.toLowerCase())\n this.host.style.setProperty('--mtb-brand-hover', brandColors.hover.toLowerCase())\n this.host.style.setProperty('--mtb-brand-active', brandColors.active.toLowerCase())\n this.host.style.setProperty('--mtb-brand-on-primary', brandColors.onPrimary.toLowerCase())\n this.host.style.setProperty('--mtb-brand-rgb', rgb.join(', '))\n this.host.style.setProperty('--mtb-brand-on-primary-rgb', onPrimaryRgb.join(', '))\n this.host.style.setProperty('--mtb-brand-emphasis', brandColors.primary.toLowerCase())\n this.host.style.setProperty('--mtb-brand-emphasis-rgb', rgb.join(', '))\n this.host.style.setProperty('--mtb-brand-gradient-start', brandColors.primary.toLowerCase())\n this.host.style.setProperty('--mtb-brand-gradient-end', brandColors.hover.toLowerCase())\n }\n this.host.setAttribute('data-mtb-theme', theme)\n if (this.ownsHost) document.body.appendChild(this.host)\n\n for (const eventType of ShadowContainer.HOST_INTERACTION_EVENTS) {\n this.root.addEventListener(eventType, this.stopHostInteraction)\n }\n\n const style = document.createElement('style')\n style.textContent = cssText\n this.root.appendChild(style)\n }\n\n el<T extends HTMLElement = HTMLElement>(tag: string, className?: string): T {\n const el = document.createElement(tag) as T\n if (className) el.className = className\n return el\n }\n\n append(...nodes: Node[]): void {\n for (const n of nodes) this.root.appendChild(n)\n }\n\n remove(...nodes: Node[]): void {\n for (const n of nodes) {\n if (n.parentNode === this.root) this.root.removeChild(n)\n }\n }\n\n private stopHostInteraction = (event: Event): void => {\n event.stopPropagation()\n }\n\n destroy(): void {\n for (const eventType of ShadowContainer.HOST_INTERACTION_EVENTS) {\n this.root.removeEventListener(eventType, this.stopHostInteraction)\n }\n\n if (!this.ownsHost) {\n // Borrowed. Empty it and leave it standing for whoever put it there.\n this.root.replaceChildren()\n this.host.removeAttribute('data-mtb-toolbar')\n if (this.originalInlineStyle === null) this.host.removeAttribute('style')\n else this.host.setAttribute('style', this.originalInlineStyle)\n if (this.originalTheme === null) this.host.removeAttribute('data-mtb-theme')\n else this.host.setAttribute('data-mtb-theme', this.originalTheme)\n return\n }\n\n // host.remove() is a safe no-op if the host is already detached — e.g.\n // when Turbo swapped document.body on navigation. removeChild(host) would\n // throw NotFoundError there, aborting re-init and dropping the widget.\n this.host.remove()\n }\n}\n","import type { ShadowContainer } from './shadow'\nimport type { I18nMessages } from '../i18n'\n\nexport class FeedbackTab {\n private el: HTMLButtonElement\n private onClick: () => void\n private idleLabel: string\n private activeLabel: string\n private customText?: string\n private active = false\n\n constructor(\n shadow: ShadowContainer,\n messages: I18nMessages,\n position: 'left' | 'right',\n onClick: () => void,\n customText?: string,\n ) {\n this.onClick = onClick\n this.customText = customText\n this.el = shadow.el<HTMLButtonElement>('button', 'mtb-tab')\n if (position === 'left') this.el.classList.add('left')\n this.idleLabel = customText ?? messages.tab\n this.activeLabel = messages.toolbar.exit\n this.setLabel(this.idleLabel)\n this.el.addEventListener('mousedown', this.preserveHostFocus)\n this.el.addEventListener('click', onClick)\n shadow.append(this.el)\n }\n\n private preserveHostFocus = (event: MouseEvent): void => {\n event.preventDefault()\n }\n\n private setLabel(label: string): void {\n this.el.textContent = label\n this.el.setAttribute('aria-label', label)\n }\n\n setMessages(messages: I18nMessages): void {\n this.idleLabel = this.customText ?? messages.tab\n this.activeLabel = messages.toolbar.exit\n this.setLabel(this.active ? this.activeLabel : this.idleLabel)\n }\n\n setActive(active: boolean): void {\n this.active = active\n this.el.classList.toggle('active', active)\n this.setLabel(active ? this.activeLabel : this.idleLabel)\n }\n\n destroy(): void {\n this.el.removeEventListener('mousedown', this.preserveHostFocus)\n this.el.removeEventListener('click', this.onClick)\n this.el.remove()\n }\n}\n","import type { ShadowContainer } from './shadow'\nimport type { I18nMessages } from '../i18n'\nimport { escapeHtml } from '../context/dom-utils'\n\nconst FRUSTRATION_DISMISS_MS = 8000\n\nexport interface FrustrationPromptHandlers {\n onTell: () => void\n // The reporter clicked Dismiss — a deliberate refusal.\n onDismiss: () => void\n // Nobody touched the card and it faded out on its own. Kept separate from\n // onDismiss because \"did not notice\" is not \"said no\": treating the two the\n // same suppressed every later prompt for the session.\n onAutoHide: () => void\n}\n\nexport class FrustrationPromptCard {\n private el: HTMLDivElement\n private autoHideTimer: ReturnType<typeof setTimeout> | null = null\n\n constructor(\n shadow: ShadowContainer,\n messages: I18nMessages,\n handlers: FrustrationPromptHandlers,\n ) {\n this.el = shadow.el<HTMLDivElement>('div', 'mtb-frustration-prompt')\n this.el.innerHTML = `\n <div class=\"mtb-frustration-icon\">⚠️</div>\n <span class=\"mtb-frustration-text\">${escapeHtml(messages.frustration.prompt)}</span>\n <div class=\"mtb-frustration-actions\">\n <button class=\"mtb-frustration-tell\">${escapeHtml(messages.frustration.action)}</button>\n <button class=\"mtb-frustration-dismiss\">${escapeHtml(messages.frustration.dismiss)}</button>\n </div>\n `\n\n this.el.querySelector('.mtb-frustration-tell')!.addEventListener('click', handlers.onTell)\n this.el.querySelector('.mtb-frustration-dismiss')!.addEventListener('click', handlers.onDismiss)\n\n shadow.append(this.el)\n this.autoHideTimer = setTimeout(handlers.onAutoHide, FRUSTRATION_DISMISS_MS)\n }\n\n destroy(): void {\n // Clearing matters on the engagement path too: without it, a card destroyed\n // by \"Tell us\" still fired its auto-hide 8s later.\n if (this.autoHideTimer) {\n clearTimeout(this.autoHideTimer)\n this.autoHideTimer = null\n }\n this.el.remove()\n }\n}\n","import type { ShadowContainer } from './shadow'\n\n/**\n * A touch browser replays the tap that opened the popup as a compatibility\n * click a moment after pointerup — and by then this scrim exists underneath,\n * so that click dismissed the popup the same gesture had just opened. On a\n * phone it read as the sheet flashing and vanishing, or as tapping doing\n * nothing at all. Cancelling pointerdown does not prevent it: WebKit still\n * dispatches the click (observed on iOS 18.7).\n *\n * So the scrim ignores anything that arrives before it could plausibly be a\n * new gesture. A deliberate dismissal is a separate press, well past this.\n */\nconst SPAWN_CLICK_GRACE_MS = 400\n\n// Full-screen scrim shown behind the comment popup. Clicking it resets the\n// widget, mirroring the design's dim-to-dismiss behaviour.\nexport class DimOverlay {\n private el: HTMLDivElement\n private bornAt = Date.now()\n private onClick: (e: Event) => void\n\n constructor(shadow: ShadowContainer, onDismiss: () => void, now: () => number = () => Date.now()) {\n this.bornAt = now()\n this.onClick = () => {\n if (now() - this.bornAt < SPAWN_CLICK_GRACE_MS) return\n onDismiss()\n }\n this.el = shadow.el<HTMLDivElement>('div', 'mtb-dim mtb-dim-clickable')\n this.el.addEventListener('click', this.onClick)\n shadow.append(this.el)\n }\n\n destroy(): void {\n this.el.removeEventListener('click', this.onClick)\n this.el.remove()\n }\n}\n","/**\n * Whether a finger is driving, rather than a mouse.\n *\n * Several behaviours fork on this — freezing the host page, which input\n * surface collects a note — and they have to agree, so the question is asked\n * in exactly one place.\n *\n * No matchMedia means no evidence of a touch device: assume a mouse and leave\n * the desktop path alone, which is the one that already worked.\n */\nexport function isTouchPointer(): boolean {\n return window.matchMedia?.('(pointer: coarse)').matches ?? false\n}\n","/**\n * Reserves room at the top of the host page for the annotation toolbar, so the\n * bar occupies space of its own instead of sitting on the customer's content.\n *\n * Without this the bar is roughly 100px of dead zone on a phone — 44px plus\n * the notch inset — covering exactly the strip most sites put their header in,\n * and taps there do nothing at all with no explanation. Measured on a device:\n * everything below responded, the whole top band did not.\n *\n * Known limit, and it is not small: `position: fixed` and `sticky` elements do\n * not move with padding. A site whose header is fixed keeps it where it is,\n * still underneath the bar. This buys back normal-flow content, which is most\n * of the page, and nothing else.\n *\n * The host page is not ours, so the previous inline value is put back verbatim\n * — writing '' would delete a padding the page had set for its own reasons.\n */\nexport class PageOffset {\n private applied = false\n private previousPadding = ''\n private previousTransition = ''\n\n reserve(px: number): void {\n if (this.applied || px <= 0) return\n this.applied = true\n\n const style = document.body.style\n this.previousPadding = style.paddingTop\n this.previousTransition = style.transition\n\n const current = parseFloat(getComputedStyle(document.body).paddingTop) || 0\n // Suppress any transition the page has on padding: the bar appears at once,\n // and a site animating its own layout in response looks like a glitch.\n style.transition = 'none'\n style.paddingTop = `${current + px}px`\n }\n\n release(): void {\n if (!this.applied) return\n this.applied = false\n\n const style = document.body.style\n style.paddingTop = this.previousPadding\n style.transition = this.previousTransition\n }\n\n isApplied(): boolean {\n return this.applied\n }\n}\n","/**\n * Holds the host page still while strokes or a note card are on screen.\n *\n * Annotations are positioned in viewport coordinates; scrolling moves the page\n * out from under them. The lock engages when drawing starts or a note card\n * opens, and releases when the surface tears down or the flow ends (exitAll).\n *\n * Nothing on the host page is mutated. Cancelling the gestures that scroll\n * leaves the page's own styles untouched, so there is nothing to restore and\n * nothing to strand if teardown is ever missed.\n */\nexport interface ScrollLock {\n /** Let the page scroll again. Safe to call more than once. */\n release(): void\n}\n\nconst POINTER_GESTURES = ['wheel', 'touchmove'] as const\n\nconst SCROLL_KEYS = new Set([\n 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight',\n 'PageUp', 'PageDown', 'Home', 'End', ' ',\n])\n\nfunction scrollsInternally(event: Event): boolean {\n for (const node of event.composedPath()) {\n if (!(node instanceof Element)) continue\n const style = getComputedStyle(node)\n const scrollable = /auto|scroll/.test(`${style.overflowY} ${style.overflowX}`)\n if (!scrollable) continue\n if (node.scrollHeight > node.clientHeight || node.scrollWidth > node.clientWidth) return true\n }\n return false\n}\n\nfunction consumesScrollKey(el: Element, key: string): boolean {\n const tag = el.tagName\n if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return true\n if (el instanceof HTMLElement && el.isContentEditable) return true\n if (key === ' ' && (tag === 'BUTTON' || tag === 'SUMMARY')) return true\n return false\n}\n\nexport function lockPageScroll(): ScrollLock {\n const blockGesture = (event: Event): void => {\n if (scrollsInternally(event)) return\n if (event.cancelable) event.preventDefault()\n }\n\n const blockKey = (event: Event): void => {\n if (!(event instanceof KeyboardEvent)) return\n if (!SCROLL_KEYS.has(event.key)) return\n const origin = event.composedPath()[0]\n if (origin instanceof Element && consumesScrollKey(origin, event.key)) return\n if (event.cancelable) event.preventDefault()\n }\n\n for (const gesture of POINTER_GESTURES) {\n window.addEventListener(gesture, blockGesture, { capture: true, passive: false })\n }\n window.addEventListener('keydown', blockKey, { capture: true })\n\n return {\n release(): void {\n for (const gesture of POINTER_GESTURES) {\n window.removeEventListener(gesture, blockGesture, { capture: true })\n }\n window.removeEventListener('keydown', blockKey, { capture: true })\n },\n }\n}\n","import type { ShadowContainer } from '../widget/shadow'\nimport type { I18nMessages } from '../i18n'\nimport { isTouchPointer } from '../widget/pointer-kind'\nimport { escapeHtml } from '../context/dom-utils'\n\nexport type ToolbarMode = 'markup' | 'record'\n\n// Mirrors the `height` the max-width: 480px block gives .mtb-toolbar. The\n// safe-area inset stacked on top of it can only be measured, not predicted.\nconst MOBILE_BAR_HEIGHT_PX = 44\n\nexport class AnnotationToolbar {\n private el: HTMLDivElement\n // The capsule, which is what gets measured and dimmed. hintTextEl is only the\n // words inside it — dimming that instead would leave the capsule around them\n // at full strength, and shrink the region counted as \"under the pointer\".\n private hintBarEl: HTMLDivElement\n private hintTextEl!: HTMLSpanElement\n private host: HTMLElement\n private markupBtn!: HTMLButtonElement\n private recordBtn!: HTMLButtonElement\n private messages: I18nMessages\n private currentMode: ToolbarMode = 'markup'\n private readonly touch: boolean\n\n // The hint sits over the page and carries nothing to click, so it steps back\n // while the pointer is on top of it and comes forward again the moment the\n // pointer leaves. Nothing else about the page's movement affects it.\n //\n // This is a hover affordance, and touch has no hover — but a tap still emits\n // a compatibility move wherever the finger landed, which would dim the hint\n // for no reason. Restricting to a real mouse makes it a no-op on a phone.\n private handlePointerMove = (e: PointerEvent): void => {\n if (e.pointerType !== 'mouse') return\n\n const rect = this.hintBarEl.getBoundingClientRect()\n const inside =\n e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom\n\n this.hintBarEl.classList.toggle('mtb-hint-bar--under-pointer', inside)\n }\n\n // Tracks whether the touch-hint dismiss listener is currently attached, so\n // add and remove stay symmetric: it is only ever added on touch with the\n // hint showing, and destroy() must not issue a remove for an add that never\n // happened.\n private touchHintDismissListening = false\n\n private dismissTouchHint = (): void => {\n this.hintBarEl.classList.add('mtb-hint-bar--dismissed')\n this.stopTouchHintDismissListening()\n }\n\n private stopTouchHintDismissListening(): void {\n if (!this.touchHintDismissListening) return\n this.touchHintDismissListening = false\n document.removeEventListener('pointerdown', this.dismissTouchHint, true)\n }\n\n constructor(\n shadow: ShadowContainer,\n messages: I18nMessages,\n onExit: () => void,\n onModeChange?: (mode: ToolbarMode) => void,\n position: 'left' | 'right' = 'right',\n showTouchHint = true,\n ) {\n this.messages = messages\n this.touch = isTouchPointer()\n\n // Two siblings, not one element. A fixed-position child is laid out against\n // its nearest transformed ancestor rather than the viewport, and the bar is\n // transformed both at rest and by its entry animation — so a hint nested\n // inside it could never be centred on the page.\n this.hintBarEl = shadow.el<HTMLDivElement>('div', 'mtb-hint-bar')\n // \"Click to comment\" is a lie on a phone, where a tap is deliberately inert\n // — the hint has to describe the one gesture that does anything.\n this.hintBarEl.innerHTML = `<span class=\"mtb-toolbar-hint\">${escapeHtml(this.idleHint())}</span>`\n if (this.touch && !showTouchHint) this.hintBarEl.classList.add('mtb-hint-bar--dismissed')\n\n this.el = shadow.el<HTMLDivElement>('div', 'mtb-toolbar')\n if (position === 'left') this.el.classList.add('left')\n this.el.innerHTML = this.buildHTML(messages)\n shadow.append(this.hintBarEl, this.el)\n\n // Lets the CSS retire the tab for exactly as long as this bar is up. Keyed\n // to the bar's own lifetime rather than to the tab's active state: the tab\n // is still active while a clarification card is open, and by then the bar\n // is gone — hiding it there would leave no way out at all.\n this.host = shadow.host\n this.host.setAttribute('data-mtb-toolbar', '')\n\n this.hintTextEl = this.hintBarEl.querySelector('.mtb-toolbar-hint')!\n this.markupBtn = this.el.querySelector('.mtb-toolbar-mode-markup')!\n this.recordBtn = this.el.querySelector('.mtb-toolbar-mode-record')!\n\n this.markupBtn.addEventListener('click', () => {\n this.setMode('markup')\n onModeChange?.('markup')\n })\n this.recordBtn.addEventListener('click', () => {\n this.setMode('record')\n onModeChange?.('record')\n })\n this.el.querySelector('.mtb-exit-btn')!.addEventListener('click', onExit)\n\n document.addEventListener('pointermove', this.handlePointerMove as EventListener)\n if (this.touch && showTouchHint) {\n document.addEventListener('pointerdown', this.dismissTouchHint, true)\n this.touchHintDismissListening = true\n }\n }\n\n private buildHTML(m: I18nMessages): string {\n return `\n <div class=\"mtb-toolbar-modes\">\n <button class=\"mtb-toolbar-mode-btn mtb-toolbar-mode-markup active\" aria-label=\"${escapeHtml(m.toolbar.markup)}\" title=\"${escapeHtml(m.toolbar.markup)}\">\n <svg width=\"16\" height=\"16\" 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\"/><line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"7\"/><line x1=\"12\" y1=\"17\" x2=\"12\" y2=\"21\"/><line x1=\"3\" y1=\"12\" x2=\"7\" y2=\"12\"/><line x1=\"17\" y1=\"12\" x2=\"21\" y2=\"12\"/></svg><span class=\"mtb-toolbar-label\">${escapeHtml(m.toolbar.markup)}</span>\n </button>\n <button class=\"mtb-toolbar-mode-btn mtb-toolbar-mode-record\" aria-label=\"${escapeHtml(m.toolbar.record)}\" title=\"${escapeHtml(m.toolbar.record)}\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"2\" y=\"6\" width=\"13\" height=\"12\" rx=\"2.5\"/><path d=\"M15 10.5 21.2 7.4a.6.6 0 0 1 .8.5v8.2a.6.6 0 0 1-.8.5L15 13.5z\"/></svg><span class=\"mtb-toolbar-label\">${escapeHtml(m.toolbar.record)}</span>\n </button>\n </div>\n <span class=\"mtb-toolbar-sep\"></span>\n <button class=\"mtb-exit-btn\" aria-label=\"${escapeHtml(m.toolbar.exit)}\" title=\"${escapeHtml(m.toolbar.exit)}\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 6L6 18M6 6l12 12\"/>\n </svg><span class=\"mtb-toolbar-label\">${escapeHtml(m.toolbar.exit)}</span>\n </button>\n `\n }\n\n private idleHint(): string {\n return this.touch ? this.messages.toolbar.hintTouch : this.messages.toolbar.hint\n }\n\n setMode(mode: ToolbarMode): void {\n this.currentMode = mode\n if (mode === 'markup') {\n this.markupBtn.classList.add('active')\n this.recordBtn.classList.remove('active')\n this.hintTextEl.textContent = this.idleHint()\n } else {\n this.markupBtn.classList.remove('active')\n this.recordBtn.classList.add('active')\n this.hintTextEl.textContent = this.messages.toolbar.hintRecord\n }\n }\n\n getMode(): ToolbarMode {\n return this.currentMode\n }\n\n /**\n * How much vertical room the bar occupies, including any safe-area inset.\n *\n * Falls back to the height the stylesheet declares when measurement returns\n * nothing — which happens before first paint, and in any environment without\n * layout. Reserving the wrong amount is recoverable; reserving none means\n * the bar covers the page again, which is the defect this exists to fix.\n */\n height(): number {\n return this.el.getBoundingClientRect().height || MOBILE_BAR_HEIGHT_PX\n }\n\n destroy(): void {\n document.removeEventListener('pointermove', this.handlePointerMove as EventListener)\n this.stopTouchHintDismissListening()\n this.host.removeAttribute('data-mtb-toolbar')\n this.hintBarEl.remove()\n this.el.remove()\n }\n}\n","import type { ShadowContainer } from '../widget/shadow'\n\nexport interface OverlayHandlers {\n onPointerDown: (e: PointerEvent) => void\n onPointerMove: (e: PointerEvent) => void\n onPointerUp: (e: PointerEvent) => void\n onPointerCancel: (e: PointerEvent) => void\n}\n\n/**\n * The annotation surface. Runs on pointer events only — never mouse events.\n *\n * Mouse listeners are deliberately absent. Touch browsers replay a tap as\n * pointerdown/up followed by compatibility mousedown/up/click, so listening to\n * both would run the annotation logic twice for one tap and drop two pins.\n * Pointer events already cover mouse and stylus, so the desktop loses nothing.\n */\nexport class AnnotationOverlay {\n private overlayEl: HTMLDivElement\n private dimEl: HTMLDivElement\n private handlers: OverlayHandlers\n private onPointerDown: (e: Event) => void\n private onPointerMove: (e: Event) => void\n private onPointerUp: (e: Event) => void\n private onPointerCancel: (e: Event) => void\n\n constructor(shadow: ShadowContainer, handlers: OverlayHandlers) {\n this.handlers = handlers\n\n this.overlayEl = shadow.el<HTMLDivElement>('div', 'mtb-overlay')\n\n this.onPointerDown = (e) => {\n const pe = e as PointerEvent\n if (!pe.isPrimary) return\n // Capture keeps a stroke alive when the finger crosses the overlay edge.\n // Without it, a drag that leaves the surface silently stops mid-line.\n try {\n this.overlayEl.setPointerCapture(pe.pointerId)\n } catch {\n // Thrown when the pointer is already gone. The stroke can still run\n // uncaptured, so this is not worth aborting the gesture over.\n }\n this.handlers.onPointerDown(pe)\n }\n\n this.onPointerMove = (e) => {\n const pe = e as PointerEvent\n if (!pe.isPrimary) return\n this.handlers.onPointerMove(pe)\n }\n\n this.onPointerUp = (e) => {\n const pe = e as PointerEvent\n if (!pe.isPrimary) return\n this.releaseCapture(pe.pointerId)\n this.handlers.onPointerUp(pe)\n }\n\n // Replaces the old mouseleave path. The browser fires this when it takes\n // the gesture away from us — a system gesture, or the page going\n // background — which is exactly when a half-drawn stroke should end.\n this.onPointerCancel = (e) => {\n const pe = e as PointerEvent\n // A palm or second finger can be cancelled on its own. That pointer never\n // reached the handlers, so acting on its cancel would cut short a stroke\n // the primary finger is still drawing.\n if (!pe.isPrimary) return\n this.releaseCapture(pe.pointerId)\n this.handlers.onPointerCancel(pe)\n }\n\n this.overlayEl.addEventListener('pointerdown', this.onPointerDown)\n this.overlayEl.addEventListener('pointermove', this.onPointerMove)\n this.overlayEl.addEventListener('pointerup', this.onPointerUp)\n this.overlayEl.addEventListener('pointercancel', this.onPointerCancel)\n shadow.append(this.overlayEl)\n\n this.dimEl = shadow.el<HTMLDivElement>('div', 'mtb-dim')\n shadow.append(this.dimEl)\n }\n\n private releaseCapture(pointerId: number): void {\n try {\n this.overlayEl.releasePointerCapture(pointerId)\n } catch {\n // Already released, or the pointer is gone. Nothing to undo.\n }\n }\n\n destroy(): void {\n this.overlayEl.removeEventListener('pointerdown', this.onPointerDown)\n this.overlayEl.removeEventListener('pointermove', this.onPointerMove)\n this.overlayEl.removeEventListener('pointerup', this.onPointerUp)\n this.overlayEl.removeEventListener('pointercancel', this.onPointerCancel)\n this.overlayEl.remove()\n this.dimEl.remove()\n }\n}\n","import type { ShadowContainer } from '../widget/shadow'\nimport { getElementName } from '../context/dom-utils'\n\nexport class ElementHighlighter {\n private highlightEl: HTMLDivElement\n private tooltipEl: HTMLDivElement\n\n constructor(shadow: ShadowContainer) {\n this.highlightEl = shadow.el<HTMLDivElement>('div', 'mtb-highlight')\n this.tooltipEl = shadow.el<HTMLDivElement>('div', 'mtb-highlight-tooltip')\n this.hide()\n shadow.append(this.highlightEl, this.tooltipEl)\n }\n\n highlight(el: Element): void {\n const rect = el.getBoundingClientRect()\n this.highlightEl.style.top = `${rect.top - 2}px`\n this.highlightEl.style.left = `${rect.left - 2}px`\n this.highlightEl.style.width = `${rect.width + 4}px`\n this.highlightEl.style.height = `${rect.height + 4}px`\n this.highlightEl.style.display = 'block'\n this.showTooltip(el, rect)\n }\n\n private showTooltip(el: Element, rect: DOMRect): void {\n this.tooltipEl.textContent = getElementName(el)\n const below = rect.top < 30\n this.tooltipEl.classList.toggle('below', below)\n this.tooltipEl.style.left = `${rect.left + rect.width / 2}px`\n this.tooltipEl.style.top = below ? `${rect.bottom + 8}px` : `${rect.top - 8}px`\n this.tooltipEl.style.display = 'block'\n }\n\n select(): void {\n this.highlightEl.classList.add('selected')\n this.tooltipEl.style.display = 'none'\n }\n\n hide(): void {\n this.highlightEl.style.display = 'none'\n this.highlightEl.classList.remove('selected')\n this.tooltipEl.style.display = 'none'\n }\n\n destroy(): void {\n this.highlightEl.remove()\n this.tooltipEl.remove()\n }\n}\n","// The capture pipeline works in clone-space: viewport rects corrected for\n// nested-scroll drift, i.e. where html-to-image renders each element in the\n// captured bitmap. Capture metrics and privacy covers are the same kind of\n// rectangle in that one space, so they share this spelling. Crop rectangles\n// (viewport / canvas pixels) stay a separate type on purpose — the space\n// difference is information.\nexport interface Rect {\n left: number\n top: number\n width: number\n height: number\n}\n\n// Owns the offset+scale math from clone-space onto the rendered canvas, so\n// callers stop hand-writing (v - left) * scaleX at every layer. left/top is\n// the clone origin inside the capture; scale is capture CSS px → canvas px.\nexport interface CaptureTransform {\n left: number\n top: number\n scaleX: number\n scaleY: number\n}\n\nexport function captureTransform(region: Rect, canvasWidth: number, canvasHeight: number): CaptureTransform {\n return {\n left: region.left,\n top: region.top,\n scaleX: canvasWidth / region.width,\n scaleY: canvasHeight / region.height,\n }\n}\n\nexport function toCanvasX(transform: CaptureTransform, x: number): number {\n return (x - transform.left) * transform.scaleX\n}\n\nexport function toCanvasY(transform: CaptureTransform, y: number): number {\n return (y - transform.top) * transform.scaleY\n}\n\nexport function rectToCanvas(transform: CaptureTransform, rect: Rect): Rect {\n return {\n left: toCanvasX(transform, rect.left),\n top: toCanvasY(transform, rect.top),\n width: rect.width * transform.scaleX,\n height: rect.height * transform.scaleY,\n }\n}\n\nexport interface ScrollOffset {\n x: number\n y: number\n}\n\n/**\n * The capture region: the visual viewport, and nothing else. html-to-image\n * renders document.body shifted by the page scroll so the on-screen region\n * lands at the canvas origin — which makes this rect both the capture request\n * and the deliverable. There is no crop step: cropping a clone-space bitmap\n * was only ever needed when the capture was the whole document.\n */\nexport function viewportCaptureRect(): Rect {\n return { left: 0, top: 0, width: window.innerWidth, height: window.innerHeight }\n}\n\n/**\n * How far an anchored annotation must move to land in a capture's frame.\n *\n * The anchor is the annotation's scroll debt at creation (page + nested\n * scrollers); the capture's pageScroll is the page half of that debt at capture\n * time. Adding the anchor lifts the annotation into clone space, subtracting\n * the capture scroll brings it back down into the viewport bitmap.\n */\nexport function anchorScreenshotOffset(\n anchor: ScrollOffset,\n pageScroll: ScrollOffset,\n): ScrollOffset {\n return { x: anchor.x - pageScroll.x, y: anchor.y - pageScroll.y }\n}\n\n// How far a viewport measurement has to move to reach clone space: every\n// scroller between the element and the body has pushed its content out of view\n// by its own scrollLeft/scrollTop, and html-to-image renders it back.\nexport function nestedScrollOffset(target: Element | null): ScrollOffset {\n let x = 0\n let y = 0\n let current = target\n\n while (current && current !== document.body) {\n if (current instanceof HTMLElement) {\n x += current.scrollLeft\n y += current.scrollTop\n }\n current = parentElement(current)\n }\n\n return { x, y }\n}\n\nexport function parentElement(element: Element): Element | null {\n if (element.parentElement) return element.parentElement\n\n const root = element.getRootNode()\n return root instanceof ShadowRoot ? root.host : null\n}\n\n/**\n * The full offset from a viewport measurement to clone space, page scroll\n * included — the anchor an annotation records so that later scrolling cannot\n * move it.\n *\n * nestedScrollOffset stops at the body because the capture's own metrics carry\n * the page scroll for anything measured at capture time. An annotation is not\n * measured at capture time, so it has to carry that half itself.\n */\nexport function captureAnchorOffset(target: Element | null): ScrollOffset {\n const nested = nestedScrollOffset(target)\n return { x: nested.x + window.scrollX, y: nested.y + window.scrollY }\n}\n","import type { ShadowContainer } from '../widget/shadow'\n\nexport class PinMarker {\n private pins: HTMLDivElement[] = []\n private shadow: ShadowContainer\n\n constructor(shadow: ShadowContainer) {\n this.shadow = shadow\n }\n\n addPin(x: number, y: number): void {\n const pin = this.shadow.el<HTMLDivElement>('div', 'mtb-pin')\n pin.style.top = `${y}px`\n pin.style.left = `${x}px`\n this.shadow.append(pin)\n this.pins.push(pin)\n }\n\n clearAll(): void {\n for (const pin of this.pins) pin.remove()\n this.pins = []\n }\n\n destroy(): void {\n this.clearAll()\n }\n}\n","export const BRAND_COLOR = '#059669'\n// Same green as BRAND_COLOR, in the form shadows need. Derived once so a brand\n// change cannot leave the shadows on the old colour.\nconst BRAND_RGB = '5,150,105'\n\nexport function brandAlpha(alpha: number): string {\n return `rgba(${BRAND_RGB},${alpha})`\n}\n\nexport const FONT_STACK = '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif'\n\nexport const DRAW_STROKE = {\n color: 'var(--mtb-brand-primary)',\n width: '3.5',\n linecap: 'round',\n linejoin: 'round',\n filter: 'drop-shadow(0 0 1px rgba(255, 255, 255, 0.95)) drop-shadow(0 1px 2px rgba(15, 23, 42, 0.72))',\n} as const\n","import type { ShadowContainer } from '../widget/shadow'\nimport type { Annotation } from '../types'\nimport { DRAW_STROKE } from '../styles'\nimport { elementUnderPoint } from '../context/dom-utils'\nimport { nestedScrollOffset } from '../screenshot/geometry'\nimport type { ScrollOffset } from '../screenshot/geometry'\n\ntype Point = [number, number]\n\ninterface StrokeAnchor {\n /** Page scroll plus enclosing-container scroll: the offset to clone space. */\n offset: ScrollOffset\n /** The page-scroll half on its own, kept so viewportDrift can undo it. */\n pageScroll: ScrollOffset\n}\n\ninterface Stroke {\n path: SVGPathElement\n d: string\n points: Point[]\n anchor: StrokeAnchor\n}\n\nexport class DrawLayer {\n private svg: SVGSVGElement\n private currentPath: SVGPathElement | null = null\n private currentPoints: Point[] = []\n // Sampled when the stroke starts rather than when the drawing is handed over,\n // so a page or container scroll partway through a multi-stroke drawing moves\n // neither the strokes already made nor the ones still to come.\n private currentAnchor: StrokeAnchor = emptyAnchor()\n private isDrawing = false\n private committed: Stroke[] = []\n private redoStack: { d: string; points: Point[]; anchor: StrokeAnchor }[] = []\n\n constructor(shadow: ShadowContainer) {\n this.svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')\n this.svg.setAttribute('class', 'mtb-draw-svg')\n shadow.append(this.svg)\n }\n\n startDraw(x: number, y: number): void {\n this.isDrawing = true\n this.currentPoints = [[x, y]]\n this.currentAnchor = strokeAnchor(x, y)\n this.currentPath = this.createPath()\n this.svg.appendChild(this.currentPath)\n }\n\n continueDraw(x: number, y: number): void {\n if (!this.isDrawing || !this.currentPath) return\n this.currentPoints.push([x, y])\n this.currentPath.setAttribute('d', buildPathD(this.currentPoints))\n }\n\n /**\n * How far the pointer has wandered from where it went down, in pixels.\n *\n * This is what separates a tap from a stroke. Counting points cannot: a\n * mouse click reports no movement at all, while a finger reports a burst of\n * sub-pixel jitter, so any point-based threshold reads every tap on a phone\n * as a drawing.\n */\n getCurrentSpan(): number {\n const [start] = this.currentPoints\n if (!start) return 0\n\n let max = 0\n for (const [x, y] of this.currentPoints) {\n max = Math.max(max, Math.hypot(x - start[0], y - start[1]))\n }\n return max\n }\n\n // Commits the in-progress stroke to the undo stack and clears the redo stack.\n // Callers decide whether the stroke has enough points before committing.\n commitStroke(): boolean {\n if (!this.isDrawing || !this.currentPath) return false\n this.isDrawing = false\n this.committed.push({\n path: this.currentPath,\n d: buildPathD(this.currentPoints),\n points: this.currentPoints,\n anchor: this.currentAnchor,\n })\n this.redoStack = []\n this.currentPath = null\n this.currentPoints = []\n return true\n }\n\n cancelCurrentDraw(): void {\n this.currentPath?.remove()\n this.currentPath = null\n this.isDrawing = false\n this.currentPoints = []\n }\n\n undo(): boolean {\n const stroke = this.committed.pop()\n if (!stroke) return false\n stroke.path.remove()\n this.redoStack.push({ d: stroke.d, points: stroke.points, anchor: stroke.anchor })\n return true\n }\n\n redo(): boolean {\n const entry = this.redoStack.pop()\n if (!entry) return false\n const path = this.createPath()\n path.setAttribute('d', entry.d)\n this.svg.appendChild(path)\n // The stroke comes back in the frame it was drawn in, not the frame the\n // page happens to be scrolled to when redo is pressed.\n this.committed.push({ path, d: entry.d, points: entry.points, anchor: entry.anchor })\n return true\n }\n\n canUndo(): boolean {\n return this.committed.length > 0\n }\n\n canRedo(): boolean {\n return this.redoStack.length > 0\n }\n\n hasStrokes(): boolean {\n return this.committed.length > 0\n }\n\n getAnnotation(): Annotation | null {\n const [first] = this.committed\n if (!first) return null\n\n // Strokes made either side of a scroll were recorded against different\n // viewports. Restate them all in the frame the first stroke was drawn in so\n // the drawing is one shape, and hand that frame's anchor to the caller.\n const strokes = this.committed.map(stroke => shiftPoints(stroke.points, {\n x: stroke.anchor.offset.x - first.anchor.offset.x,\n y: stroke.anchor.offset.y - first.anchor.offset.y,\n }))\n const [cx, cy] = center(strokes.flat())\n return {\n type: 'draw',\n x: cx,\n y: cy,\n drawPath: strokes.map(buildPathD).join(' '),\n captureOffsetX: first.anchor.offset.x,\n captureOffsetY: first.anchor.offset.y,\n }\n }\n\n /**\n * How far the drawing's frame has drifted from the live viewport, so a caller\n * can map a point in the drawing back onto the page as it stands now — which\n * is what hit-testing the drawing's target needs.\n */\n viewportDrift(): ScrollOffset {\n const [first] = this.committed\n if (!first) return { x: 0, y: 0 }\n return {\n x: window.scrollX - first.anchor.pageScroll.x,\n y: window.scrollY - first.anchor.pageScroll.y,\n }\n }\n\n clearAll(): void {\n while (this.svg.firstChild) this.svg.removeChild(this.svg.firstChild)\n this.currentPath = null\n this.isDrawing = false\n this.currentPoints = []\n this.committed = []\n this.redoStack = []\n }\n\n isCurrentlyDrawing(): boolean {\n return this.isDrawing\n }\n\n destroy(): void {\n this.svg.remove()\n }\n\n private createPath(): SVGPathElement {\n const path = document.createElementNS('http://www.w3.org/2000/svg', 'path')\n path.setAttribute('fill', 'none')\n path.setAttribute('stroke', DRAW_STROKE.color)\n path.setAttribute('stroke-width', DRAW_STROKE.width)\n path.setAttribute('stroke-linecap', DRAW_STROKE.linecap)\n path.setAttribute('stroke-linejoin', DRAW_STROKE.linejoin)\n path.setAttribute('style', `filter:${DRAW_STROKE.filter}`)\n return path\n }\n}\n\nfunction buildPathD(points: Point[]): string {\n if (points.length === 0) return ''\n const [fx, fy] = points[0]\n let d = `M ${fx} ${fy}`\n for (let i = 1; i < points.length; i++) {\n const [x, y] = points[i]\n d += ` L ${x} ${y}`\n }\n return d\n}\n\nfunction emptyAnchor(): StrokeAnchor {\n return { offset: { x: 0, y: 0 }, pageScroll: { x: 0, y: 0 } }\n}\n\nfunction strokeAnchor(x: number, y: number): StrokeAnchor {\n const pageScroll = { x: window.scrollX, y: window.scrollY }\n const nested = nestedScrollOffset(elementUnderPoint(x, y))\n return {\n offset: { x: nested.x + pageScroll.x, y: nested.y + pageScroll.y },\n pageScroll,\n }\n}\n\nfunction shiftPoints(points: Point[], offset: ScrollOffset): Point[] {\n if (offset.x === 0 && offset.y === 0) return points\n return points.map(([x, y]) => [x + offset.x, y + offset.y])\n}\n\nfunction center(points: Point[]): Point {\n const xs = points.map(p => p[0])\n const ys = points.map(p => p[1])\n const cx = (Math.min(...xs) + Math.max(...xs)) / 2\n const cy = (Math.min(...ys) + Math.max(...ys)) / 2\n return [cx, cy]\n}\n","// rrweb's own defaults for opting an element out of a replay. Named here so the\n// README can document them and so the values stay in one place.\nexport const BLOCK_CLASS = 'rr-block'\nexport const MASK_TEXT_CLASS = 'rr-mask'\n\nexport const PRIVACY_SELECTOR = `.${BLOCK_CLASS}, .${MASK_TEXT_CLASS}`\n\nexport function isPrivacyProtected(element: Element): boolean {\n return element.closest(PRIVACY_SELECTOR) !== null\n}\n\nexport function structuralElementName(element: Element): string {\n const role = element.getAttribute('role')?.trim().split(/\\s+/)[0]\n if (role && /^[a-z][a-z0-9-]*$/i.test(role)) return role\n return element.tagName.toLowerCase()\n}\n\nexport function structuralElementSelector(element: Element): string {\n return element.tagName.toLowerCase()\n}\n","import type { ShadowContainer } from '../widget/shadow'\nimport type { I18nMessages } from '../i18n'\nimport type { Annotation } from '../types'\nimport { AnnotationOverlay } from './overlay'\nimport { ElementHighlighter } from './highlighter'\nimport { getElementName, getCssSelector, elementUnderPoint } from '../context/dom-utils'\nimport { captureAnchorOffset } from '../screenshot/geometry'\nimport { PinMarker } from './pin'\nimport { DrawLayer } from './draw'\nimport { isTouchPointer } from '../widget/pointer-kind'\nimport { isPrivacyProtected, structuralElementName, structuralElementSelector } from '../privacy/dom'\n\n// How far the pointer must travel before the gesture counts as drawing rather\n// than pointing. Roughly the slop browsers themselves allow between mousedown\n// and click, and comfortably above the jitter a resting finger produces.\nconst DRAW_MIN_TRAVEL_PX = 10\n\ninterface SessionHandlers {\n onPin: (annotation: Annotation, targetName: string) => void\n onDrawStroke: () => void\n}\n\nfunction annotationTarget(element: Element): { name: string; text: string; selector: string } {\n if (isPrivacyProtected(element)) {\n return {\n name: structuralElementName(element),\n text: '',\n selector: structuralElementSelector(element),\n }\n }\n\n return {\n name: getElementName(element),\n text: element.textContent?.trim().slice(0, 200) ?? '',\n selector: getCssSelector(element),\n }\n}\n\nexport class AnnotationSession {\n private overlay: AnnotationOverlay\n private highlighter: ElementHighlighter\n private pinMarker: PinMarker\n private drawLayer: DrawLayer\n private interactionActive = true\n private pendingHighlightFrame: number | null = null\n private lastMoveX = 0\n private lastMoveY = 0\n // Read once. A pointer kind does not change mid-session, and re-querying it\n // per gesture would let a mid-stroke media-query flip strand a stroke.\n private readonly touch = isTouchPointer()\n\n constructor(\n shadow: ShadowContainer,\n private messages: I18nMessages,\n private handlers: SessionHandlers,\n ) {\n this.highlighter = new ElementHighlighter(shadow)\n this.pinMarker = new PinMarker(shadow)\n this.drawLayer = new DrawLayer(shadow)\n this.overlay = new AnnotationOverlay(shadow, {\n onPointerDown: (e) => this.handlePointerDown(e),\n onPointerMove: (e) => this.handlePointerMove(e),\n onPointerUp: (e) => this.handlePointerUp(e),\n // Previously mouseleave. The overlay now captures the pointer, so a\n // stroke no longer ends by wandering off the surface — only by the\n // browser taking the gesture away, which is what pointercancel means.\n onPointerCancel: () => {\n if (this.drawLayer.isCurrentlyDrawing()) this.finishStroke()\n this.cancelPendingHighlight()\n this.highlighter.hide()\n },\n })\n }\n\n private handlePointerDown(e: PointerEvent): void {\n e.preventDefault()\n this.drawLayer.startDraw(e.clientX, e.clientY)\n }\n\n private handlePointerMove(e: PointerEvent): void {\n if (this.drawLayer.isCurrentlyDrawing()) {\n this.cancelPendingHighlight()\n this.drawLayer.continueDraw(e.clientX, e.clientY)\n this.highlighter.hide()\n return\n }\n\n this.lastMoveX = e.clientX\n this.lastMoveY = e.clientY\n if (this.pendingHighlightFrame !== null) return\n this.pendingHighlightFrame = requestAnimationFrame(() => {\n this.pendingHighlightFrame = null\n this.updateHighlight()\n })\n }\n\n private updateHighlight(): void {\n if (!this.interactionActive || this.drawLayer.isCurrentlyDrawing() || this.drawSessionActive()) return\n const underEl = elementUnderPoint(this.lastMoveX, this.lastMoveY)\n if (underEl) this.highlighter.highlight(underEl)\n else this.highlighter.hide()\n }\n\n private cancelPendingHighlight(): void {\n if (this.pendingHighlightFrame === null) return\n cancelAnimationFrame(this.pendingHighlightFrame)\n this.pendingHighlightFrame = null\n }\n\n private handlePointerUp(e: PointerEvent): void {\n if (!this.drawLayer.isCurrentlyDrawing()) return\n this.finishStroke(e)\n }\n\n // A drag that travels far enough becomes a freehand stroke and opens the\n // draw note bar; a tap on an element becomes a pin. Stray taps while a\n // drawing already exists are ignored so they don't hijack the draw flow.\n private finishStroke(e?: PointerEvent): void {\n if (this.drawLayer.getCurrentSpan() >= DRAW_MIN_TRAVEL_PX) {\n this.drawLayer.commitStroke()\n this.highlighter.hide()\n this.handlers.onDrawStroke()\n return\n }\n\n this.drawLayer.cancelCurrentDraw()\n // A finger draws; it does not point. Hitting a 40px control on a phone is\n // fiddly, and the habit people already have is to scribble over what is\n // wrong rather than tap it precisely. So on touch a tap means nothing at\n // all — which also retires a whole family of defects, since every \"the tap\n // did nothing\" and \"a stray dot appeared\" report was a tap that carried\n // meaning it should never have carried. The element identity a pin used to\n // supply is recovered from the stroke instead, in getDrawAnnotation().\n if (this.touch) return\n if (this.drawLayer.hasStrokes() || !e) return\n\n const underEl = elementUnderPoint(e.clientX, e.clientY)\n const target = underEl ? annotationTarget(underEl) : null\n const targetName = target?.name ?? this.messages.annotation.element\n const rect = underEl?.getBoundingClientRect()\n // Sampled here, alongside the coordinates it belongs to. Read any later and\n // it would describe wherever the reporter has scrolled to since.\n const anchor = captureAnchorOffset(underEl)\n const annotation: Annotation = {\n type: 'pin',\n x: e.clientX,\n y: e.clientY,\n targetSelector: target?.selector,\n targetText: target?.text ?? '',\n targetName,\n targetRect: rect ? { top: rect.top, left: rect.left, width: rect.width, height: rect.height, bottom: rect.bottom } : undefined,\n captureOffsetX: anchor.x,\n captureOffsetY: anchor.y,\n }\n this.pinMarker.addPin(e.clientX, e.clientY)\n this.highlighter.select()\n this.handlers.onPin(annotation, targetName)\n }\n\n undoDraw(): void {\n this.drawLayer.undo()\n }\n\n redoDraw(): void {\n this.drawLayer.redo()\n }\n\n canUndoDraw(): boolean {\n return this.drawLayer.canUndo()\n }\n\n canRedoDraw(): boolean {\n return this.drawLayer.canRedo()\n }\n\n hasDrawStrokes(): boolean {\n return this.drawLayer.hasStrokes()\n }\n\n private drawSessionActive(): boolean {\n return this.drawLayer.hasStrokes() || this.drawLayer.canRedo()\n }\n\n // The stroke carries the element identity a pin used to supply. Without it a\n // touch report would say only \"the user drew somewhere\", where the board has\n // always been able to say which control they meant — that identity is what\n // makes a report actionable, and it should not be the price of not making\n // people aim. Resolved from the stroke's centre, so a circle drawn around a\n // button lands inside it. Best effort by design: an underline sits below its\n // target and will miss, which is why the drawing itself is still the record.\n getDrawAnnotation(): Annotation | null {\n const annotation = this.drawLayer.getAnnotation()\n if (!annotation) return null\n\n // The drawing's coordinates are in the frame it was drawn in; hit-testing\n // reads the page as it is now, so the centre has to make that trip first.\n const drift = this.drawLayer.viewportDrift()\n const underEl = elementUnderPoint(annotation.x - drift.x, annotation.y - drift.y)\n if (!underEl) return annotation\n\n // The rect and its own anchor are read in the same breath, so together they\n // say where this element sits in the capture; subtracting the drawing's\n // anchor restates that in the frame the rest of the annotation uses.\n const rect = underEl.getBoundingClientRect()\n const anchor = captureAnchorOffset(underEl)\n const dx = anchor.x - (annotation.captureOffsetX ?? 0)\n const dy = anchor.y - (annotation.captureOffsetY ?? 0)\n const target = annotationTarget(underEl)\n return {\n ...annotation,\n targetSelector: target.selector,\n targetText: target.text,\n targetName: target.name,\n targetRect: {\n top: rect.top + dy,\n left: rect.left + dx,\n width: rect.width,\n height: rect.height,\n bottom: rect.bottom + dy,\n },\n }\n }\n\n clearDraw(): void {\n this.drawLayer.clearAll()\n }\n\n dismissInteraction(): void {\n if (!this.interactionActive) return\n this.interactionActive = false\n this.cancelPendingHighlight()\n this.overlay.destroy()\n }\n\n hideHighlight(): void {\n this.highlighter.hide()\n }\n\n destroy(): void {\n this.dismissInteraction()\n this.highlighter.destroy()\n this.pinMarker.destroy()\n this.drawLayer.destroy()\n }\n}\n","/**\n * How much of the window the software keyboard is covering.\n *\n * A `position: fixed` sheet gets no help from the browser here — the built-in\n * \"scroll the focused field into view\" only moves elements that are in the\n * document flow, so on iOS the keyboard simply covers the sheet.\n *\n * The two platforms hide the keyboard in different places, and this one\n * formula covers both. iOS keeps the layout viewport at full height and\n * shrinks (and shifts) the visual viewport, so the difference is the keyboard.\n * Android resizes the layout viewport itself, so `innerHeight` shrinks too and\n * the difference falls out to zero — which is the right answer there, because\n * the bottom of the window is already above the keyboard.\n *\n * `offsetTop` matters as much as `height`: iOS shifts the visual viewport up\n * as well as shrinking it, and ignoring the shift leaves the sheet floating\n * exactly that far off, with a strip of page showing beneath it.\n */\n\n/**\n * The keyboard slides in, and iOS reports the viewport continuously while it\n * moves. Measured on an iOS 18.7 device over 130ms: 660 -> 323 -> 95 -> 660.\n * Acting on each of those walked the sheet up to 337px, then 565px — off the\n * top of the screen — and back down. Waiting for the numbers to stop moving\n * turns that into one placement.\n */\nconst SETTLE_MS = 140\n\nexport class KeyboardInset {\n private onChange: ((inset: number) => void) | null = null\n private timer: ReturnType<typeof setTimeout> | null = null\n private lastReading: number | null = null\n\n private handler = (): void => {\n if (this.timer !== null) clearTimeout(this.timer)\n this.timer = setTimeout(() => {\n this.timer = null\n const reading = this.current()\n // Take it only once the viewport says the same thing twice. Mid-slide\n // iOS reports values that are not measurements — -122 and 95 on a 660px\n // window were both seen — and a single quiet moment is not proof the\n // animation has finished, only that events paused. Two agreeing reads\n // are, and cost one more settle interval.\n if (reading === this.lastReading) {\n this.lastReading = null\n this.onChange?.(reading)\n return\n }\n this.lastReading = reading\n this.handler()\n }, SETTLE_MS)\n }\n\n current(): number {\n const vp = window.visualViewport\n if (!vp) return 0\n\n // Observed on iOS 18.7 mid-keyboard-animation: height reported as -122.\n // A viewport has no negative height — that is not a small measurement, it\n // is not a measurement. Deriving an inset from it gave 782px on a 660px\n // window, and only the settle window happened to discard it that time.\n if (vp.height <= 0) return 0\n\n const inset = Math.round(window.innerHeight - (vp.height + vp.offsetTop))\n // The keyboard cannot hide more than the window contains.\n return Math.min(Math.max(0, inset), window.innerHeight)\n }\n\n observe(onChange: (inset: number) => void): void {\n const vp = window.visualViewport\n if (!vp) return\n this.onChange = onChange\n vp.addEventListener('resize', this.handler)\n // Scroll fires for the shift-up on iOS, which resize alone does not report.\n vp.addEventListener('scroll', this.handler)\n }\n\n stop(): void {\n const vp = window.visualViewport\n this.onChange = null\n this.lastReading = null\n if (this.timer !== null) {\n clearTimeout(this.timer)\n this.timer = null\n }\n if (!vp) return\n vp.removeEventListener('resize', this.handler)\n vp.removeEventListener('scroll', this.handler)\n }\n}\n","import { KeyboardInset } from './keyboard-inset'\n\n/**\n * Matches the `max-width: 480px` block in widget.css, where every panel that\n * holds a text field stops being a card anchored near the annotation and\n * becomes a bottom sheet.\n */\nexport const SHEET_MAX_WIDTH = 480\n\nexport function isSheetViewport(): boolean {\n return window.innerWidth <= SHEET_MAX_WIDTH\n}\n\n/**\n * Hands a panel's geometry to the stylesheet on narrow screens, and keeps it\n * clear of the software keyboard.\n *\n * Two jobs that have to live together. The panels each position themselves\n * with inline `left`/`top`, which would beat the media query and drag the sheet\n * back into a floating card — so the inline values have to be cleared, not just\n * overridden. And a `position: fixed` sheet gets no help from the browser's\n * scroll-into-view, which only moves elements in the document flow, so the\n * keyboard simply covers it unless something lifts it.\n *\n * Shared by the comment popup, the clarify card, and the draw note bar: all\n * three carry a text field, and all three are the last thing between a reporter\n * and a submitted report.\n */\nexport class SheetLayout {\n private inset = new KeyboardInset()\n\n constructor(private el: HTMLElement) {}\n\n /** Returns true when this took over layout, i.e. the caller should not position. */\n apply(): boolean {\n if (!isSheetViewport()) return false\n\n this.el.style.left = ''\n this.el.style.top = ''\n this.el.style.maxHeight = ''\n this.lift(this.inset.current())\n this.inset.observe((px) => this.lift(px))\n return true\n }\n\n release(): void {\n this.inset.stop()\n }\n\n /**\n * Lifting is clamped so the panel's own top edge stays on screen.\n *\n * The rule is geometric rather than a guess about how tall a keyboard gets:\n * an earlier version refused any inset above 60% of the window, a number\n * calibrated from a single reading on a single device. What actually matters\n * is that the reporter can still see and reach the panel, and that is a\n * question about this panel's height, which is known right here.\n */\n private lift(inset: number): void {\n const headroom = Math.max(0, window.innerHeight - this.el.offsetHeight)\n const applied = Math.min(inset, headroom)\n this.el.style.bottom = applied > 0 ? `${applied}px` : ''\n }\n}\n","import type { ShadowContainer } from '../widget/shadow'\nimport type { I18nMessages } from '../i18n'\nimport { escapeHtml, isImeConfirmKeydown } from '../context/dom-utils'\nimport { SheetLayout } from '../popup/sheet'\n\ninterface DrawNoteBarOptions {\n messages: I18nMessages\n onUndo: () => void\n onRedo: () => void\n onCancel: () => void\n onSubmit: (note: string) => void\n // Fired on every keystroke; the owner uses it to defer heavy capture work\n // until the reporter pauses.\n onActivity?: () => void\n}\n\nexport class DrawNoteBar {\n private el: HTMLDivElement\n private sheet: SheetLayout\n private input!: HTMLInputElement\n private undoBtn!: HTMLButtonElement\n private redoBtn!: HTMLButtonElement\n private submitBtn!: HTMLButtonElement\n private onSubmit: (note: string) => void\n private submitLabel: string\n private messages: I18nMessages\n\n constructor(shadow: ShadowContainer, opts: DrawNoteBarOptions) {\n this.onSubmit = opts.onSubmit\n this.submitLabel = opts.messages.draw.submit\n this.messages = opts.messages\n\n this.el = shadow.el<HTMLDivElement>('div', 'mtb-draw-bar')\n this.el.innerHTML = this.buildHTML(opts.messages)\n shadow.append(this.el)\n // The bar carries a text field and sits flush to the bottom on a phone,\n // which is exactly where the keyboard lands.\n this.sheet = new SheetLayout(this.el)\n this.sheet.apply()\n\n this.input = this.el.querySelector('.mtb-draw-input')!\n this.undoBtn = this.el.querySelector('.mtb-draw-undo')!\n this.redoBtn = this.el.querySelector('.mtb-draw-redo')!\n this.submitBtn = this.el.querySelector('.mtb-draw-submit')!\n\n this.undoBtn.addEventListener('click', opts.onUndo)\n this.redoBtn.addEventListener('click', opts.onRedo)\n this.el.querySelector('.mtb-draw-cancel')!.addEventListener('click', opts.onCancel)\n this.submitBtn.addEventListener('click', () => this.submit())\n if (opts.onActivity) this.input.addEventListener('input', opts.onActivity)\n this.input.addEventListener('keydown', (e: KeyboardEvent) => {\n if (isImeConfirmKeydown(e)) return\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault()\n this.submit()\n }\n })\n\n this.input.focus({ preventScroll: true })\n }\n\n private buildHTML(m: I18nMessages): string {\n return `\n <input class=\"mtb-draw-input\" type=\"text\" placeholder=\"${escapeHtml(m.draw.note_placeholder)}\" />\n <button class=\"mtb-draw-icon-btn mtb-draw-undo\" type=\"button\" title=\"${escapeHtml(m.draw.undo)}\" aria-label=\"${escapeHtml(m.draw.undo)}\" disabled>\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M9 14L4 9l5-5\"/><path d=\"M4 9h11a5 5 0 0 1 0 10h-1\"/></svg>\n </button>\n <button class=\"mtb-draw-icon-btn mtb-draw-redo\" type=\"button\" title=\"${escapeHtml(m.draw.redo)}\" aria-label=\"${escapeHtml(m.draw.redo)}\" disabled>\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M15 14l5-5-5-5\"/><path d=\"M20 9H9a5 5 0 0 0 0 10h1\"/></svg>\n </button>\n <label class=\"mtb-draw-shot\" title=\"${escapeHtml(m.popup.screenshot_locked_hint)}\">\n <input type=\"checkbox\" checked disabled><span>${escapeHtml(m.popup.screenshot_short)}</span>\n </label>\n <span class=\"mtb-draw-sep\"></span>\n <button class=\"mtb-draw-cancel\" type=\"button\">${escapeHtml(m.draw.cancel)}</button>\n <button class=\"mtb-draw-submit\" type=\"button\">${escapeHtml(m.draw.submit)}</button>\n `\n }\n\n setLoading(loading: boolean): void {\n if (loading) {\n this.submitBtn.disabled = true\n this.submitBtn.classList.add('mtb-btn-loading')\n this.submitBtn.textContent = this.messages.popup.sending\n this.input.disabled = true\n } else {\n this.submitBtn.disabled = false\n this.submitBtn.classList.remove('mtb-btn-loading')\n this.submitBtn.textContent = this.submitLabel\n this.input.disabled = false\n }\n }\n\n setUndoRedo(canUndo: boolean, canRedo: boolean): void {\n this.undoBtn.disabled = !canUndo\n this.redoBtn.disabled = !canRedo\n }\n\n private submit(): void {\n this.onSubmit(this.input.value.trim())\n }\n\n destroy(): void {\n this.sheet.release()\n this.el.remove()\n }\n}\n\n","// Shared floating-panel geometry for the chat card and its kin. Panels anchor\n// a fixed panel inside the viewport while keeping clear of the launcher tab;\n// the margin and tab reserve used to be duplicated in each class and had to\n// stay hand-synced, so they are named once here.\n\n/** Gap kept between a floating panel and every viewport edge. */\nexport const VIEWPORT_MARGIN = 12\n/** Horizontal space reserved for the launcher tab on its side of the screen. */\nexport const TAB_WIDTH = 32\n\n/**\n * The horizontal band a floating panel may occupy: the viewport minus the edge\n * margin, minus the launcher tab on whichever side it sits.\n */\nexport interface ViewportFrame {\n vw: number\n vh: number\n minLeft: number\n maxRight: number\n}\n\nexport function measureViewportFrame(tabPosition: 'left' | 'right'): ViewportFrame {\n const vw = window.innerWidth\n const vh = window.innerHeight\n return {\n vw,\n vh,\n minLeft: VIEWPORT_MARGIN + (tabPosition === 'left' ? TAB_WIDTH : 0),\n maxRight: vw - VIEWPORT_MARGIN - (tabPosition === 'right' ? TAB_WIDTH : 0),\n }\n}\n\n/** Widest a panel can be without crossing the edge margin or the launcher tab. */\nexport function panelWidth(preferred: number, frame: ViewportFrame): number {\n return Math.min(preferred, frame.vw - 2 * VIEWPORT_MARGIN - TAB_WIDTH)\n}\n","/**\n * Keeps keyboard focus inside an open card, and remembers who opened it.\n *\n * The cards float above a dimmed host page, but the dim is only visual: Tab\n * happily walks out of the card and into controls the reporter can no longer\n * see or reach with a pointer. Cycling within the card is what the dialog role\n * promises assistive tech, and restoring the opener afterwards puts a keyboard\n * user back where they were instead of at the top of the document.\n *\n * The Tab handler lives on the card element, not on document or window: it can\n * only ever see keys typed while focus is inside the card, and removing the\n * card's own listener is a teardown the host page cannot be stranded by.\n */\nexport interface FocusTrap {\n /**\n * Detach the Tab handler without moving focus — for when the element is\n * handed to a successor (the clarify continuation) that installs its own\n * trap and must not have focus yanked out from under it.\n */\n release(): void\n /** Put focus back on whatever had it when the trap was installed. */\n restoreFocus(): void\n}\n\nconst TABBABLE_SELECTOR = [\n 'button:not([disabled])',\n 'input:not([disabled])',\n 'textarea:not([disabled])',\n 'select:not([disabled])',\n 'a[href]',\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(', ')\n\nexport function trapFocus(card: HTMLElement): FocusTrap {\n // Captured before any card control gets autofocused, so the trap remembers\n // the host page's element, not the card's own field.\n const opener = document.activeElement instanceof HTMLElement ? document.activeElement : null\n\n const handleKeydown = (event: KeyboardEvent): void => {\n if (event.key !== 'Tab') return\n const tabbables = Array.from(card.querySelectorAll<HTMLElement>(TABBABLE_SELECTOR))\n .filter(el => !el.hidden)\n if (tabbables.length === 0) return\n\n const first = tabbables[0]\n const last = tabbables[tabbables.length - 1]\n // Inside a shadow tree the document only reports the host element; the\n // shadow root knows which control actually holds focus. Fall back to the\n // document for environments that leave ShadowRoot.activeElement null.\n const root = card.getRootNode() as Document | ShadowRoot\n const active = root.activeElement ?? document.activeElement\n\n if (event.shiftKey) {\n if (active === first || !card.contains(active)) {\n event.preventDefault()\n last.focus({ preventScroll: true })\n }\n } else if (active === last || !card.contains(active)) {\n event.preventDefault()\n first.focus({ preventScroll: true })\n }\n }\n\n card.addEventListener('keydown', handleKeydown)\n\n return {\n release(): void {\n card.removeEventListener('keydown', handleKeydown)\n },\n restoreFocus(): void {\n // A detached opener would silently drop focus on <body>, so only reach\n // for one that still exists. preventScroll for the same reason every\n // panel uses it: the widget must never scroll the host page.\n if (opener?.isConnected) opener.focus({ preventScroll: true })\n },\n }\n}\n","import type { ShadowContainer } from '../widget/shadow'\nimport type { I18nMessages } from '../i18n'\nimport type { ClarifyStreamResult } from '../api/client'\nimport type { SubmissionSessionResponse, TargetRect } from '../types'\nimport { escapeHtml, isImeConfirmKeydown } from '../context/dom-utils'\nimport { SheetLayout } from './sheet'\nimport { measureViewportFrame, panelWidth, VIEWPORT_MARGIN } from './position'\nimport { trapFocus, type FocusTrap } from './focus-trap'\n\ntype ChatState = 'idle' | 'sending' | 'clarifying' | 'answering' | 'done'\n\ninterface ChatPopupOptions {\n targetName: string\n x: number\n y: number\n targetRect?: TargetRect\n messages: I18nMessages\n position?: 'left' | 'right'\n screenshotEnabled?: boolean\n onScreenshotToggle?: (enabled: boolean) => void\n onSubmit: (description: string) => Promise<SubmissionSessionResponse | null>\n onClarify: (sessionId: string, token: string, onDelta: (text: string) => void, signal?: AbortSignal) => Promise<ClarifyStreamResult>\n onAnswer: (sessionId: string, token: string, message: string) => Promise<unknown>\n onFinalize: () => Promise<void> | void\n onCancel: () => void\n /** A question is on screen: the card now holds an uploaded submission. */\n onConversationOpen?: () => void\n onActivity?: () => void\n recordingSeconds?: number\n // Resolved when the success view renders, not at mount: both depend on the\n // handoff the finalize returns, which does not exist while the reporter is\n // still typing.\n resolveBoardLink?: () => (() => void) | undefined\n resolveEmailCapture?: () => { onSubmit: (email: string) => Promise<boolean> } | undefined\n}\n\nconst CARD_WIDTH = 320\nconst AUTO_CLOSE_MS = 5000\n\nconst BRAND_URL = 'https://makethisbetter.dev'\n\nconst BRAND_ICON = '<svg viewBox=\"0 0 512 512\" fill=\"none\"><defs><linearGradient id=\"mtb-bg\" x1=\".3\" y1=\"0\" x2=\".7\" y2=\"1\"><stop offset=\"0%\" stop-color=\"var(--mtb-chat-logo-start, #10b981)\"/><stop offset=\"100%\" stop-color=\"var(--mtb-chat-logo-end, #059669)\"/></linearGradient></defs><rect width=\"512\" height=\"512\" rx=\"96\" fill=\"url(#mtb-bg)\"/><g transform=\"translate(89,89) scale(13.9)\" stroke=\"var(--mtb-chat-logo-fg, #fff)\" stroke-width=\"2.1\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 2v6h-6\"/><path d=\"M21 13a9 9 0 1 1-3-7.7L21 8\"/><circle cx=\"12\" cy=\"12\" r=\"2.2\" fill=\"var(--mtb-chat-logo-fg, #fff)\" stroke=\"none\"/></g></svg>'\nconst CLOSE_ICON = '<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"currentColor\"><path d=\"M8.9 8l3.3-3.3-.9-.9L8 7.1 4.7 3.8l-.9.9L7.1 8l-3.3 3.3.9.9L8 8.9l3.3 3.3.9-.9z\"/></svg>'\nconst CAMERA_ICON = '<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentColor\"><path d=\"M6.2 2.6h3.6l.9 1.4H14a1 1 0 011 1v7.4a1 1 0 01-1 1H2a1 1 0 01-1-1V5a1 1 0 011-1h3.3l.9-1.4z\"/><circle cx=\"8\" cy=\"9\" r=\"2.5\" class=\"mtb-chat-icon-lens\"/></svg>'\nconst RECORD_ICON = '<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentColor\"><circle cx=\"8\" cy=\"8\" r=\"6.6\"/><polygon points=\"6.4,5.2 11,8 6.4,10.8\" class=\"mtb-chat-icon-lens\"/></svg>'\nconst CHECK_ICON = '<svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path d=\"M8.1 14.6L3.8 10.3l1.5-1.5 2.8 2.8 6.6-6.6 1.5 1.5z\"/></svg>'\n\nexport class ChatPopup {\n private el: HTMLDivElement\n private sheet: SheetLayout\n private focusTrap: FocusTrap\n private messagesEl!: HTMLDivElement\n private composerEl!: HTMLDivElement\n private actionsEl!: HTMLDivElement\n private inputEl!: HTMLTextAreaElement\n private sendBtn!: HTMLButtonElement\n private quickRepliesEl!: HTMLDivElement\n private state: ChatState = 'idle'\n private i18n: I18nMessages\n private opts: ChatPopupOptions\n private autoCloseTimer: ReturnType<typeof setTimeout> | null = null\n private session: SubmissionSessionResponse | null = null\n private clarifyAbort: AbortController | null = null\n\n constructor(shadow: ShadowContainer, opts: ChatPopupOptions) {\n this.opts = opts\n this.i18n = opts.messages\n\n this.el = shadow.el<HTMLDivElement>('div', 'mtb-chat')\n this.el.setAttribute('role', 'dialog')\n this.el.setAttribute('aria-modal', 'true')\n this.el.setAttribute('aria-label', opts.targetName)\n this.el.innerHTML = this.buildHTML()\n shadow.append(this.el)\n this.focusTrap = trapFocus(this.el)\n this.sheet = new SheetLayout(this.el)\n if (!this.sheet.apply()) {\n this.positionCard(opts.x, opts.y, opts.targetRect, opts.position ?? 'right')\n }\n\n this.messagesEl = this.el.querySelector('.mtb-chat-messages')!\n this.composerEl = this.el.querySelector('.mtb-chat-composer')!\n this.actionsEl = this.el.querySelector('.mtb-chat-actions')!\n this.inputEl = this.el.querySelector('.mtb-chat-input')!\n this.sendBtn = this.el.querySelector('.mtb-chat-send')!\n this.quickRepliesEl = this.el.querySelector('.mtb-chat-quick-replies')!\n\n this.bindEvents()\n this.handleEscape = this.handleEscape.bind(this)\n window.addEventListener('keydown', this.handleEscape)\n this.inputEl.focus({ preventScroll: true })\n }\n\n private handleEscape(e: KeyboardEvent): void {\n if (e.key !== 'Escape') return\n e.preventDefault()\n e.stopPropagation()\n if (this.state === 'sending') return\n if (this.state === 'done') {\n this.destroy()\n return\n }\n this.opts.onCancel()\n }\n\n private buildHTML(): string {\n const m = this.i18n\n const opts = this.opts\n const quickOpts = m.popup.quickOptions\n .map(o => `<button class=\"mtb-chat-qr\" type=\"button\" data-quick=\"${escapeHtml(o.label)}\" data-value=\"${escapeHtml(o.value ?? o.label)}\">${escapeHtml(o.emoji)} ${escapeHtml(o.label)}</button>`)\n .join('')\n\n const capture = opts.recordingSeconds != null\n ? `<span class=\"mtb-chat-attachment\" title=\"${escapeHtml(m.popup.recording_locked_hint)}\">${RECORD_ICON}<span class=\"mtb-chat-capture-label\">${Math.round(opts.recordingSeconds)}s</span></span>`\n : opts.onScreenshotToggle\n ? `<button class=\"mtb-chat-screenshot\" type=\"button\" title=\"${escapeHtml(m.popup.screenshot_hint)}\" aria-pressed=\"${opts.screenshotEnabled !== false}\">${CAMERA_ICON}<span class=\"mtb-chat-capture-label\">${escapeHtml(m.popup.screenshot_short)}</span></button>`\n : ''\n\n return `\n <div class=\"mtb-chat-header\">\n <div class=\"mtb-chat-handle\"></div>\n <div class=\"mtb-chat-header-row\">\n <a class=\"mtb-chat-brand\" href=\"${BRAND_URL}\" target=\"_blank\" rel=\"noopener\">\n <span class=\"mtb-chat-logo\">${BRAND_ICON}</span>\n <span class=\"mtb-chat-brand-name\">Make This Better</span>\n </a>\n <span class=\"mtb-chat-header-gap\"></span>\n <button class=\"mtb-chat-close\" type=\"button\" aria-label=\"${escapeHtml(m.popup.close)}\">${CLOSE_ICON}</button>\n </div>\n </div>\n <div class=\"mtb-chat-messages\"></div>\n <div class=\"mtb-chat-quick-replies\">${quickOpts}</div>\n <div class=\"mtb-chat-composer\">\n <textarea class=\"mtb-chat-input\" rows=\"1\" placeholder=\"${escapeHtml(m.popup.placeholder)}\"></textarea>\n <div class=\"mtb-chat-actions\">\n ${capture}\n <span class=\"mtb-chat-actions-gap\"></span>\n <button class=\"mtb-chat-send\" type=\"button\" disabled>${escapeHtml(m.clarify.send)}</button>\n </div>\n </div>\n `\n }\n\n private bindEvents(): void {\n this.el.querySelector('.mtb-chat-close')!.addEventListener('click', () => {\n this.opts.onCancel()\n })\n\n this.quickRepliesEl.querySelectorAll('.mtb-chat-qr').forEach(btn => {\n btn.addEventListener('click', () => {\n const value = (btn as HTMLElement).dataset['value'] ?? (btn as HTMLElement).dataset['quick'] ?? ''\n this.inputEl.value = value\n this.sendMessage(value)\n })\n })\n\n this.inputEl.addEventListener('input', () => {\n this.sendBtn.disabled = !this.inputEl.value.trim()\n this.opts.onActivity?.()\n })\n\n this.inputEl.addEventListener('keydown', (e: KeyboardEvent) => {\n if (isImeConfirmKeydown(e)) return\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault()\n const text = this.inputEl.value.trim()\n if (text) {\n this.sendMessage(text)\n } else if (this.state === 'clarifying') {\n this.skipAndSend()\n }\n }\n })\n\n this.sendBtn.addEventListener('click', () => {\n const text = this.inputEl.value.trim()\n if (text) this.sendMessage(text)\n })\n\n const screenshotBtn = this.el.querySelector('.mtb-chat-screenshot')\n if (screenshotBtn && this.opts.onScreenshotToggle) {\n screenshotBtn.addEventListener('click', () => {\n const pressed = screenshotBtn.getAttribute('aria-pressed') === 'true'\n screenshotBtn.setAttribute('aria-pressed', String(!pressed))\n screenshotBtn.classList.toggle('mtb-chat-screenshot-off', pressed)\n this.opts.onScreenshotToggle!(!pressed)\n })\n }\n }\n\n private async sendMessage(text: string): Promise<void> {\n if (this.state === 'sending' || this.state === 'answering') return\n\n if (this.state === 'clarifying') {\n this.handleAnswer(text)\n return\n }\n\n this.state = 'sending'\n this.appendUserBubble(text)\n this.quickRepliesEl.style.display = 'none'\n this.hideCapture()\n this.showThinking()\n this.setComposerDisabled(true)\n\n let session: SubmissionSessionResponse | null\n try {\n session = await this.opts.onSubmit(text)\n } catch {\n session = null\n }\n\n if (!session) {\n this.removeThinking()\n this.setComposerDisabled(false)\n this.showRetryableError(this.i18n.error.submit, () => {\n this.clearError()\n void this.sendMessage(text)\n })\n this.state = 'idle'\n return\n }\n\n this.session = session\n\n if (!session.ai_clarify_available) {\n this.removeThinking()\n void this.showDone()\n return\n }\n\n let result: ClarifyStreamResult | null = null\n let streaming = false\n this.clarifyAbort = new AbortController()\n try {\n result = await this.opts.onClarify(session.id, session.token, (delta) => {\n if (!streaming) {\n this.removeThinking()\n this.startStreamingBubble()\n streaming = true\n }\n this.appendStreamDelta(delta)\n }, this.clarifyAbort.signal)\n } catch {\n if (!streaming) this.removeThinking()\n else this.endStreamingBubble()\n this.showDone()\n return\n }\n\n if (!streaming) this.removeThinking()\n else this.endStreamingBubble()\n\n if (result.done || !result.messages.length) {\n void this.showDone()\n return\n }\n\n const aiMessage = result.messages.find(msg => msg.role === 'assistant')\n if (!streaming && aiMessage) {\n this.appendAiBubble(aiMessage.content)\n }\n this.renderSuggestions(result.suggestions)\n this.state = 'clarifying'\n this.setComposerDisabled(false)\n this.inputEl.value = ''\n this.inputEl.placeholder = this.i18n.clarify.placeholder\n this.sendBtn.textContent = this.i18n.clarify.submit ?? this.i18n.clarify.send\n this.sendBtn.disabled = true\n this.showSkip()\n this.inputEl.focus({ preventScroll: true })\n this.opts.onConversationOpen?.()\n }\n\n private handleAnswer(text: string): void {\n const session = this.session\n if (!session) return\n this.state = 'done'\n this.appendUserBubble(text)\n this.clearSuggestions()\n this.hideSkip()\n this.setComposerDisabled(true)\n this.renderSuccess()\n // The answer has to reach the server before finalize runs: finalizing\n // deactivates the session, after which the server rejects the answer as\n // terminal. A failed answer still finalizes — losing the follow-up answer\n // must never lose the feedback itself.\n Promise.resolve(this.opts.onAnswer(session.id, session.token, text))\n .catch(() => {})\n .then(() => this.finalizeInBackground())\n }\n\n /**\n * The receipt goes up the moment the reporter is done rather than waiting on\n * the finalize round trip; the hand-off it returns is patched into the card\n * when it lands. A failed finalize replaces this receipt with an in-card\n * retry state so the reporter stays in the conversation they used.\n */\n private showDone(): void {\n this.state = 'done'\n this.hideSkip()\n this.setComposerDisabled(true)\n this.renderSuccess()\n this.finalizeInBackground()\n }\n\n /**\n * The success view takes over everything under the header — the transcript,\n * the quick replies and the composer are all gone, so the card reads as a\n * receipt rather than a conversation the reporter could keep typing into.\n */\n private renderSuccess(): void {\n const m = this.i18n.success\n this.messagesEl.remove()\n this.quickRepliesEl.remove()\n this.composerEl.remove()\n\n const view = this.el.querySelector<HTMLDivElement>('.mtb-chat-success') ?? document.createElement('div')\n view.className = 'mtb-chat-success mtb-chat-success-simple'\n view.replaceChildren()\n\n const icon = document.createElement('div')\n icon.className = 'mtb-chat-success-icon'\n icon.innerHTML = CHECK_ICON\n view.appendChild(icon)\n\n const title = document.createElement('div')\n title.className = 'mtb-chat-success-title'\n title.textContent = this.session?.ai_clarify_available === false ? m.title_no_ai : m.title\n view.appendChild(title)\n\n const link = document.createElement('a')\n link.className = 'mtb-chat-success-link mtb-chat-success-link-pending'\n link.href = '#'\n link.textContent = m.track_feedback\n link.addEventListener('click', (e: MouseEvent) => e.preventDefault())\n view.appendChild(link)\n\n if (!view.parentElement) this.el.appendChild(view)\n this.attachHandoff()\n }\n\n /** Keep a failed background finalize in the conversation the reporter used. */\n showFinalizeFailure(onRetry?: () => void): void {\n if (this.autoCloseTimer) {\n clearTimeout(this.autoCloseTimer)\n this.autoCloseTimer = null\n }\n\n let view = this.el.querySelector<HTMLDivElement>('.mtb-chat-success')\n if (!view) {\n this.messagesEl.remove()\n this.quickRepliesEl.remove()\n this.composerEl.remove()\n view = document.createElement('div')\n this.el.appendChild(view)\n }\n\n view.className = 'mtb-chat-success mtb-chat-finalize-failure'\n view.innerHTML = `\n <div class=\"mtb-chat-success-icon mtb-chat-failure-icon\">!</div>\n <div class=\"mtb-chat-success-title\">${escapeHtml(this.i18n.error.finalize_title)}</div>\n <div class=\"mtb-chat-finalize-message\">${escapeHtml(this.i18n.error.finalize_message)}</div>\n ${onRetry ? `<button class=\"mtb-chat-finalize-retry\" type=\"button\">${escapeHtml(this.i18n.clarify.retry)}</button>` : ''}\n <button class=\"mtb-chat-finalize-close\" type=\"button\">${escapeHtml(this.i18n.success.close)}</button>\n `\n\n if (onRetry) {\n view.querySelector<HTMLButtonElement>('.mtb-chat-finalize-retry')!.addEventListener('click', () => {\n const retry = view.querySelector<HTMLButtonElement>('.mtb-chat-finalize-retry')!\n retry.disabled = true\n retry.innerHTML = '<span class=\"mtb-chat-finalize-spinner\" aria-label=\"Retrying\"></span>'\n onRetry()\n })\n }\n view.querySelector<HTMLButtonElement>('.mtb-chat-finalize-close')!.addEventListener('click', () => this.opts.onCancel())\n }\n\n /** Restore the receipt after a retry reaches the final endpoint. */\n showFinalizeSuccess(): void {\n this.state = 'done'\n this.renderSuccess()\n }\n\n /**\n * The board link and the email form are both built from the hand-off that\n * finalize returns, but the receipt renders the moment the reporter is done\n * rather than waiting on that round trip — so this runs twice: once against\n * whatever is already known, and again when finalize lands.\n */\n private attachHandoff(): void {\n const view = this.el.querySelector<HTMLDivElement>('.mtb-chat-success')\n if (!view) return\n const m = this.i18n.success\n\n const openBoard = this.opts.resolveBoardLink?.()\n const pendingLink = view.querySelector<HTMLAnchorElement>('.mtb-chat-success-link-pending')\n if (openBoard && pendingLink) {\n pendingLink.classList.remove('mtb-chat-success-link-pending')\n pendingLink.addEventListener('click', (e: MouseEvent) => {\n e.preventDefault()\n openBoard()\n })\n }\n\n const emailCapture = this.opts.resolveEmailCapture?.()\n if (emailCapture && !view.querySelector('.mtb-chat-email-card')) {\n if (this.autoCloseTimer) {\n clearTimeout(this.autoCloseTimer)\n this.autoCloseTimer = null\n }\n view.classList.remove('mtb-chat-success-simple')\n view.appendChild(this.buildEmailCard(emailCapture))\n } else if (!emailCapture && !this.autoCloseTimer && !view.querySelector('.mtb-chat-email-card')) {\n this.autoCloseTimer = setTimeout(() => this.destroy(), AUTO_CLOSE_MS)\n }\n }\n\n private finalizeInBackground(): void {\n Promise.resolve(this.opts.onFinalize())\n .then(() => this.attachHandoff())\n .catch(() => {})\n }\n\n private buildEmailCard(capture: { onSubmit: (email: string) => Promise<boolean> }): HTMLDivElement {\n const m = this.i18n.success\n const card = document.createElement('div')\n card.className = 'mtb-chat-email-card'\n\n const row = document.createElement('div')\n row.className = 'mtb-chat-email-row'\n\n const field = document.createElement('div')\n field.className = 'mtb-chat-email-field'\n const input = document.createElement('input')\n input.className = 'mtb-chat-email-input'\n input.type = 'email'\n input.placeholder = m.email_cta\n field.appendChild(input)\n\n const btn = document.createElement('button')\n btn.className = 'mtb-chat-email-submit'\n btn.type = 'button'\n btn.textContent = m.notify\n btn.disabled = true\n\n const submitEmail = async () => {\n const email = input.value.trim()\n if (!email) return\n btn.disabled = true\n input.disabled = true\n const saved = await capture.onSubmit(email)\n if (saved) {\n const note = document.createElement('div')\n note.className = 'mtb-chat-email-note'\n note.textContent = m.email_saved\n card.replaceChildren(note)\n this.autoCloseTimer = setTimeout(() => this.destroy(), AUTO_CLOSE_MS)\n return\n }\n input.disabled = false\n btn.disabled = false\n const error = card.querySelector('.mtb-chat-email-error') ?? document.createElement('div')\n error.className = 'mtb-chat-email-error'\n error.textContent = m.email_error\n card.appendChild(error)\n }\n\n input.addEventListener('input', () => { btn.disabled = !input.value.trim() })\n input.addEventListener('keydown', (e: KeyboardEvent) => {\n if (e.key === 'Enter' && input.value.trim()) void submitEmail()\n })\n btn.addEventListener('click', () => void submitEmail())\n\n row.appendChild(field)\n row.appendChild(btn)\n card.appendChild(row)\n return card\n }\n\n private appendUserBubble(text: string): void {\n const bubble = document.createElement('div')\n bubble.className = 'mtb-chat-bubble mtb-chat-bubble-user'\n bubble.textContent = text\n this.messagesEl.appendChild(bubble)\n this.messagesEl.scrollTop = this.messagesEl.scrollHeight\n this.inputEl.value = ''\n this.sendBtn.disabled = true\n }\n\n private streamingBubbleEl: HTMLDivElement | null = null\n\n private startStreamingBubble(): void {\n const row = document.createElement('div')\n row.className = 'mtb-chat-ai-row'\n row.innerHTML = `<div class=\"mtb-chat-avatar\">${BRAND_ICON}</div><div class=\"mtb-chat-bubble mtb-chat-bubble-ai\"></div>`\n this.messagesEl.appendChild(row)\n this.streamingBubbleEl = row.querySelector('.mtb-chat-bubble-ai')!\n }\n\n private appendStreamDelta(text: string): void {\n if (!this.streamingBubbleEl) return\n this.streamingBubbleEl.textContent = (this.streamingBubbleEl.textContent ?? '') + text\n this.messagesEl.scrollTop = this.messagesEl.scrollHeight\n }\n\n private endStreamingBubble(): void {\n this.streamingBubbleEl = null\n }\n\n private appendAiBubble(text: string): void {\n const row = document.createElement('div')\n row.className = 'mtb-chat-ai-row'\n row.innerHTML = `<div class=\"mtb-chat-avatar\">${BRAND_ICON}</div><div class=\"mtb-chat-bubble mtb-chat-bubble-ai\"></div>`\n row.querySelector('.mtb-chat-bubble-ai')!.textContent = text\n this.messagesEl.appendChild(row)\n this.messagesEl.scrollTop = this.messagesEl.scrollHeight\n }\n\n private renderSuggestions(suggestions: string[] | undefined): void {\n this.clearSuggestions()\n if (!suggestions?.length) return\n\n const row = document.createElement('div')\n row.className = 'mtb-chat-suggestions'\n for (const suggestion of suggestions) {\n const pill = document.createElement('button')\n pill.className = 'mtb-chat-sg'\n pill.type = 'button'\n pill.textContent = suggestion\n pill.addEventListener('click', () => {\n this.inputEl.value = suggestion\n this.sendBtn.disabled = false\n this.inputEl.focus({ preventScroll: true })\n })\n row.appendChild(pill)\n }\n this.messagesEl.appendChild(row)\n this.messagesEl.scrollTop = this.messagesEl.scrollHeight\n }\n\n private clearSuggestions(): void {\n this.messagesEl.querySelector('.mtb-chat-suggestions')?.remove()\n }\n\n private showThinking(): void {\n if (this.messagesEl.querySelector('.mtb-chat-thinking')) return\n const row = document.createElement('div')\n row.className = 'mtb-chat-ai-row mtb-chat-thinking'\n row.innerHTML = `<div class=\"mtb-chat-avatar\">${BRAND_ICON}</div><div class=\"mtb-chat-dots\"><span></span><span></span><span></span></div>`\n this.messagesEl.appendChild(row)\n this.messagesEl.scrollTop = this.messagesEl.scrollHeight\n }\n\n private removeThinking(): void {\n this.messagesEl.querySelector('.mtb-chat-thinking')?.remove()\n }\n\n private showRetryableError(text: string, onRetry: () => void): void {\n this.clearError()\n const row = document.createElement('div')\n row.className = 'mtb-chat-error-row'\n const msg = document.createElement('span')\n msg.className = 'mtb-chat-error-text'\n msg.textContent = text\n const retry = document.createElement('button')\n retry.className = 'mtb-chat-error-retry'\n retry.type = 'button'\n retry.textContent = this.i18n.clarify.retry\n retry.addEventListener('click', onRetry)\n row.appendChild(msg)\n row.appendChild(retry)\n this.messagesEl.appendChild(row)\n this.messagesEl.scrollTop = this.messagesEl.scrollHeight\n }\n\n private clearError(): void {\n this.messagesEl.querySelectorAll('.mtb-chat-error-row').forEach(el => el.remove())\n }\n\n private showSkip(): void {\n if (this.actionsEl.querySelector('.mtb-chat-skip')) return\n const skip = document.createElement('button')\n skip.className = 'mtb-chat-skip'\n skip.type = 'button'\n skip.title = this.i18n.clarify.skip\n skip.textContent = this.i18n.clarify.skip_short\n skip.addEventListener('click', () => this.skipAndSend())\n this.actionsEl.insertBefore(skip, this.sendBtn)\n }\n\n private skipAndSend(): void {\n this.hideSkip()\n this.state = 'done'\n this.setComposerDisabled(true)\n this.renderSuccess()\n this.finalizeInBackground()\n }\n\n private hideSkip(): void {\n this.actionsEl?.querySelector('.mtb-chat-skip')?.remove()\n }\n\n private hideCapture(): void {\n this.actionsEl.querySelector('.mtb-chat-screenshot')?.remove()\n this.actionsEl.querySelector('.mtb-chat-attachment')?.remove()\n }\n\n private setComposerDisabled(disabled: boolean): void {\n this.inputEl.disabled = disabled\n this.sendBtn.disabled = disabled\n this.composerEl.classList.toggle('mtb-chat-composer-disabled', disabled)\n }\n\n private positionCard(x: number, y: number, targetRect: TargetRect | undefined, tabPosition: 'left' | 'right'): void {\n const frame = measureViewportFrame(tabPosition)\n const { vw, vh, minLeft, maxRight } = frame\n const margin = VIEWPORT_MARGIN\n const gap = 8\n const cardW = panelWidth(CARD_WIDTH, frame)\n const measured = this.el.offsetHeight || 280\n const cardH = measured + 80\n\n let left: number\n let top: number\n\n const anchorIsInformative =\n targetRect && targetRect.width < vw * 0.6 && targetRect.height < vh * 0.6\n\n if (targetRect && anchorIsInformative) {\n left = targetRect.left\n top = targetRect.bottom + gap\n if (top + cardH > vh - margin) top = targetRect.top - cardH - gap\n if (top < margin) top = Math.min(targetRect.bottom + gap, vh - cardH - margin)\n } else {\n left = x + 12\n top = y + 12\n }\n\n if (left + cardW > maxRight) left = maxRight - cardW\n if (left < minLeft) left = minLeft\n if (top < margin) top = margin\n\n if (top + cardH > vh - margin) top = vh - cardH - margin\n if (top < margin) top = margin\n\n this.el.style.left = `${left}px`\n this.el.style.top = `${top}px`\n this.el.style.maxHeight = `${vh - top - margin}px`\n }\n\n submit(text: string): void {\n void this.sendMessage(text)\n }\n\n /** Which submission this card holds — null while the reporter is still typing. */\n sessionId(): string | null {\n return this.session?.id ?? null\n }\n\n destroy(): void {\n this.clarifyAbort?.abort()\n if (this.autoCloseTimer) clearTimeout(this.autoCloseTimer)\n window.removeEventListener('keydown', this.handleEscape)\n this.sheet.release()\n this.focusTrap.release()\n this.el.remove()\n this.focusTrap.restoreFocus()\n }\n}\n","import type { ShadowContainer } from '../widget/shadow'\nimport type { I18nMessages } from '../i18n'\nimport { trapFocus, type FocusTrap } from './focus-trap'\n\nconst AUTO_CLOSE_MS = 5000\n\nconst CHECK_ICON = '<svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path d=\"M8.1 14.6L3.8 10.3l1.5-1.5 2.8 2.8 6.6-6.6 1.5 1.5z\"/></svg>'\n\nexport interface ReceiptCardOptions {\n skipFollowup?: boolean\n onViewFeedback?: () => void\n}\n\n/**\n * The standalone receipt for a submission whose chat card is already gone —\n * today that is one path: a background finalize failed, its failure card was\n * dismissed by the retry, and the retry then succeeded. Renders with the chat\n * card's own receipt classes so the two ways of saying \"sent\" stay one design.\n */\nexport class ReceiptCard {\n private el: HTMLDivElement\n private focusTrap: FocusTrap\n private autoCloseTimer: ReturnType<typeof setTimeout>\n\n constructor(shadow: ShadowContainer, messages: I18nMessages, onClose: () => void, options?: ReceiptCardOptions) {\n const m = messages.success\n const title = options?.skipFollowup ? m.title_no_ai : m.title\n\n this.el = shadow.el<HTMLDivElement>('div', 'mtb-chat mtb-chat-receipt')\n this.el.setAttribute('role', 'dialog')\n this.el.setAttribute('aria-modal', 'true')\n this.el.setAttribute('aria-label', title)\n\n const view = document.createElement('div')\n view.className = 'mtb-chat-success mtb-chat-success-simple'\n\n const icon = document.createElement('div')\n icon.className = 'mtb-chat-success-icon'\n icon.innerHTML = CHECK_ICON\n view.appendChild(icon)\n\n const titleEl = document.createElement('div')\n titleEl.className = 'mtb-chat-success-title'\n titleEl.textContent = title\n view.appendChild(titleEl)\n\n if (options?.onViewFeedback) {\n const link = document.createElement('a')\n link.className = 'mtb-chat-success-link'\n link.href = '#'\n link.textContent = m.track_feedback\n link.addEventListener('click', (e: MouseEvent) => {\n e.preventDefault()\n options.onViewFeedback!()\n })\n view.appendChild(link)\n }\n\n this.el.appendChild(view)\n shadow.append(this.el)\n this.focusTrap = trapFocus(this.el)\n this.autoCloseTimer = setTimeout(onClose, AUTO_CLOSE_MS)\n }\n\n destroy(): void {\n clearTimeout(this.autoCloseTimer)\n this.focusTrap.release()\n this.el.remove()\n this.focusTrap.restoreFocus()\n }\n}\n","export const FILTERED_VALUE = '[Filtered]'\n\nconst PRIVATE_KEY_PATTERN = /-----BEGIN ((?:[A-Z0-9]+ )*PRIVATE KEY)-----[\\s\\S]*?-----END \\1-----/g\nconst JWT_PATTERN = /\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\b/g\nconst API_KEY_PATTERNS = [\n /\\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\\b/g,\n /\\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{20,}\\b/g,\n /\\bgh[pousr]_[A-Za-z0-9]{30,}\\b/g,\n /\\bAKIA[0-9A-Z]{16}\\b/g,\n /\\bxox[baprs]-[A-Za-z0-9-]{20,}\\b/g,\n]\nconst CARD_CANDIDATE_PATTERN = /\\b(?:\\d[ -]?){12,18}\\d\\b/g\nconst HTTP_URL_PATTERN = /\\bhttps?:\\/\\/[^\\s<>\"']+/gi\nconst FIELD_SEMANTIC_KEYS = new Set(['type', 'autocomplete', 'name', 'id', 'aria_label', 'placeholder'])\nconst VALUE_ATTRIBUTE_KEYS = new Set(['value', 'content'])\nconst SENSITIVE_INPUT_TYPES = new Set(['password', 'hidden', 'file'])\nconst SENSITIVE_AUTOCOMPLETE = new Set([\n 'current_password',\n 'new_password',\n 'one_time_code',\n 'cc_number',\n 'cc_csc',\n 'cc_exp',\n 'cc_exp_month',\n 'cc_exp_year',\n])\nconst SENSITIVE_KEY_PATTERN = /(?:^|_)(?:password|passwd|passcode|card_(?:number|no)(?:_[0-9]+)?|credit_card|cc_(?:number|num|csc|cvv|cvc|exp|expiry)|cvv|cvc|csc|security_code|one_time_(?:code|password)|otp(?:_code)?|verification_code|auth(?:entication)?_token|access_token|refresh_token|id_token|token|api_key|client_secret|secret|authorization|cookie|set_cookie|credential|private_key|hidden_value|file_name|filename)(?:$|_(?:value|field|input|confirmation|confirm|header|data|pem)$)/\n\nexport function filterSensitiveText(value: string): string {\n let filtered = value\n .replace(PRIVATE_KEY_PATTERN, FILTERED_VALUE)\n .replace(JWT_PATTERN, FILTERED_VALUE)\n\n for (const pattern of API_KEY_PATTERNS) {\n filtered = filtered.replace(pattern, FILTERED_VALUE)\n }\n\n filtered = filtered.replace(CARD_CANDIDATE_PATTERN, candidate => (\n passesLuhn(candidate) ? FILTERED_VALUE : candidate\n ))\n\n return filtered.replace(HTTP_URL_PATTERN, candidate => sanitizeUrl(candidate) || FILTERED_VALUE)\n}\n\nexport function sanitizeUrl(value: string): string {\n try {\n const url = new URL(value)\n return `${url.origin}${url.pathname}`\n } catch {\n return ''\n }\n}\n\nexport function filterSensitiveValue<T>(value: T, key?: string): T {\n if (key && isSensitiveKey(key) && value != null) return FILTERED_VALUE as T\n if (typeof value === 'string') return filterSensitiveText(value) as T\n if (Array.isArray(value)) return value.map(item => filterSensitiveValue(item)) as T\n if (value && typeof value === 'object') {\n const sensitiveField = isSensitiveFieldAttributes(value as Record<string, unknown>)\n const filtered = Object.fromEntries(\n Object.entries(value).map(([nestedKey, item]) => {\n const filteredItem = sensitiveField && VALUE_ATTRIBUTE_KEYS.has(normalizeIdentifier(nestedKey)) && item != null\n ? FILTERED_VALUE\n : filterSensitiveValue(item, nestedKey)\n return [filterSensitiveText(nestedKey), filteredItem]\n }),\n )\n return filtered as T\n }\n return value\n}\n\nexport function isSensitiveKey(value: string): boolean {\n return SENSITIVE_KEY_PATTERN.test(normalizeIdentifier(value))\n}\n\nexport function isSensitiveFieldAttributes(attributes: Record<string, unknown>): boolean {\n return Object.entries(attributes).some(([key, value]) => {\n if (typeof value !== 'string') return false\n\n const normalizedKey = normalizeIdentifier(key)\n if (!FIELD_SEMANTIC_KEYS.has(normalizedKey)) return false\n if (normalizedKey === 'type') return SENSITIVE_INPUT_TYPES.has(normalizeIdentifier(value))\n if (normalizedKey === 'autocomplete') {\n return value.split(/\\s+/).some(token => SENSITIVE_AUTOCOMPLETE.has(normalizeIdentifier(token)))\n }\n return isSensitiveKey(value)\n })\n}\n\nfunction normalizeIdentifier(value: string): string {\n return value\n .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '_')\n .replace(/^_+|_+$/g, '')\n}\n\nfunction passesLuhn(candidate: string): boolean {\n const digits = candidate.replace(/\\D/g, '')\n if (digits.length < 13 || digits.length > 19) return false\n\n let sum = 0\n let doubleDigit = false\n for (let index = digits.length - 1; index >= 0; index -= 1) {\n let digit = Number(digits[index])\n if (doubleDigit) {\n digit *= 2\n if (digit > 9) digit -= 9\n }\n sum += digit\n doubleDigit = !doubleDigit\n }\n return sum % 10 === 0\n}\n","import { FILTERED_VALUE, filterSensitiveText, isSensitiveFieldAttributes } from './sanitize'\n\nexport function isSensitiveField(element: Element): boolean {\n return isSensitiveFieldAttributes({\n type: element.getAttribute('type'),\n autocomplete: element.getAttribute('autocomplete'),\n name: element.getAttribute('name'),\n id: element.id,\n 'aria-label': element.getAttribute('aria-label'),\n placeholder: element.getAttribute('placeholder'),\n })\n}\n\nexport function filterInputValue(value: string, element: Element): string {\n if (!value) return value\n if (isSensitiveField(element)) return FILTERED_VALUE\n return filterSensitiveText(value)\n}\n","import { filterSensitiveText } from './sanitize'\n\nconst MAX_GROUPED_TEXT_LENGTH = 512\ntype SensitiveTextState = 'none' | 'sensitive' | 'contains-sensitive-child' | 'too-large'\nexport type SensitiveTextContext = WeakMap<Element, SensitiveTextState>\n\nexport function createSensitiveTextContext(): SensitiveTextContext {\n return new WeakMap()\n}\n\nexport function hasSensitiveTextContext(node: Node, context: SensitiveTextContext): boolean {\n let element = node.parentElement\n while (element && element !== document.body && element !== document.documentElement) {\n const state = sensitiveTextState(element, context)\n if (state === 'sensitive') return true\n if (state === 'contains-sensitive-child' || state === 'too-large') return false\n element = element.parentElement\n }\n return false\n}\n\nfunction sensitiveTextState(element: Element, context: SensitiveTextContext): SensitiveTextState {\n const cached = context.get(element)\n if (cached) return cached\n\n const text = element.textContent ?? ''\n let state: SensitiveTextState = 'none'\n if (text.length > MAX_GROUPED_TEXT_LENGTH) {\n state = 'too-large'\n } else if (filterSensitiveText(text) !== text) {\n const childContainsSensitiveText = Array.from(element.children).some((child) => {\n const childState = sensitiveTextState(child, context)\n return childState === 'sensitive' || childState === 'contains-sensitive-child'\n })\n state = childContainsSensitiveText ? 'contains-sensitive-child' : 'sensitive'\n }\n context.set(element, state)\n return state\n}\n","export function loadImage(dataUrl: string): Promise<HTMLImageElement | null> {\n return new Promise(resolve => {\n const image = new Image()\n image.onload = () => resolve(image)\n image.onerror = () => resolve(null)\n image.src = dataUrl\n })\n}\n","// The draw layer only ever emits M/L polyline commands (see buildPathD in\n// annotate/draw.ts); anything else in the string is ignored.\nexport function parsePathPolylines(drawPath: string): Array<Array<[number, number]>> {\n const polylines: Array<Array<[number, number]>> = []\n let current: Array<[number, number]> | null = null\n const tokens = drawPath.match(/[ML]\\s*[-\\d.]+\\s+[-\\d.]+/g) ?? []\n for (const token of tokens) {\n const [x, y] = token.slice(1).trim().split(/\\s+/).map(Number)\n if (Number.isNaN(x) || Number.isNaN(y)) continue\n if (token[0] === 'M') {\n current = [[x, y]]\n polylines.push(current)\n } else if (current) {\n current.push([x, y])\n }\n }\n return polylines\n}\n\n// Rebuilt from the parsed polylines so this module stays the single reader of\n// the path grammar. Tokens the parser ignores are dropped, which is safe\n// because every consumer reads the translated path through that same parser.\nexport function translatePathD(drawPath: string, dx: number, dy: number): string {\n return parsePathPolylines(drawPath)\n .map(points =>\n points.map(([x, y], index) => `${index === 0 ? 'M' : 'L'} ${x + dx} ${y + dy}`).join(' '),\n )\n .join(' ')\n}\n","import type { Annotation, WidgetBrandColors } from '../types'\nimport { BLOCK_CLASS, MASK_TEXT_CLASS } from '../privacy/dom'\nimport { WIDGET_HOST_ID } from '../context/dom-utils'\nimport { filterInputValue, isSensitiveField } from '../privacy/fields'\nimport { filterSensitiveText } from '../privacy/sanitize'\nimport { createSensitiveTextContext, hasSensitiveTextContext } from '../privacy/text-context'\nimport { BRAND_COLOR } from '../styles'\nimport { anchorScreenshotOffset, captureTransform, nestedScrollOffset, parentElement, rectToCanvas, toCanvasX, toCanvasY, viewportCaptureRect } from './geometry'\nimport type { CaptureTransform, Rect, ScrollOffset } from './geometry'\nimport { loadImage } from './image'\nimport { parsePathPolylines, translatePathD } from './svg-path'\n\n// Capture at CSS pixels (pixelRatio 1) as JPEG to keep the upload small: on\n// retina screens the default devicePixelRatio would quadruple the pixel count,\n// and the model downscales past ~1568px anyway. The capture covers only the\n// viewport; capturePixelRatio stays as a guard for a viewport that would ever\n// exceed the browser's canvas ceiling.\n//\n// Annotations are baked onto the captured bitmap with canvas 2D afterwards —\n// never mounted into the live page — so the capture leaves the page visually\n// untouched no matter how long the DOM snapshot takes.\nexport interface BaseScreenshot {\n dataUrl: string\n metrics: Rect\n redactions: Rect[]\n /**\n * Where the page was scrolled when the bitmap was measured. The bake happens\n * later — the reporter is still typing — so it cannot read this itself, and\n * annotations that carry their own anchor need it to come back to this\n * capture's frame.\n */\n pageScroll: ScrollOffset\n}\n\n// html-to-image is the largest dependency in the bundle, and most sessions\n// never open the widget, let alone capture a screenshot. Loading it lazily\n// keeps it out of the first-injection payload for ESM/CJS consumers (the IIFE\n// build inlines the import back into its single file). warmFontEmbedCss is the\n// first caller on every capture path, so the popup-open warm-up that prefetches\n// the font CSS also prefetches this module and submit latency does not regress.\ntype HtmlToImageModule = typeof import('html-to-image')\n\nlet htmlToImageLoaded: Promise<HtmlToImageModule> | null = null\n\nfunction loadHtmlToImage(): Promise<HtmlToImageModule> {\n if (htmlToImageLoaded) return htmlToImageLoaded\n // A rejection must not stay cached: one flaky-network failure would otherwise\n // make every later capture fail instantly until the page reloads. Same\n // reset-on-failure as record/session.ts loadRrweb.\n htmlToImageLoaded = import('html-to-image').catch((err) => {\n htmlToImageLoaded = null\n throw err\n })\n return htmlToImageLoaded\n}\n\n// Embedding webfonts is the expensive half of a capture: html-to-image walks\n// every stylesheet and fetches each referenced font file — a hundred-odd\n// requests on a Google-Fonts page — on every capture. The font set does not\n// change while the page lives, so compute the embed CSS once and reuse it.\nlet fontEmbedCss: string | undefined\nlet fontEmbedCssRequest: Promise<string | undefined> | null = null\n\nexport function warmFontEmbedCss(): Promise<string | undefined> {\n if (fontEmbedCss !== undefined) return Promise.resolve(fontEmbedCss)\n // On failure fall back to undefined so toJpeg embeds fonts itself, and allow\n // a later capture to try warming again.\n fontEmbedCssRequest ??= loadHtmlToImage()\n .then(({ getFontEmbedCSS }) => getFontEmbedCSS(document.body))\n .then((css) => {\n fontEmbedCss = css\n return css\n })\n .catch(() => {\n fontEmbedCssRequest = null\n return undefined\n })\n return fontEmbedCssRequest\n}\n\n// The rasterized page without annotations. Annotations are baked afterwards\n// from data, so this half can run early — during a pause in the reporter's\n// typing — and the submit path only pays for the cheap bake.\n//\n// Everything below this margin past the viewport is culled from the clone and\n// never rasterized. A full-page capture rasterized a tall page — sometimes 10x\n// the pixels and DOM nodes — only for the bake to crop it back to roughly a\n// viewport, and that wasted work is what froze typing on heavy pages.\n//\n// Only content BELOW the viewport can go: removing in-flow content above it\n// collapses the clone's layout upward and breaks the scroll alignment the\n// transform relies on (verified: culling above + translate rendered an\n// entirely blank capture).\nconst CULL_MARGIN_PX = 200\n// The browser cannot read cross-origin images whose server omits CORS headers.\n// Replace only that resource in html-to-image's clone so one inaccessible image\n// cannot discard the rest of the privacy-filtered screenshot.\nconst TRANSPARENT_IMAGE_PLACEHOLDER = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'\n\nexport type ScreenshotResourceUrlResolver = (\n resourceUrl: string,\n contentType?: string,\n) => string | undefined | Promise<string | undefined>\n\nexport async function captureBaseScreenshot(\n resourceUrlResolver?: ScreenshotResourceUrlResolver,\n signal?: AbortSignal,\n): Promise<BaseScreenshot | null> {\n try {\n if (signal?.aborted) return null\n // The module load rides alongside the warm-up (which itself needs the\n // module); a load failure rejects here and fails the capture closed to\n // null, same as any other capture error.\n const [{ toJpeg }, fontEmbedCSS] = await Promise.all([loadHtmlToImage(), warmFontEmbedCss()])\n // Measure only after the font warm-up settles: a cold warm-up is long\n // enough (a hundred-odd requests) for late-arriving fonts to reflow the\n // page, and a cover measured before that shift lands on the wrong pixels\n // with no fail-closed signal.\n const bodyRect = document.body.getBoundingClientRect()\n const metrics = viewportCaptureRect()\n const pageScroll = { x: window.scrollX, y: window.scrollY }\n const redactions = collectPrivacyRedactions(document.body)\n const dataUrl = await toJpeg(document.body, {\n quality: 0.85,\n imagePlaceholder: TRANSPARENT_IMAGE_PLACEHOLDER,\n resourceUrlResolver,\n fetchRequestInit: signal ? { signal } : undefined,\n width: metrics.width,\n height: metrics.height,\n pixelRatio: capturePixelRatio(metrics),\n backgroundColor: '#ffffff',\n skipFonts: false,\n skipAutoScale: false,\n fontEmbedCSS,\n style: {\n // The clone lays out from the document top; shifting it by the body's\n // current viewport offset puts the scrolled-to region at the canvas\n // origin, so live viewport measurements map onto the bitmap 1:1.\n transform: `translate(${bodyRect.left}px, ${bodyRect.top}px)`,\n transformOrigin: 'top left',\n },\n filter: (node) => {\n if (node instanceof HTMLElement && node.id === WIDGET_HOST_ID) return false\n if (!(node instanceof Element)) return true\n const rect = node.getBoundingClientRect()\n // Zero-sized nodes carry no pixels but often carry styles or heads of\n // rendered subtrees (style/script/positioned wrappers) — keep them.\n if (rect.width === 0 && rect.height === 0) return true\n // The clone walk is asynchronous, so the page can scroll while it\n // runs — and a live rect.top is measured against wherever the page is\n // by then. Re-expressing it against the scroll this capture was framed\n // at keeps the cull decision stable however late the walk reads it.\n const topInCapture = rect.top + window.scrollY - pageScroll.y\n return topInCapture <= metrics.height + CULL_MARGIN_PX\n },\n })\n if (signal?.aborted) return null\n return { dataUrl, metrics, redactions, pageScroll }\n } catch {\n return null\n }\n}\n\nexport async function bakeAnnotatedScreenshot(\n base: BaseScreenshot,\n annotations: Annotation[] = [],\n brandColors?: WidgetBrandColors,\n): Promise<Blob | null> {\n try {\n const screenshotAnnotations = annotations.map(\n annotation => translateAnnotationForScreenshot(annotation, base.pageScroll),\n )\n return await bakeScreenshot(base.dataUrl, screenshotAnnotations, base.metrics, base.redactions, brandColors)\n } catch {\n return null\n }\n}\n\nexport async function captureScreenshot(\n annotations: Annotation[] = [],\n brandColors?: WidgetBrandColors,\n): Promise<Blob | null> {\n const base = await captureBaseScreenshot()\n if (!base) return null\n return bakeAnnotatedScreenshot(base, annotations, brandColors)\n}\n\n// html-to-image clones an element's children at their unscrolled positions.\n// Annotations, however, are recorded in viewport coordinates. Translate the\n// screenshot-only copy so it remains attached to its target in that clone;\n// the original annotation is still sent to the API unchanged.\n//\n// The capture is one viewport — the one on screen at capture time, which the\n// prime-at-annotation flow makes the viewport the first annotation was made in.\n// An annotation whose anchor puts it outside that rect simply is not in the\n// shot: its strokes land off-canvas and the canvas clips them silently. That is\n// the honest picture (the marked content was not captured), and the wire\n// annotation data still carries the mark for the board.\nfunction translateAnnotationForScreenshot(annotation: Annotation, pageScroll: ScrollOffset): Annotation {\n const offset = screenshotOffset(annotation, pageScroll)\n if (offset.x === 0 && offset.y === 0) return annotation\n\n return {\n ...annotation,\n x: annotation.x + offset.x,\n y: annotation.y + offset.y,\n targetRect: annotation.targetRect && {\n ...annotation.targetRect,\n top: annotation.targetRect.top + offset.y,\n left: annotation.targetRect.left + offset.x,\n bottom: annotation.targetRect.bottom + offset.y,\n },\n drawPath: annotation.drawPath && translatePathD(annotation.drawPath, offset.x, offset.y),\n }\n}\n\n/**\n * How far to move an annotation to land where the capture rendered its target.\n *\n * An annotation that recorded its own anchor already knows where it belongs in\n * clone space, so all that is left is coming back to the frame this capture was\n * measured in — no live DOM read, and therefore nothing that a scroll between\n * the annotation and the capture can spoil.\n *\n * Without that anchor (an older client, or replayed data) the only thing left to\n * do is measure the target now and hope nothing moved, which is what every\n * annotation used to do.\n */\nfunction screenshotOffset(annotation: Annotation, pageScroll: ScrollOffset): ScrollOffset {\n if (annotation.captureOffsetX !== undefined && annotation.captureOffsetY !== undefined) {\n return anchorScreenshotOffset(\n { x: annotation.captureOffsetX, y: annotation.captureOffsetY },\n pageScroll,\n )\n }\n\n return nestedScrollOffset(findAnnotationTarget(annotation))\n}\n\nfunction findAnnotationTarget(annotation: Annotation): Element | null {\n if (annotation.targetSelector) {\n try {\n const target = document.querySelector(annotation.targetSelector)\n if (target) return target\n } catch {\n // Selectors are captured from the page, so a later DOM change can make\n // one invalid. Fall back to the element beneath the annotation instead.\n }\n }\n\n return document.elementsFromPoint?.(annotation.x, annotation.y)[0] ?? null\n}\n\nasync function bakeScreenshot(\n dataUrl: string,\n annotations: Annotation[],\n captureMetrics: Rect,\n redactions: Rect[],\n brandColors?: WidgetBrandColors,\n): Promise<Blob | null> {\n const canvas = document.createElement('canvas')\n const ctx = canvas.getContext('2d')\n if (!ctx) return null\n\n const image = await loadImage(dataUrl)\n if (!image) return null\n\n canvas.width = image.naturalWidth || window.innerWidth\n canvas.height = image.naturalHeight || window.innerHeight\n ctx.drawImage(image, 0, 0)\n const transform = captureTransform(captureMetrics, canvas.width, canvas.height)\n\n ctx.fillStyle = '#e5e7eb'\n for (const rect of redactions) {\n const cover = rectToCanvas(transform, rect)\n ctx.fillRect(cover.left, cover.top, cover.width, cover.height)\n }\n\n for (const annotation of annotations) {\n if (annotation.type === 'draw' && annotation.drawPath) {\n drawStroke(ctx, annotation.drawPath, transform, brandColors?.primary)\n } else if (annotation.type === 'pin') {\n drawPin(\n ctx,\n toCanvasX(transform, annotation.x),\n toCanvasY(transform, annotation.y),\n brandColors,\n )\n }\n }\n\n // No crop step: the capture rect is the viewport, which is exactly the\n // deliverable. Cropping this bitmap could only ever remove captured pixels —\n // and cropping content above the visible band was the bug class that shifted\n // the remaining content up and blanked the shot.\n return canvasToJpegBlob(canvas)\n}\n\nfunction collectPrivacyRedactions(root: HTMLElement): Rect[] {\n const redactions: Rect[] = []\n for (const element of elementsIncludingRoot(root, `.${BLOCK_CLASS}`)) {\n addRedaction(redactions, element.getBoundingClientRect(), cloneScrollOffset(element))\n }\n\n for (const element of root.querySelectorAll('input, textarea, select')) {\n if (isWidgetOrBlocked(element)) continue\n const value = element instanceof HTMLInputElement\n || element instanceof HTMLTextAreaElement\n || element instanceof HTMLSelectElement\n ? element.value\n : ''\n const isMasked = element.closest(`.${MASK_TEXT_CLASS}`) !== null\n const hasSensitiveValue = isSensitiveField(element) || filterInputValue(value, element) !== value\n if (!isMasked && !hasSensitiveValue) continue\n addRedaction(redactions, element.getBoundingClientRect(), cloneScrollOffset(element), REDACTION_PADDING_PX)\n }\n\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT)\n const textContext = createSensitiveTextContext()\n let node = walker.nextNode()\n while (node) {\n const parent = node.parentElement\n const text = node.textContent ?? ''\n const isMasked = parent?.closest(`.${MASK_TEXT_CLASS}`) !== null\n const hasSensitiveText = filterSensitiveText(text) !== text || hasSensitiveTextContext(node, textContext)\n if (parent && !isWidgetOrBlocked(parent) && (isMasked || hasSensitiveText)) {\n // Text is the parent's content, so the parent's own scrollTop moves it\n // too — start the offset walk at the parent itself, not its ancestors.\n const offset = nestedScrollOffset(parent)\n const range = document.createRange()\n range.selectNodeContents(node)\n for (const rect of Array.from(range.getClientRects())) {\n addRedaction(redactions, rect, offset, REDACTION_PADDING_PX)\n }\n }\n node = walker.nextNode()\n }\n\n return redactions\n}\n\n// html-to-image clones a scroller's children back at their unscrolled\n// positions, so a cover measured from the live viewport rect of an element\n// inside a scrolled container would land away from the secret in the export\n// and leave it visible — the same drift the annotation path compensates in\n// translateAnnotationForScreenshot. A border box moves with ancestor scroll\n// only (an element's own scrollTop shifts its content, not its box), so the\n// walk starts at the parent; page scroll stays out because nestedScrollOffset\n// stops at body, whose offset captureMetrics already carries.\nfunction cloneScrollOffset(element: Element): { x: number; y: number } {\n return nestedScrollOffset(parentElement(element))\n}\n\nfunction elementsIncludingRoot(root: HTMLElement, selector: string): Element[] {\n const descendants = Array.from(root.querySelectorAll(selector))\n return root.matches(selector) ? [root, ...descendants] : descendants\n}\n\nfunction isWidgetOrBlocked(element: Element): boolean {\n return element.closest(`#${WIDGET_HOST_ID}`) !== null || element.closest(`.${BLOCK_CLASS}`) !== null\n}\n\nfunction addRedaction(\n redactions: Rect[],\n rect: DOMRect,\n offset: { x: number; y: number },\n padding = 0,\n): void {\n if (![rect.left, rect.top, rect.width, rect.height].every(Number.isFinite)) {\n throw new Error('Invalid privacy redaction geometry')\n }\n if (rect.width <= 0 || rect.height <= 0) return\n redactions.push({\n left: rect.left + offset.x - padding,\n top: rect.top + offset.y - padding,\n width: rect.width + padding * 2,\n height: rect.height + padding * 2,\n })\n}\n\nfunction drawStroke(\n ctx: CanvasRenderingContext2D,\n drawPath: string,\n transform: CaptureTransform,\n color = BRAND_COLOR,\n): void {\n ctx.save()\n ctx.lineCap = 'round'\n ctx.lineJoin = 'round'\n for (const points of parsePathPolylines(drawPath)) {\n if (points.length === 0) continue\n ctx.beginPath()\n ctx.moveTo(\n toCanvasX(transform, points[0][0]),\n toCanvasY(transform, points[0][1]),\n )\n for (let i = 1; i < points.length; i++) {\n ctx.lineTo(\n toCanvasX(transform, points[i][0]),\n toCanvasY(transform, points[i][1]),\n )\n }\n ctx.shadowColor = 'transparent'\n ctx.strokeStyle = 'rgba(15, 23, 42, 0.72)'\n ctx.lineWidth = 7\n ctx.stroke()\n ctx.strokeStyle = '#ffffff'\n ctx.lineWidth = 5.5\n ctx.stroke()\n ctx.strokeStyle = color\n ctx.lineWidth = 3.5\n ctx.stroke()\n }\n ctx.restore()\n}\n\n// Safari refuses to back a canvas larger than roughly 16.7M pixels and hands\n// back a blank one instead, and the caller's catch turns that into a silent\n// screenshot-less submission. A viewport should never get near the ceiling,\n// but the guard costs nothing and keeps an absurd embedder viewport rendered\n// smaller instead of not at all; bakeScreenshot derives its scale from the\n// returned bitmap, so annotations follow along.\nconst MAX_CAPTURE_PIXELS = 16_000_000\nconst REDACTION_PADDING_PX = 2\n\nfunction capturePixelRatio(metrics: Rect): number {\n const total = metrics.width * metrics.height\n if (!Number.isFinite(total) || total <= MAX_CAPTURE_PIXELS) return 1\n return Math.sqrt(MAX_CAPTURE_PIXELS / total)\n}\n\nfunction drawPin(\n ctx: CanvasRenderingContext2D,\n x: number,\n y: number,\n brandColors?: WidgetBrandColors,\n): void {\n const primary = brandColors?.primary ?? BRAND_COLOR\n ctx.save()\n ctx.shadowColor = 'rgba(15, 23, 42, 0.68)'\n ctx.shadowOffsetY = 2\n ctx.shadowBlur = 8\n ctx.fillStyle = primary\n ctx.beginPath()\n ctx.arc(x, y, 11, 0, Math.PI * 2)\n ctx.fill()\n ctx.shadowColor = 'transparent'\n ctx.lineWidth = 2\n ctx.strokeStyle = '#ffffff'\n ctx.stroke()\n ctx.restore()\n}\n\nfunction canvasToJpegBlob(canvas: HTMLCanvasElement): Promise<Blob | null> {\n return new Promise(resolve => {\n try {\n canvas.toBlob(blob => resolve(blob), 'image/jpeg', 0.85)\n } catch {\n resolve(null)\n }\n })\n}\n","export interface ErrorSourceEvent {\n name?: string\n message: string\n source?: string\n line?: number\n col?: number\n error?: unknown\n}\n\ntype ErrorListener = (event: ErrorSourceEvent) => void\n\nclass ErrorSource {\n private listeners = new Set<ErrorListener>()\n private originalOnError: OnErrorEventHandler | null = null\n private installedOnError: OnErrorEventHandler | null = null\n private rejectionHandler: ((e: PromiseRejectionEvent) => void) | null = null\n private hooked = false\n\n subscribe(listener: ErrorListener): () => void {\n this.listeners.add(listener)\n this.hook()\n return () => {\n this.listeners.delete(listener)\n if (this.listeners.size === 0) this.unhook()\n }\n }\n\n private hook(): void {\n if (this.hooked) return\n this.hooked = true\n\n this.originalOnError = window.onerror\n this.installedOnError = (msg, source, line, col, error) => {\n this.emit({ name: errorName(error), message: String(msg), source, line, col, error })\n if (this.originalOnError) {\n return this.originalOnError.call(window, msg, source, line, col, error)\n }\n return false\n }\n window.onerror = this.installedOnError\n\n this.rejectionHandler = (e: PromiseRejectionEvent) => {\n const reason = e.reason instanceof Error ? e.reason.message : String(e.reason)\n const name = e.reason instanceof Error ? e.reason.name : 'UnhandledRejection'\n this.emit({ name, message: `Unhandled rejection: ${reason}` })\n }\n window.addEventListener('unhandledrejection', this.rejectionHandler)\n }\n\n private unhook(): void {\n if (!this.hooked) return\n this.hooked = false\n // Only restore if nothing replaced our handler meanwhile — restoring\n // the stale snapshot would clobber a handler the page installed later.\n if (window.onerror === this.installedOnError) {\n window.onerror = this.originalOnError\n }\n this.installedOnError = null\n this.originalOnError = null\n if (this.rejectionHandler) {\n window.removeEventListener('unhandledrejection', this.rejectionHandler)\n this.rejectionHandler = null\n }\n }\n\n private emit(event: ErrorSourceEvent): void {\n for (const listener of this.listeners) listener(event)\n }\n}\n\nexport const errorSource = new ErrorSource()\n\nfunction errorName(error: unknown): string | undefined {\n if (!error || typeof error !== 'object' || !('name' in error)) return undefined\n const name = String(error.name)\n return name || undefined\n}\n","import type { ErrorSourceEvent } from './error-source'\n\nexport function summarizeError(event: ErrorSourceEvent): string {\n const name = validErrorName(event.name) || inferErrorName(event.message)\n const location = errorLocation(event)\n return location ? `${name} (${location})` : name\n}\n\nfunction validErrorName(name: string | undefined): string {\n return name && /^[A-Za-z][A-Za-z0-9]*(?:Error|Exception|Rejection)$/.test(name) ? name : ''\n}\n\nfunction inferErrorName(message: string): string {\n return message.match(/^([A-Za-z][A-Za-z0-9]*(?:Error|Exception))\\b/)?.[1] ?? 'Error'\n}\n\nfunction errorLocation(event: ErrorSourceEvent): string {\n if (!event.source) return ''\n\n try {\n const pathname = new URL(event.source, window.location.href).pathname\n if (event.line == null) return pathname\n return `${pathname}:${event.line}:${event.col ?? 0}`\n } catch {\n return ''\n }\n}\n","import { errorSource, type ErrorSourceEvent } from './error-source'\nimport { summarizeError } from './error-summary'\n\nconst MAX_CONSOLE_ERRORS = 20\n\nexport class ConsoleErrorCollector {\n private errors: string[] = []\n private unsubscribe: (() => void) | null = null\n\n start(): void {\n if (this.unsubscribe) return\n this.unsubscribe = errorSource.subscribe((e) => this.record(e))\n }\n\n private record(e: ErrorSourceEvent): void {\n this.addError(summarizeError(e))\n }\n\n private addError(msg: string): void {\n if (this.errors.length >= MAX_CONSOLE_ERRORS) return\n if (!this.errors.includes(msg)) {\n this.errors.push(msg)\n }\n }\n\n getErrors(): string[] {\n return [...this.errors]\n }\n\n stop(): void {\n this.unsubscribe?.()\n this.unsubscribe = null\n }\n}\n","// Single owner of the history.pushState / replaceState patch.\n//\n// Two collectors (breadcrumbs and frustration) both need to know when an SPA\n// navigates. When each wrapped history itself, teardown could only restore the\n// native function if it happened to be the outermost wrapper — so tearing them\n// down in install order (FIFO teardown of a LIFO stack) left one dead wrapper\n// behind per cycle, and Turbo Drive reinstalls on every visit. Refcounting one\n// wrapper here makes the install order irrelevant: the patch goes on when the\n// first listener subscribes and comes off when the last one unsubscribes.\n\nexport interface HistoryNavigationEvent {\n type: 'push' | 'replace' | 'pop'\n fromUrl: string\n toUrl: string\n}\n\ntype NavListener = (event: HistoryNavigationEvent) => void\n\nconst listeners = new Set<NavListener>()\n\nlet nativePushState: History['pushState'] | null = null\nlet nativeReplaceState: History['replaceState'] | null = null\nlet installedPushState: History['pushState'] | null = null\nlet installedReplaceState: History['replaceState'] | null = null\nlet popstateHandler: (() => void) | null = null\nlet lastUrl: string | null = null\n\n// Subscribes to pushState, replaceState and popstate. Returns an unsubscribe\n// function; calling it twice is harmless.\nexport function onHistoryNavigation(listener: NavListener): () => void {\n listeners.add(listener)\n install()\n\n let released = false\n return () => {\n if (released) return\n released = true\n listeners.delete(listener)\n if (listeners.size === 0) uninstall()\n }\n}\n\nfunction notify(type: HistoryNavigationEvent['type'], fromUrl: string): void {\n const event = { type, fromUrl, toUrl: location.href }\n lastUrl = event.toUrl\n // Copy first: a listener may unsubscribe (or subscribe) while being notified.\n for (const listener of [...listeners]) {\n try { listener(event) } catch { /* never propagate into the host page */ }\n }\n}\n\nfunction install(): void {\n if (installedPushState) return\n\n lastUrl = location.href\n popstateHandler = () => notify('pop', lastUrl ?? location.href)\n window.addEventListener('popstate', popstateHandler)\n\n // Snapshot the raw functions and invoke them with .call — binding would hand\n // back a different function object on uninstall, so the host page would never\n // get its own pushState identity back and each cycle would add a bind layer.\n nativePushState = history.pushState\n nativeReplaceState = history.replaceState\n const pushOrig = nativePushState\n const replaceOrig = nativeReplaceState\n\n installedPushState = function (data: unknown, unused: string, url?: string | URL | null) {\n const fromUrl = location.href\n pushOrig.call(history, data, unused, url)\n notify('push', fromUrl)\n }\n installedReplaceState = function (data: unknown, unused: string, url?: string | URL | null) {\n const fromUrl = location.href\n replaceOrig.call(history, data, unused, url)\n notify('replace', fromUrl)\n }\n history.pushState = installedPushState\n history.replaceState = installedReplaceState\n}\n\nfunction uninstall(): void {\n if (popstateHandler) {\n window.removeEventListener('popstate', popstateHandler)\n popstateHandler = null\n }\n // Only restore if nothing (e.g. an SPA router) re-wrapped the functions\n // after us — restoring the stale snapshot would discard their wrapper.\n if (nativePushState && history.pushState === installedPushState) {\n history.pushState = nativePushState\n }\n if (nativeReplaceState && history.replaceState === installedReplaceState) {\n history.replaceState = nativeReplaceState\n }\n nativePushState = null\n nativeReplaceState = null\n installedPushState = null\n installedReplaceState = null\n lastUrl = null\n}\n","import { errorSource, type ErrorSourceEvent } from './error-source'\nimport { summarizeError } from './error-summary'\nimport { getCssSelector, getElementName, isWidgetElement } from './dom-utils'\nimport { onHistoryNavigation } from './history-patch'\nimport { isPrivacyProtected, structuralElementName, structuralElementSelector } from '../privacy/dom'\nimport { sanitizeUrl } from '../privacy/sanitize'\nimport type { Breadcrumb } from '../types'\n\nconst MAX_BREADCRUMBS = 20\n// A click breadcrumb names the element by its on-screen text, which on a page\n// showing an order total or a patient name is that value verbatim. The replay\n// recorder already honours rr-block/rr-mask for exactly this; a customer who\n// marked a region has marked it for the whole SDK, not for replays only. Inside\n// one, fall back to what the element is rather than what it says.\nfunction describeTarget(el: Element): string {\n if (isPrivacyProtected(el)) return structuralElementName(el)\n return getElementName(el)\n}\n\nfunction describeSelector(el: Element): string {\n return isPrivacyProtected(el) ? structuralElementSelector(el) : getCssSelector(el)\n}\n\nexport class BreadcrumbCollector {\n private breadcrumbs: Breadcrumb[] = []\n private active = false\n private unsubscribeErrors: (() => void) | null = null\n private boundClickHandler: (e: MouseEvent) => void\n private boundChangeHandler: (e: Event) => void\n private unsubscribeNavigation: (() => void) | null = null\n private lastUrl = ''\n\n constructor() {\n this.boundClickHandler = (e: MouseEvent) => this.handleClick(e)\n this.boundChangeHandler = (e: Event) => this.handleChange(e)\n }\n\n start(): void {\n if (this.active) return\n this.active = true\n this.lastUrl = location.href\n document.addEventListener('click', this.boundClickHandler, { capture: true, passive: true })\n document.addEventListener('change', this.boundChangeHandler, { capture: true, passive: true })\n this.unsubscribeErrors = errorSource.subscribe((e) => this.recordError(e))\n this.unsubscribeNavigation = onHistoryNavigation(() => this.handleNavigation())\n }\n\n stop(): void {\n if (!this.active) return\n this.active = false\n document.removeEventListener('click', this.boundClickHandler, true)\n document.removeEventListener('change', this.boundChangeHandler, true)\n this.unsubscribeErrors?.()\n this.unsubscribeErrors = null\n this.unsubscribeNavigation?.()\n this.unsubscribeNavigation = null\n }\n\n getBreadcrumbs(): Breadcrumb[] {\n return [...this.breadcrumbs]\n }\n\n private add(crumb: Breadcrumb): void {\n this.breadcrumbs.push(crumb)\n if (this.breadcrumbs.length > MAX_BREADCRUMBS) {\n this.breadcrumbs.shift()\n }\n }\n\n private handleClick(e: MouseEvent): void {\n const target = e.target as Element | null\n if (!target) return\n if (isWidgetElement(target)) return\n\n try {\n this.add({\n type: 'ui',\n category: 'ui.click',\n timestamp: Date.now(),\n message: describeTarget(target),\n data: { selector: describeSelector(target) },\n })\n } catch { /* never propagate into host page */ }\n }\n\n private handleChange(e: Event): void {\n const target = e.target as Element | null\n if (!target) return\n if (isWidgetElement(target)) return\n\n try {\n const input = target as HTMLInputElement\n const field = isPrivacyProtected(target)\n ? structuralElementName(target)\n : input.name || input.placeholder || target.getAttribute('aria-label') || target.tagName\n this.add({\n type: 'ui',\n category: 'ui.input',\n timestamp: Date.now(),\n message: field,\n data: { field },\n })\n } catch { /* never propagate into host page */ }\n }\n\n private recordError(e: ErrorSourceEvent): void {\n const summary = summarizeError(e)\n this.add({\n type: 'console',\n category: 'console.error',\n timestamp: Date.now(),\n message: summary,\n data: { message: summary },\n })\n }\n\n private handleNavigation(): void {\n if (!this.active) return\n const from = this.lastUrl\n const to = location.href\n if (from === to) return\n this.lastUrl = to\n const sanitizedFrom = sanitizeUrl(from)\n const sanitizedTo = sanitizeUrl(to)\n this.add({\n type: 'navigation',\n category: 'navigation',\n timestamp: Date.now(),\n message: `${sanitizedFrom} → ${sanitizedTo}`,\n data: { from: sanitizedFrom, to: sanitizedTo },\n })\n }\n\n}\n","// Frustration state that must outlive a single page visit.\n//\n// The detector and the widget controller are both rebuilt from scratch on every\n// Turbo Drive navigation (see src/index.ts), which used to reset all of this.\n// The consequences were: `rapid_navigation` could never fire (it needs 3\n// timestamps within 5s but each visit contributed at most one), the 60s cooldown\n// was bypassed by navigating, an already-reported error page re-prompted on\n// revisit, and a reporter's explicit \"Dismiss\" did not survive one visit.\n//\n// sessionStorage is the right scope — this is per-tab, per-visit-series state,\n// not a long-lived identity like mtb_anon_id. Writes are best-effort: Safari\n// private mode and storage-partitioned iframes throw, so an in-memory copy backs\n// every read (it covers the current page at least).\n\nconst STORAGE_KEY = 'mtb_frustration_state'\n// Keeps a long browsing session from growing the stored payload without bound.\nconst MAX_ERROR_PAGE_URLS = 20\n\nexport interface FrustrationState {\n navTimestamps: number[]\n cooldownUntil: number\n errorPageUrls: string[]\n dismissedByUser: boolean\n}\n\nlet memoryState: FrustrationState | null = null\n\n// Every return path goes through normalize(), which rebuilds the arrays. Callers\n// own their copy and mutate it in place (the detector pushes onto navTimestamps),\n// so handing out a shared array would let one caller corrupt the stored state.\nexport function loadFrustrationState(): FrustrationState {\n try {\n const raw = window.sessionStorage.getItem(STORAGE_KEY)\n if (!raw) return normalize(memoryState)\n return normalize(JSON.parse(raw))\n } catch {\n return normalize(memoryState)\n }\n}\n\nexport function saveFrustrationState(state: FrustrationState): void {\n const normalized = normalize(state)\n memoryState = normalized\n try {\n window.sessionStorage.setItem(STORAGE_KEY, JSON.stringify(normalized))\n } catch {\n // sessionStorage unavailable — the in-memory copy covers this page\n }\n}\n\nexport function isFrustrationDismissed(): boolean {\n return loadFrustrationState().dismissedByUser\n}\n\n// Records a real refusal (the reporter clicked Dismiss), which suppresses\n// proactive prompts for the rest of the tab session. An auto-hide must never\n// call this — the 60s cooldown is what rate-limits an ignored prompt.\nexport function markFrustrationDismissed(): void {\n saveFrustrationState({ ...loadFrustrationState(), dismissedByUser: true })\n}\n\nexport function clearFrustrationState(): void {\n memoryState = null\n try {\n window.sessionStorage.removeItem(STORAGE_KEY)\n } catch {\n // nothing to clear\n }\n}\n\nfunction normalize(value: unknown): FrustrationState {\n const raw = (value ?? {}) as Partial<Record<keyof FrustrationState, unknown>>\n return {\n navTimestamps: Array.isArray(raw.navTimestamps)\n ? raw.navTimestamps.filter((t): t is number => typeof t === 'number')\n : [],\n cooldownUntil: typeof raw.cooldownUntil === 'number' ? raw.cooldownUntil : 0,\n errorPageUrls: Array.isArray(raw.errorPageUrls)\n ? raw.errorPageUrls.filter((u): u is string => typeof u === 'string').slice(-MAX_ERROR_PAGE_URLS)\n : [],\n dismissedByUser: raw.dismissedByUser === true,\n }\n}\n","import { errorSource } from './error-source'\nimport { isWidgetElement } from './dom-utils'\nimport { onHistoryNavigation } from './history-patch'\nimport type { HistoryNavigationEvent } from './history-patch'\nimport { loadFrustrationState, saveFrustrationState } from './frustration-state'\n\nexport type FrustrationSignal =\n | 'rage_click'\n | 'dead_click'\n | 'rapid_navigation'\n | 'form_failure'\n | 'error_page'\n | 'dead_click_dom'\n\nexport interface FrustrationEvent {\n signal: FrustrationSignal\n target?: string\n detail?: string\n timestamp: number\n}\n\ninterface ClickRecord {\n target: EventTarget | null\n time: number\n}\n\nconst RAGE_CLICK_THRESHOLD = 4\nconst RAGE_CLICK_WINDOW_MS = 1500\nconst RAPID_NAV_THRESHOLD = 3\nconst RAPID_NAV_WINDOW_MS = 5000\nconst FORM_CHECK_DELAY_MS = 500\nconst FORM_FAILURE_THRESHOLD = 2\nconst FORM_FAILURE_WINDOW_MS = 30_000\nconst INTERACTION_ERROR_WINDOW_MS = 2000\nconst DEAD_CLICK_DOM_DELAY_MS = 1000\nconst ERROR_PAGE_CHECK_DELAY_MS = 300\n\nconst INTERACTIVE_TAGS = new Set([\n 'A', 'BUTTON', 'INPUT', 'SELECT', 'TEXTAREA', 'LABEL', 'SUMMARY', 'DETAILS',\n])\n\nconst INTERACTIVE_ROLES = new Set([\n 'button', 'link', 'checkbox', 'radio', 'tab', 'menuitem', 'option',\n 'switch', 'textbox', 'combobox', 'listbox', 'slider', 'spinbutton',\n])\n\nconst COMMAND_TAGS = new Set(['A', 'BUTTON', 'SUMMARY'])\nconst COMMAND_ROLES = new Set(['button', 'link', 'tab', 'menuitem'])\n\n// Matched as whole words, never as substrings. Substring matching read the\n// '500' in \"Top 500 SaaS Tools\" as an outage and put the high-priority prompt\n// in front of every visitor on load.\n//\n// A bare status code stays ambiguous even with word boundaries — it is a\n// listicle count, a company name, a price — so it only counts when the title is\n// nothing but the code, or an explicit error word stands next to it. The\n// phrases are error-shaped on their own and carry themselves.\nconst ERROR_PAGE_PATTERNS = [\n /\\bnot found\\b/,\n /\\bserver error\\b/,\n /^\\s*(?:http\\s*)?[45]\\d{2}\\s*$/,\n /\\b(?:error|http|status)\\s*[:-]?\\s*[45]\\d{2}\\b/,\n /\\b[45]\\d{2}\\s*[:-]?\\s*error\\b/,\n]\n\nexport class FrustrationDetector {\n private clickHistory: ClickRecord[] = []\n private onFrustration: (event: FrustrationEvent) => void\n private boundClickHandler: (e: MouseEvent) => void\n private unsubscribeErrors: (() => void) | null = null\n private active = false\n private lastInteraction: { target: Element; time: number } | null = null\n private cooldownUntil = 0\n private static readonly COOLDOWN_MS = 60_000\n\n private navTimestamps: number[] = []\n private unsubscribeNavigation: (() => void) | null = null\n private boundPageLeaveHandler: (() => void) | null = null\n private boundTurboNavigationHandler: (() => void) | null = null\n private lastNavigationAt = -1\n private boundSubmitHandler: ((e: Event) => void) | null = null\n private boundInvalidHandler: ((e: Event) => void) | null = null\n private recentSubmitForms = new WeakSet<HTMLFormElement>()\n private recentFailedForms = new WeakSet<HTMLFormElement>()\n private formFailureTimestamps = new WeakMap<HTMLFormElement, number[]>()\n private errorPageUrls = new Set<string>()\n private domObserver: MutationObserver | null = null\n private pendingDomChecks = 0\n private lastMutationAt = -1\n\n // Every deferred check outlives the interaction that scheduled it, so stop()\n // has to cancel the ones still waiting. Two reasons: a dead-click timer holds\n // the clicked host element for a second past teardown, and a start() inside\n // that window would let the previous session's click emit against the new one.\n private pendingTimers = new Set<ReturnType<typeof setTimeout>>()\n\n constructor(onFrustration: (event: FrustrationEvent) => void) {\n this.onFrustration = onFrustration\n this.boundClickHandler = (e: MouseEvent) => this.handleClick(e)\n }\n\n start(): void {\n if (this.active) return\n this.active = true\n this.restoreState()\n document.addEventListener('click', this.boundClickHandler, true)\n this.unsubscribeErrors = errorSource.subscribe(() => {\n const now = Date.now()\n if (!this.lastInteraction || now - this.lastInteraction.time > INTERACTION_ERROR_WINDOW_MS) return\n\n const interaction = this.lastInteraction\n this.lastInteraction = null\n this.emitIfReady({\n signal: 'dead_click',\n target: describeElement(interaction.target),\n detail: 'Uncaught error after interaction',\n timestamp: now,\n })\n })\n\n this.startNavigationTracking()\n this.startFormTracking()\n this.checkErrorPage()\n }\n\n stop(): void {\n if (!this.active) return\n this.active = false\n document.removeEventListener('click', this.boundClickHandler, true)\n this.unsubscribeErrors?.()\n this.unsubscribeErrors = null\n this.clickHistory = []\n this.lastInteraction = null\n\n this.stopNavigationTracking()\n this.stopFormTracking()\n this.errorPageUrls.clear()\n\n this.clearPendingTimers()\n // The cancelled dead-click timers are the only thing that would have\n // decremented this, so the count has to come back to zero by hand or the\n // observer of a restarted detector never reaches its disconnect condition.\n this.pendingDomChecks = 0\n this.disconnectDomObserver()\n }\n\n private schedule(callback: () => void, delayMs: number): void {\n const timer = setTimeout(() => {\n this.pendingTimers.delete(timer)\n callback()\n }, delayMs)\n this.pendingTimers.add(timer)\n }\n\n private clearPendingTimers(): void {\n for (const timer of this.pendingTimers) clearTimeout(timer)\n this.pendingTimers.clear()\n }\n\n private startNavigationTracking(): void {\n this.unsubscribeNavigation = onHistoryNavigation((event) => this.handleNavigation(event))\n\n // A click that starts a full-page navigation (checkout, OAuth, external\n // link, download, window.location assignment) leaves the DOM untouched\n // while the browser tears the page down, which is indistinguishable from a\n // dead click by mutation alone. Recording when the page starts leaving lets\n // checkDeadClickDom acquit those clicks. Both events are needed: browsers\n // skip beforeunload when nothing registered an unload prompt, and Safari\n // reaches bfcache via pagehide only.\n this.boundPageLeaveHandler = () => this.noteNavigationStarted()\n window.addEventListener('beforeunload', this.boundPageLeaveHandler)\n window.addEventListener('pagehide', this.boundPageLeaveHandler)\n this.boundTurboNavigationHandler = () => this.noteNavigationStarted()\n document.addEventListener('turbo:before-visit', this.boundTurboNavigationHandler)\n document.addEventListener('turbo:submit-start', this.boundTurboNavigationHandler)\n document.addEventListener('turbo:before-fetch-request', this.boundTurboNavigationHandler)\n }\n\n private stopNavigationTracking(): void {\n this.unsubscribeNavigation?.()\n this.unsubscribeNavigation = null\n this.navTimestamps = []\n\n if (this.boundPageLeaveHandler) {\n window.removeEventListener('beforeunload', this.boundPageLeaveHandler)\n window.removeEventListener('pagehide', this.boundPageLeaveHandler)\n this.boundPageLeaveHandler = null\n }\n if (this.boundTurboNavigationHandler) {\n document.removeEventListener('turbo:before-visit', this.boundTurboNavigationHandler)\n document.removeEventListener('turbo:submit-start', this.boundTurboNavigationHandler)\n document.removeEventListener('turbo:before-fetch-request', this.boundTurboNavigationHandler)\n this.boundTurboNavigationHandler = null\n }\n this.lastNavigationAt = -1\n }\n\n private noteNavigationStarted(): void {\n this.lastNavigationAt = Date.now()\n }\n\n // Rehydrates the state a Turbo Drive visit would otherwise reset. Without this,\n // a fresh detector on every page starts with an empty nav history (so\n // `rapid_navigation` can never reach its 3-in-5s threshold), no cooldown, and no\n // memory of which error pages it already reported.\n private restoreState(): void {\n const stored = loadFrustrationState()\n this.navTimestamps = stored.navTimestamps\n this.cooldownUntil = stored.cooldownUntil\n this.errorPageUrls = new Set(stored.errorPageUrls)\n }\n\n private persistState(): void {\n const stored = loadFrustrationState()\n saveFrustrationState({\n ...stored,\n navTimestamps: this.navTimestamps,\n cooldownUntil: this.cooldownUntil,\n errorPageUrls: [...this.errorPageUrls],\n })\n }\n\n private startFormTracking(): void {\n this.boundSubmitHandler = (e: Event) => this.handleFormSubmit(e)\n this.boundInvalidHandler = (e: Event) => this.handleFormInvalid(e)\n document.addEventListener('submit', this.boundSubmitHandler, true)\n document.addEventListener('invalid', this.boundInvalidHandler, true)\n }\n\n private stopFormTracking(): void {\n if (this.boundSubmitHandler) {\n document.removeEventListener('submit', this.boundSubmitHandler, true)\n this.boundSubmitHandler = null\n }\n if (this.boundInvalidHandler) {\n document.removeEventListener('invalid', this.boundInvalidHandler, true)\n this.boundInvalidHandler = null\n }\n // Their expiry timers were just cancelled, so membership would otherwise be\n // permanent and a form left mid-submit here would be judged failed by a\n // later detector on the strength of an attempt from the previous one.\n this.recentSubmitForms = new WeakSet()\n this.recentFailedForms = new WeakSet()\n }\n\n private handleClick(e: MouseEvent): void {\n const target = e.target as Element | null\n if (!target) return\n\n if (isWidgetElement(target)) return\n this.trackFormSubmitIntent(target)\n this.trackNativeNavigationIntent(e, target)\n const interactionTarget = this.interactionTarget(target)\n\n const now = Date.now()\n this.lastInteraction = { target: interactionTarget, time: now }\n this.clickHistory.push({ target: interactionTarget, time: now })\n\n const cutoff = now - RAGE_CLICK_WINDOW_MS\n this.clickHistory = this.clickHistory.filter(r => r.time > cutoff)\n\n const sameTargetClicks = this.clickHistory.filter(r => r.target === interactionTarget)\n if (sameTargetClicks.length >= RAGE_CLICK_THRESHOLD) {\n this.clickHistory = []\n this.emitHighPriority({\n signal: 'rage_click',\n target: describeElement(interactionTarget),\n timestamp: now,\n })\n return\n }\n\n this.checkDeadClickDom(interactionTarget, now)\n }\n\n private handleNavigation(event: HistoryNavigationEvent): void {\n const now = Date.now()\n this.noteNavigationStarted()\n const meaningfulNavigation = event.type !== 'replace' && event.fromUrl !== event.toUrl\n // Successful forward navigation is ordinary browsing, even when several\n // pages are opened quickly. Repeated browser back/forward movement is the\n // narrower signal that the visitor may be struggling to find their way.\n if (meaningfulNavigation && event.type === 'pop') {\n this.navTimestamps.push(now)\n const cutoff = now - RAPID_NAV_WINDOW_MS\n this.navTimestamps = this.navTimestamps.filter(t => t > cutoff)\n if (this.navTimestamps.length >= RAPID_NAV_THRESHOLD) {\n this.navTimestamps = []\n this.emitIfReady({\n signal: 'rapid_navigation',\n timestamp: now,\n })\n }\n }\n this.persistState()\n this.schedule(() => {\n if (this.active) this.checkErrorPage()\n }, ERROR_PAGE_CHECK_DELAY_MS)\n }\n\n private handleFormSubmit(e: Event): void {\n const form = e.target\n if (!(form instanceof HTMLFormElement)) return\n if (isWidgetElement(form)) return\n\n this.rememberSubmitAttempt(form)\n\n this.schedule(() => {\n if (!this.active) return\n if (form.querySelector('[aria-invalid=\"true\"], .is-invalid')) {\n this.recordFormFailure(form)\n }\n }, FORM_CHECK_DELAY_MS)\n }\n\n private trackFormSubmitIntent(target: Element): void {\n const control = target.closest('button, input')\n if (!(control instanceof HTMLButtonElement || control instanceof HTMLInputElement)) return\n if (control.type !== 'submit' && control.type !== 'image') return\n if (!control.form || isWidgetElement(control.form)) return\n\n this.rememberSubmitAttempt(control.form)\n }\n\n private trackNativeNavigationIntent(event: MouseEvent, target: Element): void {\n if (!target.closest('a[href]')) return\n\n queueMicrotask(() => {\n if (this.active && !event.defaultPrevented) this.noteNavigationStarted()\n })\n }\n\n private rememberSubmitAttempt(form: HTMLFormElement): void {\n this.recentSubmitForms.add(form)\n this.schedule(() => {\n this.recentSubmitForms.delete(form)\n }, FORM_CHECK_DELAY_MS)\n }\n\n private recordFormFailure(form: HTMLFormElement): void {\n if (this.recentFailedForms.has(form)) return\n this.recentFailedForms.add(form)\n this.schedule(() => this.recentFailedForms.delete(form), FORM_CHECK_DELAY_MS)\n\n const now = Date.now()\n const cutoff = now - FORM_FAILURE_WINDOW_MS\n const failures = (this.formFailureTimestamps.get(form) ?? []).filter(timestamp => timestamp > cutoff)\n failures.push(now)\n this.formFailureTimestamps.set(form, failures)\n if (failures.length < FORM_FAILURE_THRESHOLD) return\n\n this.formFailureTimestamps.set(form, [])\n this.emitIfReady({\n signal: 'form_failure',\n target: describeElement(form),\n timestamp: now,\n })\n }\n\n private handleFormInvalid(e: Event): void {\n const el = e.target as Element | null\n if (!el) return\n if (isWidgetElement(el)) return\n const form = el.closest('form')\n if (!form || !this.recentSubmitForms.has(form)) return\n this.recordFormFailure(form)\n }\n\n private checkErrorPage(): void {\n const url = location.href\n if (this.errorPageUrls.has(url)) return\n\n const title = document.title.toLowerCase()\n if (ERROR_PAGE_PATTERNS.some(p => p.test(title))) {\n this.errorPageUrls.add(url)\n this.emitHighPriority({\n signal: 'error_page',\n detail: document.title,\n timestamp: Date.now(),\n })\n return\n }\n\n const candidates = document.querySelectorAll(\n 'h1, h2, [class*=\"error\"], [class*=\"not-found\"], [id*=\"error\"], [id*=\"not-found\"]'\n )\n for (const el of candidates) {\n const text = el.textContent ?? ''\n if (text.length < 200 && ERROR_PAGE_PATTERNS.some(p => p.test(text.toLowerCase()))) {\n this.errorPageUrls.add(url)\n this.emitHighPriority({\n signal: 'error_page',\n detail: text.trim().slice(0, 60),\n timestamp: Date.now(),\n })\n return\n }\n }\n }\n\n // One shared observer for all in-flight dead-click checks, connected only\n // while at least one check is pending — a full-page observer per click was\n // too heavy on mutation-busy pages.\n private checkDeadClickDom(target: Element, clickTime: number): void {\n if (!this.looksInteractive(target)) return\n\n this.pendingDomChecks++\n this.ensureDomObserver()\n\n this.schedule(() => {\n this.pendingDomChecks--\n const mutated = this.lastMutationAt >= clickTime\n // A navigation that began at or after the click means the click worked;\n // the page just left before it could mutate anything.\n const navigated = this.lastNavigationAt >= clickTime\n if (this.pendingDomChecks === 0) this.disconnectDomObserver()\n\n if (!mutated && !navigated && this.active) {\n this.emitIfReady({\n signal: 'dead_click_dom',\n target: describeElement(target),\n timestamp: clickTime,\n })\n }\n }, DEAD_CLICK_DOM_DELAY_MS)\n }\n\n private ensureDomObserver(): void {\n if (this.domObserver) return\n this.domObserver = new MutationObserver(() => {\n this.lastMutationAt = Date.now()\n })\n this.domObserver.observe(document.documentElement, {\n childList: true,\n subtree: true,\n attributes: true,\n characterData: true,\n })\n }\n\n private disconnectDomObserver(): void {\n this.domObserver?.disconnect()\n this.domObserver = null\n }\n\n private looksInteractive(el: Element): boolean {\n if (COMMAND_TAGS.has(el.tagName) || el.hasAttribute('onclick')) return true\n\n const role = el.getAttribute('role')\n if (role && COMMAND_ROLES.has(role)) return true\n\n const style = window.getComputedStyle(el)\n if (style.cursor === 'pointer') return true\n\n if (el.classList) {\n for (const cls of el.classList) {\n const lower = cls.toLowerCase()\n if (lower.includes('btn') || lower.includes('button')) return true\n }\n }\n\n return false\n }\n\n private interactionTarget(el: Element): Element {\n let current: Element | null = el\n while (current && current !== document.body) {\n if (this.isIntrinsicInteractive(current)) return current\n current = current.parentElement\n }\n\n let candidate = el\n current = el.parentElement\n while (current && current !== document.body && this.looksInteractive(current)) {\n candidate = current\n current = current.parentElement\n }\n return candidate\n }\n\n private isIntrinsicInteractive(el: Element): boolean {\n if (INTERACTIVE_TAGS.has(el.tagName)) return true\n\n const role = el.getAttribute('role')\n if (role && INTERACTIVE_ROLES.has(role)) return true\n\n if (el.hasAttribute('onclick') || el.hasAttribute('tabindex')) return true\n return (el as HTMLElement).contentEditable === 'true'\n }\n\n private emitIfReady(event: FrustrationEvent): void {\n const now = Date.now()\n if (now < this.cooldownUntil) return\n this.cooldownUntil = now + FrustrationDetector.COOLDOWN_MS\n this.persistState()\n this.onFrustration(event)\n }\n\n private emitHighPriority(event: FrustrationEvent): void {\n this.cooldownUntil = Date.now() + FrustrationDetector.COOLDOWN_MS\n this.persistState()\n this.onFrustration(event)\n }\n}\n\nfunction describeElement(el: Element): string {\n const tag = el.tagName.toLowerCase()\n const id = el.id ? `#${el.id}` : ''\n const cls = el.className && typeof el.className === 'string'\n ? '.' + el.className.trim().split(/\\s+/).slice(0, 2).join('.')\n : ''\n const text = el.textContent?.trim().slice(0, 30) ?? ''\n return `<${tag}${id}${cls}>${text ? ` \"${text}\"` : ''}`\n}\n","import type {\n FeedbackPayload,\n FeedbackResponse,\n SubmissionSessionResponse,\n ClarifyResponse,\n ClarifyMessage,\n} from '../types'\nimport { filterSensitiveText } from '../privacy/sanitize'\n\nexport interface ClarifyStreamResult {\n messages: ClarifyMessage[]\n suggestions?: string[]\n done: boolean\n failed?: boolean\n}\n\n// Carries the HTTP status so callers can tell a retryable failure from a terminal\n// one (e.g. 409 = the submission session is no longer accepting a finalize, which\n// retrying can never fix). The message stays `HTTP <status>` for compatibility.\nexport class ApiError extends Error {\n readonly status: number\n\n constructor(status: number) {\n super(`HTTP ${status}`)\n this.name = 'ApiError'\n this.status = status\n }\n}\n\nexport function isTerminalConflict(error: unknown): boolean {\n return error instanceof ApiError && error.status === 409\n}\n\n// A finalize the reporter can no longer watch must still end. Without a ceiling\n// a request that never settles leaves the widget mid-submission forever, with\n// the launcher inert and nothing on screen to explain why.\nexport const FINALIZE_TIMEOUT_MS = 25_000\n\nconst DEFAULT_API_URL = 'https://makethisbetter.dev/api/v1'\nconst CAPTURE_POLICY = 'sensitive-data-v1'\n\nexport interface ApiClientOptions {\n projectKey: string\n apiUrl?: string\n retryDelayMs?: number\n userToken?: string\n userTokenFn?: () => Promise<string>\n}\n\nexport class ApiClient {\n private apiUrl: string\n private projectKey: string\n private retryDelayMs: number\n private userToken?: string\n private userTokenFn?: () => Promise<string>\n\n constructor(options: ApiClientOptions) {\n this.projectKey = options.projectKey\n this.apiUrl = (options.apiUrl ?? DEFAULT_API_URL).replace(/\\/$/, '')\n this.retryDelayMs = options.retryDelayMs ?? 1000\n this.userToken = options.userToken\n this.userTokenFn = options.userTokenFn\n }\n\n async resolveUserToken(): Promise<string | undefined> {\n if (this.userTokenFn) return this.userTokenFn()\n return this.userToken\n }\n\n async resolveScreenshotResourceUrl(\n submissionSessionId: string,\n token: string,\n resourceUrl: string,\n contentType?: string,\n signal?: AbortSignal,\n ): Promise<string | undefined> {\n if (contentType && !contentType.startsWith('image/')) return undefined\n\n try {\n const source = new URL(resourceUrl, window.location.href)\n if (!['http:', 'https:'].includes(source.protocol)) return undefined\n if (source.origin === window.location.origin) return undefined\n\n const headers = await this.submissionHeaders(token, 'application/json')\n const res = await this.fetchWithRetry(\n `${this.apiUrl}/widget/feedback_submission_sessions/${submissionSessionId}/image_proxy_url`,\n {\n method: 'POST',\n headers,\n body: JSON.stringify({ image_proxy_url: { source: source.href } }),\n signal,\n },\n )\n const body = await res.json() as { url?: unknown }\n return typeof body.url === 'string' ? body.url : undefined\n } catch {\n return undefined\n }\n }\n\n private async authHeaders(contentType?: string): Promise<Record<string, string>> {\n const headers: Record<string, string> = { 'X-Project-Key': this.projectKey }\n const token = await this.resolveUserToken()\n if (token) headers['X-User-Token'] = token\n if (contentType) headers['Content-Type'] = contentType\n return headers\n }\n\n private async submissionHeaders(token: string, contentType?: string): Promise<Record<string, string>> {\n const headers = await this.authHeaders(contentType)\n headers['X-Submission-Token'] = token\n return headers\n }\n\n async createSubmissionSession(payload: FeedbackPayload, screenshot: Blob | null): Promise<SubmissionSessionResponse> {\n const submissionToken = generateSubmissionToken()\n const form = new FormData()\n form.append('feedback[description]', payload.description)\n form.append('feedback[page_url]', payload.page_url)\n form.append('feedback[user_agent]', payload.user_agent)\n form.append('feedback[browser]', payload.browser)\n form.append('feedback[os]', payload.os)\n form.append('feedback[screen_width]', String(payload.screen_width))\n form.append('feedback[screen_height]', String(payload.screen_height))\n form.append('feedback[console_errors]', JSON.stringify(payload.console_errors))\n form.append('feedback[annotations]', JSON.stringify(payload.annotations))\n\n if (payload.target_element) {\n form.append('feedback[target_element]', JSON.stringify(payload.target_element))\n }\n if (payload.user_id) form.append('feedback[user_id]', payload.user_id)\n if (payload.reporter_external_id) form.append('feedback[reporter_external_id]', payload.reporter_external_id)\n if (payload.reporter_email) form.append('feedback[reporter_email]', payload.reporter_email)\n if (payload.user_email) form.append('feedback[user_email]', payload.user_email)\n if (payload.user_name) form.append('feedback[user_name]', payload.user_name)\n\n if (payload.breadcrumbs && payload.breadcrumbs.length > 0) {\n form.append('feedback[breadcrumbs]', JSON.stringify(payload.breadcrumbs))\n }\n\n if (payload.recording_events && payload.recording_events.length > 0) {\n const json = JSON.stringify(payload.recording_events)\n const blob = new Blob([json], { type: 'application/json' })\n form.append('feedback[recording]', blob, 'interaction-replay.json')\n form.append('feedback[recording_duration]', String(payload.recording_duration ?? 0))\n }\n\n if (screenshot) {\n const ext = screenshot.type === 'image/jpeg' ? 'jpg' : 'png'\n form.append('feedback[screenshot]', screenshot, `screenshot.${ext}`)\n }\n\n const headers = await this.authHeaders()\n headers['X-Submission-Token'] = submissionToken\n headers['X-MTB-Capture-Policy'] = CAPTURE_POLICY\n // One key per submission, reused across retries: if the first POST\n // reached the server but the response was lost, the retry must not\n // create a second feedback.\n headers['X-Idempotency-Key'] = generateIdempotencyKey()\n const res = await this.fetchWithRetry(`${this.apiUrl}/widget/feedback_submission_sessions`, {\n method: 'POST',\n headers,\n body: form,\n })\n const body = await res.json() as {\n submission_session: Omit<SubmissionSessionResponse, 'token'> & { token?: string }\n }\n return { ...body.submission_session, token: submissionToken }\n }\n\n async createIdentityToken(reporterExternalId: string): Promise<{ identity_token: string; board_url: string } | null> {\n const headers = await this.authHeaders('application/json')\n try {\n const res = await this.fetchWithRetry(`${this.apiUrl}/widget/identity_tokens`, {\n method: 'POST',\n headers,\n body: JSON.stringify({ reporter_external_id: reporterExternalId }),\n })\n return await res.json()\n } catch {\n return null\n }\n }\n\n async updateReporter(feedbackId: string, email: string, identityToken: string): Promise<boolean> {\n const headers = await this.authHeaders('application/json')\n headers['X-Identity-Token'] = identityToken\n try {\n await this.fetchWithRetry(`${this.apiUrl}/widget/feedbacks/${feedbackId}/reporter`, {\n method: 'PATCH',\n headers,\n body: JSON.stringify({ reporter: { email } }),\n })\n return true\n } catch {\n return false\n }\n }\n\n // Records the reporter's answer to the AI follow-up question. Must run\n // before finalize: finalizing deactivates the session and the server then\n // rejects the answer as terminal.\n async answerClarification(submissionSessionId: string, token: string, message: string): Promise<ClarifyResponse> {\n return this.requestClarification(submissionSessionId, token, { message: filterSensitiveText(message) })\n }\n\n private async requestClarification(\n submissionSessionId: string,\n token: string,\n body: Record<string, string | boolean>,\n ): Promise<ClarifyResponse> {\n const headers = await this.submissionHeaders(token, 'application/json')\n const res = await this.fetchWithRetry(`${this.apiUrl}/widget/feedback_submission_sessions/${submissionSessionId}/clarification`, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n })\n const response = await res.json() as { clarification: ClarifyResponse }\n return response.clarification\n }\n\n // Streams a clarification turn over SSE. Calls onDelta for each token and\n // resolves with the committed result.\n async streamClarification(\n submissionSessionId: string,\n token: string,\n onDelta: (text: string) => void,\n signal?: AbortSignal,\n ): Promise<ClarifyStreamResult> {\n const headers = await this.submissionHeaders(token, 'application/json')\n headers['Accept'] = 'text/event-stream'\n\n const res = await fetch(`${this.apiUrl}/widget/feedback_submission_sessions/${submissionSessionId}/clarification`, {\n method: 'POST',\n headers,\n body: JSON.stringify({}),\n signal,\n })\n\n const contentType = res.headers.get('Content-Type') ?? ''\n if (!res.ok || !res.body || !contentType.includes('text/event-stream')) {\n throw new Error('clarify stream unavailable')\n }\n\n return consumeSse(res.body, onDelta)\n }\n\n // No X-Idempotency-Key here, unlike createSubmissionSession: the session id in\n // the path already is one. The server finalizes under a row lock and returns\n // the feedback it already created if the session is finalized, so the retry\n // fetchWithRetry makes after a 5xx cannot produce a second Feedback.\n async finalizeSubmissionSession(submissionSessionId: string, token: string): Promise<FeedbackResponse> {\n const headers = await this.submissionHeaders(token)\n const controller = new AbortController()\n // Covers both attempts, so the caller waits the ceiling once rather than\n // once per retry.\n const timer = setTimeout(() => controller.abort(), FINALIZE_TIMEOUT_MS)\n try {\n const res = await this.fetchWithRetry(`${this.apiUrl}/widget/feedback_submission_sessions/${submissionSessionId}/feedback`, {\n method: 'POST',\n headers,\n signal: controller.signal,\n })\n const response = await res.json() as { feedback: FeedbackResponse }\n return response.feedback\n } finally {\n clearTimeout(timer)\n }\n }\n\n async uploadScreenshot(\n submissionSessionId: string,\n token: string,\n screenshot: Blob,\n signal?: AbortSignal,\n ): Promise<void> {\n const headers = await this.submissionHeaders(token)\n delete (headers as Record<string, string>)['Content-Type']\n const body = new FormData()\n body.append('screenshot', screenshot, 'screenshot.jpg')\n await this.fetchWithRetry(`${this.apiUrl}/widget/feedback_submission_sessions/${submissionSessionId}/screenshot`, {\n method: 'PUT',\n headers,\n body,\n signal,\n })\n }\n\n async abandonSubmissionSession(submissionSessionId: string, token: string): Promise<void> {\n const headers = await this.submissionHeaders(token)\n await this.fetchWithRetry(`${this.apiUrl}/widget/feedback_submission_sessions/${submissionSessionId}`, {\n method: 'DELETE',\n headers,\n })\n }\n\n private async fetchWithRetry(url: string, init: RequestInit, retries = 1): Promise<Response> {\n let res: Response\n try {\n res = await fetch(url, init)\n } catch (err) {\n // An aborted request has spent its deadline; retrying only makes the\n // caller wait out the retry delay for a second guaranteed abort.\n if (retries > 0 && !init.signal?.aborted) {\n await delay(this.retryDelayMs)\n return this.fetchWithRetry(url, init, retries - 1)\n }\n throw err\n }\n\n if (res.ok) return res\n if (res.status >= 500 && retries > 0) {\n await delay(this.retryDelayMs)\n return this.fetchWithRetry(url, init, retries - 1)\n }\n throw new ApiError(res.status)\n }\n}\n\nfunction delay(ms: number): Promise<void> {\n if (ms <= 0) return Promise.resolve()\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n\nasync function consumeSse(\n body: ReadableStream<Uint8Array>,\n onDelta: (text: string) => void,\n): Promise<ClarifyStreamResult> {\n const reader = body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n let result: ClarifyStreamResult | null = null\n\n const flush = (raw: string): void => {\n // Each SSE record is \"event: <name>\\n data: <json>\". Parse event + data.\n const eventLine = raw.split('\\n').find(l => l.startsWith('event:'))\n const dataLine = raw.split('\\n').find(l => l.startsWith('data:'))\n if (!dataLine) return\n const event = eventLine?.slice(6).trim()\n const payload = JSON.parse(dataLine.slice(5).trim())\n if (event === 'delta' && typeof payload.text === 'string') {\n onDelta(payload.text)\n } else if (event === 'done') {\n result = {\n messages: Array.isArray(payload.messages) ? payload.messages : [],\n suggestions: Array.isArray(payload.suggestions)\n ? payload.suggestions.filter((s: unknown): s is string => typeof s === 'string')\n : [],\n done: payload.done !== false,\n failed: payload.error === true,\n }\n }\n }\n\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n buffer += decoder.decode(value, { stream: true })\n let sep: number\n while ((sep = buffer.indexOf('\\n\\n')) !== -1) {\n const record = buffer.slice(0, sep)\n buffer = buffer.slice(sep + 2)\n try { flush(record) } catch { /* skip malformed record */ }\n }\n }\n\n buffer += decoder.decode()\n if (buffer.trim()) {\n try { flush(buffer) } catch { /* handled by the missing done check below */ }\n }\n if (!result) throw new Error('clarify stream ended before done')\n\n return result\n}\n\nfunction generateIdempotencyKey(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID()\n }\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`\n}\n\nfunction generateSubmissionToken(): string {\n if (typeof crypto === 'undefined' || typeof crypto.getRandomValues !== 'function') {\n throw new Error('Secure random generation is unavailable')\n }\n\n const bytes = crypto.getRandomValues(new Uint8Array(32))\n return Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('')\n}\n","const STORAGE_KEY = 'mtb_anon_id'\nconst EMAIL_KEY = 'mtb_reporter_email'\nconst BOARD_URL_KEY = 'mtb_board_url'\n\nlet memoryId: string | null = null\nlet memoryEmail: string | null = null\nlet memoryBoardUrl: string | null = null\n\nexport function getAnonId(): string {\n try {\n const stored = window.localStorage.getItem(STORAGE_KEY)\n if (stored?.startsWith('anon_')) return stored\n const id = generateAnonId()\n window.localStorage.setItem(STORAGE_KEY, id)\n return id\n } catch {\n memoryId ??= generateAnonId()\n return memoryId\n }\n}\n\nexport function getStoredReporterEmail(): string | undefined {\n try {\n return window.localStorage.getItem(EMAIL_KEY) ?? undefined\n } catch {\n return memoryEmail ?? undefined\n }\n}\n\nexport function rememberReporterEmail(email: string): void {\n memoryEmail = email\n try {\n window.localStorage.setItem(EMAIL_KEY, email)\n } catch {\n // localStorage unavailable — the in-memory copy covers this session\n }\n}\n\nexport function getCachedBoardUrl(): string | undefined {\n try {\n return window.localStorage.getItem(BOARD_URL_KEY) ?? undefined\n } catch {\n return memoryBoardUrl ?? undefined\n }\n}\n\nexport function cacheBoardUrl(url: string): void {\n memoryBoardUrl = url\n try {\n window.localStorage.setItem(BOARD_URL_KEY, url)\n } catch {\n // localStorage unavailable — the in-memory copy covers this session\n }\n}\n\nfunction generateAnonId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return `anon_${crypto.randomUUID()}`\n }\n const random = `${Date.now().toString(16)}-${Math.random().toString(16).slice(2, 10)}`\n return `anon_${random}`\n}\n","export default {\n tab: 'Feedback',\n toolbar: {\n markup: 'Mark up',\n record: 'Record',\n hint: 'Click to comment, drag to draw',\n hintTouch: \"Drag to mark what's wrong\",\n hintRecord: 'Recording your screen',\n exit: 'Exit',\n },\n popup: {\n about: 'About',\n placeholder: 'Describe the issue or suggestion...',\n submit: 'Submit',\n sending: 'Sending...',\n cancel: 'Cancel',\n close: 'Close',\n my_feedback: 'My feedback',\n screenshot_label: 'Attach screenshot',\n screenshot_hint: 'Passwords and other sensitive fields are covered automatically.',\n screenshot_short: 'Screenshot',\n screenshot_locked_hint: 'Your drawing is baked onto the screenshot, so it is always attached. Sensitive fields are covered automatically.',\n recording_label: 'Recording',\n recording_locked_hint: 'A replay of what the page showed and how it was used — always included with a recording. No camera or microphone. Sensitive fields are covered automatically.',\n quickOptions: [\n { emoji: '🐛', label: 'Broken', value: \"Something's broken\" },\n { emoji: '💡', label: 'Suggestion', value: 'I have a suggestion' },\n { emoji: '🤔', label: 'Confused', value: \"I'm confused\" },\n ],\n },\n success: {\n title: 'Thanks, your feedback is in.',\n title_no_ai: \"Thanks! We've received your feedback.\",\n close: 'Close',\n email_saved: \"You'll get an email when it's resolved.\",\n email_error: \"Couldn't save your email. Please try again.\",\n track_feedback: 'Track your feedback',\n email_cta: 'Email me when it\\'s fixed',\n notify: 'Notify',\n },\n error: {\n submit: 'Failed to submit. Please try again.',\n finalize_title: \"We couldn't send this yet.\",\n finalize_message: 'Your feedback is still saved. Please try again.',\n },\n pet: {\n bubble: 'Need help?',\n },\n frustration: {\n prompt: 'Something not working? Tell us.',\n action: 'Annotate',\n dismiss: 'Dismiss',\n },\n annotation: {\n drawing: 'Drawing',\n element: 'element',\n },\n draw: {\n note_placeholder: 'Add a note to your drawing',\n undo: 'Undo',\n redo: 'Redo',\n cancel: 'Cancel',\n submit: 'Submit',\n },\n record: {\n stop: 'Stop',\n timer_label: 'Recording',\n max_reached: 'Maximum recording time reached',\n replay_label: 'screen-recording.json',\n play: 'Play recording',\n close: 'Close',\n preview_unavailable: 'Preview unavailable',\n },\n clarify: {\n title: 'Make This Better AI',\n subtitle: 'Sharpening your feedback',\n thinking: 'Thinking...',\n placeholder: 'Add details, then submit',\n send: 'Send',\n submit: 'Submit',\n send_feedback: 'Send feedback',\n skip: 'Skip & submit',\n skip_short: 'Skip',\n cancel: \"Cancel — don't send feedback\",\n retry: 'Retry',\n error: \"Couldn't load the question. Send your feedback as is.\",\n expired: 'This feedback session expired before it could be sent. Please close the widget and submit again.',\n },\n}\n","export default {\n tab: '反馈',\n toolbar: {\n markup: '标注',\n record: '录屏',\n hint: '点击标注,拖动绘制',\n hintTouch: '用手指圈出问题',\n hintRecord: '正在录制屏幕',\n exit: '退出',\n },\n popup: {\n about: '关于',\n placeholder: '描述问题或建议...',\n submit: '提交',\n sending: '发送中...',\n cancel: '取消',\n close: '关闭',\n my_feedback: '我的反馈',\n screenshot_label: '附带页面截图',\n screenshot_hint: '密码等敏感字段会自动打码。',\n screenshot_short: '截图',\n screenshot_locked_hint: '圈画会画在截图上,所以必定附带。敏感字段会自动打码。',\n recording_label: '录制',\n recording_locked_hint: '页面画面与操作过程的回放,录制内容必定随反馈提交。不含摄像头和麦克风,敏感字段会自动打码。',\n quickOptions: [\n { emoji: '🐛', label: '有问题', value: '出问题了' },\n { emoji: '💡', label: '建议', value: '我有一个建议' },\n { emoji: '🤔', label: '疑问', value: '我有点疑问' },\n ],\n },\n success: {\n title: '收到了,谢谢你的反馈。',\n title_no_ai: '感谢!我们已收到您的反馈。',\n close: '关闭',\n email_saved: '问题解决后您会收到邮件通知。',\n email_error: '邮箱保存失败,请重试。',\n track_feedback: '查看处理进度',\n email_cta: '解决后邮件通知我',\n notify: '通知我',\n },\n error: {\n submit: '提交失败,请重试。',\n finalize_title: '暂时无法发送。',\n finalize_message: '你的反馈仍已保存,请重试。',\n },\n pet: {\n bubble: '需要帮助吗?',\n },\n frustration: {\n prompt: '遇到问题了?告诉我们。',\n action: '标注',\n dismiss: '关闭',\n },\n annotation: {\n drawing: '绘制',\n element: '元素',\n },\n draw: {\n note_placeholder: '给你的绘制添加说明',\n undo: '撤销',\n redo: '重做',\n cancel: '取消',\n submit: '提交',\n },\n record: {\n stop: '停止',\n timer_label: '录制中',\n max_reached: '已达到最长录制时间',\n replay_label: 'screen-recording.json',\n play: '播放录屏',\n close: '关闭',\n preview_unavailable: '暂无法预览',\n },\n clarify: {\n title: 'Make This Better AI',\n subtitle: '正在完善你的反馈',\n thinking: '思考中...',\n placeholder: '补充说明后提交',\n send: '发送',\n submit: '提交',\n send_feedback: '发送反馈',\n skip: '跳过并提交',\n skip_short: '跳过',\n cancel: '取消并不发送',\n retry: '重试',\n error: '追问加载失败,可以直接发送反馈。',\n expired: '这条反馈在发送前已过期。请关闭组件后重新提交。',\n },\n}\n","export default {\n tab: 'フィードバック',\n toolbar: {\n markup: 'マークアップ',\n record: '録画',\n hint: 'クリックでコメント、ドラッグで描画',\n hintTouch: 'ドラッグして問題箇所を囲んでください',\n hintRecord: '画面を録画中',\n exit: '終了',\n },\n popup: {\n about: '概要',\n placeholder: '問題や提案を入力してください...',\n submit: '送信',\n sending: '送信中...',\n cancel: 'キャンセル',\n close: '閉じる',\n my_feedback: '自分のフィードバック',\n screenshot_label: 'スクリーンショットを添付',\n screenshot_hint: 'パスワードなどの機密項目は自動的に隠されます。',\n screenshot_short: 'スクリーンショット',\n screenshot_locked_hint: '描き込みはスクリーンショット上に焼き込まれるため、必ず添付されます。機密項目は自動的に隠されます。',\n recording_label: '記録',\n recording_locked_hint: 'ページの表示と操作の再現記録で、必ず添付されます。カメラやマイクは含まれず、機密項目は自動的に隠されます。',\n quickOptions: [\n { emoji: '🐛', label: '不具合', value: '不具合があります' },\n { emoji: '💡', label: '提案', value: '提案があります' },\n { emoji: '🤔', label: '不明点', value: 'わかりにくいです' },\n ],\n },\n success: {\n title: 'フィードバックを受け取りました。ありがとうございます。',\n title_no_ai: 'ありがとうございます!フィードバックを受け付けました。',\n close: '閉じる',\n email_saved: '解決次第メールでお知らせします。',\n email_error: 'メールアドレスを保存できませんでした。もう一度お試しください。',\n track_feedback: '対応状況を確認',\n email_cta: '解決したらメールで知らせる',\n notify: '通知',\n },\n error: {\n submit: '送信に失敗しました。もう一度お試しください。',\n finalize_title: 'まだ送信できませんでした。',\n finalize_message: 'フィードバックは保存されています。もう一度お試しください。',\n },\n pet: {\n bubble: 'お困りですか?',\n },\n frustration: {\n prompt: 'うまくいかないことがありますか?お知らせください。',\n action: '注釈',\n dismiss: '閉じる',\n },\n annotation: {\n drawing: '描画',\n element: '要素',\n },\n draw: {\n note_placeholder: '描画にメモを追加',\n undo: '元に戻す',\n redo: 'やり直す',\n cancel: 'キャンセル',\n submit: '送信',\n },\n record: {\n stop: '停止',\n timer_label: '録画中',\n max_reached: '最大記録時間に達しました',\n replay_label: 'screen-recording.json',\n play: '録画を再生',\n close: '閉じる',\n preview_unavailable: 'プレビューは利用できません',\n },\n clarify: {\n title: 'Make This Better AI',\n subtitle: 'フィードバックを整えています',\n thinking: '考え中...',\n placeholder: '詳細を追加して送信',\n send: '送信',\n submit: '送信する',\n send_feedback: 'フィードバックを送信',\n skip: 'スキップして送信',\n skip_short: 'スキップ',\n cancel: 'キャンセル — 送信しない',\n retry: '再試行',\n error: '質問を読み込めませんでした。そのままフィードバックを送信できます。',\n expired: 'このフィードバックは送信される前に有効期限が切れました。ウィジェットを閉じて、もう一度送信してください。',\n },\n}\n","export default {\n tab: '피드백',\n toolbar: {\n markup: '마크업',\n record: '녹화',\n hint: '클릭하여 댓글, 드래그하여 그리기',\n hintTouch: '드래그해서 문제가 있는 곳을 표시하세요',\n hintRecord: '화면 녹화 중',\n exit: '나가기',\n },\n popup: {\n about: '정보',\n placeholder: '문제 또는 제안 사항을 설명해 주세요...',\n submit: '제출',\n sending: '전송 중...',\n cancel: '취소',\n close: '닫기',\n my_feedback: '내 피드백',\n screenshot_label: '스크린샷 첨부',\n screenshot_hint: '비밀번호 등 민감한 입력란은 자동으로 가려집니다.',\n screenshot_short: '스크린샷',\n screenshot_locked_hint: '그린 표시는 스크린샷 위에 합쳐지므로 항상 첨부됩니다. 민감한 입력란은 자동으로 가려집니다.',\n recording_label: '기록',\n recording_locked_hint: '페이지 화면과 조작 과정의 재현 기록으로, 항상 첨부됩니다. 카메라와 마이크는 포함되지 않으며 민감한 입력란은 자동으로 가려집니다.',\n quickOptions: [\n { emoji: '🐛', label: '오류', value: '문제가 있어요' },\n { emoji: '💡', label: '제안', value: '제안이 있어요' },\n { emoji: '🤔', label: '혼란', value: '헷갈려요' },\n ],\n },\n success: {\n title: '피드백이 접수되었습니다. 감사합니다.',\n title_no_ai: '감사합니다! 피드백을 접수했습니다.',\n close: '닫기',\n email_saved: '해결되면 이메일로 알려드립니다.',\n email_error: '이메일을 저장하지 못했습니다. 다시 시도해 주세요.',\n track_feedback: '처리 상황 보기',\n email_cta: '해결되면 이메일로 알림',\n notify: '알림',\n },\n error: {\n submit: '제출에 실패했습니다. 다시 시도해 주세요.',\n finalize_title: '아직 전송하지 못했습니다.',\n finalize_message: '피드백은 저장되어 있습니다. 다시 시도해 주세요.',\n },\n pet: {\n bubble: '도움이 필요하신가요?',\n },\n frustration: {\n prompt: '문제가 있으신가요? 알려 주세요.',\n action: '주석',\n dismiss: '닫기',\n },\n annotation: {\n drawing: '그리기',\n element: '요소',\n },\n draw: {\n note_placeholder: '그림에 메모 추가',\n undo: '실행 취소',\n redo: '다시 실행',\n cancel: '취소',\n submit: '보내기',\n },\n record: {\n stop: '중지',\n timer_label: '녹화 중',\n max_reached: '최대 녹화 시간에 도달했습니다',\n replay_label: 'screen-recording.json',\n play: '녹화 재생',\n close: '닫기',\n preview_unavailable: '미리보기를 사용할 수 없습니다',\n },\n clarify: {\n title: 'Make This Better AI',\n subtitle: '피드백을 다듬는 중',\n thinking: '생각 중...',\n placeholder: '자세히 적은 뒤 제출',\n send: '보내기',\n submit: '제출',\n send_feedback: '피드백 보내기',\n skip: '건너뛰고 보내기',\n skip_short: '건너뛰기',\n cancel: '취소 — 보내지 않기',\n retry: '재시도',\n error: '질문을 불러오지 못했습니다. 피드백을 그대로 보내세요.',\n expired: '이 피드백 세션은 전송되기 전에 만료되었습니다. 위젯을 닫고 다시 보내 주세요.',\n },\n}\n","export default {\n tab: 'Comentarios',\n toolbar: {\n markup: 'Anotar',\n record: 'Grabar',\n hint: 'Clic para comentar, arrastrar para dibujar',\n hintTouch: 'Arrastra para marcar el problema',\n hintRecord: 'Grabando pantalla',\n exit: 'Salir',\n },\n popup: {\n about: 'Acerca de',\n placeholder: 'Describe el problema o sugerencia...',\n submit: 'Enviar',\n sending: 'Enviando...',\n cancel: 'Cancelar',\n close: 'Cerrar',\n my_feedback: 'Mis comentarios',\n screenshot_label: 'Adjuntar captura de pantalla',\n screenshot_hint: 'Las contraseñas y otros campos sensibles se ocultan automáticamente.',\n screenshot_short: 'Captura',\n screenshot_locked_hint: 'Tu dibujo se integra en la captura, por eso siempre se adjunta. Los campos sensibles se ocultan automáticamente.',\n recording_label: 'Grabación',\n recording_locked_hint: 'Una reproducción de lo que mostraba la página y cómo se usó; siempre se adjunta. Sin cámara ni micrófono. Los campos sensibles se ocultan automáticamente.',\n quickOptions: [\n { emoji: '🐛', label: 'Error', value: 'Algo no funciona' },\n { emoji: '💡', label: 'Sugerencia', value: 'Tengo una sugerencia' },\n { emoji: '🤔', label: 'Confuso', value: 'Estoy confundido' },\n ],\n },\n success: {\n title: 'Gracias, hemos recibido tu comentario.',\n title_no_ai: '¡Gracias! Hemos recibido tu comentario.',\n close: 'Cerrar',\n email_saved: 'Recibirás un correo cuando se resuelva.',\n email_error: 'No se pudo guardar tu correo. Inténtalo de nuevo.',\n track_feedback: 'Seguir mi comentario',\n email_cta: 'Avísame por correo cuando se arregle',\n notify: 'Avisarme',\n },\n error: {\n submit: 'Error al enviar. Por favor, inténtalo de nuevo.',\n finalize_title: 'Aún no pudimos enviarlo.',\n finalize_message: 'Tu comentario sigue guardado. Inténtalo de nuevo.',\n },\n pet: {\n bubble: '¿Necesitas ayuda?',\n },\n frustration: {\n prompt: '¿Algo no funciona? Cuéntanos.',\n action: 'Anotar',\n dismiss: 'Cerrar',\n },\n annotation: {\n drawing: 'Dibujo',\n element: 'elemento',\n },\n draw: {\n note_placeholder: 'Añade una nota a tu dibujo',\n undo: 'Deshacer',\n redo: 'Rehacer',\n cancel: 'Cancelar',\n submit: 'Enviar',\n },\n record: {\n stop: 'Detener',\n timer_label: 'Grabando',\n max_reached: 'Tiempo máximo de grabación alcanzado',\n replay_label: 'screen-recording.json',\n play: 'Reproducir grabación',\n close: 'Cerrar',\n preview_unavailable: 'Vista previa no disponible',\n },\n clarify: {\n title: 'Make This Better AI',\n subtitle: 'Afinando tus comentarios',\n thinking: 'Pensando...',\n placeholder: 'Añade detalles y envía',\n send: 'Enviar',\n submit: 'Enviar',\n send_feedback: 'Enviar comentario',\n skip: 'Omitir y enviar',\n skip_short: 'Omitir',\n cancel: 'Cancelar — no enviar',\n retry: 'Reintentar',\n error: 'No se pudo cargar la pregunta. Envía tus comentarios tal cual.',\n expired: 'Esta sesión de comentarios caducó antes de poder enviarse. Cierra el widget y envíalo de nuevo.',\n },\n}\n","export default {\n tab: 'Avis',\n toolbar: {\n markup: 'Annoter',\n record: 'Enregistrer',\n hint: 'Cliquer pour commenter, glisser pour dessiner',\n hintTouch: 'Faites glisser pour marquer le problème',\n hintRecord: \"Enregistrement de l'écran\",\n exit: 'Quitter',\n },\n popup: {\n about: 'À propos',\n placeholder: 'Décrivez le problème ou votre suggestion...',\n submit: 'Envoyer',\n sending: 'Envoi en cours...',\n cancel: 'Annuler',\n close: 'Fermer',\n my_feedback: 'Mes avis',\n screenshot_label: 'Joindre une capture d\\'écran',\n screenshot_hint: 'Les mots de passe et autres champs sensibles sont masqués automatiquement.',\n screenshot_short: 'Capture',\n screenshot_locked_hint: \"Votre dessin est intégré à la capture, elle est donc toujours jointe. Les champs sensibles sont masqués automatiquement.\",\n recording_label: 'Enregistrement',\n recording_locked_hint: \"Une relecture de ce que la page affichait et de son utilisation — toujours jointe. Ni caméra ni micro. Les champs sensibles sont masqués automatiquement.\",\n quickOptions: [\n { emoji: '🐛', label: 'Bug', value: 'Quelque chose ne fonctionne pas' },\n { emoji: '💡', label: 'Suggestion', value: \"J'ai une suggestion\" },\n { emoji: '🤔', label: 'Confus', value: 'Je suis perplexe' },\n ],\n },\n success: {\n title: 'Merci, votre retour est bien arrivé.',\n title_no_ai: 'Merci ! Nous avons bien reçu votre avis.',\n close: 'Fermer',\n email_saved: 'Vous recevrez un e-mail une fois résolu.',\n email_error: \"Impossible d'enregistrer votre e-mail. Veuillez réessayer.\",\n track_feedback: 'Suivre mon retour',\n email_cta: 'M’avertir par e-mail une fois corrigé',\n notify: 'Avertir',\n },\n error: {\n submit: \"Échec de l'envoi. Veuillez réessayer.\",\n finalize_title: \"Nous n'avons pas encore pu l'envoyer.\",\n finalize_message: 'Votre retour est toujours enregistré. Veuillez réessayer.',\n },\n pet: {\n bubble: \"Besoin d'aide ?\",\n },\n frustration: {\n prompt: 'Un problème ? Dites-le-nous.',\n action: 'Annoter',\n dismiss: 'Fermer',\n },\n annotation: {\n drawing: 'Dessin',\n element: 'élément',\n },\n draw: {\n note_placeholder: 'Ajoutez une note à votre dessin',\n undo: 'Annuler',\n redo: 'Rétablir',\n cancel: 'Annuler',\n submit: 'Envoyer',\n },\n record: {\n stop: 'Arrêter',\n timer_label: 'Enregistrement',\n max_reached: \"Durée maximale d'enregistrement atteinte\",\n replay_label: 'screen-recording.json',\n play: \"Lire l'enregistrement\",\n close: 'Fermer',\n preview_unavailable: 'Aperçu indisponible',\n },\n clarify: {\n title: 'Make This Better AI',\n subtitle: 'Affinement de votre retour',\n thinking: 'Réflexion...',\n placeholder: 'Ajoutez des détails, puis envoyez',\n send: 'Envoyer',\n submit: 'Soumettre',\n send_feedback: 'Envoyer le retour',\n skip: 'Ignorer et envoyer',\n skip_short: 'Ignorer',\n cancel: 'Annuler — ne pas envoyer',\n retry: 'Réessayer',\n error: 'Impossible de charger la question. Envoyez votre avis tel quel.',\n expired: 'Cette session de retour a expiré avant d\\'avoir pu être envoyée. Fermez le widget et renvoyez-la.',\n },\n}\n","export default {\n tab: 'Feedback',\n toolbar: {\n markup: 'Markieren',\n record: 'Aufnehmen',\n hint: 'Klicken zum Kommentieren, ziehen zum Zeichnen',\n hintTouch: 'Ziehen, um das Problem zu markieren',\n hintRecord: 'Bildschirm wird aufgenommen',\n exit: 'Beenden',\n },\n popup: {\n about: 'Info',\n placeholder: 'Beschreiben Sie das Problem oder Ihren Vorschlag...',\n submit: 'Absenden',\n sending: 'Wird gesendet...',\n cancel: 'Abbrechen',\n close: 'Schließen',\n my_feedback: 'Mein Feedback',\n screenshot_label: 'Screenshot anhängen',\n screenshot_hint: 'Passwörter und andere sensible Felder werden automatisch abgedeckt.',\n screenshot_short: 'Screenshot',\n screenshot_locked_hint: 'Deine Zeichnung wird in den Screenshot eingebrannt, daher wird er immer angehängt. Sensible Felder werden automatisch abgedeckt.',\n recording_label: 'Aufzeichnung',\n recording_locked_hint: 'Eine Wiedergabe dessen, was die Seite zeigte und wie sie bedient wurde — immer angehängt. Keine Kamera, kein Mikrofon. Sensible Felder werden automatisch abgedeckt.',\n quickOptions: [\n { emoji: '🐛', label: 'Fehler', value: 'Etwas funktioniert nicht' },\n { emoji: '💡', label: 'Vorschlag', value: 'Ich habe einen Vorschlag' },\n { emoji: '🤔', label: 'Unklar', value: 'Ich bin verwirrt' },\n ],\n },\n success: {\n title: 'Danke, Ihr Feedback ist angekommen.',\n title_no_ai: 'Vielen Dank! Wir haben Ihr Feedback erhalten.',\n close: 'Schließen',\n email_saved: 'Sie erhalten eine E-Mail, sobald es gelöst ist.',\n email_error: 'E-Mail konnte nicht gespeichert werden. Bitte versuchen Sie es erneut.',\n track_feedback: 'Feedback verfolgen',\n email_cta: 'E-Mail, sobald es behoben ist',\n notify: 'Melden',\n },\n error: {\n submit: 'Senden fehlgeschlagen. Bitte versuchen Sie es erneut.',\n finalize_title: 'Wir konnten dies noch nicht senden.',\n finalize_message: 'Ihr Feedback ist weiterhin gespeichert. Bitte versuchen Sie es erneut.',\n },\n pet: {\n bubble: 'Brauchen Sie Hilfe?',\n },\n frustration: {\n prompt: 'Etwas funktioniert nicht? Sagen Sie es uns.',\n action: 'Kommentieren',\n dismiss: 'Schließen',\n },\n annotation: {\n drawing: 'Zeichnung',\n element: 'Element',\n },\n draw: {\n note_placeholder: 'Fügen Sie Ihrer Zeichnung eine Notiz hinzu',\n undo: 'Rückgängig',\n redo: 'Wiederholen',\n cancel: 'Abbrechen',\n submit: 'Absenden',\n },\n record: {\n stop: 'Stopp',\n timer_label: 'Aufnahme',\n max_reached: 'Maximale Aufnahmedauer erreicht',\n replay_label: 'screen-recording.json',\n play: 'Aufnahme abspielen',\n close: 'Schließen',\n preview_unavailable: 'Vorschau nicht verfügbar',\n },\n clarify: {\n title: 'Make This Better AI',\n subtitle: 'Wir schärfen dein Feedback',\n thinking: 'Einen Moment...',\n placeholder: 'Details ergänzen, dann absenden',\n send: 'Senden',\n submit: 'Absenden',\n send_feedback: 'Feedback senden',\n skip: 'Überspringen & senden',\n skip_short: 'Überspringen',\n cancel: 'Abbrechen — nicht senden',\n retry: 'Erneut versuchen',\n error: 'Die Frage konnte nicht geladen werden. Senden Sie Ihr Feedback einfach so.',\n expired: 'Diese Feedback-Sitzung ist abgelaufen, bevor sie gesendet werden konnte. Bitte schließe das Widget und sende erneut.',\n },\n}\n","import en from './en'\nimport zhCN from './zh-CN'\nimport ja from './ja'\nimport ko from './ko'\nimport es from './es'\nimport fr from './fr'\nimport de from './de'\n\nexport type I18nMessages = typeof en\n\nconst locales: Record<string, I18nMessages> = {\n en,\n 'zh-CN': zhCN,\n ja,\n ko,\n es,\n fr,\n de,\n}\n\nexport function getMessages(locale: string): I18nMessages {\n if (locales[locale]) return locales[locale]\n const lang = locale.split('-')[0]\n if (locales[lang]) return locales[lang]\n return locales['en']\n}\n","import type { ShadowContainer } from '../widget/shadow'\nimport { BLOCK_CLASS, MASK_TEXT_CLASS } from '../privacy/dom'\nimport { filterInputValue } from '../privacy/fields'\nimport { FILTERED_VALUE, filterSensitiveValue } from '../privacy/sanitize'\nimport {\n createSensitiveTextContext,\n hasSensitiveTextContext,\n type SensitiveTextContext,\n} from '../privacy/text-context'\n\ntype RRWebEvent = Record<string, unknown>\ntype StopFn = () => void\n\ninterface RRWebRecordOptions {\n emit: (event: RRWebEvent) => void\n maskAllInputs: boolean\n maskInputOptions: Record<string, boolean>\n maskInputFn: (text: string, element: HTMLElement) => string\n maskTextFn: () => string\n blockClass: string\n maskTextClass: string\n}\n\ninterface RRWebRecordFn {\n (options: RRWebRecordOptions): StopFn\n addCustomEvent: (tag: string, payload: Record<string, unknown>) => void\n mirror: { getNode: (id: number) => Node | null }\n}\n\nexport interface RecordingResult {\n events: RRWebEvent[]\n duration: number\n}\n\nconst MAX_DURATION_S = 60\nconst VALUE_ATTRIBUTE_NAMES = ['value', 'content'] as const\nconst MASKED_INPUT_TYPES = Object.fromEntries([\n 'button',\n 'checkbox',\n 'color',\n 'date',\n 'datetime-local',\n 'email',\n 'file',\n 'hidden',\n 'image',\n 'month',\n 'number',\n 'password',\n 'radio',\n 'range',\n 'reset',\n 'search',\n 'select',\n 'submit',\n 'tel',\n 'text',\n 'textarea',\n 'time',\n 'url',\n 'week',\n].map(type => [type, true]))\n// The marker classes live with the rest of the privacy policy in privacy/dom;\n// re-exported here because this module has been their documented import point\n// since 1.x.\nexport { BLOCK_CLASS, MASK_TEXT_CLASS }\n// Pinned version + SRI: this script executes on customers' pages, so a\n// floating tag or unverified content would be a supply-chain hole. Bumping\n// the version requires recomputing the hash from the exact CDN file:\n// curl -sL <url> | openssl dgst -sha384 -binary | openssl base64 -A\n// Must stay on @rrweb/record's static /umd/ build: the `rrweb` package ships\n// no UMD in 2.x (its dist files are ESM and throw \"Unexpected token 'export'\"\n// in a classic script tag), and jsDelivr's auto-minified files are unsafe for\n// SRI because their hash changes when jsDelivr upgrades its minifier.\nconst RRWEB_CDN = 'https://cdn.jsdelivr.net/npm/@rrweb/record@2.1.0/umd/record.min.js'\nconst RRWEB_SRI = 'sha384-MrD66HBNSykaP2N95+6hQCFlF5oH2tvL3TD/zyvHNkP/sAFWZx98DX9MEDy8MdVT'\n\nlet rrwebLoaded: Promise<RRWebRecordFn> | null = null\n\n// The IIFE/CDN bundle runs as a classic script, where the bare\n// import('@rrweb/record') can never resolve — without this flag every CDN\n// user's first recording would pay a thrown TypeError (console noise on the\n// customer's page) plus a wasted round before the CDN fallback. The typeof\n// guard keeps the untransformed source (vitest) on the ESM path.\nfunction isIifeBuild(): boolean {\n return typeof __MTB_IIFE__ !== 'undefined' && __MTB_IIFE__\n}\n\nfunction loadRrweb(): Promise<RRWebRecordFn> {\n if (rrwebLoaded) return rrwebLoaded\n const load = isIifeBuild() ? loadViaCdn() : loadViaImport().catch(() => loadViaCdn())\n // A rejection must not stay cached: one flaky-network failure would otherwise\n // make every later Record tap fail instantly until the page reloads. Same\n // reset-on-failure as record/replay.ts.\n rrwebLoaded = load.catch((err) => {\n rrwebLoaded = null\n throw err\n })\n return rrwebLoaded\n}\n\nfunction filterReplayEvent(event: RRWebEvent, record: RRWebRecordFn): RRWebEvent {\n const filtered = filterSensitiveValue(event)\n filterReplayDomValues(filtered, record, createSensitiveTextContext())\n return filtered\n}\n\nfunction filterReplayDomValues(\n value: unknown,\n record: RRWebRecordFn,\n textContext: SensitiveTextContext,\n): void {\n if (Array.isArray(value)) {\n value.forEach(item => filterReplayDomValues(item, record, textContext))\n return\n }\n if (!isRecord(value)) return\n\n const attributes = isRecord(value.attributes) ? value.attributes : null\n const hasAttributeValue = attributes && VALUE_ATTRIBUTE_NAMES.some(name => typeof attributes[name] === 'string')\n const hasNodeValue = typeof value.text === 'string'\n || typeof value.textContent === 'string'\n || typeof value.value === 'string'\n || hasAttributeValue\n const node = hasNodeValue && typeof value.id === 'number' ? record.mirror.getNode(value.id) : null\n if (node instanceof Element) {\n if (typeof value.text === 'string') value.text = filterInputValue(value.text, node)\n if (attributes) {\n for (const name of VALUE_ATTRIBUTE_NAMES) {\n const attributeValue = attributes[name]\n if (typeof attributeValue === 'string') attributes[name] = filterInputValue(attributeValue, node)\n }\n }\n } else if (node?.nodeType === Node.TEXT_NODE && hasSensitiveTextContext(node, textContext)) {\n if (typeof value.textContent === 'string') value.textContent = FILTERED_VALUE\n if (typeof value.value === 'string') value.value = FILTERED_VALUE\n }\n\n Object.values(value).forEach(item => filterReplayDomValues(item, record, textContext))\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === 'object' && !Array.isArray(value)\n}\n\nasync function loadViaImport(): Promise<RRWebRecordFn> {\n const mod = await import('@rrweb/record')\n return mod.record as unknown as RRWebRecordFn\n}\n\ninterface RRWebGlobals {\n rrwebRecord?: { record: RRWebRecordFn }\n rrweb?: { record: RRWebRecordFn }\n}\n\nfunction findGlobalRecord(): RRWebRecordFn | undefined {\n const win = globalThis as unknown as Window & RRWebGlobals\n return win.rrwebRecord?.record ?? win.rrweb?.record\n}\n\nfunction loadViaCdn(): Promise<RRWebRecordFn> {\n return new Promise((resolve, reject) => {\n const existing = findGlobalRecord()\n if (existing) { resolve(existing); return }\n const script = document.createElement('script')\n script.src = RRWEB_CDN\n script.integrity = RRWEB_SRI\n script.crossOrigin = 'anonymous'\n script.onload = () => {\n const record = findGlobalRecord()\n if (record) resolve(record)\n else reject(new Error('rrweb record global not found after script load'))\n }\n script.onerror = () => reject(new Error('Failed to load rrweb from CDN'))\n document.head.appendChild(script)\n })\n}\n\nexport class RecordSession {\n private events: RRWebEvent[] = []\n private stopFn: StopFn | null = null\n private startedAt = 0\n private timer: ReturnType<typeof setTimeout> | null = null\n private clickHandler: ((e: MouseEvent) => void) | null = null\n private shadow: ShadowContainer\n private onMaxReached?: () => void\n private stopped = false\n private destroyed = false\n\n constructor(shadow: ShadowContainer, onMaxReached?: () => void) {\n this.shadow = shadow\n this.onMaxReached = onMaxReached\n }\n\n async start(): Promise<void> {\n const record = await loadRrweb()\n\n // A cold dynamic import takes hundreds of milliseconds, and the reporter can\n // cancel the replay inside that window. Without this guard the resumed start()\n // would attach a recorder, a document click listener and a 60s timer that no\n // live object holds a handle to — rrweb would then serialize every mutation of\n // the host page for the rest of the session, after an explicit cancel.\n if (this.destroyed) return\n\n this.startedAt = Date.now()\n this.stopped = false\n\n this.stopFn = record({\n emit: (event: RRWebEvent) => {\n if (this.stopped) return\n try {\n this.events.push(filterReplayEvent(event, record))\n } catch {\n // A malformed host event must not bypass filtering or stop recording.\n }\n },\n // Explicit options include hidden/file, which rrweb's maskAllInputs=true\n // preset omits. Every value is routed through the fixed policy, where\n // ordinary values are returned unchanged.\n maskAllInputs: false,\n maskInputOptions: MASKED_INPUT_TYPES,\n maskInputFn: (text, element) => (\n element.closest(`.${MASK_TEXT_CLASS}`) ? FILTERED_VALUE : filterInputValue(text, element)\n ),\n maskTextFn: () => FILTERED_VALUE,\n blockClass: BLOCK_CLASS,\n maskTextClass: MASK_TEXT_CLASS,\n })\n\n this.clickHandler = (e: MouseEvent) => {\n this.showClickRipple(e.clientX, e.clientY)\n record.addCustomEvent?.('mtb-click', {\n x: e.clientX,\n y: e.clientY,\n target: e.target instanceof HTMLElement ? e.target.tagName : '',\n })\n }\n document.addEventListener('click', this.clickHandler, true)\n\n this.timer = setTimeout(() => {\n this.onMaxReached?.()\n }, MAX_DURATION_S * 1000)\n }\n\n stop(): RecordingResult {\n this.stopped = true\n this.stopFn?.()\n this.stopFn = null\n this.cleanupTimer()\n this.cleanupClickHandler()\n\n return {\n events: this.events,\n duration: this.getDuration(),\n }\n }\n\n getDuration(): number {\n if (this.startedAt === 0) return 0\n return Math.max(1, Math.round((Date.now() - this.startedAt) / 1000))\n }\n\n destroy(): void {\n this.destroyed = true\n this.stopped = true\n this.stopFn?.()\n this.stopFn = null\n this.cleanupTimer()\n this.cleanupClickHandler()\n this.events = []\n this.startedAt = 0\n }\n\n private cleanupTimer(): void {\n if (this.timer) {\n clearTimeout(this.timer)\n this.timer = null\n }\n }\n\n private cleanupClickHandler(): void {\n if (this.clickHandler) {\n document.removeEventListener('click', this.clickHandler, true)\n this.clickHandler = null\n }\n }\n\n private showClickRipple(x: number, y: number): void {\n const ripple = this.shadow.el<HTMLDivElement>('div', 'mtb-click-ripple')\n ripple.style.left = `${x}px`\n ripple.style.top = `${y}px`\n this.shadow.append(ripple)\n ripple.addEventListener('animationend', () => ripple.remove())\n }\n}\n","import type { ShadowContainer } from '../widget/shadow'\nimport type { I18nMessages } from '../i18n'\nimport { escapeHtml } from '../context/dom-utils'\n\nexport class RecordControlBar {\n private el: HTMLDivElement\n private timerEl!: HTMLSpanElement\n private intervalId: ReturnType<typeof setInterval> | null = null\n private getDuration: () => number\n\n constructor(\n shadow: ShadowContainer,\n messages: I18nMessages,\n getDuration: () => number,\n onStop: () => void,\n ) {\n this.getDuration = getDuration\n\n this.el = shadow.el<HTMLDivElement>('div', 'mtb-record-bar')\n this.el.innerHTML = `\n <span class=\"mtb-record-dot\"></span>\n <span class=\"mtb-record-timer\">00:00</span>\n <button class=\"mtb-record-stop\" type=\"button\">${escapeHtml(messages.record.stop)}</button>\n `\n shadow.append(this.el)\n\n this.timerEl = this.el.querySelector('.mtb-record-timer')!\n this.el.querySelector('.mtb-record-stop')!.addEventListener('click', onStop)\n\n this.intervalId = setInterval(() => this.updateTimer(), 1000)\n }\n\n private updateTimer(): void {\n const secs = this.getDuration()\n const mm = String(Math.floor(secs / 60)).padStart(2, '0')\n const ss = String(secs % 60).padStart(2, '0')\n this.timerEl.textContent = `${mm}:${ss}`\n }\n\n destroy(): void {\n if (this.intervalId) {\n clearInterval(this.intervalId)\n this.intervalId = null\n }\n this.el.remove()\n }\n}\n","import { RecordSession } from '../record/session'\nimport type { RecordingResult } from '../record/session'\nimport { RecordControlBar } from '../record/control-bar'\nimport type { ShadowContainer } from './shadow'\nimport type { I18nMessages } from '../i18n'\n\nexport type { RecordingResult }\n\nexport class RecordingManager {\n private session: RecordSession | null = null\n private bar: RecordControlBar | null = null\n private result: RecordingResult | null = null\n\n async start(\n shadow: ShadowContainer,\n messages: I18nMessages,\n onStop: () => void,\n ): Promise<void> {\n this.session = new RecordSession(shadow, onStop)\n this.bar = new RecordControlBar(\n shadow,\n messages,\n () => this.session?.getDuration() ?? 0,\n onStop,\n )\n await this.session.start()\n }\n\n stop(): RecordingResult | null {\n if (!this.session) return null\n this.result = this.session.stop()\n this.teardownUI()\n return this.result\n }\n\n getRecording(): RecordingResult | null {\n return this.result\n }\n\n destroy(): void {\n this.teardownUI()\n this.result = null\n }\n\n private teardownUI(): void {\n this.session?.destroy()\n this.session = null\n this.bar?.destroy()\n this.bar = null\n }\n}\n","import { sanitizeUrl } from '../privacy/sanitize'\n\nexport interface PageContext {\n page_url: string\n user_agent: string\n browser: string\n os: string\n screen_width: number\n screen_height: number\n}\n\nexport function collectPageContext(): PageContext {\n return {\n page_url: sanitizeUrl(window.location.href),\n user_agent: navigator.userAgent,\n browser: detectBrowser(),\n os: detectOS(),\n screen_width: window.screen.width,\n screen_height: window.screen.height,\n }\n}\n\nfunction detectBrowser(): string {\n const ua = navigator.userAgent\n if (/Edg\\//.test(ua)) return `Edge ${ua.match(/Edg\\/(\\S+)/)?.[1] ?? ''}`\n if (/OPR\\//.test(ua)) return `Opera ${ua.match(/OPR\\/(\\S+)/)?.[1] ?? ''}`\n if (/Chrome\\//.test(ua)) return `Chrome ${ua.match(/Chrome\\/(\\S+)/)?.[1] ?? ''}`\n if (/Safari\\//.test(ua) && /Version\\//.test(ua)) return `Safari ${ua.match(/Version\\/(\\S+)/)?.[1] ?? ''}`\n if (/Firefox\\//.test(ua)) return `Firefox ${ua.match(/Firefox\\/(\\S+)/)?.[1] ?? ''}`\n return 'Unknown'\n}\n\nfunction detectOS(): string {\n const ua = navigator.userAgent\n if (/Windows NT 10/.test(ua)) return 'Windows 10/11'\n if (/Windows NT/.test(ua)) return 'Windows'\n if (/Mac OS X 10_15|macOS 1[56789]/.test(ua)) return 'macOS'\n if (/Mac OS X/.test(ua)) return 'macOS'\n if (/Android/.test(ua)) return `Android ${ua.match(/Android (\\d+)/)?.[1] ?? ''}`\n if (/iPhone|iPad|iPod/.test(ua)) return 'iOS'\n if (/Linux/.test(ua)) return 'Linux'\n return 'Unknown'\n}\n","import type { PageContext } from '../context/collector'\nimport { filterSensitiveValue, sanitizeUrl } from '../privacy/sanitize'\nimport type { Annotation, Breadcrumb, FeedbackPayload, MakeThisBetterConfig } from '../types'\nimport type { RecordingResult } from '../record/session'\n\nexport interface BuildPayloadOptions {\n description: string\n pageContext: PageContext\n consoleErrors: string[]\n annotation: Annotation | null\n user: MakeThisBetterConfig['user']\n recording?: RecordingResult\n anonId?: string\n anonEmail?: string\n breadcrumbs?: Breadcrumb[]\n}\n\nexport function buildPayload(options: BuildPayloadOptions): FeedbackPayload {\n const { description, pageContext, consoleErrors, annotation, user, recording, anonId, anonEmail, breadcrumbs } = options\n\n const capturedContext = filterSensitiveValue({\n description,\n ...pageContext,\n page_url: sanitizeUrl(pageContext.page_url),\n console_errors: consoleErrors,\n annotations: annotation ? [annotation] : [],\n target_element: annotation?.targetSelector\n ? {\n selector: annotation.targetSelector,\n text: annotation.targetText ?? '',\n name: annotation.targetName ?? '',\n }\n : undefined,\n recording_events: recording?.events,\n recording_duration: recording?.duration,\n breadcrumbs,\n })\n\n return {\n ...capturedContext,\n user_id: user?.id,\n user_email: user?.email,\n user_name: user?.name,\n // An identified user carries no anonymous identity: the host has vouched\n // for who the reporter is, so attaching the stored anon id/email would tie\n // that account to a stranger's earlier anonymous submissions.\n reporter_external_id: user ? undefined : anonId,\n reporter_email: user ? undefined : anonEmail,\n }\n}\n","import { bakeAnnotatedScreenshot, captureBaseScreenshot, warmFontEmbedCss } from '../screenshot/capture'\nimport { collectPageContext } from '../context/collector'\nimport { buildPayload } from './payload'\nimport { getAnonId, getStoredReporterEmail } from './anon-id'\nimport type { ApiClient } from '../api/client'\nimport type { ConsoleErrorCollector } from '../context/console'\nimport type { BreadcrumbCollector } from '../context/breadcrumbs'\nimport type { ShadowContainer } from './shadow'\nimport type { RecordingResult } from './recording-manager'\nimport type { WidgetBrandColors } from './brand-colors'\nimport type { getMessages } from '../i18n'\nimport type { MakeThisBetterConfig, Annotation, SubmissionSessionResponse, FeedbackResponse } from '../types'\n\nconst MIN_FINALIZE_LOADING_MS = 300\nexport const SCREENSHOT_WAIT_MS = 5_000\n\n/**\n * Mode/UI transitions the lifecycle needs from its owner. The controller owns\n * mode and the launcher tab; the lifecycle only reports when a submission\n * outcome forces a transition, so \"which UI is on screen\" stays decided in one\n * place.\n */\nexport interface SubmissionLifecycleHost {\n /** True once the conversation has transitioned to its final receipt state. */\n isBackground(): boolean\n /** Replace the current conversation receipt with its retryable failure state. */\n onFinalizeFailure(onRetry?: () => void): void\n /** Return to the background-finalizing state ahead of a retry. */\n onReenterBackground(): void\n /** A finalize completed: hand off the feedback and show success. */\n onFinalized(feedback: FeedbackResponse, submission: SubmissionSessionResponse): void\n}\n\n/**\n * Everything the submission pipeline reads from the widget. Getters where the\n * value can change after construction (locale swaps messages; shadow only\n * exists once startup succeeded).\n */\nexport interface SubmissionLifecycleDeps {\n apiClient: ApiClient\n consoleCollector: ConsoleErrorCollector\n breadcrumbCollector: BreadcrumbCollector\n brandColors: WidgetBrandColors | undefined\n shadow: () => ShadowContainer\n messages: () => ReturnType<typeof getMessages>\n user: () => MakeThisBetterConfig['user']\n}\n\n/**\n * The submission concurrency state machine: attempt versioning, stale-request\n * abandonment, the foreground/background finalize dual path, and the failure\n * card's retry loop. Extracted so the controller's mode/UI orchestration and\n * this bookkeeping cannot silently interleave.\n */\nexport class SubmissionLifecycle {\n private activeSubmission: SubmissionSessionResponse | null = null\n private pendingSubmissionRequest: Promise<SubmissionSessionResponse | null> | null = null\n private submissionAttempt = 0\n private pendingScreenshotTask: Promise<void> | null = null\n private screenshotAbortController: AbortController | null = null\n private screenshotEnabled = true\n\n constructor(\n private deps: SubmissionLifecycleDeps,\n private host: SubmissionLifecycleHost,\n ) {}\n\n /**\n * What the note surface must render. Reading it back rather than assuming\n * the default is what keeps the checkbox from claiming a screenshot is\n * attached while this object has already been told otherwise — the popup is\n * remounted mid-session (record mode returns through it), so \"freshly opened\"\n * does not imply \"freshly consented\".\n */\n isScreenshotEnabled(): boolean {\n return this.screenshotEnabled\n }\n\n /**\n * Back to the default for a fresh report. Unlike setScreenshotEnabled(true)\n * this starts no capture: the widget is idle at both call sites, and\n * rasterizing a page nobody is annotating would be work done for a report\n * that may never be written.\n */\n resetScreenshotConsent(): void {\n this.screenshotEnabled = true\n }\n\n /**\n * Consent, as reported by the note surface's checkbox.\n *\n * Turning it off prevents capture at submit. Turning it back on warms the\n * lazy screenshot dependency while the reporter is still composing.\n */\n setScreenshotEnabled(enabled: boolean): void {\n this.screenshotEnabled = enabled\n if (enabled) {\n this.warmScreenshotDeps()\n return\n }\n this.screenshotAbortController?.abort()\n }\n\n // Load the screenshot dependency and font CSS ahead of submit. Bitmap capture\n // starts alongside Session creation; only cross-origin fallback waits for it.\n warmScreenshotDeps(): void {\n if (!this.screenshotEnabled) return\n void warmFontEmbedCss()\n }\n\n start(\n description: string,\n annotation: Annotation | null,\n recording: RecordingResult | undefined,\n ): Promise<SubmissionSessionResponse | null> {\n const submissionAttempt = ++this.submissionAttempt\n const request = this.createSubmission(description, annotation, recording, submissionAttempt)\n this.pendingSubmissionRequest = request\n return request\n }\n\n private async createSubmission(\n description: string,\n annotation: Annotation | null,\n recording: RecordingResult | undefined,\n submissionAttempt: number,\n ): Promise<SubmissionSessionResponse | null> {\n await waitForPaint()\n if (submissionAttempt !== this.submissionAttempt) return null\n const pageContext = collectPageContext()\n\n const payload = buildPayload({\n description,\n pageContext,\n consoleErrors: this.deps.consoleCollector.getErrors(),\n annotation,\n user: this.deps.user(),\n recording,\n anonId: getAnonId(),\n anonEmail: getStoredReporterEmail(),\n breadcrumbs: this.deps.breadcrumbCollector.getBreadcrumbs(),\n })\n\n // Create the Session and capture concurrently. Directly readable resources\n // can finish without the Session; only proxy fallback and upload await it.\n let submissionRequest: Promise<SubmissionSessionResponse>\n try {\n submissionRequest = this.deps.apiClient.createSubmissionSession(payload, null)\n } catch {\n return null\n }\n\n let screenshotController: AbortController | null = null\n if (this.screenshotEnabled) {\n const controller = new AbortController()\n screenshotController = controller\n this.screenshotAbortController?.abort()\n this.screenshotAbortController = controller\n const screenshotTask = (async () => {\n try {\n const resolver = async (resourceUrl: string, contentType?: string) => {\n const submission = await submissionRequest\n if (controller.signal.aborted || submissionAttempt !== this.submissionAttempt) return undefined\n return this.deps.apiClient.resolveScreenshotResourceUrl(\n submission.id,\n submission.token,\n resourceUrl,\n contentType,\n controller.signal,\n )\n }\n const base = await captureBaseScreenshot(resolver, controller.signal)\n if (controller.signal.aborted || submissionAttempt !== this.submissionAttempt || !base) return\n const screenshot = await bakeAnnotatedScreenshot(base, annotation ? [annotation] : [], this.deps.brandColors)\n if (controller.signal.aborted || submissionAttempt !== this.submissionAttempt || !screenshot) return\n const submission = await submissionRequest\n if (controller.signal.aborted || submissionAttempt !== this.submissionAttempt) return\n await this.deps.apiClient.uploadScreenshot(submission.id, submission.token, screenshot, controller.signal)\n } catch { /* screenshot is best-effort */ }\n })()\n const trackedTask = screenshotTask.finally(() => {\n if (this.pendingScreenshotTask === trackedTask) this.pendingScreenshotTask = null\n if (this.screenshotAbortController === controller) this.screenshotAbortController = null\n })\n this.pendingScreenshotTask = trackedTask\n }\n\n let submission: SubmissionSessionResponse\n try {\n submission = await submissionRequest\n if (submissionAttempt !== this.submissionAttempt) {\n screenshotController?.abort()\n await this.deps.apiClient.abandonSubmissionSession(submission.id, submission.token).catch(() => {})\n return null\n }\n this.activeSubmission = submission\n } catch {\n screenshotController?.abort()\n return null\n }\n\n return submission\n }\n\n async finalize(): Promise<void> {\n const submissionAttempt = this.submissionAttempt\n // Consent is per submission and this one is decided — its screenshot was\n // captured (or declined) at session creation. Resetting here, when the\n // receipt renders, rather than when the finalize round trip lands, means a\n // reporter who starts their next report immediately gets a fresh default\n // instead of inheriting a decline through the in-flight window.\n this.resetScreenshotConsent()\n // The conversation has become a receipt on the skip path. A finalization\n // failure replaces that receipt with an in-card retry state.\n const background = this.host.isBackground()\n // Finalize can be asked for (skip button) while the session upload is still\n // in flight — wait for it rather than failing a healthy submission.\n const submission = this.activeSubmission ?? await (this.pendingSubmissionRequest ?? Promise.resolve(null))\n if (submissionAttempt !== this.submissionAttempt) return\n if (!submission) {\n if (background) return this.showFinalizeFailure({ canRetry: false })\n throw new Error('No active submission session')\n }\n\n await this.waitForScreenshot()\n if (submissionAttempt !== this.submissionAttempt) return\n\n // The loading floor protects the clarify card's busy state; on the\n // background path the card is gone, so waiting would only delay the result.\n const minimumLoading = background\n ? Promise.resolve()\n : new Promise<void>((resolve) => setTimeout(resolve, MIN_FINALIZE_LOADING_MS))\n let feedback: FeedbackResponse\n try {\n feedback = await this.deps.apiClient.finalizeSubmissionSession(submission.id, submission.token)\n } catch (error) {\n if (background) return this.showFinalizeFailure({ canRetry: true })\n throw error\n }\n await Promise.all([minimumLoading, waitForPaint()])\n if (submissionAttempt !== this.submissionAttempt || this.activeSubmission !== submission) return\n\n this.activeSubmission = null\n this.pendingSubmissionRequest = null\n this.host.onFinalized(feedback, submission)\n }\n\n private async waitForScreenshot(): Promise<void> {\n const task = this.pendingScreenshotTask\n if (!task) return\n\n let timer: ReturnType<typeof setTimeout> | undefined\n const completed = await Promise.race([\n task.then(() => true),\n new Promise<false>((resolve) => {\n timer = setTimeout(() => resolve(false), SCREENSHOT_WAIT_MS)\n }),\n ])\n if (timer !== undefined) clearTimeout(timer)\n if (!completed) {\n this.screenshotAbortController?.abort()\n if (this.pendingScreenshotTask === task) this.pendingScreenshotTask = null\n }\n }\n\n private showFinalizeFailure(options: {canRetry: boolean}): void {\n this.host.onFinalizeFailure(options.canRetry ? () => this.retryBackgroundFinalization() : undefined)\n }\n\n private retryBackgroundFinalization(): void {\n // Re-enter the background state so another failure comes back to this card\n // rather than throwing with nothing on screen.\n this.host.onReenterBackground()\n void this.finalize().catch(() => this.showFinalizeFailure({canRetry: true}))\n }\n\n /**\n * Invalidate every in-flight attempt and abandon what already reached the\n * server. Both exit and destroy route through here.\n */\n abandon(): void {\n this.submissionAttempt += 1\n this.screenshotAbortController?.abort()\n this.screenshotAbortController = null\n this.pendingScreenshotTask = null\n // Consent is per submission, not per visitor. A reporter who declined once\n // on a page with something private on it is not declining for every report\n // they ever file, and a team silently receiving no screenshots forever\n // would have no way to notice why.\n //\n // This covers the exit path only. A successful submit never routes through\n // exitAll, so the controller resets there as well.\n this.resetScreenshotConsent()\n const submission = this.activeSubmission\n this.activeSubmission = null\n this.pendingSubmissionRequest = null\n if (submission) {\n void this.deps.apiClient.abandonSubmissionSession(submission.id, submission.token).catch(() => {})\n }\n }\n}\n\nfunction waitForPaint(): Promise<void> {\n return new Promise<void>((r) => requestAnimationFrame(() => setTimeout(r, 0)))\n}\n","import { isTouchPointer } from './pointer-kind'\nimport type { ToolbarMode } from '../annotate/toolbar'\nimport type { TargetRect } from '../types'\n\nexport type ModeKind = 'idle' | 'annotating' | 'recording' | 'drawing' | 'popup' | 'clarifying' | 'submitting'\n\nexport interface NotePopupOptions {\n x: number\n y: number\n targetName: string\n targetRect?: TargetRect\n}\n\n/**\n * The controller-side verbs a mode may perform. Each verb owns one concrete\n * mount, unmount, or hand-off; the states own which verbs run for which input\n * in which mode. Kept as an interface (implemented by WidgetController) so the\n * coupling between the machine and its resources is written down in one place.\n */\nexport interface ModeHost {\n transitionTo(next: ModeState): void\n exitAll(): void\n dismissFrustrationPrompt(): void\n /** True once the annotation toolbar is mounted — the flow chrome exists. */\n hasAnnotationChrome(): boolean\n /** First entry into markup: font warmup, toolbar, drawing surface, mobile strip. */\n mountAnnotationChrome(): void\n ensureSession(): void\n destroySession(): void\n clearCurrentAnnotation(): void\n teardownDrawBar(): void\n destroyRecordingManager(): void\n startRecorder(): void\n stopRecordingIntoNote(): void\n cancelDraw(): void\n mountDrawBar(): void\n mountDrawSheet(): void\n refreshDrawBar(): void\n mountNotePopup(opts: NotePopupOptions): void\n /** Abandon the note popup without ending the flow: surface, scrim and scroll lock come down, annotating resumes. */\n dismissNotePopup(): void\n /** Dismiss the success card from the previous submission. */\n dismissSuccess(): void\n revertToolbarDuringClarify(): void\n releaseClarifyUi(): void\n}\n\n/**\n * One object per widget mode. The controller holds a single `state` reference\n * and delegates every mode-dependent input here; transitions run through\n * `transitionTo(next)`, which calls the outgoing state's exit() before the\n * incoming state's enter().\n *\n * enter() mounts what the mode itself needs. exit() is deliberately thin: the\n * toolbar and the annotation session span the whole flow (annotating through\n * clarifying), and several out-edges hand a resource to the next mode instead\n * of releasing it — the note popup's element becomes the clarify card, a\n * stopped recording becomes the submit payload. Those edges release or hand\n * off through their own named paths (cancelDraw, the submit teardown, exitAll),\n * and exitAll remains the universal release every abandon route ends in.\n */\nexport interface ModeState {\n readonly kind: ModeKind\n /** Whether the docked launcher renders lit while this mode is active. */\n readonly launcherLit: boolean\n /** Whether a frustration signal may surface its prompt in this mode. */\n readonly acceptsFrustrationPrompt: boolean\n enter(): void\n exit(): void\n /** Public API open(). Only idle acts; while open it is an idempotent no-op. */\n handleOpen(): void\n handleClose(): void\n handleEscape(event: KeyboardEvent): void\n handleTabClick(): void\n handleToolbarModeChange(mode: ToolbarMode): void\n /** A stroke landed on the annotation overlay. */\n handleDrawStroke(): void\n}\n\nfunction consume(event: KeyboardEvent): void {\n event.preventDefault()\n event.stopPropagation()\n}\n\nabstract class BaseModeState implements ModeState {\n abstract readonly kind: ModeKind\n readonly launcherLit: boolean = true\n readonly acceptsFrustrationPrompt: boolean = false\n\n constructor(protected host: ModeHost) {}\n\n enter(): void {}\n exit(): void {}\n handleOpen(): void {}\n handleClose(): void {\n this.host.exitAll()\n }\n handleEscape(_event: KeyboardEvent): void {}\n handleTabClick(): void {\n this.host.exitAll()\n }\n handleToolbarModeChange(_mode: ToolbarMode): void {}\n handleDrawStroke(): void {}\n}\n\nexport class IdleState extends BaseModeState {\n readonly kind = 'idle'\n override readonly launcherLit = false\n override readonly acceptsFrustrationPrompt = true\n\n override handleOpen(): void {\n this.host.dismissFrustrationPrompt()\n this.host.dismissSuccess()\n this.host.transitionTo(new AnnotatingState(this.host))\n }\n\n override handleClose(): void {\n // Already closed; nothing to exit.\n }\n\n // Escape falls through untouched (not consumed) so the host page's own\n // Escape handling still works while the widget shows nothing.\n\n override handleTabClick(): void {\n this.handleOpen()\n }\n}\n\nexport class AnnotatingState extends BaseModeState {\n readonly kind = 'annotating'\n\n override enter(): void {\n if (!this.host.hasAnnotationChrome()) {\n this.host.mountAnnotationChrome()\n return\n }\n // Resuming markup from recording, a cancelled drawing, or the note popup:\n // the toolbar survived, only the mode-specific surfaces change hands.\n this.host.destroyRecordingManager()\n this.host.teardownDrawBar()\n this.host.ensureSession()\n }\n\n // exit: the toolbar and session deliberately outlive this state — they span\n // the whole flow and are released by the flow-ending paths, not per mode.\n\n override handleEscape(event: KeyboardEvent): void {\n consume(event)\n this.host.exitAll()\n }\n\n override handleToolbarModeChange(mode: ToolbarMode): void {\n if (mode === 'record') {\n this.host.transitionTo(new RecordingState(this.host))\n } else {\n // Re-entering markup is idempotent: enter() finds the chrome and session\n // already in place and changes nothing.\n this.host.transitionTo(new AnnotatingState(this.host))\n }\n }\n\n override handleDrawStroke(): void {\n // The draw note bar packs a field, undo/redo, cancel and submit into one\n // pill at the bottom of the screen — which on a phone is exactly where the\n // keyboard lands, and none of it is thumb-sized. Touch collects the note in\n // the same bottom sheet the pin flow already uses.\n this.host.transitionTo(new DrawingState(this.host, isTouchPointer() ? 'sheet' : 'bar'))\n }\n}\n\nexport class RecordingState extends BaseModeState {\n readonly kind = 'recording'\n\n override enter(): void {\n // Recording exists to capture the user reproducing something, which the\n // pin and draw surfaces would sit on top of — they come down first.\n this.host.clearCurrentAnnotation()\n this.host.teardownDrawBar()\n this.host.destroySession()\n this.host.startRecorder()\n }\n\n override handleEscape(event: KeyboardEvent): void {\n // Escape here is the Stop button, not a cancel: the recording is kept and\n // the note popup opens, exactly as if the reporter had pressed Stop. The\n // recording itself is carried into the popup for the submit payload, so\n // this edge hands the manager off instead of destroying it.\n consume(event)\n this.host.stopRecordingIntoNote()\n }\n\n override handleToolbarModeChange(mode: ToolbarMode): void {\n // A second press of Record must not restart the session already running.\n if (mode === 'record') return\n this.host.transitionTo(new AnnotatingState(this.host))\n }\n}\n\nexport class DrawingState extends BaseModeState {\n readonly kind = 'drawing'\n\n // Which note surface collects the drawing's note: the inline bar on a\n // desktop, the bottom sheet on touch.\n constructor(host: ModeHost, private surface: 'bar' | 'sheet') {\n super(host)\n }\n\n override enter(): void {\n if (this.surface === 'sheet') {\n this.host.mountDrawSheet()\n } else {\n this.host.mountDrawBar()\n this.host.refreshDrawBar()\n }\n }\n\n override handleEscape(event: KeyboardEvent): void {\n // One Escape costs the stroke, not the session: back to annotating.\n consume(event)\n this.host.cancelDraw()\n }\n\n override handleToolbarModeChange(mode: ToolbarMode): void {\n if (mode === 'record') {\n this.host.transitionTo(new RecordingState(this.host))\n } else {\n this.host.transitionTo(new AnnotatingState(this.host))\n }\n }\n\n override handleDrawStroke(): void {\n // Additional strokes while the note surface is already up only need the\n // undo/redo affordances refreshed; the sheet has none.\n if (this.surface === 'bar') this.host.refreshDrawBar()\n }\n}\n\nexport class PopupState extends BaseModeState {\n readonly kind = 'popup'\n\n constructor(host: ModeHost, private opts: NotePopupOptions) {\n super(host)\n }\n\n override enter(): void {\n this.host.mountNotePopup(this.opts)\n }\n\n override handleEscape(event: KeyboardEvent): void {\n consume(event)\n this.host.exitAll()\n }\n\n override handleToolbarModeChange(mode: ToolbarMode): void {\n if (mode === 'record') {\n // The popup and its scrim deliberately stay up: stopping the recording\n // opens a fresh note popup whose mount replaces both, so exactly one\n // scrim exists throughout instead of an orphaned pair.\n this.host.transitionTo(new RecordingState(this.host))\n } else {\n // Markup has no successor surface to replace the popup, so it must come\n // down here. Left standing it is a zombie: a scrim and scroll lock the\n // reporter cannot see past, and a checkbox whose change listener keeps\n // writing screenshot consent for a note that was just abandoned.\n this.host.dismissNotePopup()\n this.host.transitionTo(new AnnotatingState(this.host))\n }\n }\n}\n\n/**\n * A question is open inside the chat card. The card is already mounted and\n * stays put, so entering mounts nothing — what changes is that the submission\n * is now on the server, which is why Escape stops being a cancel here.\n */\nexport class ChatClarifyingState extends BaseModeState {\n readonly kind = 'clarifying'\n\n // handleEscape stays a no-op here because the chat card owns its own Escape\n // and routes it to the same cancel its X does — routing it twice would\n // cancel an already-cancelled submission.\n\n override handleToolbarModeChange(_mode: ToolbarMode): void {\n // No new surface may open under the conversation; snap the toolbar's\n // visual back to the mode the submission came from.\n this.host.revertToolbarDuringClarify()\n }\n}\n\nexport class SubmittingState extends BaseModeState {\n readonly kind = 'submitting'\n override readonly launcherLit = false\n\n override enter(): void {\n // Both entries converge here: skipping the clarification drops its chrome,\n // and re-entering from a failure-card retry finds it already gone — every\n // release below is idempotent.\n this.host.releaseClarifyUi()\n }\n\n override handleTabClick(): void {\n // Deliberate no-op: the launcher must not start a second feedback on top\n // of the one still finalizing in the background.\n }\n\n // Escape falls through unconsumed: nothing is on screen to dismiss, and the\n // background finalize must not be cancellable by a stray keypress.\n}\n","import type { MakeThisBetterConfig, WidgetBrandColors } from '../types'\n\nexport type { WidgetBrandColors } from '../types'\n\nconst HEX_COLOR = /^#[0-9a-f]{6}$/i\n\nexport function resolveBrandColors(\n colors: MakeThisBetterConfig['brandColors'],\n): WidgetBrandColors | undefined {\n if (colors === undefined) return undefined\n\n if (typeof colors !== 'object' || colors === null) return rejectBrandColors()\n\n const values = [colors.primary, colors.hover, colors.active, colors.onPrimary]\n if (!values.every((color) => typeof color === 'string' && HEX_COLOR.test(color))) {\n return rejectBrandColors()\n }\n\n return {\n primary: colors.primary.toLowerCase(),\n hover: colors.hover.toLowerCase(),\n active: colors.active.toLowerCase(),\n onPrimary: colors.onPrimary.toLowerCase(),\n }\n}\n\nfunction rejectBrandColors(): undefined {\n console.warn(\n '[MakeThisBetter] brandColors requires primary, hover, active, and onPrimary as six-digit hex colors.',\n )\n return undefined\n}\n","import { ShadowContainer } from './shadow'\nimport { isImeConfirmKeydown } from '../context/dom-utils'\nimport { FeedbackTab } from './tab'\nimport { FrustrationPromptCard } from './frustration-prompt'\nimport { DimOverlay } from './dim-overlay'\nimport { isTouchPointer } from './pointer-kind'\nimport { PageOffset } from './page-offset'\nimport { lockPageScroll } from './scroll-lock'\nimport type { ScrollLock } from './scroll-lock'\nimport { AnnotationToolbar } from '../annotate/toolbar'\nimport { AnnotationSession } from '../annotate/session'\nimport { DrawNoteBar } from '../annotate/draw-note-bar'\nimport { ChatPopup } from '../popup/chat-popup'\nimport { ReceiptCard } from '../popup/receipt'\nimport { warmFontEmbedCss } from '../screenshot/capture'\nimport { ConsoleErrorCollector } from '../context/console'\nimport { BreadcrumbCollector } from '../context/breadcrumbs'\nimport { FrustrationDetector } from '../context/frustration'\nimport { isFrustrationDismissed, markFrustrationDismissed } from '../context/frustration-state'\nimport { ApiClient } from '../api/client'\nimport { cacheBoardUrl, getAnonId, getCachedBoardUrl, getStoredReporterEmail, rememberReporterEmail } from './anon-id'\nimport { getMessages } from '../i18n'\nimport { RecordingManager } from './recording-manager'\nimport { SubmissionLifecycle } from './submission-lifecycle'\nimport { AnnotatingState, ChatClarifyingState, IdleState, PopupState, SubmittingState } from './mode-states'\nimport type { ModeHost, ModeState, NotePopupOptions } from './mode-states'\nimport type { MakeThisBetterConfig, Annotation, SubmissionSessionResponse, FeedbackResponse } from '../types'\nimport { resolveBrandColors } from './brand-colors'\nimport type { WidgetBrandColors } from './brand-colors'\n\ntype Via = 'pin' | 'draw' | 'record'\n\n// A browser SDK has no build-time environment, so \"is a developer watching?\"\n// has to be inferred. Loopback and the .local/.test dev TLDs are where an\n// integrator runs; anything else is somebody's live site.\nfunction isLocalHost(): boolean {\n const { hostname } = window.location\n return (\n hostname === 'localhost' ||\n hostname === '127.0.0.1' ||\n hostname === '[::1]' ||\n hostname.endsWith('.localhost') ||\n hostname.endsWith('.local') ||\n hostname.endsWith('.test')\n )\n}\n\nexport class WidgetController implements ModeHost {\n private config: MakeThisBetterConfig\n private brandColors: WidgetBrandColors | undefined\n private shadow!: ShadowContainer\n private tab: FeedbackTab | null = null\n private toolbar: AnnotationToolbar | null = null\n private session: AnnotationSession | null = null\n private drawBar: DrawNoteBar | null = null\n private dim: DimOverlay | null = null\n private pageOffset = new PageOffset()\n private chatPopup: ChatPopup | null = null\n private scrollLock: ScrollLock | null = null\n private success: ReceiptCard | null = null\n private frustrationPrompt: FrustrationPromptCard | null = null\n private apiClient: ApiClient\n private consoleCollector: ConsoleErrorCollector\n private breadcrumbCollector: BreadcrumbCollector\n private frustrationDetector: FrustrationDetector | null = null\n private messages: ReturnType<typeof getMessages>\n // The mode machine. One state object per mode owns what mounts on entry and\n // how each input behaves there; the controller only delegates and keeps the\n // component references the states operate on (through the ModeHost verbs).\n private state: ModeState = new IdleState(this)\n private via: Via = 'pin'\n private currentAnnotation: Annotation | null = null\n private lastPopupPos: { x: number; y: number } | null = null\n private recordingManager: RecordingManager | null = null\n private lifecycle: SubmissionLifecycle\n private touchHintShown = false\n private lastHandoff: { feedbackId?: string; boardUrl?: string; identityToken?: string } = {}\n\n constructor(config: MakeThisBetterConfig) {\n this.config = config\n // A missing key still renders a fully working widget whose every submit\n // 401s — the reporter sees a generic failure and the integrator sees\n // nothing at all. Say it at init, where the integrator is looking.\n if (!config.projectKey) {\n console.warn('[MakeThisBetter] projectKey is missing. Feedback submission will be rejected.')\n }\n this.brandColors = resolveBrandColors(config.brandColors)\n this.messages = getMessages(config.locale ?? document.documentElement.lang ?? 'en')\n this.apiClient = new ApiClient({\n projectKey: config.projectKey,\n apiUrl: config.apiUrl,\n userToken: config.userToken,\n userTokenFn: config.userTokenFn,\n })\n this.consoleCollector = new ConsoleErrorCollector()\n this.consoleCollector.start()\n this.breadcrumbCollector = new BreadcrumbCollector()\n this.breadcrumbCollector.start()\n this.lifecycle = new SubmissionLifecycle(\n {\n apiClient: this.apiClient,\n consoleCollector: this.consoleCollector,\n breadcrumbCollector: this.breadcrumbCollector,\n brandColors: this.brandColors,\n shadow: () => this.shadow,\n messages: () => this.messages,\n user: () => this.config.user,\n },\n {\n isBackground: () => this.state.kind === 'submitting',\n onFinalizeFailure: (onRetry) => this.chatPopup?.showFinalizeFailure(onRetry),\n onReenterBackground: () => this.transitionTo(new SubmittingState(this)),\n onFinalized: (feedback, submission) => this.handleFinalized(feedback, submission),\n },\n )\n try {\n this.buildUI()\n } catch (error) {\n // A page-supplied host can be an element that refuses attachShadow. The\n // collectors above have already patched window.onerror and history, and\n // letting this throw into the customer's script would leave those\n // patches with no owner to release them — so roll back and go dark\n // instead of crashing code that is not ours.\n this.consoleCollector.stop()\n this.breadcrumbCollector.stop()\n console.warn('[MakeThisBetter] Widget failed to start:', error)\n return\n }\n\n if (config.frustrationDetection !== false) {\n this.frustrationDetector = new FrustrationDetector(() => this.handleFrustration())\n this.frustrationDetector.start()\n }\n\n document.addEventListener('keydown', this.handleKeydown, true)\n }\n\n /**\n * The single mutation point of the mode machine: the outgoing state exits,\n * the incoming state enters, and the launcher reflects the new mode.\n */\n transitionTo(next: ModeState): void {\n const previous = this.state\n this.state = next\n previous.exit()\n next.enter()\n this.tab?.setActive(next.launcherLit)\n }\n\n private handleKeydown = (event: KeyboardEvent): void => {\n if (event.key !== 'Escape') return\n // Escape during IME composition cancels the candidate list, not the widget.\n // Without this a reporter typing Chinese or Japanese loses the popup and\n // everything they had written by backing out of one wrong candidate.\n if (isImeConfirmKeydown(event)) return\n\n this.state.handleEscape(event)\n }\n\n private buildUI(): void {\n this.shadow = new ShadowContainer(this.config.theme ?? 'auto', this.brandColors)\n\n if (this.config.entryMode === 'api') {\n // Nothing is reachable until the host wires up open(), and silence would\n // look identical to a broken install. But 'api' is a supported setting,\n // so a warning on every production page load would be a third-party SDK\n // shouting at customers who did nothing wrong — say it only where an\n // integrator is watching.\n if (isLocalHost()) {\n console.warn(\n '[MakeThisBetter] entryMode: \"api\" renders no launcher. ' +\n 'Call MakeThisBetter.open() from your own UI.',\n )\n }\n return\n }\n\n // A phone gets no launcher of ours. The edge tab is 36x40 of someone\n // else's screen, and it is genuinely hard to find — its own author missed\n // it on a test page — while a narrow layout has no spare margin to dock\n // anything to. Where a feedback entry belongs on a phone is the host's\n // call, not a third-party script's, so touch renders nothing and waits for\n // open() from the host's own UI.\n if (isTouchPointer()) {\n if (isLocalHost()) {\n console.warn(\n '[MakeThisBetter] No launcher is rendered on touch devices. ' +\n 'Call MakeThisBetter.open() from your own mobile UI.',\n )\n }\n return\n }\n\n this.mountTab()\n }\n\n private mountTab(): void {\n // No shadow means startup was rolled back; there is nowhere to mount.\n if (this.tab || !this.shadow) return\n this.tab = new FeedbackTab(\n this.shadow,\n this.messages,\n this.config.position ?? 'right',\n () => this.state.handleTabClick(),\n this.config.tabText,\n )\n }\n\n /** Opens annotation mode. Idempotent: a second call while open does nothing. */\n open(): void {\n if (!this.shadow) return\n this.state.handleOpen()\n }\n\n close(): void {\n this.state.handleClose()\n }\n\n showLauncher(): void {\n if (isTouchPointer()) return\n this.mountTab()\n this.tab?.setActive(this.state.launcherLit)\n }\n\n hideLauncher(): void {\n this.tab?.destroy()\n this.tab = null\n }\n\n // Takes effect for anything opened afterwards. Components already on screen keep\n // the language they were opened in rather than being re-rendered underneath a\n // reporter who may be mid-sentence.\n setLocale(locale: string): void {\n this.config.locale = locale\n this.messages = getMessages(locale)\n this.tab?.setMessages(this.messages)\n }\n\n private handleFrustration(): void {\n if (!this.state.acceptsFrustrationPrompt || this.frustrationPrompt || isFrustrationDismissed()) return\n\n this.frustrationPrompt = new FrustrationPromptCard(this.shadow, this.messages, {\n onTell: () => {\n this.dismissFrustrationPrompt()\n this.state.handleOpen()\n },\n onDismiss: () => {\n markFrustrationDismissed()\n this.dismissFrustrationPrompt()\n },\n // An unnoticed card is not a refusal, so it must not suppress later\n // prompts. The detector's own 60s cooldown does the rate limiting.\n onAutoHide: () => this.dismissFrustrationPrompt(),\n })\n }\n\n dismissSuccess(): void {\n this.success?.destroy()\n this.success = null\n // The chat card ends as its own receipt, so it is the previous\n // submission's success card on that path.\n this.chatPopup?.destroy()\n this.chatPopup = null\n }\n\n dismissFrustrationPrompt(): void {\n this.frustrationPrompt?.destroy()\n this.frustrationPrompt = null\n }\n\n hasAnnotationChrome(): boolean {\n return this.toolbar !== null\n }\n\n mountAnnotationChrome(): void {\n // Network-only warmup: annotating usually ends in a capture, and the font\n // embed CSS is the slow, cacheable half of one.\n void warmFontEmbedCss()\n const touch = isTouchPointer()\n\n this.toolbar = new AnnotationToolbar(\n this.shadow,\n this.messages,\n () => this.exitAll(),\n (mode) => this.state.handleToolbarModeChange(mode),\n // The bar stands at whichever edge the tab does. Leaving it pinned right\n // while a left-configured tab sat opposite would split the widget across\n // both sides of the page.\n this.config.position ?? 'right',\n !touch || !this.touchHintShown,\n )\n if (touch) this.touchHintShown = true\n this.ensureSession()\n // The bar is a full-bleed strip at the top on a phone. Left overlapping,\n // it covers roughly 100px of the page — usually the site's own header —\n // and taps there do nothing, silently. Give it room of its own instead.\n if (touch && this.toolbar) this.pageOffset.reserve(this.toolbar.height())\n }\n\n ensureSession(): void {\n if (this.session) return\n this.session = new AnnotationSession(this.shadow, this.messages, {\n onPin: (annotation, targetName) => {\n this.currentAnnotation = annotation\n this.via = 'pin'\n this.transitionTo(new PopupState(this, {\n x: annotation.x,\n y: annotation.y,\n targetName,\n targetRect: annotation.targetRect,\n }))\n },\n onDrawStroke: () => this.state.handleDrawStroke(),\n })\n }\n\n destroySession(): void {\n this.session?.destroy()\n this.session = null\n }\n\n clearCurrentAnnotation(): void {\n this.currentAnnotation = null\n }\n\n startRecorder(): void {\n this.recordingManager = new RecordingManager()\n this.recordingManager.start(\n this.shadow,\n this.messages,\n this.stopRecordingIntoNote,\n ).catch((error) => {\n // rrweb failed to load (network / SRI mismatch) — fall back to markup\n console.warn('[MakeThisBetter] Interaction replay unavailable, falling back to markup:', error)\n // Staleness guard: the reporter may have left recording while the load\n // was still failing, and a stale fallback must not disturb wherever\n // they are now.\n if (this.state.kind !== 'recording') return\n this.toolbar?.setMode('markup')\n this.state.handleToolbarModeChange('markup')\n })\n }\n\n destroyRecordingManager(): void {\n this.recordingManager?.destroy()\n this.recordingManager = null\n }\n\n stopRecordingIntoNote = (): void => {\n if (!this.recordingManager) return\n this.recordingManager.stop()\n this.via = 'record'\n this.transitionTo(new PopupState(this, {\n x: window.innerWidth / 2,\n y: window.innerHeight / 2,\n targetName: this.messages.record.timer_label,\n }))\n }\n\n mountDrawBar(): void {\n this.engageScrollLock()\n this.drawBar = new DrawNoteBar(this.shadow, {\n messages: this.messages,\n onUndo: () => this.handleDrawUndoRedo('undo'),\n onRedo: () => this.handleDrawUndoRedo('redo'),\n onCancel: () => this.cancelDraw(),\n onSubmit: (note) => this.handleDrawSubmit(note),\n onActivity: () => this.lifecycle.warmScreenshotDeps(),\n })\n // Strokes are baked onto the screenshot, so this surface offers no opt-out\n // and must undo one made earlier in the session.\n this.lifecycle.setScreenshotEnabled(true)\n this.lifecycle.warmScreenshotDeps()\n }\n\n /**\n * The note sheet for a finished drawing on touch.\n *\n * Cancel here means \"throw the drawing away and keep annotating\", not \"close\n * the widget\" — the reporter is mid-task and a stray stroke should cost one\n * tap, not the whole session.\n */\n mountDrawSheet(): void {\n // No dismissInteraction and no scrim here, matching the desktop draw bar:\n // the overlay stays live so more strokes are still possible, and the scrim\n // sits below it anyway so it could never receive the tap.\n this.engageScrollLock()\n // Cancel means \"throw the drawing away\" only while the drawing is still the\n // reporter's; once the note is sent the card is a receipt and closing it\n // ends the session instead.\n let submitted = false\n this.chatPopup = new ChatPopup(this.shadow, {\n targetName: this.messages.annotation.drawing,\n x: window.innerWidth / 2,\n y: window.innerHeight / 2,\n messages: this.messages,\n position: this.config.position ?? 'right',\n onSubmit: async (description: string) => {\n const annotation = this.session?.getDrawAnnotation()\n if (!annotation) {\n this.cancelDraw()\n return null\n }\n submitted = true\n this.currentAnnotation = annotation\n this.via = 'draw'\n this.session?.dismissInteraction()\n this.teardownDrawBar()\n const recording = this.recordingManager?.getRecording() ?? undefined\n const session = await this.lifecycle.start(\n description || this.messages.annotation.drawing,\n annotation,\n recording,\n )\n if (session?.board_url) cacheBoardUrl(session.board_url)\n return session\n },\n onClarify: async (sessionId: string, token: string, onDelta: (text: string) => void, signal?: AbortSignal) => {\n return this.apiClient.streamClarification(sessionId, token, onDelta, signal)\n },\n onAnswer: (sessionId: string, token: string, message: string) =>\n this.apiClient.answerClarification(sessionId, token, message),\n onFinalize: () => {\n this.transitionTo(new SubmittingState(this))\n return this.lifecycle.finalize()\n },\n onCancel: () => (submitted ? this.exitAll() : this.cancelDraw()),\n onConversationOpen: () => this.transitionTo(new ChatClarifyingState(this)),\n onActivity: () => this.lifecycle.warmScreenshotDeps(),\n resolveBoardLink: () => this.buildViewFeedbackHandler() ?? this.buildMyFeedbackHandler(),\n resolveEmailCapture: () => this.buildEmailCapture(),\n })\n // Same rule as the desktop draw bar: no opt-out on a drawing.\n this.lifecycle.setScreenshotEnabled(true)\n this.lifecycle.warmScreenshotDeps()\n }\n\n private handleDrawUndoRedo(action: 'undo' | 'redo'): void {\n if (action === 'undo') this.session?.undoDraw()\n else this.session?.redoDraw()\n this.refreshDrawBar()\n }\n\n refreshDrawBar(): void {\n if (!this.session) return\n this.drawBar?.setUndoRedo(this.session.canUndoDraw(), this.session.canRedoDraw())\n }\n\n cancelDraw(): void {\n this.session?.clearDraw()\n this.releaseScrollLock()\n this.teardownDrawBar()\n // On touch the sheet is the note surface, so cancelling the drawing has to\n // take the sheet and its scrim with it — teardownDrawBar only knows the bar.\n this.chatPopup?.destroy()\n this.chatPopup = null\n this.dim?.destroy()\n this.dim = null\n this.transitionTo(new AnnotatingState(this))\n }\n\n teardownDrawBar(): void {\n this.drawBar?.destroy()\n this.drawBar = null\n }\n\n private handleDrawSubmit(note: string): void {\n const annotation = this.session?.getDrawAnnotation()\n if (!annotation) {\n this.cancelDraw()\n return\n }\n this.currentAnnotation = annotation\n this.via = 'draw'\n this.session?.dismissInteraction()\n const description = note || this.messages.annotation.drawing\n const recording = this.recordingManager?.getRecording() ?? undefined\n\n this.teardownDrawBar()\n this.releaseScrollLock()\n this.dim?.destroy()\n this.dim = null\n\n this.mountChatAfterSubmit(description, annotation, recording)\n }\n\n private mountChatAfterSubmit(description: string, annotation: Annotation | null, recording: import('./recording-manager').RecordingResult | undefined): void {\n this.dim = new DimOverlay(this.shadow, () => this.exitAll())\n this.engageScrollLock()\n\n this.chatPopup = new ChatPopup(this.shadow, {\n targetName: annotation?.targetName ?? this.messages.annotation.drawing,\n x: this.lastPopupPos?.x ?? window.innerWidth / 2,\n y: this.lastPopupPos?.y ?? window.innerHeight / 2,\n messages: this.messages,\n position: this.config.position ?? 'right',\n onSubmit: async () => {\n const session = await this.lifecycle.start(description, annotation, recording)\n if (session?.board_url) cacheBoardUrl(session.board_url)\n return session\n },\n onClarify: async (sessionId: string, token: string, onDelta: (text: string) => void, signal?: AbortSignal) => {\n return this.apiClient.streamClarification(sessionId, token, onDelta, signal)\n },\n onAnswer: (sessionId: string, token: string, message: string) =>\n this.apiClient.answerClarification(sessionId, token, message),\n onFinalize: () => {\n this.transitionTo(new SubmittingState(this))\n return this.lifecycle.finalize()\n },\n onCancel: () => this.exitAll(),\n onConversationOpen: () => this.transitionTo(new ChatClarifyingState(this)),\n resolveBoardLink: () => this.buildViewFeedbackHandler() ?? this.buildMyFeedbackHandler(),\n resolveEmailCapture: () => this.buildEmailCapture(),\n })\n this.chatPopup.submit(description)\n }\n\n mountNotePopup({ x, y, targetName, targetRect }: NotePopupOptions): void {\n this.lastPopupPos = { x, y }\n this.teardownDrawBar()\n this.session?.dismissInteraction()\n this.chatPopup?.destroy()\n this.chatPopup = null\n this.dim?.destroy()\n this.dim = null\n this.dim = new DimOverlay(this.shadow, () => this.exitAll())\n const recording = this.recordingManager?.getRecording()\n if (recording) this.lifecycle.setScreenshotEnabled(true)\n\n const annotation = this.currentAnnotation\n const recordingResult = this.recordingManager?.getRecording() ?? undefined\n\n this.chatPopup = new ChatPopup(this.shadow, {\n targetName,\n x,\n y,\n targetRect,\n messages: this.messages,\n position: this.config.position ?? 'right',\n onSubmit: async (description: string) => {\n this.currentAnnotation = annotation ?? this.currentAnnotation\n const session = await this.lifecycle.start(description, this.currentAnnotation, recordingResult)\n if (session?.board_url) cacheBoardUrl(session.board_url)\n return session\n },\n onClarify: async (sessionId: string, token: string, onDelta: (text: string) => void, signal?: AbortSignal) => {\n return this.apiClient.streamClarification(sessionId, token, onDelta, signal)\n },\n onAnswer: (sessionId: string, token: string, message: string) =>\n this.apiClient.answerClarification(sessionId, token, message),\n // The chat card turns into a receipt at this point: the pin, the scrim\n // and the scroll lock have nothing left to hold. A finalization failure\n // remains in this card as a retryable receipt state.\n onFinalize: () => {\n this.teardownSubmittingUi()\n this.transitionTo(new SubmittingState(this))\n return this.lifecycle.finalize()\n },\n onCancel: () => this.exitAll(),\n onConversationOpen: () => this.transitionTo(new ChatClarifyingState(this)),\n onActivity: () => this.lifecycle.warmScreenshotDeps(),\n resolveBoardLink: () => this.buildViewFeedbackHandler() ?? this.buildMyFeedbackHandler(),\n resolveEmailCapture: () => this.buildEmailCapture(),\n ...(recording\n ? { recordingSeconds: recording.duration }\n : {\n onScreenshotToggle: (enabled: boolean) => this.lifecycle.setScreenshotEnabled(enabled),\n screenshotEnabled: this.lifecycle.isScreenshotEnabled(),\n }),\n })\n this.engageScrollLock()\n this.lifecycle.warmScreenshotDeps()\n }\n\n /**\n * The Markup-button escape from a note popup. The popup's mount destroyed\n * the session's overlay (dismissInteraction), so the session goes with it —\n * AnnotatingState.enter() rebuilds a fresh one and the reporter can point\n * and draw again, rather than facing a dead overlay under a leftover scrim.\n */\n dismissNotePopup(): void {\n this.chatPopup?.destroy()\n this.chatPopup = null\n this.dim?.destroy()\n this.dim = null\n this.releaseScrollLock()\n this.clearCurrentAnnotation()\n this.destroySession()\n }\n\n private engageScrollLock(): void {\n // Re-entrant: record mode returns here and mounts a fresh popup over the\n // existing one, and a second lock would leave a listener behind.\n if (!this.scrollLock) this.scrollLock = lockPageScroll()\n }\n\n private releaseScrollLock(): void {\n this.scrollLock?.release()\n this.scrollLock = null\n }\n\n private buildMyFeedbackHandler(): (() => void) | undefined {\n if (this.config.user) return undefined\n if (!getCachedBoardUrl()) return undefined\n\n return async () => {\n const exchange = await this.apiClient.createIdentityToken(getAnonId())\n if (!exchange?.identity_token || !exchange.board_url) return\n cacheBoardUrl(exchange.board_url)\n window.open(`${exchange.board_url}?identity=${encodeURIComponent(exchange.identity_token)}`, '_blank')\n }\n }\n\n private teardownSubmittingUi(): void {\n // The highlight exists to hold the target visible under a pin. This is\n // where the session — and with it the pin — goes away, so the highlight\n // has to go with it rather than outliving what it pointed at.\n this.teardownDrawBar()\n // The pin goes away with the session below, so nothing anchored is left for\n // a scroll to strand — the conversation that follows scrolls freely.\n this.releaseScrollLock()\n this.dim?.destroy()\n this.dim = null\n this.session?.hideHighlight()\n this.session?.destroy()\n this.session = null\n this.recordingManager?.destroy()\n this.recordingManager = null\n }\n\n revertToolbarDuringClarify(): void {\n this.toolbar?.setMode(this.via === 'record' ? 'record' : 'markup')\n }\n\n releaseClarifyUi(): void {\n this.teardownToolbar()\n this.currentAnnotation = null\n // The card is not destroyed here: the skip path's card dismisses itself\n // after handing off, and every other entry into submitting finds it gone.\n }\n\n private handleFinalized(feedback: FeedbackResponse, submission: SubmissionSessionResponse): void {\n this.lastHandoff = {\n feedbackId: feedback.id,\n boardUrl: feedback.board_url,\n identityToken: feedback.identity_token,\n }\n if (feedback.board_url) cacheBoardUrl(feedback.board_url)\n\n // A finalize that lands after the reporter already started their next\n // report must not touch the screen: any toolbar, consent state or chat\n // card up now belongs to the new report, not to this submission. The\n // hand-off above is still recorded — it is about the finalized feedback,\n // not about what is on screen.\n if (this.chatPopup && this.chatPopup.sessionId() !== submission.id) return\n if (!this.chatPopup && this.state.kind !== 'submitting' && this.state.kind !== 'clarifying') return\n\n if (this.chatPopup) {\n this.chatPopup.showFinalizeSuccess()\n this.teardownSubmittingUi()\n this.teardownToolbar()\n this.lifecycle.resetScreenshotConsent()\n this.transitionTo(new IdleState(this))\n return\n }\n\n this.teardownToolbar()\n this.lifecycle.resetScreenshotConsent()\n this.showSuccess({ skipFollowup: submission.ai_clarify_available === false })\n }\n\n private showSuccess(options?: { skipFollowup?: boolean }): void {\n this.transitionTo(new IdleState(this))\n this.success = new ReceiptCard(this.shadow, this.messages, () => {\n this.success?.destroy()\n this.success = null\n }, { ...options, onViewFeedback: this.buildViewFeedbackHandler() })\n }\n\n private buildEmailCapture(): { onSubmit: (email: string) => Promise<boolean> } | undefined {\n if (this.config.user) return undefined\n const { feedbackId, identityToken } = this.lastHandoff\n if (!feedbackId || !identityToken) return undefined\n if (getStoredReporterEmail()) return undefined\n\n return {\n onSubmit: async (email: string) => {\n const saved = await this.apiClient.updateReporter(feedbackId, email, identityToken)\n if (saved) rememberReporterEmail(email)\n return saved\n },\n }\n }\n\n private buildViewFeedbackHandler(): (() => void) | undefined {\n const { boardUrl, identityToken } = this.lastHandoff\n if (!boardUrl) return undefined\n\n if (identityToken) {\n return () => {\n window.open(`${boardUrl}?identity=${encodeURIComponent(identityToken)}`, '_blank')\n }\n }\n\n if (this.config.userToken || this.config.userTokenFn) {\n return async () => {\n // resolveUserToken runs the host's own userTokenFn. A rejection there\n // is the customer's backend failing, not ours — swallowing it beats\n // handing their page an unhandled rejection from a click handler they\n // never wrote.\n try {\n const token = await this.apiClient.resolveUserToken()\n if (token) window.open(`${boardUrl}?identity=${encodeURIComponent(token)}`, '_blank')\n } catch {\n // The board link just does nothing; the feedback itself already sent.\n }\n }\n }\n\n return undefined\n }\n\n /**\n * The reserved strip belongs to the toolbar, so it goes back the moment the\n * toolbar does. Both teardown paths route through here: exit, and the\n * successful submit — which never passes through exitAll and used to leave\n * the host page padded for the rest of its life.\n */\n private teardownToolbar(): void {\n this.pageOffset.release()\n this.toolbar?.destroy()\n this.toolbar = null\n }\n\n exitAll(): void {\n this.lifecycle.abandon()\n this.via = 'pin'\n this.transitionTo(new IdleState(this))\n // Ahead of anything that can throw: a half-torn-down widget is survivable,\n // a host page left permanently padded is not.\n this.teardownToolbar()\n this.teardownDrawBar()\n this.releaseScrollLock()\n this.session?.hideHighlight()\n this.session?.destroy()\n this.session = null\n this.dim?.destroy()\n this.dim = null\n this.chatPopup?.destroy()\n this.chatPopup = null\n this.currentAnnotation = null\n this.recordingManager?.destroy()\n this.recordingManager = null\n }\n\n destroy(): void {\n document.removeEventListener('keydown', this.handleKeydown, true)\n this.exitAll()\n this.consoleCollector.stop()\n this.breadcrumbCollector.stop()\n this.frustrationDetector?.stop()\n this.dismissFrustrationPrompt()\n this.success?.destroy()\n this.tab?.destroy()\n this.recordingManager?.destroy()\n // Undefined when startup rolled back before the shadow container existed.\n this.shadow?.destroy()\n }\n}\n","import { WidgetController } from './widget/controller'\nimport { WIDGET_HOST_ID } from './context/dom-utils'\nimport type { MakeThisBetterConfig } from './types'\n\nlet instance: WidgetController | null = null\nlet lastConfig: MakeThisBetterConfig | null = null\n// A mount deferred until DOMContentLoaded. Held so a second init() — or a\n// destroy() — can call it off: otherwise each init before the document is ready\n// queues its own listener, and every controller but the last mounts with no\n// live reference, leaking its document listeners and detectors for good.\nlet pendingMount: (() => void) | null = null\n\nfunction cancelPendingMount(): void {\n if (!pendingMount) return\n document.removeEventListener('DOMContentLoaded', pendingMount)\n pendingMount = null\n}\n\nfunction replaceInstance(config?: MakeThisBetterConfig): void {\n cancelPendingMount()\n instance?.destroy()\n instance = config ? new WidgetController(config) : null\n}\n\nconst MakeThisBetter = {\n init(config: MakeThisBetterConfig): void {\n // SSR frameworks run the same component code on the server, where there is\n // no document at all — a widget cannot exist there, and throwing a\n // ReferenceError into the host's render pass is the one thing a\n // third-party SDK must never do.\n if (typeof document === 'undefined') return\n replaceInstance()\n lastConfig = config\n if (document.readyState === 'loading') {\n pendingMount = () => {\n pendingMount = null\n replaceInstance(config)\n }\n document.addEventListener('DOMContentLoaded', pendingMount, { once: true })\n } else {\n replaceInstance(config)\n }\n },\n\n destroy(): void {\n replaceInstance()\n lastConfig = null\n },\n\n setLocale(locale: string): void {\n if (lastConfig) lastConfig.locale = locale\n instance?.setLocale(locale)\n },\n\n /**\n * Opens annotation mode from the host's own UI. Pair with\n * `entryMode: 'api'` to replace the docked tab entirely, which is the only\n * good answer on a phone where a permanent floating control is in the way.\n */\n open(): void {\n instance?.open()\n },\n\n close(): void {\n instance?.close()\n },\n\n /** Shows the docked tab on non-touch devices. Mobile entry UI belongs to the host. */\n showLauncher(): void {\n instance?.showLauncher()\n },\n\n hideLauncher(): void {\n instance?.hideLauncher()\n },\n}\n\nexport { MakeThisBetter }\nexport type { MakeThisBetterConfig }\n\nif (typeof window !== 'undefined') {\n (window as Window & { MakeThisBetter?: typeof MakeThisBetter }).MakeThisBetter = MakeThisBetter\n\n document.addEventListener('turbo:load', () => {\n const host = document.getElementById(WIDGET_HOST_ID)\n if (lastConfig && (!host || !host.shadowRoot)) {\n host?.remove()\n replaceInstance(lastConfig)\n }\n })\n}\n"],"names":["WIDGET_HOST_ID","isWidgetElement","el","host","elementUnderPoint","x","y","escapeHtml","text","isImeConfirmKeydown","event","UNRENDERED","renderedText","limit","out","node","getElementName","label","title","placeholder","alt","dataMark","getCssSelector","parts","current","selector","classNames","c","parent","siblings","idx","_ShadowContainer","theme","brandColors","provided","rgb","offset","onPrimaryRgb","eventType","style","cssText","tag","className","nodes","n","ShadowContainer","FeedbackTab","shadow","messages","position","onClick","customText","active","FRUSTRATION_DISMISS_MS","FrustrationPromptCard","handlers","SPAWN_CLICK_GRACE_MS","DimOverlay","onDismiss","now","isTouchPointer","PageOffset","px","POINTER_GESTURES","SCROLL_KEYS","scrollsInternally","consumesScrollKey","key","lockPageScroll","blockGesture","blockKey","origin","gesture","MOBILE_BAR_HEIGHT_PX","AnnotationToolbar","onExit","onModeChange","showTouchHint","e","rect","inside","m","mode","AnnotationOverlay","pe","pointerId","ElementHighlighter","below","captureTransform","region","canvasWidth","canvasHeight","toCanvasX","transform","toCanvasY","rectToCanvas","viewportCaptureRect","anchorScreenshotOffset","anchor","pageScroll","nestedScrollOffset","target","parentElement","element","root","captureAnchorOffset","nested","PinMarker","pin","BRAND_COLOR","DRAW_STROKE","DrawLayer","emptyAnchor","strokeAnchor","buildPathD","start","max","stroke","entry","path","first","strokes","shiftPoints","cx","cy","center","points","fx","fy","d","i","xs","p","ys","BLOCK_CLASS","MASK_TEXT_CLASS","PRIVACY_SELECTOR","isPrivacyProtected","structuralElementName","role","structuralElementSelector","DRAW_MIN_TRAVEL_PX","annotationTarget","AnnotationSession","underEl","targetName","annotation","drift","dx","dy","SETTLE_MS","KeyboardInset","reading","vp","inset","onChange","SHEET_MAX_WIDTH","isSheetViewport","SheetLayout","headroom","applied","DrawNoteBar","opts","loading","canUndo","canRedo","VIEWPORT_MARGIN","TAB_WIDTH","measureViewportFrame","tabPosition","vw","vh","panelWidth","preferred","frame","TABBABLE_SELECTOR","trapFocus","card","opener","handleKeydown","tabbables","last","CARD_WIDTH","AUTO_CLOSE_MS","BRAND_URL","BRAND_ICON","CLOSE_ICON","CAMERA_ICON","RECORD_ICON","CHECK_ICON","ChatPopup","quickOpts","o","capture","btn","value","screenshotBtn","pressed","session","result","streaming","delta","aiMessage","msg","view","icon","link","onRetry","retry","openBoard","pendingLink","emailCapture","row","field","input","submitEmail","email","note","error","bubble","suggestions","suggestion","pill","skip","disabled","targetRect","minLeft","maxRight","margin","gap","cardW","cardH","left","top","anchorIsInformative","ReceiptCard","onClose","options","titleEl","FILTERED_VALUE","PRIVATE_KEY_PATTERN","JWT_PATTERN","API_KEY_PATTERNS","CARD_CANDIDATE_PATTERN","HTTP_URL_PATTERN","FIELD_SEMANTIC_KEYS","VALUE_ATTRIBUTE_KEYS","SENSITIVE_INPUT_TYPES","SENSITIVE_AUTOCOMPLETE","SENSITIVE_KEY_PATTERN","filterSensitiveText","filtered","pattern","candidate","passesLuhn","sanitizeUrl","url","filterSensitiveValue","isSensitiveKey","item","sensitiveField","isSensitiveFieldAttributes","nestedKey","filteredItem","normalizeIdentifier","attributes","normalizedKey","token","digits","sum","doubleDigit","index","digit","isSensitiveField","filterInputValue","MAX_GROUPED_TEXT_LENGTH","createSensitiveTextContext","hasSensitiveTextContext","context","state","sensitiveTextState","cached","child","childState","loadImage","dataUrl","resolve","image","parsePathPolylines","drawPath","polylines","tokens","translatePathD","htmlToImageLoaded","loadHtmlToImage","err","fontEmbedCss","fontEmbedCssRequest","warmFontEmbedCss","getFontEmbedCSS","css","CULL_MARGIN_PX","TRANSPARENT_IMAGE_PLACEHOLDER","captureBaseScreenshot","resourceUrlResolver","signal","toJpeg","fontEmbedCSS","bodyRect","metrics","redactions","collectPrivacyRedactions","capturePixelRatio","bakeAnnotatedScreenshot","base","annotations","screenshotAnnotations","translateAnnotationForScreenshot","bakeScreenshot","screenshotOffset","findAnnotationTarget","captureMetrics","canvas","ctx","cover","drawStroke","drawPin","canvasToJpegBlob","elementsIncludingRoot","addRedaction","cloneScrollOffset","isWidgetOrBlocked","isMasked","hasSensitiveValue","REDACTION_PADDING_PX","walker","textContext","hasSensitiveText","range","descendants","padding","color","MAX_CAPTURE_PIXELS","total","primary","blob","ErrorSource","listener","source","line","col","errorName","reason","name","errorSource","summarizeError","validErrorName","inferErrorName","location","errorLocation","message","pathname","MAX_CONSOLE_ERRORS","ConsoleErrorCollector","listeners","nativePushState","nativeReplaceState","installedPushState","installedReplaceState","popstateHandler","lastUrl","onHistoryNavigation","install","released","uninstall","notify","type","fromUrl","pushOrig","replaceOrig","data","unused","MAX_BREADCRUMBS","describeTarget","describeSelector","BreadcrumbCollector","crumb","summary","from","to","sanitizedFrom","sanitizedTo","STORAGE_KEY","memoryState","loadFrustrationState","raw","normalize","saveFrustrationState","normalized","isFrustrationDismissed","markFrustrationDismissed","t","u","RAGE_CLICK_THRESHOLD","RAGE_CLICK_WINDOW_MS","RAPID_NAV_THRESHOLD","RAPID_NAV_WINDOW_MS","FORM_CHECK_DELAY_MS","FORM_FAILURE_THRESHOLD","FORM_FAILURE_WINDOW_MS","INTERACTION_ERROR_WINDOW_MS","DEAD_CLICK_DOM_DELAY_MS","ERROR_PAGE_CHECK_DELAY_MS","INTERACTIVE_TAGS","INTERACTIVE_ROLES","COMMAND_TAGS","COMMAND_ROLES","ERROR_PAGE_PATTERNS","_FrustrationDetector","onFrustration","interaction","describeElement","callback","delayMs","timer","stored","interactionTarget","cutoff","r","form","control","failures","timestamp","candidates","clickTime","mutated","navigated","cls","lower","FrustrationDetector","id","ApiError","status","FINALIZE_TIMEOUT_MS","DEFAULT_API_URL","CAPTURE_POLICY","ApiClient","submissionSessionId","resourceUrl","contentType","headers","body","payload","screenshot","submissionToken","generateSubmissionToken","json","ext","generateIdempotencyKey","reporterExternalId","feedbackId","identityToken","onDelta","res","consumeSse","controller","init","retries","delay","ms","reader","decoder","buffer","flush","eventLine","l","dataLine","s","done","sep","record","bytes","byte","EMAIL_KEY","BOARD_URL_KEY","memoryId","memoryEmail","memoryBoardUrl","getAnonId","generateAnonId","getStoredReporterEmail","rememberReporterEmail","getCachedBoardUrl","cacheBoardUrl","en","zhCN","ja","ko","es","fr","de","locales","getMessages","locale","lang","MAX_DURATION_S","VALUE_ATTRIBUTE_NAMES","MASKED_INPUT_TYPES","RRWEB_CDN","RRWEB_SRI","rrwebLoaded","loadRrweb","loadViaImport","loadViaCdn","filterReplayEvent","filterReplayDomValues","isRecord","hasAttributeValue","attributeValue","findGlobalRecord","win","reject","existing","script","RecordSession","onMaxReached","ripple","RecordControlBar","getDuration","onStop","secs","mm","ss","RecordingManager","collectPageContext","detectBrowser","detectOS","ua","buildPayload","description","pageContext","consoleErrors","user","recording","anonId","anonEmail","breadcrumbs","MIN_FINALIZE_LOADING_MS","SCREENSHOT_WAIT_MS","SubmissionLifecycle","deps","enabled","submissionAttempt","request","waitForPaint","submissionRequest","screenshotController","trackedTask","submission","background","minimumLoading","feedback","task","completed","consume","BaseModeState","_event","_mode","IdleState","AnnotatingState","RecordingState","DrawingState","surface","PopupState","ChatClarifyingState","SubmittingState","HEX_COLOR","resolveBrandColors","colors","rejectBrandColors","isLocalHost","hostname","WidgetController","config","next","previous","touch","submitted","sessionId","action","recordingResult","exchange","saved","boardUrl","instance","lastConfig","pendingMount","cancelPendingMount","replaceInstance","MakeThisBetter"],"mappings":"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"}
|