@mhosaic/feedback 0.7.1 → 0.7.3

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/api/client.ts","../src/capture/urlSanitizer.ts","../src/capture/device.ts","../src/capture/console.ts","../src/capture/network.ts","../src/capture/errors.ts","../src/capture/performance.ts","../src/capture/ringBuffer.ts","../src/capture/index.ts","../src/screenshot/index.ts","../src/widget/i18n.ts","../src/widget/mount.tsx","../src/widget/Fab.tsx","../src/widget/Form.tsx","../src/widget/Annotator.tsx","../src/widget/screenshot-utils.ts","../src/widget/MineList.tsx","../src/widget/ReportRow.tsx","../src/widget/Modal.tsx","../src/widget/ReportDetailView.tsx","../src/widget/CommentBubble.tsx","../src/widget/styles.ts","../src/core.ts"],"sourcesContent":["import type {\n ReportPayload,\n SubmittedReport,\n WidgetCommentRow,\n WidgetReportDetail,\n WidgetReportRow,\n} from '../types'\n\nexport interface ApiClientOptions {\n apiKey: string\n endpoint: string\n fetch?: typeof fetch\n beforeSend?: (payload: ReportPayload) => ReportPayload | false | Promise<ReportPayload | false>\n}\n\nexport interface ApiClient {\n submitReport(payload: ReportPayload): Promise<SubmittedReport>\n /** GET /v1/reports/widget/mine/ — caller's own reports on this project. */\n listMine(externalId: string): Promise<WidgetReportRow[]>\n /** GET /v1/reports/widget/<id>/ — single report + thread. */\n getReport(reportId: string, externalId: string): Promise<WidgetReportDetail>\n /** POST /v1/reports/widget/<id>/comments/ — submitter follow-up. */\n addComment(\n reportId: string,\n externalId: string,\n body: string,\n clientNonce?: string,\n ): Promise<WidgetCommentRow>\n /** PATCH /v1/reports/widget/<id>/ — close-as-resolved by the submitter. */\n closeAsResolved(\n reportId: string,\n externalId: string,\n ): Promise<WidgetReportDetail>\n}\n\nconst SCALAR_FIELDS: Array<keyof ReportPayload> = [\n 'description', 'feedback_type', 'severity', 'env', 'page_url', 'user_agent', 'capture_method',\n]\n\nexport function createApiClient(options: ApiClientOptions): ApiClient {\n // No silent fallback to a hardcoded host — a misconfigured deploy used\n // to leak reports to https://core.mhosaic.com (which doesn't even\n // resolve). Failing fast at construction makes the misconfig obvious.\n const endpoint = (options.endpoint ?? '').replace(/\\/+$/, '')\n if (!endpoint) {\n throw new Error(\n '[mhosaic-feedback] `endpoint` is required (e.g. \"https://feedback.example.com\").',\n )\n }\n const fetcher = options.fetch ?? globalThis.fetch\n\n async function submitReport(input: ReportPayload): Promise<SubmittedReport> {\n let payload: ReportPayload | false = input\n if (options.beforeSend) payload = await options.beforeSend(input)\n if (payload === false) throw new Error('Submission cancelled by beforeSend')\n\n const form = new FormData()\n for (const field of SCALAR_FIELDS) {\n form.append(field, String(payload[field]))\n }\n form.append('technical_context', JSON.stringify(payload.technical_context))\n if (payload.screenshot) form.append('screenshot', payload.screenshot, 'screenshot.png')\n // Only emit `synthetic` when truthy — a `false` value would still trigger\n // the BooleanField parser on the backend, which is fine, but omitting it\n // keeps the request shape byte-identical for existing curated submissions.\n if (payload.synthetic) form.append('synthetic', 'true')\n // v0.7: nest the identity payload so DRF's WidgetUserIdentitySerializer\n // sees a real object. FormData can't carry nested objects directly —\n // use a JSON-encoded string under the `user` key; DRF parses it via\n // the JSONField semantics on the WidgetUser fields.\n if (payload.user?.id) {\n form.append('user', JSON.stringify(payload.user))\n }\n // v0.7.3: widget_version is build-time-stamped by tsup. Lets the\n // backend show \"currently running v0.7.2\" per project on the\n // operator Companies page (per-customer version observability).\n if (payload.widget_version) {\n form.append('widget_version', payload.widget_version)\n }\n\n const response = await fetcher(`${endpoint}/api/feedback/v1/reports/`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${options.apiKey}` },\n body: form,\n })\n if (!response.ok) {\n const text = await response.text().catch(() => '')\n throw new Error(`Feedback submit failed: ${response.status} ${text}`)\n }\n return response.json() as Promise<SubmittedReport>\n }\n\n function widgetHeaders(externalId: string) {\n return {\n Authorization: `Bearer ${options.apiKey}`,\n 'X-Mhosaic-User': externalId,\n }\n }\n\n async function listMine(externalId: string): Promise<WidgetReportRow[]> {\n const response = await fetcher(`${endpoint}/api/feedback/v1/reports/widget/mine/`, {\n method: 'GET',\n headers: widgetHeaders(externalId),\n })\n if (response.status === 404) return [] // submitter has no thread yet\n if (!response.ok) {\n const text = await response.text().catch(() => '')\n throw new Error(`listMine failed: ${response.status} ${text}`)\n }\n return response.json() as Promise<WidgetReportRow[]>\n }\n\n async function getReport(\n reportId: string,\n externalId: string,\n ): Promise<WidgetReportDetail> {\n const response = await fetcher(\n `${endpoint}/api/feedback/v1/reports/widget/${reportId}/`,\n { method: 'GET', headers: widgetHeaders(externalId) },\n )\n if (!response.ok) {\n const text = await response.text().catch(() => '')\n throw new Error(`getReport failed: ${response.status} ${text}`)\n }\n return response.json() as Promise<WidgetReportDetail>\n }\n\n async function addComment(\n reportId: string,\n externalId: string,\n body: string,\n clientNonce?: string,\n ): Promise<WidgetCommentRow> {\n const response = await fetcher(\n `${endpoint}/api/feedback/v1/reports/widget/${reportId}/comments/`,\n {\n method: 'POST',\n headers: {\n ...widgetHeaders(externalId),\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n body,\n ...(clientNonce !== undefined && { client_nonce: clientNonce }),\n }),\n },\n )\n if (!response.ok) {\n const text = await response.text().catch(() => '')\n throw new Error(`addComment failed: ${response.status} ${text}`)\n }\n return response.json() as Promise<WidgetCommentRow>\n }\n\n async function closeAsResolved(\n reportId: string,\n externalId: string,\n ): Promise<WidgetReportDetail> {\n const response = await fetcher(\n `${endpoint}/api/feedback/v1/reports/widget/${reportId}/`,\n {\n method: 'PATCH',\n headers: {\n ...widgetHeaders(externalId),\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ status: 'closed' }),\n },\n )\n if (!response.ok) {\n const text = await response.text().catch(() => '')\n throw new Error(`closeAsResolved failed: ${response.status} ${text}`)\n }\n return response.json() as Promise<WidgetReportDetail>\n }\n\n return { submitReport, listMine, getReport, addComment, closeAsResolved }\n}\n","const SENSITIVE = /token|key|password|secret|auth|session|sig/i\n\nexport function sanitizeUrl(url: string): string {\n try {\n // Accept absolute URLs or root-relative paths; reject everything else\n const isAbsolute = /^https?:\\/\\//i.test(url)\n const isRootRelative = url.startsWith('/')\n if (!isAbsolute && !isRootRelative) return url\n\n const base = typeof window !== 'undefined' ? window.location.origin : 'http://localhost'\n const u = new URL(url, base)\n const clean = new URLSearchParams()\n u.searchParams.forEach((value, name) => {\n clean.set(name, SENSITIVE.test(name) ? '[redacted]' : value)\n })\n u.search = clean.toString()\n return u.toString()\n } catch {\n return url\n }\n}\n","import type { DeviceContext } from '../types'\n\nexport function collectDevice(): DeviceContext {\n const nav = navigator as Navigator & { connection?: { effectiveType?: string }; deviceMemory?: number }\n const connection = nav.connection?.effectiveType\n const deviceMemory = nav.deviceMemory\n const referrer = document.referrer || undefined\n return {\n viewport: { w: window.innerWidth, h: window.innerHeight, dpr: window.devicePixelRatio || 1 },\n screen: { w: window.screen.width, h: window.screen.height },\n platform: nav.platform,\n language: nav.language,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n timezoneOffset: new Date().getTimezoneOffset(),\n ...(connection !== undefined && { connection }),\n online: nav.onLine,\n ...(deviceMemory !== undefined && { deviceMemory }),\n hardwareConcurrency: nav.hardwareConcurrency,\n ...(referrer !== undefined && { referrer }),\n title: document.title,\n pathname: window.location.pathname,\n }\n}\n","import type { RingBuffer } from './ringBuffer'\nimport type { ConsoleEntry } from '../types'\n\ntype ConsoleLevel = 'log' | 'info' | 'warn' | 'error' | 'debug'\n\nfunction safeStringify(arg: unknown): string {\n if (arg == null) return String(arg)\n if (typeof arg === 'string') return arg\n if (typeof arg === 'number' || typeof arg === 'boolean') return String(arg)\n if (arg instanceof Error) return `${arg.name}: ${arg.message}`\n if (arg instanceof Element) return `<${arg.tagName.toLowerCase()}>`\n try {\n return JSON.stringify(arg, (_k, v) => (typeof v === 'bigint' ? v.toString() : v))\n } catch {\n try { return String(arg) } catch { return '[unserializable]' }\n }\n}\n\nexport function installConsolePatch(buffer: RingBuffer<ConsoleEntry>): () => void {\n const levels: ConsoleLevel[] = ['log', 'info', 'warn', 'error', 'debug']\n const originals: Partial<Record<ConsoleLevel, (...args: unknown[]) => void>> = {}\n for (const level of levels) {\n const original = console[level] as ((...args: unknown[]) => void) | undefined\n if (typeof original !== 'function') continue\n originals[level] = original\n console[level] = function patched(...args: unknown[]) {\n try {\n const message = args.map(safeStringify).join(' ').slice(0, 2000)\n const entry: ConsoleEntry = { level, message, ts: Date.now() }\n if (level === 'error') {\n const stack = new Error().stack\n if (stack) entry.stack = stack.split('\\n').slice(2, 8).join('\\n')\n }\n buffer.push(entry)\n } catch { /* never break console */ }\n original.apply(console, args)\n }\n }\n return () => {\n for (const [level, fn] of Object.entries(originals)) {\n (console as unknown as Record<string, (...args: unknown[]) => void>)[level] = fn\n }\n }\n}\n","import type { RingBuffer } from './ringBuffer'\nimport type { NetworkEntry } from '../types'\n\nexport function installFetchPatch(buffer: RingBuffer<NetworkEntry>, sanitize: (url: string) => string): () => void {\n if (typeof window === 'undefined' || typeof window.fetch !== 'function') return () => {}\n const original = window.fetch.bind(window)\n window.fetch = async function patched(input: RequestInfo | URL, init?: RequestInit) {\n const start = performance.now()\n const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url\n const method = (init?.method || (input instanceof Request ? input.method : 'GET')).toUpperCase()\n try {\n const response = await original(input, init)\n buffer.push({ url: sanitize(url), method, status: response.status, durationMs: Math.round(performance.now() - start), ts: Date.now() })\n return response\n } catch (err) {\n buffer.push({\n url: sanitize(url), method, status: 0,\n durationMs: Math.round(performance.now() - start),\n ts: Date.now(),\n error: err instanceof Error ? err.message : String(err),\n })\n throw err\n }\n }\n return () => { window.fetch = original }\n}\n\nexport function installXhrPatch(buffer: RingBuffer<NetworkEntry>, sanitize: (url: string) => string): () => void {\n if (typeof window === 'undefined' || typeof window.XMLHttpRequest !== 'function') return () => {}\n const Original = window.XMLHttpRequest\n const originalOpen = Original.prototype.open\n const originalSend = Original.prototype.send\n\n Original.prototype.open = function patchedOpen(this: XMLHttpRequest & { __mfb?: { method: string; url: string; start: number } }, method: string, url: string | URL) {\n this.__mfb = { method: method.toUpperCase(), url: typeof url === 'string' ? url : url.toString(), start: performance.now() }\n return originalOpen.apply(this, arguments as unknown as Parameters<typeof originalOpen>)\n }\n\n Original.prototype.send = function patchedSend(this: XMLHttpRequest & { __mfb?: { method: string; url: string; start: number } }, body?: Document | XMLHttpRequestBodyInit | null) {\n this.addEventListener('loadend', () => {\n try {\n const ctx = this.__mfb\n if (!ctx) return\n buffer.push({\n url: sanitize(ctx.url),\n method: ctx.method,\n status: this.status,\n durationMs: Math.round(performance.now() - ctx.start),\n ts: Date.now(),\n })\n } catch { /* noop */ }\n })\n return originalSend.call(this, body ?? null)\n }\n\n return () => {\n Original.prototype.open = originalOpen\n Original.prototype.send = originalSend\n }\n}\n","import type { RingBuffer } from './ringBuffer'\nimport type { ErrorEntry } from '../types'\n\nexport function installErrorHandlers(buffer: RingBuffer<ErrorEntry>): () => void {\n if (typeof window === 'undefined') return () => {}\n const onError = (e: ErrorEvent) => {\n const stack = e.error instanceof Error ? e.error.stack : undefined\n buffer.push({\n message: e.message || 'Unknown error',\n ...(stack !== undefined && { stack }),\n ts: Date.now(),\n source: 'window.error',\n })\n }\n const onRejection = (e: PromiseRejectionEvent) => {\n const reason = e.reason\n const message =\n reason instanceof Error ? reason.message :\n typeof reason === 'string' ? reason :\n (() => { try { return JSON.stringify(reason) } catch { return String(reason) } })()\n const stack = reason instanceof Error ? reason.stack : undefined\n buffer.push({\n message,\n ...(stack !== undefined && { stack }),\n ts: Date.now(),\n source: 'unhandledrejection',\n })\n }\n window.addEventListener('error', onError)\n window.addEventListener('unhandledrejection', onRejection)\n return () => {\n window.removeEventListener('error', onError)\n window.removeEventListener('unhandledrejection', onRejection)\n }\n}\n","export interface PerformanceSnapshot {\n navigation?: { type: string; duration: number }\n longTasks: { duration: number; startTime: number }[]\n slowResources: { name: string; duration: number; initiatorType: string }[]\n}\n\nexport function createPerformanceCollector(slowResourceMs = 1000) {\n const longTasks: PerformanceSnapshot['longTasks'] = []\n const slowResources: PerformanceSnapshot['slowResources'] = []\n let observer: PerformanceObserver | null = null\n\n if (typeof PerformanceObserver !== 'undefined') {\n try {\n observer = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n if (entry.entryType === 'longtask') {\n longTasks.push({ duration: entry.duration, startTime: entry.startTime })\n while (longTasks.length > 20) longTasks.shift()\n } else if (entry.entryType === 'resource') {\n const e = entry as PerformanceResourceTiming\n if (e.duration > slowResourceMs) {\n slowResources.push({ name: e.name, duration: e.duration, initiatorType: e.initiatorType })\n while (slowResources.length > 20) slowResources.shift()\n }\n }\n }\n })\n observer.observe({ entryTypes: ['longtask', 'resource'] })\n } catch { /* unsupported */ }\n }\n\n return {\n snapshot(): PerformanceSnapshot {\n const nav = typeof performance !== 'undefined' ? performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming | undefined : undefined\n const navigation = nav ? { type: nav.type, duration: nav.duration } : undefined\n return {\n ...(navigation !== undefined && { navigation }),\n longTasks: longTasks.slice(),\n slowResources: slowResources.slice(),\n }\n },\n dispose() { observer?.disconnect() },\n }\n}\n","export class RingBuffer<T> {\n private items: T[] = []\n constructor(private readonly max: number) {}\n\n push(item: T): void {\n this.items.push(item)\n while (this.items.length > this.max) this.items.shift()\n }\n\n snapshot(): T[] {\n return this.items.slice()\n }\n\n clear(): void {\n this.items.length = 0\n }\n}\n","import { sanitizeUrl } from './urlSanitizer'\nimport { collectDevice } from './device'\nimport { installConsolePatch } from './console'\nimport { installFetchPatch, installXhrPatch } from './network'\nimport { installErrorHandlers } from './errors'\nimport { createPerformanceCollector } from './performance'\nimport { RingBuffer } from './ringBuffer'\nimport type { CapturedContext, ConsoleEntry, ErrorEntry, NetworkEntry } from '../types'\n\nexport interface CaptureOptions {\n sanitizeUrl?: (url: string) => string\n maxConsole?: number\n maxNetwork?: number\n maxErrors?: number\n}\n\nexport interface CaptureHandle {\n snapshot(): CapturedContext\n clear(): void\n dispose(): void\n}\n\nexport function installCapture(options: CaptureOptions = {}): CaptureHandle {\n const { maxConsole = 50, maxNetwork = 50, maxErrors = 20 } = options\n const sanitize = options.sanitizeUrl ?? sanitizeUrl\n\n const consoleBuf = new RingBuffer<ConsoleEntry>(maxConsole)\n const networkBuf = new RingBuffer<NetworkEntry>(maxNetwork)\n const errorBuf = new RingBuffer<ErrorEntry>(maxErrors)\n\n const uninstallConsole = installConsolePatch(consoleBuf)\n const uninstallFetch = installFetchPatch(networkBuf, sanitize)\n const uninstallXhr = installXhrPatch(networkBuf, sanitize)\n const uninstallErrors = installErrorHandlers(errorBuf)\n const perf = createPerformanceCollector()\n\n return {\n snapshot(): CapturedContext {\n return {\n consoleLogs: consoleBuf.snapshot(),\n networkRequests: networkBuf.snapshot(),\n errors: errorBuf.snapshot(),\n device: collectDevice(),\n capturedAt: Date.now(),\n }\n },\n clear() {\n consoleBuf.clear(); networkBuf.clear(); errorBuf.clear()\n },\n dispose() {\n uninstallConsole(); uninstallFetch(); uninstallXhr(); uninstallErrors()\n perf.dispose()\n },\n }\n}\n","export function isMaskedElement(el: Element): boolean {\n return el.hasAttribute('data-mfb-mask') || el.classList.contains('mfb-mask')\n}\n\nexport function prepareMaskMatcher(selectors: string[]): (el: Element) => boolean {\n const joined = selectors.join(',').trim()\n if (!joined) return isMaskedElement\n return (el: Element) => isMaskedElement(el) || el.matches(joined)\n}\n\nexport interface ScreenshotOptions {\n mask?: string[]\n maxBytes?: number\n}\n\nexport async function takeScreenshot(target: Element, options: ScreenshotOptions = {}): Promise<Blob | null> {\n if (typeof document === 'undefined') return null\n try {\n const html2canvas = (await import('html2canvas-pro')).default\n const matcher = prepareMaskMatcher(options.mask ?? [])\n const canvas = await html2canvas(target as HTMLElement, {\n ignoreElements: (el) => matcher(el),\n useCORS: true,\n logging: false,\n backgroundColor: null,\n })\n return await new Promise<Blob | null>((resolve) => canvas.toBlob(resolve, 'image/png'))\n } catch {\n return null\n }\n}\n","export const DEFAULT_STRINGS = {\n 'fab.label': 'Send feedback',\n 'form.title': 'Send feedback',\n 'form.description.label': 'What happened?',\n 'form.description.placeholder': 'Describe the issue or idea in one or two sentences.',\n 'form.type.label': 'Type',\n 'form.severity.label': 'Severity',\n 'form.submit': 'Send',\n 'form.cancel': 'Cancel',\n 'form.close': 'Close',\n 'form.submitting': 'Sending…',\n 'form.success': 'Thanks — your feedback was sent.',\n 'form.error': 'Could not send. Please try again.',\n 'form.description.required': 'Please describe the issue before sending.',\n 'form.screenshot.label': 'Screenshot',\n 'form.screenshot.cta_click': 'Click',\n 'form.screenshot.cta_rest': 'drop, or paste an image',\n 'form.screenshot.formats': 'PNG, JPEG or WebP — up to 10 MB',\n 'form.screenshot.remove': 'Remove screenshot',\n 'form.screenshot.annotate': 'Annotate',\n 'form.screenshot.error_type': 'Unsupported file type. Use PNG, JPEG or WebP.',\n 'form.screenshot.error_size': 'File too large (max {max} MB).',\n 'form.context.label': 'Page',\n 'type.bug': 'Bug',\n 'type.feature': 'Feature request',\n 'type.question': 'Question',\n 'type.praise': 'Praise',\n 'type.typo': 'Typo',\n 'severity.blocker': 'Blocker',\n 'severity.high': 'High',\n 'severity.medium': 'Medium',\n 'severity.low': 'Low',\n 'annotator.title': 'Annotate screenshot',\n 'annotator.tool.rectangle': 'Rectangle',\n 'annotator.tool.arrow': 'Arrow',\n 'annotator.tool.freehand': 'Freehand',\n 'annotator.tool.text': 'Text',\n 'annotator.text_prompt': 'Enter text:',\n 'annotator.undo': 'Undo',\n 'annotator.clear': 'Clear all',\n 'annotator.count_suffix': 'annotations',\n 'annotator.loading': 'Loading…',\n 'annotator.apply': 'Apply',\n 'annotator.applying': 'Applying…',\n 'tab.send': 'Send',\n 'tab.mine': 'My reports',\n 'mine.empty.title': 'No reports yet',\n 'mine.empty.body': 'Once you send feedback you can follow the thread here.',\n 'mine.refresh': 'Refresh',\n 'mine.loading': 'Loading…',\n 'mine.error': 'Could not load your reports.',\n 'mine.replies_one': '1 reply',\n 'mine.replies_many': '{count} replies',\n 'detail.back': 'Back',\n 'detail.thread': 'Conversation',\n 'detail.no_replies': 'No replies yet — we’ll let you know when an operator responds.',\n 'detail.compose_placeholder': 'Add a follow-up reply…',\n 'detail.compose_send': 'Reply',\n 'detail.compose_sending': 'Sending…',\n 'detail.close_cta': 'Mark as resolved',\n 'detail.close_busy': 'Marking…',\n 'detail.history': 'Status history',\n 'detail.context.submitted_at': 'Submitted',\n 'detail.context.page': 'Page',\n 'detail.context.capture.manual': 'Manual capture',\n 'detail.context.capture.html2canvas': 'Auto capture',\n 'detail.context.capture.display_media': 'Screen share',\n 'detail.context.capture.none': 'No screenshot',\n 'detail.author.staff': 'Operator',\n 'detail.author.mcp': 'Mhosaic Team',\n 'detail.author.system': 'System',\n 'status.new': 'New',\n 'status.in_progress': 'In progress',\n 'status.awaiting_validation': 'Awaiting your validation',\n 'status.closed': 'Closed',\n 'status.rejected': 'Rejected',\n 'status.duplicate': 'Duplicate',\n 'status.wontfix': 'Won’t fix',\n}\n\nexport type StringKey = keyof typeof DEFAULT_STRINGS\n\nconst FRENCH_STRINGS: Record<StringKey, string> = {\n 'fab.label': 'Envoyer un commentaire',\n 'form.title': 'Envoyer un commentaire',\n 'form.description.label': 'Qu’est-il arrivé ?',\n 'form.description.placeholder': 'Décrivez le problème ou l’idée en une ou deux phrases.',\n 'form.type.label': 'Type',\n 'form.severity.label': 'Sévérité',\n 'form.submit': 'Envoyer',\n 'form.cancel': 'Annuler',\n 'form.close': 'Fermer',\n 'form.submitting': 'Envoi…',\n 'form.success': 'Merci — votre commentaire a été envoyé.',\n 'form.error': 'Échec d’envoi. Veuillez réessayer.',\n 'form.description.required': 'Décrivez le problème avant d’envoyer.',\n 'form.screenshot.label': 'Capture d’écran',\n 'form.screenshot.cta_click': 'Cliquez',\n 'form.screenshot.cta_rest': 'déposez ou collez une image',\n 'form.screenshot.formats': 'PNG, JPEG ou WebP — jusqu’à 10 Mo',\n 'form.screenshot.remove': 'Retirer la capture',\n 'form.screenshot.annotate': 'Annoter',\n 'form.screenshot.error_type': 'Format non supporté. Utilisez PNG, JPEG ou WebP.',\n 'form.screenshot.error_size': 'Fichier trop volumineux (max {max} Mo).',\n 'form.context.label': 'Page',\n 'type.bug': 'Bogue',\n 'type.feature': 'Suggestion',\n 'type.question': 'Question',\n 'type.praise': 'Compliment',\n 'type.typo': 'Coquille',\n 'severity.blocker': 'Bloquant',\n 'severity.high': 'Élevée',\n 'severity.medium': 'Moyenne',\n 'severity.low': 'Faible',\n 'annotator.title': 'Annoter la capture',\n 'annotator.tool.rectangle': 'Rectangle',\n 'annotator.tool.arrow': 'Flèche',\n 'annotator.tool.freehand': 'Dessin libre',\n 'annotator.tool.text': 'Texte',\n 'annotator.text_prompt': 'Entrez le texte :',\n 'annotator.undo': 'Annuler',\n 'annotator.clear': 'Tout effacer',\n 'annotator.count_suffix': 'annotations',\n 'annotator.loading': 'Chargement…',\n 'annotator.apply': 'Appliquer',\n 'annotator.applying': 'Application…',\n 'tab.send': 'Envoyer',\n 'tab.mine': 'Mes rapports',\n 'mine.empty.title': 'Aucun rapport',\n 'mine.empty.body': 'Après votre premier envoi vous pourrez suivre la conversation ici.',\n 'mine.refresh': 'Actualiser',\n 'mine.loading': 'Chargement…',\n 'mine.error': 'Impossible de charger vos rapports.',\n 'mine.replies_one': '1 réponse',\n 'mine.replies_many': '{count} réponses',\n 'detail.back': 'Retour',\n 'detail.thread': 'Conversation',\n 'detail.no_replies': 'Pas encore de réponse — vous serez notifié dès qu’un opérateur répondra.',\n 'detail.compose_placeholder': 'Ajouter une réponse…',\n 'detail.compose_send': 'Répondre',\n 'detail.compose_sending': 'Envoi…',\n 'detail.close_cta': 'Marquer comme résolu',\n 'detail.close_busy': 'Validation…',\n 'detail.history': 'Historique du statut',\n 'detail.context.submitted_at': 'Envoyé',\n 'detail.context.page': 'Page',\n 'detail.context.capture.manual': 'Capture manuelle',\n 'detail.context.capture.html2canvas': 'Capture automatique',\n 'detail.context.capture.display_media': 'Partage d’écran',\n 'detail.context.capture.none': 'Sans capture',\n 'detail.author.staff': 'Opérateur',\n 'detail.author.mcp': 'Équipe Mhosaic',\n 'detail.author.system': 'Système',\n 'status.new': 'Nouveau',\n 'status.in_progress': 'En cours',\n 'status.awaiting_validation': 'En attente de validation',\n 'status.closed': 'Fermé',\n 'status.rejected': 'Rejeté',\n 'status.duplicate': 'Doublon',\n 'status.wontfix': 'Non corrigé',\n}\n\nconst LOCALE_PACKS: Record<string, Record<StringKey, string>> = {\n fr: FRENCH_STRINGS,\n}\n\ninterface ResolveOptions {\n locale?: string\n}\n\nfunction packFor(locale: string | undefined): Record<StringKey, string> | null {\n if (!locale) return null\n // Match the language subtag only (fr-CA, fr-FR → fr).\n const tag = locale.toLowerCase().split(/[-_]/)[0]!\n return LOCALE_PACKS[tag] ?? null\n}\n\nexport function resolveStrings(\n overrides: Record<string, string>,\n options: ResolveOptions = {},\n): Record<StringKey, string> {\n const localePack = packFor(options.locale) ?? {}\n return {\n ...DEFAULT_STRINGS,\n ...localePack,\n ...overrides,\n } as Record<StringKey, string>\n}\n","import { h, render } from 'preact'\nimport { useCallback, useState } from 'preact/hooks'\n\nimport type { ApiClient } from '../api/client'\nimport { Fab } from './Fab'\nimport { Form, type FormValues } from './Form'\nimport { MineList } from './MineList'\nimport { Modal } from './Modal'\nimport { ReportDetailView } from './ReportDetailView'\nimport { WIDGET_STYLES } from './styles'\nimport type { StringKey } from './i18n'\n\nexport interface MountOptions {\n host: HTMLElement\n strings: Record<StringKey, string>\n showFAB: boolean\n onSubmit: (values: FormValues) => Promise<void>\n /** v0.7: ApiClient for the conversation-loop reader UI. Optional so\n * the synthetic / auto-error path keeps working with no host changes. */\n api?: ApiClient\n /** v0.7: callback that returns the host's identified user id, or\n * undefined when no `identify()` has been called yet. The \"My\n * reports\" tab + FAB visibility both depend on this. */\n getExternalId?: () => string | undefined\n}\n\nexport interface MountHandle {\n open(): void\n close(): void\n dispose(): void\n /** Force a re-render — e.g. after the host calls `identify()` so\n * the FAB becomes visible without a page reload. */\n notifyIdentityChanged(): void\n}\n\ntype Status = 'idle' | 'submitting' | 'error' | 'success'\ntype Tab = 'send' | 'mine'\ninterface State {\n open: boolean\n status: Status\n error?: string\n tab: Tab\n /** When set, MineList is replaced by the detail view for that report id. */\n selectedReportId?: string\n}\n\nexport function mountWidget(options: MountOptions): MountHandle {\n const shadow = options.host.attachShadow({ mode: 'open' })\n const style = document.createElement('style')\n style.textContent = WIDGET_STYLES\n shadow.appendChild(style)\n const mountPoint = document.createElement('div')\n shadow.appendChild(mountPoint)\n\n let currentState: State = { open: false, status: 'idle', tab: 'send' }\n\n function rerender(state: State) {\n currentState = state\n render(h(Root, { state }), mountPoint)\n }\n\n /** Strip selectedReportId rather than reassigning `undefined` — under\n * exactOptionalPropertyTypes the explicit `undefined` is not assignable\n * back into the optional slot. */\n function clearSelected(s: State): State {\n const { selectedReportId: _drop, ...rest } = s\n void _drop\n return rest\n }\n\n function Root({ state }: { state: State }) {\n const handleSubmit = useCallback(async (values: FormValues) => {\n rerender({ ...currentState, status: 'submitting' })\n try {\n await options.onSubmit(values)\n rerender({ ...currentState, status: 'success' })\n setTimeout(\n () =>\n rerender({\n ...currentState,\n open: false,\n status: 'idle',\n // After a successful submit, jump the user to \"My reports\" so\n // they immediately see their just-filed report in the list\n // (and the conversation that's about to start).\n tab: options.api ? 'mine' : 'send',\n }),\n 1200,\n )\n } catch (err) {\n rerender({\n ...currentState,\n status: 'error',\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }, [])\n\n const externalId = options.getExternalId?.()\n // FAB-visibility gate: when the host opts into identity (passes a\n // getExternalId callback), require a resolved user before the FAB\n // appears — that's the \"only allowed users see the widget\" semantics\n // Victor signed off on. Legacy hosts that never wired identity stay\n // on the old \"showFAB → visible\" path.\n const fabVisible =\n options.showFAB &&\n (options.getExternalId === undefined ? true : Boolean(externalId))\n const showMineTab = Boolean(options.api && externalId)\n\n return (\n <>\n {fabVisible && (\n <Fab\n label={options.strings['fab.label']}\n onClick={() => rerender({ ...currentState, open: true })}\n />\n )}\n {state.open && (\n <Modal\n onDismiss={() =>\n rerender(clearSelected({ ...currentState, open: false, status: 'idle' }))\n }\n closeLabel={options.strings['form.close']}\n >\n {showMineTab && (\n <div class=\"tab-strip\" role=\"tablist\">\n <button\n type=\"button\"\n role=\"tab\"\n aria-selected={state.tab === 'send'}\n class={`tab-button ${state.tab === 'send' ? 'is-active' : ''}`}\n onClick={() =>\n rerender(clearSelected({ ...currentState, tab: 'send' }))\n }\n >\n {options.strings['tab.send']}\n </button>\n <button\n type=\"button\"\n role=\"tab\"\n aria-selected={state.tab === 'mine'}\n class={`tab-button ${state.tab === 'mine' ? 'is-active' : ''}`}\n onClick={() =>\n rerender(clearSelected({ ...currentState, tab: 'mine' }))\n }\n >\n {options.strings['tab.mine']}\n </button>\n </div>\n )}\n {state.tab === 'send' && (\n <Form\n strings={options.strings}\n onSubmit={handleSubmit}\n onCancel={() =>\n rerender({ ...currentState, open: false, status: 'idle' })\n }\n status={state.status}\n {...(state.error !== undefined && { errorMessage: state.error })}\n />\n )}\n {state.tab === 'mine' && options.api && externalId && !state.selectedReportId && (\n <MineList\n api={options.api}\n externalId={externalId}\n strings={options.strings}\n onSelect={(row) =>\n rerender({ ...currentState, selectedReportId: row.id })\n }\n />\n )}\n {state.tab === 'mine' && options.api && externalId && state.selectedReportId && (\n <ReportDetailView\n api={options.api}\n externalId={externalId}\n reportId={state.selectedReportId}\n strings={options.strings}\n onBack={() =>\n rerender(clearSelected({ ...currentState }))\n }\n />\n )}\n </Modal>\n )}\n </>\n )\n }\n\n rerender(currentState)\n\n return {\n open() {\n rerender({ ...currentState, open: true })\n },\n close() {\n rerender({ ...currentState, open: false, status: 'idle' })\n },\n dispose() {\n render(null, mountPoint)\n options.host.innerHTML = ''\n },\n notifyIdentityChanged() {\n rerender({ ...currentState })\n },\n }\n}\n","import { h } from 'preact'\n\ninterface FabProps {\n label: string\n onClick: () => void\n}\n\n/**\n * Custom chat-bubble glyph at 24px. Emoji rendered inconsistently across\n * platforms (Apple's bubble is blue, Windows' is monochrome, Android's\n * green) and couldn't inherit color — replaced with a single-color SVG\n * that picks up `currentColor` from the FAB's accent-contrast variable.\n */\nfunction ChatBubbleIcon() {\n return (\n <svg\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <path\n d=\"M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z\"\n fill=\"currentColor\"\n />\n </svg>\n )\n}\n\nexport function Fab({ label, onClick }: FabProps) {\n return (\n <button type=\"button\" class=\"fab\" aria-label={label} title={label} onClick={onClick}>\n <ChatBubbleIcon />\n </button>\n )\n}\n","import { h } from 'preact'\nimport { useEffect, useRef, useState } from 'preact/hooks'\n\nimport type { FeedbackSeverity, FeedbackType } from '../types'\nimport { Annotator } from './Annotator'\nimport type { StringKey } from './i18n'\nimport { truncateUrl, validateScreenshotFile } from './screenshot-utils'\n\nexport type FormStatus = 'idle' | 'submitting' | 'error' | 'success'\n\nexport interface FormValues {\n description: string\n feedback_type: FeedbackType\n severity: FeedbackSeverity\n /** When set, the host should send this blob instead of running html2canvas. */\n screenshot?: Blob\n}\n\ninterface FormProps {\n strings: Record<StringKey, string>\n onSubmit: (values: FormValues) => void\n onCancel: () => void\n status: FormStatus\n errorMessage?: string\n}\n\nconst TYPES: FeedbackType[] = ['bug', 'feature', 'question', 'praise', 'typo']\nconst SEVERITIES: FeedbackSeverity[] = ['blocker', 'high', 'medium', 'low']\n\nexport function Form({ strings, onSubmit, onCancel, status, errorMessage }: FormProps) {\n const [description, setDescription] = useState('')\n const [feedbackType, setFeedbackType] = useState<FeedbackType>('bug')\n const [severity, setSeverity] = useState<FeedbackSeverity>('medium')\n const [localError, setLocalError] = useState('')\n const [screenshotBlob, setScreenshotBlob] = useState<Blob | null>(null)\n const [screenshotPreview, setScreenshotPreview] = useState<string | null>(null)\n const [isDragOver, setIsDragOver] = useState(false)\n const [annotatorOpen, setAnnotatorOpen] = useState(false)\n const fileInputRef = useRef<HTMLInputElement | null>(null)\n const dropZoneRef = useRef<HTMLDivElement | null>(null)\n\n const submitting = status === 'submitting'\n const submitLabel = submitting ? strings['form.submitting'] : strings['form.submit']\n const pageUrl = typeof window !== 'undefined' ? window.location.href : ''\n\n // Revoke any object URL when the preview changes or the form unmounts.\n useEffect(() => {\n return () => {\n if (screenshotPreview) URL.revokeObjectURL(screenshotPreview)\n }\n }, [screenshotPreview])\n\n const acceptFile = (file: File | Blob) => {\n setLocalError('')\n if (file instanceof File) {\n const err = validateScreenshotFile(file)\n if (err) {\n setLocalError(\n err.kind === 'type'\n ? strings['form.screenshot.error_type']\n : strings['form.screenshot.error_size'].replace('{max}', String(err.maxMb)),\n )\n return\n }\n }\n if (screenshotPreview) URL.revokeObjectURL(screenshotPreview)\n setScreenshotBlob(file)\n setScreenshotPreview(URL.createObjectURL(file))\n }\n\n const clearScreenshot = () => {\n if (screenshotPreview) URL.revokeObjectURL(screenshotPreview)\n setScreenshotPreview(null)\n setScreenshotBlob(null)\n setLocalError('')\n if (fileInputRef.current) fileInputRef.current.value = ''\n }\n\n const handleFileInputChange = (e: Event) => {\n const file = (e.target as HTMLInputElement).files?.[0]\n if (file) acceptFile(file)\n }\n\n const handleDragOver = (e: DragEvent) => {\n e.preventDefault()\n e.stopPropagation()\n setIsDragOver(true)\n }\n const handleDragLeave = (e: DragEvent) => {\n e.preventDefault()\n e.stopPropagation()\n if (e.currentTarget === e.target) setIsDragOver(false)\n }\n const handleDrop = (e: DragEvent) => {\n e.preventDefault()\n e.stopPropagation()\n setIsDragOver(false)\n const file = e.dataTransfer?.files?.[0]\n if (file) acceptFile(file)\n }\n\n // Clipboard paste, scoped to the dropzone — so we don't hijack paste from\n // textareas elsewhere in the page or in our own description field.\n useEffect(() => {\n const zone = dropZoneRef.current\n if (!zone) return\n const onPaste = (e: ClipboardEvent) => {\n const items = e.clipboardData?.items\n if (!items) return\n for (const item of Array.from(items)) {\n if (item.kind === 'file' && item.type.startsWith('image/')) {\n const file = item.getAsFile()\n if (file) {\n e.preventDefault()\n acceptFile(file)\n return\n }\n }\n }\n }\n zone.addEventListener('paste', onPaste)\n return () => zone.removeEventListener('paste', onPaste)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [screenshotPreview])\n\n const handleAnnotated = (annotated: Blob) => {\n if (screenshotPreview) URL.revokeObjectURL(screenshotPreview)\n setScreenshotBlob(annotated)\n setScreenshotPreview(URL.createObjectURL(annotated))\n setAnnotatorOpen(false)\n }\n\n const handleSubmit = (e: Event) => {\n e.preventDefault()\n if (!description.trim()) {\n setLocalError(strings['form.description.required'])\n return\n }\n setLocalError('')\n const values: FormValues = {\n description: description.trim(),\n feedback_type: feedbackType,\n severity,\n }\n if (screenshotBlob) values.screenshot = screenshotBlob\n onSubmit(values)\n }\n\n return (\n <form onSubmit={handleSubmit}>\n <h2>{strings['form.title']}</h2>\n\n <div class=\"field\">\n <label for=\"mfb-desc\">{strings['form.description.label']}</label>\n <textarea\n id=\"mfb-desc\"\n value={description}\n placeholder={strings['form.description.placeholder']}\n onInput={(e) => setDescription((e.target as HTMLTextAreaElement).value)}\n />\n </div>\n\n <div class=\"row\">\n <div class=\"field\">\n <label for=\"mfb-type\">{strings['form.type.label']}</label>\n <select\n id=\"mfb-type\"\n value={feedbackType}\n onChange={(e) => setFeedbackType((e.target as HTMLSelectElement).value as FeedbackType)}\n >\n {TYPES.map((t) => <option value={t}>{strings[`type.${t}` as StringKey]}</option>)}\n </select>\n </div>\n <div class=\"field\">\n <label for=\"mfb-sev\">{strings['form.severity.label']}</label>\n <select\n id=\"mfb-sev\"\n value={severity}\n onChange={(e) => setSeverity((e.target as HTMLSelectElement).value as FeedbackSeverity)}\n >\n {SEVERITIES.map((s) => <option value={s}>{strings[`severity.${s}` as StringKey]}</option>)}\n </select>\n </div>\n </div>\n\n <div class=\"field\">\n <label>{strings['form.screenshot.label']}</label>\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\"image/png,image/jpeg,image/webp\"\n class=\"mfb-sr-only\"\n onChange={handleFileInputChange}\n aria-hidden=\"true\"\n tabIndex={-1}\n />\n {screenshotPreview ? (\n <div class=\"screenshot-preview\">\n <img src={screenshotPreview} alt=\"\" />\n <button\n type=\"button\"\n class=\"screenshot-remove\"\n onClick={clearScreenshot}\n aria-label={strings['form.screenshot.remove']}\n >\n ×\n </button>\n <button\n type=\"button\"\n class=\"btn screenshot-annotate\"\n onClick={() => setAnnotatorOpen(true)}\n >\n {strings['form.screenshot.annotate']}\n </button>\n </div>\n ) : (\n <div\n ref={dropZoneRef}\n class={`screenshot-dropzone ${isDragOver ? 'is-dragover' : ''}`}\n tabIndex={0}\n role=\"button\"\n aria-label={strings['form.screenshot.label']}\n onClick={() => fileInputRef.current?.click()}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n fileInputRef.current?.click()\n }\n }}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n onDrop={handleDrop}\n >\n <div class=\"screenshot-cta\">\n <strong>{strings['form.screenshot.cta_click']}</strong>\n {', '}\n {strings['form.screenshot.cta_rest']}\n </div>\n <div class=\"screenshot-formats\">{strings['form.screenshot.formats']}</div>\n </div>\n )}\n </div>\n\n {pageUrl && (\n <div class=\"page-context\" title={pageUrl}>\n <span class=\"page-context-label\">{strings['form.context.label']}</span>\n <span class=\"page-context-url\">{truncateUrl(pageUrl, 90)}</span>\n </div>\n )}\n\n {localError && <div class=\"error\">{localError}</div>}\n {status === 'error' && errorMessage && <div class=\"error\">{errorMessage}</div>}\n {status === 'success' && <div class=\"success\">{strings['form.success']}</div>}\n\n <div class=\"actions\">\n <button type=\"button\" class=\"btn\" onClick={onCancel} disabled={submitting}>{strings['form.cancel']}</button>\n <button type=\"submit\" class=\"btn btn--primary\" disabled={submitting}>{submitLabel}</button>\n </div>\n\n {annotatorOpen && screenshotBlob && (\n <Annotator\n imageBlob={screenshotBlob}\n strings={strings}\n onCancel={() => setAnnotatorOpen(false)}\n onSave={handleAnnotated}\n />\n )}\n </form>\n )\n}\n","/**\n * Annotator — minimal screenshot annotation modal (v0.6.0).\n *\n * Ported from thepnr-platform's FeedbackAnnotator. Custom canvas, no\n * external lib. Supports rectangle, arrow, freehand and text shapes with\n * undo / clear-all / color-picker. The Save call rasterizes the original\n * image + every shape onto a fresh canvas and returns a PNG Blob.\n *\n * Inline SVG icons (no lucide) and inline CSS classes (the widget shadow\n * root sees the styles in styles.ts).\n */\n\nimport { h } from 'preact'\nimport { useEffect, useRef, useState } from 'preact/hooks'\n\nimport type { StringKey } from './i18n'\n\ntype Tool = 'rectangle' | 'arrow' | 'freehand' | 'text'\n\ninterface BaseShape {\n color: string\n lineWidth: number\n}\n\ninterface RectShape extends BaseShape {\n kind: 'rectangle'\n x: number\n y: number\n w: number\n h: number\n}\n\ninterface ArrowShape extends BaseShape {\n kind: 'arrow'\n x1: number\n y1: number\n x2: number\n y2: number\n}\n\ninterface FreehandShape extends BaseShape {\n kind: 'freehand'\n points: Array<{ x: number; y: number }>\n}\n\ninterface TextShape extends BaseShape {\n kind: 'text'\n x: number\n y: number\n text: string\n fontSize: number\n}\n\ntype Shape = RectShape | ArrowShape | FreehandShape | TextShape\n\nconst COLORS: string[] = ['#ef4444', '#f59e0b', '#10b981', '#3b82f6', '#ffffff']\n\n// ---------------------------------------------------------------------------\n// Drawing helpers\n// ---------------------------------------------------------------------------\n\nfunction drawShape(ctx: CanvasRenderingContext2D, shape: Shape) {\n ctx.save()\n ctx.strokeStyle = shape.color\n ctx.fillStyle = shape.color\n ctx.lineWidth = shape.lineWidth\n ctx.lineCap = 'round'\n ctx.lineJoin = 'round'\n\n if (shape.kind === 'rectangle') {\n ctx.strokeRect(shape.x, shape.y, shape.w, shape.h)\n } else if (shape.kind === 'arrow') {\n drawArrow(ctx, shape.x1, shape.y1, shape.x2, shape.y2)\n } else if (shape.kind === 'freehand') {\n if (shape.points.length < 2) {\n ctx.restore()\n return\n }\n ctx.beginPath()\n ctx.moveTo(shape.points[0]!.x, shape.points[0]!.y)\n for (let i = 1; i < shape.points.length; i++) {\n ctx.lineTo(shape.points[i]!.x, shape.points[i]!.y)\n }\n ctx.stroke()\n } else if (shape.kind === 'text') {\n ctx.font = `bold ${shape.fontSize}px -apple-system, BlinkMacSystemFont, sans-serif`\n ctx.textBaseline = 'top'\n const metrics = ctx.measureText(shape.text)\n const padding = 4\n const w = metrics.width + padding * 2\n const hh = shape.fontSize + padding * 2\n ctx.fillStyle = 'rgba(0, 0, 0, 0.6)'\n ctx.fillRect(shape.x - padding, shape.y - padding, w, hh)\n ctx.fillStyle = shape.color\n ctx.fillText(shape.text, shape.x, shape.y)\n }\n ctx.restore()\n}\n\nfunction drawArrow(\n ctx: CanvasRenderingContext2D,\n x1: number,\n y1: number,\n x2: number,\n y2: number,\n) {\n const headLen = 14\n const angle = Math.atan2(y2 - y1, x2 - x1)\n ctx.beginPath()\n ctx.moveTo(x1, y1)\n ctx.lineTo(x2, y2)\n ctx.stroke()\n ctx.beginPath()\n ctx.moveTo(x2, y2)\n ctx.lineTo(\n x2 - headLen * Math.cos(angle - Math.PI / 6),\n y2 - headLen * Math.sin(angle - Math.PI / 6),\n )\n ctx.lineTo(\n x2 - headLen * Math.cos(angle + Math.PI / 6),\n y2 - headLen * Math.sin(angle + Math.PI / 6),\n )\n ctx.closePath()\n ctx.fill()\n}\n\n// ---------------------------------------------------------------------------\n// Inline icons\n// ---------------------------------------------------------------------------\n\nconst Icon = {\n rect: (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <rect x=\"2\" y=\"3\" width=\"12\" height=\"10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" />\n </svg>\n ),\n arrow: (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M2 8h11M9 4l4 4-4 4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n ),\n pencil: (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M11.5 2.5l2 2L5 13H3v-2l8.5-8.5z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linejoin=\"round\" />\n </svg>\n ),\n text: (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M3 3h10M8 3v10M5 13h6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" />\n </svg>\n ),\n undo: (\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M4 7l3-3M4 7l3 3M4 7h6a3 3 0 0 1 0 6H7\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n ),\n trash: (\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M3 4h10M6 4V2.5h4V4M5 4l.5 9h5L11 4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n ),\n close: (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M4 4l8 8M12 4l-8 8\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" />\n </svg>\n ),\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport interface AnnotatorProps {\n imageBlob: Blob\n strings: Record<StringKey, string>\n onSave: (annotated: Blob) => void\n onCancel: () => void\n}\n\nexport function Annotator({ imageBlob, strings, onSave, onCancel }: AnnotatorProps) {\n const canvasRef = useRef<HTMLCanvasElement | null>(null)\n const containerRef = useRef<HTMLDivElement | null>(null)\n const imageRef = useRef<HTMLImageElement | null>(null)\n const [tool, setTool] = useState<Tool>('rectangle')\n const [color, setColor] = useState<string>(COLORS[0]!)\n const [shapes, setShapes] = useState<Shape[]>([])\n const isDrawingRef = useRef(false)\n const [draftShape, setDraftShape] = useState<Shape | null>(null)\n const [imageLoaded, setImageLoaded] = useState(false)\n const [saving, setSaving] = useState(false)\n const scaleRef = useRef(1)\n\n // Esc closes the annotator. The parent Modal has its own Esc handler but\n // bails when it sees an .annotator-backdrop in the shadow root, so the\n // top-most dialog wins.\n useEffect(() => {\n const onKey = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n e.stopPropagation()\n onCancel()\n }\n }\n window.addEventListener('keydown', onKey)\n return () => window.removeEventListener('keydown', onKey)\n }, [onCancel])\n\n // Decode the source image once, into an offscreen <img> we keep around for\n // every redraw.\n useEffect(() => {\n const url = URL.createObjectURL(imageBlob)\n const img = new Image()\n img.onload = () => {\n imageRef.current = img\n setImageLoaded(true)\n }\n img.src = url\n return () => URL.revokeObjectURL(url)\n }, [imageBlob])\n\n // Once the image loads, fit the canvas to whatever room the modal has.\n useEffect(() => {\n if (\n !imageLoaded ||\n !canvasRef.current ||\n !imageRef.current ||\n !containerRef.current\n ) {\n return\n }\n const img = imageRef.current\n const container = containerRef.current\n const maxW = container.clientWidth - 16\n const maxH = window.innerHeight * 0.6\n const fitScale = Math.min(maxW / img.width, maxH / img.height, 1)\n scaleRef.current = fitScale\n\n const canvas = canvasRef.current\n canvas.width = img.width\n canvas.height = img.height\n canvas.style.width = `${img.width * fitScale}px`\n canvas.style.height = `${img.height * fitScale}px`\n redraw()\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [imageLoaded])\n\n // Redraw whenever the shape list or in-flight draft changes.\n useEffect(() => {\n redraw()\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [shapes, draftShape])\n\n function redraw() {\n const canvas = canvasRef.current\n const img = imageRef.current\n if (!canvas || !img) return\n const ctx = canvas.getContext('2d')\n if (!ctx) return\n ctx.clearRect(0, 0, canvas.width, canvas.height)\n ctx.drawImage(img, 0, 0)\n for (const s of shapes) drawShape(ctx, s)\n if (draftShape) drawShape(ctx, draftShape)\n }\n\n function getCanvasCoords(e: MouseEvent) {\n const canvas = canvasRef.current!\n const rect = canvas.getBoundingClientRect()\n return {\n x: (e.clientX - rect.left) / scaleRef.current,\n y: (e.clientY - rect.top) / scaleRef.current,\n }\n }\n\n const handleMouseDown = (e: MouseEvent) => {\n if (!imageLoaded) return\n const { x, y } = getCanvasCoords(e)\n const lineWidth = Math.max(3, Math.round(canvasRef.current!.width / 400))\n\n if (tool === 'text') {\n const text = window.prompt(strings['annotator.text_prompt'])\n if (text && text.trim()) {\n setShapes((prev) => [\n ...prev,\n {\n kind: 'text',\n x,\n y,\n text: text.trim(),\n color,\n fontSize: Math.max(16, Math.round(canvasRef.current!.width / 50)),\n lineWidth: 1,\n },\n ])\n }\n return\n }\n\n isDrawingRef.current = true\n if (tool === 'rectangle') {\n setDraftShape({ kind: 'rectangle', x, y, w: 0, h: 0, color, lineWidth })\n } else if (tool === 'arrow') {\n setDraftShape({ kind: 'arrow', x1: x, y1: y, x2: x, y2: y, color, lineWidth })\n } else if (tool === 'freehand') {\n setDraftShape({ kind: 'freehand', points: [{ x, y }], color, lineWidth })\n }\n }\n\n const handleMouseMove = (e: MouseEvent) => {\n if (!isDrawingRef.current || !draftShape) return\n const { x, y } = getCanvasCoords(e)\n if (draftShape.kind === 'rectangle') {\n setDraftShape({ ...draftShape, w: x - draftShape.x, h: y - draftShape.y })\n } else if (draftShape.kind === 'arrow') {\n setDraftShape({ ...draftShape, x2: x, y2: y })\n } else if (draftShape.kind === 'freehand') {\n setDraftShape({ ...draftShape, points: [...draftShape.points, { x, y }] })\n }\n }\n\n const handleMouseUp = () => {\n if (isDrawingRef.current && draftShape) {\n // Discard tiny shapes (accidental clicks).\n const isTiny =\n (draftShape.kind === 'rectangle' &&\n Math.abs(draftShape.w) < 4 &&\n Math.abs(draftShape.h) < 4) ||\n (draftShape.kind === 'arrow' &&\n Math.hypot(\n draftShape.x2 - draftShape.x1,\n draftShape.y2 - draftShape.y1,\n ) < 4) ||\n (draftShape.kind === 'freehand' && draftShape.points.length < 3)\n if (!isTiny) {\n setShapes((prev) => [...prev, draftShape])\n }\n }\n isDrawingRef.current = false\n setDraftShape(null)\n }\n\n const handleSave = async () => {\n const canvas = canvasRef.current\n if (!canvas) return\n setSaving(true)\n try {\n const blob = await new Promise<Blob | null>((resolve) =>\n canvas.toBlob(resolve, 'image/png', 0.92),\n )\n if (blob) onSave(blob)\n } finally {\n setSaving(false)\n }\n }\n\n const tools: Array<{ id: Tool; icon: typeof Icon.rect; label: string }> = [\n { id: 'rectangle', icon: Icon.rect, label: strings['annotator.tool.rectangle'] },\n { id: 'arrow', icon: Icon.arrow, label: strings['annotator.tool.arrow'] },\n { id: 'freehand', icon: Icon.pencil, label: strings['annotator.tool.freehand'] },\n { id: 'text', icon: Icon.text, label: strings['annotator.tool.text'] },\n ]\n\n return (\n <div\n class=\"annotator-backdrop\"\n role=\"presentation\"\n onClick={(e) => {\n if (e.target === e.currentTarget) onCancel()\n }}\n >\n <div class=\"annotator\" role=\"dialog\" aria-modal=\"true\" aria-label={strings['annotator.title']}>\n <div class=\"annotator-header\">\n <span>{strings['annotator.title']}</span>\n <button\n type=\"button\"\n class=\"modal-close\"\n aria-label={strings['form.close']}\n onClick={onCancel}\n >\n {Icon.close}\n </button>\n </div>\n\n <div class=\"annotator-toolbar\">\n <div class=\"annotator-tools\">\n {tools.map((t) => (\n <button\n key={t.id}\n type=\"button\"\n onClick={() => setTool(t.id)}\n title={t.label}\n aria-label={t.label}\n aria-pressed={tool === t.id}\n class={`annotator-tool ${tool === t.id ? 'is-active' : ''}`}\n >\n {t.icon}\n </button>\n ))}\n </div>\n\n <span class=\"annotator-sep\" />\n\n <div class=\"annotator-colors\">\n {COLORS.map((c) => (\n <button\n key={c}\n type=\"button\"\n onClick={() => setColor(c)}\n aria-label={c}\n aria-pressed={color === c}\n class={`annotator-color ${color === c ? 'is-active' : ''}`}\n style={{ backgroundColor: c }}\n />\n ))}\n </div>\n\n <span class=\"annotator-sep\" />\n\n <button\n type=\"button\"\n class=\"annotator-btn\"\n onClick={() => setShapes((prev) => prev.slice(0, -1))}\n disabled={shapes.length === 0}\n >\n {Icon.undo}\n <span>{strings['annotator.undo']}</span>\n </button>\n <button\n type=\"button\"\n class=\"annotator-btn\"\n onClick={() => setShapes([])}\n disabled={shapes.length === 0}\n >\n {Icon.trash}\n <span>{strings['annotator.clear']}</span>\n </button>\n\n <span class=\"annotator-spacer\" />\n\n <span class=\"annotator-count\">\n {shapes.length} {strings['annotator.count_suffix']}\n </span>\n </div>\n\n <div ref={containerRef} class=\"annotator-canvas-wrap\">\n {!imageLoaded ? (\n <span class=\"annotator-loading\">{strings['annotator.loading']}</span>\n ) : (\n <canvas\n ref={canvasRef}\n onMouseDown={handleMouseDown}\n onMouseMove={handleMouseMove}\n onMouseUp={handleMouseUp}\n onMouseLeave={handleMouseUp}\n class=\"annotator-canvas\"\n />\n )}\n </div>\n\n <div class=\"annotator-footer\">\n <button type=\"button\" class=\"btn\" onClick={onCancel}>\n {strings['form.cancel']}\n </button>\n <button\n type=\"button\"\n class=\"btn btn--primary\"\n onClick={handleSave}\n disabled={saving || !imageLoaded}\n >\n {saving ? strings['annotator.applying'] : strings['annotator.apply']}\n </button>\n </div>\n </div>\n </div>\n )\n}\n","/**\n * Helpers for the manual-screenshot-upload UX (v0.6.0).\n *\n * Kept tiny and dependency-free so they can be unit-tested without DOM/jsdom\n * setup. The interesting work — drag/drop, paste, file picker — lives in\n * Form.tsx.\n */\n\nexport const ALLOWED_IMAGE_TYPES = [\n 'image/png',\n 'image/jpeg',\n 'image/webp',\n] as const\n\nexport const MAX_SCREENSHOT_BYTES = 10 * 1024 * 1024 // 10 MB\n\nexport type ScreenshotValidationError =\n | { kind: 'type' }\n | { kind: 'size'; maxMb: number }\n\nexport function validateScreenshotFile(file: File): ScreenshotValidationError | null {\n if (\n !ALLOWED_IMAGE_TYPES.includes(\n file.type as (typeof ALLOWED_IMAGE_TYPES)[number],\n )\n ) {\n return { kind: 'type' }\n }\n if (file.size > MAX_SCREENSHOT_BYTES) {\n return { kind: 'size', maxMb: MAX_SCREENSHOT_BYTES / (1024 * 1024) }\n }\n return null\n}\n\n/**\n * Truncate a URL while keeping the start and end visible. Used in the form's\n * \"Vous étiez ici\" footer so the user can verify the URL we'll send without\n * the long path overflowing the modal.\n *\n * \"https://app.example.com/very/long/deep/path?a=1&b=2\" (53 chars, max 30)\n * → \"https://app.examp…h?a=1&b=2\"\n */\nexport function truncateUrl(url: string, maxLength = 80): string {\n if (url.length <= maxLength) return url\n const keepStart = Math.floor((maxLength - 1) / 2)\n const keepEnd = maxLength - 1 - keepStart\n return `${url.slice(0, keepStart)}…${url.slice(url.length - keepEnd)}`\n}\n","/**\n * MineList — the user's own reports on this project.\n *\n * Polls every 30 s while mounted (Sentry-style staleTime + manual\n * refresh). Empty state when the submitter has no thread yet (404 from\n * the backend is mapped to []). Click a row to open the detail view.\n */\n\nimport { h } from 'preact'\nimport { useEffect, useRef, useState } from 'preact/hooks'\n\nimport type { ApiClient } from '../api/client'\nimport type { WidgetReportRow } from '../types'\nimport type { StringKey } from './i18n'\nimport { ReportRow } from './ReportRow'\n\ninterface MineListProps {\n api: ApiClient\n externalId: string\n strings: Record<StringKey, string>\n onSelect: (report: WidgetReportRow) => void\n}\n\nconst POLL_MS = 30_000\n\nexport function MineList({ api, externalId, strings, onSelect }: MineListProps) {\n const [rows, setRows] = useState<WidgetReportRow[] | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [refreshing, setRefreshing] = useState(false)\n const mountedRef = useRef(true)\n\n const fetchRows = async () => {\n setRefreshing(true)\n setError(null)\n try {\n const next = await api.listMine(externalId)\n if (!mountedRef.current) return\n setRows(next)\n } catch (err) {\n if (!mountedRef.current) return\n setError(err instanceof Error ? err.message : strings['mine.error'])\n } finally {\n if (mountedRef.current) setRefreshing(false)\n }\n }\n\n useEffect(() => {\n mountedRef.current = true\n void fetchRows()\n const timer = setInterval(() => {\n void fetchRows()\n }, POLL_MS)\n return () => {\n mountedRef.current = false\n clearInterval(timer)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [externalId])\n\n const isEmpty = rows !== null && rows.length === 0\n const isLoading = rows === null && !error\n\n return (\n <div class=\"mine-list\">\n <div class=\"mine-list-header\">\n <h2>{strings['tab.mine']}</h2>\n <button\n type=\"button\"\n class=\"btn\"\n onClick={() => {\n void fetchRows()\n }}\n disabled={refreshing}\n >\n {refreshing ? strings['mine.loading'] : strings['mine.refresh']}\n </button>\n </div>\n {isLoading && <div class=\"mine-loading\">{strings['mine.loading']}</div>}\n {error && <div class=\"error\">{error}</div>}\n {isEmpty && (\n <div class=\"mine-empty\">\n <strong>{strings['mine.empty.title']}</strong>\n <p>{strings['mine.empty.body']}</p>\n </div>\n )}\n {rows && rows.length > 0 && (\n <ul class=\"mine-rows\">\n {rows.map((row) => (\n <li>\n <ReportRow row={row} strings={strings} onClick={() => onSelect(row)} />\n </li>\n ))}\n </ul>\n )}\n </div>\n )\n}\n","/**\n * ReportRow — single list item in MineList.\n *\n * Shows status / type / severity badges, a one-line description preview,\n * a relative timestamp, and a \"N replies\" hint pulled from the\n * comment_count annotation on the backend. Whole row is the click\n * target so keyboard nav (Tab + Enter) works.\n */\n\nimport { h } from 'preact'\n\nimport type { WidgetReportRow } from '../types'\nimport type { StringKey } from './i18n'\n\ninterface ReportRowProps {\n row: WidgetReportRow\n strings: Record<StringKey, string>\n onClick: () => void\n}\n\nfunction statusClassName(status: string): string {\n return `pill pill-status pill-status--${status}`\n}\n\nfunction severityClassName(severity: string): string {\n return `pill pill-severity pill-severity--${severity}`\n}\n\nfunction typeClassName(): string {\n return 'pill pill-type'\n}\n\nfunction formatRelative(iso: string): string {\n const then = Date.parse(iso)\n if (!Number.isFinite(then)) return ''\n const seconds = Math.max(1, Math.round((Date.now() - then) / 1000))\n if (seconds < 60) return `${seconds}s`\n const minutes = Math.round(seconds / 60)\n if (minutes < 60) return `${minutes}m`\n const hours = Math.round(minutes / 60)\n if (hours < 48) return `${hours}h`\n const days = Math.round(hours / 24)\n return `${days}d`\n}\n\nfunction repliesLabel(count: number, strings: Record<StringKey, string>): string {\n if (count === 1) return strings['mine.replies_one']\n return strings['mine.replies_many'].replace('{count}', String(count))\n}\n\nexport function ReportRow({ row, strings, onClick }: ReportRowProps) {\n const preview =\n row.description.length > 120\n ? row.description.slice(0, 117) + '…'\n : row.description\n return (\n <button type=\"button\" class=\"mine-row\" onClick={onClick}>\n <div class=\"mine-row-pills\">\n <span class={statusClassName(row.status)}>{strings[`status.${row.status}` as StringKey] ?? row.status}</span>\n <span class={typeClassName()}>{strings[`type.${row.feedback_type}` as StringKey]}</span>\n <span class={severityClassName(row.severity)}>{strings[`severity.${row.severity}` as StringKey]}</span>\n </div>\n <div class=\"mine-row-preview\">{preview}</div>\n <div class=\"mine-row-meta\">\n <span>{formatRelative(row.updated_at || row.created_at)}</span>\n {row.comment_count > 0 && <span>· {repliesLabel(row.comment_count, strings)}</span>}\n </div>\n </button>\n )\n}\n","import { h, type ComponentChildren } from 'preact'\nimport { useEffect, useRef } from 'preact/hooks'\n\ninterface ModalProps {\n onDismiss: () => void\n children: ComponentChildren\n closeLabel?: string\n}\n\nexport function Modal({ onDismiss, children, closeLabel = 'Close' }: ModalProps) {\n const modalRef = useRef<HTMLDivElement>(null)\n const previouslyFocused = useRef<Element | null>(null)\n\n // Esc-to-close + focus management. Without this, users couldn't dismiss\n // with the keyboard (a real-world UX expectation). Listener installed on\n // window so it works regardless of which element inside the modal has focus.\n useEffect(() => {\n previouslyFocused.current = document.activeElement\n const onKey = (e: KeyboardEvent) => {\n if (e.key !== 'Escape') return\n // Ignore Esc when a topmost dialog (e.g. the Annotator) is mounted in\n // our shadow root — it owns the keystroke. Without this guard the\n // Esc fires both handlers on the same event and dismisses the form\n // behind the annotator.\n const root = modalRef.current?.getRootNode()\n if (\n root instanceof ShadowRoot &&\n root.querySelector('.annotator-backdrop')\n ) {\n return\n }\n e.stopPropagation()\n onDismiss()\n }\n window.addEventListener('keydown', onKey)\n // Focus first focusable element inside the modal so the keyboard user\n // lands inside, not still on the FAB or whatever opened the modal.\n const first = modalRef.current?.querySelector<HTMLElement>(\n 'textarea, input, select, button',\n )\n first?.focus()\n return () => {\n window.removeEventListener('keydown', onKey)\n // Restore focus on dismiss so screen-readers don't lose place.\n const prev = previouslyFocused.current as HTMLElement | null\n if (prev && typeof prev.focus === 'function') prev.focus()\n }\n }, [onDismiss])\n\n return (\n <div\n class=\"backdrop\"\n role=\"presentation\"\n onClick={(e) => {\n if (e.target === e.currentTarget) onDismiss()\n }}\n >\n <div ref={modalRef} class=\"modal\" role=\"dialog\" aria-modal=\"true\">\n <button\n type=\"button\"\n class=\"modal-close\"\n aria-label={closeLabel}\n onClick={onDismiss}\n >\n ×\n </button>\n {children}\n </div>\n </div>\n )\n}\n","/**\n * ReportDetailView — selected report's full thread.\n *\n * Renders: status pill + description + screenshot + comment thread +\n * compose box + (when status=awaiting_validation) \"Mark as resolved\"\n * button. Polls every 30 s while open so the user sees an operator's\n * reply without manually refreshing.\n */\n\nimport { h } from 'preact'\nimport { useEffect, useRef, useState } from 'preact/hooks'\n\nimport type { ApiClient } from '../api/client'\nimport type {\n WidgetCommentRow,\n WidgetReportDetail,\n} from '../types'\nimport { CommentBubble } from './CommentBubble'\nimport type { StringKey } from './i18n'\nimport { truncateUrl } from './screenshot-utils'\n\ninterface ReportDetailViewProps {\n api: ApiClient\n externalId: string\n reportId: string\n strings: Record<StringKey, string>\n onBack: () => void\n}\n\nconst POLL_MS = 30_000\n\nexport function ReportDetailView({\n api,\n externalId,\n reportId,\n strings,\n onBack,\n}: ReportDetailViewProps) {\n const [detail, setDetail] = useState<WidgetReportDetail | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [composeBody, setComposeBody] = useState('')\n const [sending, setSending] = useState(false)\n const [closing, setClosing] = useState(false)\n const mountedRef = useRef(true)\n\n const fetchDetail = async () => {\n try {\n const next = await api.getReport(reportId, externalId)\n if (!mountedRef.current) return\n setDetail(next)\n setError(null)\n } catch (err) {\n if (!mountedRef.current) return\n setError(err instanceof Error ? err.message : 'load_failed')\n }\n }\n\n useEffect(() => {\n mountedRef.current = true\n void fetchDetail()\n const timer = setInterval(() => {\n void fetchDetail()\n }, POLL_MS)\n return () => {\n mountedRef.current = false\n clearInterval(timer)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [reportId, externalId])\n\n const handleSend = async () => {\n if (!composeBody.trim() || sending) return\n setSending(true)\n try {\n const nonce = `${reportId}:${Date.now()}`\n const created = await api.addComment(reportId, externalId, composeBody.trim(), nonce)\n if (!mountedRef.current) return\n setComposeBody('')\n // Optimistically append + then refetch so server-rendered timestamps\n // and any race with operator replies stay coherent.\n setDetail((prev) =>\n prev ? { ...prev, comments: appendComment(prev.comments, created) } : prev,\n )\n void fetchDetail()\n } catch (err) {\n if (!mountedRef.current) return\n setError(err instanceof Error ? err.message : 'comment_failed')\n } finally {\n if (mountedRef.current) setSending(false)\n }\n }\n\n const handleClose = async () => {\n if (closing) return\n setClosing(true)\n try {\n const next = await api.closeAsResolved(reportId, externalId)\n if (!mountedRef.current) return\n setDetail(next)\n } catch (err) {\n if (!mountedRef.current) return\n setError(err instanceof Error ? err.message : 'close_failed')\n } finally {\n if (mountedRef.current) setClosing(false)\n }\n }\n\n if (!detail && !error) {\n return <div class=\"mine-loading\">{strings['mine.loading']}</div>\n }\n\n if (!detail) {\n return (\n <div class=\"error\" role=\"alert\">\n {error}\n </div>\n )\n }\n\n const showCloseCta = detail.status === 'awaiting_validation'\n\n return (\n <div class=\"report-detail\">\n <div class=\"report-detail-header\">\n <button type=\"button\" class=\"btn\" onClick={onBack}>\n ← {strings['detail.back']}\n </button>\n <span class={`pill pill-status pill-status--${detail.status}`}>\n {strings[`status.${detail.status}` as StringKey] ?? detail.status}\n </span>\n </div>\n\n <div class=\"report-detail-body\">\n <ContextBlock detail={detail} strings={strings} />\n <p class=\"report-detail-description\">{detail.description}</p>\n {detail.screenshot_url && (\n <a\n class=\"report-detail-screenshot\"\n href={detail.screenshot_url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <img src={detail.screenshot_url} alt=\"\" loading=\"lazy\" />\n </a>\n )}\n\n <h3 class=\"report-detail-section\">{strings['detail.thread']}</h3>\n {detail.comments.length === 0 ? (\n <p class=\"report-detail-empty\">{strings['detail.no_replies']}</p>\n ) : (\n <ul class=\"report-comments\">\n {detail.comments.map((c) => (\n <li>\n <CommentBubble comment={c} strings={strings} />\n </li>\n ))}\n </ul>\n )}\n\n <div class=\"report-compose\">\n <textarea\n value={composeBody}\n placeholder={strings['detail.compose_placeholder']}\n onInput={(e) => setComposeBody((e.target as HTMLTextAreaElement).value)}\n disabled={sending}\n />\n <div class=\"report-compose-actions\">\n {showCloseCta && (\n <button\n type=\"button\"\n class=\"btn\"\n onClick={handleClose}\n disabled={closing}\n >\n {closing ? strings['detail.close_busy'] : strings['detail.close_cta']}\n </button>\n )}\n <button\n type=\"button\"\n class=\"btn btn--primary\"\n onClick={handleSend}\n disabled={!composeBody.trim() || sending}\n >\n {sending ? strings['detail.compose_sending'] : strings['detail.compose_send']}\n </button>\n </div>\n </div>\n\n {error && <div class=\"error\">{error}</div>}\n </div>\n </div>\n )\n}\n\nfunction appendComment(\n current: WidgetCommentRow[],\n next: WidgetCommentRow,\n): WidgetCommentRow[] {\n if (current.some((c) => c.id === next.id)) return current\n return [...current, next]\n}\n\ninterface ContextBlockProps {\n detail: WidgetReportDetail\n strings: Record<StringKey, string>\n}\n\n/**\n * The original-context block that sits above the description on the\n * detail page. Surfaces what the user originally reported (type +\n * severity at submit time, page they were on, when they sent it,\n * how the screenshot was captured) so they can recall the report\n * without having to re-derive it from the description.\n */\nfunction ContextBlock({ detail, strings }: ContextBlockProps) {\n const captureKey: StringKey = `detail.context.capture.${detail.capture_method}` as StringKey\n const captureLabel = strings[captureKey] ?? detail.capture_method\n return (\n <div class=\"report-detail-context\">\n <div class=\"report-detail-context-pills\">\n <span class=\"pill pill-type\">{strings[`type.${detail.feedback_type}` as StringKey]}</span>\n <span class={`pill pill-severity pill-severity--${detail.severity}`}>\n {strings[`severity.${detail.severity}` as StringKey]}\n </span>\n <span class=\"pill pill-capture\">{captureLabel}</span>\n </div>\n <div class=\"report-detail-context-line\">\n <span class=\"report-detail-context-label\">{strings['detail.context.submitted_at']}</span>\n <span>{formatSubmittedAt(detail.created_at)}</span>\n </div>\n {detail.page_url && (\n <div class=\"report-detail-context-line\" title={detail.page_url}>\n <span class=\"report-detail-context-label\">{strings['detail.context.page']}</span>\n <a\n class=\"report-detail-context-url\"\n href={detail.page_url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n {truncateUrl(detail.page_url, 64)}\n </a>\n </div>\n )}\n </div>\n )\n}\n\nfunction formatSubmittedAt(iso: string): string {\n try {\n return new Date(iso).toLocaleString(undefined, {\n dateStyle: 'medium',\n timeStyle: 'short',\n })\n } catch {\n return iso\n }\n}\n","/**\n * CommentBubble — single comment rendered inside the report detail.\n *\n * USER comments float right (the user's own follow-ups). MCP / STAFF /\n * SYSTEM float left and pick up an author label so you can tell at a\n * glance who wrote what. The \"Mhosaic Team\" label for MCP comments\n * matches PNR's pattern; hosts can hide it via a project flag (future).\n */\n\nimport { h } from 'preact'\n\nimport type { WidgetCommentRow } from '../types'\nimport type { StringKey } from './i18n'\n\ninterface CommentBubbleProps {\n comment: WidgetCommentRow\n strings: Record<StringKey, string>\n}\n\nexport function CommentBubble({ comment, strings }: CommentBubbleProps) {\n // The submitter's own follow-ups float right; everything else (operator,\n // agent, system) floats left with an attribution label. Both submitter\n // and operator comments arrive as `author_source = USER` from the\n // backend, so we rely on the server-computed `is_mine` flag to tell\n // them apart instead of guessing from the source enum.\n const isMine = comment.is_mine\n const isAgent = !isMine && comment.author_source === 'mcp'\n const isSystem = !isMine && comment.author_source === 'system'\n const variant: 'mcp' | 'system' | 'staff' = isAgent\n ? 'mcp'\n : isSystem\n ? 'system'\n : 'staff'\n const labelKey: StringKey =\n variant === 'mcp'\n ? 'detail.author.mcp'\n : variant === 'system'\n ? 'detail.author.system'\n : 'detail.author.staff'\n const label = comment.author_label || strings[labelKey]\n return (\n <div class={`comment-bubble ${isMine ? 'is-mine' : 'is-other'}`}>\n {!isMine && label && (\n <div class={`comment-author comment-author--${variant}`}>{label}</div>\n )}\n <div class=\"comment-body\">{comment.body}</div>\n <div class=\"comment-time\">{formatTime(comment.created_at)}</div>\n </div>\n )\n}\n\nfunction formatTime(iso: string): string {\n try {\n return new Date(iso).toLocaleString(undefined, {\n dateStyle: 'short',\n timeStyle: 'short',\n })\n } catch {\n return iso\n }\n}\n","export const WIDGET_STYLES = `\n:host {\n --mfb-accent: #3b82f6;\n --mfb-accent-contrast: #ffffff;\n --mfb-bg: #ffffff;\n --mfb-surface: #f9fafb;\n --mfb-text: #0a0a0a;\n --mfb-text-muted: #6b7280;\n --mfb-border: #e5e7eb;\n --mfb-radius: 8px;\n --mfb-font: system-ui, -apple-system, sans-serif;\n --mfb-z-index: 2147483640;\n\n all: initial;\n font-family: var(--mfb-font);\n color: var(--mfb-text);\n position: fixed;\n z-index: var(--mfb-z-index);\n}\n\n@media (prefers-color-scheme: dark) {\n :host {\n --mfb-bg: #111827;\n --mfb-surface: #1f2937;\n --mfb-text: #f9fafb;\n --mfb-text-muted: #9ca3af;\n --mfb-border: #374151;\n }\n}\n\n/* FAB — 56px (Material standard), two-layer elevation, scale-on-press,\n custom SVG icon (no emoji — emoji renders inconsistently across OSes\n and can't inherit color). */\n.fab {\n position: fixed;\n bottom: 24px;\n right: 24px;\n width: 56px;\n height: 56px;\n border-radius: 999px;\n background: var(--mfb-accent);\n color: var(--mfb-accent-contrast);\n border: none;\n cursor: pointer;\n /* Two-layer elevation: ambient (soft, large) + key (tighter, near). */\n box-shadow:\n 0 4px 12px rgba(0, 0, 0, 0.08),\n 0 2px 4px rgba(0, 0, 0, 0.12);\n display: grid;\n place-items: center;\n transition: transform 180ms cubic-bezier(0.4, 0, 0.2, 1),\n box-shadow 180ms cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.fab:hover {\n transform: translateY(-2px);\n box-shadow:\n 0 8px 24px rgba(0, 0, 0, 0.12),\n 0 3px 6px rgba(0, 0, 0, 0.16);\n}\n.fab:active {\n transform: translateY(0) scale(0.96);\n box-shadow:\n 0 3px 8px rgba(0, 0, 0, 0.10),\n 0 1px 2px rgba(0, 0, 0, 0.14);\n}\n.fab:focus-visible {\n outline: 2px solid var(--mfb-accent-contrast);\n outline-offset: 3px;\n box-shadow:\n 0 0 0 4px var(--mfb-accent),\n 0 4px 12px rgba(0, 0, 0, 0.08),\n 0 2px 4px rgba(0, 0, 0, 0.12);\n}\n@media (prefers-color-scheme: dark) {\n /* Slightly desaturate so the FAB doesn't glow against dark backgrounds. */\n .fab { box-shadow:\n 0 4px 12px rgba(0, 0, 0, 0.32),\n 0 2px 4px rgba(0, 0, 0, 0.40); }\n}\n@media (prefers-reduced-motion: reduce) {\n .fab { transition: none; }\n .fab:hover, .fab:active { transform: none; }\n}\n\n.backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.45);\n display: grid;\n place-items: center;\n}\n\n.modal {\n background: var(--mfb-bg);\n border-radius: calc(var(--mfb-radius) * 1.5);\n box-shadow: 0 20px 48px rgba(0, 0, 0, 0.25);\n /* 440px is the industry sweet spot for short forms — Linear, Plain,\n Sentry all sit in the 420-480 range. 92vw caps it on narrow screens. */\n width: min(440px, 92vw);\n /* 24px is shadcn's default; gives every input room to breathe. */\n padding: 24px;\n display: flex;\n flex-direction: column;\n /* 18px between major blocks (h2 → field → field → footer). Within a\n field group, .field uses a tighter 6px label-to-input gap. */\n gap: 18px;\n position: relative;\n /* Cap modal height on short viewports (mobile landscape, tiny laptops)\n so the form scrolls inside the modal rather than overflowing the\n viewport with no way to reach the actions row. */\n max-height: calc(100vh - 32px);\n overflow-y: auto;\n}\n\n.modal h2 {\n margin: 0;\n font-size: 17px;\n font-weight: 600;\n padding-right: 28px;\n letter-spacing: -0.01em;\n}\n\n/* The form sits inside .modal as a single flex child, so the modal-level\n gap doesn't separate the form's *own* children. Mirror the column +\n gap pattern on the form itself; 22px lands in the middle of the\n 20-24px field-to-field range the design critic recommended. */\n.modal form {\n display: flex;\n flex-direction: column;\n gap: 22px;\n}\n\n.modal-close {\n position: absolute;\n top: 8px;\n right: 8px;\n width: 32px;\n height: 32px;\n display: grid;\n place-items: center;\n background: transparent;\n border: none;\n border-radius: var(--mfb-radius);\n color: var(--mfb-text-muted);\n font: inherit;\n font-size: 22px;\n line-height: 1;\n cursor: pointer;\n}\n.modal-close:hover { background: var(--mfb-surface); color: var(--mfb-text); }\n.modal-close:focus-visible { outline: 2px solid var(--mfb-accent); outline-offset: 2px; }\n\n/* Each field group: label (13px medium, muted) + input (14px regular)\n stacked with a 6px gap. The 18px modal-level gap separates groups. */\n.field { display: flex; flex-direction: column; gap: 6px; font-size: 13px; }\n\n.field label {\n color: var(--mfb-text-muted);\n font-weight: 500;\n font-size: 12px;\n letter-spacing: 0.01em;\n}\n\n.field input, .field select, .field textarea {\n font-family: inherit;\n font-size: 14px;\n color: inherit;\n padding: 9px 12px;\n border: 1px solid var(--mfb-border);\n border-radius: var(--mfb-radius);\n background: var(--mfb-surface);\n transition: border-color 120ms ease, box-shadow 120ms ease;\n}\n\n.field input:hover, .field select:hover, .field textarea:hover { border-color: var(--mfb-text-muted); }\n.field input:focus, .field select:focus, .field textarea:focus {\n outline: none;\n border-color: var(--mfb-accent);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mfb-accent) 22%, transparent);\n}\n\n.field select {\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n padding-right: 28px;\n background-image: url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path fill='none' stroke='%236b7280' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='M1 1l4 4 4-4'/></svg>\");\n background-repeat: no-repeat;\n background-position: right 10px center;\n}\n\n.field textarea { min-height: 96px; resize: vertical; line-height: 1.45; }\n\n.row { display: flex; gap: 12px; }\n.row > * { flex: 1; }\n\n/* Footer separation: border-top + 16px breathing room, Stripe/Linear pattern.\n Stops Cancel/Send from floating against the dropzone or page-URL footer. */\n.actions {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n padding-top: 16px;\n margin-top: 4px;\n border-top: 1px solid var(--mfb-border);\n}\n\n.btn {\n padding: 9px 16px;\n border-radius: var(--mfb-radius);\n border: 1px solid var(--mfb-border);\n background: var(--mfb-bg);\n color: var(--mfb-text);\n font: inherit;\n font-weight: 500;\n cursor: pointer;\n transition: background 120ms ease, border-color 120ms ease;\n}\n.btn:hover { background: var(--mfb-surface); }\n\n.btn--primary {\n background: var(--mfb-accent);\n color: var(--mfb-accent-contrast);\n border-color: var(--mfb-accent);\n}\n.btn--primary:hover {\n background: color-mix(in srgb, var(--mfb-accent) 88%, black);\n border-color: color-mix(in srgb, var(--mfb-accent) 88%, black);\n}\n\n.btn[disabled] { opacity: 0.6; cursor: not-allowed; }\n\n.error { color: #dc2626; font-size: 13px; }\n.success { color: #059669; font-size: 13px; }\n\n/* ---- v0.6.0: manual screenshot upload + annotator -------------------- */\n\n.mfb-sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n\n.screenshot-dropzone {\n border: 1px dashed var(--mfb-border);\n border-radius: var(--mfb-radius);\n padding: 18px 14px;\n text-align: center;\n cursor: pointer;\n background: var(--mfb-surface);\n transition: border-color 120ms ease, background 120ms ease;\n}\n.screenshot-dropzone:hover { border-color: var(--mfb-text-muted); }\n.screenshot-dropzone.is-dragover {\n border-color: var(--mfb-accent);\n border-style: solid;\n background: color-mix(in srgb, var(--mfb-accent) 8%, var(--mfb-surface));\n}\n.screenshot-dropzone:focus-visible {\n outline: 2px solid var(--mfb-accent);\n outline-offset: 2px;\n}\n.screenshot-cta { font-size: 13px; color: var(--mfb-text); }\n.screenshot-cta strong { color: var(--mfb-accent); font-weight: 600; }\n.screenshot-formats { font-size: 11px; color: var(--mfb-text-muted); margin-top: 4px; }\n\n.screenshot-preview {\n position: relative;\n border: 1px solid var(--mfb-border);\n border-radius: var(--mfb-radius);\n overflow: hidden;\n background: var(--mfb-surface);\n display: flex;\n flex-direction: column;\n}\n.screenshot-preview img {\n display: block;\n width: 100%;\n height: auto;\n max-height: 180px;\n object-fit: contain;\n background: #1a1a1a;\n}\n.screenshot-remove {\n position: absolute;\n top: 6px;\n right: 6px;\n width: 26px;\n height: 26px;\n display: grid;\n place-items: center;\n background: rgba(255, 255, 255, 0.9);\n border: 1px solid var(--mfb-border);\n border-radius: 999px;\n font-size: 18px;\n line-height: 1;\n cursor: pointer;\n color: #111827;\n}\n.screenshot-remove:hover { background: #fff; }\n.screenshot-annotate {\n border-radius: 0;\n border-width: 0;\n border-top: 1px solid var(--mfb-border);\n background: var(--mfb-bg);\n}\n.screenshot-annotate:hover { background: var(--mfb-surface); }\n\n.page-context {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 11px;\n color: var(--mfb-text-muted);\n}\n.page-context-label {\n text-transform: uppercase;\n font-weight: 600;\n letter-spacing: 0.04em;\n font-size: 10px;\n}\n.page-context-url {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 11px;\n color: var(--mfb-text-muted);\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Annotator modal — sits above the feedback modal (z-index +1). */\n\n.annotator-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.78);\n display: grid;\n place-items: center;\n z-index: 1;\n padding: 12px;\n}\n.annotator {\n position: relative;\n background: var(--mfb-bg);\n color: var(--mfb-text);\n border-radius: calc(var(--mfb-radius) * 1.5);\n width: min(960px, 96vw);\n max-height: calc(100vh - 24px);\n display: flex;\n flex-direction: column;\n box-shadow: 0 24px 60px rgba(0, 0, 0, 0.4);\n}\n.annotator-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 14px;\n border-bottom: 1px solid var(--mfb-border);\n font-size: 13px;\n font-weight: 600;\n}\n.annotator-toolbar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n padding: 8px 14px;\n border-bottom: 1px solid var(--mfb-border);\n}\n.annotator-tools, .annotator-colors { display: flex; gap: 4px; }\n.annotator-sep {\n display: inline-block;\n width: 1px;\n height: 18px;\n background: var(--mfb-border);\n margin: 0 4px;\n}\n.annotator-spacer { flex: 1; }\n.annotator-tool {\n width: 30px;\n height: 30px;\n display: grid;\n place-items: center;\n background: var(--mfb-bg);\n color: var(--mfb-text);\n border: 1px solid var(--mfb-border);\n border-radius: var(--mfb-radius);\n cursor: pointer;\n}\n.annotator-tool:hover { background: var(--mfb-surface); }\n.annotator-tool.is-active {\n background: var(--mfb-text);\n color: var(--mfb-bg);\n border-color: var(--mfb-text);\n}\n.annotator-color {\n width: 24px;\n height: 24px;\n border-radius: 999px;\n border: 2px solid var(--mfb-border);\n cursor: pointer;\n padding: 0;\n transition: transform 120ms ease;\n}\n.annotator-color.is-active {\n transform: scale(1.12);\n border-color: var(--mfb-text);\n}\n.annotator-btn {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n height: 30px;\n padding: 0 10px;\n font: inherit;\n font-size: 12px;\n background: var(--mfb-bg);\n color: var(--mfb-text);\n border: 1px solid var(--mfb-border);\n border-radius: var(--mfb-radius);\n cursor: pointer;\n}\n.annotator-btn:hover { background: var(--mfb-surface); }\n.annotator-btn[disabled] { opacity: 0.5; cursor: not-allowed; }\n.annotator-count { font-size: 11px; color: var(--mfb-text-muted); }\n.annotator-canvas-wrap {\n flex: 1;\n overflow: auto;\n background: #1a1a1a;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 12px;\n min-height: 200px;\n}\n.annotator-canvas {\n cursor: crosshair;\n box-shadow: 0 8px 28px rgba(0, 0, 0, 0.45);\n background: #fff;\n}\n.annotator-loading {\n color: rgba(255, 255, 255, 0.7);\n font-size: 13px;\n}\n.annotator-footer {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 8px;\n padding: 10px 14px;\n border-top: 1px solid var(--mfb-border);\n}\n\n/* ---- v0.7: tabs + reader UI -------------------------------------- */\n\n.tab-strip {\n display: flex;\n gap: 4px;\n border-bottom: 1px solid var(--mfb-border);\n margin: 0 -4px 4px;\n padding: 0 4px;\n}\n.tab-button {\n appearance: none;\n background: transparent;\n border: 0;\n border-bottom: 2px solid transparent;\n padding: 8px 12px;\n font: inherit;\n font-size: 13px;\n font-weight: 500;\n color: var(--mfb-text-muted);\n cursor: pointer;\n}\n.tab-button:hover { color: var(--mfb-text); }\n.tab-button.is-active {\n color: var(--mfb-text);\n border-bottom-color: var(--mfb-accent);\n}\n.tab-button[aria-selected=\"true\"] { font-weight: 600; }\n\n.mine-list { display: flex; flex-direction: column; gap: 10px; }\n.mine-list-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n}\n.mine-list-header h2 { margin: 0; font-size: 15px; font-weight: 600; }\n.mine-loading { color: var(--mfb-text-muted); font-size: 13px; }\n.mine-empty {\n text-align: center;\n padding: 24px 12px;\n color: var(--mfb-text-muted);\n font-size: 13px;\n}\n.mine-empty strong { display: block; color: var(--mfb-text); margin-bottom: 4px; }\n\n.mine-rows {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 6px;\n max-height: 380px;\n overflow-y: auto;\n}\n.mine-row {\n appearance: none;\n text-align: left;\n background: var(--mfb-surface);\n border: 1px solid var(--mfb-border);\n border-radius: var(--mfb-radius);\n padding: 10px 12px;\n font: inherit;\n color: inherit;\n cursor: pointer;\n display: flex;\n flex-direction: column;\n gap: 4px;\n width: 100%;\n transition: border-color 120ms ease, background 120ms ease;\n}\n.mine-row:hover { border-color: var(--mfb-text-muted); }\n.mine-row:focus-visible {\n outline: 2px solid var(--mfb-accent);\n outline-offset: 2px;\n}\n.mine-row-pills { display: flex; gap: 4px; flex-wrap: wrap; }\n.mine-row-preview {\n font-size: 13px;\n color: var(--mfb-text);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n.mine-row-meta {\n display: flex;\n gap: 6px;\n font-size: 11px;\n color: var(--mfb-text-muted);\n}\n\n.pill {\n display: inline-block;\n font-size: 10px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n padding: 2px 8px;\n border-radius: 999px;\n border: 1px solid transparent;\n}\n.pill-status { background: #eff6ff; color: #1e40af; border-color: #dbeafe; }\n.pill-status--in_progress { background: #fffbeb; color: #92400e; border-color: #fde68a; }\n.pill-status--awaiting_validation { background: #faf5ff; color: #6b21a8; border-color: #e9d5ff; }\n.pill-status--closed { background: #ecfdf5; color: #065f46; border-color: #a7f3d0; }\n.pill-status--rejected, .pill-status--wontfix, .pill-status--duplicate { background: #f3f4f6; color: #374151; border-color: #e5e7eb; }\n.pill-type { background: var(--mfb-surface); color: var(--mfb-text-muted); border-color: var(--mfb-border); }\n.pill-severity { background: var(--mfb-surface); color: var(--mfb-text-muted); border-color: var(--mfb-border); }\n.pill-severity--blocker { background: #fef2f2; color: #991b1b; border-color: #fecaca; }\n.pill-severity--high { background: #fff7ed; color: #9a3412; border-color: #fed7aa; }\n.pill-severity--medium { background: #fefce8; color: #854d0e; border-color: #fef08a; }\n.pill-severity--low { background: var(--mfb-surface); color: var(--mfb-text-muted); }\n\n.report-detail { display: flex; flex-direction: column; gap: 12px; }\n.report-detail-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n.report-detail-body { display: flex; flex-direction: column; gap: 10px; }\n.report-detail-description {\n font-size: 14px;\n white-space: pre-wrap;\n margin: 0;\n}\n.report-detail-screenshot {\n display: block;\n border: 1px solid var(--mfb-border);\n border-radius: var(--mfb-radius);\n overflow: hidden;\n background: #1a1a1a;\n}\n.report-detail-screenshot img {\n display: block;\n width: 100%;\n max-height: 200px;\n object-fit: contain;\n}\n.report-detail-section {\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--mfb-text-muted);\n margin: 8px 0 4px;\n font-weight: 600;\n}\n\n.report-detail-context {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px 12px;\n background: var(--mfb-surface);\n border-radius: var(--mfb-radius);\n border: 1px solid var(--mfb-border);\n}\n.report-detail-context-pills { display: flex; gap: 4px; flex-wrap: wrap; }\n.report-detail-context-line {\n display: flex;\n align-items: baseline;\n gap: 8px;\n font-size: 12px;\n color: var(--mfb-text);\n}\n.report-detail-context-label {\n text-transform: uppercase;\n letter-spacing: 0.04em;\n font-size: 10px;\n font-weight: 600;\n color: var(--mfb-text-muted);\n min-width: 56px;\n}\n.report-detail-context-url {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n color: var(--mfb-accent);\n text-decoration: none;\n font-size: 11px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.report-detail-context-url:hover { text-decoration: underline; }\n.pill-capture { background: var(--mfb-bg); color: var(--mfb-text-muted); border-color: var(--mfb-border); }\n.report-detail-empty {\n font-size: 12px;\n color: var(--mfb-text-muted);\n margin: 0;\n}\n\n.report-comments {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 6px;\n max-height: 300px;\n overflow-y: auto;\n}\n\n.comment-bubble {\n padding: 8px 10px;\n border-radius: 12px;\n max-width: 88%;\n font-size: 13px;\n line-height: 1.4;\n}\n.comment-bubble.is-mine {\n align-self: flex-end;\n background: var(--mfb-accent);\n color: var(--mfb-accent-contrast);\n}\n.comment-bubble.is-other {\n align-self: flex-start;\n background: var(--mfb-surface);\n border: 1px solid var(--mfb-border);\n color: var(--mfb-text);\n}\n.comment-author {\n font-size: 10px;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n font-weight: 600;\n margin-bottom: 2px;\n}\n.comment-author--mcp { color: #6b21a8; }\n.comment-author--staff { color: #1e40af; }\n.comment-author--system { color: var(--mfb-text-muted); }\n.comment-body { white-space: pre-wrap; }\n.comment-time {\n font-size: 10px;\n margin-top: 4px;\n opacity: 0.7;\n}\n\n.report-compose {\n display: flex;\n flex-direction: column;\n gap: 6px;\n margin-top: 4px;\n}\n.report-compose textarea {\n font: inherit;\n font-size: 13px;\n padding: 8px 10px;\n border: 1px solid var(--mfb-border);\n border-radius: var(--mfb-radius);\n background: var(--mfb-surface);\n color: inherit;\n min-height: 64px;\n resize: vertical;\n}\n.report-compose-actions {\n display: flex;\n justify-content: flex-end;\n gap: 6px;\n}\n`\n","import { createApiClient } from './api/client'\nimport { installCapture } from './capture'\nimport { takeScreenshot } from './screenshot'\nimport { resolveStrings } from './widget/i18n'\nimport { mountWidget } from './widget/mount'\nimport type { FeedbackApi, FeedbackConfig, ReportPayload, ReportTransformer, UserIdentity } from './types'\n\nexport interface InternalConfig extends FeedbackConfig {\n fetchImpl?: typeof fetch\n}\n\ninterface WindowWithGlobal extends Window {\n mhosaicFeedback?: FeedbackApi\n}\n\nexport function createFeedback(config: InternalConfig): FeedbackApi & { _registerTransformer(fn: ReportTransformer): void } {\n const env = config.env ?? 'prod'\n const locale =\n config.locale ?? (typeof navigator !== 'undefined' ? navigator.language : undefined)\n const strings = resolveStrings(\n config.translations ?? {},\n locale !== undefined ? { locale } : {},\n )\n const capture = installCapture({\n ...(config.sanitizeUrl !== undefined && { sanitizeUrl: config.sanitizeUrl }),\n })\n const api = createApiClient({\n apiKey: config.apiKey,\n endpoint: config.endpoint,\n ...(config.fetchImpl !== undefined && { fetch: config.fetchImpl }),\n ...(config.beforeSend !== undefined && { beforeSend: config.beforeSend }),\n })\n\n let user: UserIdentity | undefined = config.user\n let metadata: Record<string, unknown> = config.metadata ?? {}\n const transformers: ReportTransformer[] = []\n\n const host = document.createElement('div')\n host.className = 'mhosaic-feedback'\n if (config.attachTo) {\n const attach = typeof config.attachTo === 'string' ? document.querySelector(config.attachTo) : config.attachTo\n attach?.appendChild(host)\n } else {\n document.body.appendChild(host)\n }\n\n async function buildAndSubmit(values: {\n description: string\n feedback_type?: string\n severity?: string\n synthetic?: boolean\n /** When set, used as-is and html2canvas is skipped. v0.6.0 manual upload. */\n screenshot?: Blob\n }) {\n // Auto-error reports skip screenshot capture: html2canvas is async/slow,\n // and the DOM is often in an inconsistent state when a JS error fires.\n // The captured ring buffer (errors, console, network) carries the signal.\n // Manual uploads (user dropped/pasted/annotated their own image) take\n // precedence over the html2canvas auto-grab.\n const manualScreenshot = values.screenshot\n const screenshot = values.synthetic\n ? undefined\n : (manualScreenshot ??\n (await takeScreenshot(document.body, {\n mask: ['.mhosaic-feedback', '[data-mfb-mask]'],\n })))\n const technical_context = capture.snapshot()\n // Surface identify()/setMetadata() values on the report. Without this\n // the host-supplied user identity and metadata sit in closure forever\n // and never reach the operator — a stack trace from a logged-in user\n // looks anonymous on the dashboard.\n if (user) technical_context.user = user\n if (metadata && Object.keys(metadata).length > 0) {\n technical_context.metadata = { ...metadata }\n }\n const payload: ReportPayload = {\n description: values.description,\n feedback_type: (values.feedback_type ?? 'bug') as ReportPayload['feedback_type'],\n severity: (values.severity ?? 'medium') as ReportPayload['severity'],\n env,\n page_url: window.location.href,\n user_agent: navigator.userAgent,\n capture_method: screenshot\n ? manualScreenshot\n ? 'manual'\n : 'html2canvas'\n : 'none',\n technical_context,\n }\n // Build-time stamp; tsup's `define` substitutes the package.json\n // version into __MFB_VERSION__. Conditionally assigned so\n // exactOptionalPropertyTypes stays happy under vitest (no tsup\n // pass) where the constant is undefined.\n if (typeof __MFB_VERSION__ !== 'undefined' && __MFB_VERSION__) {\n payload.widget_version = __MFB_VERSION__\n }\n if (screenshot) payload.screenshot = screenshot\n if (values.synthetic) payload.synthetic = true\n // v0.7: lift the host-provided identity to the top level so the\n // backend can upsert a WidgetUser FK. technical_context.user still\n // carries it for backward-compat with the operator's tech-context\n // viewer (and so an audit-log entry knows who was identified at\n // submission time).\n if (user?.id !== undefined && user.id !== null && user.id !== '') {\n payload.user = {\n // The host can pass `id` as a string or number; the backend\n // stores it as an opaque string. Coerce here to a stable shape.\n id: String(user.id),\n ...(user.email !== undefined && { email: user.email }),\n ...(user.name !== undefined && { name: user.name }),\n }\n }\n let finalPayload: ReportPayload = payload\n for (const t of transformers) finalPayload = await t(finalPayload)\n try {\n const result = await api.submitReport(finalPayload)\n config.onSubmitSuccess?.(result)\n capture.clear()\n return result\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err))\n config.onError?.(error)\n throw error\n }\n }\n\n const handle = mountWidget({\n host,\n strings,\n showFAB: config.showFAB ?? true,\n onSubmit: async (values) => { await buildAndSubmit(values) },\n api,\n // Keep this a callback (not a snapshot) so the mount picks up identity\n // changes that happen after createFeedback() — `notifyIdentityChanged()`\n // is the trigger for a re-render.\n getExternalId: () => (user?.id !== undefined && user.id !== null && user.id !== '' ? String(user.id) : undefined),\n })\n\n const instance: FeedbackApi & { _registerTransformer(fn: ReportTransformer): void } = {\n show() { handle.open() },\n hide() { handle.close() },\n open(opts) { handle.open(); void opts },\n async submit(partial) {\n return buildAndSubmit({\n description: partial.description,\n ...(partial.feedback_type !== undefined && { feedback_type: partial.feedback_type }),\n ...(partial.severity !== undefined && { severity: partial.severity }),\n ...(partial.synthetic !== undefined && { synthetic: partial.synthetic }),\n ...(partial.screenshot !== undefined && { screenshot: partial.screenshot }),\n })\n },\n identify(u) {\n user = u\n // Tell the mount to re-evaluate FAB visibility / Mine tab gating.\n handle.notifyIdentityChanged()\n },\n setMetadata(kv) { metadata = { ...metadata, ...kv } },\n shutdown() {\n handle.dispose()\n capture.dispose()\n host.remove()\n // Only release the global if it still points at *us* — otherwise a\n // newer instance has taken ownership and we mustn't blow it away.\n const w = window as unknown as WindowWithGlobal\n if (w.mhosaicFeedback === instance) {\n delete w.mhosaicFeedback\n }\n },\n _registerTransformer(fn: ReportTransformer) { transformers.push(fn) },\n }\n\n // Expose the instance globally for ad-hoc callers (DevTools, docs pages,\n // help-widget integrations). The most-recently-created instance wins.\n ;(window as unknown as WindowWithGlobal).mhosaicFeedback = instance\n\n return instance\n}\n"],"mappings":"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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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","names":["useEffect","useRef","useState","jsx","jsx","jsxs","useState","useRef","useEffect","useEffect","useRef","useState","jsx","jsxs","jsx","jsxs","useState","useRef","useEffect","useEffect","useRef","jsx","jsxs","useEffect","useRef","useState","jsx","jsxs","jsx","jsxs","POLL_MS","useState","useRef","useEffect","jsx","jsxs"]}