@mhosaic/feedback 0.35.0 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-A2TMODNI.mjs → chunk-T36VC7TZ.mjs} +132 -11
- package/dist/chunk-T36VC7TZ.mjs.map +1 -0
- package/dist/embed.min.js +47 -11
- package/dist/embed.min.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/react.mjs +1 -1
- package/dist/vue.mjs +1 -1
- package/dist/widget.min.js +48 -12
- package/dist/widget.min.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-A2TMODNI.mjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/api/client.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/widget/i18n.ts","../src/widget/mount.tsx","../src/widget/currentPage.ts","../src/widget/BoardView.tsx","../src/widget/DiagnosticHint.tsx","../src/widget/boardCache.ts","../src/widget/boardViewStore.ts","../src/widget/poll.ts","../src/widget/rateLimit.ts","../src/widget/ReportDetailView.tsx","../src/widget/linkifySeq.tsx","../src/widget/CommentBubble.tsx","../src/widget/screenshot-utils.ts","../src/widget/ChangelogList.tsx","../src/widget/ReportRow.tsx","../src/widget/Fab.tsx","../src/widget/Form.tsx","../src/widget/Annotator.tsx","../src/widget/MineList.tsx","../src/widget/KpiStrip.tsx","../src/widget/Modal.tsx","../src/widget/PageActivityStrip.tsx","../src/widget/PagePeekPanel.tsx","../src/widget/pageSignal.ts","../src/widget/styles.ts","../src/core.ts"],"sourcesContent":["import type {\n DiagnosisKind,\n BoardFilters,\n ReportPayload,\n SubmittedReport,\n WidgetBoardKpis,\n WidgetBoardRow,\n WidgetChangelogRow,\n WidgetCommentRow,\n WidgetReportDetail,\n WidgetReportRow,\n} from '../types'\n\n/** Page envelope DRF returns for paginated list endpoints. */\nexport interface BoardListPage {\n count: number\n next: string | null\n previous: string | null\n results: WidgetBoardRow[]\n /** Phase-3 backends embed the KPI-strip payload in the list response so\n * a board poll is one request; absent on older backends (the widget\n * falls back to the standalone /board/kpis/ endpoint). */\n kpis?: WidgetBoardKpis\n}\n\n/** Per-call options for reader requests. `signal` lets callers cancel a\n * superseded fetch (filter/search changed, component unmounted) so the\n * backend stops paying for a response nobody will render — a fast typist\n * could otherwise stack overlapping project-wide `q=` scans (the\n * pre-0.30.0 OOM shape; the debounce caps rate, not concurrency). */\nexport interface ReadOpts {\n signal?: AbortSignal\n}\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 * v0.13 — signed-identity callback. Returns the current\n * `(userHash, exp, email)` triple if the host has called\n * `identify({userHash, exp, ...})`. The API client reads this fresh\n * on every request so a delayed `identify()` lights up signed\n * headers on subsequent calls. Return `null` for the legacy\n * unsigned path (the default until the project issues a\n * `WidgetSigningSecret`).\n */\n getSignedIdentity?: () => { userHash: string; exp: number; email: string } | null\n}\n\nexport interface ApiClient {\n submitReport(payload: ReportPayload): Promise<SubmittedReport>\n /** POST /v1/widget/visibility/ — whether this end-user may see the widget\n * (Phase 4 allowlist). `email`, when the host identified one, lets an\n * email-based allowlist match (the backend matches external_id OR email).\n * Throws on error so the caller can fail closed. */\n checkVisibility(externalId: string, email?: string): Promise<boolean>\n /** GET /v1/reports/widget/mine/ — caller's own reports on this project. */\n listMine(externalId: string): Promise<WidgetReportRow[]>\n /** GET /v1/reports/widget/changelog/ — caller's resolved reports for the\n * \"This week\" tab; client groups by ISO week of `resolved_at`. */\n listChangelog(externalId: string): Promise<WidgetChangelogRow[]>\n /** Classify why the widget can't reach the API (roadmap #8): a\n * no-header probe detects gateway interception, a preflighted probe\n * detects CORS-stripping proxies (the corporate SSL-inspection\n * signature), plus key/throttle/offline verdicts. */\n diagnose(opts?: { onLine?: boolean }): Promise<DiagnosisKind>\n /** Hostname of the configured endpoint — for diagnostic copy. */\n endpointHost: string\n /** GET /v1/reports/widget/<id>/ — single report + thread. */\n getReport(reportId: string, externalId: string, opts?: ReadOpts): Promise<WidgetReportDetail>\n /** Resolve a report by its per-project #seq — powers clickable \"#NN\"\n * references and jump-by-number (#85). */\n getReportBySeq(seq: number, externalId: string): Promise<WidgetReportDetail>\n /** POST /v1/reports/widget/<id>/comments/ — submitter follow-up.\n * When `attachments` are supplied the request switches to multipart so\n * images ride along (#91); a text-only comment stays a JSON POST. */\n addComment(\n reportId: string,\n externalId: string,\n body: string,\n clientNonce?: string,\n attachments?: readonly Blob[],\n ): Promise<WidgetCommentRow>\n /** PATCH /v1/reports/widget/<id>/ — the author edits their report's\n * description after submitting (F4). Author-only server-side. */\n editDescription(\n reportId: string,\n externalId: string,\n description: string,\n ): Promise<WidgetReportDetail>\n /** PATCH /v1/reports/widget/<id>/ — close-as-resolved by the submitter. */\n closeAsResolved(\n reportId: string,\n externalId: string,\n ): Promise<WidgetReportDetail>\n /** PATCH /v1/reports/widget/<id>/ — reopen (→ in_progress) when the\n * submitter says the shipped fix didn't actually resolve the issue. */\n reopenUnresolved(\n reportId: string,\n externalId: string,\n ): Promise<WidgetReportDetail>\n /** GET /v1/reports/widget/board/ — paginated, filtered list backing the\n * v0.12 Board tab. Scope follows the project's\n * `share_reports_with_widget` flag (submitter-only when off, project-\n * wide when on); `filters.mine = true` narrows even when on. */\n listBoard(externalId: string, filters?: BoardFilters, opts?: ReadOpts): Promise<BoardListPage>\n /** GET /v1/reports/widget/board/kpis/ — total + per-status counts +\n * resolution rate. Cheap; safe to poll alongside the list. */\n fetchBoardKpis(\n externalId: string,\n filters?: BoardFilters,\n opts?: ReadOpts,\n ): Promise<WidgetBoardKpis>\n}\n\nconst SCALAR_FIELDS: Array<keyof ReportPayload> = [\n 'description', 'feedback_type', 'severity', 'env', 'page_url', 'user_agent', 'capture_method',\n]\n\nexport function assertSafeEndpoint(endpoint: string): void {\n let parsed: URL\n try {\n parsed = new URL(endpoint)\n } catch {\n throw new Error(\n `[mhosaic-feedback] \\`endpoint\\` is not a valid URL: ${endpoint}`,\n )\n }\n if (parsed.protocol === 'https:') return\n if (\n parsed.protocol === 'http:' &&\n (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname === '[::1]')\n ) {\n return\n }\n throw new Error(\n `[mhosaic-feedback] \\`endpoint\\` must use https:// (got ${parsed.protocol}//${parsed.hostname}). ` +\n 'http:// is only allowed for localhost in dev.',\n )\n}\n\n/**\n * A widget reader endpoint answered 429. Carried as a typed error so the UI\n * can show an actionable \"too many requests — retry in Xs\" message + a retry\n * button, instead of a generic failure or an infinite \"Loading…\" (#80/#81).\n */\nexport class RateLimitError extends Error {\n readonly retryAfterSeconds: number\n constructor(retryAfterSeconds: number) {\n super(`rate limited; retry after ${retryAfterSeconds}s`)\n this.name = 'RateLimitError'\n this.retryAfterSeconds = retryAfterSeconds\n }\n}\n\nfunction parseRetryAfter(response: Response): number {\n const raw = response.headers.get('Retry-After')\n const n = raw ? Number.parseInt(raw, 10) : NaN\n return Number.isFinite(n) && n >= 0 ? n : 0\n}\n\n// Turn a reader response into a throw on any non-ok status: 429 → typed\n// RateLimitError; everything else → a labelled Error. Callers must handle\n// 404 (empty-thread) BEFORE calling this.\nasync function ensureReadable(response: Response, label: string): Promise<void> {\n if (response.status === 429) throw new RateLimitError(parseRetryAfter(response))\n if (!response.ok) {\n const text = await response.text().catch(() => '')\n throw new Error(`${label} failed: ${response.status} ${text}`)\n }\n}\n\n// A malformed-but-200 body used to be handed straight to the list views,\n// which set their rows to null/undefined and hung on \"Loading…\" forever.\n// Validating the shape here means a 200 always resolves the loading state —\n// to real data, an empty list, or (on a bad shape) an actionable error (#80).\nasync function readJsonArray<T>(response: Response, label: string): Promise<T[]> {\n await ensureReadable(response, label)\n const data = await response.json().catch(() => undefined)\n if (!Array.isArray(data)) {\n throw new Error(`${label}: unexpected response shape (expected an array)`)\n }\n return data as T[]\n}\n\nasync function readJsonObject<T>(response: Response, label: string): Promise<T> {\n await ensureReadable(response, label)\n const data = await response.json().catch(() => undefined)\n if (data === null || typeof data !== 'object' || Array.isArray(data)) {\n throw new Error(`${label}: unexpected response shape (expected an object)`)\n }\n return data as T\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 // SECURITY: endpoint is host-controlled config. Without a scheme guard a\n // misconfigured host (or a host XSS that injects a `<script\n // data-endpoint=\"http://attacker\">` embed tag) can steer the widget at\n // any URL and exfiltrate every captured report — including the\n // pk_proj_ key in the Authorization header — to attacker-controlled\n // infra. Only allow https:; permit http:// for localhost/127.0.0.1 so\n // dev sandboxes keep working.\n assertSafeEndpoint(endpoint)\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 // Repeated `screenshot` parts — the backend reads FILES.getlist. The\n // first part keeps the legacy filename so single-shot requests stay\n // byte-identical to pre-multi widget builds.\n const screenshots = payload.screenshots?.length\n ? payload.screenshots\n : payload.screenshot\n ? [payload.screenshot]\n : []\n screenshots.forEach((blob, i) => {\n form.append('screenshot', blob, i === 0 ? 'screenshot.png' : `screenshot-${i + 1}.png`)\n })\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 if (payload.page_path) {\n form.append('page_path', payload.page_path)\n }\n\n // The backend honors `synthetic=true` only when this header is set,\n // so a hand-crafted curl with the public key can't smuggle a curated\n // report into the auto-error bucket (where the default operator view\n // hides it). The bundled debugger always sets payload.synthetic; the\n // user-facing widget never does.\n const headers: Record<string, string> = {\n Authorization: `Bearer ${options.apiKey}`,\n }\n if (payload.synthetic) {\n headers['X-Mhosaic-Capture-Source'] = 'error-tracking'\n }\n // Signed-identity headers on the SUBMIT path too: when the host has\n // wired up identify({userHash, exp, email}) and the backend project\n // has a WidgetSigningSecret, the request must carry the HMAC. We\n // also re-stamp `X-Mhosaic-User` so the verifier sees the\n // signed-over external_id directly (the body `user` block is still\n // sent — the backend prefers the header pair when signing is in\n // effect; see views.py:CreateFeedbackReportView.post).\n const signed = options.getSignedIdentity?.()\n if (signed && signed.userHash && signed.exp && payload.user?.id) {\n headers['X-Mhosaic-User'] = String(payload.user.id)\n headers['X-Mhosaic-User-Hmac'] = signed.userHash\n headers['X-Mhosaic-User-Exp'] = String(signed.exp)\n if (signed.email) headers['X-Mhosaic-User-Email'] = signed.email\n }\n const response = await fetcher(`${endpoint}/api/feedback/v1/reports/`, {\n method: 'POST',\n headers,\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): Record<string, string> {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${options.apiKey}`,\n 'X-Mhosaic-User': externalId,\n }\n // Signed-identity Phase 1 (v0.13): forward the HMAC envelope when the\n // host has supplied one via `identify({userHash, exp, email})`. The\n // backend gates on the project's `WidgetSigningSecret` row — projects\n // that haven't issued a secret continue to accept the bare header.\n const signed = options.getSignedIdentity?.()\n if (signed && signed.userHash && signed.exp) {\n headers['X-Mhosaic-User-Hmac'] = signed.userHash\n headers['X-Mhosaic-User-Exp'] = String(signed.exp)\n if (signed.email) headers['X-Mhosaic-User-Email'] = signed.email\n }\n return headers\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 return readJsonArray<WidgetReportRow>(response, 'listMine')\n }\n\n async function diagnose(opts?: { onLine?: boolean }): Promise<DiagnosisKind> {\n const onLine =\n opts?.onLine ??\n (typeof navigator !== 'undefined' ? navigator.onLine : true)\n if (onLine === false) return 'offline'\n const probeUrl = `${endpoint}/api/feedback/v1/widget-manifest/?pk=${encodeURIComponent(options.apiKey)}`\n // Probe A — simple GET, no headers: no CORS preflight, so it tests raw\n // reachability. A redirect here is the SSL-inspection gateway signature\n // (the API never redirects the manifest).\n let simple: Response\n try {\n simple = await fetcher(probeUrl, { method: 'GET', redirect: 'manual' })\n } catch {\n return 'blocked'\n }\n if (\n simple.type === 'opaqueredirect' ||\n (simple.status >= 300 && simple.status < 400)\n ) {\n return 'intercepted'\n }\n // Probe B — same URL with an Authorization header: forces the CORS\n // preflight the reader endpoints need. Proxies that swallow OPTIONS\n // fail exactly here while probe A stays green.\n let authed: Response\n try {\n authed = await fetcher(probeUrl, {\n method: 'GET',\n headers: { Authorization: `Bearer ${options.apiKey}` },\n })\n } catch {\n return 'preflight'\n }\n if (authed.status === 401 || authed.status === 403) return 'key'\n if (authed.status === 429) return 'throttle'\n return 'healthy'\n }\n\n async function listChangelog(externalId: string): Promise<WidgetChangelogRow[]> {\n const response = await fetcher(\n `${endpoint}/api/feedback/v1/reports/widget/changelog/`,\n { method: 'GET', headers: widgetHeaders(externalId) },\n )\n if (response.status === 404) return []\n return readJsonArray<WidgetChangelogRow>(response, 'listChangelog')\n }\n\n async function getReport(\n reportId: string,\n externalId: string,\n opts?: ReadOpts,\n ): Promise<WidgetReportDetail> {\n const response = await fetcher(\n `${endpoint}/api/feedback/v1/reports/widget/${reportId}/`,\n {\n method: 'GET',\n headers: widgetHeaders(externalId),\n ...(opts?.signal ? { signal: opts.signal } : {}),\n },\n )\n return readJsonObject<WidgetReportDetail>(response, 'getReport')\n }\n\n async function getReportBySeq(\n seq: number,\n externalId: string,\n ): Promise<WidgetReportDetail> {\n const response = await fetcher(\n `${endpoint}/api/feedback/v1/reports/widget/by-seq/${seq}/`,\n { method: 'GET', headers: widgetHeaders(externalId) },\n )\n return readJsonObject<WidgetReportDetail>(response, 'getReportBySeq')\n }\n\n async function addComment(\n reportId: string,\n externalId: string,\n body: string,\n clientNonce?: string,\n attachments?: readonly Blob[],\n ): Promise<WidgetCommentRow> {\n // Multipart only when images are attached — a plain follow-up stays a\n // JSON POST so its request shape is byte-identical to pre-#91 builds.\n // With FormData we must NOT set Content-Type ourselves: the browser\n // appends the multipart boundary, and a hardcoded value would lose it.\n let init: RequestInit\n if (attachments && attachments.length > 0) {\n const form = new FormData()\n form.append('body', body)\n if (clientNonce !== undefined) form.append('client_nonce', clientNonce)\n attachments.forEach((blob, i) =>\n form.append('attachment', blob, `attachment-${i + 1}.png`),\n )\n init = { method: 'POST', headers: widgetHeaders(externalId), body: form }\n } else {\n init = {\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 const response = await fetcher(\n `${endpoint}/api/feedback/v1/reports/widget/${reportId}/comments/`,\n init,\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 editDescription(\n reportId: string,\n externalId: string,\n description: 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({ description }),\n },\n )\n if (!response.ok) {\n const text = await response.text().catch(() => '')\n throw new Error(`editDescription failed: ${response.status} ${text}`)\n }\n return response.json() as Promise<WidgetReportDetail>\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 async function reopenUnresolved(\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: 'in_progress' }),\n },\n )\n if (!response.ok) {\n const text = await response.text().catch(() => '')\n throw new Error(`reopenUnresolved failed: ${response.status} ${text}`)\n }\n return response.json() as Promise<WidgetReportDetail>\n }\n\n function boardQueryString(filters?: BoardFilters): string {\n if (!filters) return ''\n const params = new URLSearchParams()\n // Multi-value fields use append() so repeated keys stack the way DRF's\n // `request.query_params.getlist()` reads them on the server.\n filters.status?.forEach((s) => params.append('status', s))\n filters.type?.forEach((t) => params.append('type', t))\n filters.severity?.forEach((s) => params.append('severity', s))\n if (filters.q) params.set('q', filters.q)\n if (filters.mine) params.set('mine', '1')\n if (filters.ordering) params.set('ordering', filters.ordering)\n if (filters.page && filters.page > 1) params.set('page', String(filters.page))\n if (filters.pagePath) params.set('page_path', filters.pagePath)\n const qs = params.toString()\n return qs ? `?${qs}` : ''\n }\n\n async function listBoard(\n externalId: string,\n filters?: BoardFilters,\n opts?: ReadOpts,\n ): Promise<BoardListPage> {\n const response = await fetcher(\n `${endpoint}/api/feedback/v1/reports/widget/board/${boardQueryString(filters)}`,\n {\n method: 'GET',\n headers: widgetHeaders(externalId),\n ...(opts?.signal ? { signal: opts.signal } : {}),\n },\n )\n if (response.status === 404) {\n // Caller has no WidgetUser row yet — render an empty Board rather\n // than a hard error so the first-time experience is a clean slate.\n return { count: 0, next: null, previous: null, results: [] }\n }\n const page = await readJsonObject<BoardListPage>(response, 'listBoard')\n if (!Array.isArray(page.results)) {\n throw new Error('listBoard: unexpected response shape (missing results[])')\n }\n return page\n }\n\n async function fetchBoardKpis(\n externalId: string,\n filters?: BoardFilters,\n opts?: ReadOpts,\n ): Promise<WidgetBoardKpis> {\n const response = await fetcher(\n `${endpoint}/api/feedback/v1/reports/widget/board/kpis/${boardQueryString(filters)}`,\n {\n method: 'GET',\n headers: widgetHeaders(externalId),\n ...(opts?.signal ? { signal: opts.signal } : {}),\n },\n )\n if (response.status === 404) {\n return { total: 0, by_status: {}, resolution_rate: 0, scope: 'mine' }\n }\n return readJsonObject<WidgetBoardKpis>(response, 'fetchBoardKpis')\n }\n\n async function checkVisibility(\n externalId: string,\n email?: string,\n ): Promise<boolean> {\n const response = await fetcher(\n `${endpoint}/api/feedback/v1/widget/visibility/`,\n {\n method: 'POST',\n headers: {\n ...widgetHeaders(externalId),\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n external_id: externalId,\n ...(email ? { email } : {}),\n }),\n },\n )\n if (!response.ok) {\n throw new Error(`checkVisibility failed: ${response.status}`)\n }\n const data = (await response.json()) as { show?: boolean }\n return Boolean(data.show)\n }\n\n return {\n submitReport,\n checkVisibility,\n listMine,\n listChangelog,\n diagnose,\n endpointHost: (() => {\n try {\n return new URL(endpoint).host\n } catch {\n return endpoint\n }\n })(),\n getReport,\n getReportBySeq,\n addComment,\n editDescription,\n closeAsResolved,\n reopenUnresolved,\n listBoard,\n fetchBoardKpis,\n }\n}\n","import type { DeviceContext } from '../types'\nimport { scrubCredentials } from './scrub'\nimport { sanitizeUrl } from './urlSanitizer'\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 // Auth flows commonly hand off via query-string tokens (`?token=…`,\n // `?code=…`); when the user clicks through to the host page, both the\n // raw referrer AND the host's own pathname can carry those values.\n // Run both through the same sanitizer used for fetch/XHR; collapse the\n // result back to a path-only string so we don't accidentally upload the\n // operator-side host's origin as if it were the captured page's URL.\n const rawReferrer = document.referrer || undefined\n const referrer = rawReferrer !== undefined ? sanitizeUrl(rawReferrer) : undefined\n const sanitizedHere = sanitizeUrl(window.location.pathname + window.location.search)\n let pathname: string\n try {\n const parsed = new URL(sanitizedHere, window.location.origin)\n pathname = parsed.pathname + parsed.search\n } catch {\n pathname = window.location.pathname\n }\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 // Hosts commonly stuff credentials / PII into page titles\n // (\"Inbox (3) — bob@x.com\", \"Reset password — token=abc\"). Cap to a\n // sane length and run through the same scrubber the console/error\n // paths use.\n title: scrubCredentials((document.title || '')).slice(0, 200),\n pathname,\n }\n}\n","import type { RingBuffer } from './ringBuffer'\nimport type { ConsoleEntry } from '../types'\nimport { scrubCredentials } from './scrub'\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 rawMessage = args.map(safeStringify).join(' ')\n const message = scrubCredentials(rawMessage).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'\nimport { scrubCredentials } from './scrub'\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 // Error messages from fetch (CORS rejection, abort, etc.) sometimes\n // echo the requested URL back including its full query string. Scrub\n // for the same patterns the console path does.\n const rawErr = err instanceof Error ? err.message : String(err)\n buffer.push({\n url: sanitize(url), method, status: 0,\n durationMs: Math.round(performance.now() - start),\n ts: Date.now(),\n error: scrubCredentials(rawErr),\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'\nimport { scrubCredentials } from './scrub'\n\n// Errors that bubble up to `window.error` or `unhandledrejection` often\n// carry credentials in plain text — e.g. an SDK throws `new Error(\"Bearer\n// eyJ... rejected (401)\")`, or a promise rejects with `{apiKey: \"sk_live_…\"}`.\n// Both ship to the central backend unless we scrub here.\n\nexport function installErrorHandlers(buffer: RingBuffer<ErrorEntry>): () => void {\n if (typeof window === 'undefined') return () => {}\n const onError = (e: ErrorEvent) => {\n const rawStack = e.error instanceof Error ? e.error.stack : undefined\n const stack = rawStack !== undefined ? scrubCredentials(rawStack) : undefined\n buffer.push({\n message: scrubCredentials(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 rawMessage =\n reason instanceof Error ? reason.message :\n typeof reason === 'string' ? reason :\n (() => { try { return JSON.stringify(reason) } catch { return String(reason) } })()\n const rawStack = reason instanceof Error ? reason.stack : undefined\n const stack = rawStack !== undefined ? scrubCredentials(rawStack) : undefined\n buffer.push({\n message: scrubCredentials(rawMessage),\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","import { sanitizeUrl } from './urlSanitizer'\n\nexport 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 // sanitizeUrl strips credential-shaped query params + the hash\n // — the resource URL is otherwise raw input from any third-party\n // request the host page makes (analytics pixels, OAuth\n // callbacks with tokens in the query, etc.).\n slowResources.push({ name: sanitizeUrl(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 const DEFAULT_STRINGS = {\n 'fab.label': 'Send feedback',\n 'page.badge.aria': 'Feedback — {n} open on this page',\n 'page.badge.aria.one': 'Feedback — 1 open on this page',\n 'page.strip.text': '{n} open reports on this page',\n 'page.strip.text.one': '1 open report on this page',\n 'page.strip.view': 'View',\n 'page.peek.title': 'On this page',\n 'page.peek.viewAll': 'View all {n}',\n 'page.peek.viewAll.one': 'View 1 open report',\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.success.seq': 'Thanks — report #{seq} was sent. ✓',\n 'form.discard.title': 'Unsaved changes',\n 'form.discard.body': 'Discard your feedback?',\n 'form.discard.keep': 'Keep editing',\n 'form.discard.confirm': 'Discard',\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.screenshot.error_count': 'Too many screenshots (max {max}).',\n 'form.context.label': 'Page',\n 'form.capture.notice':\n 'Console, network activity and errors are captured automatically to help us debug.',\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.tool.highlight': 'Highlight',\n 'annotator.tool.blur': 'Blur (hide sensitive data)',\n 'annotator.text_prompt': 'Enter text:',\n 'annotator.color_picker': 'Custom color',\n 'annotator.undo': 'Undo',\n 'annotator.redo': 'Redo',\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 'tab.changelog': 'This week',\n 'tab.board': 'Board',\n 'board.kpi.total': 'Total',\n 'board.kpi.new': 'New',\n 'board.kpi.in_progress': 'In progress',\n 'board.kpi.awaiting_validation': 'Awaiting validation',\n 'board.kpi.closed': 'Closed',\n 'board.kpi.rejected': 'Rejected',\n 'board.kpi.resolution_rate': 'Resolution rate',\n 'board.sort': 'Sort',\n 'board.sort.recent': 'Newest first',\n 'board.sort.oldest': 'Oldest first',\n 'board.sort.updated': 'Recently active',\n 'board.sort.severity': 'Severity',\n 'board.sort.status': 'Status',\n 'board.filter.status': 'Status',\n 'board.filter.type': 'Type',\n 'board.filter.severity': 'Severity',\n 'board.filter.search.placeholder': 'Search…',\n 'board.filter.mine': 'Mine only',\n 'board.filter.clear': 'Clear filters',\n 'board.list.empty.title': 'Nothing here yet',\n 'board.list.empty.description': 'When reports land, they’ll show up here.',\n 'board.list.loading': 'Loading…',\n 'board.list.error': 'Couldn’t load reports.',\n 'board.retry': 'Try again',\n 'board.list.you': 'you',\n 'board.list.count': '{n} of {total}',\n 'board.list.loadMore': 'Show more',\n 'board.detail.empty': 'Pick a report on the left to see its full thread.',\n 'board.detail.by': 'By',\n 'board.detail.confirm_resolution': 'Confirm resolution',\n 'board.detail.status_history': 'History',\n 'board.scope.project': 'Project reports',\n 'board.scope.mine': 'Your reports',\n 'board.scope.thisPage': 'This page',\n 'board.scope.allPages': 'All pages',\n 'board.scope.onThisPage': 'on this page',\n 'board.list.count.page': '{n} of {total} · this page',\n 'board.list.empty.page.title': 'No feedback on this page yet.',\n 'board.list.empty.page.description':\n 'Be the first to report something here — or browse the whole project.',\n 'board.back': 'Back',\n 'changelog.empty.title': 'Nothing resolved yet',\n 'changelog.empty.body': 'Once a report you sent is fixed it will appear here, grouped by week.',\n 'changelog.week_of': 'Week of {date}',\n 'changelog.resolved_one': '{count} resolved',\n 'changelog.resolved_many': '{count} resolved',\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.validate.banner': '{count} fix(es) awaiting your confirmation',\n 'mine.validate.confirm': 'Fix works',\n 'mine.validate.reopen': 'Still broken',\n 'mine.validate.view': 'Review',\n 'diag.run': 'Diagnose',\n 'diag.running': 'Checking the connection…',\n 'diag.offline': 'You appear to be offline. Reconnect and retry.',\n 'diag.blocked':\n 'The feedback service is unreachable from this network. A firewall or proxy is blocking it — ask IT to allow {host}.',\n 'diag.intercepted':\n 'Your corporate network (SSL-inspection proxy) is intercepting widget traffic. Ask IT to add {host} to the inspection bypass list.',\n 'diag.preflight':\n 'A network proxy is blocking the widget\\u2019s secure requests (CORS preflight). Ask IT to allow {host}.',\n 'diag.key': 'The widget\\u2019s access key was rejected. Contact the site team.',\n 'diag.throttle': 'Too many requests right now — wait a moment and retry.',\n 'diag.healthy':\n 'The service is reachable — this looks like a temporary hiccup. Retry.',\n 'mine.loading': 'Loading…',\n 'mine.error': 'Could not load your reports.',\n 'mine.jump.placeholder': '#',\n 'mine.jump.go': 'Open',\n 'mine.jump.aria': 'Open a report by number',\n 'mine.jump.not_found': 'No report with that number.',\n 'error.rate_limited': 'Too many requests — retry in {seconds}s.',\n 'error.rate_limited_generic': 'Too many requests — try again in a moment.',\n 'mine.replies_one': '1 reply',\n 'mine.replies_many': '{count} replies',\n 'mine.filter.empty': 'No reports match this filter.',\n 'kpi.new': 'New',\n 'kpi.in_progress': 'In progress',\n 'kpi.awaiting_validation': 'Awaiting you',\n 'kpi.resolution_rate': 'Resolution rate',\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.attach_add': 'Add image',\n 'detail.attach_remove': 'Remove',\n 'detail.attachment_alt': 'Attached image',\n 'detail.copy_link': 'Copy link',\n 'detail.copied': 'Link copied',\n 'detail.edit': 'Edit',\n 'detail.edit_save': 'Save',\n 'detail.edit_cancel': 'Cancel',\n 'detail.edit_saving': 'Saving…',\n 'detail.edit_failed': 'Could not save your changes.',\n 'detail.close_cta': 'Mark as resolved',\n 'detail.close_busy': 'Marking…',\n 'detail.reopen_cta': 'Still not fixed',\n 'detail.reopen_busy': 'Reopening…',\n 'detail.teammate_notice':\n 'Viewing a teammate’s report — replies are private to the submitter.',\n 'detail.load_failed.title': 'Report not available',\n 'detail.load_failed.body':\n 'It may have been deleted, or you no longer have access to it.',\n 'detail.load_failed.cta': 'Back',\n 'detail.send_failed': 'Couldn’t send your reply. Try again.',\n 'detail.close_failed': 'Couldn’t mark as resolved. Try again.',\n 'detail.reopen_failed': 'Couldn’t reopen the report. Try again.',\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 'detail.tech.title': 'What we received',\n 'detail.tech.errors_one': 'error',\n 'detail.tech.errors_many': 'errors',\n 'detail.tech.device': 'Device',\n 'detail.tech.device.viewport': 'Viewport',\n 'detail.tech.device.platform': 'Platform',\n 'detail.tech.device.language': 'Language',\n 'detail.tech.device.timezone': 'Timezone',\n 'detail.tech.device.connection': 'Connection',\n 'detail.tech.errors': 'Runtime errors',\n 'detail.tech.console': 'Console (last 20)',\n 'detail.tech.network': 'Network (last 15)',\n 'detail.verified.badge': 'Agent-verified',\n 'detail.verified.title': 'Proof of fix',\n 'detail.verified.functional': 'Functional check',\n 'detail.verified.ui': 'UI check',\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 'page.badge.aria': 'Retours — {n} ouverts sur cette page',\n 'page.badge.aria.one': 'Retours — 1 ouvert sur cette page',\n 'page.strip.text': '{n} retours ouverts sur cette page',\n 'page.strip.text.one': '1 retour ouvert sur cette page',\n 'page.strip.view': 'Voir',\n 'page.peek.title': 'Sur cette page',\n 'page.peek.viewAll': 'Voir les {n}',\n 'page.peek.viewAll.one': 'Voir 1 retour ouvert',\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.success.seq': 'Merci — rapport n°{seq} envoyé. ✓',\n 'form.discard.title': 'Modifications non enregistrées',\n 'form.discard.body': 'Abandonner votre commentaire ?',\n 'form.discard.keep': 'Continuer la saisie',\n 'form.discard.confirm': 'Abandonner',\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.screenshot.error_count': 'Trop de captures d’écran (max {max}).',\n 'form.context.label': 'Page',\n 'form.capture.notice':\n 'La console, l’activité réseau et les erreurs sont capturées automatiquement pour faciliter le diagnostic.',\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.tool.highlight': 'Surligner',\n 'annotator.tool.blur': 'Flouter (données sensibles)',\n 'annotator.text_prompt': 'Entrez le texte :',\n 'annotator.color_picker': 'Couleur personnalisée',\n 'annotator.undo': 'Annuler',\n 'annotator.redo': 'Refaire',\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 'tab.changelog': 'Cette semaine',\n 'tab.board': 'Tableau',\n 'board.kpi.total': 'Total',\n 'board.kpi.new': 'Nouveau',\n 'board.kpi.in_progress': 'En cours',\n 'board.kpi.awaiting_validation': 'À valider',\n 'board.kpi.closed': 'Fermé',\n 'board.kpi.rejected': 'Refusé',\n 'board.kpi.resolution_rate': 'Taux de résolution',\n 'board.sort': 'Trier',\n 'board.sort.recent': 'Plus récents',\n 'board.sort.oldest': 'Plus anciens',\n 'board.sort.updated': 'Activité récente',\n 'board.sort.severity': 'Sévérité',\n 'board.sort.status': 'Statut',\n 'board.filter.status': 'Statut',\n 'board.filter.type': 'Type',\n 'board.filter.severity': 'Sévérité',\n 'board.filter.search.placeholder': 'Rechercher…',\n 'board.filter.mine': 'Les miens',\n 'board.filter.clear': 'Effacer les filtres',\n 'board.list.empty.title': 'Rien à voir ici',\n 'board.list.empty.description': 'Les rapports apparaîtront ici dès qu’ils arrivent.',\n 'board.list.loading': 'Chargement…',\n 'board.list.error': 'Impossible de charger les rapports.',\n 'board.retry': 'Réessayer',\n 'board.list.you': 'vous',\n 'board.list.count': '{n} sur {total}',\n 'board.list.loadMore': 'Afficher plus',\n 'board.detail.empty': 'Sélectionnez un rapport à gauche pour voir le fil complet.',\n 'board.detail.by': 'Par',\n 'board.detail.confirm_resolution': 'Confirmer la résolution',\n 'board.detail.status_history': 'Historique',\n 'board.scope.project': 'Rapports du projet',\n 'board.scope.mine': 'Vos rapports',\n 'board.scope.thisPage': 'Cette page',\n 'board.scope.allPages': 'Toutes les pages',\n 'board.scope.onThisPage': 'sur cette page',\n 'board.list.count.page': '{n} sur {total} · cette page',\n 'board.list.empty.page.title': 'Aucun retour sur cette page pour l’instant.',\n 'board.list.empty.page.description':\n 'Soyez le premier à signaler quelque chose ici — ou parcourez tout le projet.',\n 'board.back': 'Retour',\n 'changelog.empty.title': 'Rien de résolu pour l’instant',\n 'changelog.empty.body': 'Quand un rapport que vous avez envoyé est corrigé, il apparaîtra ici, regroupé par semaine.',\n 'changelog.week_of': 'Semaine du {date}',\n 'changelog.resolved_one': '{count} résolu',\n 'changelog.resolved_many': '{count} résolus',\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.validate.banner': '{count} correctif(s) à confirmer',\n 'mine.validate.confirm': 'Corrigé ✓',\n 'mine.validate.reopen': 'Toujours cassé',\n 'mine.validate.view': 'Voir',\n 'diag.run': 'Diagnostiquer',\n 'diag.running': 'Vérification de la connexion…',\n 'diag.offline': 'Vous semblez hors ligne. Reconnectez-vous puis réessayez.',\n 'diag.blocked':\n 'Le service de feedback est injoignable depuis ce réseau. Un pare-feu ou proxy le bloque — demandez à votre TI d\\u2019autoriser {host}.',\n 'diag.intercepted':\n 'Votre réseau d\\u2019entreprise (inspection SSL) intercepte le trafic du widget. Demandez à votre TI d\\u2019ajouter {host} à la liste d\\u2019exclusion.',\n 'diag.preflight':\n 'Un proxy réseau bloque les requêtes sécurisées du widget (préflight CORS). Demandez à votre TI d\\u2019autoriser {host}.',\n 'diag.key': 'La clé d\\u2019accès du widget a été refusée. Contactez l\\u2019équipe du site.',\n 'diag.throttle': 'Trop de requêtes pour le moment — patientez puis réessayez.',\n 'diag.healthy':\n 'Le service est joignable — le problème semble passager. Réessayez.',\n 'mine.loading': 'Chargement…',\n 'mine.error': 'Impossible de charger vos rapports.',\n 'mine.jump.placeholder': '#',\n 'mine.jump.go': 'Ouvrir',\n 'mine.jump.aria': 'Ouvrir un rapport par numéro',\n 'mine.jump.not_found': 'Aucun rapport avec ce numéro.',\n 'error.rate_limited': 'Trop de requêtes — réessaie dans {seconds} s.',\n 'error.rate_limited_generic': 'Trop de requêtes — réessaie dans un instant.',\n 'mine.replies_one': '1 réponse',\n 'mine.replies_many': '{count} réponses',\n 'mine.filter.empty': 'Aucun rapport ne correspond à ce filtre.',\n 'kpi.new': 'Nouveau',\n 'kpi.in_progress': 'En cours',\n 'kpi.awaiting_validation': 'À valider',\n 'kpi.resolution_rate': 'Taux de résolution',\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.attach_add': 'Ajouter une image',\n 'detail.attach_remove': 'Retirer',\n 'detail.attachment_alt': 'Image jointe',\n 'detail.copy_link': 'Copier le lien',\n 'detail.copied': 'Lien copié',\n 'detail.edit': 'Modifier',\n 'detail.edit_save': 'Enregistrer',\n 'detail.edit_cancel': 'Annuler',\n 'detail.edit_saving': 'Enregistrement…',\n 'detail.edit_failed': 'Impossible d’enregistrer vos modifications.',\n 'detail.close_cta': 'Marquer comme résolu',\n 'detail.close_busy': 'Validation…',\n 'detail.reopen_cta': 'Toujours pas réglé',\n 'detail.reopen_busy': 'Réouverture…',\n 'detail.teammate_notice':\n 'Vous consultez le rapport d’un coéquipier — les réponses sont privées au soumetteur.',\n 'detail.load_failed.title': 'Rapport indisponible',\n 'detail.load_failed.body':\n 'Il a peut-être été supprimé, ou vous n’y avez plus accès.',\n 'detail.load_failed.cta': 'Retour',\n 'detail.send_failed': 'Impossible d’envoyer votre réponse. Réessayez.',\n 'detail.close_failed':\n 'Impossible de marquer comme résolu. Réessayez.',\n 'detail.reopen_failed':\n 'Impossible de rouvrir le rapport. Réessayez.',\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 'detail.tech.title': 'Ce que nous avons reçu',\n 'detail.tech.errors_one': 'erreur',\n 'detail.tech.errors_many': 'erreurs',\n 'detail.tech.device': 'Appareil',\n 'detail.tech.device.viewport': 'Fenêtre',\n 'detail.tech.device.platform': 'Plateforme',\n 'detail.tech.device.language': 'Langue',\n 'detail.tech.device.timezone': 'Fuseau horaire',\n 'detail.tech.device.connection': 'Connexion',\n 'detail.tech.errors': 'Erreurs d’exécution',\n 'detail.tech.console': 'Console (20 derniers)',\n 'detail.tech.network': 'Réseau (15 derniers)',\n 'detail.verified.badge': 'Vérifié par l’agent',\n 'detail.verified.title': 'Preuve de correction',\n 'detail.verified.functional': 'Fonctionnel vérifié',\n 'detail.verified.ui': 'Interface vérifiée',\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, useEffect, useRef, useState } from 'preact/hooks'\n\nimport type { ApiClient } from '../api/client'\nimport type { SubmittedReport } from '../types'\nimport { currentPagePath } from './currentPage'\nimport { BoardView } from './BoardView'\nimport { ChangelogList } from './ChangelogList'\nimport { Fab } from './Fab'\nimport { Form, type FormValues } from './Form'\nimport { MineList } from './MineList'\nimport { Modal } from './Modal'\nimport { PageActivityStrip } from './PageActivityStrip'\nimport { PagePeekPanel } from './PagePeekPanel'\nimport { fetchPageKpis, usePageOpenCount } from './pageSignal'\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<SubmittedReport | 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 /** Phase 4: when true (driven by the manifest's requires_visibility_check),\n * the FAB renders only if `checkVisibility` confirms this end-user is on the\n * project allowlist. Off (default) → the legacy showFAB/identity path. */\n requiresVisibilityCheck?: boolean\n checkVisibility?: (externalId: string) => Promise<boolean>\n /** Host override for the current-page path — same contract as\n * FeedbackConfig.getCurrentPage. Threaded to BoardView so the \"Cette page\"\n * chip resolves identically to what the submit side records. */\n getCurrentPage?: () => string | null\n /** Opt-in: FAB opens to the page-scoped Board when the current page has\n * feedback (else Send). Default false — never hijack the submit flow. */\n openToCurrentPageFeedback?: boolean\n /** Ambient page-activity surfaces (badge/peek/strip). Default true. */\n showPageActivity?: boolean\n /** #85 — deep-link / permalink support. On mount the widget reads this\n * query param from the host URL and, if it holds a report id or #seq,\n * opens that report directly (read-only — the host URL is never mutated).\n * The detail view also offers a \"copy link\" button built from it.\n * Default `'mhfeedback'`; pass `false` to disable entirely. */\n deepLinkParam?: string | false\n}\n\n/** Pure FAB-visibility decision (Phase 4 adds the `visibilityAllowed` gate on\n * top of the showFAB + identity rules). Exported for unit testing. */\nexport function computeFabVisible(\n opts: Pick<MountOptions, 'showFAB' | 'getExternalId' | 'requiresVisibilityCheck'>,\n externalId: string | undefined,\n visibilityAllowed: boolean,\n): boolean {\n if (!opts.showFAB) return false\n if (opts.getExternalId !== undefined && !externalId) return false\n if (opts.requiresVisibilityCheck && !visibilityAllowed) return false\n return true\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' | 'changelog' | 'board'\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 /** Set by a peek-panel row click — BoardView opens with this report\n * pre-selected (spec §3.2). */\n boardSelectId?: string\n /** Sequence number of the just-submitted report, shown in the success\n * acknowledgement (\"report #42 sent\") (#82). */\n submittedSeq?: number\n /** When true, the \"discard unsaved changes?\" confirmation is shown over\n * the form instead of closing on an accidental backdrop/Esc dismiss (#89). */\n confirmingDiscard?: boolean\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 let postSubmitTimer: ReturnType<typeof setTimeout> | null = null\n // Tracks whether the send Form currently holds unsaved input, so an\n // accidental backdrop/Esc dismiss can be guarded with a confirmation (#89).\n let formDirty = false\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, boardSelectId: _drop2, ...rest } = s\n void _drop\n void _drop2\n return rest\n }\n\n /** Open the modal to `send` immediately (non-blocking), then switch to\n * `board` if the current page already has feedback (total > 0).\n * Errors from fetchBoardKpis are silently swallowed — a slow or failing\n * network must never delay or break the FAB open. */\n function openWidget(externalId: string | undefined): void {\n rerender({ ...currentState, open: true, tab: 'send' })\n if (options.openToCurrentPageFeedback && options.api && externalId) {\n // Through the pageSignal cache: the FAB badge fetched these exact\n // KPIs moments ago — don't fire a second identical request per open.\n fetchPageKpis(options.api, externalId, currentPagePath(options))\n .then((kpis) => {\n if (kpis.total > 0 && currentState.open && currentState.tab === 'send') {\n rerender({ ...currentState, tab: 'board' })\n }\n })\n .catch(() => {\n // best-effort; stay on send\n })\n }\n }\n\n function Root({ state }: { state: State }) {\n const handleSubmit = useCallback(async (values: FormValues) => {\n rerender({ ...currentState, status: 'submitting' })\n try {\n const result = await options.onSubmit(values)\n pageSignal.refresh()\n rerender({\n ...currentState,\n status: 'success',\n ...(result && result.seq !== undefined && { submittedSeq: result.seq }),\n })\n // Track the close-the-modal timer so dispose() can cancel it.\n // Without this, calling shutdown() mid-submit (e.g. SPA route\n // change right after the user hits send) lets the timer fire\n // after teardown and rerender into a stale shadow root.\n if (postSubmitTimer !== null) clearTimeout(postSubmitTimer)\n postSubmitTimer = setTimeout(() => {\n postSubmitTimer = null\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 } catch (err) {\n // Don't leak raw API errors to the form's error banner (e.g.\n // `Feedback submit failed: 429 Too Many Requests {…}` straight\n // into a `<div class=\"error\">`). The strings table already has\n // `form.error` — a friendly fallback. Raw error to console.\n if (typeof console !== 'undefined')\n console.warn('[mhosaic] submit:', err)\n rerender({\n ...currentState,\n status: 'error',\n error: options.strings['form.error'],\n })\n }\n }, [])\n\n const externalId = options.getExternalId?.()\n // Resolve a report by its per-project #seq and open its detail. Shared by\n // the jump-by-#seq box and clickable \"#NN\" references (#85). Resolves\n // false when the seq can't be opened (unknown / no access / throttled).\n const openSeq = (seq: number): Promise<boolean> => {\n if (!options.api || !externalId) return Promise.resolve(false)\n return options.api\n .getReportBySeq(seq, externalId)\n .then((r) => {\n rerender({ ...currentState, open: true, tab: 'mine', selectedReportId: r.id })\n return true\n })\n .catch(() => false)\n }\n // Build a shareable permalink for a report id (#85), unless deep-linking\n // is disabled. Sets/replaces the param on the current URL without mutating\n // the live location (the returned string is only ever copied to clipboard).\n const buildPermalink =\n options.deepLinkParam === false\n ? undefined\n : (id: string): string => {\n const param = options.deepLinkParam || 'mhfeedback'\n try {\n const u = new URL(window.location.href)\n u.searchParams.set(param, id)\n return u.toString()\n } catch {\n return ''\n }\n }\n const pageActivityEnabled = options.showPageActivity !== false\n const pageSignal = usePageOpenCount({\n ...(options.api !== undefined && { api: options.api }),\n ...(externalId !== undefined && { externalId }),\n ...(options.getCurrentPage !== undefined && { getCurrentPage: options.getCurrentPage }),\n enabled: pageActivityEnabled,\n })\n const fabLabel =\n pageSignal.open === 1\n ? options.strings['page.badge.aria.one']\n : pageSignal.open > 0\n ? options.strings['page.badge.aria'].replace('{n}', String(pageSignal.open))\n : options.strings['fab.label']\n // Phase 4: per-end-user server visibility. Default true unless the project\n // requires a check; then false until checkVisibility() confirms allowlist\n // membership. Re-runs whenever the resolved identity changes.\n const [visibilityAllowed, setVisibilityAllowed] = useState(\n !options.requiresVisibilityCheck,\n )\n useEffect(() => {\n if (!options.requiresVisibilityCheck) {\n setVisibilityAllowed(true)\n return\n }\n if (!externalId || !options.checkVisibility) {\n setVisibilityAllowed(false)\n return\n }\n let cancelled = false\n options\n .checkVisibility(externalId)\n .then((show) => {\n if (!cancelled) setVisibilityAllowed(show)\n })\n .catch(() => {\n if (!cancelled) setVisibilityAllowed(false)\n })\n return () => {\n cancelled = true\n }\n }, [externalId])\n\n // FAB-visibility gate: showFAB + (identity resolved when the host wired\n // identity) + (server visibility when the project gates it). Legacy hosts\n // that never wired identity stay on the old \"showFAB → visible\" path.\n const fabVisible = computeFabVisible(options, externalId, visibilityAllowed)\n const showMineTab = Boolean(options.api && externalId)\n\n const [peekOpen, setPeekOpen] = useState(false)\n // Hover intent: 250ms to open (no accidental flashes), 140ms debounce to\n // close (QA-Meter precedent) so moving FAB→panel doesn't flicker.\n const peekTimers = useRef<{\n open?: ReturnType<typeof setTimeout>\n close?: ReturnType<typeof setTimeout>\n }>({})\n const peekEnter = () => {\n if (peekTimers.current.close) clearTimeout(peekTimers.current.close)\n peekTimers.current.open = setTimeout(() => setPeekOpen(true), 250)\n }\n const peekLeave = () => {\n if (peekTimers.current.open) clearTimeout(peekTimers.current.open)\n peekTimers.current.close = setTimeout(() => setPeekOpen(false), 140)\n }\n\n return (\n <>\n {fabVisible && (\n <div\n class=\"fab-area\"\n onMouseEnter={peekEnter}\n onMouseLeave={peekLeave}\n onFocusIn={(e: FocusEvent) => {\n // Modal restores focus to the FAB on every dismissal (X,\n // backdrop, Escape, the post-submit auto-close) — that's a\n // programmatic re-focus, not deliberate intent, so only\n // open for keyboard-modality focus (:focus-visible). This\n // also clears any pending mouse-leave close timer so a\n // hover→tab handoff doesn't get closed out from under a\n // keyboard user by a stale 140ms timeout.\n if (peekTimers.current.close) clearTimeout(peekTimers.current.close)\n if ((e.target as HTMLElement).matches?.(':focus-visible')) setPeekOpen(true)\n }}\n onFocusOut={() => setPeekOpen(false)}\n onKeyDown={(e: KeyboardEvent) => e.key === 'Escape' && setPeekOpen(false)}\n >\n {peekOpen &&\n !state.open &&\n pageActivityEnabled &&\n pageSignal.open > 0 &&\n options.api &&\n externalId && (\n <PagePeekPanel\n api={options.api}\n externalId={externalId}\n strings={options.strings}\n {...(options.getCurrentPage !== undefined && {\n getCurrentPage: options.getCurrentPage,\n })}\n open={pageSignal.open}\n onViewAll={() => {\n setPeekOpen(false)\n rerender({ ...currentState, open: true, tab: 'board' })\n }}\n onPickRow={(reportId) => {\n setPeekOpen(false)\n rerender({ ...currentState, open: true, tab: 'board', boardSelectId: reportId })\n }}\n onSend={() => {\n setPeekOpen(false)\n openWidget(externalId)\n }}\n />\n )}\n <Fab\n label={fabLabel}\n onClick={() => openWidget(externalId)}\n {...(pageSignal.open > 0 && { count: pageSignal.open })}\n />\n </div>\n )}\n {state.open && (\n <Modal\n onDismiss={(reason) => {\n // Guard an accidental dismiss (backdrop/Esc) while the send\n // form holds unsaved input — show a discard confirmation\n // instead of closing. The explicit ✕ (reason 'button') and\n // the Cancel action always close (#89).\n if (\n reason !== 'button' &&\n formDirty &&\n currentState.tab === 'send' &&\n currentState.status !== 'submitting'\n ) {\n rerender({ ...currentState, confirmingDiscard: true })\n return\n }\n formDirty = false\n rerender(\n clearSelected({\n ...currentState,\n open: false,\n status: 'idle',\n confirmingDiscard: false,\n }),\n )\n }}\n closeLabel={options.strings['form.close']}\n expanded={state.tab === 'board'}\n >\n {state.confirmingDiscard && (\n <div class=\"discard-confirm\" role=\"alertdialog\" aria-modal=\"true\">\n <div class=\"discard-confirm-card\">\n <p class=\"discard-confirm-title\">\n {options.strings['form.discard.title']}\n </p>\n <p class=\"discard-confirm-body\">\n {options.strings['form.discard.body']}\n </p>\n <div class=\"discard-confirm-actions\">\n <button\n type=\"button\"\n class=\"btn\"\n onClick={() =>\n rerender({ ...currentState, confirmingDiscard: false })\n }\n >\n {options.strings['form.discard.keep']}\n </button>\n <button\n type=\"button\"\n class=\"btn btn--primary\"\n onClick={() => {\n formDirty = false\n rerender(\n clearSelected({\n ...currentState,\n open: false,\n status: 'idle',\n confirmingDiscard: false,\n }),\n )\n }}\n >\n {options.strings['form.discard.confirm']}\n </button>\n </div>\n </div>\n </div>\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 <button\n type=\"button\"\n role=\"tab\"\n aria-selected={state.tab === 'changelog'}\n class={`tab-button ${state.tab === 'changelog' ? 'is-active' : ''}`}\n onClick={() =>\n rerender(clearSelected({ ...currentState, tab: 'changelog' }))\n }\n >\n {options.strings['tab.changelog']}\n </button>\n <button\n type=\"button\"\n role=\"tab\"\n aria-selected={state.tab === 'board'}\n class={`tab-button tab-button--board ${state.tab === 'board' ? 'is-active' : ''}`}\n onClick={() =>\n rerender(clearSelected({ ...currentState, tab: 'board' }))\n }\n >\n {options.strings['tab.board']}\n </button>\n </div>\n )}\n {state.tab === 'send' && (\n <>\n {showMineTab && pageActivityEnabled && (\n <PageActivityStrip\n open={pageSignal.open}\n strings={options.strings}\n onView={() =>\n rerender(clearSelected({ ...currentState, tab: 'board' }))\n }\n />\n )}\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 {...(state.submittedSeq !== undefined && {\n submittedSeq: state.submittedSeq,\n })}\n onDirtyChange={(d) => {\n formDirty = d\n }}\n />\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 onOpenSeq={openSeq}\n />\n )}\n {(state.tab === 'mine' || state.tab === 'changelog') &&\n options.api &&\n externalId &&\n 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 onOpenSeq={openSeq}\n {...(buildPermalink && { buildPermalink })}\n />\n )}\n {state.tab === 'changelog' &&\n options.api &&\n externalId &&\n !state.selectedReportId && (\n <ChangelogList\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 === 'board' && options.api && externalId && (\n // BoardView owns its own master/detail navigation — no\n // selectedReportId routing through the modal-level state.\n <BoardView\n api={options.api}\n externalId={externalId}\n strings={options.strings}\n {...(options.getCurrentPage !== undefined && { getCurrentPage: options.getCurrentPage })}\n {...(state.boardSelectId !== undefined && { initialSelectedId: state.boardSelectId })}\n />\n )}\n </Modal>\n )}\n </>\n )\n }\n\n rerender(currentState)\n\n /** #85 — open a report by id or #seq. Numeric refs resolve via the\n * by-seq endpoint; anything else is treated as a report id. Best-effort:\n * a stale/inaccessible ref (or a missing identity) just no-ops. */\n function openReportRef(ref: string): void {\n const api = options.api\n const externalId = options.getExternalId?.()\n if (!api || !externalId || !ref) return\n const seq = /^\\d+$/.test(ref) ? parseInt(ref, 10) : null\n const resolve =\n seq !== null ? api.getReportBySeq(seq, externalId) : api.getReport(ref, externalId)\n void resolve\n .then((r) =>\n rerender({ ...currentState, open: true, tab: 'mine', selectedReportId: r.id }),\n )\n .catch(() => {\n /* unknown ref / no access / throttled — silent */\n })\n }\n\n // Deep-link on load: if the host URL carries the permalink param, open that\n // report. Read-only — we never write back to the host's URL.\n if (options.deepLinkParam !== false) {\n try {\n const param = options.deepLinkParam || 'mhfeedback'\n const value = new URLSearchParams(window.location.search).get(param)\n if (value) openReportRef(value.trim())\n } catch {\n /* no window / malformed URL — skip */\n }\n }\n\n return {\n open() {\n openWidget(options.getExternalId?.())\n },\n close() {\n rerender({ ...currentState, open: false, status: 'idle' })\n },\n dispose() {\n if (postSubmitTimer !== null) {\n clearTimeout(postSubmitTimer)\n postSubmitTimer = null\n }\n render(null, mountPoint)\n options.host.innerHTML = ''\n },\n notifyIdentityChanged() {\n rerender({ ...currentState })\n },\n }\n}\n","/** Current host-page path for \"feedback on this page\". A host getCurrentPage()\n * override (same contract as QaMeterConfig.getCurrentPage) wins; else the raw\n * pathname. The backend turns this into the canonical key (derive_page_key),\n * so the widget never computes the key itself. */\nexport function currentPagePath(opts: { getCurrentPage?: () => string | null }): string {\n const override = opts.getCurrentPage?.()\n if (override) return override\n return typeof window !== 'undefined' ? window.location.pathname : '/'\n}\n\nconst LOCATION_CHANGE = 'mfb:locationchange'\nlet patched = false\n\nfunction patchHistory(): void {\n if (patched || typeof history === 'undefined') return\n patched = true\n for (const m of ['pushState', 'replaceState'] as const) {\n const orig = history[m]\n history[m] = function (this: History, ...args: unknown[]) {\n const r = (orig as (...a: unknown[]) => unknown).apply(this, args)\n window.dispatchEvent(new Event(LOCATION_CHANGE))\n return r\n } as History[typeof m]\n }\n}\n\n/** Subscribe to client-side navigation (SPA + back/forward). Returns an\n * unsubscribe. Mirrors the QA Meter's history-patch approach. */\nexport function onLocationChange(cb: () => void): () => void {\n patchHistory()\n window.addEventListener('popstate', cb)\n window.addEventListener(LOCATION_CHANGE, cb)\n return () => {\n window.removeEventListener('popstate', cb)\n window.removeEventListener(LOCATION_CHANGE, cb)\n }\n}\n","/**\n * BoardView — the \"Voir tout\" surface (v0.12).\n *\n * Renders the project's reports in a master/detail layout with a KPI\n * strip on top and a filter row below it. Scope follows the project's\n * `share_reports_with_widget` flag server-side; the client just renders\n * what the endpoint returns and uses `is_mine` to permission-gate the\n * detail-pane status buttons.\n *\n * Architecture notes:\n * - The list polls every 30s while open (same cadence as MineList),\n * pausing in hidden tabs and backing off on failures — see poll.ts.\n * Filters trigger an immediate refetch.\n * - The KPI strip refetches alongside the list so the counts always\n * match what the user sees.\n * - Detail pane is the existing ReportDetailView used by My-reports +\n * This-week. Permission-gating happens via the `canModerate` prop\n * (false for project-scoped rows that aren't `is_mine`).\n * - The whole tab assumes the Modal it's rendered into has been\n * `expanded` — see Modal.tsx + .modal.is-expanded in styles.ts.\n */\n\nimport { h, type JSX } from 'preact'\nimport { useEffect, useMemo, useState } from 'preact/hooks'\n\nimport type { ApiClient, BoardListPage } from '../api/client'\nimport type {\n BoardFilters,\n BoardSortKey,\n FeedbackSeverity,\n FeedbackType,\n ReportStatus,\n WidgetBoardKpis,\n WidgetBoardRow,\n} from '../types'\nimport type { StringKey } from './i18n'\nimport { DiagnosticHint } from './DiagnosticHint'\nimport { boardCacheKey, readBoardCache, writeBoardCache } from './boardCache'\nimport { loadBoardView, saveBoardView } from './boardViewStore'\nimport { currentPagePath, onLocationChange } from './currentPage'\nimport { startPoll } from './poll'\nimport { rateLimitMessage } from './rateLimit'\nimport { ReportDetailView } from './ReportDetailView'\n\nconst POLL_MS = 30_000\n\n// The search box debounces before it drives a fetch. Every keystroke used to\n// spin up an immediate project-wide listBoard + fetchBoardKpis pair, so a fast\n// typist fired a burst of expensive `q=` scans that OOM-restarted the shared\n// backend and blanked the board. 300ms matches the admin reports search (#284).\nconst SEARCH_DEBOUNCE_MS = 300\n\nconst SORT_OPTIONS: BoardSortKey[] = ['recent', 'oldest', 'updated', 'severity', 'status']\n\nconst STATUSES: ReportStatus[] = [\n 'new',\n 'in_progress',\n 'awaiting_validation',\n 'closed',\n 'rejected',\n]\nconst TYPES: FeedbackType[] = ['bug', 'feature', 'question', 'praise', 'typo']\nconst SEVERITIES: FeedbackSeverity[] = ['blocker', 'high', 'medium', 'low']\n\ninterface BoardViewProps {\n api: ApiClient\n externalId: string\n strings: Record<StringKey, string>\n getCurrentPage?: () => string | null\n /** Set by the hover peek panel's row click — Board opens with this\n * report pre-selected instead of the empty detail pane (spec §3.2). */\n initialSelectedId?: string\n}\n\nexport function BoardView({\n api,\n externalId,\n strings,\n getCurrentPage,\n initialSelectedId,\n}: BoardViewProps) {\n const [filters, setFilters] = useState<BoardFilters>(() => loadBoardView(externalId))\n // `thisPage` is transient (not persisted) — it tracks whether the board\n // is scoped to the current page path. Defaults on.\n const [thisPage, setThisPage] = useState(true)\n useEffect(() => {\n saveBoardView(externalId, filters)\n }, [externalId, filtersHash(filters)])\n const [page, setPage] = useState<BoardListPage | null>(null)\n const [kpis, setKpis] = useState<WidgetBoardKpis | null>(null)\n const [loading, setLoading] = useState(true)\n // True while the visible rows are the PREVIOUS query's data (locally\n // pre-filtered where possible) and the server round-trip for the new\n // query is still in flight — drives the dimmed \"refreshing\" look.\n const [stale, setStale] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const [selectedId, setSelectedId] = useState<string | null>(initialSelectedId ?? null)\n // Detail-pane mount tick — increments when the user picks a row so the\n // panel can re-mount and re-fetch without us caching old data.\n const [detailKey, setDetailKey] = useState(0)\n const [reloadTick, setReloadTick] = useState(0)\n // Pages 2+ accumulated via the \"Show more\" button (#93). Page 1 lives in\n // `page` and is what the 30s poll refreshes; extras are kept across polls\n // and deduped by id at render. Reset whenever the query changes.\n const [extra, setExtra] = useState<{\n rows: WidgetBoardRow[]\n nextPage: number\n hasMore: boolean\n } | null>(null)\n const [loadingMore, setLoadingMore] = useState(false)\n\n // Search text is kept as a local draft (updated per keystroke so typing stays\n // snappy) and debounced into `filters.q` — the value that actually drives the\n // list + KPI fetch. Committing per keystroke is what stormed the backend.\n const [qDraft, setQDraft] = useState<string>(filters.q ?? '')\n const debouncedQ = useDebouncedValue(qDraft, SEARCH_DEBOUNCE_MS)\n // Commit the settled query into the fetch-driving filters (drop the key when\n // empty, matching the exactOptionalPropertyTypes convention below).\n useEffect(() => {\n setFilters((f) => {\n if ((f.q ?? '') === debouncedQ) return f\n if (debouncedQ === '') {\n const { q: _drop, ...rest } = f\n void _drop\n return rest\n }\n return { ...f, q: debouncedQ }\n })\n }, [debouncedQ])\n // Keep the draft in sync when q is reset elsewhere (the Clear button).\n useEffect(() => {\n setQDraft(filters.q ?? '')\n }, [filters.q])\n\n const activeFilterCount = useMemo(() => {\n return (\n (filters.status?.length ?? 0) +\n (filters.type?.length ?? 0) +\n (filters.severity?.length ?? 0) +\n (filters.q ? 1 : 0) +\n (filters.mine ? 1 : 0)\n )\n }, [filters])\n\n // When thisPage is on and the host SPA navigates, bump reloadTick so the\n // fetch effect re-runs with the new currentPagePath. Clean up on unmount or\n // when thisPage is toggled off.\n useEffect(() => {\n if (!thisPage) return\n return onLocationChange(() => setReloadTick((t) => t + 1))\n }, [thisPage])\n\n // Merge the transient page-path scope into a fetch-only filters object.\n // `pagePath` is never written into the persisted boardViewStore blob.\n // A search query always searches the whole project — the \"Cette page\"\n // scope silently hid cross-page matches, so search looked broken (#92).\n const fetchFilters: BoardFilters = useMemo(() => {\n const searching = !!filters.q?.trim()\n return thisPage && !searching\n ? { ...filters, pagePath: currentPagePath(getCurrentPage !== undefined ? { getCurrentPage } : {}) }\n : filters\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [filtersHash(filters), thisPage, reloadTick])\n\n // Single source of truth for list + KPIs — they refetch together so the\n // counts never drift from the visible rows. Cadence, hidden-tab pause and\n // failure backoff live in startPoll.\n //\n // Query changes are stale-while-revalidate, NOT blank-and-spinner. The\n // #352 requirement stands — a toggle must never look inert when the\n // backend is slow — but it's met by responding instantly instead of by\n // clearing: rows carry status/type/severity/is_mine, so those filters\n // (and the sort) apply locally the moment the user clicks, while the\n // dimmed `is-stale` look says \"confirming\". Search/scope/nav changes\n // can't be computed locally — previous rows stay, dimmed. The skeleton\n // only ever shows on a true cold start (no previous data, no cache).\n // Background 30s polls run inside startPoll and never re-enter this\n // effect, so they keep their data — no flicker on refresh.\n useEffect(() => {\n let cancelled = false\n const controller = new AbortController()\n const cacheKey = boardCacheKey(externalId, JSON.stringify(fetchFilters))\n const cached = readBoardCache(cacheKey)\n if (cached) {\n // Instant paint (e.g. modal reopen, or back to a recently-viewed\n // filter combo) — the poll below still revalidates immediately.\n setPage(cached.page)\n setKpis(cached.kpis)\n setStale(false)\n setLoading(false)\n } else {\n setPage((prev) =>\n prev ? { ...prev, results: applyLocalFilters(prev.results, fetchFilters) } : null,\n )\n setStale(true)\n setLoading(true)\n }\n // Pages 2+ belong to the previous query — drop them.\n setExtra(null)\n setLoadingMore(false)\n const poll = startPoll(async () => {\n try {\n const list = await api.listBoard(externalId, fetchFilters, {\n signal: controller.signal,\n })\n // Phase-3 backends embed the KPI payload in the page envelope so\n // each poll is ONE request; fall back to the standalone endpoint\n // against older backends.\n const k =\n list.kpis ??\n (await api.fetchBoardKpis(externalId, fetchFilters, {\n signal: controller.signal,\n }))\n if (cancelled) return true\n writeBoardCache(cacheKey, list, k)\n setPage(list)\n setKpis(k)\n setStale(false)\n setError(null)\n return true\n } catch (e) {\n if (cancelled) return false\n // 429 → actionable \"retry in Xs\"; otherwise the generic friendly\n // copy. Never store the raw error text (it used to be shown) (#80).\n setError(rateLimitMessage(e, strings) ?? strings['board.list.error'])\n return false\n } finally {\n if (!cancelled) setLoading(false)\n }\n }, POLL_MS)\n return () => {\n cancelled = true\n poll.stop()\n // Cancel the in-flight request server-side too. `cancelled` is set\n // first, so the AbortError lands in a catch that ignores it.\n controller.abort()\n }\n // fetchFilters already encodes filters + thisPage + reloadTick.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [api, externalId, JSON.stringify(fetchFilters)])\n\n // Page 1 (poll-refreshed) + loaded-more extras, deduped by id — a poll\n // tick can shift a row that was on page 2 up into page 1.\n const visibleRows = useMemo(() => {\n if (!page) return []\n if (!extra || extra.rows.length === 0) return page.results\n const seen = new Set(page.results.map((r) => r.id))\n return [...page.results, ...extra.rows.filter((r) => !seen.has(r.id))]\n }, [page, extra])\n const hasMore = extra ? extra.hasMore : Boolean(page?.next)\n\n const loadMore = async () => {\n if (loadingMore || !page) return\n const pageNum = extra ? extra.nextPage : 2\n setLoadingMore(true)\n try {\n const res = await api.listBoard(externalId, { ...fetchFilters, page: pageNum })\n setExtra((prev) => ({\n rows: [...(prev?.rows ?? []), ...res.results],\n nextPage: pageNum + 1,\n hasMore: res.next !== null,\n }))\n } catch {\n // Leave state as-is — the button stays for a retry.\n } finally {\n setLoadingMore(false)\n }\n }\n\n const selectedRow = useMemo(() => {\n if (!selectedId) return null\n return visibleRows.find((r) => r.id === selectedId) ?? null\n }, [selectedId, visibleRows])\n\n const onPickRow = (row: WidgetBoardRow) => {\n setSelectedId(row.id)\n setDetailKey((k) => k + 1)\n }\n\n return (\n <div class={`board-view ${stale ? 'is-stale' : ''}`}>\n <BoardHeader strings={strings} kpis={kpis} thisPage={thisPage} />\n <BoardFilters\n filters={filters}\n onChange={setFilters}\n searchDraft={qDraft}\n onSearchDraftChange={setQDraft}\n activeCount={activeFilterCount}\n strings={strings}\n thisPage={thisPage}\n onToggleThisPage={() => setThisPage((v) => !v)}\n />\n <div class=\"board-body\">\n <div class=\"board-list-wrap\" aria-busy={loading}>\n {error && (\n <div class=\"board-error\" role=\"alert\">\n <span>{error}</span>\n <button\n type=\"button\"\n class=\"btn btn--ghost\"\n onClick={() => {\n setError(null)\n setLoading(true)\n setReloadTick((t) => t + 1)\n }}\n >\n {strings['board.retry']}\n </button>\n <DiagnosticHint api={api} strings={strings} host={api.endpointHost} />\n </div>\n )}\n {!error && loading && !page && (\n <BoardListSkeleton />\n )}\n {/* `stale` widens the condition: when the locally pre-filtered\n previous rows are already empty, say so immediately instead\n of showing dimmed nothing until the server confirms. */}\n {!error && page && visibleRows.length === 0 && (!loading || stale) && (\n <BoardEmpty\n strings={strings}\n thisPage={thisPage}\n onAllPages={() => setThisPage(false)}\n />\n )}\n {!error && page && visibleRows.length > 0 && (\n <>\n <BoardList\n rows={visibleRows}\n selectedId={selectedId}\n onPick={onPickRow}\n strings={strings}\n total={page.count}\n thisPage={thisPage}\n />\n {hasMore && !stale && (\n <button\n type=\"button\"\n class=\"btn btn--ghost board-load-more\"\n onClick={() => void loadMore()}\n disabled={loadingMore}\n >\n {strings['board.list.loadMore']}\n </button>\n )}\n </>\n )}\n </div>\n {/* Keyed on selectedId (not the row): a preselected report that\n isn't on the current list page — the hover-peek fetches a\n different scope than the persisted board filters — still\n renders via its own fetch instead of silently showing the\n empty pane. The row, when present, seeds the instant paint. */}\n <div class={`board-detail-wrap ${selectedId ? 'has-selection' : ''}`}>\n {selectedId ? (\n <ReportDetailView\n key={detailKey}\n api={api}\n externalId={externalId}\n reportId={selectedId}\n strings={strings}\n onBack={() => setSelectedId(null)}\n canModerate={\n selectedRow ? (selectedRow.can_moderate ?? selectedRow.is_mine) : false\n }\n {...(selectedRow ? { seed: selectedRow } : {})}\n variant=\"board\"\n onOpenSeq={(seq) => {\n // Resolve the #NN reference and open it in the detail pane.\n void api\n .getReportBySeq(seq, externalId)\n .then((r) => {\n setSelectedId(r.id)\n setDetailKey((k) => k + 1)\n })\n .catch(() => {\n // Best-effort — a stale/inaccessible #ref just no-ops.\n })\n }}\n />\n ) : (\n <div class=\"board-detail-empty\">\n <DetailEmptyIllustration />\n <p>{strings['board.detail.empty']}</p>\n </div>\n )}\n </div>\n </div>\n </div>\n )\n}\n\n// ---------- subcomponents -------------------------------------------------\n\nfunction BoardHeader({\n strings,\n kpis,\n thisPage,\n}: {\n strings: Record<StringKey, string>\n kpis: WidgetBoardKpis | null\n thisPage: boolean\n}) {\n const scopeLabel = thisPage\n ? strings['board.scope.onThisPage']\n : kpis?.scope === 'project'\n ? strings['board.scope.project']\n : strings['board.scope.mine']\n return (\n <header class=\"board-header\">\n <div class=\"board-header-title\">\n <span class=\"board-header-emoji\" aria-hidden=\"true\">📋</span>\n <div>\n <h2 class=\"board-header-h\">{strings['tab.board']}</h2>\n <p class=\"board-header-sub\">\n {kpis ? `${kpis.total} · ${scopeLabel}` : scopeLabel}\n </p>\n </div>\n </div>\n <BoardKpiStrip kpis={kpis} strings={strings} />\n </header>\n )\n}\n\nfunction BoardKpiStrip({\n kpis,\n strings,\n}: {\n kpis: WidgetBoardKpis | null\n strings: Record<StringKey, string>\n}) {\n // Always render 4 cards so the strip's height is stable while the\n // first fetch is in flight — eliminates the layout shift the PNR\n // page has on its first paint.\n const cells: { key: string; label: string; value: string; tone: string }[] = [\n {\n key: 'new',\n label: strings['board.kpi.new'],\n value: String(kpis?.by_status?.new ?? 0),\n tone: 'tone-new',\n },\n {\n key: 'in_progress',\n label: strings['board.kpi.in_progress'],\n value: String(kpis?.by_status?.in_progress ?? 0),\n tone: 'tone-progress',\n },\n {\n key: 'awaiting_validation',\n label: strings['board.kpi.awaiting_validation'],\n value: String(kpis?.by_status?.awaiting_validation ?? 0),\n tone: 'tone-validation',\n },\n {\n key: 'rate',\n label: strings['board.kpi.resolution_rate'],\n value: kpis ? `${Math.round((kpis.resolution_rate || 0) * 100)}%` : '—',\n tone: 'tone-rate',\n },\n ]\n return (\n <div class={`board-kpi-strip ${kpis ? 'is-ready' : 'is-loading'}`} aria-live=\"polite\">\n {cells.map((c) => (\n <div key={c.key} class={`board-kpi-card ${c.tone}`}>\n <div class=\"board-kpi-value\">{c.value}</div>\n <div class=\"board-kpi-label\">{c.label}</div>\n </div>\n ))}\n </div>\n )\n}\n\nfunction BoardFilters({\n filters,\n onChange,\n searchDraft,\n onSearchDraftChange,\n activeCount,\n strings,\n thisPage,\n onToggleThisPage,\n}: {\n filters: BoardFilters\n onChange: (f: BoardFilters) => void\n /** Live search text (debounced into `filters.q` by the parent). */\n searchDraft: string\n onSearchDraftChange: (q: string) => void\n activeCount: number\n strings: Record<StringKey, string>\n thisPage: boolean\n onToggleThisPage: () => void\n}) {\n // Under `exactOptionalPropertyTypes: true` we can't set keys to\n // `undefined` to clear them — the optional slot doesn't accept the\n // explicit undefined. Drop the key via destructuring instead.\n const setStatus = (value: ReportStatus | '') => {\n if (value === '') {\n const { status: _drop, ...rest } = filters\n void _drop\n onChange(rest)\n } else {\n onChange({ ...filters, status: [value] })\n }\n }\n const setType = (value: FeedbackType | '') => {\n if (value === '') {\n const { type: _drop, ...rest } = filters\n void _drop\n onChange(rest)\n } else {\n onChange({ ...filters, type: [value] })\n }\n }\n const setSeverity = (value: FeedbackSeverity | '') => {\n if (value === '') {\n const { severity: _drop, ...rest } = filters\n void _drop\n onChange(rest)\n } else {\n onChange({ ...filters, severity: [value] })\n }\n }\n const setOrdering = (value: BoardSortKey) => onChange({ ...filters, ordering: value })\n const toggleMine = () => {\n if (filters.mine) {\n const { mine: _drop, ...rest } = filters\n void _drop\n onChange(rest)\n } else {\n onChange({ ...filters, mine: true })\n }\n }\n const clear = () => onChange({})\n return (\n <div class=\"board-filters\">\n <div class=\"board-scope\" role=\"group\" aria-label={strings['board.scope.thisPage']}>\n <button\n type=\"button\"\n class={`board-scope-btn ${thisPage ? 'is-active' : ''}`}\n aria-pressed={thisPage}\n onClick={() => !thisPage && onToggleThisPage()}\n >\n {strings['board.scope.thisPage']}\n </button>\n <button\n type=\"button\"\n class={`board-scope-btn ${!thisPage ? 'is-active' : ''}`}\n aria-pressed={!thisPage}\n onClick={() => thisPage && onToggleThisPage()}\n >\n {strings['board.scope.allPages']}\n </button>\n </div>\n <select\n class=\"board-filter-select\"\n aria-label={strings['board.sort']}\n value={filters.ordering ?? 'recent'}\n onChange={(e) => setOrdering((e.target as HTMLSelectElement).value as BoardSortKey)}\n >\n {SORT_OPTIONS.map((o) => (\n <option key={o} value={o}>\n {strings[`board.sort.${o}` as StringKey]}\n </option>\n ))}\n </select>\n <select\n class=\"board-filter-select\"\n aria-label={strings['board.filter.status']}\n value={filters.status?.[0] ?? ''}\n onChange={(e) =>\n setStatus((e.target as HTMLSelectElement).value as ReportStatus | '')\n }\n >\n <option value=\"\">{strings['board.filter.status']}</option>\n {STATUSES.map((s) => (\n <option key={s} value={s}>\n {strings[`board.kpi.${s}` as StringKey] ?? s}\n </option>\n ))}\n </select>\n <select\n class=\"board-filter-select\"\n aria-label={strings['board.filter.type']}\n value={filters.type?.[0] ?? ''}\n onChange={(e) =>\n setType((e.target as HTMLSelectElement).value as FeedbackType | '')\n }\n >\n <option value=\"\">{strings['board.filter.type']}</option>\n {TYPES.map((t) => (\n <option key={t} value={t}>\n {strings[`type.${t}` as StringKey] ?? t}\n </option>\n ))}\n </select>\n <select\n class=\"board-filter-select\"\n aria-label={strings['board.filter.severity']}\n value={filters.severity?.[0] ?? ''}\n onChange={(e) =>\n setSeverity((e.target as HTMLSelectElement).value as FeedbackSeverity | '')\n }\n >\n <option value=\"\">{strings['board.filter.severity']}</option>\n {SEVERITIES.map((s) => (\n <option key={s} value={s}>\n {strings[`severity.${s}` as StringKey] ?? s}\n </option>\n ))}\n </select>\n <input\n type=\"search\"\n class=\"board-filter-search\"\n placeholder={strings['board.filter.search.placeholder']}\n value={searchDraft}\n onInput={(e) => onSearchDraftChange((e.target as HTMLInputElement).value)}\n />\n <label class=\"board-filter-toggle\">\n <input\n type=\"checkbox\"\n checked={Boolean(filters.mine)}\n onChange={toggleMine}\n />\n <span>{strings['board.filter.mine']}</span>\n </label>\n {activeCount > 0 && (\n <button type=\"button\" class=\"board-filter-clear\" onClick={clear}>\n <CloseIcon />\n {strings['board.filter.clear']}\n </button>\n )}\n </div>\n )\n}\n\nfunction BoardList({\n rows,\n selectedId,\n onPick,\n strings,\n total,\n thisPage,\n}: {\n rows: WidgetBoardRow[]\n selectedId: string | null\n onPick: (row: WidgetBoardRow) => void\n strings: Record<StringKey, string>\n total: number\n thisPage: boolean\n}) {\n return (\n <>\n <div class=\"board-list-count\">\n {strings[thisPage ? 'board.list.count.page' : 'board.list.count']\n .replace('{n}', String(rows.length))\n .replace('{total}', String(total))}\n </div>\n <ul class=\"board-list\" role=\"list\">\n {rows.map((row) => (\n <BoardRowCard\n key={row.id}\n row={row}\n selected={row.id === selectedId}\n onPick={onPick}\n strings={strings}\n />\n ))}\n </ul>\n </>\n )\n}\n\nfunction BoardRowCard({\n row,\n selected,\n onPick,\n strings,\n}: {\n row: WidgetBoardRow\n selected: boolean\n onPick: (row: WidgetBoardRow) => void\n strings: Record<StringKey, string>\n}) {\n const author = row.is_mine\n ? strings['board.list.you']\n : row.author_label || '—'\n return (\n <li>\n <button\n type=\"button\"\n class={`board-row ${selected ? 'is-selected' : ''}`}\n onClick={() => onPick(row)}\n aria-pressed={selected}\n >\n <div class=\"board-row-badges\">\n <span class={`badge status-${row.status}`}>\n {strings[`status.${row.status}` as StringKey] ?? row.status}\n </span>\n <span class={`badge severity-${row.severity}`}>\n {strings[`severity.${row.severity}` as StringKey] ?? row.severity}\n </span>\n </div>\n <div class=\"board-row-description\">{row.description}</div>\n <div class=\"board-row-meta\">\n <span class=\"board-row-author\">{author}</span>\n {row.comment_count > 0 && (\n <span class=\"board-row-comments\">\n <CommentIcon /> {row.comment_count}\n </span>\n )}\n <span class=\"board-row-time\">{formatRelative(row.created_at)}</span>\n </div>\n </button>\n </li>\n )\n}\n\nfunction BoardEmpty({\n strings,\n thisPage,\n onAllPages,\n}: {\n strings: Record<StringKey, string>\n thisPage: boolean\n onAllPages: () => void\n}) {\n return (\n <div class=\"board-empty\">\n <EmptyIllustration />\n <h3>{strings[thisPage ? 'board.list.empty.page.title' : 'board.list.empty.title']}</h3>\n <p>\n {strings[thisPage ? 'board.list.empty.page.description' : 'board.list.empty.description']}\n </p>\n {thisPage && (\n <button type=\"button\" class=\"btn btn--ghost\" onClick={onAllPages}>\n {strings['board.scope.allPages']}\n </button>\n )}\n </div>\n )\n}\n\nfunction BoardListSkeleton() {\n return (\n <ul class=\"board-list board-list-skeleton\" aria-hidden=\"true\">\n {Array.from({ length: 5 }).map((_, i) => (\n <li key={i}>\n <div class=\"board-row skeleton-row\">\n <div class=\"skeleton-line skeleton-badges\" />\n <div class=\"skeleton-line skeleton-text\" />\n <div class=\"skeleton-line skeleton-text short\" />\n </div>\n </li>\n ))}\n </ul>\n )\n}\n\n// ---------- icons ---------------------------------------------------------\n\nfunction CommentIcon(): JSX.Element {\n return (\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\" />\n </svg>\n )\n}\n\nfunction CloseIcon(): JSX.Element {\n return (\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n )\n}\n\nfunction EmptyIllustration(): JSX.Element {\n return (\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\" aria-hidden=\"true\">\n <circle cx=\"32\" cy=\"32\" r=\"28\" stroke=\"currentColor\" stroke-opacity=\"0.18\" stroke-width=\"2\" />\n <path d=\"M22 32h20M32 22v20\" stroke=\"currentColor\" stroke-opacity=\"0.35\" stroke-width=\"2\" stroke-linecap=\"round\" />\n </svg>\n )\n}\n\nfunction DetailEmptyIllustration(): JSX.Element {\n return (\n <svg width=\"80\" height=\"80\" viewBox=\"0 0 64 64\" fill=\"none\" aria-hidden=\"true\">\n <rect x=\"10\" y=\"12\" width=\"44\" height=\"36\" rx=\"4\" stroke=\"currentColor\" stroke-opacity=\"0.22\" stroke-width=\"2\" />\n <line x1=\"18\" y1=\"22\" x2=\"46\" y2=\"22\" stroke=\"currentColor\" stroke-opacity=\"0.22\" stroke-width=\"2\" stroke-linecap=\"round\" />\n <line x1=\"18\" y1=\"30\" x2=\"38\" y2=\"30\" stroke=\"currentColor\" stroke-opacity=\"0.18\" stroke-width=\"2\" stroke-linecap=\"round\" />\n <line x1=\"18\" y1=\"38\" x2=\"32\" y2=\"38\" stroke=\"currentColor\" stroke-opacity=\"0.14\" stroke-width=\"2\" stroke-linecap=\"round\" />\n </svg>\n )\n}\n\n// ---------- helpers -------------------------------------------------------\n\n/** Returns `value` delayed by `ms`, re-timing on every change so a burst of\n * updates collapses to the last one. Keeps the board search from firing a\n * project-wide query on every keystroke. */\nfunction useDebouncedValue<T>(value: T, ms: number): T {\n const [debounced, setDebounced] = useState(value)\n useEffect(() => {\n const t = setTimeout(() => setDebounced(value), ms)\n return () => clearTimeout(t)\n }, [value, ms])\n return debounced\n}\n\nconst SEVERITY_RANK: Record<string, number> = { blocker: 0, high: 1, medium: 2, low: 3 }\n\n/** Best-effort local application of the new query to the PREVIOUS rows so\n * a filter/sort click responds in the same frame (#352: never inert).\n * Only predicates the row data can answer are applied — q / page scope\n * are server-side concepts and leave the rows as-is (dimmed instead).\n * The server response replaces all of this within the round-trip. */\nfunction applyLocalFilters(rows: WidgetBoardRow[], f: BoardFilters): WidgetBoardRow[] {\n let out = rows\n if (f.status?.length) out = out.filter((r) => f.status!.includes(r.status))\n if (f.type?.length) out = out.filter((r) => f.type!.includes(r.feedback_type))\n if (f.severity?.length) out = out.filter((r) => f.severity!.includes(r.severity))\n if (f.mine) out = out.filter((r) => r.is_mine)\n const sorted = out.slice()\n switch (f.ordering ?? 'recent') {\n case 'oldest':\n sorted.sort((a, b) => a.created_at.localeCompare(b.created_at))\n break\n case 'updated':\n sorted.sort((a, b) => b.updated_at.localeCompare(a.updated_at))\n break\n case 'severity':\n sorted.sort(\n (a, b) => (SEVERITY_RANK[a.severity] ?? 9) - (SEVERITY_RANK[b.severity] ?? 9),\n )\n break\n case 'status':\n // Server-defined ordering — leave the previous order until it lands.\n break\n default:\n sorted.sort((a, b) => b.created_at.localeCompare(a.created_at))\n }\n return sorted\n}\n\nfunction filtersHash(f: BoardFilters): string {\n // Stable serialization for the useEffect dep array — avoids re-running\n // when the BoardView re-renders with a new object identity but the\n // same filter values.\n return JSON.stringify({\n s: f.status?.slice().sort(),\n t: f.type?.slice().sort(),\n sv: f.severity?.slice().sort(),\n q: f.q ?? '',\n m: Boolean(f.mine),\n o: f.ordering ?? '',\n })\n}\n\nfunction formatRelative(iso: string): string {\n // Tiny \"5m / 3h / 2d / 5w\" formatter — same shape PNR uses. Doesn't\n // depend on date-fns; keeps the bundle lean.\n const then = new Date(iso).getTime()\n if (Number.isNaN(then)) return ''\n const delta = Math.max(0, Date.now() - then)\n const m = Math.floor(delta / 60_000)\n if (m < 1) return 'just now'\n if (m < 60) return `${m}m`\n const h = Math.floor(m / 60)\n if (h < 24) return `${h}h`\n const d = Math.floor(h / 24)\n if (d < 7) return `${d}d`\n const w = Math.floor(d / 7)\n return `${w}w`\n}\n","import { useState } from 'preact/hooks'\n\nimport type { ApiClient } from '../api/client'\nimport type { DiagnosisKind } from '../types'\nimport type { StringKey } from './i18n'\n\ninterface DiagnosticHintProps {\n api: ApiClient\n strings: Record<StringKey, string>\n /** Host shown in the \"ask IT to allow {host}\" copy. */\n host: string\n}\n\n/**\n * Runtime self-diagnosis (roadmap #8) — the Zscaler lesson. When readers\n * fail, the user can ask the widget WHY instead of filing \"it's broken\":\n * two probes classify offline / blocked / SSL-inspection interception /\n * preflight-stripping proxy / key / throttle, with copy IT can act on.\n */\nexport function DiagnosticHint({ api, strings, host }: DiagnosticHintProps) {\n const [state, setState] = useState<'idle' | 'running' | DiagnosisKind>('idle')\n\n const run = async () => {\n setState('running')\n try {\n setState(await api.diagnose())\n } catch {\n setState('blocked')\n }\n }\n\n if (state === 'idle') {\n return (\n <button type=\"button\" class=\"btn diag-btn\" onClick={() => void run()}>\n {strings['diag.run']}\n </button>\n )\n }\n if (state === 'running') {\n return <div class=\"diag-result\">{strings['diag.running']}</div>\n }\n const key = `diag.${state}` as StringKey\n return <div class=\"diag-result\">{(strings[key] ?? state).replace('{host}', host)}</div>\n}\n","/** Module-level board data cache — the `pageSignal.ts` pattern applied to\n * the Board tab's list + KPIs.\n *\n * The modal unmounts BoardView on close, so before this cache every\n * reopen was a skeleton + a fresh listBoard/fetchBoardKpis pair even\n * when the user had looked at the exact same board seconds earlier.\n * Entries are keyed by (externalId, serialized fetch filters) and only\n * ever used as an instant first paint — BoardView always revalidates\n * immediately, so a stale entry can survive at most one poll tick. */\nimport type { BoardListPage } from '../api/client'\nimport type { WidgetBoardKpis } from '../types'\n\nconst TTL_MS = 60_000\n// Session-scoped safety cap: distinct filter combos accumulate over a long\n// session; evict oldest-written past this rather than growing unbounded.\nconst MAX_ENTRIES = 50\n\ninterface BoardCacheEntry {\n page: BoardListPage\n kpis: WidgetBoardKpis\n at: number\n}\n\nconst cache = new Map<string, BoardCacheEntry>()\n\nexport function boardCacheKey(externalId: string, fetchKey: string): string {\n return `${externalId}|${fetchKey}`\n}\n\nexport function readBoardCache(key: string): BoardCacheEntry | null {\n const hit = cache.get(key)\n return hit && Date.now() - hit.at < TTL_MS ? hit : null\n}\n\nexport function writeBoardCache(\n key: string,\n page: BoardListPage,\n kpis: WidgetBoardKpis,\n): void {\n if (!cache.has(key) && cache.size >= MAX_ENTRIES) {\n const oldest = cache.keys().next().value\n if (oldest !== undefined) cache.delete(oldest)\n }\n cache.delete(key) // re-insert so Map order stays write-ordered\n cache.set(key, { page, kpis, at: Date.now() })\n}\n\nexport function _resetBoardCache(): void {\n cache.clear()\n}\n","import type { BoardFilters } from '../types'\n\n// Per-user persisted Board view (sort + filters). A widget instance is\n// scoped to one project, so the external id is a sufficient key. Best-\n// effort: storage may be disabled or hold a stale/corrupt blob — every\n// path falls back to an empty view rather than throwing into render.\nconst PREFIX = 'mhosaic.boardView.'\n\nexport function loadBoardView(externalId: string): BoardFilters {\n try {\n const raw = localStorage.getItem(PREFIX + externalId)\n if (!raw) return {}\n const parsed = JSON.parse(raw)\n return parsed && typeof parsed === 'object' ? (parsed as BoardFilters) : {}\n } catch {\n return {}\n }\n}\n\nexport function saveBoardView(externalId: string, view: BoardFilters): void {\n try {\n localStorage.setItem(PREFIX + externalId, JSON.stringify(view))\n } catch {\n /* storage disabled / quota — non-fatal, just no persistence */\n }\n}\n","/**\n * startPoll — shared scheduler for the widget's 30s refresh loops\n * (BoardView, MineList, ChangelogList, ReportDetailView).\n *\n * Born from the 2026-07-07 OOM outage: board tabs left open overnight kept\n * polling at full cadence — even while every response was a 429 — and the\n * accumulated fleet-wide churn restart-looped the backend. Two rules fix\n * that class of failure at the source:\n *\n * - Hidden tabs don't poll. When a tick comes due while `document.hidden`,\n * the loop parks instead of fetching; a `visibilitychange` listener\n * fires an immediate refresh when the tab comes back, so a returning\n * user still sees fresh data without ever paying for a background tab.\n * - Failures back off. Any failed tick doubles the delay, capped at\n * MAX_BACKOFF_MULTIPLIER × interval — a throttled (429) or down backend\n * sees pressure decay instead of a steady hammer. One success resets\n * the cadence.\n *\n * The first tick runs immediately (every caller renders from it). `tick`\n * reports success via its return value instead of throwing — callers\n * already own their error state; a throw is treated as a failed tick so a\n * bug in a caller can never kill the loop.\n */\n\nexport interface PollHandle {\n stop(): void\n}\n\n/** Backoff ceiling: 30s cadence degrades no further than 5 min. */\nconst MAX_BACKOFF_MULTIPLIER = 10\n\nexport function startPoll(\n tick: () => Promise<boolean>,\n intervalMs: number,\n): PollHandle {\n let stopped = false\n let parked = false\n let failures = 0\n let timer: ReturnType<typeof setTimeout> | null = null\n const doc = typeof document !== 'undefined' ? document : null\n\n const delayMs = () =>\n Math.min(intervalMs * 2 ** failures, intervalMs * MAX_BACKOFF_MULTIPLIER)\n\n const run = async () => {\n if (stopped) return\n if (doc?.hidden) {\n parked = true\n return\n }\n let ok = false\n try {\n ok = await tick()\n } catch {\n ok = false\n }\n if (stopped) return\n failures = ok ? 0 : failures + 1\n timer = setTimeout(() => void run(), delayMs())\n }\n\n const onVisibilityChange = () => {\n if (stopped || !parked || doc?.hidden) return\n parked = false\n void run()\n }\n\n doc?.addEventListener('visibilitychange', onVisibilityChange)\n void run()\n\n return {\n stop() {\n stopped = true\n if (timer) clearTimeout(timer)\n doc?.removeEventListener('visibilitychange', onVisibilityChange)\n },\n }\n}\n","import { RateLimitError } from '../api/client'\nimport type { StringKey } from './i18n'\n\n/**\n * Map a caught reader error to an actionable \"too many requests\" message when\n * it's a 429 (#80/#81), else null so the caller can fall back to its generic\n * error copy. Prevents the widget from showing an infinite \"Loading…\" or a\n * false \"Report not available\" when the real cause is a rate limit.\n */\nexport function rateLimitMessage(\n err: unknown,\n strings: Record<StringKey, string>,\n): string | null {\n if (!(err instanceof RateLimitError)) return null\n return err.retryAfterSeconds > 0\n ? strings['error.rate_limited'].replace(\n '{seconds}',\n String(err.retryAfterSeconds),\n )\n : strings['error.rate_limited_generic']\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 { linkifySeq } from './linkifySeq'\nimport { rateLimitMessage } from './rateLimit'\n\nimport type { ApiClient } from '../api/client'\nimport type {\n CapturedContext,\n ConsoleEntry,\n ErrorEntry,\n NetworkEntry,\n WidgetBoardRow,\n WidgetCommentRow,\n WidgetReportDetail,\n WidgetStatusChangeRow,\n WidgetVerification,\n} from '../types'\nimport { CommentBubble } from './CommentBubble'\nimport type { StringKey } from './i18n'\nimport { startPoll } from './poll'\nimport {\n MAX_SCREENSHOTS,\n safeExternalHref,\n truncateUrl,\n validateScreenshotFile,\n} from './screenshot-utils'\n\ninterface ReportDetailViewProps {\n api: ApiClient\n externalId: string\n reportId: string\n strings: Record<StringKey, string>\n onBack: () => void\n /** When false, the self-service \"Confirm resolution\" button is hidden\n * even if the report is in awaiting_validation. The Board passes false\n * for project-scoped rows that don't belong to the current user. */\n canModerate?: boolean\n /** Layout flavor. \"modal\" (default) keeps the original My-reports look —\n * scrollable card centered in the modal. \"board\" tightens the padding\n * + drops the top \"← back\" because the Board renders the back action\n * in its own header. */\n variant?: 'modal' | 'board'\n /** Open another report from a clickable \"#NN\" reference in the description\n * or a comment (#85). Absent → references render as plain text. */\n onOpenSeq?: (seq: number) => void\n /** Build a shareable permalink for this report id (#85). Absent → the\n * \"copy link\" affordance is hidden. */\n buildPermalink?: (reportId: string) => string\n /** Board row already in memory for this report. When set, the pane paints\n * the row's status + description in the SAME frame as the click and only\n * the thread section waits on the network — clicking a report used to be\n * a guaranteed full-pane spinner even though these fields were already\n * loaded (perf program D6). */\n seed?: WidgetBoardRow\n}\n\nconst POLL_MS = 30_000\n\nexport function ReportDetailView({\n api,\n externalId,\n reportId,\n strings,\n onBack,\n canModerate = true,\n variant = 'modal',\n onOpenSeq,\n buildPermalink,\n seed,\n}: ReportDetailViewProps) {\n const [detail, setDetail] = useState<WidgetReportDetail | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [copied, setCopied] = useState(false)\n const [editing, setEditing] = useState(false)\n const [editBody, setEditBody] = useState('')\n const [savingEdit, setSavingEdit] = useState(false)\n const [editError, setEditError] = useState(false)\n const [composeBody, setComposeBody] = useState('')\n const [sending, setSending] = useState(false)\n const [closing, setClosing] = useState(false)\n const [reopening, setReopening] = useState(false)\n // Pending images to attach to the next comment (#91). Same {blob, preview}\n // shape and limits as the report compose form.\n const [composeShots, setComposeShots] = useState<\n Array<{ blob: Blob; preview: string }>\n >([])\n const [attachError, setAttachError] = useState('')\n const fileInputRef = useRef<HTMLInputElement>(null)\n const mountedRef = useRef(true)\n\n // Screenshot/attachment URLs are presigned per response — SigV4 stamps\n // the signing time, so every 30s poll returns DIFFERENT URLs for the\n // same immutable images. Swapping `src` each tick made the browser\n // re-download every image for as long as the pane stayed open. Pin the\n // first URL seen per stable identity (screenshot index / attachment id);\n // presigns live 3600s, so refresh a pin only after 45min or when the\n // image errors (expired early / revoked).\n const urlPins = useRef(new Map<string, { url: string; at: number }>())\n const [, bumpPinTick] = useState(0)\n const PIN_MAX_AGE_MS = 45 * 60_000\n const pinUrl = (key: string, url: string | null): string | null => {\n if (!url) return url\n const pin = urlPins.current.get(key)\n if (pin && Date.now() - pin.at < PIN_MAX_AGE_MS) return pin.url\n urlPins.current.set(key, { url, at: Date.now() })\n return url\n }\n const unpinUrl = (key: string) => {\n urlPins.current.delete(key)\n // Re-render so the next paint picks the freshest presigned URL from\n // the current `detail`.\n bumpPinTick((t) => t + 1)\n }\n\n // Revoke every pending preview URL on unmount (individual revokes happen\n // on remove/send).\n const shotsRef = useRef(composeShots)\n shotsRef.current = composeShots\n useEffect(() => {\n return () => shotsRef.current.forEach((s) => URL.revokeObjectURL(s.preview))\n }, [])\n\n const acceptComposeFiles = (files: File[]) => {\n setAttachError('')\n const remaining = MAX_SCREENSHOTS - composeShots.length\n const batch = files.slice(0, Math.max(0, remaining))\n for (const file of batch) {\n const err = validateScreenshotFile(file)\n if (err) {\n setAttachError(\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 (batch.length) {\n const incoming = batch.map((blob) => ({\n blob,\n preview: URL.createObjectURL(blob),\n }))\n setComposeShots((prev) => [...prev, ...incoming])\n }\n if (files.length > batch.length) {\n setAttachError(\n strings['form.screenshot.error_count'].replace('{max}', String(MAX_SCREENSHOTS)),\n )\n }\n }\n\n const handleAttachChange = (e: Event) => {\n const input = e.target as HTMLInputElement\n const files = Array.from(input.files ?? [])\n if (files.length) acceptComposeFiles(files)\n input.value = '' // re-picking the same file must re-fire change\n }\n\n const removeComposeShot = (index: number) => {\n const shot = composeShots[index]\n if (shot) URL.revokeObjectURL(shot.preview)\n setComposeShots((prev) => prev.filter((_, i) => i !== index))\n setAttachError('')\n }\n\n const fetchDetail = async () => {\n try {\n const next = await api.getReport(reportId, externalId)\n if (!mountedRef.current) return true\n setDetail(next)\n setError(null)\n return true\n } catch (err) {\n // The verbatim `err.message` (e.g. `getReport failed: 404 …`) leaked\n // straight into the panel pre-v0.15.3. Friendly UI via the\n // `!detail` empty-state branch below; raw error to console.\n if (typeof console !== 'undefined') console.warn('[mhosaic] getReport:', err)\n if (!mountedRef.current) return false\n // `load_failed` is a sentinel — the render branch swaps to the\n // friendly \"not available\" empty-state when `error` is that sentinel\n // and `detail` is null. A 429 must NOT claim the report was deleted\n // (it wasn't — it's just throttled), so we store the actionable\n // rate-limit message instead and the render shows it verbatim (#80/#81).\n const rl = rateLimitMessage(err, strings)\n setError(rl ?? 'load_failed')\n return false\n }\n }\n\n useEffect(() => {\n mountedRef.current = true\n const poll = startPoll(fetchDetail, POLL_MS)\n return () => {\n mountedRef.current = false\n poll.stop()\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [reportId, externalId])\n\n const handleSend = async () => {\n // A comment may be text, images, or both — send if either is present.\n if ((!composeBody.trim() && composeShots.length === 0) || sending) return\n setSending(true)\n try {\n const nonce = `${reportId}:${Date.now()}`\n const attachments = composeShots.map((s) => s.blob)\n const created = await api.addComment(\n reportId,\n externalId,\n composeBody.trim(),\n nonce,\n attachments.length ? attachments : undefined,\n )\n if (!mountedRef.current) return\n setComposeBody('')\n composeShots.forEach((s) => URL.revokeObjectURL(s.preview))\n setComposeShots([])\n setAttachError('')\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 // Don't surface raw API messages (e.g. `addComment failed: 403 …`).\n // Friendly fallback in the UI, raw error in the console.\n if (typeof console !== 'undefined') console.warn('[mhosaic] addComment:', err)\n if (!mountedRef.current) return\n setError(strings['detail.send_failed'])\n } finally {\n if (mountedRef.current) setSending(false)\n }\n }\n\n const handleCopyLink = async () => {\n if (!buildPermalink || !detail) return\n try {\n await navigator.clipboard.writeText(buildPermalink(detail.id))\n if (!mountedRef.current) return\n setCopied(true)\n setTimeout(() => {\n if (mountedRef.current) setCopied(false)\n }, 2000)\n } catch {\n /* clipboard blocked (permissions / insecure context) — no-op */\n }\n }\n\n const startEdit = () => {\n if (!detail) return\n setEditBody(detail.description)\n setEditError(false)\n setEditing(true)\n }\n const cancelEdit = () => {\n setEditing(false)\n setEditError(false)\n }\n const handleSaveEdit = async () => {\n if (!detail || savingEdit) return\n const next = editBody.trim()\n if (!next) return\n setSavingEdit(true)\n setEditError(false)\n try {\n const updated = await api.editDescription(reportId, externalId, next)\n if (!mountedRef.current) return\n setDetail(updated)\n setEditing(false)\n } catch (err) {\n if (typeof console !== 'undefined') console.warn('[mhosaic] editDescription:', err)\n if (!mountedRef.current) return\n setEditError(true)\n } finally {\n if (mountedRef.current) setSavingEdit(false)\n }\n }\n\n const handleClose = async () => {\n if (closing || reopening) 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 (typeof console !== 'undefined')\n console.warn('[mhosaic] closeAsResolved:', err)\n if (!mountedRef.current) return\n setError(strings['detail.close_failed'])\n } finally {\n if (mountedRef.current) setClosing(false)\n }\n }\n\n const handleReopen = async () => {\n if (closing || reopening) return\n setReopening(true)\n try {\n const next = await api.reopenUnresolved(reportId, externalId)\n if (!mountedRef.current) return\n setDetail(next)\n } catch (err) {\n if (typeof console !== 'undefined')\n console.warn('[mhosaic] reopenUnresolved:', err)\n if (!mountedRef.current) return\n setError(strings['detail.reopen_failed'])\n } finally {\n if (mountedRef.current) setReopening(false)\n }\n }\n\n if (!detail && !error) {\n if (seed) {\n // Optimistic first paint from the board row: real status pill + full\n // description immediately, spinner scoped to the thread only. The\n // fetched detail replaces this wholesale (including any fresher\n // status), so a stale seed survives at most one round-trip.\n const seedPill = (\n <span class={`pill pill-status pill-status--${seed.status}`}>\n {strings[`status.${seed.status}` as StringKey] ?? seed.status}\n </span>\n )\n return (\n <div class={`report-detail variant-${variant} is-seeded`}>\n {variant === 'modal' && (\n <div class=\"report-detail-header\">\n <button type=\"button\" class=\"btn\" onClick={onBack}>\n ← {strings['detail.back']}\n </button>\n {seedPill}\n </div>\n )}\n {variant === 'board' && (\n <div class=\"report-detail-header report-detail-header--board\">\n <button\n type=\"button\"\n class=\"btn btn--ghost report-detail-back\"\n onClick={onBack}\n aria-label={strings['board.back']}\n >\n ← {strings['board.back']}\n </button>\n {seedPill}\n </div>\n )}\n <div class=\"report-detail-body\">\n <div class=\"report-detail-description-row\">\n <p class=\"report-detail-description\">\n {linkifySeq(seed.description, onOpenSeq)}\n </p>\n </div>\n <h3 class=\"report-detail-section\">{strings['detail.thread']}</h3>\n <div class=\"mine-loading\">{strings['mine.loading']}</div>\n </div>\n </div>\n )\n }\n return <div class=\"mine-loading\">{strings['mine.loading']}</div>\n }\n\n if (!detail) {\n // A 429 stores an actionable rate-limit message rather than the\n // 'load_failed' sentinel — show it with a Retry instead of the (false)\n // \"may have been deleted\" copy (#80/#81).\n if (error && error !== 'load_failed') {\n return (\n <div class=\"report-detail-empty-state\" role=\"alert\">\n <p class=\"report-detail-empty-state-body\">{error}</p>\n <button type=\"button\" class=\"btn\" onClick={() => void fetchDetail()}>\n {strings['board.retry']}\n </button>\n <button type=\"button\" class=\"btn\" onClick={onBack}>\n ← {strings['detail.load_failed.cta']}\n </button>\n </div>\n )\n }\n // The raw `error.message` from the api client used to render here as\n // e.g. `getReport failed: 404 {\"detail\":\"Report not found.\"}` — a\n // verbatim leak of the backend error. The friendly variant + a Back\n // button is what end users (and external testers) expect to see.\n return (\n <div class=\"report-detail-empty-state\" role=\"alert\">\n <p class=\"report-detail-empty-state-title\">\n {strings['detail.load_failed.title']}\n </p>\n <p class=\"report-detail-empty-state-body\">\n {strings['detail.load_failed.body']}\n </p>\n <button type=\"button\" class=\"btn\" onClick={onBack}>\n ← {strings['detail.load_failed.cta']}\n </button>\n </div>\n )\n }\n\n // Permission gating. `canModerate` is passed by the parent (Board\n // passes `is_mine`; My-reports defaults to true). v0.15.3 also reads\n // `detail.is_mine` from the server when present, so a direct fetch\n // (no parent context) still gates correctly.\n const isMine = detail.is_mine ?? canModerate\n // `can_moderate` (server-computed) widens the action strip to\n // colleagues when the project opted into widget_peer_validation;\n // older backends omit it and we fall back to the submitter-only rule.\n const canAct = detail.can_moderate ?? isMine\n // \"Mark as resolved\" is the only self-service status transition\n // the widget allows (awaiting_validation → closed).\n const showCloseCta = canAct && detail.status === 'awaiting_validation'\n // Reply compose box: submitter, or a peer under peer validation.\n // Hidden (not just disabled) otherwise, since the action is genuinely\n // unavailable.\n const showComposeBox = canAct\n\n return (\n <div class={`report-detail variant-${variant}`}>\n {variant === 'modal' && (\n <div class=\"report-detail-header\">\n <button type=\"button\" class=\"btn\" onClick={onBack}>\n ← {strings['detail.back']}\n </button>\n <span class=\"report-detail-header-pills\">\n <span class={`pill pill-status pill-status--${detail.status}`}>\n {strings[`status.${detail.status}` as StringKey] ?? detail.status}\n </span>\n {detail.verification && (\n <span class=\"pill pill-verified\">{strings['detail.verified.badge']}</span>\n )}\n </span>\n </div>\n )}\n {variant === 'board' && (\n <div class=\"report-detail-header report-detail-header--board\">\n <button\n type=\"button\"\n class=\"btn btn--ghost report-detail-back\"\n onClick={onBack}\n aria-label={strings['board.back']}\n >\n ← {strings['board.back']}\n </button>\n <span class=\"report-detail-header-pills\">\n <span class={`pill pill-status pill-status--${detail.status}`}>\n {strings[`status.${detail.status}` as StringKey] ?? detail.status}\n </span>\n {detail.verification && (\n <span class=\"pill pill-verified\">{strings['detail.verified.badge']}</span>\n )}\n </span>\n </div>\n )}\n\n <div class=\"report-detail-body\">\n <ContextBlock detail={detail} strings={strings} />\n {editing ? (\n <div class=\"report-detail-edit\">\n <textarea\n class=\"report-detail-edit-input\"\n value={editBody}\n onInput={(e) => {\n setEditBody((e.target as HTMLTextAreaElement).value)\n setEditError(false)\n }}\n disabled={savingEdit}\n />\n {editError && (\n <p class=\"error\" role=\"alert\">\n {strings['detail.edit_failed']}\n </p>\n )}\n <div class=\"report-detail-edit-actions\">\n <button\n type=\"button\"\n class=\"btn btn--ghost\"\n onClick={cancelEdit}\n disabled={savingEdit}\n >\n {strings['detail.edit_cancel']}\n </button>\n <button\n type=\"button\"\n class=\"btn btn--primary\"\n onClick={handleSaveEdit}\n disabled={savingEdit || !editBody.trim()}\n >\n {savingEdit\n ? strings['detail.edit_saving']\n : strings['detail.edit_save']}\n </button>\n </div>\n </div>\n ) : (\n <div class=\"report-detail-description-row\">\n <p class=\"report-detail-description\">\n {linkifySeq(detail.description, onOpenSeq)}\n </p>\n {isMine && (\n <button\n type=\"button\"\n class=\"btn btn--ghost report-detail-edit\"\n onClick={startEdit}\n >\n {strings['detail.edit']}\n </button>\n )}\n </div>\n )}\n {buildPermalink && (\n <div class=\"report-detail-permalink\">\n <button\n type=\"button\"\n class=\"btn btn--ghost report-detail-copy-link\"\n onClick={handleCopyLink}\n >\n {copied ? strings['detail.copied'] : strings['detail.copy_link']}\n </button>\n </div>\n )}\n {(detail.screenshots?.length\n ? detail.screenshots.map((s) => s.url)\n : [detail.screenshot_url]\n )\n // Screenshots are immutable and in fixed submission order, so the\n // index is a stable identity to pin the presigned URL on.\n .map((url, i) => ({ url: pinUrl(`shot:${i}`, url ?? null), pinKey: `shot:${i}` }))\n .filter((s): s is { url: string; pinKey: string } => Boolean(s.url))\n .map(({ url, pinKey }) => {\n const safeHref = safeExternalHref(url)\n // No clickable open-in-new-tab for non-http(s) screenshot URLs\n // (defense-in-depth — server returns presigned https URLs, but\n // we don't trust client-rendered content with the operator's\n // origin). The inline preview is `<img src>`, which is safe\n // even if the URL is weird because Preact escapes attrs and\n // the browser refuses to render non-image content.\n return safeHref ? (\n <a\n class=\"report-detail-screenshot\"\n href={safeHref}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <img src={url} alt=\"\" loading=\"lazy\" onError={() => unpinUrl(pinKey)} />\n </a>\n ) : (\n <div class=\"report-detail-screenshot\">\n <img src={url} alt=\"\" loading=\"lazy\" onError={() => unpinUrl(pinKey)} />\n </div>\n )\n })}\n\n {detail.verification && (\n <VerificationPanel\n verification={detail.verification}\n strings={strings}\n pinUrl={pinUrl}\n unpinUrl={unpinUrl}\n />\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\n comment={c}\n strings={strings}\n {...(onOpenSeq && { onOpenSeq })}\n stableUrl={(key, url) => pinUrl(key, url) ?? url}\n />\n </li>\n ))}\n </ul>\n )}\n\n {detail.status_history && detail.status_history.length > 0 && (\n <StatusHistorySection rows={detail.status_history} strings={strings} />\n )}\n\n {detail.technical_context && (\n <TechnicalContextDrawer ctx={detail.technical_context} strings={strings} />\n )}\n\n {showComposeBox ? (\n <div class=\"report-compose\">\n <textarea\n value={composeBody}\n placeholder={strings['detail.compose_placeholder']}\n onInput={(e) =>\n setComposeBody((e.target as HTMLTextAreaElement).value)\n }\n disabled={sending}\n />\n {composeShots.length > 0 && (\n <div class=\"compose-attachments\">\n {composeShots.map((s, i) => (\n <div class=\"compose-attachment\" key={s.preview}>\n <img src={s.preview} alt={strings['detail.attachment_alt']} />\n <button\n type=\"button\"\n class=\"compose-attachment-remove\"\n aria-label={strings['detail.attach_remove']}\n onClick={() => removeComposeShot(i)}\n disabled={sending}\n >\n ×\n </button>\n </div>\n ))}\n </div>\n )}\n {attachError && (\n <p class=\"compose-attach-error\" role=\"alert\">\n {attachError}\n </p>\n )}\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\"image/png,image/jpeg,image/webp\"\n multiple\n class=\"compose-attach-input\"\n onChange={handleAttachChange}\n hidden\n />\n <div class=\"report-compose-actions\">\n <button\n type=\"button\"\n class=\"btn btn--ghost compose-attach-btn\"\n onClick={() => fileInputRef.current?.click()}\n disabled={sending || composeShots.length >= MAX_SCREENSHOTS}\n >\n {strings['detail.attach_add']}\n </button>\n {showCloseCta && (\n <button\n type=\"button\"\n class=\"btn\"\n onClick={handleClose}\n disabled={closing || reopening}\n >\n {closing\n ? strings['detail.close_busy']\n : strings['detail.close_cta']}\n </button>\n )}\n {showCloseCta && (\n <button\n type=\"button\"\n class=\"btn btn--ghost\"\n onClick={handleReopen}\n disabled={closing || reopening}\n >\n {reopening\n ? strings['detail.reopen_busy']\n : strings['detail.reopen_cta']}\n </button>\n )}\n <button\n type=\"button\"\n class=\"btn btn--primary\"\n onClick={handleSend}\n disabled={\n (!composeBody.trim() && composeShots.length === 0) || sending\n }\n >\n {sending\n ? strings['detail.compose_sending']\n : strings['detail.compose_send']}\n </button>\n </div>\n </div>\n ) : (\n // Teammate is reading a project-wide row. Replies are private\n // to the submitter, so we hide the compose box entirely (action\n // is genuinely unavailable) and surface a short notice so the\n // viewer understands why.\n <p class=\"report-detail-teammate-notice\" role=\"note\">\n {strings['detail.teammate_notice']}\n </p>\n )}\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 const safeHref = safeExternalHref(detail.page_url)\n return (\n <div class=\"report-detail-context-line\" title={detail.page_url}>\n <span class=\"report-detail-context-label\">{strings['detail.context.page']}</span>\n {safeHref ? (\n <a\n class=\"report-detail-context-url\"\n href={safeHref}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n {truncateUrl(detail.page_url, 64)}\n </a>\n ) : (\n <span class=\"report-detail-context-url\">{truncateUrl(detail.page_url, 64)}</span>\n )}\n </div>\n )\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\ninterface TechnicalContextDrawerProps {\n ctx: CapturedContext\n strings: Record<StringKey, string>\n}\n\nconst TECH_CONSOLE_LIMIT = 20\nconst TECH_NETWORK_LIMIT = 15\n\n/**\n * The \"transparency drawer\" — collapsible panel showing the user the\n * exact diagnostic data their browser submitted with the report (console\n * tail, network tail, JS errors, device summary). Closed by default so\n * the regular conversation flow stays uncluttered; one click reveals\n * everything. Borrowed from thePnr's customer-facing `TechnicalContextBlock`.\n */\nfunction TechnicalContextDrawer({ ctx, strings }: TechnicalContextDrawerProps) {\n const errors = ctx.errors ?? []\n const consoleLogs = (ctx.consoleLogs ?? []).slice(-TECH_CONSOLE_LIMIT)\n const network = (ctx.networkRequests ?? []).slice(-TECH_NETWORK_LIMIT)\n const device = ctx.device\n const hasAny =\n !!device || errors.length > 0 || consoleLogs.length > 0 || network.length > 0\n if (!hasAny) return null\n const errorCount = errors.length\n const summary =\n errorCount > 0\n ? `${strings['detail.tech.title']} · ${errorCount} ${\n errorCount === 1 ? strings['detail.tech.errors_one'] : strings['detail.tech.errors_many']\n }`\n : strings['detail.tech.title']\n return (\n <details class=\"report-detail-tech\">\n <summary>{summary}</summary>\n <div class=\"tech-body\">\n {device && <DeviceSection device={device} strings={strings} />}\n {errors.length > 0 && <ErrorsSection errors={errors} strings={strings} />}\n {consoleLogs.length > 0 && <ConsoleSection logs={consoleLogs} strings={strings} />}\n {network.length > 0 && <NetworkSection rows={network} strings={strings} />}\n </div>\n </details>\n )\n}\n\ninterface VerificationPanelProps {\n verification: WidgetVerification\n strings: Record<StringKey, string>\n pinUrl: (key: string, url: string | null) => string | null\n unpinUrl: (key: string) => void\n}\n\n/**\n * \"Preuve de correction\" drawer — surfaces an agent's verification pass\n * (Phase B) so the submitter can see the evidence behind the \"Vérifié par\n * l'agent\" badge without digging through comments. Closed by default,\n * same collapsible convention as TechnicalContextDrawer above. Renders\n * nothing when `detail.verification` is null/absent (older backends).\n */\nfunction VerificationPanel({\n verification,\n strings,\n pinUrl,\n unpinUrl,\n}: VerificationPanelProps) {\n return (\n <details class=\"report-detail-verification\">\n <summary>{strings['detail.verified.title']}</summary>\n <div class=\"verification-body\">\n <p class=\"verification-evidence\">{verification.evidence}</p>\n <div class=\"verification-meta\">\n <span>\n {verification.functional_ok ? '✓' : '✗'} {strings['detail.verified.functional']}\n </span>\n <span>\n {verification.ui_ok ? '✓' : '✗'} {strings['detail.verified.ui']}\n </span>\n <span>\n {verification.environment}\n {verification.app_version ? ` · ${verification.app_version}` : ''}\n </span>\n </div>\n {verification.screenshots.length > 0 && (\n <div class=\"verification-shots\">\n {verification.screenshots.map((s, i) => {\n // Id-less key is intentional: the submitter-safe payload carries no\n // verification id, and urlPins is fresh per mount so latest-wins swaps are a non-issue.\n const pinKey = `verif:${i}`\n const url = pinUrl(pinKey, s.url)\n if (!url) return null\n const safeHref = safeExternalHref(url)\n return safeHref ? (\n <a\n key={i}\n class=\"report-detail-screenshot\"\n href={safeHref}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <img\n src={url}\n alt={s.caption ?? ''}\n loading=\"lazy\"\n onError={() => unpinUrl(pinKey)}\n />\n </a>\n ) : (\n <div key={i} class=\"report-detail-screenshot\">\n <img\n src={url}\n alt={s.caption ?? ''}\n loading=\"lazy\"\n onError={() => unpinUrl(pinKey)}\n />\n </div>\n )\n })}\n </div>\n )}\n </div>\n </details>\n )\n}\n\nfunction DeviceSection({\n device,\n strings,\n}: {\n device: CapturedContext['device']\n strings: Record<StringKey, string>\n}) {\n const parts: Array<{ label: string; value: string }> = []\n if (device?.viewport) {\n const dpr = device.viewport.dpr ? ` @${device.viewport.dpr}x` : ''\n parts.push({\n label: strings['detail.tech.device.viewport'],\n value: `${device.viewport.w}×${device.viewport.h}${dpr}`,\n })\n }\n if (device?.platform) parts.push({ label: strings['detail.tech.device.platform'], value: device.platform })\n if (device?.language) parts.push({ label: strings['detail.tech.device.language'], value: device.language })\n if (device?.timezone) parts.push({ label: strings['detail.tech.device.timezone'], value: device.timezone })\n if (device?.connection) parts.push({ label: strings['detail.tech.device.connection'], value: device.connection })\n if (parts.length === 0) return null\n return (\n <div class=\"tech-section\">\n <h4>{strings['detail.tech.device']}</h4>\n <dl class=\"tech-device\">\n {parts.map((p) => (\n <>\n <dt>{p.label}</dt>\n <dd>{p.value}</dd>\n </>\n ))}\n </dl>\n </div>\n )\n}\n\nfunction ErrorsSection({\n errors,\n strings,\n}: {\n errors: ErrorEntry[]\n strings: Record<StringKey, string>\n}) {\n return (\n <div class=\"tech-section\">\n <h4>{strings['detail.tech.errors']}</h4>\n <ul class=\"tech-errors\">\n {errors.map((e) => (\n <li>\n <div class=\"tech-errors-msg\">{e.message}</div>\n {e.stack && <pre class=\"tech-errors-stack\">{truncateStack(e.stack)}</pre>}\n </li>\n ))}\n </ul>\n </div>\n )\n}\n\nfunction ConsoleSection({\n logs,\n strings,\n}: {\n logs: ConsoleEntry[]\n strings: Record<StringKey, string>\n}) {\n return (\n <div class=\"tech-section\">\n <h4>{strings['detail.tech.console']}</h4>\n <ul class=\"tech-console\">\n {logs.map((l) => (\n <li class={`tech-console-row tech-console-row--${l.level}`}>\n <span class=\"tech-console-level\">{l.level}</span>\n <span class=\"tech-console-msg\">{l.message}</span>\n </li>\n ))}\n </ul>\n </div>\n )\n}\n\nfunction NetworkSection({\n rows,\n strings,\n}: {\n rows: NetworkEntry[]\n strings: Record<StringKey, string>\n}) {\n return (\n <div class=\"tech-section\">\n <h4>{strings['detail.tech.network']}</h4>\n <ul class=\"tech-network\">\n {rows.map((n) => {\n const failed = n.status >= 400 || n.status === 0\n return (\n <li class={`tech-network-row${failed ? ' tech-network-row--fail' : ''}`}>\n <span class=\"tech-network-status\">{n.status || '—'}</span>\n <span class=\"tech-network-method\">{n.method}</span>\n <span class=\"tech-network-url\" title={n.url}>\n {truncateUrl(n.url, 56)}\n </span>\n <span class=\"tech-network-time\">{Math.round(n.durationMs)}ms</span>\n </li>\n )\n })}\n </ul>\n </div>\n )\n}\n\nfunction truncateStack(stack: string): string {\n // Clip to first 12 frames so the drawer doesn't blow out vertically on\n // a deep React stack — the user can always click through to a full view\n // if we add one later.\n const lines = stack.split('\\n')\n if (lines.length <= 12) return stack\n return lines.slice(0, 12).join('\\n') + '\\n …'\n}\n\ninterface StatusHistorySectionProps {\n rows: WidgetStatusChangeRow[]\n strings: Record<StringKey, string>\n}\n\n/**\n * The full audit trail of who changed the report's status, when, and via\n * which surface (operator UI, MCP agent, or system). Read-only — purely\n * a transparency / \"you're being heard by real humans\" gesture borrowed\n * from thePnr's customer-facing detail view.\n */\nfunction StatusHistorySection({ rows, strings }: StatusHistorySectionProps) {\n return (\n <div class=\"report-detail-history\">\n <h3 class=\"report-detail-section\">{strings['detail.history']}</h3>\n <ol class=\"status-history\">\n {rows.map((r) => {\n const from = strings[`status.${r.from_status}` as StringKey] ?? r.from_status\n const to = strings[`status.${r.to_status}` as StringKey] ?? r.to_status\n const sourceKey: StringKey =\n r.changed_by_source === 'mcp'\n ? 'detail.author.mcp'\n : r.changed_by_source === 'system'\n ? 'detail.author.system'\n : 'detail.author.staff'\n return (\n <li class=\"status-history-row\">\n <span class=\"status-history-time\">{formatSubmittedAt(r.created_at)}</span>\n <span class=\"status-history-transition\">\n <span class={`pill pill-status pill-status--${r.from_status}`}>{from}</span>\n <span class=\"status-history-arrow\" aria-hidden=\"true\">→</span>\n <span class={`pill pill-status pill-status--${r.to_status}`}>{to}</span>\n </span>\n <span class={`status-history-source status-history-source--${r.changed_by_source}`}>\n {strings[sourceKey]}\n </span>\n </li>\n )\n })}\n </ol>\n </div>\n )\n}\n","import { h, type VNode } from 'preact'\n\nconst SEQ_RE = /#(\\d+)/g\n\n/**\n * Split text on \"#NN\" report references and render each as a clickable link\n * that opens that report (#85 — \"voir #81\" used to be dead text). Returns the\n * original text as a single item when there's nothing to linkify or no\n * handler is supplied (so callers can render the result directly).\n */\nexport function linkifySeq(\n text: string | null | undefined,\n onOpenSeq?: (seq: number) => void,\n): (string | VNode)[] {\n const value = text ?? ''\n if (!value || !onOpenSeq) return [value]\n const parts: (string | VNode)[] = []\n let last = 0\n SEQ_RE.lastIndex = 0\n let m: RegExpExecArray | null\n while ((m = SEQ_RE.exec(value)) !== null) {\n if (m.index > last) parts.push(value.slice(last, m.index))\n const seq = Number(m[1])\n parts.push(\n <button\n type=\"button\"\n class=\"seq-link\"\n onClick={() => onOpenSeq(seq)}\n aria-label={`Open report #${seq}`}\n >\n #{seq}\n </button>,\n )\n last = m.index + m[0].length\n }\n if (last < value.length) parts.push(value.slice(last))\n return parts.length ? parts : [value]\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'\nimport { linkifySeq } from './linkifySeq'\n\ninterface CommentBubbleProps {\n comment: WidgetCommentRow\n strings: Record<StringKey, string>\n /** Make \"#NN\" references in the comment body clickable (#85). */\n onOpenSeq?: (seq: number) => void\n /** Presigned attachment URLs change on every detail poll; the parent can\n * pin the first URL seen per attachment id so `src` stays stable and the\n * browser doesn't re-download every image every 30s. */\n stableUrl?: (key: string, url: string) => string\n}\n\nexport function CommentBubble({ comment, strings, onOpenSeq, stableUrl }: 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 // Only attachments the store could presign are renderable (#91).\n const images = (comment.attachments ?? []).filter((a) => a.url)\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 {comment.body && (\n <div class=\"comment-body\">{linkifySeq(comment.body, onOpenSeq)}</div>\n )}\n {images.length > 0 && (\n <div class=\"comment-attachments\">\n {images.map((a) => {\n const src = stableUrl ? stableUrl(`att:${a.id}`, a.url!) : a.url!\n return (\n <a\n key={a.id}\n class=\"comment-attachment\"\n href={src}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <img src={src} alt={strings['detail.attachment_alt']} loading=\"lazy\" />\n </a>\n )\n })}\n </div>\n )}\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","/**\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\n/** Per-report cap — mirrors the backend SCREENSHOT_MAX_COUNT default. */\nexport const MAX_SCREENSHOTS = 5\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/**\n * Return `url` only if its scheme is in the safe-for-href allowlist —\n * `https:` always, `http:` for localhost only. Otherwise return `undefined`,\n * which renders as a non-clickable element (and React/Preact omit the href\n * attribute entirely). This stops a malicious widget user from planting\n * `javascript:`, `data:`, `vbscript:`, or `blob:` URLs in their report's\n * `page_url` / `screenshot_url` and getting an operator's click to execute\n * arbitrary code in the widget host's origin.\n */\nexport function safeExternalHref(url: string | null | undefined): string | undefined {\n if (!url) return undefined\n let parsed: URL\n try {\n parsed = new URL(url)\n } catch {\n return undefined\n }\n if (parsed.protocol === 'https:') return parsed.toString()\n if (\n parsed.protocol === 'http:' &&\n (parsed.hostname === 'localhost' ||\n parsed.hostname === '127.0.0.1' ||\n parsed.hostname === '[::1]')\n ) {\n return parsed.toString()\n }\n return undefined\n}\n\n","/**\n * ChangelogList — the \"This week\" tab.\n *\n * Renders the user's resolved reports grouped by ISO week of resolution\n * date, most recent week first. Trust-building surface borrowed from\n * thePnr's `FeedbackChangelogView` (\"see what's been fixed\"). Polls every\n * 30 s while open, same cadence as MineList.\n *\n * Scope is the submitter's own resolved reports only — multi-tenant\n * privacy default. A per-project \"show all-project changelog to\n * customers\" setting can expand the scope in a later release.\n */\n\nimport { h } from 'preact'\nimport { useEffect, useMemo, useRef, useState } from 'preact/hooks'\n\nimport type { ApiClient } from '../api/client'\nimport type { WidgetChangelogRow } from '../types'\nimport type { StringKey } from './i18n'\nimport { startPoll } from './poll'\nimport { ReportRow } from './ReportRow'\n\ninterface ChangelogListProps {\n api: ApiClient\n externalId: string\n strings: Record<StringKey, string>\n onSelect: (report: WidgetChangelogRow) => void\n}\n\nconst POLL_MS = 30_000\n\ninterface WeekGroup {\n /** ISO date (YYYY-MM-DD) of the Monday anchoring this week. */\n weekKey: string\n /** Localized \"Week of {Mon DD, YYYY}\" label, computed once per group. */\n label: string\n rows: WidgetChangelogRow[]\n}\n\n/**\n * Week-of-resolution key for a row. Anchored on Monday in UTC so the\n * grouping is consistent across timezones — the user-visible label is\n * localized at render time.\n */\nexport function isoWeekKey(iso: string): string {\n const d = new Date(iso)\n if (Number.isNaN(d.getTime())) return ''\n // JS Sunday=0; we want Monday=0 to anchor weeks the way thePnr does.\n const day = (d.getUTCDay() + 6) % 7\n const monday = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() - day))\n return monday.toISOString().slice(0, 10)\n}\n\nexport function groupRowsByWeek(rows: WidgetChangelogRow[]): WeekGroup[] {\n const buckets = new Map<string, WidgetChangelogRow[]>()\n for (const row of rows) {\n const key = isoWeekKey(row.resolved_at)\n if (!key) continue\n const existing = buckets.get(key)\n if (existing) existing.push(row)\n else buckets.set(key, [row])\n }\n // Sort buckets by week descending (most recent week first); within each\n // bucket the server already returned -resolved_at order.\n return Array.from(buckets.entries())\n .sort(([a], [b]) => (a < b ? 1 : -1))\n .map(([weekKey, weekRows]) => ({\n weekKey,\n label: formatWeekLabel(weekKey),\n rows: weekRows,\n }))\n}\n\nfunction formatWeekLabel(weekKey: string): string {\n // Render just the date portion (\"May 5, 2026\"); the i18n template\n // wraps it as \"Week of {date}\".\n try {\n return new Date(weekKey).toLocaleDateString(undefined, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n })\n } catch {\n return weekKey\n }\n}\n\nexport function ChangelogList({ api, externalId, strings, onSelect }: ChangelogListProps) {\n const [rows, setRows] = useState<WidgetChangelogRow[] | 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.listChangelog(externalId)\n if (!mountedRef.current) return true\n setRows(next)\n return true\n } catch (err) {\n // Friendly UI fallback; raw error to console for the developer.\n if (typeof console !== 'undefined')\n console.warn('[mhosaic] listChangelog:', err)\n if (!mountedRef.current) return false\n setError(strings['mine.error'])\n return false\n } finally {\n if (mountedRef.current) setRefreshing(false)\n }\n }\n\n useEffect(() => {\n mountedRef.current = true\n const poll = startPoll(fetchRows, POLL_MS)\n return () => {\n mountedRef.current = false\n poll.stop()\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [externalId])\n\n const groups = useMemo(() => (rows ? groupRowsByWeek(rows) : []), [rows])\n const isLoading = rows === null && !error\n const isEmpty = rows !== null && rows.length === 0\n\n return (\n <div class=\"mine-list\">\n <div class=\"mine-list-header\">\n <h2>{strings['tab.changelog']}</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['changelog.empty.title']}</strong>\n <p>{strings['changelog.empty.body']}</p>\n </div>\n )}\n {groups.length > 0 && (\n <div class=\"changelog-groups\">\n {groups.map((g) => (\n <section class=\"changelog-group\" key={g.weekKey}>\n <header class=\"changelog-group-header\">\n <span class=\"changelog-group-marker\" aria-hidden=\"true\">●</span>\n <span class=\"changelog-group-label\">\n {strings['changelog.week_of'].replace('{date}', g.label)}\n </span>\n <span class=\"changelog-group-rule\" aria-hidden=\"true\" />\n <span class=\"changelog-group-count\">\n {strings[\n g.rows.length === 1 ? 'changelog.resolved_one' : 'changelog.resolved_many'\n ].replace('{count}', String(g.rows.length))}\n </span>\n </header>\n <ul class=\"mine-rows\">\n {g.rows.map((row) => (\n <li>\n <ReportRow\n row={row}\n strings={strings}\n onClick={() => onSelect(row)}\n extraMeta={row.pr_number ? `PR #${row.pr_number}` : undefined}\n />\n </li>\n ))}\n </ul>\n </section>\n ))}\n </div>\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 /** Extra trailing meta chip (e.g. the changelog's merged-PR ref). */\n extraMeta?: string | undefined\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, extraMeta }: 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 {extraMeta && <span>· {extraMeta}</span>}\n </div>\n </button>\n )\n}\n","import { h } from 'preact'\n\ninterface FabProps {\n label: string\n onClick: () => void\n /** Open reports on the current page. 0/undefined → no badge. Display caps at 9+. */\n count?: number\n}\n\n/**\n * Bug glyph from lucide.dev (`Bug` icon), inlined as a single-color SVG.\n * Sized at 20×20 inside the 48×48 FAB so the icon reads as a confident\n * accent rather than dominating the circle (vs the prior 24/56 ratio,\n * which felt heavy). Picks up `currentColor` from `--mfb-fab-icon`\n * set on the .fab class.\n */\nfunction BugIcon() {\n return (\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <path d=\"m8 2 1.88 1.88\" />\n <path d=\"M14.12 3.88 16 2\" />\n <path d=\"M9 7.13v-1a3.003 3.003 0 1 1 6 0v1\" />\n <path d=\"M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6\" />\n <path d=\"M12 20v-9\" />\n <path d=\"M6.53 9C4.6 8.8 3 7.1 3 5\" />\n <path d=\"M6 13H2\" />\n <path d=\"M3 21c0-2.1 1.7-3.9 3.8-4\" />\n <path d=\"M20.97 5c0 2.1-1.6 3.8-3.5 4\" />\n <path d=\"M22 13h-4\" />\n <path d=\"M17.2 17c2.1.1 3.8 1.9 3.8 4\" />\n </svg>\n )\n}\n\nexport function Fab({ label, onClick, count }: FabProps) {\n return (\n <button type=\"button\" class=\"fab\" aria-label={label} title={label} onClick={onClick}>\n <BugIcon />\n {count !== undefined && count > 0 && (\n <span class=\"fab-badge\" aria-hidden=\"true\">\n {count > 9 ? '9+' : String(count)}\n </span>\n )}\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 { MAX_SCREENSHOTS, 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 /** Optional user-attached screenshots — via upload, paste, drag-drop, or\n * the annotator, in attach order (max MAX_SCREENSHOTS). v0.12 dropped\n * html2canvas-on-submit; v0.7.1 dropped the getDisplayMedia \"Capture this\n * page\" path (asked the operator for screen share permission on every\n * click, too high a friction for the value). */\n screenshots?: Blob[]\n /** Always \"manual\" when screenshots are attached — kept as a discriminated\n * field so the backend schema (which still accepts the legacy values) can\n * evolve independently. Undefined when no screenshot is attached. */\n capture_method?: 'manual'\n}\n\ninterface AttachedShot {\n blob: Blob\n preview: string\n}\n\ninterface FormProps {\n strings: Record<StringKey, string>\n onSubmit: (values: FormValues) => void\n onCancel: () => void\n status: FormStatus\n errorMessage?: string\n /** Sequence number of the just-created report, surfaced in the success\n * acknowledgement (\"report #42 sent\") so the submitter knows it\n * persisted (#82). Absent → the generic \"sent\" copy is shown. */\n submittedSeq?: number\n /** Reports whether the form holds unsaved input (typed text or attached\n * screenshots). The mount uses it to guard an accidental modal close (#89). */\n onDirtyChange?: (dirty: boolean) => void\n}\n\nconst TYPES: FeedbackType[] = ['bug', 'feature', 'question', 'praise', 'typo']\nconst SEVERITIES: FeedbackSeverity[] = ['blocker', 'high', 'medium', 'low']\n\nexport function Form({\n strings,\n onSubmit,\n onCancel,\n status,\n errorMessage,\n submittedSeq,\n onDirtyChange,\n}: 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 [shots, setShots] = useState<AttachedShot[]>([])\n const [isDragOver, setIsDragOver] = useState(false)\n const [annotatingIndex, setAnnotatingIndex] = useState<number | null>(null)\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 // Report unsaved-input state up so the mount can guard an accidental close\n // (backdrop/Esc) with a discard confirmation (#89).\n const isDirty = description.trim() !== '' || shots.length > 0\n useEffect(() => {\n onDirtyChange?.(isDirty)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isDirty])\n\n // Revoke every object URL when the form unmounts; individual revokes\n // happen on remove/replace.\n const shotsRef = useRef(shots)\n shotsRef.current = shots\n useEffect(() => {\n return () => {\n shotsRef.current.forEach((s) => URL.revokeObjectURL(s.preview))\n }\n }, [])\n\n const acceptFiles = (files: Array<File | Blob>) => {\n setLocalError('')\n const remaining = MAX_SCREENSHOTS - shots.length\n const batch = files.slice(0, Math.max(0, remaining))\n for (const file of batch) {\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 }\n if (batch.length) {\n const incoming = batch.map((blob) => ({ blob, preview: URL.createObjectURL(blob) }))\n setShots((prev) => [...prev, ...incoming])\n }\n if (files.length > batch.length) {\n setLocalError(\n strings['form.screenshot.error_count'].replace('{max}', String(MAX_SCREENSHOTS)),\n )\n }\n }\n\n const removeShot = (index: number) => {\n const shot = shots[index]\n if (shot) URL.revokeObjectURL(shot.preview)\n setShots((prev) => prev.filter((_, i) => i !== index))\n setLocalError('')\n if (fileInputRef.current) fileInputRef.current.value = ''\n }\n\n const handleFileInputChange = (e: Event) => {\n const input = e.target as HTMLInputElement\n const files = Array.from(input.files ?? [])\n if (files.length) acceptFiles(files)\n // Reset so picking the same file again re-triggers `change`.\n input.value = ''\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 files = Array.from(e.dataTransfer?.files ?? [])\n if (files.length) acceptFiles(files)\n }\n\n // Clipboard paste, scoped to the form — an image paste attaches wherever\n // focus is inside the widget (users type a description, then Ctrl+V), while\n // paste in the host page stays untouched. Text-only paste falls through to\n // the default (image items are the only thing consumed).\n const handlePaste = (e: ClipboardEvent) => {\n const items = e.clipboardData?.items\n if (!items) return\n const files: File[] = []\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) files.push(file)\n }\n }\n if (!files.length) return\n e.preventDefault()\n acceptFiles(files)\n }\n\n const handleAnnotated = (annotated: Blob) => {\n const index = annotatingIndex\n if (index === null || !shots[index]) {\n setAnnotatingIndex(null)\n return\n }\n URL.revokeObjectURL(shots[index].preview)\n const replacement = { blob: annotated, preview: URL.createObjectURL(annotated) }\n setShots((prev) => prev.map((s, i) => (i === index ? replacement : s)))\n setAnnotatingIndex(null)\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 (shots.length) {\n values.screenshots = shots.map((s) => s.blob)\n values.capture_method = 'manual'\n }\n onSubmit(values)\n }\n\n return (\n <form onSubmit={handleSubmit} onPaste={handlePaste}>\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) => (\n <option value={t}>{strings[`type.${t}` as StringKey]}</option>\n ))}\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) => (\n <option value={s}>{strings[`severity.${s}` as StringKey]}</option>\n ))}\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 multiple\n class=\"mfb-sr-only\"\n onChange={handleFileInputChange}\n aria-hidden=\"true\"\n tabIndex={-1}\n />\n {shots.length > 0 && (\n <div class=\"screenshot-strip\">\n {shots.map((shot, index) => (\n <div class=\"screenshot-preview\" key={shot.preview}>\n <img src={shot.preview} alt=\"\" />\n <div class=\"screenshot-preview-actions\">\n <button\n type=\"button\"\n class=\"btn btn--primary screenshot-annotate\"\n onClick={() => setAnnotatingIndex(index)}\n >\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\" />\n <path d=\"M18.5 2.5a2.12 2.12 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\" />\n </svg>\n {strings['form.screenshot.annotate']}\n </button>\n <button type=\"button\" class=\"btn\" onClick={() => removeShot(index)}>\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6h14z\" />\n </svg>\n {strings['form.screenshot.remove']}\n </button>\n </div>\n </div>\n ))}\n </div>\n )}\n {shots.length < MAX_SCREENSHOTS && (\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 <svg\n class=\"screenshot-icon\"\n width=\"28\"\n height=\"28\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.6\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\" />\n <polyline points=\"17 8 12 3 7 8\" />\n <line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"15\" />\n </svg>\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 {/* REQ-A7: be transparent that technical context is captured. */}\n <div class=\"capture-notice\">{strings['form.capture.notice']}</div>\n\n {localError && <div class=\"error\">{localError}</div>}\n {status === 'error' && errorMessage && <div class=\"error\">{errorMessage}</div>}\n {status === 'success' && (\n <div class=\"success\">\n {submittedSeq !== undefined\n ? strings['form.success.seq'].replace('{seq}', String(submittedSeq))\n : strings['form.success']}\n </div>\n )}\n\n <div class=\"actions\">\n <button type=\"button\" class=\"btn\" onClick={onCancel} disabled={submitting}>\n {strings['form.cancel']}\n </button>\n <button type=\"submit\" class=\"btn btn--primary\" disabled={submitting}>\n {submitLabel}\n </button>\n </div>\n\n {annotatingIndex !== null && shots[annotatingIndex] && (\n <Annotator\n imageBlob={shots[annotatingIndex].blob}\n strings={strings}\n onCancel={() => setAnnotatingIndex(null)}\n onSave={handleAnnotated}\n />\n )}\n </form>\n )\n}\n","/**\n * Annotator — screenshot annotation modal.\n *\n * v0.6.0: rectangle, arrow, freehand, text. Custom canvas, no external lib.\n * v0.10.0: + highlight (semi-transparent fill), blur (pixelate sensitive\n * regions), full undo/redo stack, native color picker alongside swatches.\n *\n * Save rasterizes the original image + every shape onto a fresh canvas\n * and returns a PNG Blob. Blurs are always sampled from the source image\n * pixels (not the canvas), so strokes/text/highlights drawn near a blurred\n * region don't get re-blurred by accident.\n */\n\nimport { h } from 'preact'\nimport { useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks'\n\nimport type { StringKey } from './i18n'\n\ntype Tool = 'rectangle' | 'arrow' | 'freehand' | 'text' | 'highlight' | 'blur'\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\ninterface HighlightShape extends BaseShape {\n kind: 'highlight'\n x: number\n y: number\n w: number\n h: number\n}\n\ninterface BlurShape extends BaseShape {\n kind: 'blur'\n x: number\n y: number\n w: number\n h: number\n /** Tile size in source-image pixels. Scales with canvas width. */\n tile: number\n}\n\ntype Shape =\n | RectShape\n | ArrowShape\n | FreehandShape\n | TextShape\n | HighlightShape\n | BlurShape\n\nconst COLORS: string[] = ['#ef4444', '#f59e0b', '#10b981', '#3b82f6', '#ffffff']\nconst HIGHLIGHT_ALPHA = 0.35\n\n// ---------------------------------------------------------------------------\n// Drawing helpers\n// ---------------------------------------------------------------------------\n\nfunction drawShape(\n ctx: CanvasRenderingContext2D,\n shape: Shape,\n sourceImage: HTMLImageElement | null,\n) {\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 } else if (shape.kind === 'highlight') {\n // Semi-transparent filled rect. Multiply-ish blend so the underlying\n // pixels show through — typical marker-pen look.\n ctx.globalAlpha = HIGHLIGHT_ALPHA\n ctx.fillRect(\n normalizeStart(shape.x, shape.w),\n normalizeStart(shape.y, shape.h),\n Math.abs(shape.w),\n Math.abs(shape.h),\n )\n } else if (shape.kind === 'blur') {\n drawBlur(ctx, shape, sourceImage)\n }\n ctx.restore()\n}\n\nfunction normalizeStart(start: number, size: number): number {\n return size < 0 ? start + size : start\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 * Pixelate a rectangular region by averaging tiles of the source image.\n * Always samples from the original `<img>` so blurs over text/strokes\n * still hide the underlying pixels, not the marks drawn on top.\n */\nfunction drawBlur(\n ctx: CanvasRenderingContext2D,\n shape: BlurShape,\n sourceImage: HTMLImageElement | null,\n) {\n if (!sourceImage) return\n const x = normalizeStart(shape.x, shape.w)\n const y = normalizeStart(shape.y, shape.h)\n const w = Math.abs(shape.w)\n const h = Math.abs(shape.h)\n if (w < 2 || h < 2) return\n const tile = Math.max(4, shape.tile)\n // Drop tiles aligned to the source image — pixelation is at native res\n // so the result is crisp regardless of CSS scale on the display canvas.\n for (let yy = y; yy < y + h; yy += tile) {\n for (let xx = x; xx < x + w; xx += tile) {\n const tw = Math.min(tile, x + w - xx)\n const th = Math.min(tile, y + h - yy)\n ctx.drawImage(\n sourceImage,\n xx,\n yy,\n tw,\n th, // source rect from the image\n xx,\n yy,\n tw,\n th, // dest rect on the canvas (same coords)\n )\n }\n }\n // Sampling a single tile-sized chunk and re-painting it at tile size\n // gives the mosaic. To get the visual look, downscale + upscale via\n // imageSmoothingDisabled.\n ctx.imageSmoothingEnabled = false\n for (let yy = y; yy < y + h; yy += tile) {\n for (let xx = x; xx < x + w; xx += tile) {\n const tw = Math.min(tile, x + w - xx)\n const th = Math.min(tile, y + h - yy)\n // Sample the top-left pixel of this tile from the source and stretch.\n ctx.drawImage(\n sourceImage,\n xx,\n yy,\n 1,\n 1, // single source pixel\n xx,\n yy,\n tw,\n th, // stretched dest tile\n )\n }\n }\n ctx.imageSmoothingEnabled = true\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 highlight: (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M3 13l3-3L11 5l-2-2-5 5-3 3v2h2zM10 4l2 2\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n <path d=\"M2 14h12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" />\n </svg>\n ),\n blur: (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <rect x=\"2\" y=\"2\" width=\"4\" height=\"4\" fill=\"currentColor\" opacity=\"0.85\" />\n <rect x=\"7\" y=\"2\" width=\"3\" height=\"3\" fill=\"currentColor\" opacity=\"0.55\" />\n <rect x=\"11\" y=\"2\" width=\"3\" height=\"4\" fill=\"currentColor\" opacity=\"0.4\" />\n <rect x=\"2\" y=\"7\" width=\"3\" height=\"3\" fill=\"currentColor\" opacity=\"0.6\" />\n <rect x=\"6\" y=\"7\" width=\"3\" height=\"3\" fill=\"currentColor\" opacity=\"0.85\" />\n <rect x=\"10\" y=\"7\" width=\"4\" height=\"3\" fill=\"currentColor\" opacity=\"0.5\" />\n <rect x=\"2\" y=\"11\" width=\"4\" height=\"3\" fill=\"currentColor\" opacity=\"0.4\" />\n <rect x=\"7\" y=\"11\" width=\"3\" height=\"3\" fill=\"currentColor\" opacity=\"0.65\" />\n <rect x=\"11\" y=\"11\" width=\"3\" height=\"3\" fill=\"currentColor\" opacity=\"0.85\" />\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 redo: (\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M12 7l-3-3M12 7l-3 3M12 7H6a3 3 0 0 0 0 6h2\" 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 [past, setPast] = useState<Shape[][]>([])\n const [future, setFuture] = 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 function addShape(shape: Shape) {\n setShapes((s) => {\n // Use the functional setState so we read the most recent shape list\n // even when this fires from a stale-closure event handler. The past\n // stack captures `s` here (the truly current state).\n setPast((p) => [...p, s])\n return [...s, shape]\n })\n setFuture([])\n }\n\n function clearShapes() {\n setShapes((s) => {\n setPast((p) => [...p, s])\n return []\n })\n setFuture([])\n }\n\n function undo() {\n setPast((p) => {\n if (p.length === 0) return p\n const prev = p[p.length - 1]!\n setShapes((s) => {\n setFuture((f) => [s, ...f])\n return prev\n })\n return p.slice(0, -1)\n })\n }\n\n function redo() {\n setFuture((f) => {\n if (f.length === 0) return f\n const next = f[0]!\n setShapes((s) => {\n setPast((p) => [...p, s])\n return next\n })\n return f.slice(1)\n })\n }\n\n // Esc handler uses useLayoutEffect (not useEffect) so the listener is\n // attached SYNCHRONOUSLY after DOM commit, BEFORE paint. Tests (and\n // fast human users) that press Escape the moment the annotator becomes\n // visible would otherwise hit a brief window where the listener isn't\n // yet attached. The parent Modal's keydown handler bails when it sees\n // .annotator-backdrop in the shadow root, so we don't double-fire.\n useLayoutEffect(() => {\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 // Cmd/Ctrl-Z = undo, Cmd/Ctrl-Shift-Z (or Cmd/Ctrl-Y) = redo. Re-binds\n // on each state mutation to capture fresh shapes/past/future closures.\n useEffect(() => {\n const onKey = (e: KeyboardEvent) => {\n const mod = e.metaKey || e.ctrlKey\n if (!mod) return\n const k = e.key.toLowerCase()\n if (k === 'z') {\n e.preventDefault()\n e.stopPropagation()\n if (e.shiftKey) redo()\n else undo()\n } else if (k === 'y') {\n e.preventDefault()\n e.stopPropagation()\n redo()\n }\n }\n window.addEventListener('keydown', onKey)\n return () => window.removeEventListener('keydown', onKey)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [past, future, shapes])\n\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 // useLayoutEffect (not useEffect) so the canvas's width/height/style\n // are set SYNCHRONOUSLY after Preact's render commit and BEFORE the\n // browser's paint. Without this, the canvas first mounts at the HTML\n // default 300×150 and only resizes to the image's dimensions on the\n // next microtask — a window during which Playwright's\n // `canvas.boundingBox()` can read the stale layout. Tests then fire\n // mouse events at coordinates that no longer land inside the canvas,\n // mousedown is dispatched to the wrap/backdrop instead, no draftShape\n // is created, and `.annotator-count` stays at 0 → flake.\n useLayoutEffect(() => {\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.65\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 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, img)\n if (draftShape) drawShape(ctx, draftShape, img)\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 canvasW = canvasRef.current!.width\n const lineWidth = Math.max(3, Math.round(canvasW / 400))\n const blurTile = Math.max(8, Math.round(canvasW / 80))\n\n if (tool === 'text') {\n const text = window.prompt(strings['annotator.text_prompt'])\n if (text && text.trim()) {\n addShape({\n kind: 'text',\n x,\n y,\n text: text.trim(),\n color,\n fontSize: Math.max(16, Math.round(canvasW / 50)),\n lineWidth: 1,\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 } else if (tool === 'highlight') {\n setDraftShape({\n kind: 'highlight',\n x,\n y,\n w: 0,\n h: 0,\n color: color === '#ffffff' ? '#fde047' : color,\n lineWidth: 1,\n })\n } else if (tool === 'blur') {\n setDraftShape({\n kind: 'blur',\n x,\n y,\n w: 0,\n h: 0,\n color: '#000000',\n lineWidth: 0,\n tile: blurTile,\n })\n }\n }\n\n const handleMouseMove = (e: MouseEvent) => {\n if (!isDrawingRef.current) return\n const { x, y } = getCanvasCoords(e)\n // Functional setState: read the most recent draftShape rather than the\n // closure-captured value. Playwright (and fast humans) can fire\n // mousedown→mousemove faster than Preact commits the re-render, leaving\n // the closure's `draftShape` at the previous frame (often null right\n // after mousedown). audit R6 e2e regression fix.\n setDraftShape((current) => {\n if (!current) return current\n if (\n current.kind === 'rectangle' ||\n current.kind === 'highlight' ||\n current.kind === 'blur'\n ) {\n return { ...current, w: x - current.x, h: y - current.y }\n }\n if (current.kind === 'arrow') {\n return { ...current, x2: x, y2: y }\n }\n if (current.kind === 'freehand') {\n return { ...current, points: [...current.points, { x, y }] }\n }\n return current\n })\n }\n\n const handleMouseUp = () => {\n // Same closure-staleness concern as handleMouseMove: read the\n // committed draft via the functional setter rather than rely on the\n // capture from this render.\n setDraftShape((current) => {\n if (isDrawingRef.current && current) {\n const isTiny =\n ((current.kind === 'rectangle' ||\n current.kind === 'highlight' ||\n current.kind === 'blur') &&\n Math.abs(current.w) < 4 &&\n Math.abs(current.h) < 4) ||\n (current.kind === 'arrow' &&\n Math.hypot(\n current.x2 - current.x1,\n current.y2 - current.y1,\n ) < 4) ||\n (current.kind === 'freehand' && current.points.length < 3)\n if (!isTiny) {\n addShape(current)\n }\n }\n return null\n })\n isDrawingRef.current = false\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 { id: 'highlight', icon: Icon.highlight, label: strings['annotator.tool.highlight'] },\n { id: 'blur', icon: Icon.blur, label: strings['annotator.tool.blur'] },\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 <label class=\"annotator-color annotator-color-picker\" title={strings['annotator.color_picker']}>\n <input\n type=\"color\"\n value={color}\n onInput={(e) => setColor((e.target as HTMLInputElement).value)}\n aria-label={strings['annotator.color_picker']}\n />\n </label>\n </div>\n\n <span class=\"annotator-sep\" />\n\n <button\n type=\"button\"\n class=\"annotator-btn\"\n onClick={undo}\n disabled={past.length === 0}\n title={strings['annotator.undo']}\n >\n {Icon.undo}\n <span>{strings['annotator.undo']}</span>\n </button>\n <button\n type=\"button\"\n class=\"annotator-btn\"\n onClick={redo}\n disabled={future.length === 0}\n title={strings['annotator.redo']}\n >\n {Icon.redo}\n <span>{strings['annotator.redo']}</span>\n </button>\n <button\n type=\"button\"\n class=\"annotator-btn\"\n onClick={clearShapes}\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 * 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 { rateLimitMessage } from './rateLimit'\nimport type { WidgetReportRow } from '../types'\nimport type { StringKey } from './i18n'\nimport { KpiStrip, type KpiFilter, rowsMatchingFilter } from './KpiStrip'\nimport { startPoll } from './poll'\nimport { DiagnosticHint } from './DiagnosticHint'\nimport { ReportRow } from './ReportRow'\n\ninterface MineListProps {\n api: ApiClient\n externalId: string\n strings: Record<StringKey, string>\n onSelect: (report: WidgetReportRow) => void\n /** Resolve a report by its per-project #seq and open it. Resolves false\n * when no such report is visible (powers the jump-by-#seq box, #85).\n * When omitted the jump form is not rendered. */\n onOpenSeq?: (seq: number) => Promise<boolean>\n}\n\nconst POLL_MS = 30_000\n\nexport function MineList({ api, externalId, strings, onSelect, onOpenSeq }: 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 [filter, setFilter] = useState<KpiFilter>('all')\n const [validating, setValidating] = useState<string | null>(null)\n\n // One-tap validation (roadmap #10): confirm the fix (→ closed) or send\n // it back (→ in_progress) straight from the row — reversible either\n // way, so no confirm dialog stands between the reporter and closing\n // the loop.\n const validate = async (reportId: string, kind: 'confirm' | 'reopen') => {\n setValidating(reportId)\n try {\n if (kind === 'confirm') await api.closeAsResolved(reportId, externalId)\n else await api.reopenUnresolved(reportId, externalId)\n await fetchRows()\n } catch (err) {\n if (typeof console !== 'undefined') console.warn('[mhosaic] validate:', err)\n if (mountedRef.current) setError(strings['mine.error'])\n } finally {\n if (mountedRef.current) setValidating(null)\n }\n }\n const [jumpValue, setJumpValue] = useState('')\n const [jumpError, setJumpError] = useState(false)\n const [jumping, setJumping] = useState(false)\n const mountedRef = useRef(true)\n\n const handleJump = async (e: Event) => {\n e.preventDefault()\n if (!onOpenSeq || jumping) return\n const seq = parseInt(jumpValue.replace(/^#/, '').trim(), 10)\n if (!Number.isInteger(seq) || seq <= 0) {\n setJumpError(true)\n return\n }\n setJumping(true)\n setJumpError(false)\n try {\n const found = await onOpenSeq(seq)\n if (!mountedRef.current) return\n if (found) setJumpValue('')\n else setJumpError(true)\n } finally {\n if (mountedRef.current) setJumping(false)\n }\n }\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 true\n setRows(next)\n return true\n } catch (err) {\n // Don't surface raw API error messages to the user — they used to\n // render as e.g. `listMine failed: 403 {\"detail\":\"Public API key…\"}`\n // straight into the panel. Friendly fallback in the UI, raw error\n // in the console for the developer.\n if (typeof console !== 'undefined') console.warn('[mhosaic] listMine:', err)\n if (!mountedRef.current) return false\n // A 429 gets an actionable \"retry in Xs\" message; anything else falls\n // back to the generic copy. Either way loading resolves (#80/#81).\n setError(rateLimitMessage(err, strings) ?? strings['mine.error'])\n return false\n } finally {\n if (mountedRef.current) setRefreshing(false)\n }\n }\n\n useEffect(() => {\n mountedRef.current = true\n const poll = startPoll(fetchRows, POLL_MS)\n return () => {\n mountedRef.current = false\n poll.stop()\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 const visibleRows = rows ? rowsMatchingFilter(rows, filter) : null\n const visibleEmpty = !!rows && rows.length > 0 && (visibleRows?.length ?? 0) === 0\n\n return (\n <div class=\"mine-list\">\n <div class=\"mine-list-header\">\n <h2>{strings['tab.mine']}</h2>\n <div class=\"mine-list-header-actions\">\n {onOpenSeq && (\n <form class=\"mine-jump\" onSubmit={handleJump}>\n <input\n type=\"text\"\n inputMode=\"numeric\"\n value={jumpValue}\n placeholder={strings['mine.jump.placeholder']}\n aria-label={strings['mine.jump.aria']}\n onInput={(e) => {\n setJumpValue((e.target as HTMLInputElement).value)\n setJumpError(false)\n }}\n disabled={jumping}\n />\n <button type=\"submit\" class=\"btn\" disabled={jumping || !jumpValue.trim()}>\n {strings['mine.jump.go']}\n </button>\n </form>\n )}\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 </div>\n {jumpError && (\n <div class=\"mine-jump-error error\" role=\"alert\">\n {strings['mine.jump.not_found']}\n </div>\n )}\n {rows && rows.some((r) => r.status === 'awaiting_validation') && (\n <div class=\"validate-banner\" role=\"status\">\n <span>\n {strings['mine.validate.banner'].replace(\n '{count}',\n String(rows.filter((r) => r.status === 'awaiting_validation').length),\n )}\n </span>\n <button\n type=\"button\"\n class=\"btn\"\n onClick={() => setFilter('awaiting_validation')}\n >\n {strings['mine.validate.view']}\n </button>\n </div>\n )}\n {rows && rows.length > 0 && (\n <KpiStrip rows={rows} filter={filter} onFilter={setFilter} strings={strings} />\n )}\n {isLoading && <div class=\"mine-loading\">{strings['mine.loading']}</div>}\n {error && (\n <div class=\"error\">\n {error}\n <DiagnosticHint api={api} strings={strings} host={api.endpointHost} />\n </div>\n )}\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 {visibleEmpty && (\n <div class=\"mine-empty\">\n <p>{strings['mine.filter.empty']}</p>\n </div>\n )}\n {visibleRows && visibleRows.length > 0 && (\n <ul class=\"mine-rows\">\n {visibleRows.map((row) => (\n <li>\n <ReportRow row={row} strings={strings} onClick={() => onSelect(row)} />\n {row.status === 'awaiting_validation' && (\n <div class=\"validate-actions\">\n <button\n type=\"button\"\n class=\"btn\"\n disabled={validating === row.id}\n onClick={() => {\n void validate(row.id, 'confirm')\n }}\n >\n {strings['mine.validate.confirm']}\n </button>\n <button\n type=\"button\"\n class=\"btn btn--ghost\"\n disabled={validating === row.id}\n onClick={() => {\n void validate(row.id, 'reopen')\n }}\n >\n {strings['mine.validate.reopen']}\n </button>\n </div>\n )}\n </li>\n ))}\n </ul>\n )}\n </div>\n )\n}\n","/**\n * KpiStrip — four-pill summary at the top of the user's \"My reports\" tab.\n *\n * Computes counts client-side from the same `WidgetReportRow[]` that\n * MineList already fetches; no extra endpoint. Each pill is clickable\n * and toggles a status filter on the list below — matches thePnr's\n * customer-facing pattern (`FeedbackKPICards.tsx`).\n */\n\nimport { h } from 'preact'\n\nimport type { ReportStatus, WidgetReportRow } from '../types'\nimport type { StringKey } from './i18n'\n\nexport type KpiFilter = 'all' | 'new' | 'in_progress' | 'awaiting_validation' | 'resolved'\n\ninterface KpiStripProps {\n rows: WidgetReportRow[]\n filter: KpiFilter\n onFilter: (next: KpiFilter) => void\n strings: Record<StringKey, string>\n}\n\ninterface Counts {\n new: number\n in_progress: number\n awaiting_validation: number\n resolved: number\n total: number\n}\n\nexport function computeKpiCounts(rows: WidgetReportRow[]): Counts {\n const counts: Counts = {\n new: 0,\n in_progress: 0,\n awaiting_validation: 0,\n resolved: 0,\n total: 0,\n }\n for (const row of rows) {\n counts.total += 1\n switch (row.status) {\n case 'new':\n counts.new += 1\n break\n case 'in_progress':\n counts.in_progress += 1\n break\n case 'awaiting_validation':\n counts.awaiting_validation += 1\n break\n case 'closed':\n case 'rejected':\n case 'duplicate':\n case 'wontfix':\n counts.resolved += 1\n break\n }\n }\n return counts\n}\n\n/**\n * Resolution rate = (awaiting_validation + closed + rejected + duplicate +\n * wontfix) / total. Includes operator-resolved-as-no-action so the\n * percentage reflects \"your reports the team has acted on\" rather than\n * \"your reports that turned into shipped fixes\". Returns null if there\n * are no rows yet — avoids showing \"0%\" before the user has filed\n * anything, which reads as ominous.\n */\nexport function computeResolutionRate(counts: Counts): number | null {\n if (counts.total === 0) return null\n return Math.round(\n ((counts.awaiting_validation + counts.resolved) / counts.total) * 100,\n )\n}\n\nconst FILTER_FOR_STATUS: Record<ReportStatus, KpiFilter> = {\n new: 'new',\n in_progress: 'in_progress',\n awaiting_validation: 'awaiting_validation',\n closed: 'resolved',\n rejected: 'resolved',\n duplicate: 'resolved',\n wontfix: 'resolved',\n}\n\nexport function rowsMatchingFilter(\n rows: WidgetReportRow[],\n filter: KpiFilter,\n): WidgetReportRow[] {\n if (filter === 'all') return rows\n return rows.filter((r) => FILTER_FOR_STATUS[r.status] === filter)\n}\n\nexport function KpiStrip({ rows, filter, onFilter, strings }: KpiStripProps) {\n const counts = computeKpiCounts(rows)\n const resolutionRate = computeResolutionRate(counts)\n const cells: Array<{ key: KpiFilter; label: string; value: string }> = [\n { key: 'new', label: strings['kpi.new'], value: String(counts.new) },\n { key: 'in_progress', label: strings['kpi.in_progress'], value: String(counts.in_progress) },\n {\n key: 'awaiting_validation',\n label: strings['kpi.awaiting_validation'],\n value: String(counts.awaiting_validation),\n },\n {\n key: 'resolved',\n label: strings['kpi.resolution_rate'],\n value: resolutionRate === null ? '—' : `${resolutionRate}%`,\n },\n ]\n return (\n <div class=\"kpi-strip\" role=\"toolbar\">\n {cells.map((c) => {\n const active = filter === c.key\n const toggleTo: KpiFilter = active ? 'all' : c.key\n return (\n <button\n type=\"button\"\n class={`kpi-cell kpi-cell--${c.key}${active ? ' is-active' : ''}`}\n onClick={() => onFilter(toggleTo)}\n aria-pressed={active}\n >\n <span class=\"kpi-value\">{c.value}</span>\n <span class=\"kpi-label\">{c.label}</span>\n </button>\n )\n })}\n </div>\n )\n}\n","import { h, type ComponentChildren } from 'preact'\nimport { useEffect, useRef } from 'preact/hooks'\n\n/** Why the modal is being dismissed. `backdrop`/`escape` are \"accidental\"\n * gestures a caller may want to guard (e.g. unsaved input, #89); `button`\n * is the explicit ✕ and always closes. */\nexport type DismissReason = 'backdrop' | 'escape' | 'button'\n\ninterface ModalProps {\n onDismiss: (reason: DismissReason) => void\n children: ComponentChildren\n closeLabel?: string\n /** v0.12: when true, the modal grows toward near-fullscreen on desktop\n * and full-screen on mobile. Drives the \"transport\" feel for the Board\n * tab — opening the tab feels like clicking through to a real page. */\n expanded?: boolean\n}\n\nexport function Modal({ onDismiss, children, closeLabel = 'Close', expanded = false }: 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('escape')\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 ${expanded ? 'is-expanded' : ''}`}\n role=\"presentation\"\n onClick={(e) => {\n if (e.target === e.currentTarget) onDismiss('backdrop')\n }}\n >\n <div\n ref={modalRef}\n class={`modal ${expanded ? 'is-expanded' : ''}`}\n role=\"dialog\"\n aria-modal=\"true\"\n >\n <button\n type=\"button\"\n class=\"modal-close\"\n aria-label={closeLabel}\n onClick={() => onDismiss('button')}\n >\n ×\n </button>\n {children}\n </div>\n </div>\n )\n}\n","/** One passive line on the Send tab: \"N open reports on this page — View\".\n * Dedup affordance (spec §3.3) — never blocks, never steals focus. */\nimport { h } from 'preact'\n\nimport type { StringKey } from './i18n'\n\nexport function PageActivityStrip({\n open,\n strings,\n onView,\n}: {\n open: number\n strings: Record<StringKey, string>\n onView: () => void\n}) {\n if (open === 0) return null\n const text =\n open === 1\n ? strings['page.strip.text.one']\n : strings['page.strip.text'].replace('{n}', String(open))\n return (\n <div class=\"page-strip\">\n <span>{text}</span>\n <button type=\"button\" class=\"page-strip-view\" onClick={onView}>\n {strings['page.strip.view']}\n </button>\n </div>\n )\n}\n","/** Desktop hover peek: up to 5 of this page's reports above the FAB\n * (spec §3.2). Non-modal; failures degrade to the footer actions. */\nimport { h } from 'preact'\nimport { useEffect, useState } from 'preact/hooks'\n\nimport type { ApiClient } from '../api/client'\nimport type { WidgetBoardRow } from '../types'\nimport { currentPagePath } from './currentPage'\nimport type { StringKey } from './i18n'\n\nexport function PagePeekPanel({\n api,\n externalId,\n strings,\n getCurrentPage,\n open,\n onViewAll,\n onPickRow,\n onSend,\n}: {\n api: ApiClient\n externalId: string\n strings: Record<StringKey, string>\n getCurrentPage?: () => string | null\n open: number\n onViewAll: () => void\n onPickRow?: (reportId: string) => void\n onSend: () => void\n}) {\n const [rows, setRows] = useState<WidgetBoardRow[] | null>(null)\n const [failed, setFailed] = useState(false)\n\n useEffect(() => {\n let cancelled = false\n const pagePath = currentPagePath(getCurrentPage !== undefined ? { getCurrentPage } : {})\n api\n .listBoard(externalId, {\n pagePath,\n status: ['new', 'in_progress', 'awaiting_validation'],\n })\n .then((page) => {\n if (!cancelled) setRows(page.results.slice(0, 5))\n })\n .catch(() => {\n if (!cancelled) setFailed(true)\n })\n return () => {\n cancelled = true\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [api, externalId])\n\n return (\n <div class=\"page-peek\" role=\"region\" aria-label={strings['page.peek.title']}>\n <div class=\"page-peek-title\">{strings['page.peek.title']}</div>\n {rows === null && !failed && (\n <div class=\"page-peek-skeleton\" aria-hidden=\"true\">\n <div /> <div />\n </div>\n )}\n {rows !== null &&\n rows.map((row) => (\n <button\n key={row.id}\n type=\"button\"\n class=\"page-peek-row\"\n onClick={() => onPickRow?.(row.id)}\n >\n <span class={`page-peek-dot status-${row.status}`} aria-hidden=\"true\" />\n <span class=\"page-peek-desc\">{row.description}</span>\n </button>\n ))}\n <div class=\"page-peek-footer\">\n <button type=\"button\" class=\"page-peek-viewall\" onClick={onViewAll}>\n {open === 1\n ? strings['page.peek.viewAll.one']\n : strings['page.peek.viewAll'].replace('{n}', String(open))}\n </button>\n <button type=\"button\" class=\"page-peek-send\" onClick={onSend}>\n {strings['fab.label']}\n </button>\n </div>\n </div>\n )\n}\n","/** \"What's open on this page\" — one signal powering the FAB badge, the\n * hover peek and the Send-tab strip (spec 2026-07-06-page-aware-widget).\n * Identity-gated server-side; every failure is silent (surfaces hide). */\nimport { useCallback, useEffect, useState } from 'preact/hooks'\n\nimport type { ApiClient } from '../api/client'\nimport type { WidgetBoardKpis } from '../types'\nimport { currentPagePath, onLocationChange } from './currentPage'\n\nconst TTL_MS = 60_000\n\nexport function computeOpenCount(kpis: WidgetBoardKpis): number {\n const s = kpis.by_status\n return (s.new ?? 0) + (s.in_progress ?? 0) + (s.awaiting_validation ?? 0)\n}\n\n// Module-level so remounts (modal open/close) reuse the fact. Stores the\n// full KPI payload (not just the derived open count) so every page-scoped\n// KPI consumer — the FAB badge, the smart-open check in openWidget — shares\n// ONE request per (user, path, TTL) instead of each firing its own.\nconst cache = new Map<string, { kpis: WidgetBoardKpis; fetchedAt: number }>()\nconst inflight = new Map<string, Promise<WidgetBoardKpis>>()\n\nexport function invalidatePageSignal(): void {\n cache.clear()\n}\n\nexport function _resetPageSignalCache(): void {\n cache.clear()\n inflight.clear()\n}\n\n/** Page-scoped KPIs through the module cache (60s TTL) + in-flight dedup.\n * The FAB-open path used to call `api.fetchBoardKpis` directly, firing a\n * second identical request right after the badge had fetched the same\n * endpoint+params. */\nexport function fetchPageKpis(\n api: ApiClient,\n externalId: string,\n path: string,\n): Promise<WidgetBoardKpis> {\n const key = `${externalId}|${path}`\n const hit = cache.get(key)\n if (hit && Date.now() - hit.fetchedAt < TTL_MS) return Promise.resolve(hit.kpis)\n const pending = inflight.get(key)\n if (pending) return pending\n const p = api\n .fetchBoardKpis(externalId, { pagePath: path })\n .then((kpis) => {\n cache.set(key, { kpis, fetchedAt: Date.now() })\n return kpis\n })\n .finally(() => inflight.delete(key))\n inflight.set(key, p)\n return p\n}\n\nfunction fetchOpen(api: ApiClient, externalId: string, path: string): Promise<number> {\n return fetchPageKpis(api, externalId, path).then(computeOpenCount)\n}\n\nexport function usePageOpenCount(opts: {\n api?: ApiClient\n externalId?: string\n getCurrentPage?: () => string | null\n enabled: boolean\n}): { open: number; refresh: () => void } {\n const { api, externalId, getCurrentPage, enabled } = opts\n const [open, setOpen] = useState(0)\n const [tick, setTick] = useState(0)\n\n const refresh = useCallback(() => {\n invalidatePageSignal()\n setTick((t) => t + 1)\n }, [])\n\n useEffect(() => {\n if (!enabled || !api || !externalId) {\n setOpen(0)\n return\n }\n let cancelled = false\n const load = () => {\n const path = currentPagePath(getCurrentPage !== undefined ? { getCurrentPage } : {})\n fetchOpen(api, externalId, path)\n .then((n) => {\n if (!cancelled) setOpen(n)\n })\n .catch(() => {\n // Silent by design — a failing count must never surface UI.\n if (!cancelled) setOpen(0)\n })\n }\n load()\n const unsub = onLocationChange(load)\n return () => {\n cancelled = true\n unsub()\n }\n }, [api, externalId, enabled, tick])\n\n return { open, refresh }\n}\n","export const WIDGET_STYLES = `\n:host {\n --mfb-accent: #3b82f6;\n --mfb-accent-contrast: #ffffff;\n\n /* FAB-specific palette — Mhosaic brand (steel blue family, per\n mhosaic-core's design system v4). Soft-black surface + light\n steel-blue glyph. Kept separate from --mfb-accent on purpose:\n the accent is used everywhere inside the panel for submit\n buttons / focus rings, and we don't want changing the FAB chrome\n to inadvertently restyle the whole widget. */\n --mfb-fab-bg: #1c2230; /* Mhosaic --black (oklch ≈ 0.22 0.025 258) */\n --mfb-fab-icon: #8aa9e5; /* Mhosaic --blue-300 (oklch ≈ 0.70 0.13 258) */\n --mfb-bg: #ffffff;\n --mfb-surface: #f9fafb;\n --mfb-surface-2: #f3f4f6;\n --mfb-text: #0a0a0a;\n --mfb-text-muted: #6b7280;\n --mfb-border: #e5e7eb;\n --mfb-border-strong: #d1d5db;\n --mfb-radius: 8px;\n --mfb-radius-lg: 14px;\n --mfb-font: system-ui, -apple-system, sans-serif;\n --mfb-z-index: 2147483640;\n\n /* Spacing scale — 4px base, used everywhere instead of magic numbers. */\n --mfb-space-1: 4px;\n --mfb-space-2: 8px;\n --mfb-space-3: 12px;\n --mfb-space-4: 16px;\n --mfb-space-5: 24px;\n --mfb-space-6: 32px;\n --mfb-space-7: 48px;\n\n /* Type scale — fixed sizes, no fluid scaling. Body 14px sits in the\n \"comfortable on every viewport\" range; headings step up gracefully. */\n --mfb-text-xs: 11px;\n --mfb-text-sm: 13px;\n --mfb-text-base: 14px;\n --mfb-text-md: 15px;\n --mfb-text-lg: 17px;\n --mfb-text-xl: 20px;\n\n /* Elevation — three tiers for layering. */\n --mfb-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06), 0 1px 3px rgba(0, 0, 0, 0.08);\n --mfb-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.12);\n --mfb-shadow-lg: 0 24px 56px rgba(0, 0, 0, 0.18), 0 8px 20px rgba(0, 0, 0, 0.10);\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: #0f172a;\n --mfb-surface: #1e293b;\n --mfb-surface-2: #253349;\n --mfb-text: #f8fafc;\n --mfb-text-muted: #94a3b8;\n --mfb-border: #334155;\n --mfb-border-strong: #475569;\n --mfb-shadow-lg: 0 24px 56px rgba(0, 0, 0, 0.55), 0 8px 20px rgba(0, 0, 0, 0.40);\n }\n}\n\n/* FAB — 48px circle (down from Material's 56px) for a more discrete\n presence on client surfaces (matches PNR's 48px pattern). Mhosaic\n soft-black background with a light steel-blue lucide-bug glyph;\n custom SVG inlined (no emoji — emoji renders inconsistently across\n OSes and can't inherit color). Two-layer elevation, scale-on-press. */\n.fab-area {\n position: fixed;\n bottom: 24px;\n right: 24px;\n /* No z-index on purpose: the modal .backdrop/.modal are later siblings in\n the shadow root, so DOM order must keep them painting above the FAB —\n an explicit z-index here would trap the FAB on top of the open modal. */\n}\n.fab-area .fab {\n position: relative;\n right: auto;\n bottom: auto;\n}\n.fab {\n width: 48px;\n height: 48px;\n border-radius: 999px;\n background: var(--mfb-fab-bg);\n color: var(--mfb-fab-icon);\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-fab-icon);\n outline-offset: 3px;\n box-shadow:\n 0 0 0 4px var(--mfb-fab-bg),\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\n/* Page-activity badge — absolute so it never shifts the FAB's layout. */\n.fab-badge {\n position: absolute;\n top: -2px;\n right: -2px;\n min-width: 16px;\n height: 16px;\n padding: 0 4px;\n box-sizing: border-box;\n border-radius: 999px;\n background: var(--mfb-accent);\n color: #fff;\n font-size: 10px;\n font-weight: 700;\n line-height: 16px;\n text-align: center;\n box-shadow: 0 0 0 2px var(--mfb-fab-bg);\n animation: mfb-badge-in 150ms ease-out;\n}\n@keyframes mfb-badge-in {\n from {\n opacity: 0;\n transform: scale(0.6);\n }\n to {\n opacity: 1;\n transform: scale(1);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .fab { transition: none; }\n .fab:hover, .fab:active { transform: none; }\n .fab-badge {\n animation: none;\n }\n}\n\n/* Backdrop — fade in with a slight blur for depth. The blur gives the\n modal that \"page that opens\" weight: the underlying page recedes\n visually so the widget feels foregrounded, not pasted on. */\n.backdrop {\n position: fixed;\n inset: 0;\n background: rgba(15, 23, 42, 0.55);\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n display: grid;\n place-items: center;\n animation: mfb-backdrop-in 220ms cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n@keyframes mfb-backdrop-in {\n from { opacity: 0; backdrop-filter: blur(0px); -webkit-backdrop-filter: blur(0px); }\n to { opacity: 1; backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); }\n}\n\n.modal {\n background: var(--mfb-bg);\n border-radius: var(--mfb-radius-lg);\n box-shadow: var(--mfb-shadow-lg);\n /* 720px is the \"page that opens\" sweet spot — wide enough to feel\n like a workspace, narrow enough to not dominate the screen. Was\n 440px before; the bump trades a denser modal for a calmer canvas. */\n width: min(720px, calc(100vw - var(--mfb-space-7)));\n padding: var(--mfb-space-6);\n display: flex;\n flex-direction: column;\n gap: var(--mfb-space-5);\n position: relative;\n /* Cap height with breathing room. The form scrolls internally if\n the user attaches a tall screenshot. */\n max-height: min(820px, calc(100vh - var(--mfb-space-7)));\n overflow-y: auto;\n /* Entrance: opacity-only. Avoids any geometric shift during the\n animation so interaction tests that read boundingBox immediately\n after the modal becomes visible get stable coordinates. 220ms\n ease-out is snappy enough that human users barely register it. */\n animation: mfb-modal-in 220ms ease-out;\n /* Transition lives on the BASE rule with a SHORTER duration so the\n \"back\" trip (Board → other tab, where .is-expanded is removed)\n feels snappy. The grow direction overrides this with a slower\n curve in .modal.is-expanded below — the asymmetry is intentional:\n a slow grow reads as a transport, a slow shrink reads as\n hesitation. */\n transition:\n width 200ms cubic-bezier(0.4, 0, 0.2, 1),\n height 200ms cubic-bezier(0.4, 0, 0.2, 1),\n max-height 200ms cubic-bezier(0.4, 0, 0.2, 1),\n padding 200ms cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n@keyframes mfb-modal-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n/* Mobile: full-height sheet that slides up from the bottom. The\n slide-up gesture matches platform-native sheets users already know. */\n@media (max-width: 640px) {\n .backdrop { place-items: end center; }\n .modal {\n width: 100vw;\n height: calc(100vh - var(--mfb-space-7));\n max-height: none;\n border-radius: var(--mfb-radius-lg) var(--mfb-radius-lg) 0 0;\n padding: var(--mfb-space-5) var(--mfb-space-4) var(--mfb-space-4);\n animation: mfb-sheet-up 280ms cubic-bezier(0.16, 1, 0.3, 1);\n }\n}\n\n@keyframes mfb-sheet-up {\n from { transform: translateY(100%); }\n to { transform: translateY(0); }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .backdrop, .modal { animation: none; }\n}\n\n.modal h2 {\n margin: 0;\n font-size: var(--mfb-text-xl);\n font-weight: 600;\n padding-right: var(--mfb-space-6);\n letter-spacing: -0.015em;\n line-height: 1.2;\n}\n\n.modal form {\n display: flex;\n flex-direction: column;\n gap: var(--mfb-space-5);\n /* Match the entrance motion the Board/MineList/Changelog containers\n * use so switching tabs (especially Board → Send, where the modal\n * is mid-shrink) doesn't show a content snap behind the transition.\n * 40ms delay lets the modal's shrink finish first. */\n animation: mfb-board-in 220ms ease-out 40ms both;\n}\n@media (prefers-reduced-motion: reduce) {\n .modal form { animation: none; }\n}\n\n.modal-close {\n position: absolute;\n top: var(--mfb-space-3);\n right: var(--mfb-space-3);\n width: 36px;\n height: 36px;\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 transition: background 120ms ease, color 120ms ease;\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 + input stacked with breathing room.\n The 24px modal-level gap separates groups. */\n.field { display: flex; flex-direction: column; gap: var(--mfb-space-2); font-size: var(--mfb-text-sm); }\n\n.field label {\n color: var(--mfb-text-muted);\n font-weight: 500;\n font-size: var(--mfb-text-xs);\n letter-spacing: 0.03em;\n text-transform: uppercase;\n}\n\n.field input, .field select, .field textarea {\n font-family: inherit;\n font-size: var(--mfb-text-base);\n color: inherit;\n padding: 11px 14px;\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, background 120ms ease;\n}\n\n.field input:hover, .field select:hover, .field textarea:hover { border-color: var(--mfb-border-strong); }\n.field input:focus, .field select:focus, .field textarea:focus {\n outline: none;\n border-color: var(--mfb-accent);\n background: var(--mfb-bg);\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: 120px; resize: vertical; line-height: 1.5; }\n\n.row { display: flex; gap: var(--mfb-space-3); }\n.row > * { flex: 1; }\n\n/* Footer: subtle separation via border, slightly more vertical space. */\n.actions {\n display: flex;\n gap: var(--mfb-space-2);\n justify-content: flex-end;\n padding-top: var(--mfb-space-4);\n margin-top: var(--mfb-space-1);\n border-top: 1px solid var(--mfb-border);\n}\n\n.btn {\n padding: 10px 18px;\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-size: var(--mfb-text-sm);\n font-weight: 500;\n cursor: pointer;\n transition: background 120ms ease, border-color 120ms ease, transform 80ms ease;\n}\n.btn:hover { background: var(--mfb-surface); border-color: var(--mfb-border-strong); }\n.btn:active { transform: scale(0.98); }\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/* Subdued button — borrows accent text but transparent background. Used\n * for the \"Capture this page\" alt-action that sits below the dropzone,\n * where a full --primary would compete with the form's main submit. */\n.btn--ghost {\n background: transparent;\n color: var(--mfb-accent);\n border-color: color-mix(in srgb, var(--mfb-accent) 40%, transparent);\n}\n.btn--ghost:hover {\n background: color-mix(in srgb, var(--mfb-accent) 8%, transparent);\n border-color: var(--mfb-accent);\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/* REQ-A7: quiet transparency line under the form. */\n.capture-notice { color: var(--mfb-muted, #6b7280); font-size: 11px; line-height: 1.4; margin-top: 6px; }\n\n/* #85: inline \"#NN\" report reference rendered as a link (a button for a11y). */\n.seq-link {\n display: inline;\n padding: 0;\n border: none;\n background: none;\n color: var(--mfb-accent);\n font: inherit;\n cursor: pointer;\n}\n.seq-link:hover { text-decoration: underline; }\n\n/* ---- #89: discard-unsaved-changes confirmation over the form -------- */\n.discard-confirm {\n position: absolute;\n inset: 0;\n z-index: 5;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 16px;\n background: rgba(15, 23, 42, 0.45);\n border-radius: inherit;\n}\n.discard-confirm-card {\n max-width: 260px;\n text-align: center;\n background: var(--mfb-bg);\n color: var(--mfb-text);\n border-radius: var(--mfb-radius-lg);\n padding: 18px 16px;\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.18);\n}\n.discard-confirm-title { font-weight: 600; font-size: 15px; margin: 0 0 4px; }\n.discard-confirm-body { font-size: 13px; margin: 0 0 14px; opacity: 0.8; }\n.discard-confirm-actions { display: flex; gap: 8px; justify-content: center; }\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/* Dropzone — bigger, more inviting; the icon + heading + sub-line\n hierarchy makes it feel like a deliberate target, not an afterthought. */\n.screenshot-dropzone {\n border: 1.5px dashed var(--mfb-border-strong);\n border-radius: var(--mfb-radius-lg);\n padding: var(--mfb-space-6) var(--mfb-space-4);\n text-align: center;\n cursor: pointer;\n background: var(--mfb-surface);\n transition: border-color 160ms ease, background 160ms ease, transform 120ms ease;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: var(--mfb-space-2);\n}\n.screenshot-dropzone:hover {\n border-color: var(--mfb-accent);\n background: color-mix(in srgb, var(--mfb-accent) 4%, var(--mfb-surface));\n}\n.screenshot-dropzone.is-dragover {\n border-color: var(--mfb-accent);\n border-style: solid;\n background: color-mix(in srgb, var(--mfb-accent) 10%, var(--mfb-surface));\n transform: scale(1.005);\n}\n.screenshot-dropzone:focus-visible {\n outline: 2px solid var(--mfb-accent);\n outline-offset: 2px;\n}\n.screenshot-icon {\n color: var(--mfb-text-muted);\n opacity: 0.7;\n margin-bottom: var(--mfb-space-1);\n transition: color 160ms ease, opacity 160ms ease;\n}\n.screenshot-dropzone:hover .screenshot-icon,\n.screenshot-dropzone.is-dragover .screenshot-icon {\n color: var(--mfb-accent);\n opacity: 1;\n}\n.screenshot-cta { font-size: var(--mfb-text-base); color: var(--mfb-text); font-weight: 500; }\n.screenshot-cta strong { color: var(--mfb-accent); font-weight: 600; }\n.screenshot-formats { font-size: var(--mfb-text-xs); color: var(--mfb-text-muted); }\n\n.screenshot-strip {\n display: flex;\n flex-direction: column;\n gap: var(--mfb-space-2);\n margin-bottom: var(--mfb-space-2);\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: 280px;\n object-fit: contain;\n background: #1a1a1a;\n}\n/* In the multi-shot strip each preview stays compact so several fit. */\n.screenshot-strip .screenshot-preview img {\n max-height: 120px;\n}\n.screenshot-preview-actions {\n display: flex;\n gap: var(--mfb-space-2);\n padding: var(--mfb-space-2);\n border-top: 1px solid var(--mfb-border);\n background: var(--mfb-bg);\n}\n.screenshot-preview-actions .btn {\n flex: 1;\n padding: 8px 12px;\n font-size: var(--mfb-text-sm);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n}\n.screenshot-preview-actions .btn--primary {\n background: var(--mfb-bg);\n color: var(--mfb-accent);\n border-color: var(--mfb-accent);\n}\n.screenshot-preview-actions .btn--primary:hover {\n background: color-mix(in srgb, var(--mfb-accent) 8%, var(--mfb-bg));\n border-color: var(--mfb-accent);\n color: var(--mfb-accent);\n}\n.screenshot-remove {\n /* Kept for legacy markup that uses the old corner-cross button. */\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 /* Kept for legacy; new markup uses .screenshot-preview-actions. */\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 position: relative;\n}\n.annotator-color.is-active {\n transform: scale(1.12);\n border-color: var(--mfb-text);\n}\n/* Color-picker swatch: rainbow gradient fill so users see this isn't\n a preset, plus a tiny native <input type=\"color\"> overlaid invisibly. */\n.annotator-color-picker {\n display: grid;\n place-items: center;\n background: conic-gradient(from 180deg, #ef4444, #f59e0b, #10b981, #3b82f6, #8b5cf6, #ec4899, #ef4444);\n overflow: hidden;\n}\n.annotator-color-picker input[type=\"color\"] {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n border: 0;\n padding: 0;\n margin: 0;\n opacity: 0;\n cursor: pointer;\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: var(--mfb-space-1);\n border-bottom: 1px solid var(--mfb-border);\n margin: 0;\n padding: 0;\n}\n.tab-button {\n appearance: none;\n background: transparent;\n border: 0;\n border-bottom: 2px solid transparent;\n padding: var(--mfb-space-3) var(--mfb-space-4);\n margin-bottom: -1px;\n font: inherit;\n font-size: var(--mfb-text-sm);\n font-weight: 500;\n color: var(--mfb-text-muted);\n cursor: pointer;\n transition: color 120ms ease, border-color 120ms ease;\n}\n.tab-button:hover { color: var(--mfb-text); }\n.tab-button.is-active {\n color: var(--mfb-accent);\n border-bottom-color: var(--mfb-accent);\n}\n.tab-button[aria-selected=\"true\"] { font-weight: 600; }\n\n.mine-list {\n display: flex;\n flex-direction: column;\n gap: var(--mfb-space-3);\n /* Matches .board-view's entrance so switching tabs feels like a real\n * page transition rather than a flicker. */\n animation: mfb-board-in 280ms ease-out 40ms both;\n}\n@media (prefers-reduced-motion: reduce) {\n .mine-list { animation: none; }\n}\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: var(--mfb-text-lg); font-weight: 600; letter-spacing: -0.01em; }\n/* #85 — header actions: jump-by-#seq box + refresh. */\n.mine-list-header-actions {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n.mine-jump {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n.mine-jump input {\n font: inherit;\n font-size: 13px;\n width: 56px;\n padding: 5px 8px;\n border: 1px solid var(--mfb-border);\n border-radius: var(--mfb-radius);\n background: var(--mfb-surface);\n color: inherit;\n}\n.mine-jump-error {\n margin-top: 6px;\n font-size: 12px;\n}\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.validate-banner {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 8px 12px;\n margin-bottom: 8px;\n border: 1px solid var(--mfb-accent, #2563eb);\n border-radius: 8px;\n font-size: 13px;\n}\n.validate-actions {\n display: flex;\n gap: 8px;\n margin: 4px 0 8px;\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:\n border-color 140ms ease,\n background 140ms ease,\n transform 100ms ease,\n box-shadow 140ms ease;\n}\n.mine-row:hover {\n border-color: var(--mfb-border-strong);\n background: var(--mfb-bg);\n transform: translateY(-1px);\n box-shadow: var(--mfb-shadow);\n}\n.mine-row:active { transform: scale(0.997); }\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/* Agent-verified fix badge (Phase B) — emerald, same family as\n .pill-status--closed but its own modifier since it layers alongside\n the status pill rather than replacing it. */\n.pill-verified { background: #ecfdf5; color: #047857; border-color: #a7f3d0; }\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-header-pills { display: inline-flex; align-items: center; gap: 6px; }\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/* #91 — image attachments rendered inside a comment bubble. */\n.comment-attachments {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-top: 6px;\n}\n.comment-attachment {\n display: block;\n line-height: 0;\n border-radius: 8px;\n overflow: hidden;\n border: 1px solid var(--mfb-border);\n}\n.comment-attachment img {\n width: 96px;\n height: 96px;\n object-fit: cover;\n display: block;\n}\n.comment-time {\n font-size: 10px;\n margin-top: 4px;\n opacity: 0.7;\n}\n\n/* F4 — author edits the description in place. */\n.report-detail-description-row {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n}\n.report-detail-description-row .report-detail-description {\n flex: 1;\n margin: 0;\n}\n.report-detail-edit {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n.report-detail-edit-input {\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: 72px;\n resize: vertical;\n}\n.report-detail-edit-actions {\n display: flex;\n justify-content: flex-end;\n gap: 6px;\n}\n.report-detail-edit.btn,\nbutton.report-detail-edit {\n flex: 0 0 auto;\n font-size: 12px;\n padding: 4px 8px;\n}\n/* #85 — shareable-permalink affordance under the description. */\n.report-detail-permalink {\n margin: -2px 0 6px;\n}\n.report-detail-copy-link {\n font-size: 12px;\n padding: 4px 8px;\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/* #91 — pending image attachments in the reply compose box. */\n.compose-attachments {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n}\n.compose-attachment {\n position: relative;\n width: 64px;\n height: 64px;\n border-radius: 8px;\n overflow: hidden;\n border: 1px solid var(--mfb-border);\n}\n.compose-attachment img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n}\n.compose-attachment-remove {\n position: absolute;\n top: 2px;\n right: 2px;\n width: 20px;\n height: 20px;\n display: grid;\n place-items: center;\n padding: 0;\n background: rgba(255, 255, 255, 0.92);\n border: 1px solid var(--mfb-border);\n border-radius: 999px;\n font-size: 15px;\n line-height: 1;\n cursor: pointer;\n color: #111827;\n}\n.compose-attachment-remove:hover { background: #fff; }\n.compose-attach-btn { margin-right: auto; }\n.compose-attach-error {\n margin: 0;\n font-size: 12px;\n color: var(--mfb-danger, #b91c1c);\n}\n\n/* v0.15.3 — teammate-viewing notice. Shown in place of the compose\n * box when a non-submitter opens a project-wide row from the Board\n * tab. Replies are private to the submitter; we surface this once\n * (calm tone, neutral surface) so the viewer understands why no\n * Reply box appears, instead of leaving them wondering. */\n.report-detail-teammate-notice {\n margin: 0;\n padding: var(--mfb-space-3) var(--mfb-space-4);\n background: var(--mfb-surface);\n border: 1px dashed var(--mfb-border);\n border-radius: var(--mfb-radius);\n font-size: var(--mfb-text-sm);\n color: var(--mfb-text-muted);\n font-style: italic;\n}\n\n/* v0.15.3 — friendly \"report not available\" empty state. Replaces\n * the raw API error message (e.g. getReport failed: 404 …) that\n * used to leak from setError(err.message) straight into the DOM. */\n.report-detail-empty-state {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: var(--mfb-space-3);\n padding: var(--mfb-space-5);\n background: var(--mfb-surface);\n border-radius: var(--mfb-radius);\n border: 1px solid var(--mfb-border);\n}\n.report-detail-empty-state-title {\n margin: 0;\n font-size: var(--mfb-text-md);\n font-weight: 600;\n color: var(--mfb-text);\n}\n.report-detail-empty-state-body {\n margin: 0;\n font-size: var(--mfb-text-sm);\n color: var(--mfb-text-muted);\n}\n\n/* Status history — read-only audit trail visible to the submitter.\n Timeline of who flipped the status and when (operator vs. agent vs.\n system). Bordered, neutral surface so it doesn't compete visually\n with the conversation thread above. */\n.report-detail-history {\n display: flex;\n flex-direction: column;\n gap: 6px;\n border: 1px solid var(--mfb-border);\n border-radius: var(--mfb-radius);\n background: var(--mfb-surface);\n padding: 8px 10px;\n}\n.report-detail-history .report-detail-section {\n margin: 0;\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--mfb-text-muted);\n}\n.status-history {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.status-history-row {\n display: grid;\n grid-template-columns: auto 1fr auto;\n gap: 8px;\n align-items: center;\n font-size: 11px;\n}\n.status-history-time {\n color: var(--mfb-text-muted);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n.status-history-transition {\n display: inline-flex;\n gap: 4px;\n align-items: center;\n flex-wrap: wrap;\n}\n.status-history-arrow {\n color: var(--mfb-text-muted);\n font-size: 11px;\n}\n.status-history-source {\n font-size: 10px;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n font-weight: 600;\n white-space: nowrap;\n}\n.status-history-source--mcp { color: #6b21a8; }\n.status-history-source--user { color: #1e40af; }\n.status-history-source--system { color: var(--mfb-text-muted); }\n\n/* Transparency drawer — closed by default. The user can click open to\n verify what their browser sent: device, errors, console tail, network\n tail. Read-only; purely a trust-building gesture. */\n.report-detail-tech,\n.report-detail-verification {\n border: 1px solid var(--mfb-border);\n border-radius: var(--mfb-radius);\n background: var(--mfb-surface);\n}\n.report-detail-tech > summary,\n.report-detail-verification > summary {\n cursor: pointer;\n padding: 8px 10px;\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--mfb-text-muted);\n user-select: none;\n list-style: none;\n}\n.report-detail-tech > summary::-webkit-details-marker,\n.report-detail-verification > summary::-webkit-details-marker { display: none; }\n.report-detail-tech > summary::before,\n.report-detail-verification > summary::before {\n content: '▸';\n display: inline-block;\n margin-right: 6px;\n transition: transform 120ms;\n}\n.report-detail-tech[open] > summary::before,\n.report-detail-verification[open] > summary::before { transform: rotate(90deg); }\n.tech-body,\n.verification-body {\n padding: 0 10px 10px 10px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n.tech-section h4 {\n margin: 0 0 4px 0;\n font-size: 10px;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--mfb-text-muted);\n font-weight: 600;\n}\n.tech-device {\n display: grid;\n grid-template-columns: max-content 1fr;\n column-gap: 8px;\n row-gap: 2px;\n margin: 0;\n font-size: 11px;\n font-variant-numeric: tabular-nums;\n}\n.tech-device dt { color: var(--mfb-text-muted); }\n.tech-device dd { margin: 0; }\n.tech-errors, .tech-console, .tech-network {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n font-size: 11px;\n font-family: ui-monospace, \"SF Mono\", Menlo, monospace;\n max-height: 200px;\n overflow-y: auto;\n}\n.tech-errors li { padding: 4px 0; border-top: 1px solid var(--mfb-border); }\n.tech-errors li:first-child { border-top: 0; }\n.tech-errors-msg { color: #991b1b; font-weight: 600; }\n.tech-errors-stack {\n margin: 4px 0 0 0;\n padding: 4px 6px;\n background: var(--mfb-bg);\n border-radius: 4px;\n font-size: 10px;\n white-space: pre-wrap;\n color: var(--mfb-text-muted);\n max-height: 120px;\n overflow-y: auto;\n}\n.tech-console-row {\n display: grid;\n grid-template-columns: 48px 1fr;\n gap: 6px;\n padding: 1px 0;\n}\n.tech-console-level {\n text-transform: uppercase;\n font-size: 9px;\n font-weight: 600;\n align-self: center;\n color: var(--mfb-text-muted);\n}\n.tech-console-row--warn .tech-console-level { color: #92400e; }\n.tech-console-row--error .tech-console-level { color: #991b1b; }\n.tech-console-row--info .tech-console-level { color: #1e40af; }\n.tech-console-msg { word-break: break-word; }\n.tech-network-row {\n display: grid;\n grid-template-columns: 48px 56px 1fr 56px;\n gap: 6px;\n padding: 1px 0;\n}\n.tech-network-row--fail .tech-network-status { color: #991b1b; font-weight: 700; }\n.tech-network-status { font-variant-numeric: tabular-nums; }\n.tech-network-method { color: var(--mfb-text-muted); }\n.tech-network-url { word-break: break-all; }\n.tech-network-time { text-align: right; color: var(--mfb-text-muted); font-variant-numeric: tabular-nums; }\n\n/* Proof-of-fix drawer (Phase B) — shares the collapsible shell rules\n with .report-detail-tech above (grouped selectors): closed by\n default, rotating ▸ marker. Surfaces an agent's verification pass\n so the submitter can see the evidence behind the \"Vérifié par\n l'agent\" badge. */\n.verification-evidence {\n margin: 0;\n font-size: 12px;\n white-space: pre-wrap;\n}\n.verification-meta {\n display: flex;\n gap: 12px;\n flex-wrap: wrap;\n font-size: 11px;\n color: var(--mfb-text-muted);\n}\n.verification-shots {\n display: flex;\n gap: 8px;\n flex-wrap: wrap;\n}\n.verification-shots .report-detail-screenshot { width: 96px; flex: 0 0 auto; }\n.verification-shots .report-detail-screenshot img { max-height: 96px; }\n\n/* KPI strip — four pills above the My Reports list. Each is clickable\n to filter the rows below. Borrowed from thePnr's FeedbackKPICards. */\n.kpi-strip {\n display: grid;\n grid-template-columns: repeat(4, 1fr);\n gap: 6px;\n}\n.kpi-cell {\n appearance: none;\n background: var(--mfb-bg);\n border: 1px solid var(--mfb-border);\n border-radius: var(--mfb-radius);\n padding: 10px 10px 10px 14px;\n cursor: pointer;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 2px;\n font-family: inherit;\n color: inherit;\n position: relative;\n overflow: hidden;\n transition:\n background 140ms ease,\n border-color 140ms ease,\n transform 100ms ease,\n box-shadow 140ms ease;\n}\n.kpi-cell::before {\n /* Vertical tone accent — matches the Board KPI cards' visual rhythm so\n * the two surfaces feel like they belong to the same product. v0.12\n * polish pass. */\n content: '';\n position: absolute;\n top: 0;\n left: 0;\n width: 3px;\n height: 100%;\n background: var(--mfb-border);\n transition: background 140ms ease;\n}\n.kpi-cell--new::before { background: #3b82f6; }\n.kpi-cell--in_progress::before { background: #f59e0b; }\n.kpi-cell--awaiting_validation::before { background: #a855f7; }\n.kpi-cell--resolved::before { background: #10b981; }\n.kpi-cell:hover {\n background: var(--mfb-surface);\n transform: translateY(-1px);\n box-shadow: var(--mfb-shadow);\n}\n.kpi-cell.is-active {\n border-color: var(--mfb-accent);\n background: rgba(59, 130, 246, 0.08);\n}\n.kpi-cell.is-active.kpi-cell--in_progress { border-color: #d97706; background: rgba(251, 191, 36, 0.10); }\n.kpi-cell.is-active.kpi-cell--awaiting_validation { border-color: #9333ea; background: rgba(168, 85, 247, 0.10); }\n.kpi-cell.is-active.kpi-cell--resolved { border-color: #059669; background: rgba(16, 185, 129, 0.10); }\n.kpi-value {\n font-size: 18px;\n font-weight: 700;\n font-variant-numeric: tabular-nums;\n line-height: 1;\n}\n.kpi-label {\n font-size: 10px;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--mfb-text-muted);\n}\n\n/* Changelog (\"This week\") — week-grouped resolved-report timeline.\n Borrowed from thePnr's FeedbackChangelogView; the header per group\n reads \"● Week of {date} ━━━ {n} resolved\". */\n.changelog-groups {\n display: flex;\n flex-direction: column;\n gap: 12px;\n animation: mfb-board-in 280ms ease-out 40ms both;\n}\n@media (prefers-reduced-motion: reduce) {\n .changelog-groups { animation: none; }\n}\n.changelog-group {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n.changelog-group-header {\n display: grid;\n grid-template-columns: auto auto 1fr auto;\n gap: 6px;\n align-items: center;\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--mfb-text-muted);\n}\n.changelog-group-marker {\n color: var(--mfb-accent);\n font-size: 14px;\n line-height: 1;\n}\n.changelog-group-label { font-weight: 600; }\n.changelog-group-rule {\n height: 1px;\n background: var(--mfb-border);\n align-self: center;\n}\n.changelog-group-count {\n font-variant-numeric: tabular-nums;\n font-weight: 600;\n}\n\n/* ---- v0.12: Board tab + expanded modal (\"transport\" feel) ------------ */\n\n/* When the Board tab is active, the modal grows toward near-fullscreen\n * on desktop and full-screen on mobile. The CSS transition gives the\n * sense of clicking through to a real page rather than switching tabs.\n * Width/height/max-* are animated together; padding too, since the\n * board's denser layout needs less of the modal's default padding. */\n.modal.is-expanded {\n /* Margin around the expanded board view. --mfb-space-7 (48px) matches\n * the breathing room of the default modal — 24px top + 24px bottom +\n * 24px on each side — so the panel reads as \"near-fullscreen with a\n * visible frame\" instead of \"the whole viewport, content cut off\".\n *\n * The base .modal rule uses the browser default content-box, so\n * without an override the 24px padding would be ADDED to the\n * calc(100vh - 48px) height — the rendered box would exactly equal\n * the viewport again, defeating the margin. Force border-box so\n * padding lives INSIDE the height we set. */\n box-sizing: border-box;\n width: min(1280px, calc(100vw - var(--mfb-space-7)));\n max-height: calc(100vh - var(--mfb-space-7));\n height: calc(100vh - var(--mfb-space-7));\n padding: var(--mfb-space-5);\n transition:\n width 320ms cubic-bezier(0.22, 1, 0.36, 1),\n height 320ms cubic-bezier(0.22, 1, 0.36, 1),\n max-height 320ms cubic-bezier(0.22, 1, 0.36, 1),\n padding 320ms cubic-bezier(0.22, 1, 0.36, 1);\n}\n@media (prefers-reduced-motion: reduce) {\n .modal.is-expanded { transition: none; }\n}\n.backdrop.is-expanded { /* hook for any backdrop-level overrides */ }\n\n@media (max-width: 640px) {\n /* On mobile the sheet still snaps to the bottom edge and stretches\n * almost the full viewport — 24px gap from the top is enough to\n * convey \"this is a panel, not a takeover\", but we don't pull margin\n * off the sides where users expect the sheet to fill the viewport. */\n .modal.is-expanded {\n width: 100vw;\n height: calc(100vh - var(--mfb-space-5));\n border-radius: var(--mfb-radius-lg) var(--mfb-radius-lg) 0 0;\n }\n}\n\n/* Decorative purple accent on the Board tab button so users discover\n * it's a different surface — subtle, not loud. */\n.tab-button--board.is-active {\n color: #6b21a8;\n box-shadow: inset 0 -2px 0 0 #a855f7;\n}\n.tab-button--board:not(.is-active):hover {\n color: #6b21a8;\n}\n\n/* ---- Board layout ---- */\n\n.board-view {\n display: flex;\n flex-direction: column;\n gap: var(--mfb-space-4);\n flex: 1 1 auto;\n min-height: 0; /* allow inner panes to scroll */\n animation: mfb-board-in 280ms ease-out 40ms both;\n}\n@keyframes mfb-board-in {\n from { opacity: 0; transform: translateY(8px); }\n to { opacity: 1; transform: translateY(0); }\n}\n@media (prefers-reduced-motion: reduce) {\n .board-view { animation: none; }\n}\n\n.board-header {\n display: flex;\n flex-direction: column;\n gap: var(--mfb-space-4);\n}\n.board-header-title {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n.board-header-emoji {\n font-size: 26px;\n line-height: 1;\n}\n.board-header-h {\n margin: 0;\n font-size: var(--mfb-text-2xl);\n font-weight: 600;\n letter-spacing: -0.01em;\n}\n.board-header-sub {\n margin: 2px 0 0 0;\n color: var(--mfb-text-muted);\n font-size: var(--mfb-text-sm);\n}\n\n/* KPI strip — 4 cards, equal width, subtle tone-tints by bucket. */\n.board-kpi-strip {\n display: grid;\n grid-template-columns: repeat(4, 1fr);\n gap: 12px;\n}\n.board-kpi-card {\n border: 1px solid var(--mfb-border);\n border-radius: var(--mfb-radius);\n padding: 14px 16px;\n background: var(--mfb-bg);\n display: flex;\n flex-direction: column;\n gap: 4px;\n position: relative;\n overflow: hidden;\n transition: transform 160ms ease, box-shadow 160ms ease;\n}\n.board-kpi-card::after {\n content: '';\n position: absolute;\n top: 0;\n left: 0;\n width: 3px;\n height: 100%;\n background: var(--mfb-border);\n transition: background 160ms ease;\n}\n.board-kpi-card:hover {\n transform: translateY(-1px);\n box-shadow: var(--mfb-shadow);\n}\n.board-kpi-card.tone-new::after { background: #3b82f6; }\n.board-kpi-card.tone-progress::after { background: #f59e0b; }\n.board-kpi-card.tone-validation::after { background: #a855f7; }\n.board-kpi-card.tone-rate::after { background: #10b981; }\n.board-kpi-value {\n font-size: 28px;\n font-weight: 700;\n line-height: 1;\n letter-spacing: -0.02em;\n color: var(--mfb-text);\n font-variant-numeric: tabular-nums;\n}\n.board-kpi-label {\n font-size: var(--mfb-text-xs);\n color: var(--mfb-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.04em;\n font-weight: 500;\n}\n.board-kpi-strip.is-loading .board-kpi-value {\n opacity: 0.35;\n}\n@media (max-width: 720px) {\n .board-kpi-strip { grid-template-columns: repeat(2, 1fr); }\n}\n\n/* Filter row */\n.board-filters {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n.board-filter-select,\n.board-filter-search {\n padding: 6px 10px;\n border: 1px solid var(--mfb-border);\n border-radius: var(--mfb-radius);\n background: var(--mfb-bg);\n color: var(--mfb-text);\n font: inherit;\n font-size: var(--mfb-text-sm);\n height: 32px;\n transition: border-color 120ms ease, box-shadow 120ms ease;\n}\n.board-filter-select:focus-visible,\n.board-filter-search:focus-visible {\n outline: none;\n border-color: var(--mfb-accent);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mfb-accent) 18%, transparent);\n}\n.board-filter-search { min-width: 180px; flex: 1 1 220px; }\n.board-filter-toggle {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: var(--mfb-text-sm);\n color: var(--mfb-text);\n cursor: pointer;\n user-select: none;\n}\n.board-filter-toggle input { margin: 0; }\n.board-filter-clear {\n background: transparent;\n border: 0;\n color: var(--mfb-text-muted);\n font: inherit;\n font-size: var(--mfb-text-sm);\n display: inline-flex;\n align-items: center;\n gap: 4px;\n cursor: pointer;\n padding: 4px 6px;\n border-radius: var(--mfb-radius);\n transition: background 120ms ease, color 120ms ease;\n}\n.board-filter-clear:hover {\n color: var(--mfb-text);\n background: var(--mfb-surface);\n}\n\n/* Scope segmented control — [This page | All pages], front of the filter row. */\n.board-scope {\n display: inline-flex;\n border: 1px solid var(--mfb-border);\n border-radius: var(--mfb-radius);\n overflow: hidden;\n flex: none;\n}\n.board-scope-btn {\n border: none;\n background: none;\n padding: 6px 10px;\n font: inherit;\n font-size: var(--mfb-text-sm);\n color: var(--mfb-text-muted);\n cursor: pointer;\n}\n.board-scope-btn.is-active {\n background: color-mix(in srgb, var(--mfb-accent) 14%, transparent);\n color: var(--mfb-accent);\n font-weight: 600;\n}\n.board-scope-btn:focus-visible {\n outline: 2px solid var(--mfb-accent);\n outline-offset: -2px;\n}\n\n/* Send-tab page-activity strip — one line, muted, non-blocking. */\n.page-strip {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--mfb-space-3);\n margin: var(--mfb-space-3) 0 0;\n padding: var(--mfb-space-2) var(--mfb-space-3);\n border-radius: var(--mfb-radius);\n background: var(--mfb-surface-2);\n color: var(--mfb-text-muted);\n font-size: var(--mfb-text-sm);\n}\n.page-strip-view {\n border: none;\n background: none;\n padding: 0;\n color: var(--mfb-accent);\n font-size: var(--mfb-text-sm);\n font-weight: 600;\n cursor: pointer;\n}\n.page-strip-view:hover {\n text-decoration: underline;\n}\n\n/* Master/detail layout — two-column at >=900px, stacked below. */\n.board-body {\n display: grid;\n grid-template-columns: minmax(280px, 360px) 1fr;\n gap: var(--mfb-space-4);\n flex: 1 1 auto;\n min-height: 0;\n}\n@media (max-width: 900px) {\n .board-body { grid-template-columns: 1fr; }\n .board-detail-wrap:not(.has-selection) { display: none; }\n}\n\n.board-list-wrap {\n display: flex;\n flex-direction: column;\n gap: 6px;\n min-height: 0;\n overflow: hidden;\n}\n/* Stale-while-revalidate: a query change keeps the previous rows visible\n * (locally pre-filtered where the row data allows) and dims them while the\n * server confirms — the instant local response is the #352 \"never inert\"\n * guarantee, the dim is the \"still confirming\" affordance. */\n.board-view.is-stale .board-list,\n.board-view.is-stale .board-list-count,\n.board-view.is-stale .board-kpi-strip {\n opacity: 0.55;\n transition: opacity 0.15s ease;\n}\n.board-load-more {\n margin: 4px auto 8px;\n flex: 0 0 auto;\n}\n.board-list-count {\n font-size: var(--mfb-text-xs);\n color: var(--mfb-text-muted);\n padding: 0 4px;\n}\n.board-list {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 8px;\n overflow-y: auto;\n flex: 1 1 auto;\n scrollbar-width: thin;\n scrollbar-gutter: stable;\n}\n\n.board-row {\n display: flex;\n flex-direction: column;\n gap: 6px;\n width: 100%;\n text-align: left;\n padding: 12px 14px;\n border-radius: var(--mfb-radius);\n background: var(--mfb-bg);\n border: 1px solid var(--mfb-border);\n cursor: pointer;\n font: inherit;\n color: inherit;\n /* Stagger reveal on first paint. */\n animation: mfb-row-in 280ms ease-out both;\n transition: border-color 140ms ease, background 140ms ease, transform 80ms ease;\n}\n.board-row:hover {\n border-color: var(--mfb-border-strong);\n background: var(--mfb-surface);\n}\n.board-row:active { transform: scale(0.997); }\n.board-row.is-selected {\n border-color: var(--mfb-accent);\n background: color-mix(in srgb, var(--mfb-accent) 6%, var(--mfb-bg));\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mfb-accent) 18%, transparent);\n}\n@keyframes mfb-row-in {\n from { opacity: 0; transform: translateY(4px); }\n to { opacity: 1; transform: translateY(0); }\n}\n@media (prefers-reduced-motion: reduce) {\n .board-row { animation: none; }\n}\n/* Stagger first 6 rows so the list reveals top-down. */\n.board-row:nth-child(1) { animation-delay: 0ms; }\n.board-row:nth-child(2) { animation-delay: 40ms; }\n.board-row:nth-child(3) { animation-delay: 80ms; }\n.board-row:nth-child(4) { animation-delay: 120ms; }\n.board-row:nth-child(5) { animation-delay: 160ms; }\n.board-row:nth-child(6) { animation-delay: 200ms; }\n\n.board-row-badges {\n display: flex;\n gap: 6px;\n align-items: center;\n flex-wrap: wrap;\n}\n.board-row-description {\n font-size: var(--mfb-text-sm);\n color: var(--mfb-text);\n line-height: 1.4;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n.board-row-meta {\n display: flex;\n align-items: center;\n gap: 10px;\n font-size: var(--mfb-text-xs);\n color: var(--mfb-text-muted);\n flex-wrap: wrap;\n}\n.board-row-author { font-weight: 500; color: var(--mfb-text); }\n.board-row-comments {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n font-variant-numeric: tabular-nums;\n}\n.board-row-time { margin-left: auto; font-variant-numeric: tabular-nums; }\n\n/* Status / severity badges shared with the detail panel. The tones\n * borrow PNR's badge palette — calm pastels that don't fight content. */\n.board-row-badges .badge {\n font-size: 11px;\n font-weight: 500;\n letter-spacing: 0.02em;\n padding: 2px 8px;\n border-radius: 999px;\n background: #f3f4f6;\n color: #374151;\n text-transform: lowercase;\n}\n.badge.status-new { background: #dbeafe; color: #1e40af; }\n.badge.status-in_progress { background: #fef3c7; color: #92400e; }\n.badge.status-awaiting_validation { background: #ede9fe; color: #6b21a8; }\n.badge.status-closed { background: #d1fae5; color: #065f46; }\n.badge.status-rejected { background: #fee2e2; color: #991b1b; }\n.badge.status-duplicate { background: #f3f4f6; color: #4b5563; }\n.badge.status-wontfix { background: #f3f4f6; color: #4b5563; }\n.badge.severity-blocker { background: #fee2e2; color: #991b1b; }\n.badge.severity-high { background: #fee2e2; color: #b91c1c; }\n.badge.severity-medium { background: #fef3c7; color: #92400e; }\n.badge.severity-low { background: #e0f2fe; color: #075985; }\n\n/* Detail pane (within Board). Inherits from .report-detail.variant-board */\n.board-detail-wrap {\n border: 1px solid var(--mfb-border);\n border-radius: var(--mfb-radius);\n background: var(--mfb-bg);\n overflow-y: auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n.board-detail-empty {\n flex: 1 1 auto;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n color: var(--mfb-text-muted);\n padding: var(--mfb-space-5);\n text-align: center;\n font-size: var(--mfb-text-sm);\n}\n.board-detail-empty svg { opacity: 0.6; }\n\n.report-detail.variant-board { padding: 16px 18px; }\n.report-detail-header--board {\n align-items: center;\n}\n.report-detail-back {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n height: 28px;\n padding: 0 10px;\n}\n\n/* Empty + loading + error states */\n.board-empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 8px;\n text-align: center;\n padding: var(--mfb-space-5);\n color: var(--mfb-text-muted);\n flex: 1 1 auto;\n}\n.board-empty h3 {\n margin: 0;\n font-size: var(--mfb-text-base);\n color: var(--mfb-text);\n}\n.board-empty p {\n margin: 0;\n font-size: var(--mfb-text-sm);\n}\n.board-error {\n padding: 12px 14px;\n border-radius: var(--mfb-radius);\n background: #fef2f2;\n color: #991b1b;\n font-size: var(--mfb-text-sm);\n}\n\n/* Skeleton rows pulse subtly so the user knows something's coming. */\n.board-list-skeleton .skeleton-row {\n cursor: default;\n pointer-events: none;\n}\n.skeleton-line {\n height: 12px;\n background: linear-gradient(\n 90deg,\n var(--mfb-surface) 0%,\n color-mix(in srgb, var(--mfb-surface) 60%, var(--mfb-border)) 50%,\n var(--mfb-surface) 100%\n );\n background-size: 200% 100%;\n border-radius: 4px;\n animation: mfb-skeleton 1400ms ease-in-out infinite;\n}\n.skeleton-badges { width: 60%; height: 14px; }\n.skeleton-text { width: 90%; }\n.skeleton-text.short { width: 60%; }\n@keyframes mfb-skeleton {\n from { background-position: 100% 0; }\n to { background-position: -100% 0; }\n}\n@media (prefers-reduced-motion: reduce) {\n .skeleton-line { animation: none; }\n}\n\n/* Hover peek panel — desktop-only ambient surface listing this page's\n open reports above the FAB (spec §3.2). Non-modal; never renders while\n the modal itself is open. */\n.page-peek {\n position: absolute;\n right: 0;\n bottom: 60px;\n width: 300px;\n padding: var(--mfb-space-3);\n border: 1px solid var(--mfb-border);\n border-radius: var(--mfb-radius-lg);\n background: var(--mfb-surface);\n color: var(--mfb-text);\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);\n font-size: var(--mfb-text-sm);\n}\n.page-peek-title {\n font-weight: 700;\n margin-bottom: var(--mfb-space-2);\n color: var(--mfb-text-muted);\n text-transform: uppercase;\n font-size: var(--mfb-text-xs);\n letter-spacing: 0.04em;\n}\n.page-peek-row {\n display: flex;\n align-items: center;\n gap: var(--mfb-space-2);\n width: 100%;\n padding: var(--mfb-space-2);\n border: none;\n border-radius: var(--mfb-radius);\n background: none;\n color: var(--mfb-text);\n text-align: left;\n cursor: pointer;\n}\n.page-peek-row:hover {\n background: var(--mfb-surface-2);\n}\n.page-peek-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n flex: none;\n}\n.page-peek-dot.status-new { background: #1e40af; }\n.page-peek-dot.status-in_progress { background: #92400e; }\n.page-peek-dot.status-awaiting_validation { background: #6b21a8; }\n.page-peek-dot.status-closed { background: #065f46; }\n.page-peek-dot.status-rejected { background: #991b1b; }\n.page-peek-desc {\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n.page-peek-footer {\n display: flex;\n justify-content: space-between;\n margin-top: var(--mfb-space-2);\n padding-top: var(--mfb-space-2);\n border-top: 1px solid var(--mfb-border);\n}\n.page-peek-viewall,\n.page-peek-send {\n border: none;\n background: none;\n padding: 0;\n cursor: pointer;\n color: var(--mfb-accent);\n font-weight: 600;\n font-size: var(--mfb-text-sm);\n}\n.page-peek-skeleton div {\n height: 14px;\n border-radius: 4px;\n background: var(--mfb-surface-2);\n margin: var(--mfb-space-2) 0;\n}\n`\n","import { createApiClient } from './api/client'\nimport { installCapture } from './capture'\nimport { resolveStrings } from './widget/i18n'\nimport { mountWidget } from './widget/mount'\nimport { currentPagePath } from './widget/currentPage'\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\n/** Default offset for the secondary QA FAB: center the 24px QA dot above the\n * 48px feedback FAB (bottom:24 right:24). → { right: 36, bottom: 84 }. */\nfunction defaultQaPosition(): { right: number; bottom: number } {\n return { right: 24 + (48 - 24) / 2, bottom: 24 + 48 + 12 }\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 let user: UserIdentity | undefined = config.user\n let metadata: Record<string, unknown> = config.metadata ?? {}\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 // v0.13: the API client reads this on every request so a late\n // `identify({userHash, exp, ...})` lights up signed headers\n // immediately on subsequent calls. Returns null when the host\n // hasn't supplied a signature (legacy unsigned path).\n getSignedIdentity: () => {\n if (!user || !user.userHash || !user.exp || !user.email) return null\n return {\n userHash: user.userHash,\n exp: user.exp,\n email: user.email,\n }\n },\n })\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 /** Screenshot(s) the user explicitly attached — via dropzone, paste,\n * file picker, or the annotator. `screenshots` (attach order) is what\n * the form emits; the single `screenshot` stays accepted for the\n * public fb.submit() API. v0.12 dropped html2canvas-on-submit;\n * v0.7.1 dropped getDisplayMedia. If the user didn't attach anything,\n * the report ships without a screenshot. */\n screenshot?: Blob\n screenshots?: Blob[]\n /** Capture-method label for the payload. Always \"manual\" now (v0.7.1);\n * the union type stays open against the backend schema, which still\n * recognizes legacy values on existing reports. */\n capture_method?: 'manual'\n }) {\n // Auto-error reports never carry a screenshot — the captured ring\n // buffer (errors, console, network) carries the signal, and the DOM\n // is typically in an inconsistent state when a JS error fires anyway.\n const attached = values.screenshots?.length\n ? values.screenshots\n : values.screenshot\n ? [values.screenshot]\n : undefined\n const manualScreenshots = values.synthetic ? undefined : attached\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 //\n // SECURITY: identify() may carry `userHash` + `exp` (the host's HMAC\n // envelope used to attest the user's identity to the backend). That\n // envelope is a bearer-style auth token until it expires; leaking it\n // into technical_context surfaces it on the operator dashboard and to\n // anyone who can read the report. Strip it before embedding.\n if (user) {\n const { userHash: _hash, exp: _exp, ...safeUser } = user\n technical_context.user = safeUser\n }\n if (metadata && Object.keys(metadata).length > 0) {\n technical_context.metadata = { ...metadata }\n }\n const captureMethod: ReportPayload['capture_method'] = manualScreenshots?.length\n ? (values.capture_method ?? 'manual')\n : 'none'\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: captureMethod,\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 (manualScreenshots?.length) {\n payload.screenshots = manualScreenshots\n // Mirror the first into the legacy field for beforeSend hooks that\n // still read payload.screenshot.\n payload.screenshot = manualScreenshots[0]!\n }\n if (values.synthetic) payload.synthetic = true\n // current-page feedback: resolve path via host override or pathname.\n const pagePath = currentPagePath(config)\n if (pagePath) payload.page_path = pagePath\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 mountOptions: Parameters<typeof mountWidget>[0] = ({\n host,\n strings,\n showFAB: config.showFAB ?? true,\n // Return the created report so the modal can acknowledge it by #seq (#82).\n onSubmit: (values) => 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 // Phase 4: the manifest tells the loader whether this project gates the\n // widget per end-user; the loader forwards it as `requiresVisibilityCheck`.\n requiresVisibilityCheck: config.requiresVisibilityCheck ?? false,\n // Send the identified email alongside the id so an email-based allowlist\n // can match (the backend matches external_id OR email). Read from `user`\n // live so identity set after createFeedback() is reflected.\n checkVisibility: (externalId) => api.checkVisibility(externalId, user?.email),\n // Thread the host's getCurrentPage override so the Board chip resolves\n // the page identically to what the submit side records (§ currentPage).\n ...(config.getCurrentPage !== undefined && { getCurrentPage: config.getCurrentPage }),\n // Opt-in: FAB opens to the page-scoped Board (default off → Send-first).\n ...(config.openToCurrentPageFeedback !== undefined && {\n openToCurrentPageFeedback: config.openToCurrentPageFeedback,\n }),\n ...(config.showPageActivity !== undefined && {\n showPageActivity: config.showPageActivity,\n }),\n })\n const handle = mountWidget(mountOptions)\n\n // Roadmap #17 — server-controlled behavior for BUNDLED hosts. Loader\n // installs inject behavior flags via config before we get here; when\n // the host left a flag unset and no loader did either, self-fetch the\n // (60s-cached) manifest and honor the operator's server-side toggle.\n // Host constructor values always win; failures leave defaults.\n // No FAB → nothing ever opens → the flag is irrelevant; skipping also\n // keeps headless/test instances from firing surprise requests.\n if (config.openToCurrentPageFeedback === undefined && config.showFAB !== false) {\n const behaviorFetch = config.fetchImpl ?? globalThis.fetch\n void (async () => {\n try {\n const r = await behaviorFetch(\n `${config.endpoint}/api/feedback/v1/widget-manifest/?pk=${encodeURIComponent(config.apiKey)}`,\n )\n if (!r.ok) return\n const m = (await r.json()) as {\n config?: { behavior?: { openToCurrentPageFeedback?: unknown } }\n }\n const v = m?.config?.behavior?.openToCurrentPageFeedback\n if (\n typeof v === 'boolean' &&\n mountOptions.openToCurrentPageFeedback === undefined\n ) {\n mountOptions.openToCurrentPageFeedback = v\n }\n } catch {\n /* behavior stays default — never breaks boot */\n }\n })()\n }\n\n // Opt-in QA Meter second FAB. Lazily imported so the QA bytes stay out of\n // the main bundle for consumers who don't enable it. The host serves its\n // own artifact via qaMeter.source.\n let qaHandle: { dispose(): void } | undefined\n let qaDisposed = false\n if (config.qaMeter?.source) {\n const qa = config.qaMeter\n const qaLocale: 'fr' | 'en' =\n qa.locale ?? (String(locale ?? '').toLowerCase().startsWith('fr') ? 'fr' : 'en')\n void import('./qa-meter').then(({ createQaMeter }) => {\n if (qaDisposed) return // shutdown() ran before the import resolved\n qaHandle = createQaMeter({\n source: qa.source,\n size: qa.size ?? 'sm',\n position: qa.position ?? defaultQaPosition(),\n locale: qaLocale,\n ...(qa.getCurrentPage && { getCurrentPage: qa.getCurrentPage }),\n })\n })\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 ...(partial.screenshots !== undefined && { screenshots: partial.screenshots }),\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 qaDisposed = true\n qaHandle?.dispose()\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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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":["patched","patched","useCallback","useEffect","useRef","useState","useEffect","useState","useState","jsx","jsxs","jsx","jsxs","useState","Fragment","jsx","jsxs","POLL_MS","useState","useEffect","h","useEffect","useMemo","useRef","useState","jsx","jsxs","formatRelative","jsx","jsxs","POLL_MS","useState","useRef","useEffect","useMemo","jsx","jsxs","useEffect","useRef","useState","useEffect","useRef","useState","jsx","jsxs","h","jsx","jsxs","TYPES","SEVERITIES","useState","useRef","useEffect","useEffect","useRef","useState","jsx","jsxs","jsx","jsxs","POLL_MS","useState","useRef","useEffect","useEffect","useRef","jsx","jsxs","jsx","jsxs","useEffect","useState","jsx","jsxs","useState","useEffect","useEffect","useState","TTL_MS","cache","cache","TTL_MS","useState","useEffect","Fragment","jsx","jsxs","useCallback","useState","useEffect","useRef"]}
|