@mhosaic/feedback 0.15.6 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,180 @@
1
+ // src/loader/bundleLoader.ts
2
+ var SCRIPT_DATA_ATTR = "data-mhosaic-feedback-bundle";
3
+ async function injectBundle(opts) {
4
+ if (window.MhosaicFeedback?.createFeedback) {
5
+ return window.MhosaicFeedback;
6
+ }
7
+ const existing = document.querySelector(
8
+ `script[${SCRIPT_DATA_ATTR}]`
9
+ );
10
+ if (existing) {
11
+ return waitForGlobal(opts.timeoutMs ?? 3e4);
12
+ }
13
+ return new Promise((resolve, reject) => {
14
+ const script = document.createElement("script");
15
+ script.src = opts.bundleUrl;
16
+ script.integrity = opts.sriHash;
17
+ script.crossOrigin = "anonymous";
18
+ script.async = true;
19
+ script.setAttribute(SCRIPT_DATA_ATTR, "1");
20
+ script.onload = () => {
21
+ if (window.MhosaicFeedback?.createFeedback) {
22
+ resolve(window.MhosaicFeedback);
23
+ } else {
24
+ reject(
25
+ new Error(
26
+ "mhosaic-feedback: bundle loaded but window.MhosaicFeedback.createFeedback is missing \u2014 bundle/loader version mismatch?"
27
+ )
28
+ );
29
+ }
30
+ };
31
+ script.onerror = () => {
32
+ reject(
33
+ new Error(
34
+ `mhosaic-feedback: failed to load bundle from ${opts.bundleUrl} (network, CSP, or SRI mismatch)`
35
+ )
36
+ );
37
+ };
38
+ document.head.appendChild(script);
39
+ const timeoutMs = opts.timeoutMs ?? 3e4;
40
+ setTimeout(() => {
41
+ reject(
42
+ new Error(`mhosaic-feedback: bundle load timeout after ${timeoutMs}ms`)
43
+ );
44
+ }, timeoutMs);
45
+ });
46
+ }
47
+ function waitForGlobal(timeoutMs) {
48
+ return new Promise((resolve, reject) => {
49
+ const start = Date.now();
50
+ const tick = () => {
51
+ if (window.MhosaicFeedback?.createFeedback) {
52
+ resolve(window.MhosaicFeedback);
53
+ return;
54
+ }
55
+ if (Date.now() - start > timeoutMs) {
56
+ reject(
57
+ new Error(
58
+ "mhosaic-feedback: timed out waiting for bundle global (another loader instance started the fetch but never finished)"
59
+ )
60
+ );
61
+ return;
62
+ }
63
+ setTimeout(tick, 50);
64
+ };
65
+ tick();
66
+ });
67
+ }
68
+
69
+ // src/loader/manifest.ts
70
+ async function fetchManifest(endpoint, apiKey, signal) {
71
+ const base = endpoint.replace(/\/$/, "");
72
+ const url = `${base}/api/feedback/v1/widget-manifest/?pk=${encodeURIComponent(apiKey)}`;
73
+ const init = { credentials: "omit" };
74
+ if (signal) init.signal = signal;
75
+ const res = await fetch(url, init);
76
+ if (!res.ok) {
77
+ throw new Error(
78
+ `mhosaic-feedback: manifest fetch failed (HTTP ${res.status})`
79
+ );
80
+ }
81
+ const body = await res.json();
82
+ return body;
83
+ }
84
+
85
+ // src/loader/deferredApi.ts
86
+ function createDeferredApi() {
87
+ let real = null;
88
+ let failure = null;
89
+ const queue = [];
90
+ function flush(item) {
91
+ if (real) {
92
+ if (item.kind === "void") {
93
+ ;
94
+ real[item.method]();
95
+ } else if (item.kind === "arg") {
96
+ ;
97
+ real[item.method](item.arg);
98
+ } else {
99
+ Promise.resolve(real.submit(item.payload)).then(
100
+ item.resolve,
101
+ item.reject
102
+ );
103
+ }
104
+ return;
105
+ }
106
+ if (failure) {
107
+ if (item.kind === "submit") item.reject(failure);
108
+ return;
109
+ }
110
+ queue.push(item);
111
+ }
112
+ return {
113
+ show() {
114
+ flush({ kind: "void", method: "show" });
115
+ },
116
+ hide() {
117
+ flush({ kind: "void", method: "hide" });
118
+ },
119
+ shutdown() {
120
+ flush({ kind: "void", method: "shutdown" });
121
+ },
122
+ open(arg) {
123
+ flush({ kind: "arg", method: "open", arg });
124
+ },
125
+ identify(arg) {
126
+ flush({ kind: "arg", method: "identify", arg });
127
+ },
128
+ setMetadata(arg) {
129
+ flush({ kind: "arg", method: "setMetadata", arg });
130
+ },
131
+ submit(payload) {
132
+ return new Promise((resolve, reject) => {
133
+ flush({ kind: "submit", payload, resolve, reject });
134
+ });
135
+ },
136
+ _attach(impl) {
137
+ real = impl;
138
+ while (queue.length > 0) flush(queue.shift());
139
+ },
140
+ _fail(err) {
141
+ failure = err;
142
+ for (const item of queue) {
143
+ if (item.kind === "submit") item.reject(err);
144
+ }
145
+ queue.length = 0;
146
+ }
147
+ };
148
+ }
149
+
150
+ // src/loader/index.ts
151
+ function createFeedback(config) {
152
+ const deferred = createDeferredApi();
153
+ loadAndAttach(config, deferred).catch((err) => {
154
+ console.warn("[mhosaic-feedback] widget did not load:", err.message);
155
+ deferred._fail(err);
156
+ });
157
+ return deferred;
158
+ }
159
+ async function loadAndAttach(config, deferred) {
160
+ const manifest = await fetchManifest(config.endpoint, config.apiKey);
161
+ if (!manifest.enabled || !manifest.bundle_url || !manifest.sri_hash) {
162
+ deferred._fail(
163
+ new Error(
164
+ `mhosaic-feedback: widget disabled for this project${manifest.detail ? ` \u2014 ${manifest.detail}` : ""}`
165
+ )
166
+ );
167
+ return;
168
+ }
169
+ const bundle = await injectBundle({
170
+ bundleUrl: manifest.bundle_url,
171
+ sriHash: manifest.sri_hash
172
+ });
173
+ const real = bundle.createFeedback(config);
174
+ deferred._attach(real);
175
+ }
176
+
177
+ export {
178
+ createFeedback
179
+ };
180
+ //# sourceMappingURL=chunk-6JDT2KWQ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/loader/bundleLoader.ts","../src/loader/manifest.ts","../src/loader/deferredApi.ts","../src/loader/index.ts"],"sourcesContent":["/**\n * Inject the widget bundle as an SRI-protected <script> and wait for its\n * window global to appear.\n *\n * Why a <script> tag and not `import()`:\n * `import()` can fetch cross-origin ESM but doesn't honor Subresource\n * Integrity on the dynamic-import call itself. A <script> tag with\n * `integrity=…` is the only path where the browser actually refuses\n * to execute a bundle whose bytes don't match the manifest's hash.\n *\n * The bundle is built with `tsup --format iife --globalName MhosaicFeedback`,\n * so once it parses it sets `window.MhosaicFeedback = { createFeedback }`\n * (per `src/widget.ts`'s exports). We wait for that to appear.\n *\n * Idempotency: if multiple FeedbackProviders mount on the same page (e.g.\n * during a React fast-refresh cycle), we reuse an already-injected bundle\n * instead of fetching a second copy.\n */\n\nimport type { FeedbackApi, FeedbackConfig } from '../types'\n\ndeclare global {\n interface Window {\n MhosaicFeedback?: {\n createFeedback(config: FeedbackConfig): FeedbackApi\n }\n }\n}\n\nconst SCRIPT_DATA_ATTR = 'data-mhosaic-feedback-bundle'\n\nexport interface BundleLoadOptions {\n bundleUrl: string\n sriHash: string\n /** Default 30 s. Long enough for slow networks; short enough to surface\n * CDN outages quickly. */\n timeoutMs?: number\n}\n\nexport interface BundleHandle {\n createFeedback(config: FeedbackConfig): FeedbackApi\n}\n\nexport async function injectBundle(\n opts: BundleLoadOptions,\n): Promise<BundleHandle> {\n // Reuse a bundle already loaded this page.\n if (window.MhosaicFeedback?.createFeedback) {\n return window.MhosaicFeedback\n }\n\n const existing = document.querySelector<HTMLScriptElement>(\n `script[${SCRIPT_DATA_ATTR}]`,\n )\n if (existing) {\n // Another loader instance started the fetch; wait for the global.\n return waitForGlobal(opts.timeoutMs ?? 30_000)\n }\n\n return new Promise<BundleHandle>((resolve, reject) => {\n const script = document.createElement('script')\n script.src = opts.bundleUrl\n script.integrity = opts.sriHash\n script.crossOrigin = 'anonymous'\n script.async = true\n script.setAttribute(SCRIPT_DATA_ATTR, '1')\n script.onload = () => {\n if (window.MhosaicFeedback?.createFeedback) {\n resolve(window.MhosaicFeedback)\n } else {\n reject(\n new Error(\n 'mhosaic-feedback: bundle loaded but window.MhosaicFeedback.createFeedback is missing — bundle/loader version mismatch?',\n ),\n )\n }\n }\n script.onerror = () => {\n reject(\n new Error(\n `mhosaic-feedback: failed to load bundle from ${opts.bundleUrl} ` +\n '(network, CSP, or SRI mismatch)',\n ),\n )\n }\n document.head.appendChild(script)\n\n const timeoutMs = opts.timeoutMs ?? 30_000\n setTimeout(() => {\n reject(\n new Error(`mhosaic-feedback: bundle load timeout after ${timeoutMs}ms`),\n )\n }, timeoutMs)\n })\n}\n\nfunction waitForGlobal(timeoutMs: number): Promise<BundleHandle> {\n return new Promise((resolve, reject) => {\n const start = Date.now()\n const tick = () => {\n if (window.MhosaicFeedback?.createFeedback) {\n resolve(window.MhosaicFeedback)\n return\n }\n if (Date.now() - start > timeoutMs) {\n reject(\n new Error(\n 'mhosaic-feedback: timed out waiting for bundle global (another loader instance started the fetch but never finished)',\n ),\n )\n return\n }\n setTimeout(tick, 50)\n }\n tick()\n })\n}\n","/**\n * Fetch the widget-manifest from the Mhosaic backend.\n *\n * The loader calls this on every page load. The backend reads `Project.\n * pinned_version` (or current stable) + `Project.widget_enabled` and\n * returns the bundle URL + SRI hash for the version this tenant should\n * receive — or `{enabled: false}` if the widget is killed for the tenant.\n *\n * Network failures are surfaced to the caller. The loader degrades to\n * \"widget not present\" rather than crashing the host page.\n */\n\nexport interface Manifest {\n /** False when the widget is disabled for this project (kill switch) or\n * no stable release exists. Loader bails silently. */\n enabled: boolean\n /** Semver of the bundle being served. Present when enabled=true. */\n version?: string\n /** Fully-qualified URL to the version-pinned bundle (jsDelivr). */\n bundle_url?: string\n /** Subresource integrity hash for the bundle. The loader injects the\n * script tag with `integrity=…` so the browser refuses to run a\n * bundle whose bytes don't match. */\n sri_hash?: string\n /** Static config the manifest endpoint passes through to the widget. */\n config?: {\n endpoint: string\n project_slug: string\n share_reports_with_widget: boolean\n }\n /** Human-readable detail when `enabled` is false. */\n detail?: string\n}\n\nexport async function fetchManifest(\n endpoint: string,\n apiKey: string,\n signal?: AbortSignal,\n): Promise<Manifest> {\n const base = endpoint.replace(/\\/$/, '')\n const url = `${base}/api/feedback/v1/widget-manifest/?pk=${encodeURIComponent(apiKey)}`\n // credentials: 'omit' — we authenticate via the public pk_proj_ query\n // param; sending cookies would just trigger CORS preflights for no\n // reason and is unnecessary since the manifest endpoint doesn't read\n // any session.\n const init: RequestInit = { credentials: 'omit' }\n if (signal) init.signal = signal\n const res = await fetch(url, init)\n if (!res.ok) {\n throw new Error(\n `mhosaic-feedback: manifest fetch failed (HTTP ${res.status})`,\n )\n }\n const body = (await res.json()) as Manifest\n return body\n}\n","/**\n * A FeedbackApi-shaped object that queues method calls until the real\n * widget bundle finishes loading, then drains the queue against the real\n * implementation.\n *\n * Why we need this: hosts that already have code like\n * const fb = createFeedback({...})\n * fb.identify({id, email, name})\n * useEffect(() => fb.open(...), [])\n * shouldn't have to refactor for async. The loader's createFeedback\n * returns sync — a deferred handle that records intent, then replays it\n * once the real bundle arrives. Idiomatic queue-then-flush pattern.\n *\n * If the bundle never loads (network failure, SRI mismatch, kill switch),\n * `_fail()` is called instead. Void methods (identify/setMetadata/open/...)\n * silently drop in that case — the widget just doesn't appear. The\n * `submit` Promise rejects so callers can surface real errors.\n */\n\nimport type {\n FeedbackApi,\n ReportPayload,\n SubmittedReport,\n UserIdentity,\n} from '../types'\n\ntype VoidMethod = 'show' | 'hide' | 'shutdown'\ntype ArgMethod = 'open' | 'identify' | 'setMetadata'\n\ntype Queued =\n | { kind: 'void'; method: VoidMethod }\n | { kind: 'arg'; method: 'open'; arg: Parameters<FeedbackApi['open']>[0] }\n | { kind: 'arg'; method: 'identify'; arg: UserIdentity }\n | { kind: 'arg'; method: 'setMetadata'; arg: Record<string, unknown> }\n | {\n kind: 'submit'\n payload: Partial<ReportPayload> & { description: string }\n resolve: (r: SubmittedReport) => void\n reject: (e: unknown) => void\n }\n\nexport interface DeferredHandle extends FeedbackApi {\n /** Called by the loader once the bundle is loaded and a real API exists. */\n _attach(real: FeedbackApi): void\n /** Called by the loader if the bundle fails to load. */\n _fail(err: Error): void\n}\n\nexport function createDeferredApi(): DeferredHandle {\n let real: FeedbackApi | null = null\n let failure: Error | null = null\n const queue: Queued[] = []\n\n function flush(item: Queued): void {\n if (real) {\n if (item.kind === 'void') {\n ;(real[item.method] as () => void)()\n } else if (item.kind === 'arg') {\n ;(real[item.method] as (a: unknown) => void)(item.arg)\n } else {\n // submit\n Promise.resolve(real.submit(item.payload)).then(\n item.resolve,\n item.reject,\n )\n }\n return\n }\n if (failure) {\n if (item.kind === 'submit') item.reject(failure)\n // Void / arg methods silently dropped — the widget never appeared.\n return\n }\n queue.push(item)\n }\n\n return {\n show() {\n flush({ kind: 'void', method: 'show' })\n },\n hide() {\n flush({ kind: 'void', method: 'hide' })\n },\n shutdown() {\n flush({ kind: 'void', method: 'shutdown' })\n },\n open(arg) {\n flush({ kind: 'arg', method: 'open', arg })\n },\n identify(arg) {\n flush({ kind: 'arg', method: 'identify', arg })\n },\n setMetadata(arg) {\n flush({ kind: 'arg', method: 'setMetadata', arg })\n },\n submit(payload) {\n return new Promise<SubmittedReport>((resolve, reject) => {\n flush({ kind: 'submit', payload, resolve, reject })\n })\n },\n _attach(impl) {\n real = impl\n while (queue.length > 0) flush(queue.shift()!)\n },\n _fail(err) {\n failure = err\n // Drain any pending submits with the failure; drop void/arg.\n for (const item of queue) {\n if (item.kind === 'submit') item.reject(err)\n }\n queue.length = 0\n },\n }\n}\n","/**\n * `@mhosaic/feedback/loader` — public entry for the loader architecture.\n *\n * Same shape as the direct-import path (`@mhosaic/feedback`), but the\n * widget bundle is fetched at runtime from a CDN URL the Mhosaic backend\n * dictates via the manifest endpoint. Letting hosts switch from the\n * direct-import path to the loader path gives them auto-updates without\n * any further code changes — Mhosaic ships a new Release row, every host\n * picks up the new bundle on next page load.\n *\n * Public surface MUST mirror the direct-import path exactly:\n * - `createFeedback(config)` returns a `FeedbackApi`-shaped handle.\n * - Method calls before the bundle finishes loading queue up and replay.\n * - `submit()` returns a Promise that resolves after the bundle is up\n * and the real submit completes.\n *\n * If the manifest endpoint reports the widget is disabled for this\n * project (`enabled: false` — kill switch or no stable release configured),\n * the deferred handle silently no-ops void calls and rejects `submit()`\n * calls with a clear error. The host page never crashes.\n */\n\nimport type { FeedbackApi, FeedbackConfig } from '../types'\nimport { injectBundle } from './bundleLoader'\nimport { fetchManifest } from './manifest'\nimport { createDeferredApi, type DeferredHandle } from './deferredApi'\n\nexport type { FeedbackApi, FeedbackConfig } from '../types'\nexport type { Manifest } from './manifest'\n\nexport function createFeedback(config: FeedbackConfig): FeedbackApi {\n const deferred = createDeferredApi()\n loadAndAttach(config, deferred).catch((err: Error) => {\n // Surface to console — host devtools is the canonical channel for\n // widget bootstrap errors. Production telemetry is the bundle's job\n // (it can't run if it never loaded), so console.warn is the floor.\n // eslint-disable-next-line no-console\n console.warn('[mhosaic-feedback] widget did not load:', err.message)\n deferred._fail(err)\n })\n return deferred\n}\n\nasync function loadAndAttach(\n config: FeedbackConfig,\n deferred: DeferredHandle,\n): Promise<void> {\n const manifest = await fetchManifest(config.endpoint, config.apiKey)\n if (!manifest.enabled || !manifest.bundle_url || !manifest.sri_hash) {\n // Disabled by Mhosaic (kill switch) or no stable release. Surface a\n // clear failure so submit() callers see an error, but no host crash.\n deferred._fail(\n new Error(\n `mhosaic-feedback: widget disabled for this project${\n manifest.detail ? ` — ${manifest.detail}` : ''\n }`,\n ),\n )\n return\n }\n const bundle = await injectBundle({\n bundleUrl: manifest.bundle_url,\n sriHash: manifest.sri_hash,\n })\n const real: FeedbackApi = bundle.createFeedback(config)\n deferred._attach(real)\n}\n"],"mappings":"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","names":[]}
@@ -1794,29 +1794,39 @@ function ChangelogList({ api, externalId, strings, onSelect }) {
1794
1794
  }
1795
1795
 
1796
1796
  // src/widget/Fab.tsx
1797
- import { jsx as jsx6 } from "preact/jsx-runtime";
1798
- function ChatBubbleIcon() {
1799
- return /* @__PURE__ */ jsx6(
1797
+ import { jsx as jsx6, jsxs as jsxs6 } from "preact/jsx-runtime";
1798
+ function BugIcon() {
1799
+ return /* @__PURE__ */ jsxs6(
1800
1800
  "svg",
1801
1801
  {
1802
1802
  width: "24",
1803
1803
  height: "24",
1804
1804
  viewBox: "0 0 24 24",
1805
1805
  fill: "none",
1806
+ stroke: "currentColor",
1807
+ "stroke-width": "2",
1808
+ "stroke-linecap": "round",
1809
+ "stroke-linejoin": "round",
1806
1810
  "aria-hidden": "true",
1807
1811
  focusable: "false",
1808
- children: /* @__PURE__ */ jsx6(
1809
- "path",
1810
- {
1811
- d: "M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z",
1812
- fill: "currentColor"
1813
- }
1814
- )
1812
+ children: [
1813
+ /* @__PURE__ */ jsx6("path", { d: "m8 2 1.88 1.88" }),
1814
+ /* @__PURE__ */ jsx6("path", { d: "M14.12 3.88 16 2" }),
1815
+ /* @__PURE__ */ jsx6("path", { d: "M9 7.13v-1a3.003 3.003 0 1 1 6 0v1" }),
1816
+ /* @__PURE__ */ jsx6("path", { d: "M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6" }),
1817
+ /* @__PURE__ */ jsx6("path", { d: "M12 20v-9" }),
1818
+ /* @__PURE__ */ jsx6("path", { d: "M6.53 9C4.6 8.8 3 7.1 3 5" }),
1819
+ /* @__PURE__ */ jsx6("path", { d: "M6 13H2" }),
1820
+ /* @__PURE__ */ jsx6("path", { d: "M3 21c0-2.1 1.7-3.9 3.8-4" }),
1821
+ /* @__PURE__ */ jsx6("path", { d: "M20.97 5c0 2.1-1.6 3.8-3.5 4" }),
1822
+ /* @__PURE__ */ jsx6("path", { d: "M22 13h-4" }),
1823
+ /* @__PURE__ */ jsx6("path", { d: "M17.2 17c2.1.1 3.8 1.9 3.8 4" })
1824
+ ]
1815
1825
  }
1816
1826
  );
1817
1827
  }
1818
1828
  function Fab({ label, onClick }) {
1819
- return /* @__PURE__ */ jsx6("button", { type: "button", class: "fab", "aria-label": label, title: label, onClick, children: /* @__PURE__ */ jsx6(ChatBubbleIcon, {}) });
1829
+ return /* @__PURE__ */ jsx6("button", { type: "button", class: "fab", "aria-label": label, title: label, onClick, children: /* @__PURE__ */ jsx6(BugIcon, {}) });
1820
1830
  }
1821
1831
 
1822
1832
  // src/widget/Form.tsx
@@ -1824,7 +1834,7 @@ import { useEffect as useEffect5, useRef as useRef4, useState as useState5 } fro
1824
1834
 
1825
1835
  // src/widget/Annotator.tsx
1826
1836
  import { useEffect as useEffect4, useLayoutEffect, useRef as useRef3, useState as useState4 } from "preact/hooks";
1827
- import { jsx as jsx7, jsxs as jsxs6 } from "preact/jsx-runtime";
1837
+ import { jsx as jsx7, jsxs as jsxs7 } from "preact/jsx-runtime";
1828
1838
  var COLORS = ["#ef4444", "#f59e0b", "#10b981", "#3b82f6", "#ffffff"];
1829
1839
  var HIGHLIGHT_ALPHA = 0.35;
1830
1840
  function drawShape(ctx, shape, sourceImage) {
@@ -1950,11 +1960,11 @@ var Icon = {
1950
1960
  arrow: /* @__PURE__ */ jsx7("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx7("path", { d: "M2 8h11M9 4l4 4-4 4", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round" }) }),
1951
1961
  pencil: /* @__PURE__ */ jsx7("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx7("path", { d: "M11.5 2.5l2 2L5 13H3v-2l8.5-8.5z", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linejoin": "round" }) }),
1952
1962
  text: /* @__PURE__ */ jsx7("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx7("path", { d: "M3 3h10M8 3v10M5 13h6", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round" }) }),
1953
- highlight: /* @__PURE__ */ jsxs6("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: [
1963
+ highlight: /* @__PURE__ */ jsxs7("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: [
1954
1964
  /* @__PURE__ */ jsx7("path", { d: "M3 13l3-3L11 5l-2-2-5 5-3 3v2h2zM10 4l2 2", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round" }),
1955
1965
  /* @__PURE__ */ jsx7("path", { d: "M2 14h12", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round" })
1956
1966
  ] }),
1957
- blur: /* @__PURE__ */ jsxs6("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: [
1967
+ blur: /* @__PURE__ */ jsxs7("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: [
1958
1968
  /* @__PURE__ */ jsx7("rect", { x: "2", y: "2", width: "4", height: "4", fill: "currentColor", opacity: "0.85" }),
1959
1969
  /* @__PURE__ */ jsx7("rect", { x: "7", y: "2", width: "3", height: "3", fill: "currentColor", opacity: "0.55" }),
1960
1970
  /* @__PURE__ */ jsx7("rect", { x: "11", y: "2", width: "3", height: "4", fill: "currentColor", opacity: "0.4" }),
@@ -2210,8 +2220,8 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
2210
2220
  onClick: (e) => {
2211
2221
  if (e.target === e.currentTarget) onCancel();
2212
2222
  },
2213
- children: /* @__PURE__ */ jsxs6("div", { class: "annotator", role: "dialog", "aria-modal": "true", "aria-label": strings["annotator.title"], children: [
2214
- /* @__PURE__ */ jsxs6("div", { class: "annotator-header", children: [
2223
+ children: /* @__PURE__ */ jsxs7("div", { class: "annotator", role: "dialog", "aria-modal": "true", "aria-label": strings["annotator.title"], children: [
2224
+ /* @__PURE__ */ jsxs7("div", { class: "annotator-header", children: [
2215
2225
  /* @__PURE__ */ jsx7("span", { children: strings["annotator.title"] }),
2216
2226
  /* @__PURE__ */ jsx7(
2217
2227
  "button",
@@ -2224,7 +2234,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
2224
2234
  }
2225
2235
  )
2226
2236
  ] }),
2227
- /* @__PURE__ */ jsxs6("div", { class: "annotator-toolbar", children: [
2237
+ /* @__PURE__ */ jsxs7("div", { class: "annotator-toolbar", children: [
2228
2238
  /* @__PURE__ */ jsx7("div", { class: "annotator-tools", children: tools.map((t) => /* @__PURE__ */ jsx7(
2229
2239
  "button",
2230
2240
  {
@@ -2239,7 +2249,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
2239
2249
  t.id
2240
2250
  )) }),
2241
2251
  /* @__PURE__ */ jsx7("span", { class: "annotator-sep" }),
2242
- /* @__PURE__ */ jsxs6("div", { class: "annotator-colors", children: [
2252
+ /* @__PURE__ */ jsxs7("div", { class: "annotator-colors", children: [
2243
2253
  COLORS.map((c) => /* @__PURE__ */ jsx7(
2244
2254
  "button",
2245
2255
  {
@@ -2263,7 +2273,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
2263
2273
  ) })
2264
2274
  ] }),
2265
2275
  /* @__PURE__ */ jsx7("span", { class: "annotator-sep" }),
2266
- /* @__PURE__ */ jsxs6(
2276
+ /* @__PURE__ */ jsxs7(
2267
2277
  "button",
2268
2278
  {
2269
2279
  type: "button",
@@ -2277,7 +2287,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
2277
2287
  ]
2278
2288
  }
2279
2289
  ),
2280
- /* @__PURE__ */ jsxs6(
2290
+ /* @__PURE__ */ jsxs7(
2281
2291
  "button",
2282
2292
  {
2283
2293
  type: "button",
@@ -2291,7 +2301,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
2291
2301
  ]
2292
2302
  }
2293
2303
  ),
2294
- /* @__PURE__ */ jsxs6(
2304
+ /* @__PURE__ */ jsxs7(
2295
2305
  "button",
2296
2306
  {
2297
2307
  type: "button",
@@ -2305,7 +2315,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
2305
2315
  }
2306
2316
  ),
2307
2317
  /* @__PURE__ */ jsx7("span", { class: "annotator-spacer" }),
2308
- /* @__PURE__ */ jsxs6("span", { class: "annotator-count", children: [
2318
+ /* @__PURE__ */ jsxs7("span", { class: "annotator-count", children: [
2309
2319
  shapes.length,
2310
2320
  " ",
2311
2321
  strings["annotator.count_suffix"]
@@ -2322,7 +2332,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
2322
2332
  class: "annotator-canvas"
2323
2333
  }
2324
2334
  ) }),
2325
- /* @__PURE__ */ jsxs6("div", { class: "annotator-footer", children: [
2335
+ /* @__PURE__ */ jsxs7("div", { class: "annotator-footer", children: [
2326
2336
  /* @__PURE__ */ jsx7("button", { type: "button", class: "btn", onClick: onCancel, children: strings["form.cancel"] }),
2327
2337
  /* @__PURE__ */ jsx7(
2328
2338
  "button",
@@ -2341,7 +2351,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
2341
2351
  }
2342
2352
 
2343
2353
  // src/widget/Form.tsx
2344
- import { jsx as jsx8, jsxs as jsxs7 } from "preact/jsx-runtime";
2354
+ import { jsx as jsx8, jsxs as jsxs8 } from "preact/jsx-runtime";
2345
2355
  var TYPES2 = ["bug", "feature", "question", "praise", "typo"];
2346
2356
  var SEVERITIES2 = ["blocker", "high", "medium", "low"];
2347
2357
  function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
@@ -2450,9 +2460,9 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
2450
2460
  }
2451
2461
  onSubmit(values);
2452
2462
  };
2453
- return /* @__PURE__ */ jsxs7("form", { onSubmit: handleSubmit, children: [
2463
+ return /* @__PURE__ */ jsxs8("form", { onSubmit: handleSubmit, children: [
2454
2464
  /* @__PURE__ */ jsx8("h2", { children: strings["form.title"] }),
2455
- /* @__PURE__ */ jsxs7("div", { class: "field", children: [
2465
+ /* @__PURE__ */ jsxs8("div", { class: "field", children: [
2456
2466
  /* @__PURE__ */ jsx8("label", { for: "mfb-desc", children: strings["form.description.label"] }),
2457
2467
  /* @__PURE__ */ jsx8(
2458
2468
  "textarea",
@@ -2464,8 +2474,8 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
2464
2474
  }
2465
2475
  )
2466
2476
  ] }),
2467
- /* @__PURE__ */ jsxs7("div", { class: "row", children: [
2468
- /* @__PURE__ */ jsxs7("div", { class: "field", children: [
2477
+ /* @__PURE__ */ jsxs8("div", { class: "row", children: [
2478
+ /* @__PURE__ */ jsxs8("div", { class: "field", children: [
2469
2479
  /* @__PURE__ */ jsx8("label", { for: "mfb-type", children: strings["form.type.label"] }),
2470
2480
  /* @__PURE__ */ jsx8(
2471
2481
  "select",
@@ -2477,7 +2487,7 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
2477
2487
  }
2478
2488
  )
2479
2489
  ] }),
2480
- /* @__PURE__ */ jsxs7("div", { class: "field", children: [
2490
+ /* @__PURE__ */ jsxs8("div", { class: "field", children: [
2481
2491
  /* @__PURE__ */ jsx8("label", { for: "mfb-sev", children: strings["form.severity.label"] }),
2482
2492
  /* @__PURE__ */ jsx8(
2483
2493
  "select",
@@ -2490,7 +2500,7 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
2490
2500
  )
2491
2501
  ] })
2492
2502
  ] }),
2493
- /* @__PURE__ */ jsxs7("div", { class: "field", children: [
2503
+ /* @__PURE__ */ jsxs8("div", { class: "field", children: [
2494
2504
  /* @__PURE__ */ jsx8("label", { children: strings["form.screenshot.label"] }),
2495
2505
  /* @__PURE__ */ jsx8(
2496
2506
  "input",
@@ -2504,17 +2514,17 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
2504
2514
  tabIndex: -1
2505
2515
  }
2506
2516
  ),
2507
- screenshotPreview ? /* @__PURE__ */ jsxs7("div", { class: "screenshot-preview", children: [
2517
+ screenshotPreview ? /* @__PURE__ */ jsxs8("div", { class: "screenshot-preview", children: [
2508
2518
  /* @__PURE__ */ jsx8("img", { src: screenshotPreview, alt: "" }),
2509
- /* @__PURE__ */ jsxs7("div", { class: "screenshot-preview-actions", children: [
2510
- /* @__PURE__ */ jsxs7(
2519
+ /* @__PURE__ */ jsxs8("div", { class: "screenshot-preview-actions", children: [
2520
+ /* @__PURE__ */ jsxs8(
2511
2521
  "button",
2512
2522
  {
2513
2523
  type: "button",
2514
2524
  class: "btn btn--primary screenshot-annotate",
2515
2525
  onClick: () => setAnnotatorOpen(true),
2516
2526
  children: [
2517
- /* @__PURE__ */ jsxs7("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", children: [
2527
+ /* @__PURE__ */ jsxs8("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", children: [
2518
2528
  /* @__PURE__ */ jsx8("path", { d: "M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" }),
2519
2529
  /* @__PURE__ */ jsx8("path", { d: "M18.5 2.5a2.12 2.12 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" })
2520
2530
  ] }),
@@ -2522,12 +2532,12 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
2522
2532
  ]
2523
2533
  }
2524
2534
  ),
2525
- /* @__PURE__ */ jsxs7("button", { type: "button", class: "btn", onClick: clearScreenshot, children: [
2535
+ /* @__PURE__ */ jsxs8("button", { type: "button", class: "btn", onClick: clearScreenshot, children: [
2526
2536
  /* @__PURE__ */ jsx8("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx8("path", { d: "M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6h14z" }) }),
2527
2537
  strings["form.screenshot.remove"]
2528
2538
  ] })
2529
2539
  ] })
2530
- ] }) : /* @__PURE__ */ jsxs7(
2540
+ ] }) : /* @__PURE__ */ jsxs8(
2531
2541
  "div",
2532
2542
  {
2533
2543
  ref: dropZoneRef,
@@ -2546,12 +2556,12 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
2546
2556
  onDragLeave: handleDragLeave,
2547
2557
  onDrop: handleDrop,
2548
2558
  children: [
2549
- /* @__PURE__ */ jsxs7("svg", { class: "screenshot-icon", width: "28", height: "28", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "1.6", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", children: [
2559
+ /* @__PURE__ */ jsxs8("svg", { class: "screenshot-icon", width: "28", height: "28", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "1.6", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", children: [
2550
2560
  /* @__PURE__ */ jsx8("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
2551
2561
  /* @__PURE__ */ jsx8("polyline", { points: "17 8 12 3 7 8" }),
2552
2562
  /* @__PURE__ */ jsx8("line", { x1: "12", y1: "3", x2: "12", y2: "15" })
2553
2563
  ] }),
2554
- /* @__PURE__ */ jsxs7("div", { class: "screenshot-cta", children: [
2564
+ /* @__PURE__ */ jsxs8("div", { class: "screenshot-cta", children: [
2555
2565
  /* @__PURE__ */ jsx8("strong", { children: strings["form.screenshot.cta_click"] }),
2556
2566
  ", ",
2557
2567
  strings["form.screenshot.cta_rest"]
@@ -2561,14 +2571,14 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
2561
2571
  }
2562
2572
  )
2563
2573
  ] }),
2564
- pageUrl && /* @__PURE__ */ jsxs7("div", { class: "page-context", title: pageUrl, children: [
2574
+ pageUrl && /* @__PURE__ */ jsxs8("div", { class: "page-context", title: pageUrl, children: [
2565
2575
  /* @__PURE__ */ jsx8("span", { class: "page-context-label", children: strings["form.context.label"] }),
2566
2576
  /* @__PURE__ */ jsx8("span", { class: "page-context-url", children: truncateUrl(pageUrl, 90) })
2567
2577
  ] }),
2568
2578
  localError && /* @__PURE__ */ jsx8("div", { class: "error", children: localError }),
2569
2579
  status === "error" && errorMessage && /* @__PURE__ */ jsx8("div", { class: "error", children: errorMessage }),
2570
2580
  status === "success" && /* @__PURE__ */ jsx8("div", { class: "success", children: strings["form.success"] }),
2571
- /* @__PURE__ */ jsxs7("div", { class: "actions", children: [
2581
+ /* @__PURE__ */ jsxs8("div", { class: "actions", children: [
2572
2582
  /* @__PURE__ */ jsx8("button", { type: "button", class: "btn", onClick: onCancel, disabled: submitting, children: strings["form.cancel"] }),
2573
2583
  /* @__PURE__ */ jsx8("button", { type: "submit", class: "btn btn--primary", disabled: submitting, children: submitLabel })
2574
2584
  ] }),
@@ -2588,7 +2598,7 @@ function Form({ strings, onSubmit, onCancel, status, errorMessage }) {
2588
2598
  import { useEffect as useEffect6, useRef as useRef5, useState as useState6 } from "preact/hooks";
2589
2599
 
2590
2600
  // src/widget/KpiStrip.tsx
2591
- import { jsx as jsx9, jsxs as jsxs8 } from "preact/jsx-runtime";
2601
+ import { jsx as jsx9, jsxs as jsxs9 } from "preact/jsx-runtime";
2592
2602
  function computeKpiCounts(rows) {
2593
2603
  const counts = {
2594
2604
  new: 0,
@@ -2658,7 +2668,7 @@ function KpiStrip({ rows, filter, onFilter, strings }) {
2658
2668
  return /* @__PURE__ */ jsx9("div", { class: "kpi-strip", role: "toolbar", children: cells.map((c) => {
2659
2669
  const active = filter === c.key;
2660
2670
  const toggleTo = active ? "all" : c.key;
2661
- return /* @__PURE__ */ jsxs8(
2671
+ return /* @__PURE__ */ jsxs9(
2662
2672
  "button",
2663
2673
  {
2664
2674
  type: "button",
@@ -2675,7 +2685,7 @@ function KpiStrip({ rows, filter, onFilter, strings }) {
2675
2685
  }
2676
2686
 
2677
2687
  // src/widget/MineList.tsx
2678
- import { jsx as jsx10, jsxs as jsxs9 } from "preact/jsx-runtime";
2688
+ import { jsx as jsx10, jsxs as jsxs10 } from "preact/jsx-runtime";
2679
2689
  var POLL_MS4 = 3e4;
2680
2690
  function MineList({ api, externalId, strings, onSelect }) {
2681
2691
  const [rows, setRows] = useState6(null);
@@ -2713,8 +2723,8 @@ function MineList({ api, externalId, strings, onSelect }) {
2713
2723
  const isLoading = rows === null && !error;
2714
2724
  const visibleRows = rows ? rowsMatchingFilter(rows, filter) : null;
2715
2725
  const visibleEmpty = !!rows && rows.length > 0 && (visibleRows?.length ?? 0) === 0;
2716
- return /* @__PURE__ */ jsxs9("div", { class: "mine-list", children: [
2717
- /* @__PURE__ */ jsxs9("div", { class: "mine-list-header", children: [
2726
+ return /* @__PURE__ */ jsxs10("div", { class: "mine-list", children: [
2727
+ /* @__PURE__ */ jsxs10("div", { class: "mine-list-header", children: [
2718
2728
  /* @__PURE__ */ jsx10("h2", { children: strings["tab.mine"] }),
2719
2729
  /* @__PURE__ */ jsx10(
2720
2730
  "button",
@@ -2732,7 +2742,7 @@ function MineList({ api, externalId, strings, onSelect }) {
2732
2742
  rows && rows.length > 0 && /* @__PURE__ */ jsx10(KpiStrip, { rows, filter, onFilter: setFilter, strings }),
2733
2743
  isLoading && /* @__PURE__ */ jsx10("div", { class: "mine-loading", children: strings["mine.loading"] }),
2734
2744
  error && /* @__PURE__ */ jsx10("div", { class: "error", children: error }),
2735
- isEmpty && /* @__PURE__ */ jsxs9("div", { class: "mine-empty", children: [
2745
+ isEmpty && /* @__PURE__ */ jsxs10("div", { class: "mine-empty", children: [
2736
2746
  /* @__PURE__ */ jsx10("strong", { children: strings["mine.empty.title"] }),
2737
2747
  /* @__PURE__ */ jsx10("p", { children: strings["mine.empty.body"] })
2738
2748
  ] }),
@@ -2743,7 +2753,7 @@ function MineList({ api, externalId, strings, onSelect }) {
2743
2753
 
2744
2754
  // src/widget/Modal.tsx
2745
2755
  import { useEffect as useEffect7, useRef as useRef6 } from "preact/hooks";
2746
- import { jsx as jsx11, jsxs as jsxs10 } from "preact/jsx-runtime";
2756
+ import { jsx as jsx11, jsxs as jsxs11 } from "preact/jsx-runtime";
2747
2757
  function Modal({ onDismiss, children, closeLabel = "Close", expanded = false }) {
2748
2758
  const modalRef = useRef6(null);
2749
2759
  const previouslyFocused = useRef6(null);
@@ -2777,7 +2787,7 @@ function Modal({ onDismiss, children, closeLabel = "Close", expanded = false })
2777
2787
  onClick: (e) => {
2778
2788
  if (e.target === e.currentTarget) onDismiss();
2779
2789
  },
2780
- children: /* @__PURE__ */ jsxs10(
2790
+ children: /* @__PURE__ */ jsxs11(
2781
2791
  "div",
2782
2792
  {
2783
2793
  ref: modalRef,
@@ -4483,7 +4493,7 @@ var WIDGET_STYLES = `
4483
4493
  `;
4484
4494
 
4485
4495
  // src/widget/mount.tsx
4486
- import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs11 } from "preact/jsx-runtime";
4496
+ import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs12 } from "preact/jsx-runtime";
4487
4497
  function mountWidget(options) {
4488
4498
  const shadow = options.host.attachShadow({ mode: "open" });
4489
4499
  const style = document.createElement("style");
@@ -4534,7 +4544,7 @@ function mountWidget(options) {
4534
4544
  const externalId = options.getExternalId?.();
4535
4545
  const fabVisible = options.showFAB && (options.getExternalId === void 0 ? true : Boolean(externalId));
4536
4546
  const showMineTab = Boolean(options.api && externalId);
4537
- return /* @__PURE__ */ jsxs11(Fragment3, { children: [
4547
+ return /* @__PURE__ */ jsxs12(Fragment3, { children: [
4538
4548
  fabVisible && /* @__PURE__ */ jsx12(
4539
4549
  Fab,
4540
4550
  {
@@ -4542,14 +4552,14 @@ function mountWidget(options) {
4542
4552
  onClick: () => rerender({ ...currentState, open: true })
4543
4553
  }
4544
4554
  ),
4545
- state.open && /* @__PURE__ */ jsxs11(
4555
+ state.open && /* @__PURE__ */ jsxs12(
4546
4556
  Modal,
4547
4557
  {
4548
4558
  onDismiss: () => rerender(clearSelected({ ...currentState, open: false, status: "idle" })),
4549
4559
  closeLabel: options.strings["form.close"],
4550
4560
  expanded: state.tab === "board",
4551
4561
  children: [
4552
- showMineTab && /* @__PURE__ */ jsxs11("div", { class: "tab-strip", role: "tablist", children: [
4562
+ showMineTab && /* @__PURE__ */ jsxs12("div", { class: "tab-strip", role: "tablist", children: [
4553
4563
  /* @__PURE__ */ jsx12(
4554
4564
  "button",
4555
4565
  {
@@ -4731,8 +4741,8 @@ function createFeedback(config) {
4731
4741
  capture_method: captureMethod,
4732
4742
  technical_context
4733
4743
  };
4734
- if ("0.15.6") {
4735
- payload.widget_version = "0.15.6";
4744
+ if ("0.16.0") {
4745
+ payload.widget_version = "0.16.0";
4736
4746
  }
4737
4747
  if (manualScreenshot) payload.screenshot = manualScreenshot;
4738
4748
  if (values.synthetic) payload.synthetic = true;
@@ -4818,4 +4828,4 @@ function createFeedback(config) {
4818
4828
  export {
4819
4829
  createFeedback
4820
4830
  };
4821
- //# sourceMappingURL=chunk-AQW4WVZE.mjs.map
4831
+ //# sourceMappingURL=chunk-CSQGHTXF.mjs.map