@signalsupport/sdk 0.0.0-stage → 0.7.1

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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +373 -2
  3. package/dist/chunks/app-7692BILt.js +2 -0
  4. package/dist/chunks/app-7692BILt.js.map +1 -0
  5. package/dist/chunks/constants-ClGbEpjT.js +2 -0
  6. package/dist/chunks/constants-ClGbEpjT.js.map +1 -0
  7. package/dist/chunks/support-x7N-k02b.js +4 -0
  8. package/dist/chunks/support-x7N-k02b.js.map +1 -0
  9. package/dist/chunks/widget-Bxsz5StK.js +2 -0
  10. package/dist/chunks/widget-Bxsz5StK.js.map +1 -0
  11. package/dist/client.d.ts +99 -0
  12. package/dist/constants.d.ts +2 -0
  13. package/dist/errors.d.ts +25 -0
  14. package/dist/evidence.d.ts +87 -0
  15. package/dist/iife.d.ts +12 -0
  16. package/dist/index.d.ts +21 -0
  17. package/dist/index.js +1 -0
  18. package/dist/model.d.ts +201 -0
  19. package/dist/react.d.ts +26 -0
  20. package/dist/react.js +2 -0
  21. package/dist/react.js.map +1 -0
  22. package/dist/reporter.d.ts +14 -0
  23. package/dist/server.d.ts +54 -0
  24. package/dist/server.js +2 -0
  25. package/dist/server.js.map +1 -0
  26. package/dist/session.d.ts +29 -0
  27. package/dist/signal.iife.js +4 -0
  28. package/dist/signal.iife.js.map +1 -0
  29. package/dist/support.d.ts +108 -0
  30. package/dist/trail.d.ts +27 -0
  31. package/dist/widget/app.d.ts +31 -0
  32. package/dist/widget/copy.d.ts +104 -0
  33. package/dist/widget/ctx.d.ts +75 -0
  34. package/dist/widget/icons.d.ts +38 -0
  35. package/dist/widget/index.d.ts +111 -0
  36. package/dist/widget/index.js +1 -0
  37. package/dist/widget/parts.d.ts +28 -0
  38. package/dist/widget/pick.d.ts +30 -0
  39. package/dist/widget/screens/faq.d.ts +8 -0
  40. package/dist/widget/screens/feedback.d.ts +11 -0
  41. package/dist/widget/screens/home.d.ts +12 -0
  42. package/dist/widget/screens/report.d.ts +9 -0
  43. package/dist/widget/screens/reports.d.ts +26 -0
  44. package/dist/widget/store.d.ts +31 -0
  45. package/dist/widget/styles.d.ts +10 -0
  46. package/dist/widget/theme.d.ts +57 -0
  47. package/dist/widget/ui.d.ts +58 -0
  48. package/package.json +73 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"support-x7N-k02b.js","names":[],"sources":["../../src/evidence.ts","../../src/session.ts","../../src/client.ts","../../src/model.ts","../../src/reporter.ts","../../src/trail.ts","../../src/support.ts"],"sourcesContent":["/**\n * What a report can carry besides words, and the reporter's say over each part.\n *\n * # CONSENT IS A DEFAULT YOU CAN SEE, NOT ONE BURIED IN A DOC\n *\n * Screenshot, system, browser, network and the action trail are ON; locale is\n * OFF, because a timezone plus a language narrows a person down a great deal\n * and is never what makes a report actionable. Anything switched off is sent\n * by name in `withheld`, because the console renders \"the reporter declined\n * this\" differently from \"nobody collected this\", and only one of the two is\n * worth asking about.\n */\n\nexport interface Consent {\n /** A screen frame. Only ever one the reporter chose to take. */\n screenshot: boolean;\n /** Platform, device memory, hardware concurrency. */\n system: boolean;\n /** User agent, viewport, pixel ratio. */\n browser: boolean;\n /** Connection type and rough speed, where the browser exposes it. */\n network: boolean;\n /** Timezone and language. OFF by default: it is the one that locates a person. */\n locale: boolean;\n /** The last few things that happened, as the product recorded them. */\n trail: boolean;\n /** The product's own diagnostics (a GPU report, the open project). */\n extra: boolean;\n}\n\n/** The published defaults, exactly. */\nexport const DEFAULT_CONSENT: Readonly<Consent> = Object.freeze({\n screenshot: true,\n system: true,\n browser: true,\n network: true,\n locale: false,\n trail: true,\n extra: true,\n});\n\nexport function resolveConsent(...layers: (Partial<Consent> | undefined)[]): Consent {\n return layers.reduce<Consent>((acc, layer) => (layer ? { ...acc, ...layer } : acc), { ...DEFAULT_CONSENT });\n}\n\ntype Nav = Navigator & {\n userAgentData?: { platform?: string };\n deviceMemory?: number;\n connection?: { effectiveType?: string; downlink?: number; rtt?: number };\n};\n\nconst nav = (): Nav | undefined => (typeof navigator === 'undefined' ? undefined : (navigator as Nav));\nconst win = (): Window | undefined => (typeof window === 'undefined' ? undefined : window);\n\nexport function readSystem(): Record<string, unknown> | undefined {\n const n = nav();\n if (!n) return undefined;\n return { platform: n.userAgentData?.platform ?? n.platform, cores: n.hardwareConcurrency, memory: n.deviceMemory };\n}\n\nexport function readBrowser(): Record<string, unknown> | undefined {\n const n = nav();\n const w = win();\n if (!n || !w) return undefined;\n return { userAgent: n.userAgent, viewport: { width: w.innerWidth, height: w.innerHeight }, pixelRatio: w.devicePixelRatio };\n}\n\nexport function readNetwork(): Record<string, unknown> | undefined {\n const c = nav()?.connection;\n return c ? { effectiveType: c.effectiveType, downlink: c.downlink, rtt: c.rtt } : undefined;\n}\n\nexport function readLocale(): Record<string, unknown> | undefined {\n const n = nav();\n return n ? { language: n.language, timezone: Intl.DateTimeFormat().resolvedOptions().timeZone } : undefined;\n}\n\n/** \"Windows · 8 cores · 16 GB\", for showing the reporter what is about to be sent. */\nexport function describeSystem(s = readSystem()): string {\n if (!s) return 'Not available here';\n return [s.platform, s.cores ? `${String(s.cores)} cores` : null, s.memory ? `${String(s.memory)} GB` : null]\n .filter(Boolean)\n .join(' · ');\n}\n\nexport function describeBrowser(b = readBrowser()): string {\n if (!b) return 'Not available here';\n const ua = String(b.userAgent ?? '');\n const name =\n /Edg\\/(\\d+)/.exec(ua)?.[1] ? `Edge ${/Edg\\/(\\d+)/.exec(ua)![1]}` :\n /Firefox\\/(\\d+)/.exec(ua)?.[1] ? `Firefox ${/Firefox\\/(\\d+)/.exec(ua)![1]}` :\n /Chrome\\/(\\d+)/.exec(ua)?.[1] ? `Chrome ${/Chrome\\/(\\d+)/.exec(ua)![1]}` :\n /Version\\/(\\d+).*Safari/.exec(ua)?.[1] ? `Safari ${/Version\\/(\\d+)/.exec(ua)![1]}` :\n 'Browser';\n const vp = b.viewport as { width: number; height: number } | undefined;\n return vp ? `${name} · ${vp.width}×${vp.height}` : name;\n}\n\n/** Supplied by the host, collected only when the reporter reaches the Attach step. */\nexport type Diagnostics = Record<string, () => unknown | Promise<unknown>>;\n\nexport async function readDiagnostics(d: Diagnostics | undefined): Promise<Record<string, unknown> | undefined> {\n if (!d) return undefined;\n const out: Record<string, unknown> = {};\n for (const [name, read] of Object.entries(d)) {\n try {\n const v = await read();\n if (v !== undefined) out[name] = v;\n } catch (e) {\n // A machine that cannot describe itself must not block a report; that\n // machine is very often the one with the bug worth reporting.\n out[name] = { unavailable: e instanceof Error ? e.message : String(e) };\n }\n }\n return Object.keys(out).length ? out : undefined;\n}\n\nexport interface EvidenceInput {\n /** A frame the reporter chose to take, as a data: URL. */\n screenshot?: string;\n /** A key in the product's own storage, when it uploads frames itself. */\n screenshotKey?: string;\n trail?: unknown;\n extra?: Record<string, unknown>;\n}\n\n/** Collect what consent allows, and name what it does not. */\nexport function collectEvidence(consent: Consent, supplied: EvidenceInput = {}): Record<string, unknown> {\n const withheld: string[] = [];\n const evidence: Record<string, unknown> = {};\n const take = (field: keyof Consent, key: string, value: () => unknown) => {\n if (!consent[field]) {\n withheld.push(field);\n return;\n }\n const v = value();\n if (v !== undefined) evidence[key] = v;\n };\n take('system', 'system', readSystem);\n take('browser', 'browser', readBrowser);\n take('network', 'network', readNetwork);\n take('locale', 'locale', readLocale);\n take('trail', 'trail', () => supplied.trail);\n take('extra', 'extra', () => supplied.extra);\n if (!consent.screenshot) {\n withheld.push('screenshot');\n } else {\n if (supplied.screenshot) evidence.screenshot = supplied.screenshot;\n if (supplied.screenshotKey) evidence.screenshot_key = supplied.screenshotKey;\n }\n evidence.withheld = withheld;\n return evidence;\n}\n\n/* -------------------------------------------------------- one screen frame */\n\nexport const CAPTURE_MAX_EDGE = 1600;\nexport const CAPTURE_QUALITY = 0.72;\n/** Below the service's 2 MB cap with room for base64. */\nexport const CAPTURE_MAX_BYTES = 1_400_000;\n\nexport type CaptureResult =\n | { ok: true; dataUrl: string; width: number; height: number; bytes: number; mime: string }\n | { ok: false; reason: 'unsupported' | 'cancelled' | 'failed'; message: string };\n\n/**\n * Ask the browser for ONE frame of the screen, chosen by the reporter.\n *\n * `getDisplayMedia` always asks the person; this tab is offered first. The\n * stream is stopped as soon as one frame is read, so the \"sharing\" indicator\n * is up for a moment, not for the rest of the session. Downscaled to at most\n * 1600px and re-encoded until it fits under the service's cap.\n *\n * `beforeCapture` / `afterCapture` let the panel step out of its own picture.\n */\n/** Resolve when `p` does, or after `ms` whatever happened (the caller checks what it got). */\nfunction within(p: Promise<unknown>, ms: number): Promise<void> {\n return new Promise((resolve) => {\n const t = setTimeout(resolve, ms);\n p.then(\n () => (clearTimeout(t), resolve()),\n () => (clearTimeout(t), resolve()),\n );\n });\n}\n\n/** One pixel in a corner, all but transparent, changing every frame until stopped. */\nfunction keepPainting(): () => void {\n if (typeof document === 'undefined' || typeof requestAnimationFrame === 'undefined') return () => {};\n const dot = document.createElement('div');\n dot.setAttribute('aria-hidden', 'true');\n dot.style.cssText = 'position:fixed;left:0;top:0;width:1px;height:1px;pointer-events:none;z-index:2147483647;';\n document.body.appendChild(dot);\n let on = true;\n let raf = 0;\n const step = () => {\n on = !on;\n dot.style.background = on ? 'rgba(0,0,0,0.01)' : 'rgba(0,0,0,0.02)';\n raf = requestAnimationFrame(step);\n };\n raf = requestAnimationFrame(step);\n return () => {\n cancelAnimationFrame(raf);\n dot.remove();\n };\n}\n\n/** Wait for `n` new frames from a playing video, or give up after a second (no rVFC, a still page). */\nfunction freshFrames(video: HTMLVideoElement, n: number): Promise<void> {\n const v = video as HTMLVideoElement & { requestVideoFrameCallback?: (cb: () => void) => number };\n if (typeof v.requestVideoFrameCallback !== 'function') return new Promise((r) => setTimeout(r, 150));\n return new Promise((resolve) => {\n const giveUp = setTimeout(resolve, 1000);\n let left = n;\n const tick = () => {\n if (--left <= 0) {\n clearTimeout(giveUp);\n resolve();\n } else v.requestVideoFrameCallback!(tick);\n };\n v.requestVideoFrameCallback!(tick);\n });\n}\n\n/** One frame, downscaled to at most 1600px and re-encoded until it fits under the service's cap. */\nfunction encodeFrame(source: CanvasImageSource, sourceWidth: number, sourceHeight: number): CaptureResult {\n const scale = Math.min(1, CAPTURE_MAX_EDGE / Math.max(sourceWidth, sourceHeight));\n const width = Math.max(1, Math.round(sourceWidth * scale));\n const height = Math.max(1, Math.round(sourceHeight * scale));\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n canvas.getContext('2d')!.drawImage(source, 0, 0, width, height);\n let quality = CAPTURE_QUALITY;\n let dataUrl = canvas.toDataURL('image/webp', quality);\n // Safari encodes webp as png; fall back to jpeg, which every browser has.\n if (!dataUrl.startsWith('data:image/webp')) dataUrl = canvas.toDataURL('image/jpeg', quality);\n while (approxBytes(dataUrl) > CAPTURE_MAX_BYTES && quality > 0.3) {\n quality -= 0.12;\n dataUrl = canvas.toDataURL(dataUrl.startsWith('data:image/webp') ? 'image/webp' : 'image/jpeg', quality);\n }\n const mime = dataUrl.slice(5, dataUrl.indexOf(';'));\n return { ok: true, dataUrl, width, height, bytes: approxBytes(dataUrl), mime };\n}\n\n/**\n * The host's own picture of its own view, in place of `getDisplayMedia`.\n *\n * For a product inside a desktop shell (WebView2, Electron), where the browser's\n * capture opens the shell's own permission dialog, unstyled and often wider than\n * the window. The shell renders its webview itself (`CapturePreview`,\n * `capturePage`) and hands back an image URL; there is no prompt, because it is\n * the product's own pixels taken when the person pressed Capture.\n *\n * Same hide-first contract as `captureScreen`, same encoding, bounded waits.\n */\nexport async function captureFrom(\n source: () => Promise<string>,\n hooks: { beforeCapture?: () => void; afterCapture?: () => void } = {},\n): Promise<CaptureResult> {\n try {\n hooks.beforeCapture?.();\n // Two frames so the page has painted without the bar before the host reads it.\n await new Promise<void>((r) => requestAnimationFrame(() => requestAnimationFrame(() => r())));\n let url = '';\n await within(source().then((u) => void (url = u)), 5000);\n if (!url.startsWith('data:image/') && !url.startsWith('blob:')) {\n throw new Error('The screen could not be captured.');\n }\n const img = new Image();\n img.src = url;\n await img.decode();\n return encodeFrame(img, img.naturalWidth, img.naturalHeight);\n } catch (e) {\n return { ok: false, reason: 'failed', message: e instanceof Error ? e.message : 'The screen could not be captured.' };\n } finally {\n hooks.afterCapture?.();\n }\n}\n\nexport async function captureScreen(hooks: { beforeCapture?: () => void; afterCapture?: () => void } = {}): Promise<CaptureResult> {\n const md = typeof navigator === 'undefined' ? undefined : navigator.mediaDevices;\n if (!md?.getDisplayMedia) {\n return { ok: false, reason: 'unsupported', message: 'This browser cannot capture the screen.' };\n }\n let stream: MediaStream;\n try {\n stream = await md.getDisplayMedia({\n video: { displaySurface: 'browser' } as MediaTrackConstraints,\n audio: false,\n // Chrome: offer this tab first and keep the picker from hiding it.\n preferCurrentTab: true,\n selfBrowserSurface: 'include',\n } as DisplayMediaStreamOptions);\n } catch (e) {\n const name = e instanceof Error ? e.name : '';\n return name === 'NotAllowedError' || name === 'AbortError'\n ? { ok: false, reason: 'cancelled', message: 'Capture was cancelled.' }\n : { ok: false, reason: 'failed', message: e instanceof Error ? e.message : 'The screen could not be captured.' };\n }\n // A tab stream sends a frame only when the page repaints, so on a still\n // page play() can wait forever (measured: the panel never came back). A\n // near-invisible pixel that changes every frame keeps frames coming, and\n // nothing below waits without a limit.\n const pulse = keepPainting();\n try {\n hooks.beforeCapture?.();\n const video = document.createElement('video');\n video.muted = true;\n video.playsInline = true;\n video.srcObject = stream;\n await within(video.play(), 3000);\n if (!video.videoWidth) await within(new Promise((r) => video.addEventListener('loadeddata', r, { once: true })), 3000);\n if (!video.videoWidth) throw new Error('The screen could not be captured: no picture arrived.');\n // Let the page repaint without the panel before the frame is read, then\n // take a frame the stream delivered AFTER that: a fixed wait alone read a\n // frame from before the hide on a heavy page.\n await new Promise((r) => setTimeout(r, 350));\n await freshFrames(video, 2);\n return encodeFrame(video, video.videoWidth, video.videoHeight);\n } catch (e) {\n return { ok: false, reason: 'failed', message: e instanceof Error ? e.message : 'The screen could not be captured.' };\n } finally {\n pulse();\n stream.getTracks().forEach((t) => t.stop());\n hooks.afterCapture?.();\n }\n}\n\nfunction approxBytes(dataUrl: string): number {\n const b64 = dataUrl.slice(dataUrl.indexOf(',') + 1);\n return Math.floor((b64.length * 3) / 4);\n}\n\nexport function formatBytes(n: number): string {\n return n < 1024 ? `${n} B` : n < 1024 * 1024 ? `${Math.round(n / 1024)} KB` : `${(n / 1024 / 1024).toFixed(1)} MB`;\n}\n","/**\n * The reporter session: who this browser is reporting as.\n *\n * The product's SERVER mints it with the secret key (see `./server`) and hands\n * it to the browser; the browser never sees the secret. The SDK asks for one\n * when it first needs it, keeps it until a minute before it expires, and asks\n * again once if the service says it is no longer valid.\n */\n\nexport interface SessionToken {\n /** `rs_…` */\n token: string;\n /** ISO time. Without it the token is kept until the service refuses it. */\n expires_at?: string;\n}\n\n/**\n * Where a session comes from.\n *\n * - a URL on your own server (`'/api/signal/session'`): POSTed with the\n * browser's cookies, answering `{ token, expires_at }`. Anything other than\n * 200 means \"nobody is signed in\", and the panel works anonymously.\n * - a function returning the same, for apps that fetch it their own way.\n */\nexport type SessionSource = string | (() => Promise<SessionToken | string | null | undefined>);\n\nexport interface SessionCache {\n /** The current token, fetching one if needed. Null when nobody is signed in. */\n get(force?: boolean): Promise<string | null>;\n clear(): void;\n}\n\nconst EARLY_MS = 60_000;\n\nexport function createSessionCache(source: SessionSource | undefined, fetchImpl?: typeof fetch): SessionCache {\n let cached: { token: string; expiresAt: number } | null = null;\n let inflight: Promise<string | null> | null = null;\n\n async function fetchOne(): Promise<string | null> {\n if (!source) return null;\n let raw: SessionToken | string | null | undefined;\n if (typeof source === 'function') {\n raw = await source();\n } else {\n const f = fetchImpl ?? globalThis.fetch?.bind(globalThis);\n if (!f) return null;\n const res = await f(source, { method: 'POST', credentials: 'include', headers: { accept: 'application/json' } });\n if (!res.ok) return null;\n const body = (await res.json().catch(() => null)) as\n | (SessionToken & { data?: SessionToken })\n | null;\n raw = body?.data ?? body;\n }\n if (!raw) return null;\n const t = typeof raw === 'string' ? { token: raw } : raw;\n if (!t.token) return null;\n const expiresAt = t.expires_at ? Date.parse(t.expires_at) - EARLY_MS : Number.POSITIVE_INFINITY;\n cached = { token: t.token, expiresAt };\n return t.token;\n }\n\n return {\n async get(force = false) {\n if (!force && cached && Date.now() < cached.expiresAt) return cached.token;\n if (!inflight) {\n inflight = fetchOne()\n .catch(() => null)\n .finally(() => {\n inflight = null;\n });\n }\n return inflight;\n },\n clear() {\n cached = null;\n },\n };\n}\n","import { SignalError, readEnvelope, transportError } from './errors.js';\nimport { collectEvidence, resolveConsent, type Consent } from './evidence.js';\nimport type { Impact, SignalType, Urgency } from './model.js';\nimport { createSessionCache, type SessionCache, type SessionSource } from './session.js';\n\n/**\n * Filing a signal.\n *\n * # THE KEY DECIDES THE SOURCE. THE BODY NEVER DOES.\n *\n * A credential belongs to exactly one source, so the service reads the source\n * off the key (or the reporter session) and the request body cannot name one.\n * `product` is accepted because it is useful for telling apart two call sites\n * that share a key, and it travels in `source_context`, where it cannot change\n * which source a signal is filed against.\n */\n\nexport type ReporterKind = 'person' | 'monitor' | 'agent';\n\nexport interface SignalUser {\n /** The product's own id for this person. Signal never mints one. */\n id?: string;\n name?: string;\n email?: string;\n}\n\nimport { DEFAULT_API_BASE } from './constants.js';\n\nexport { DEFAULT_API_BASE };\n\nexport interface InitOptions {\n /**\n * The publishable key (`sig_pub_…`) in a browser, or a secret key (`sig_live_…`) on a\n * server. Optional when `session` is given: a signed-in person files through\n * their session instead.\n */\n key?: string;\n /**\n * Your server's route that mints a reporter session (see `./server`), or a\n * function returning one. With it, reports are filed as the signed-in person\n * and they can see and follow them; without it, reports are anonymous.\n */\n session?: SessionSource;\n /** Where the API lives. Defaults to Signal's hosted service. */\n apiBase?: string;\n /** A LABEL, not the source. Travels in `source_context`. */\n product?: string;\n /** Who is reporting, when there is no session. */\n user?: SignalUser;\n consent?: Partial<Consent>;\n /** Attached to every report as `source_context`: where, which build, which project. */\n context?: Record<string, unknown>;\n /** Injected by tests. Defaults to the platform `fetch`. */\n fetchImpl?: typeof fetch;\n}\n\nexport interface ReportInput {\n type?: SignalType;\n subject: string;\n body?: string;\n impact?: Impact;\n urgency?: Urgency;\n reporter?: SignalUser;\n /** person | monitor | agent. Software is a first-class reporter. */\n reporterKind?: ReporterKind;\n /** One frame the reporter chose to take, as a data: URL (see `captureScreen`). */\n screenshot?: string;\n /** A key in your own storage, when you upload frames yourself. */\n screenshotKey?: string;\n /** The last few things that happened (see `createTrail`). */\n trail?: unknown;\n /** Your own diagnostics. */\n extra?: Record<string, unknown>;\n consent?: Partial<Consent>;\n context?: Record<string, unknown>;\n /**\n * Safe to retry with. Generated when omitted, so a dropped connection mid-POST\n * cannot file the same report twice: the duplicate a reporter would SEE.\n */\n idempotencyKey?: string;\n}\n\nexport interface ReportResult {\n id: string;\n as_of: string;\n}\n\nexport class SignalClient {\n private options: InitOptions | null = null;\n private sessionCache: SessionCache | null = null;\n\n init(options: InitOptions): this {\n if (!options?.key && !options?.session) {\n throw new SignalError(\n 'CONFIG',\n 'Signal.init needs a key (sig_pub_ in a browser, sig_live_ on a server) or a session source.',\n );\n }\n // A secret key in a browser is the mistake this class of credential exists\n // to prevent, and it is worth saying at the moment somebody makes it.\n if (typeof window !== 'undefined' && options.key?.startsWith('sig_live_')) {\n console.warn(\n '[signal] a sig_live_ key is a SERVER credential and anyone can read it in a browser bundle. Use a sig_pub_ key here.',\n );\n }\n this.options = options;\n this.sessionCache = createSessionCache(options.session, options.fetchImpl);\n return this;\n }\n\n /** Change who is reporting, without re-initialising. */\n setUser(user: SignalUser | null): this {\n if (!this.options) throw notInitialised();\n this.options = { ...this.options, user: user ?? undefined };\n return this;\n }\n\n /** Merge into the context sent with every report. */\n setContext(context: Record<string, unknown>): this {\n if (!this.options) throw notInitialised();\n this.options = { ...this.options, context: { ...this.options.context, ...context } };\n return this;\n }\n\n get initialised(): boolean {\n return this.options !== null;\n }\n\n get apiBase(): string {\n return (this.options?.apiBase ?? DEFAULT_API_BASE).replace(/\\/+$/, '');\n }\n\n /** The session cache, shared with the reporter API. */\n get session(): SessionCache {\n if (!this.sessionCache) throw notInitialised();\n return this.sessionCache;\n }\n\n async report(input: ReportInput): Promise<ReportResult> {\n const options = this.options;\n if (!options) throw notInitialised();\n if (!input?.subject?.trim()) {\n throw new SignalError('VALIDATION', 'a signal needs a subject', { path: 'subject', status: 422 });\n }\n\n const consent = resolveConsent(options.consent, input.consent);\n const reporter = { ...options.user, ...input.reporter };\n const context = {\n ...(options.product ? { product: options.product } : {}),\n ...options.context,\n ...input.context,\n };\n\n const body = {\n // Defaults chosen so the smallest useful call is `report({ subject })`.\n // Impact and urgency default to the middle of each scale: a client that\n // could set priority would set P1 every time, which is why the service\n // derives it from these two and never reads a priority.\n type: input.type ?? 'incident',\n subject: input.subject.trim(),\n body: input.body ?? '',\n impact: input.impact ?? 'moderate',\n urgency: input.urgency ?? 'medium',\n reporter: {\n external_id: reporter.id ?? null,\n name: reporter.name ?? 'Anonymous',\n email: reporter.email ?? null,\n },\n reporter_kind: input.reporterKind ?? 'person',\n evidence: collectEvidence(consent, {\n screenshot: input.screenshot,\n screenshotKey: input.screenshotKey,\n trail: input.trail,\n extra: input.extra,\n }),\n ...(Object.keys(context).length ? { source_context: context } : {}),\n };\n\n const fetchImpl = options.fetchImpl ?? globalThis.fetch?.bind(globalThis);\n if (!fetchImpl) {\n throw new SignalError('CONFIG', 'no fetch is available; pass fetchImpl on a runtime without one');\n }\n\n // A signed-in person files through their session, so the report is theirs\n // and they can follow it. Anyone else files with the key, anonymously.\n const sessionToken = options.session ? await this.session.get() : null;\n const credential = sessionToken ?? options.key;\n if (!credential) {\n throw new SignalError('NO_SESSION', 'nobody is signed in, and no publishable key was given for anonymous reports', {\n status: 401,\n });\n }\n\n const base = this.apiBase;\n const idempotencyKey = input.idempotencyKey ?? newIdempotencyKey();\n const send = (token: string) =>\n fetchImpl(`${base}/v1/intake/signal`, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n authorization: `Bearer ${token}`,\n 'x-idempotency-key': idempotencyKey,\n },\n body: JSON.stringify(body),\n });\n\n let response: Response;\n try {\n response = await send(credential);\n // The session lapsed between being read and being used: ask once more.\n if (response.status === 401 && sessionToken) {\n const fresh = await this.session.get(true);\n if (fresh) response = await send(fresh);\n }\n } catch (cause) {\n // No status at all: the request never completed. Retryable, and said so,\n // because this is the case a caller most wants to distinguish.\n throw transportError(base, cause);\n }\n\n const data = await readEnvelope<ReportResult>(response);\n if (!data?.id) {\n throw new SignalError('MALFORMED', 'the service answered outside the { message, data } envelope', {\n status: response.status,\n });\n }\n return data;\n }\n}\n\nfunction notInitialised(): SignalError {\n return new SignalError('CONFIG', 'call Signal.init({ key }) before reporting');\n}\n\nexport function newIdempotencyKey(): string {\n const c = globalThis.crypto;\n if (c?.randomUUID) return c.randomUUID();\n return `idem_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;\n}\n\n/**\n * The default instance, which is what the published snippet uses: an install\n * has one key and one product. `createClient()` is there for a server\n * reporting on behalf of several sources.\n */\nexport const Signal = new SignalClient();\n\nexport function createClient(options: InitOptions): SignalClient {\n return new SignalClient().init(options);\n}\n","/**\n * What a report means to the person who filed it.\n *\n * Signal's statuses are written for the team working the queue. A reporter\n * needs the same facts from the other side of the table: \"someone is reading\n * it\", not \"triage\"; \"fixed, check it on your machine\", not \"resolved\". Every\n * rule here is pure and unit-tested, because a status machine enforced inside\n * a click handler is a status machine with one path per button.\n */\n\nexport type SignalType = 'incident' | 'request' | 'feedback';\nexport type Impact = 'extensive' | 'significant' | 'moderate' | 'minor';\nexport type Urgency = 'critical' | 'high' | 'medium' | 'low';\nexport type SignalStatus =\n | 'triage'\n | 'open'\n | 'in_progress'\n | 'waiting_user'\n | 'fix_pending_release'\n | 'deferred'\n | 'resolved'\n | 'rejected'\n | 'under_review'\n | 'planned'\n | 'shipped'\n | 'declined';\n\nexport type Tone = 'neutral' | 'accent' | 'info' | 'warn' | 'danger' | 'success';\n\n/** One of the reporter's own signals, as `GET /v1/reporter/signals` returns it. */\nexport interface ReporterReport {\n id: string;\n ref: string;\n type: SignalType;\n subject: string;\n body: string;\n status: SignalStatus;\n disposition: string | null;\n fixed_in_version: string | null;\n verified_at: string | null;\n reopens: number;\n source_context: Record<string, unknown> | null;\n created_at: string;\n updated_at: string;\n team_replies: number;\n last_team_reply_at: string | null;\n /**\n * The first time somebody on the team opened it. Separate from `status`: a\n * person looking is not a decision about the report. Absent from a Signal\n * that predates it, so read as \"not yet\".\n *\n * Never WHO: the team speaks as one, as it does in its replies. Signal\n * 0.14.0-alpha.1 briefly sent `read_by_name`; it is ignored if present.\n */\n read_at?: string | null;\n}\n\nexport type ReporterEvent =\n | { kind: 'status'; from: SignalStatus | null; to: SignalStatus; at: string; by: 'you' | 'team' }\n | { kind: 'reply'; body: string; at: string; by: 'you' | 'team'; author_name: string };\n\n/**\n * One line of the conversation as the panel draws it: the server's timeline,\n * plus the moment the team first read the report. The read is a field on the\n * report rather than a timeline event, so a panel that predates it is not\n * handed a kind it cannot draw.\n */\nexport type ThreadItem = ReporterEvent | { kind: 'read'; at: string; by: 'team'; name: string | null };\n\nexport interface ReporterReportDetail extends ReporterReport {\n timeline: ReporterEvent[];\n attached: { screenshot: boolean; system: boolean; browser: boolean; trail: boolean; extra: boolean };\n}\n\n/* ------------------------------------------------------------------ journey */\n\n/**\n * The stages a report walks through, as a person would describe them.\n *\n * ⚠ THE LAST STAGE IS THE REPORTER'S, AND DRAWING IT IS THE POINT. The anxiety\n * behind every bug report is \"does this go anywhere\", so the path is shown\n * before anything is filed and again on every report, with the final node\n * belonging to the person reading it.\n */\nexport const DEFECT_STAGES = ['Sent', 'Read', 'Accepted', 'Fixed', 'You check'] as const;\nexport const FEEDBACK_STAGES = ['Sent', 'Read', 'Planned', 'Shipped'] as const;\n\nexport interface StatusView {\n /** Short, for a pill. */\n label: string;\n /** One sentence, written for the reporter: what this state means for THEM. */\n blurb: string;\n tone: Tone;\n stages: readonly string[];\n /** Index into `stages`. */\n stage: number;\n /** Work is happening at this stage right now, so its marker can pulse. */\n active: boolean;\n /** Left the happy path (not taken, duplicate). The rail stops rather than implying a fix. */\n offPath: boolean;\n /** Still able to change, from the reporter's point of view. */\n open: boolean;\n /** Something is waiting on the reporter. Only this badges. */\n needsYou: boolean;\n}\n\n/**\n * Compare two version strings, segment by segment.\n *\n * ⚠ A STRING COMPARE IS WRONG HERE AND WOULD LOOK RIGHT FOR A YEAR. \"0.9.0\"\n * sorts above \"0.24.0\" lexically, so someone on 0.24.0 would be told to update\n * to an older build, and the check would never appear on the reports already\n * fixed for them. A missing segment reads as 0.\n */\nexport function compareVersions(a: string, b: string): number {\n const parse = (v: string): number[] =>\n v\n .trim()\n .replace(/^v/i, '')\n .split(/[.+-]/)\n .map((p) => Number.parseInt(p, 10))\n .filter((n) => Number.isFinite(n));\n const pa = parse(a);\n const pb = parse(b);\n for (let i = 0; i < Math.max(pa.length, pb.length); i++) {\n const d = (pa[i] ?? 0) - (pb[i] ?? 0);\n if (d !== 0) return d < 0 ? -1 : 1;\n }\n return 0;\n}\n\n/**\n * May the reporter confirm the fix on the build they are running?\n *\n * ⚠ ONLY ONCE THEY ACTUALLY HAVE IT. A \"yes, it works\" given on the broken\n * build is worse than none: afterwards it looks exactly like a real one. With\n * no fixed version named, or no running version known, the reporter is the\n * best judge, so they are asked.\n */\nexport function canVerify(r: ReporterReport, runningVersion?: string): boolean {\n if (r.status !== 'resolved' || r.verified_at || !isFixed(r)) return false;\n if (!r.fixed_in_version || !runningVersion) return true;\n return compareVersions(runningVersion, r.fixed_in_version) >= 0;\n}\n\n/** Fixed, but the reporter is still on an older build. */\nexport function awaitingUpdate(r: ReporterReport, runningVersion?: string): boolean {\n return (\n r.status === 'resolved' &&\n !r.verified_at &&\n isFixed(r) &&\n !!r.fixed_in_version &&\n !!runningVersion &&\n compareVersions(runningVersion, r.fixed_in_version) < 0\n );\n}\n\nfunction isFixed(r: ReporterReport): boolean {\n return r.disposition === null || r.disposition === 'fixed';\n}\n\n/** Closed for a reason other than a fix, said the way a person would. */\nconst CLOSED_BECAUSE: Record<string, { label: string; blurb: string; tone: Tone }> = {\n by_design: { label: 'Working as intended', blurb: 'The team looked, and this is how it is meant to work. The reply below says why.', tone: 'neutral' },\n workaround: { label: 'Workaround given', blurb: 'There is a way around this for now. The reply below has it.', tone: 'info' },\n answered: { label: 'Answered', blurb: 'The team answered this. The reply is below.', tone: 'success' },\n cannot_reproduce: { label: 'Could not reproduce', blurb: 'The team could not make it happen. If it happens again, reply with what you did.', tone: 'neutral' },\n wont_fix: { label: 'Not being fixed', blurb: 'The team decided not to change this. The reply below says why.', tone: 'neutral' },\n out_of_scope: { label: 'Out of scope', blurb: 'This is outside what the product covers.', tone: 'neutral' },\n insufficient_info: { label: 'Needs more detail', blurb: 'There was not enough to go on. Reporting it again with steps will reopen the conversation.', tone: 'warn' },\n duplicate: { label: 'Already reported', blurb: 'The same thing is tracked on another report, so the work happens there.', tone: 'neutral' },\n spam: { label: 'Closed', blurb: 'This report was closed.', tone: 'neutral' },\n test: { label: 'Closed', blurb: 'This was a test report.', tone: 'neutral' },\n withdrawn: { label: 'Withdrawn', blurb: 'You withdrew this report, so the team is no longer working on it. If it comes back, report it again.', tone: 'neutral' },\n};\n\n/**\n * Whether the reporter may still take this report back. Not once it is\n * closed, and not while its fix is on its way: by then the work is done and\n * the useful next step is checking it.\n */\nexport function canWithdraw(r: ReporterReport): boolean {\n return statusView(r).open && r.status !== 'fix_pending_release';\n}\n\nexport function statusView(r: ReporterReport, runningVersion?: string): StatusView {\n if (r.type === 'feedback') return feedbackView(r);\n const base = { stages: DEFECT_STAGES, active: false, offPath: false, open: true, needsYou: false };\n const fixedIn = r.fixed_in_version ? ` in ${r.fixed_in_version}` : '';\n switch (r.status) {\n case 'triage':\n if (r.team_replies > 0) {\n return { ...base, label: 'Being read', blurb: 'Someone on the team is reading this now.', tone: 'info', stage: 1, active: true };\n }\n if (r.read_at) {\n return { ...base, label: 'Read', blurb: 'Someone on the team has read it. Next, they decide what happens with it.', tone: 'info', stage: 1 };\n }\n return { ...base, label: 'Received', blurb: 'The team has your report. Nobody has picked it up yet.', tone: 'info', stage: 0 };\n case 'open':\n return { ...base, label: r.reopens > 0 ? 'Reopened' : 'Accepted', blurb: r.reopens > 0 ? 'You told the team it is still wrong, so it went back on the list.' : 'Taken as a real issue. It is on the list to fix.', tone: r.reopens > 0 ? 'warn' : 'accent', stage: 2 };\n case 'in_progress':\n return { ...base, label: 'Being fixed', blurb: 'Someone is working on this right now.', tone: 'accent', stage: 2, active: true };\n case 'waiting_user':\n return { ...base, label: 'Waiting for you', blurb: 'The team asked you something. Reply below and it goes straight back to them.', tone: 'warn', stage: 2, needsYou: true };\n case 'deferred':\n return { ...base, label: 'On hold', blurb: 'Accepted, but not being worked on right now.', tone: 'neutral', stage: 2 };\n case 'fix_pending_release':\n return { ...base, label: 'Fixed, shipping soon', blurb: `The fix is done and goes out${fixedIn}. You will be asked to check it once you have it.`, tone: 'success', stage: 3 };\n case 'resolved': {\n if (r.verified_at) {\n return { ...base, label: 'Closed, you confirmed it', blurb: 'You confirmed the fix on your own machine. Thank you.', tone: 'success', stage: 4, open: false };\n }\n if (!isFixed(r)) {\n const why = CLOSED_BECAUSE[r.disposition ?? ''] ?? CLOSED_BECAUSE.answered!;\n return { ...base, ...why, stage: 1, offPath: true, open: false };\n }\n if (awaitingUpdate(r, runningVersion)) {\n return { ...base, label: `Fixed${fixedIn}`, blurb: `Fixed${fixedIn}; you are on ${runningVersion}. Update, and this will ask you to check it.`, tone: 'success', stage: 3 };\n }\n return { ...base, label: 'Fixed: check it', blurb: 'The team believes this is fixed. Try what went wrong and tell them what you find.', tone: 'success', stage: 4, needsYou: true };\n }\n case 'rejected': {\n const why = CLOSED_BECAUSE[r.disposition ?? ''] ?? { label: 'Not taken', blurb: 'The team is not treating this as a bug. The reply below says why.', tone: 'danger' as Tone };\n return { ...base, ...why, stage: 1, offPath: true, open: false };\n }\n default:\n return feedbackView(r);\n }\n}\n\nfunction feedbackView(r: ReporterReport): StatusView {\n const base = { stages: FEEDBACK_STAGES, active: false, offPath: false, open: true, needsYou: false };\n if (r.disposition === 'withdrawn') return { ...base, ...CLOSED_BECAUSE.withdrawn!, stage: 1, offPath: true, open: false };\n switch (r.status) {\n case 'under_review':\n return { ...base, label: 'Being read', blurb: 'The team is reading this.', tone: 'info', stage: 1, active: true };\n case 'planned':\n return { ...base, label: 'Planned', blurb: 'This is on the plan.', tone: 'accent', stage: 2 };\n case 'in_progress':\n return { ...base, label: 'Being built', blurb: 'Someone is building this now.', tone: 'accent', stage: 2, active: true };\n case 'shipped':\n return { ...base, label: 'Shipped', blurb: 'This is out. Thank you for asking for it.', tone: 'success', stage: 3, open: false };\n case 'declined':\n return { ...base, label: 'Not planned', blurb: 'The team decided not to build this. The reply below says why.', tone: 'neutral', stage: 1, offPath: true, open: false };\n case 'deferred':\n return { ...base, label: 'Later', blurb: 'Not now, but not forgotten.', tone: 'neutral', stage: 1 };\n default:\n return r.read_at\n ? { ...base, label: 'Read', blurb: 'Someone on the team has read it.', tone: 'info', stage: 1 }\n : { ...base, label: 'Received', blurb: 'The team has your feedback.', tone: 'info', stage: 0 };\n }\n}\n\n/**\n * The conversation in the order things happened, with the team's first read\n * placed where it fell. Accepting a report usually follows reading it within\n * minutes, and without this line the two arrive as one.\n *\n * A read recorded AFTER the team had already acted is left out: the action\n * already said somebody looked, and \"Accepted\" followed by \"Read\" reads as\n * nonsense. It happens to reports acted on before reads were recorded.\n */\nexport function threadOf(d: ReporterReportDetail): ThreadItem[] {\n const items: ThreadItem[] = [...d.timeline];\n if (!d.read_at) return items;\n const at = Date.parse(d.read_at);\n if (items.some((e) => e.by === 'team' && Date.parse(e.at) < at)) return items;\n const read: ThreadItem = { kind: 'read', at: d.read_at, by: 'team', name: null };\n // After what came before it (the filing, at least), and ahead of an action\n // taken in the same instant: acting on a report records the read with it.\n const i = items.findIndex((e) => Date.parse(e.at) >= at);\n items.splice(i < 0 ? items.length : i, 0, read);\n return items;\n}\n\n/** The read line, said from the reporter's side. */\nexport function readWords(name: string | null): string {\n return name ? `Read by ${name}` : 'Read by the team';\n}\n\n/* ------------------------------------------------------------ what you send */\n\nexport type Severity = 'blocking' | 'major' | 'minor' | 'cosmetic';\n\n/**\n * How much a problem is holding the reporter up, in their words, and what the\n * team's queue needs from it. The team's priority is derived from impact and\n * urgency on the server; a reporter is never asked for a priority, because\n * every reporter would pick the top one.\n */\nexport const SEVERITY: Record<Severity, { label: string; hint: string; tone: Tone; impact: Impact; urgency: Urgency }> = {\n blocking: { label: 'Blocks me', hint: 'I cannot carry on working', tone: 'danger', impact: 'moderate', urgency: 'critical' },\n major: { label: 'Major', hint: 'A real problem, I have a workaround', tone: 'warn', impact: 'moderate', urgency: 'high' },\n minor: { label: 'Minor', hint: 'Annoying, but small', tone: 'info', impact: 'minor', urgency: 'medium' },\n cosmetic: { label: 'Cosmetic', hint: 'It just looks wrong', tone: 'neutral', impact: 'minor', urgency: 'low' },\n};\n\nexport type FeedbackKind = 'idea' | 'praise' | 'confusing' | 'complaint';\n\nexport const FEEDBACK_KINDS: Record<FeedbackKind, { label: string; hint: string }> = {\n idea: { label: 'An idea', hint: 'Something you wish this did' },\n praise: { label: 'This works well', hint: 'Tell us what to protect' },\n confusing: { label: 'This confused me', hint: 'You could not tell what it would do' },\n complaint: { label: 'This frustrates me', hint: 'It works, and it is painful' },\n};\n\nexport const MIN_DESCRIPTION = 12;\n\n/**\n * Which field stops a report being sent, if any.\n *\n * Names the field rather than returning a boolean, because \"Submit is disabled\n * and I cannot see why\" is the most common way a form wastes someone's time.\n */\nexport function validateIssue(d: { what: string; expected: string }): { field: 'what' | 'expected'; message: string } | null {\n if (d.what.trim().length < MIN_DESCRIPTION) {\n return { field: 'what', message: 'Say what happened. A sentence is enough, but one word is not.' };\n }\n if (d.expected.trim().length < MIN_DESCRIPTION) {\n return { field: 'expected', message: 'Say what you expected instead. This is the half that says what to build.' };\n }\n return null;\n}\n\n/** Open reports above closed ones whatever their age: the list exists to be acted on. */\nexport function sortReports<T extends ReporterReport>(reports: readonly T[], runningVersion?: string): T[] {\n return [...reports].sort((a, b) => {\n const va = statusView(a, runningVersion);\n const vb = statusView(b, runningVersion);\n if (va.needsYou !== vb.needsYou) return va.needsYou ? -1 : 1;\n if (va.open !== vb.open) return va.open ? -1 : 1;\n return Date.parse(b.created_at) - Date.parse(a.created_at);\n });\n}\n\n/** \"3m ago\". One phrasing for every duration on the panel. */\nexport function formatAgo(then: string | number, now = Date.now()): string {\n const t = typeof then === 'number' ? then : Date.parse(then);\n const s = Math.max(0, Math.round((now - t) / 1000));\n if (s < 10) return 'just now';\n if (s < 60) return `${s}s ago`;\n const m = Math.round(s / 60);\n if (m < 60) return `${m}m ago`;\n const h = Math.round(m / 60);\n if (h < 24) return `${h}h ago`;\n return `${Math.round(h / 24)}d ago`;\n}\n\n/**\n * One status move, said from the reporter's side.\n *\n * The same move reads differently depending on who made it and where it came\n * from: the reporter's own filing is \"you sent this\", not \"received by you\";\n * answering the team's question is \"back with the team\", not \"accepted by you\".\n */\nexport function eventWords(e: { from: SignalStatus | null; to: SignalStatus; by: 'you' | 'team' }): string {\n if (e.by === 'you') {\n if (!e.from) return 'You sent this';\n if (e.from === 'resolved' || e.from === 'shipped') return 'You reopened it';\n if (e.from === 'waiting_user') return 'Back with the team after your reply';\n if (e.to === 'rejected' || e.to === 'declined' || e.to === 'resolved') return 'You withdrew it';\n return 'You moved it';\n }\n if (e.to === 'open' && (e.from === 'resolved' || e.from === 'rejected')) return 'Reopened';\n return TEAM_WORDS[e.to] ?? e.to;\n}\n\nconst TEAM_WORDS: Partial<Record<SignalStatus, string>> = {\n triage: 'Received',\n open: 'Accepted',\n in_progress: 'Being worked on',\n waiting_user: 'The team is waiting for you',\n fix_pending_release: 'Fixed, shipping soon',\n deferred: 'On hold',\n resolved: 'Marked fixed',\n rejected: 'Closed',\n under_review: 'Being read',\n planned: 'Planned',\n shipped: 'Shipped',\n declined: 'Not planned',\n};\n\n/**\n * A report's body as the parts it was written in: the SDK files \"What\n * happened\" and \"Expected instead\" as labelled blocks, and other producers\n * send free text, which comes back as one unlabelled part.\n */\nexport function reportParts(body: string): { label: string | null; text: string }[] {\n return body\n .split(/\\n{2,}/)\n .map((block) => {\n const [first, ...rest] = block.split('\\n');\n return rest.length && /^(What happened|Expected instead)$/.test(first!.trim())\n ? { label: first!.trim(), text: rest.join('\\n').trim() }\n : { label: null, text: block.trim() };\n })\n .filter((p) => p.text);\n}\n","import { SignalError, readEnvelope, transportError } from './errors.js';\nimport type { ReporterReport, ReporterReportDetail } from './model.js';\nimport type { SessionCache } from './session.js';\n\n/**\n * The reporter's own reports, through their session: list, read, reply, and\n * say whether a fix works. Nothing here can reach anyone else's.\n */\nexport interface ReporterApi {\n list(): Promise<ReporterReport[]>;\n get(id: string): Promise<ReporterReportDetail>;\n reply(id: string, body: string): Promise<ReporterReportDetail>;\n verify(id: string, works: boolean, note?: string): Promise<ReporterReportDetail>;\n withdraw(id: string, note?: string): Promise<ReporterReportDetail>;\n}\n\nexport function createReporterApi(base: string, session: SessionCache, fetchImpl?: typeof fetch): ReporterApi {\n const f = () => {\n const impl = fetchImpl ?? globalThis.fetch?.bind(globalThis);\n if (!impl) throw new SignalError('CONFIG', 'no fetch is available; pass fetchImpl on a runtime without one');\n return impl;\n };\n\n async function call<T>(method: string, path: string, body?: unknown, retried = false): Promise<T> {\n const token = await session.get(retried);\n if (!token) {\n throw new SignalError('NO_SESSION', 'nobody is signed in, so there are no reports to read', { status: 401 });\n }\n let res: Response;\n try {\n res = await f()(`${base}${path}`, {\n method,\n headers: {\n authorization: `Bearer ${token}`,\n accept: 'application/json',\n ...(body !== undefined ? { 'content-type': 'application/json' } : {}),\n },\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n } catch (cause) {\n throw transportError(base, cause);\n }\n // An expired or revoked session: ask the product's server once more.\n if (res.status === 401 && !retried) return call<T>(method, path, body, true);\n return readEnvelope<T>(res);\n }\n\n return {\n list: () => call<ReporterReport[]>('GET', '/v1/reporter/signals'),\n get: (id) => call<ReporterReportDetail>('GET', `/v1/reporter/signals/${encodeURIComponent(id)}`),\n reply: (id, body) =>\n call<ReporterReportDetail>('POST', `/v1/reporter/signals/${encodeURIComponent(id)}/replies`, { body }),\n verify: (id, works, note) =>\n call<ReporterReportDetail>('POST', `/v1/reporter/signals/${encodeURIComponent(id)}/verify`, {\n works,\n ...(note ? { note } : {}),\n }),\n withdraw: (id, note) =>\n call<ReporterReportDetail>('POST', `/v1/reporter/signals/${encodeURIComponent(id)}/withdraw`, note ? { note } : {}),\n };\n}\n","/**\n * The last few things that happened, kept in memory and attached to a report\n * only if the reporter keeps it.\n *\n * ⚠ IT ONLY OBSERVES. The error hooks append a line and do nothing else: they\n * never swallow, never `preventDefault`, and never send anything anywhere. A\n * listener that quietly transmitted would break the promise the report form\n * makes on its own Attach step, and would be the first thing someone found in\n * their DevTools.\n */\n\nexport type TrailKind = 'click' | 'navigate' | 'edit' | 'error' | 'note' | (string & {});\n\nexport interface TrailEntry {\n /** Epoch milliseconds. */\n at: number;\n kind: TrailKind;\n label: string;\n}\n\nexport interface Trail {\n record(kind: TrailKind, label: string, at?: number): void;\n /** The most recent `count`, oldest first. */\n recent(count?: number): TrailEntry[];\n size(): number;\n clear(): void;\n /** Put uncaught errors and unhandled rejections into the trail. Returns the undo. */\n captureErrors(target?: Window): () => void;\n}\n\nexport function createTrail(capacity = 60): Trail {\n const ring: TrailEntry[] = [];\n return {\n record(kind, label, at = Date.now()) {\n const text = String(label).trim().slice(0, 300);\n if (!text) return;\n // An edit repeated in a burst is one thing the person did, not forty.\n const last = ring[ring.length - 1];\n if (last && last.kind === kind && last.label === text && at - last.at < 1500) {\n last.at = at;\n return;\n }\n ring.push({ at, kind, label: text });\n if (ring.length > capacity) ring.splice(0, ring.length - capacity);\n },\n recent(count = 10) {\n return ring.slice(-count).map((e) => ({ ...e }));\n },\n size: () => ring.length,\n clear() {\n ring.length = 0;\n },\n captureErrors(target = typeof window === 'undefined' ? undefined : window) {\n if (!target) return () => {};\n const onError = (e: ErrorEvent) => this.record('error', e.message || 'Uncaught error');\n const onRejection = (e: PromiseRejectionEvent) => {\n const r = e.reason as { message?: string } | string | undefined;\n this.record('error', typeof r === 'string' ? r : (r?.message ?? 'Unhandled promise rejection'));\n };\n target.addEventListener('error', onError);\n target.addEventListener('unhandledrejection', onRejection);\n return () => {\n target.removeEventListener('error', onError);\n target.removeEventListener('unhandledrejection', onRejection);\n };\n },\n };\n}\n","import { SignalClient, type InitOptions } from './client.js';\nimport { SignalError } from './errors.js';\nimport {\n readBrowser,\n readDiagnostics,\n readSystem,\n type Consent,\n type Diagnostics,\n} from './evidence.js';\nimport {\n FEEDBACK_KINDS,\n SEVERITY,\n sortReports,\n statusView,\n type FeedbackKind,\n type ReporterReport,\n type ReporterReportDetail,\n type Severity,\n} from './model.js';\nimport { createReporterApi, type ReporterApi } from './reporter.js';\nimport { createTrail, type Trail, type TrailEntry } from './trail.js';\n\n/**\n * Support, without a UI.\n *\n * Everything the panel does, as plain functions and one subscribable state, so\n * a product can draw its own panel and still get the whole loop: file with\n * evidence, follow the report, read and answer the team, and confirm the fix.\n * The widget (`@signalsupport/sdk/widget`) is built on exactly this.\n */\n\nexport interface SupportConfig extends InitOptions {\n /**\n * The build the person is running. Sent with every report, and what decides\n * whether \"does the fix work?\" can be asked yet.\n */\n appVersion?: string;\n /** Your own diagnostics, read only when the reporter reaches the Attach step. */\n diagnostics?: Diagnostics;\n /** Pass your own trail to share it, or `false` to keep none. */\n trail?: Trail | false;\n /** Put uncaught errors in the trail. Observes only; never reports by itself. Default true. */\n captureErrors?: boolean;\n /** How often to refresh the reporter's reports while the page is visible. Default 2 minutes. */\n refreshMs?: number;\n}\n\nexport interface SupportState {\n /** Null until known. False means reports are anonymous and cannot be followed. */\n signedIn: boolean | null;\n loading: boolean;\n reports: ReporterReport[];\n /** Reports wanting something from the reporter, or carrying an unread team reply. */\n attention: number;\n error: string | null;\n /** When the reports were last fetched successfully (ms since epoch), for \"Updated 12s ago\". */\n updatedAt: number | null;\n}\n\nexport interface IssueDraft {\n what: string;\n expected: string;\n severity: Severity;\n /** Where in the product. Prefilled from the context's `area`. */\n area?: string;\n screenshot?: string;\n /** What the reporter chose to attach, after seeing it. */\n include?: Partial<Pick<Consent, 'system' | 'browser' | 'trail' | 'extra' | 'screenshot'>>;\n /** For an anonymous report: how to reach them, if they want to be reached. */\n contact?: { name?: string; email?: string };\n}\n\nexport interface FeedbackDraft {\n kind: FeedbackKind;\n body: string;\n area?: string;\n screenshot?: string;\n contact?: { name?: string; email?: string };\n}\n\n/** What the Attach step shows, read when the reporter gets there and not before. */\nexport interface Collected {\n system?: Record<string, unknown>;\n browser?: Record<string, unknown>;\n trail: TrailEntry[];\n extra?: Record<string, unknown>;\n}\n\nexport interface Support {\n readonly client: SignalClient;\n readonly reporter: ReporterApi;\n readonly trail: Trail;\n readonly appVersion?: string;\n getState(): SupportState;\n subscribe(listener: (state: SupportState) => void): () => void;\n refresh(): Promise<void>;\n context(): Record<string, unknown>;\n setContext(context: Record<string, unknown>): void;\n /** Record something the person did, for the trail a report may carry. */\n record(kind: string, label: string): void;\n collect(): Promise<Collected>;\n submitIssue(draft: IssueDraft): Promise<{ id: string; ref: string }>;\n submitFeedback(draft: FeedbackDraft): Promise<{ id: string; ref: string }>;\n detail(id: string): Promise<ReporterReportDetail>;\n reply(id: string, body: string): Promise<ReporterReportDetail>;\n verify(id: string, works: boolean, note?: string): Promise<ReporterReportDetail>;\n /**\n * Take a report back: it sorted itself out, or they no longer need it. A\n * close, never a delete; the team keeps the report and sees why.\n */\n withdraw(id: string, note?: string): Promise<ReporterReportDetail>;\n /** Whether a report carries a team reply this browser has not opened. */\n unread(report: ReporterReport): boolean;\n markSeen(id: string): void;\n destroy(): void;\n}\n\n/** The short reference a person can quote: the tail of the id, as the service derives it. */\nexport function refOf(id: string): string {\n return `SG-${id.slice(-6).toUpperCase()}`;\n}\n\nconst SEEN_KEY = 'signal.seen.v1';\n\nexport function createSupport(config: SupportConfig): Support {\n const client = new SignalClient().init({\n ...config,\n context: { ...(config.appVersion ? { app_version: config.appVersion } : {}), ...config.context },\n });\n const reporter = createReporterApi(client.apiBase, client.session, config.fetchImpl);\n const trail = config.trail === false ? createTrail(0) : (config.trail ?? createTrail());\n const releaseErrors = config.captureErrors === false ? () => {} : trail.captureErrors();\n let ctx: Record<string, unknown> = { ...config.context };\n let seen: Record<string, number> = readSeen();\n\n let state: SupportState = { signedIn: config.session ? null : false, loading: false, reports: [], attention: 0, error: null, updatedAt: null };\n const listeners = new Set<(s: SupportState) => void>();\n const set = (patch: Partial<SupportState>) => {\n state = { ...state, ...patch };\n state.attention = attentionOf(state.reports);\n for (const l of listeners) l(state);\n };\n\n function attentionOf(reports: ReporterReport[]): number {\n return reports.filter((r) => statusView(r, config.appVersion).needsYou || unread(r)).length;\n }\n\n function unread(r: ReporterReport): boolean {\n if (!r.last_team_reply_at) return false;\n return Date.parse(r.last_team_reply_at) > (seen[r.id] ?? 0);\n }\n\n let lastRefresh = 0;\n async function refresh(): Promise<void> {\n if (!config.session) return;\n lastRefresh = Date.now();\n set({ loading: true });\n try {\n const token = await client.session.get();\n if (!token) {\n set({ signedIn: false, loading: false, reports: [], error: null });\n return;\n }\n const reports = await reporter.list();\n set({ signedIn: true, loading: false, reports: sortReports(reports, config.appVersion), error: null, updatedAt: Date.now() });\n } catch (e) {\n // A session that lapsed and could not be renewed means the person signed\n // out of the host product: their reports go with them, exactly as when\n // there was never a session, or the next person at this browser sees them.\n const signedOut = e instanceof SignalError && e.code === 'NO_SESSION';\n if (signedOut) set({ signedIn: false, loading: false, reports: [], error: null });\n else set({ loading: false, error: messageOf(e) });\n }\n }\n\n // Keep the badge honest while the panel is closed: a fix the team just\n // shipped should light it without anyone opening anything.\n let timer: ReturnType<typeof setInterval> | null = null;\n const onVisible = () => {\n if (document.visibilityState === 'visible') void refresh();\n };\n // ⚠ FOCUS, NOT ONLY VISIBILITY. The usual way a reporter learns something\n // changed is by acting somewhere else (the team's console in another window,\n // side by side) and coming back. The page never stopped being visible, so\n // visibilitychange never fires; the window regaining focus is the moment.\n // Throttled, because focus also bounces on every alt-tab.\n const onFocus = () => {\n if (Date.now() - lastRefresh > 5_000) void refresh();\n };\n if (config.session && typeof window !== 'undefined') {\n void refresh();\n timer = setInterval(() => {\n if (document.visibilityState === 'visible') void refresh();\n }, config.refreshMs ?? 120_000);\n document.addEventListener('visibilitychange', onVisible);\n window.addEventListener('focus', onFocus);\n }\n\n function upsert(d: ReporterReportDetail) {\n const { timeline: _t, attached: _a, ...row } = d;\n const rest = state.reports.filter((r) => r.id !== row.id);\n set({ reports: sortReports([row, ...rest], config.appVersion) });\n }\n\n const context = () => ({ ...ctx });\n\n return {\n client,\n reporter,\n trail,\n appVersion: config.appVersion,\n getState: () => state,\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n refresh,\n context,\n setContext(next) {\n ctx = { ...ctx, ...next };\n },\n record: (kind, label) => trail.record(kind, label),\n async collect() {\n return {\n system: readSystem(),\n browser: readBrowser(),\n trail: trail.recent(10),\n extra: await readDiagnostics(config.diagnostics),\n };\n },\n async submitIssue(draft) {\n const s = SEVERITY[draft.severity];\n const include = { system: true, browser: true, trail: true, extra: true, screenshot: true, ...draft.include };\n const collected = include.extra ? await readDiagnostics(config.diagnostics) : undefined;\n const area = draft.area?.trim() || (ctx.area as string | undefined);\n const res = await client.report({\n type: 'incident',\n subject: firstLine(draft.what),\n body: `What happened\\n${draft.what.trim()}\\n\\nExpected instead\\n${draft.expected.trim()}`,\n impact: s.impact,\n urgency: s.urgency,\n reporter: draft.contact,\n screenshot: include.screenshot ? draft.screenshot : undefined,\n trail: include.trail ? trail.recent(10) : undefined,\n extra: collected,\n consent: { system: include.system, browser: include.browser, trail: include.trail, extra: include.extra, screenshot: include.screenshot },\n context: { ...ctx, ...(area ? { area } : {}), severity: draft.severity },\n });\n trail.record('note', `Reported an issue (${refOf(res.id)})`);\n void refresh();\n return { id: res.id, ref: refOf(res.id) };\n },\n async submitFeedback(draft) {\n const area = draft.area?.trim() || (ctx.area as string | undefined);\n const res = await client.report({\n type: 'feedback',\n subject: `${FEEDBACK_KINDS[draft.kind].label}: ${firstLine(draft.body, 100)}`,\n body: draft.body.trim(),\n impact: 'minor',\n urgency: 'low',\n reporter: draft.contact,\n screenshot: draft.screenshot,\n consent: { trail: false, extra: false },\n context: { ...ctx, ...(area ? { area } : {}), feedback_kind: draft.kind },\n });\n void refresh();\n return { id: res.id, ref: refOf(res.id) };\n },\n async detail(id) {\n const d = await reporter.get(id);\n upsert(d);\n return d;\n },\n async reply(id, body) {\n const d = await reporter.reply(id, body);\n upsert(d);\n return d;\n },\n async verify(id, works, note) {\n const d = await reporter.verify(id, works, note);\n trail.record('note', works ? `Confirmed the fix for ${refOf(id)}` : `Said ${refOf(id)} is still happening`);\n upsert(d);\n return d;\n },\n async withdraw(id, note) {\n const d = await reporter.withdraw(id, note);\n trail.record('note', `Withdrew ${refOf(id)}`);\n upsert(d);\n return d;\n },\n unread,\n markSeen(id) {\n seen = { ...seen, [id]: Date.now() };\n try {\n localStorage.setItem(SEEN_KEY, JSON.stringify(seen));\n } catch {\n // Without storage the unread mark lasts for this page.\n }\n set({});\n },\n destroy() {\n releaseErrors();\n if (timer) clearInterval(timer);\n if (typeof document !== 'undefined') document.removeEventListener('visibilitychange', onVisible);\n if (typeof window !== 'undefined') window.removeEventListener('focus', onFocus);\n listeners.clear();\n },\n };\n}\n\nfunction readSeen(): Record<string, number> {\n try {\n const raw = JSON.parse(localStorage.getItem(SEEN_KEY) ?? '{}') as unknown;\n return raw && typeof raw === 'object' ? (raw as Record<string, number>) : {};\n } catch {\n return {};\n }\n}\n\nfunction firstLine(text: string, max = 120): string {\n const line = text.trim().split(/\\r?\\n/)[0] ?? '';\n return line.length > max ? `${line.slice(0, max - 1).trimEnd()}…` : line;\n}\n\nfunction messageOf(e: unknown): string {\n return e instanceof Error ? e.message : String(e);\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{t as e}from"./support-x7N-k02b.js";var t={launcher:`Help`,title:`Support`,homeTitle:`How can we help?`,reportAction:`Report an issue`,reportHint:`Something is broken or does the wrong thing`,feedbackAction:`Share feedback`,feedbackHint:`An idea, a frustration, or something that works well`,faqAction:`Browse the FAQs`,faqHint:`Answers to what people ask most`,yourReports:`Your reports`,refresh:`Check for updates`,updatedAgo:`Updated {ago}`,upToDate:`Up to date`,seeAll:`See all`,signedOutReports:`Sign in to follow your reports here.`,fixesWaitingOne:`A fix for one of your reports is ready. It needs your check.`,fixesWaitingMany:`Fixes for {n} of your reports are ready. They need your check.`,questionWaiting:`The team asked you something.`,stepDescribe:`Describe`,stepAttach:`Attach`,stepReview:`Review`,whatLabel:`What happened?`,whatHint:`What you did, and what the product did. Plain words are perfect.`,whatPlaceholder:`I selected three items and pressed Delete. Only one went away.`,expectedLabel:`What did you expect instead?`,expectedHint:`This is the half that says what to build. Two lines is plenty.`,expectedPlaceholder:`All three should have been deleted, the way they are from the menu.`,severityLabel:`How much is it holding you up?`,whereLabel:`Where in the product?`,whereHint:`Filled in from where you are. Change it if the problem is elsewhere.`,attachIntro:`Attach as much or as little as you like. Everything is shown before it is sent, and nothing is collected in the background.`,screenshot:`Screen capture`,screenshotNone:`Not captured. You choose what to share; this tab is offered first.`,capture:`Capture screen`,retake:`Retake`,capturing:`Capturing…`,pickHint:`Go to the screen you want to show`,pickCapture:`Capture`,system:`System and browser`,trail:`Your last steps`,trailNone:`Nothing recorded yet.`,diagnostics:`Diagnostics from the product`,contactLabel:`How can the team reach you?`,contactHint:`Optional. Without it, the team cannot reply to you.`,namePlaceholder:`Your name`,emailPlaceholder:`you@example.com`,attached:`Attached`,notAttached:`not attached`,next:`Next`,back:`Back`,cancel:`Cancel`,submitReport:`Send report`,reportSent:`Report sent`,reportSentBody:`You can follow it from here. When the team believes it is fixed, this is where you will be asked to check it.`,reportSentAnon:`Keep this reference. Quote it if you contact the team about this.`,track:`Follow this report`,done:`Done`,feedbackKindLabel:`What kind of feedback?`,feedbackLabel:`Tell us`,feedbackPlaceholder:`What you would change, and why it matters to you.`,sendFeedback:`Send feedback`,thanks:`Thank you`,thanksBody:`Feedback is read, and it is what decides what gets built next.`,faqSearch:`Search the FAQs`,faqEmpty:`Nothing matches.`,faqNotFound:`Did not find it?`,allReports:`All reports`,noReports:`Nothing reported yet`,noReportsBody:`When something does not behave the way you expect, this is where it goes, and where you watch what happens to it.`,conversation:`Conversation`,replyPlaceholder:`Reply to the team`,send:`Send`,closedNote:`This report is closed. Reporting it again opens a new one.`,withdrawAction:`Withdraw this report`,withdrawTitle:`Withdraw this report?`,withdrawBody:`The team stops working on it. This cannot be undone, but you can always report it again.`,withdrawPlaceholder:`Anything the team should know? (optional)`,withdrawConfirm:`Withdraw`,withdrawKeep:`Keep it`,closedReports:`Closed`,nothingOpen:`Nothing open right now.`,verifyTitle:`This should be fixed in the build you are running.`,verifyBody:`Try what went wrong and tell the team what you find. The report stays open until you do.`,verifyYes:`Yes, it works now`,verifyNo:`No, still happening`,verifyNoPlaceholder:`What is still happening? Anything that changed since you reported it helps.`,verifySend:`Send`,updateTitle:`Fixed in {version}. You are on {running}.`,updateBody:`Update to get the fix, then this report will ask you to check it.`,whatYouReported:`What you reported`,team:`Team`,you:`You`,error:`Something went wrong. Try again.`};function n(e,t){return e.replace(/\{(\w+)\}/g,(e,n)=>String(t[n]??`{${n}}`))}var r=[`header`,`home-top`,`home-bottom`,`report-top`,`feedback-top`,`sent`,`footer`];function i(e){let t=e,n=new Set;return{get:()=>t,set(e){t={...t,...e};for(let e of n)e()},subscribe(e){return n.add(e),()=>n.delete(e)}}}var a={radius:12,font:`inherit`,mono:`ui-monospace, SFMono-Regular, Menlo, Consolas, monospace`,fontSize:13,width:440},o={...a,scheme:`dark`,accent:`#6d5efc`,accentText:`#ffffff`,background:`#0e1017`,surface:`#151824`,raised:`#1c2030`,border:`rgba(255,255,255,0.09)`,text:`#eef1f8`,muted:`#a3abc0`,dim:`#7f889d`,success:`#3ccf91`,warning:`#f5a524`,danger:`#ff5a6a`,info:`#4fb8ff`,shadow:`0 24px 70px -20px rgba(0,0,0,0.75)`,scrim:`rgba(4,5,10,0.55)`},s={...a,scheme:`light`,accent:`#2563eb`,accentText:`#ffffff`,background:`#ffffff`,surface:`#f6f7f9`,raised:`#eef0f4`,border:`rgba(15,23,42,0.11)`,text:`#0f172a`,muted:`#475569`,dim:`#64748b`,success:`#15803d`,warning:`#b45309`,danger:`#dc2626`,info:`#0369a1`,shadow:`0 24px 60px -24px rgba(15,23,42,0.35)`,scrim:`rgba(15,23,42,0.35)`},c={midnight:o,daylight:s,studio:{...a,scheme:`dark`,accent:`#7a50dd`,accentText:`#ffffff`,background:`#0d0b12`,surface:`#15121d`,raised:`#1d1927`,border:`rgba(255,255,255,0.08)`,text:`#f3f0fa`,muted:`#b4abc8`,dim:`#8a819e`,success:`#34d399`,warning:`#fbbf24`,danger:`#fb7185`,info:`#a78bfa`,radius:10,fontSize:12.5,width:480,shadow:`-24px 0 60px -20px rgba(0,0,0,0.9)`,scrim:`rgba(0,0,0,0.55)`},paper:{...a,scheme:`light`,accent:`#c2410c`,accentText:`#ffffff`,background:`#fffdf8`,surface:`#f7f2e8`,raised:`#efe8da`,border:`rgba(68,48,20,0.14)`,text:`#2b2118`,muted:`#5c4b3a`,dim:`#7a6754`,success:`#3f7d3a`,warning:`#a16207`,danger:`#b91c1c`,info:`#1d4ed8`,radius:18,fontSize:14,width:420,shadow:`0 30px 60px -30px rgba(68,48,20,0.45)`,scrim:`rgba(43,33,24,0.3)`}};function l(e,t){let n=typeof e==`string`?{preset:e}:e??{},r={...c[n.preset??`midnight`],...u(n)};return r.scheme===`auto`?{...t?o:s,...a,...d(n,[`accent`,`accentText`,`radius`,`font`,`mono`,`fontSize`,`width`]),scheme:`auto`}:r}function u(e){return Object.fromEntries(Object.entries(e).filter(([e,t])=>t!==void 0&&e!==`preset`))}function d(e,t){return Object.fromEntries(Object.entries(e).filter(([e,n])=>t.includes(e)&&n!==void 0))}function f(e){let t=e.radius;return[`--sg-accent:${e.accent}`,`--sg-accent-text:${e.accentText}`,`--sg-bg:${e.background}`,`--sg-surface:${e.surface}`,`--sg-raised:${e.raised}`,`--sg-border:${e.border}`,`--sg-text:${e.text}`,`--sg-muted:${e.muted}`,`--sg-dim:${e.dim}`,`--sg-success:${e.success}`,`--sg-warning:${e.warning}`,`--sg-danger:${e.danger}`,`--sg-info:${e.info}`,`--sg-radius:${t}px`,`--sg-radius-sm:${Math.max(4,Math.round(t*.6))}px`,`--sg-font:${e.font}`,`--sg-mono:${e.mono}`,`--sg-size:${e.fontSize}px`,`--sg-width:${e.width}px`,`--sg-shadow:${e.shadow}`,`--sg-scrim:${e.scrim}`,`color-scheme:${e.scheme===`light`?`light`:e.scheme===`dark`?`dark`:`light dark`}`].join(`;`)}var p={"sg-launcher":`launcher`,"sg-pickbar":`capture-bar`,"sg-badge":`badge`,"sg-scrim":`scrim`,"sg-panel":`panel`,"sg-header":`header`,"sg-title":`title`,"sg-sub":`subtitle`,"sg-iconbtn":`icon-button`,"sg-body":`body`,"sg-footer":`footer`,"sg-section":`section`,"sg-section-title":`section-title`,"sg-action":`action`,"sg-action-label":`action-label`,"sg-action-hint":`action-hint`,"sg-tile":`tile`,"sg-btn":`button`,"sg-btn-ghost":`button-ghost`,"sg-btn-danger":`button-danger`,"sg-link":`link`,"sg-label":`label`,"sg-hint":`hint`,"sg-err":`error`,"sg-input":`input`,"sg-textarea":`textarea`,"sg-chip":`chip`,"sg-switch":`switch`,"sg-pill":`pill`,"sg-banner":`banner`,"sg-card":`card`,"sg-steps":`steps`,"sg-row":`row`,"sg-row-title":`row-title`,"sg-rail":`rail`,"sg-msg":`message`,"sg-team":`message-team`,"sg-you":`message-you`,"sg-composer":`composer`,"sg-faq":`faq-item`,"sg-confirm":`confirmation`,"sg-ref":`reference`};function m(e){let t=[];for(let n of e.classList){let e=p[n];e&&t.push(e)}return e.classList.contains(`sg-btn`)&&!e.classList.contains(`sg-btn-ghost`)&&!e.classList.contains(`sg-btn-danger`)&&t.push(`button-primary`),e.classList.contains(`sg-chip`)&&(e.getAttribute(`aria-checked`)===`true`||e.getAttribute(`aria-pressed`)===`true`)&&t.push(`chip-selected`),t.join(` `)}function h(e){let t=m(e);t?e.getAttribute(`part`)!==t&&e.setAttribute(`part`,t):e.hasAttribute(`part`)&&e.removeAttribute(`part`)}function g(e){let t=e=>{if(e instanceof Element){h(e);for(let t of e.querySelectorAll(`[class]`))h(t)}};for(let n of e.children)t(n);let n=new MutationObserver(e=>{for(let n of e)if(n.type===`attributes`)h(n.target);else for(let e of n.addedNodes)t(e)});return n.observe(e,{subtree:!0,childList:!0,attributes:!0,attributeFilter:[`class`,`aria-checked`,`aria-pressed`]}),()=>n.disconnect()}function _(n){if(typeof document>`u`)throw Error(`mountSupport runs in a browser. On a server, use @signalsupport/sdk/server.`);let a=!n.support,o=n.support??e(n),s={report:!0,feedback:!0,faq:!0,reports:!!n.session,screenshot:!0,...n.features},c={...t,...n.copy},l=n.launcher!==!1&&{position:n.launcher?.position??`bottom-right`,label:n.launcher?.label},u=document.createElement(`div`);u.setAttribute(`data-signal-support`,``),(n.container??document.body).appendChild(u);let d=u.attachShadow({mode:`open`}),f=document.createElement(`div`);d.appendChild(f);let p=i({open:!1,history:[{name:`home`}],session:0,hidden:!1,picking:!1,theme:n.theme,slots:[],tick:0}),m=null,h=null,g=e=>{try{m||(m=new CSSStyleSheet,d.adoptedStyleSheets=[...d.adoptedStyleSheets,m]),m.replaceSync(e)}catch{h||(h=document.createElement(`style`),d.appendChild(h)),h.textContent=e}};n.css&&g(n.css);let _=new Map,v=e=>{if(!r.includes(e))throw Error(`Unknown slot "${e}". Slots: ${r.join(`, `)}.`);let t=_.get(e);return t||(t=document.createElement(`div`),t.setAttribute(`slot`,e),u.appendChild(t),_.set(e,t),p.set({slots:[..._.keys()]})),t};for(let[e,t]of Object.entries(n.slots??{}))t&&v(e).appendChild(t);let y=new Map,b=(e,t)=>{for(let n of y.get(e)??[])n(t)},x=!1,S=p.subscribe(()=>{let e=p.get().open;e!==x&&b(e?`open`:`close`),x=e}),C=o.getState().attention,w=o.subscribe(e=>{e.attention!==C&&b(`attention`,e.attention),C=e.attention}),T=!1,E=null,D=null,O=()=>(D??(D=import(`./app-7692BILt.js`).then(e=>{T||(E=e,e.renderPanel(f,d,{ui:p,support:o,copy:c,features:s,faq:n.faq??[],actions:n.actions??[],layout:n.layout??`drawer`,side:n.side??`right`,launcher:l,brand:n.brand,zIndex:n.zIndex??2147483e3,captureFrame:n.captureFrame,emit:b,root:d}))})),D),k=null;if(n.eager)O();else{let e=globalThis.requestIdleCallback;k=e?e(()=>void O(),{timeout:3e3}):setTimeout(()=>void O(),1500)}let A=(e=`home`)=>{p.set({open:!0,history:e===`home`?[{name:`home`}]:[{name:`home`},{name:e}],session:p.get().session+1}),O()};return{open:A,close:()=>p.set({open:!1}),toggle:e=>p.get().open?p.set({open:!1}):A(e),isOpen:()=>p.get().open,setContext:e=>o.setContext(e),setTheme:e=>p.set({theme:e}),setCss:g,slot:v,on(e,t){let n=y.get(e)??new Set;return n.add(t),y.set(e,n),e===`attention`&&t(o.getState().attention),()=>n.delete(t)},ready:O,support:o,destroy(){T=!0;let e=globalThis.cancelIdleCallback;k!==null&&(e??clearTimeout)(k),S(),w(),E?.unmountPanel(f),u.remove(),a&&o.destroy(),y.clear()}}}export{l as a,n as c,c as i,p as n,f as o,g as r,t as s,_ as t};
2
+ //# sourceMappingURL=widget-Bxsz5StK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget-Bxsz5StK.js","names":[],"sources":["../../src/widget/copy.ts","../../src/widget/ctx.ts","../../src/widget/store.ts","../../src/widget/theme.ts","../../src/widget/parts.ts","../../src/widget/index.ts"],"sourcesContent":["/**\n * Every word the panel says, overridable one at a time.\n *\n * Written from the reporter's side of the table. A product can rename\n * anything to fit its voice (\"Help\" instead of \"Support\", \"Ticket\" instead of\n * \"Report\") without touching layout.\n */\nexport const DEFAULT_COPY = {\n launcher: 'Help',\n title: 'Support',\n homeTitle: 'How can we help?',\n reportAction: 'Report an issue',\n reportHint: 'Something is broken or does the wrong thing',\n feedbackAction: 'Share feedback',\n feedbackHint: 'An idea, a frustration, or something that works well',\n faqAction: 'Browse the FAQs',\n faqHint: 'Answers to what people ask most',\n yourReports: 'Your reports',\n refresh: 'Check for updates',\n updatedAgo: 'Updated {ago}',\n upToDate: 'Up to date',\n seeAll: 'See all',\n signedOutReports: 'Sign in to follow your reports here.',\n fixesWaitingOne: 'A fix for one of your reports is ready. It needs your check.',\n fixesWaitingMany: 'Fixes for {n} of your reports are ready. They need your check.',\n questionWaiting: 'The team asked you something.',\n\n stepDescribe: 'Describe',\n stepAttach: 'Attach',\n stepReview: 'Review',\n whatLabel: 'What happened?',\n whatHint: 'What you did, and what the product did. Plain words are perfect.',\n whatPlaceholder: 'I selected three items and pressed Delete. Only one went away.',\n expectedLabel: 'What did you expect instead?',\n expectedHint: 'This is the half that says what to build. Two lines is plenty.',\n expectedPlaceholder: 'All three should have been deleted, the way they are from the menu.',\n severityLabel: 'How much is it holding you up?',\n whereLabel: 'Where in the product?',\n whereHint: 'Filled in from where you are. Change it if the problem is elsewhere.',\n attachIntro: 'Attach as much or as little as you like. Everything is shown before it is sent, and nothing is collected in the background.',\n screenshot: 'Screen capture',\n screenshotNone: 'Not captured. You choose what to share; this tab is offered first.',\n capture: 'Capture screen',\n retake: 'Retake',\n capturing: 'Capturing…',\n pickHint: 'Go to the screen you want to show',\n pickCapture: 'Capture',\n system: 'System and browser',\n trail: 'Your last steps',\n trailNone: 'Nothing recorded yet.',\n diagnostics: 'Diagnostics from the product',\n contactLabel: 'How can the team reach you?',\n contactHint: 'Optional. Without it, the team cannot reply to you.',\n namePlaceholder: 'Your name',\n emailPlaceholder: 'you@example.com',\n attached: 'Attached',\n notAttached: 'not attached',\n next: 'Next',\n back: 'Back',\n cancel: 'Cancel',\n submitReport: 'Send report',\n reportSent: 'Report sent',\n reportSentBody: 'You can follow it from here. When the team believes it is fixed, this is where you will be asked to check it.',\n reportSentAnon: 'Keep this reference. Quote it if you contact the team about this.',\n track: 'Follow this report',\n done: 'Done',\n\n feedbackKindLabel: 'What kind of feedback?',\n feedbackLabel: 'Tell us',\n feedbackPlaceholder: 'What you would change, and why it matters to you.',\n sendFeedback: 'Send feedback',\n thanks: 'Thank you',\n thanksBody: 'Feedback is read, and it is what decides what gets built next.',\n\n faqSearch: 'Search the FAQs',\n faqEmpty: 'Nothing matches.',\n faqNotFound: 'Did not find it?',\n\n allReports: 'All reports',\n noReports: 'Nothing reported yet',\n noReportsBody: 'When something does not behave the way you expect, this is where it goes, and where you watch what happens to it.',\n conversation: 'Conversation',\n replyPlaceholder: 'Reply to the team',\n send: 'Send',\n closedNote: 'This report is closed. Reporting it again opens a new one.',\n withdrawAction: 'Withdraw this report',\n withdrawTitle: 'Withdraw this report?',\n withdrawBody: 'The team stops working on it. This cannot be undone, but you can always report it again.',\n withdrawPlaceholder: 'Anything the team should know? (optional)',\n withdrawConfirm: 'Withdraw',\n withdrawKeep: 'Keep it',\n closedReports: 'Closed',\n nothingOpen: 'Nothing open right now.',\n verifyTitle: 'This should be fixed in the build you are running.',\n verifyBody: 'Try what went wrong and tell the team what you find. The report stays open until you do.',\n verifyYes: 'Yes, it works now',\n verifyNo: 'No, still happening',\n verifyNoPlaceholder: 'What is still happening? Anything that changed since you reported it helps.',\n verifySend: 'Send',\n updateTitle: 'Fixed in {version}. You are on {running}.',\n updateBody: 'Update to get the fix, then this report will ask you to check it.',\n whatYouReported: 'What you reported',\n team: 'Team',\n you: 'You',\n error: 'Something went wrong. Try again.',\n};\n\nexport type Copy = typeof DEFAULT_COPY;\n\nexport function fill(text: string, vars: Record<string, string | number>): string {\n return text.replace(/\\{(\\w+)\\}/g, (_, k: string) => String(vars[k] ?? `{${k}}`));\n}\n","import type { CaptureResult } from '../evidence.js';\nimport type { Support, SupportState } from '../support.js';\nimport type { Copy } from './copy.js';\n\nexport interface FaqItem {\n q: string;\n a: string;\n /** Extra words the search should match. */\n tags?: string[];\n}\n\nexport interface Features {\n report: boolean;\n feedback: boolean;\n faq: boolean;\n /** \"Your reports\". Needs a session; hidden when nobody is signed in. */\n reports: boolean;\n /** Offer a screen capture in the report and feedback forms. */\n screenshot: boolean;\n}\n\nexport type Screen =\n | { name: 'home' }\n | { name: 'report' }\n | { name: 'feedback' }\n | { name: 'faq' }\n | { name: 'reports' }\n | { name: 'detail'; id: string }\n | { name: 'sent'; id: string; ref: string }\n | { name: 'thanks' };\n\nexport type View = 'home' | 'report' | 'feedback' | 'faq' | 'reports';\n\n/**\n * Where a host's own content can go. Each is a native `<slot>`: what the host\n * puts there stays in ITS document, so its own CSS and its own framework\n * render it.\n */\nexport type SlotName = 'header' | 'home-top' | 'home-bottom' | 'report-top' | 'feedback-top' | 'sent' | 'footer';\nexport const SLOT_NAMES: readonly SlotName[] = ['header', 'home-top', 'home-bottom', 'report-top', 'feedback-top', 'sent', 'footer'];\n\n/** An extra entry under \"How can we help?\": a status page, a community, a sales line. */\nexport interface HomeAction {\n label: string;\n hint?: string;\n /** A built-in icon name (`external`, `book`, `message`, …) or an image URL. */\n icon?: string;\n /** Opened in a new tab. */\n href?: string;\n /** Called on press. The panel stays open; call the widget's `close()` to leave it. */\n onClick?: () => void;\n}\n\n/** What every screen is handed. */\nexport interface Ctx {\n support: Support;\n state: SupportState;\n /** Changes when the person asks for fresh data; a screen with its own copy reloads on it. */\n tick: number;\n copy: Copy;\n features: Features;\n faq: FaqItem[];\n actions: HomeAction[];\n /** The slots the host has filled; an empty one renders nothing, not a gap. */\n slots: ReadonlySet<string>;\n appVersion?: string;\n go: (screen: Screen) => void;\n /** Take one frame, stepping the panel out of its own picture. */\n capture: () => Promise<CaptureResult>;\n emit: (event: 'submitted' | 'verified' | 'withdrawn', detail: unknown) => void;\n}\n","import type { Screen } from './ctx.js';\nimport type { ThemeInput } from './theme.js';\n\n/**\n * The panel's own state, outside Preact.\n *\n * ⚠ IT LIVES IN THE SMALL SHELL, NOT IN THE PANEL CHUNK. The panel is loaded\n * lazily (see `index.ts`), and a product can call `open()` before that chunk\n * has arrived. Keeping the state here means the call is remembered and the\n * panel opens where it was asked to the moment it renders.\n */\n\nexport type Layout = 'drawer' | 'modal' | 'popover' | 'sheet';\n\nexport interface UiState {\n open: boolean;\n history: Screen[];\n /** Bumped per open: every open starts fresh, never mid-way through an abandoned form. */\n session: number;\n hidden: boolean;\n /** Capture mode: the panel is stepped aside (still mounted) and the capture bar is up. See `pick.ts`. */\n picking: boolean;\n theme: ThemeInput | undefined;\n /** Slot names the host has filled, so the panel only makes room for those. */\n slots: string[];\n /** Bumped by the refresh button: a screen holding its own copy (a report) reloads on it. */\n tick: number;\n}\n\nexport interface UiStore {\n get(): UiState;\n set(patch: Partial<UiState>): void;\n subscribe(fn: () => void): () => void;\n}\n\nexport function createUiStore(initial: UiState): UiStore {\n let state = initial;\n const listeners = new Set<() => void>();\n return {\n get: () => state,\n set(patch) {\n state = { ...state, ...patch };\n for (const l of listeners) l();\n },\n subscribe(fn) {\n listeners.add(fn);\n return () => listeners.delete(fn);\n },\n };\n}\n","/**\n * How the panel looks, as data.\n *\n * A theme is a set of tokens, and every pixel of the panel is drawn from them,\n * so a brand changes the whole thing by naming a few colours. Presets cover\n * the common kinds of product; anything not named falls back to the preset.\n * `scheme: 'auto'` follows the operating system and switches live.\n */\n\nexport interface Theme {\n /** `auto` follows the operating system. */\n scheme: 'dark' | 'light' | 'auto';\n /** The brand colour: primary buttons, the active step, focus. */\n accent: string;\n /** Text on the accent. */\n accentText: string;\n /** The panel itself. */\n background: string;\n /** Cards and inputs on the panel. */\n surface: string;\n /** A card on a card, hover. */\n raised: string;\n border: string;\n text: string;\n muted: string;\n dim: string;\n success: string;\n warning: string;\n danger: string;\n info: string;\n /** Corner radius of cards, in px. Buttons and inputs follow it. */\n radius: number;\n /** `inherit` uses the product's own font, which is usually what looks native. */\n font: string;\n mono: string;\n /** Base text size, in px. */\n fontSize: number;\n /** Panel width for the drawer and popover, in px. */\n width: number;\n shadow: string;\n /** The dimming behind a drawer, modal or sheet. */\n scrim: string;\n}\n\nexport type PresetName = 'midnight' | 'daylight' | 'studio' | 'paper';\n\nconst BASE = {\n radius: 12,\n font: 'inherit',\n mono: 'ui-monospace, SFMono-Regular, Menlo, Consolas, monospace',\n fontSize: 13,\n width: 440,\n};\n\n/** Dark and neutral, for tools people stare at all day. */\nconst midnight: Theme = {\n ...BASE,\n scheme: 'dark',\n accent: '#6d5efc',\n accentText: '#ffffff',\n background: '#0e1017',\n surface: '#151824',\n raised: '#1c2030',\n border: 'rgba(255,255,255,0.09)',\n text: '#eef1f8',\n muted: '#a3abc0',\n dim: '#7f889d',\n success: '#3ccf91',\n warning: '#f5a524',\n danger: '#ff5a6a',\n info: '#4fb8ff',\n shadow: '0 24px 70px -20px rgba(0,0,0,0.75)',\n scrim: 'rgba(4,5,10,0.55)',\n};\n\n/** Light and quiet, for SaaS and marketing sites. */\nconst daylight: Theme = {\n ...BASE,\n scheme: 'light',\n accent: '#2563eb',\n accentText: '#ffffff',\n background: '#ffffff',\n surface: '#f6f7f9',\n raised: '#eef0f4',\n border: 'rgba(15,23,42,0.11)',\n text: '#0f172a',\n muted: '#475569',\n dim: '#64748b',\n success: '#15803d',\n warning: '#b45309',\n danger: '#dc2626',\n info: '#0369a1',\n shadow: '0 24px 60px -24px rgba(15,23,42,0.35)',\n scrim: 'rgba(15,23,42,0.35)',\n};\n\n/** Creative tools: deep, saturated, compact. Close to Character XYZ. */\nconst studio: Theme = {\n ...BASE,\n scheme: 'dark',\n accent: '#7a50dd',\n accentText: '#ffffff',\n background: '#0d0b12',\n surface: '#15121d',\n raised: '#1d1927',\n border: 'rgba(255,255,255,0.08)',\n text: '#f3f0fa',\n muted: '#b4abc8',\n dim: '#8a819e',\n success: '#34d399',\n warning: '#fbbf24',\n danger: '#fb7185',\n info: '#a78bfa',\n radius: 10,\n fontSize: 12.5,\n width: 480,\n shadow: '-24px 0 60px -20px rgba(0,0,0,0.9)',\n scrim: 'rgba(0,0,0,0.55)',\n};\n\n/** Warm and editorial, for consumer apps and content products. */\nconst paper: Theme = {\n ...BASE,\n scheme: 'light',\n accent: '#c2410c',\n accentText: '#ffffff',\n background: '#fffdf8',\n surface: '#f7f2e8',\n raised: '#efe8da',\n border: 'rgba(68,48,20,0.14)',\n text: '#2b2118',\n muted: '#5c4b3a',\n dim: '#7a6754',\n success: '#3f7d3a',\n warning: '#a16207',\n danger: '#b91c1c',\n info: '#1d4ed8',\n radius: 18,\n fontSize: 14,\n width: 420,\n shadow: '0 30px 60px -30px rgba(68,48,20,0.45)',\n scrim: 'rgba(43,33,24,0.3)',\n};\n\nexport const PRESETS: Record<PresetName, Theme> = { midnight, daylight, studio, paper };\n\nexport type ThemeInput = PresetName | (Partial<Theme> & { preset?: PresetName });\n\n/**\n * The theme to draw with, right now.\n *\n * With `scheme: 'auto'` the colours come from the dark or light preset\n * matching the system, and only the brand colours the caller named carry\n * across, so a single `accent` produces a good panel in both.\n */\nexport function resolveTheme(input: ThemeInput | undefined, prefersDark: boolean): Theme {\n const spec = typeof input === 'string' ? { preset: input } : (input ?? {});\n const base = PRESETS[spec.preset ?? 'midnight'];\n const merged: Theme = { ...base, ...stripUndefined(spec) };\n if (merged.scheme !== 'auto') return merged;\n const scheme = prefersDark ? midnight : daylight;\n // Only what the caller named survives a scheme switch; everything else is\n // the matching preset, so text and surfaces always contrast.\n return { ...scheme, ...BASE, ...pick(spec, ['accent', 'accentText', 'radius', 'font', 'mono', 'fontSize', 'width']), scheme: 'auto' };\n}\n\nfunction stripUndefined<T extends object>(o: T): Partial<T> {\n return Object.fromEntries(Object.entries(o).filter(([k, v]) => v !== undefined && k !== 'preset')) as Partial<T>;\n}\n\nfunction pick<T extends object>(o: T, keys: string[]): Partial<T> {\n return Object.fromEntries(Object.entries(o).filter(([k, v]) => keys.includes(k) && v !== undefined)) as Partial<T>;\n}\n\n/** The theme as CSS custom properties on the panel's root. */\nexport function themeVars(t: Theme): string {\n const r = t.radius;\n return [\n `--sg-accent:${t.accent}`,\n `--sg-accent-text:${t.accentText}`,\n `--sg-bg:${t.background}`,\n `--sg-surface:${t.surface}`,\n `--sg-raised:${t.raised}`,\n `--sg-border:${t.border}`,\n `--sg-text:${t.text}`,\n `--sg-muted:${t.muted}`,\n `--sg-dim:${t.dim}`,\n `--sg-success:${t.success}`,\n `--sg-warning:${t.warning}`,\n `--sg-danger:${t.danger}`,\n `--sg-info:${t.info}`,\n `--sg-radius:${r}px`,\n `--sg-radius-sm:${Math.max(4, Math.round(r * 0.6))}px`,\n `--sg-font:${t.font}`,\n `--sg-mono:${t.mono}`,\n `--sg-size:${t.fontSize}px`,\n `--sg-width:${t.width}px`,\n `--sg-shadow:${t.shadow}`,\n `--sg-scrim:${t.scrim}`,\n `color-scheme:${t.scheme === 'light' ? 'light' : t.scheme === 'dark' ? 'dark' : 'light dark'}`,\n ].join(';');\n}\n","/**\n * The panel's PUBLIC styling surface: which internal elements are exposed to\n * the host page as `::part()` names.\n *\n * ```css\n * [data-signal-support]::part(launcher) { border-radius: 8px; }\n * [data-signal-support]::part(button-primary):hover { filter: none; }\n * ```\n *\n * ⚠ THE PART NAMES ARE THE CONTRACT; THE `sg-` CLASSES ARE NOT. Classes get\n * renamed when a screen is reworked. A part name only ever gains members, and\n * removing one is a breaking change to the package. That is why this is one\n * table, applied from one place, rather than a `part=` typed by hand on each\n * element: the next screen inherits it without anyone remembering to.\n *\n * `::part()` cannot be followed by an attribute selector, so states a host may\n * want to style differently get a part of their own (`chip-selected`,\n * `message-you`).\n */\nexport const PARTS: Record<string, string> = {\n 'sg-launcher': 'launcher',\n 'sg-pickbar': 'capture-bar',\n 'sg-badge': 'badge',\n 'sg-scrim': 'scrim',\n 'sg-panel': 'panel',\n 'sg-header': 'header',\n 'sg-title': 'title',\n 'sg-sub': 'subtitle',\n 'sg-iconbtn': 'icon-button',\n 'sg-body': 'body',\n 'sg-footer': 'footer',\n 'sg-section': 'section',\n 'sg-section-title': 'section-title',\n 'sg-action': 'action',\n 'sg-action-label': 'action-label',\n 'sg-action-hint': 'action-hint',\n 'sg-tile': 'tile',\n 'sg-btn': 'button',\n 'sg-btn-ghost': 'button-ghost',\n 'sg-btn-danger': 'button-danger',\n 'sg-link': 'link',\n 'sg-label': 'label',\n 'sg-hint': 'hint',\n 'sg-err': 'error',\n 'sg-input': 'input',\n 'sg-textarea': 'textarea',\n 'sg-chip': 'chip',\n 'sg-switch': 'switch',\n 'sg-pill': 'pill',\n 'sg-banner': 'banner',\n 'sg-card': 'card',\n 'sg-steps': 'steps',\n 'sg-row': 'row',\n 'sg-row-title': 'row-title',\n 'sg-rail': 'rail',\n 'sg-msg': 'message',\n 'sg-team': 'message-team',\n 'sg-you': 'message-you',\n 'sg-composer': 'composer',\n 'sg-faq': 'faq-item',\n 'sg-confirm': 'confirmation',\n 'sg-ref': 'reference',\n};\n\n/** The part names one element carries, from its classes and its state. */\nexport function partsFor(el: Element): string {\n const out: string[] = [];\n for (const c of el.classList) {\n const p = PARTS[c];\n if (p) out.push(p);\n }\n if (el.classList.contains('sg-btn') && !el.classList.contains('sg-btn-ghost') && !el.classList.contains('sg-btn-danger')) {\n out.push('button-primary');\n }\n if (el.classList.contains('sg-chip') && (el.getAttribute('aria-checked') === 'true' || el.getAttribute('aria-pressed') === 'true')) {\n out.push('chip-selected');\n }\n return out.join(' ');\n}\n\nfunction stamp(el: Element): void {\n const parts = partsFor(el);\n if (parts) {\n if (el.getAttribute('part') !== parts) el.setAttribute('part', parts);\n } else if (el.hasAttribute('part')) {\n el.removeAttribute('part');\n }\n}\n\n/**\n * Keep every element's `part` in step with its classes for the life of the\n * panel. Preact re-renders change classes and aria state in place, so this\n * watches those attributes as well as new elements.\n */\nexport function exposeParts(root: ShadowRoot): () => void {\n const walk = (node: Node) => {\n if (node instanceof Element) {\n stamp(node);\n for (const el of node.querySelectorAll('[class]')) stamp(el);\n }\n };\n for (const child of root.children) walk(child);\n const observer = new MutationObserver((records) => {\n for (const r of records) {\n if (r.type === 'attributes') stamp(r.target as Element);\n else for (const n of r.addedNodes) walk(n);\n }\n });\n observer.observe(root, { subtree: true, childList: true, attributes: true, attributeFilter: ['class', 'aria-checked', 'aria-pressed'] });\n return () => observer.disconnect();\n}\n","/**\n * `@signalsupport/sdk/widget` — the support panel, in any page.\n *\n * ```ts\n * import { mountSupport } from '@signalsupport/sdk/widget';\n *\n * const support = mountSupport({\n * key: process.env.NEXT_PUBLIC_SIGNAL_KEY_ID, // anonymous reports\n * session: '/api/signal/session', // signed-in reporters follow theirs\n * appVersion: '2.4.0',\n * context: { area: 'Editor' },\n * theme: { preset: 'midnight', accent: '#7a50dd' },\n * layout: 'drawer',\n * });\n *\n * support.open('report');\n * support.on('attention', (n) => badge(n));\n * ```\n *\n * It renders into a Shadow DOM: your CSS cannot restyle it by accident and\n * none of its CSS reaches your page. It works in React, Vue, Svelte or plain\n * HTML alike, because it owns its own element.\n *\n * # IT COSTS ALMOST NOTHING UNTIL SOMEONE NEEDS HELP\n *\n * This module is the shell: the element, the state and the badge count. The\n * panel itself (Preact, every screen, the stylesheet) is a separate chunk,\n * fetched when the browser is idle after the page has loaded, or the moment\n * `open()` is called, whichever comes first. A product's first paint never\n * waits for its support panel.\n */\nimport { createSupport, type Support, type SupportConfig } from '../support.js';\nimport { DEFAULT_COPY, type Copy } from './copy.js';\nimport { SLOT_NAMES, type FaqItem, type Features, type HomeAction, type SlotName, type View } from './ctx.js';\nimport { createUiStore, type Layout } from './store.js';\nimport type { ThemeInput } from './theme.js';\n\nexport { PRESETS, resolveTheme, type PresetName, type Theme, type ThemeInput } from './theme.js';\nexport { DEFAULT_COPY, type Copy } from './copy.js';\nexport type { FaqItem, Features, HomeAction, SlotName, View } from './ctx.js';\nexport { PARTS } from './parts.js';\nexport type { Layout } from './store.js';\n\nexport interface WidgetConfig extends SupportConfig {\n /** A preset name, or tokens on top of one: `{ preset: 'daylight', accent: '#e11d48' }`. */\n theme?: ThemeInput;\n /** `drawer` from the side (default), `modal`, `popover` above the launcher, or `sheet` from the bottom. Phones always get a sheet. */\n layout?: Layout;\n /** Which side a drawer slides from. */\n side?: 'left' | 'right';\n /** A floating button, or `false` when your own control calls `open()`. */\n launcher?: false | { position?: 'bottom-right' | 'bottom-left'; label?: string };\n brand?: { name?: string; logo?: string };\n features?: Partial<Features>;\n faq?: FaqItem[];\n copy?: Partial<Copy>;\n /** Where the panel's element goes. Default `document.body`. */\n container?: HTMLElement;\n zIndex?: number;\n /** Share a support instance you already created headlessly. */\n support?: Support;\n /** Load the panel immediately rather than when the browser is idle. */\n eager?: boolean;\n /**\n * Your own CSS, applied INSIDE the panel after its own, so it wins at equal\n * specificity. Target the `::part()` names from outside, or `[part~=\"button\"]`\n * and friends from here. The `sg-` class names are internal and may change.\n */\n css?: string;\n /** Your own content in the panel: an element per slot name. See `slot()`. */\n slots?: Partial<Record<SlotName, HTMLElement>>;\n /** Extra entries under \"How can we help?\", after the built-in ones. */\n actions?: HomeAction[];\n /**\n * Your own picture of your own view, for Capture, in place of the browser's\n * screen capture. For a product in a desktop shell, where `getDisplayMedia`\n * opens the shell's own permission dialog: resolve to an image data URL of\n * the window (WebView2 `CapturePreview`, Electron `capturePage`). Capture\n * mode is unchanged; there is simply no prompt. Omit it in a browser.\n */\n captureFrame?: () => Promise<string>;\n}\n\nexport type WidgetEvent = 'open' | 'close' | 'submitted' | 'verified' | 'withdrawn' | 'attention';\n\nexport interface SupportWidget {\n open(view?: View): void;\n close(): void;\n toggle(view?: View): void;\n isOpen(): boolean;\n /** Merge into the context sent with reports (where in the product, which project). */\n setContext(context: Record<string, unknown>): void;\n setTheme(theme: ThemeInput): void;\n /** Replace the CSS given as `css`, live. An empty string removes it. */\n setCss(css: string): void;\n /**\n * The element for one slot, created on first ask. Render into it with\n * anything: plain DOM, a React portal, Vue's Teleport. It lives in YOUR\n * document, so your CSS styles it.\n */\n slot(name: SlotName): HTMLElement;\n on(event: WidgetEvent, fn: (detail: unknown) => void): () => void;\n /** Resolves once the panel's code has loaded and rendered. */\n ready(): Promise<void>;\n /** The headless core, for your own badges and buttons. */\n readonly support: Support;\n destroy(): void;\n}\n\nexport function mountSupport(config: WidgetConfig): SupportWidget {\n if (typeof document === 'undefined') {\n throw new Error('mountSupport runs in a browser. On a server, use @signalsupport/sdk/server.');\n }\n const ownsSupport = !config.support;\n const support = config.support ?? createSupport(config);\n const features: Features = { report: true, feedback: true, faq: true, reports: !!config.session, screenshot: true, ...config.features };\n const copy: Copy = { ...DEFAULT_COPY, ...config.copy };\n const launcher =\n config.launcher === false ? false : { position: config.launcher?.position ?? 'bottom-right', label: config.launcher?.label };\n\n const host = document.createElement('div');\n host.setAttribute('data-signal-support', '');\n (config.container ?? document.body).appendChild(host);\n const root = host.attachShadow({ mode: 'open' });\n const mountPoint = document.createElement('div');\n root.appendChild(mountPoint);\n\n const ui = createUiStore({ open: false, history: [{ name: 'home' }], session: 0, hidden: false, picking: false, theme: config.theme, slots: [], tick: 0 });\n\n // The host's CSS. A constructable sheet where the browser has them, so a\n // strict Content-Security-Policy still applies it; the panel adds its own\n // sheet AHEAD of this one when it loads, so this one keeps winning.\n let hostSheet: CSSStyleSheet | null = null;\n let hostStyle: HTMLStyleElement | null = null;\n const setCss = (css: string) => {\n try {\n if (!hostSheet) {\n hostSheet = new CSSStyleSheet();\n root.adoptedStyleSheets = [...root.adoptedStyleSheets, hostSheet];\n }\n hostSheet.replaceSync(css);\n } catch {\n if (!hostStyle) {\n hostStyle = document.createElement('style');\n root.appendChild(hostStyle);\n }\n hostStyle.textContent = css;\n }\n };\n if (config.css) setCss(config.css);\n\n const slotEls = new Map<SlotName, HTMLElement>();\n const slot = (name: SlotName): HTMLElement => {\n if (!SLOT_NAMES.includes(name)) throw new Error(`Unknown slot \"${name}\". Slots: ${SLOT_NAMES.join(', ')}.`);\n let el = slotEls.get(name);\n if (!el) {\n el = document.createElement('div');\n el.setAttribute('slot', name);\n host.appendChild(el);\n slotEls.set(name, el);\n ui.set({ slots: [...slotEls.keys()] });\n }\n return el;\n };\n for (const [name, content] of Object.entries(config.slots ?? {}) as [SlotName, HTMLElement][]) {\n if (content) slot(name).appendChild(content);\n }\n const listeners = new Map<WidgetEvent, Set<(d: unknown) => void>>();\n const emit = (event: string, detail?: unknown) => {\n for (const fn of listeners.get(event as WidgetEvent) ?? []) fn(detail);\n };\n\n let wasOpen = false;\n const offUi = ui.subscribe(() => {\n const open = ui.get().open;\n if (open !== wasOpen) emit(open ? 'open' : 'close');\n wasOpen = open;\n });\n let lastAttention = support.getState().attention;\n const offAttention = support.subscribe((s) => {\n if (s.attention !== lastAttention) emit('attention', s.attention);\n lastAttention = s.attention;\n });\n\n let destroyed = false;\n let panel: typeof import('./app.js') | null = null;\n let loading: Promise<void> | null = null;\n const load = (): Promise<void> => {\n loading ??= import('./app.js').then((m) => {\n if (destroyed) return;\n panel = m;\n m.renderPanel(mountPoint, root, {\n ui,\n support,\n copy,\n features,\n faq: config.faq ?? [],\n actions: config.actions ?? [],\n layout: config.layout ?? 'drawer',\n side: config.side ?? 'right',\n launcher,\n brand: config.brand,\n zIndex: config.zIndex ?? 2147483000,\n captureFrame: config.captureFrame,\n emit,\n root,\n });\n });\n return loading;\n };\n\n // After the page has settled, so the panel never competes with the\n // product's own first paint. `open()` short-circuits this.\n let idle: number | null = null;\n if (config.eager) {\n void load();\n } else {\n const ric = (globalThis as { requestIdleCallback?: (cb: () => void, o?: { timeout: number }) => number }).requestIdleCallback;\n idle = ric ? ric(() => void load(), { timeout: 3000 }) : (setTimeout(() => void load(), 1500) as unknown as number);\n }\n\n const openAt = (view: View = 'home') => {\n ui.set({\n open: true,\n history: view === 'home' ? [{ name: 'home' }] : [{ name: 'home' }, { name: view }],\n session: ui.get().session + 1,\n });\n void load();\n };\n\n return {\n open: openAt,\n close: () => ui.set({ open: false }),\n toggle: (view) => (ui.get().open ? ui.set({ open: false }) : openAt(view)),\n isOpen: () => ui.get().open,\n setContext: (context) => support.setContext(context),\n setTheme: (theme) => ui.set({ theme }),\n setCss,\n slot,\n on(event, fn) {\n const set = listeners.get(event) ?? new Set();\n set.add(fn);\n listeners.set(event, set);\n if (event === 'attention') fn(support.getState().attention);\n return () => set.delete(fn);\n },\n ready: load,\n support,\n destroy() {\n destroyed = true;\n const cancel = (globalThis as { cancelIdleCallback?: (id: number) => void }).cancelIdleCallback;\n if (idle !== null) (cancel ?? clearTimeout)(idle);\n offUi();\n offAttention();\n panel?.unmountPanel(mountPoint);\n host.remove();\n if (ownsSupport) support.destroy();\n listeners.clear();\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ import { type Consent } from './evidence.js';
2
+ import type { Impact, SignalType, Urgency } from './model.js';
3
+ import { type SessionCache, type SessionSource } from './session.js';
4
+ /**
5
+ * Filing a signal.
6
+ *
7
+ * # THE KEY DECIDES THE SOURCE. THE BODY NEVER DOES.
8
+ *
9
+ * A credential belongs to exactly one source, so the service reads the source
10
+ * off the key (or the reporter session) and the request body cannot name one.
11
+ * `product` is accepted because it is useful for telling apart two call sites
12
+ * that share a key, and it travels in `source_context`, where it cannot change
13
+ * which source a signal is filed against.
14
+ */
15
+ export type ReporterKind = 'person' | 'monitor' | 'agent';
16
+ export interface SignalUser {
17
+ /** The product's own id for this person. Signal never mints one. */
18
+ id?: string;
19
+ name?: string;
20
+ email?: string;
21
+ }
22
+ import { DEFAULT_API_BASE } from './constants.js';
23
+ export { DEFAULT_API_BASE };
24
+ export interface InitOptions {
25
+ /**
26
+ * The publishable key (`sig_pub_…`) in a browser, or a secret key (`sig_live_…`) on a
27
+ * server. Optional when `session` is given: a signed-in person files through
28
+ * their session instead.
29
+ */
30
+ key?: string;
31
+ /**
32
+ * Your server's route that mints a reporter session (see `./server`), or a
33
+ * function returning one. With it, reports are filed as the signed-in person
34
+ * and they can see and follow them; without it, reports are anonymous.
35
+ */
36
+ session?: SessionSource;
37
+ /** Where the API lives. Defaults to Signal's hosted service. */
38
+ apiBase?: string;
39
+ /** A LABEL, not the source. Travels in `source_context`. */
40
+ product?: string;
41
+ /** Who is reporting, when there is no session. */
42
+ user?: SignalUser;
43
+ consent?: Partial<Consent>;
44
+ /** Attached to every report as `source_context`: where, which build, which project. */
45
+ context?: Record<string, unknown>;
46
+ /** Injected by tests. Defaults to the platform `fetch`. */
47
+ fetchImpl?: typeof fetch;
48
+ }
49
+ export interface ReportInput {
50
+ type?: SignalType;
51
+ subject: string;
52
+ body?: string;
53
+ impact?: Impact;
54
+ urgency?: Urgency;
55
+ reporter?: SignalUser;
56
+ /** person | monitor | agent. Software is a first-class reporter. */
57
+ reporterKind?: ReporterKind;
58
+ /** One frame the reporter chose to take, as a data: URL (see `captureScreen`). */
59
+ screenshot?: string;
60
+ /** A key in your own storage, when you upload frames yourself. */
61
+ screenshotKey?: string;
62
+ /** The last few things that happened (see `createTrail`). */
63
+ trail?: unknown;
64
+ /** Your own diagnostics. */
65
+ extra?: Record<string, unknown>;
66
+ consent?: Partial<Consent>;
67
+ context?: Record<string, unknown>;
68
+ /**
69
+ * Safe to retry with. Generated when omitted, so a dropped connection mid-POST
70
+ * cannot file the same report twice: the duplicate a reporter would SEE.
71
+ */
72
+ idempotencyKey?: string;
73
+ }
74
+ export interface ReportResult {
75
+ id: string;
76
+ as_of: string;
77
+ }
78
+ export declare class SignalClient {
79
+ private options;
80
+ private sessionCache;
81
+ init(options: InitOptions): this;
82
+ /** Change who is reporting, without re-initialising. */
83
+ setUser(user: SignalUser | null): this;
84
+ /** Merge into the context sent with every report. */
85
+ setContext(context: Record<string, unknown>): this;
86
+ get initialised(): boolean;
87
+ get apiBase(): string;
88
+ /** The session cache, shared with the reporter API. */
89
+ get session(): SessionCache;
90
+ report(input: ReportInput): Promise<ReportResult>;
91
+ }
92
+ export declare function newIdempotencyKey(): string;
93
+ /**
94
+ * The default instance, which is what the published snippet uses: an install
95
+ * has one key and one product. `createClient()` is there for a server
96
+ * reporting on behalf of several sources.
97
+ */
98
+ export declare const Signal: SignalClient;
99
+ export declare function createClient(options: InitOptions): SignalClient;
@@ -0,0 +1,2 @@
1
+ /** Signal's hosted API. Every entry defaults to it; each takes `apiBase` to point elsewhere. */
2
+ export declare const DEFAULT_API_BASE = "https://signal-api.character.xyz";
@@ -0,0 +1,25 @@
1
+ /** A refusal the caller can act on, rather than a thrown string. */
2
+ export declare class SignalError extends Error {
3
+ readonly code: string;
4
+ readonly status?: number;
5
+ /** The offending field, on a 422. */
6
+ readonly path?: string;
7
+ /** Seconds to wait, on a 429. */
8
+ readonly retryAfter?: number;
9
+ constructor(code: string, message: string, opts?: {
10
+ status?: number;
11
+ path?: string;
12
+ retryAfter?: number;
13
+ });
14
+ /**
15
+ * Whether trying the same call again could plausibly work.
16
+ *
17
+ * A 422 never can: the body is wrong and will be wrong again. A 401 never
18
+ * can: the key is wrong. Those two are the ones an unattended caller most
19
+ * needs told, because retrying them is how an expensive loop starts.
20
+ */
21
+ get retryable(): boolean;
22
+ }
23
+ /** Read Signal's `{ message, data }` / `{ error }` envelope, or throw a SignalError. */
24
+ export declare function readEnvelope<T>(response: Response): Promise<T>;
25
+ export declare function transportError(base: string, cause: unknown): SignalError;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * What a report can carry besides words, and the reporter's say over each part.
3
+ *
4
+ * # CONSENT IS A DEFAULT YOU CAN SEE, NOT ONE BURIED IN A DOC
5
+ *
6
+ * Screenshot, system, browser, network and the action trail are ON; locale is
7
+ * OFF, because a timezone plus a language narrows a person down a great deal
8
+ * and is never what makes a report actionable. Anything switched off is sent
9
+ * by name in `withheld`, because the console renders "the reporter declined
10
+ * this" differently from "nobody collected this", and only one of the two is
11
+ * worth asking about.
12
+ */
13
+ export interface Consent {
14
+ /** A screen frame. Only ever one the reporter chose to take. */
15
+ screenshot: boolean;
16
+ /** Platform, device memory, hardware concurrency. */
17
+ system: boolean;
18
+ /** User agent, viewport, pixel ratio. */
19
+ browser: boolean;
20
+ /** Connection type and rough speed, where the browser exposes it. */
21
+ network: boolean;
22
+ /** Timezone and language. OFF by default: it is the one that locates a person. */
23
+ locale: boolean;
24
+ /** The last few things that happened, as the product recorded them. */
25
+ trail: boolean;
26
+ /** The product's own diagnostics (a GPU report, the open project). */
27
+ extra: boolean;
28
+ }
29
+ /** The published defaults, exactly. */
30
+ export declare const DEFAULT_CONSENT: Readonly<Consent>;
31
+ export declare function resolveConsent(...layers: (Partial<Consent> | undefined)[]): Consent;
32
+ export declare function readSystem(): Record<string, unknown> | undefined;
33
+ export declare function readBrowser(): Record<string, unknown> | undefined;
34
+ export declare function readNetwork(): Record<string, unknown> | undefined;
35
+ export declare function readLocale(): Record<string, unknown> | undefined;
36
+ /** "Windows · 8 cores · 16 GB", for showing the reporter what is about to be sent. */
37
+ export declare function describeSystem(s?: Record<string, unknown> | undefined): string;
38
+ export declare function describeBrowser(b?: Record<string, unknown> | undefined): string;
39
+ /** Supplied by the host, collected only when the reporter reaches the Attach step. */
40
+ export type Diagnostics = Record<string, () => unknown | Promise<unknown>>;
41
+ export declare function readDiagnostics(d: Diagnostics | undefined): Promise<Record<string, unknown> | undefined>;
42
+ export interface EvidenceInput {
43
+ /** A frame the reporter chose to take, as a data: URL. */
44
+ screenshot?: string;
45
+ /** A key in the product's own storage, when it uploads frames itself. */
46
+ screenshotKey?: string;
47
+ trail?: unknown;
48
+ extra?: Record<string, unknown>;
49
+ }
50
+ /** Collect what consent allows, and name what it does not. */
51
+ export declare function collectEvidence(consent: Consent, supplied?: EvidenceInput): Record<string, unknown>;
52
+ export declare const CAPTURE_MAX_EDGE = 1600;
53
+ export declare const CAPTURE_QUALITY = 0.72;
54
+ /** Below the service's 2 MB cap with room for base64. */
55
+ export declare const CAPTURE_MAX_BYTES = 1400000;
56
+ export type CaptureResult = {
57
+ ok: true;
58
+ dataUrl: string;
59
+ width: number;
60
+ height: number;
61
+ bytes: number;
62
+ mime: string;
63
+ } | {
64
+ ok: false;
65
+ reason: 'unsupported' | 'cancelled' | 'failed';
66
+ message: string;
67
+ };
68
+ /**
69
+ * The host's own picture of its own view, in place of `getDisplayMedia`.
70
+ *
71
+ * For a product inside a desktop shell (WebView2, Electron), where the browser's
72
+ * capture opens the shell's own permission dialog, unstyled and often wider than
73
+ * the window. The shell renders its webview itself (`CapturePreview`,
74
+ * `capturePage`) and hands back an image URL; there is no prompt, because it is
75
+ * the product's own pixels taken when the person pressed Capture.
76
+ *
77
+ * Same hide-first contract as `captureScreen`, same encoding, bounded waits.
78
+ */
79
+ export declare function captureFrom(source: () => Promise<string>, hooks?: {
80
+ beforeCapture?: () => void;
81
+ afterCapture?: () => void;
82
+ }): Promise<CaptureResult>;
83
+ export declare function captureScreen(hooks?: {
84
+ beforeCapture?: () => void;
85
+ afterCapture?: () => void;
86
+ }): Promise<CaptureResult>;
87
+ export declare function formatBytes(n: number): string;
package/dist/iife.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The script-tag build: `window.SignalSupport`, for pages without a bundler.
3
+ *
4
+ * ```html
5
+ * <script src="https://unpkg.com/@signalsupport/sdk/dist/signal.iife.js"></script>
6
+ * <script>
7
+ * SignalSupport.mountSupport({ key: 'sig_pub_…', theme: 'daylight', layout: 'popover' });
8
+ * </script>
9
+ * ```
10
+ */
11
+ export { mountSupport, PRESETS, DEFAULT_COPY } from './widget/index.js';
12
+ export { createSupport, Signal, createClient, captureScreen, statusView } from './index.js';
@@ -0,0 +1,21 @@
1
+ /**
2
+ * `@signalsupport/sdk` — support inside your product, backed by Signal.
3
+ *
4
+ * Three ways in, smallest first:
5
+ *
6
+ * - `Signal.report({ subject })` files a signal from anywhere (browser or server).
7
+ * - `createSupport(config)` is the whole support loop without a UI: file with
8
+ * evidence, follow your reports, read and answer the team, confirm the fix.
9
+ * - `mountSupport(config)` from `@signalsupport/sdk/widget` is that loop
10
+ * with a themeable panel that drops into any page.
11
+ *
12
+ * Reporter sessions are minted on your server with `@signalsupport/sdk/server`.
13
+ */
14
+ export { SignalError } from './errors.js';
15
+ export { Signal, SignalClient, createClient, newIdempotencyKey, DEFAULT_API_BASE, type InitOptions, type ReportInput, type ReportResult, type ReporterKind, type SignalUser, } from './client.js';
16
+ export { DEFAULT_CONSENT, collectEvidence, resolveConsent, captureScreen, captureFrom, describeBrowser, describeSystem, readBrowser, readSystem, readDiagnostics, formatBytes, type Consent, type CaptureResult, type Diagnostics, type EvidenceInput, } from './evidence.js';
17
+ export { DEFECT_STAGES, FEEDBACK_KINDS, FEEDBACK_STAGES, MIN_DESCRIPTION, SEVERITY, awaitingUpdate, canVerify, canWithdraw, compareVersions, eventWords, formatAgo, readWords, threadOf, reportParts, sortReports, statusView, validateIssue, type FeedbackKind, type Impact, type ReporterEvent, type ReporterReport, type ReporterReportDetail, type Severity, type SignalStatus, type SignalType, type StatusView, type ThreadItem, type Tone, type Urgency, } from './model.js';
18
+ export { createSessionCache, type SessionCache, type SessionSource, type SessionToken } from './session.js';
19
+ export { createReporterApi, type ReporterApi } from './reporter.js';
20
+ export { createTrail, type Trail, type TrailEntry, type TrailKind } from './trail.js';
21
+ export { createSupport, refOf, type Collected, type FeedbackDraft, type IssueDraft, type Support, type SupportConfig, type SupportState, } from './support.js';
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import{n as e,t}from"./chunks/constants-ClGbEpjT.js";import{A as n,C as r,D as i,E as a,F as o,I as s,L as c,M as l,N as u,O as d,P as f,S as p,T as m,_ as h,a as g,b as _,c as v,d as y,f as b,g as x,h as S,i as C,j as w,k as T,l as E,m as D,n as O,o as k,p as A,r as j,s as M,t as N,u as P,v as F,w as I,x as L,y as R}from"./chunks/support-x7N-k02b.js";export{t as DEFAULT_API_BASE,i as DEFAULT_CONSENT,g as DEFECT_STAGES,k as FEEDBACK_KINDS,M as FEEDBACK_STAGES,v as MIN_DESCRIPTION,E as SEVERITY,p as Signal,r as SignalClient,e as SignalError,P as awaitingUpdate,y as canVerify,b as canWithdraw,d as captureFrom,T as captureScreen,n as collectEvidence,A as compareVersions,I as createClient,C as createReporterApi,a as createSessionCache,N as createSupport,j as createTrail,w as describeBrowser,l as describeSystem,D as eventWords,S as formatAgo,u as formatBytes,m as newIdempotencyKey,f as readBrowser,o as readDiagnostics,s as readSystem,x as readWords,O as refOf,h as reportParts,c as resolveConsent,F as sortReports,R as statusView,_ as threadOf,L as validateIssue};