spaps-issue-reporting-react 0.5.0 → 0.6.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.
package/dist/index.js CHANGED
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
8
11
  var __export = (target, all) => {
9
12
  for (var name in all)
10
13
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -27,6 +30,553 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
30
  ));
28
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
32
 
33
+ // src/voice/VoiceCapture.tsx
34
+ var VoiceCapture_exports = {};
35
+ __export(VoiceCapture_exports, {
36
+ VoiceCapture: () => VoiceCapture,
37
+ default: () => VoiceCapture_default
38
+ });
39
+ function cn(...values) {
40
+ return values.filter(Boolean).join(" ");
41
+ }
42
+ function resolveErrorMessage(error, fallback) {
43
+ if (error instanceof Error && error.message.trim()) {
44
+ return error.message;
45
+ }
46
+ if (error && typeof error === "object" && "message" in error && typeof error.message === "string" && error.message.trim()) {
47
+ return error.message;
48
+ }
49
+ return fallback;
50
+ }
51
+ function getCommittedTranscriptText(committedTranscripts) {
52
+ return committedTranscripts.map((segment) => segment.text.trim()).filter(Boolean).join(" ").trim();
53
+ }
54
+ function IssueReportVoicePanel({
55
+ canUseText,
56
+ effectiveInputMode,
57
+ isSubmitting,
58
+ isVoiceActive,
59
+ isVoiceConnecting,
60
+ voice,
61
+ voiceTokenResult,
62
+ committedTranscript,
63
+ voiceError,
64
+ scribeError,
65
+ onSelectText,
66
+ onSelectVoice,
67
+ onStartVoiceInput,
68
+ onStopVoiceInput,
69
+ onAppendTranscript
70
+ }) {
71
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
72
+ canUseText ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-5 flex gap-2", children: [
73
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
74
+ "button",
75
+ {
76
+ type: "button",
77
+ className: cn(
78
+ "inline-flex items-center gap-1 rounded-full border px-3 py-1.5 text-xs font-semibold transition",
79
+ effectiveInputMode === "text" ? "border-slate-900 bg-slate-900 text-white" : "border-slate-200 text-slate-700 hover:bg-slate-50"
80
+ ),
81
+ onClick: onSelectText,
82
+ disabled: isSubmitting,
83
+ children: [
84
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.TextT, { className: "h-3.5 w-3.5" }),
85
+ "Text Input"
86
+ ]
87
+ }
88
+ ),
89
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
90
+ "button",
91
+ {
92
+ type: "button",
93
+ className: cn(
94
+ "inline-flex items-center gap-1 rounded-full border px-3 py-1.5 text-xs font-semibold transition",
95
+ effectiveInputMode === "voice" ? "border-slate-900 bg-slate-900 text-white" : "border-slate-200 text-slate-700 hover:bg-slate-50"
96
+ ),
97
+ onClick: onSelectVoice,
98
+ disabled: isSubmitting,
99
+ children: [
100
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Microphone, { className: "h-3.5 w-3.5" }),
101
+ "Voice Input"
102
+ ]
103
+ }
104
+ )
105
+ ] }) : null,
106
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-4 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3", children: [
107
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
108
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
109
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-xs font-medium uppercase tracking-wide text-slate-500", children: "Voice Input" }),
110
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "mt-1 text-xs text-slate-600", children: [
111
+ "Provider: ",
112
+ voiceTokenResult?.provider ?? voice.provider,
113
+ " \xB7 Model:",
114
+ " ",
115
+ voiceTokenResult?.model_id ?? voice.modelId
116
+ ] }),
117
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "mt-1 text-xs text-slate-500", children: voice.requireMicrophonePermission ? "Microphone access is required to transcribe." : "Microphone access policy is optional." })
118
+ ] }),
119
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-2", children: [
120
+ isVoiceActive || isVoiceConnecting ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
121
+ "button",
122
+ {
123
+ type: "button",
124
+ className: "rounded-full border border-slate-300 px-3 py-1.5 text-xs font-medium text-slate-700 transition hover:bg-slate-100",
125
+ onClick: onStopVoiceInput,
126
+ disabled: isSubmitting,
127
+ children: "Stop Voice Input"
128
+ }
129
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
130
+ "button",
131
+ {
132
+ type: "button",
133
+ className: "rounded-full bg-slate-900 px-3 py-1.5 text-xs font-semibold text-white transition hover:bg-slate-800",
134
+ onClick: onStartVoiceInput,
135
+ disabled: isSubmitting,
136
+ children: "Start Voice Input"
137
+ }
138
+ ),
139
+ canUseText ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
140
+ "button",
141
+ {
142
+ type: "button",
143
+ className: "rounded-full border border-slate-300 px-3 py-1.5 text-xs font-medium text-slate-700 transition hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-60",
144
+ onClick: onAppendTranscript,
145
+ disabled: isSubmitting || !committedTranscript.trim(),
146
+ children: "Append Transcript"
147
+ }
148
+ ) : null
149
+ ] })
150
+ ] }),
151
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-3 rounded-xl border border-slate-200 bg-white px-3 py-2 text-sm text-slate-700", children: committedTranscript ? committedTranscript : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-slate-500", children: "No committed transcript yet." }) }),
152
+ voiceError || scribeError ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-2 rounded-xl border border-rose-200 bg-rose-50 px-3 py-2 text-xs text-rose-700", children: voiceError ?? scribeError }) : isVoiceConnecting ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-2 flex items-center gap-1.5 text-xs text-slate-500", children: [
153
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Spinner, { className: "h-3.5 w-3.5 animate-spin" }),
154
+ "Connecting voice transcription..."
155
+ ] }) : null
156
+ ] })
157
+ ] });
158
+ }
159
+ function VoiceCapture({
160
+ canUseText,
161
+ effectiveInputMode,
162
+ isSubmitting,
163
+ voice,
164
+ createVoiceToken,
165
+ startRequestId,
166
+ appendRequestId,
167
+ onSelectText,
168
+ onSelectVoice,
169
+ onTranscriptCommitted,
170
+ onSubmitMetadataChange,
171
+ onAppendTranscript
172
+ }) {
173
+ const [voiceTokenResult, setVoiceTokenResult] = (0, import_react5.useState)(null);
174
+ const [voiceSubmitMetadata, setVoiceSubmitMetadata] = (0, import_react5.useState)(null);
175
+ const [voiceError, setVoiceError] = (0, import_react5.useState)(null);
176
+ const startInFlightRef = (0, import_react5.useRef)(false);
177
+ const lastStartRequestId = (0, import_react5.useRef)(0);
178
+ const lastAppendRequestId = (0, import_react5.useRef)(0);
179
+ const {
180
+ status: scribeStatus,
181
+ isConnected,
182
+ isTranscribing,
183
+ committedTranscripts,
184
+ error: scribeError,
185
+ connect: connectScribe,
186
+ disconnect: disconnectScribe
187
+ } = (0, import_react3.useScribe)({
188
+ autoConnect: false,
189
+ modelId: voice.modelId,
190
+ microphone: voice.microphone
191
+ });
192
+ const committedTranscript = (0, import_react5.useMemo)(
193
+ () => getCommittedTranscriptText(committedTranscripts),
194
+ [committedTranscripts]
195
+ );
196
+ const isVoiceConnecting = scribeStatus === "connecting";
197
+ const isVoiceActive = isConnected || isTranscribing;
198
+ (0, import_react5.useEffect)(() => {
199
+ onTranscriptCommitted(committedTranscript);
200
+ }, [committedTranscript, onTranscriptCommitted]);
201
+ (0, import_react5.useEffect)(() => {
202
+ onSubmitMetadataChange(voiceSubmitMetadata);
203
+ }, [onSubmitMetadataChange, voiceSubmitMetadata]);
204
+ (0, import_react5.useEffect)(() => {
205
+ return () => {
206
+ disconnectScribe();
207
+ };
208
+ }, [disconnectScribe]);
209
+ const startVoiceInput = (0, import_react5.useCallback)(async () => {
210
+ if (startInFlightRef.current || isSubmitting || isVoiceActive || isVoiceConnecting) {
211
+ return;
212
+ }
213
+ if (!createVoiceToken) {
214
+ setVoiceError("Voice input is unavailable for this client.");
215
+ return;
216
+ }
217
+ startInFlightRef.current = true;
218
+ setVoiceError(null);
219
+ try {
220
+ const tokenResult = await createVoiceToken();
221
+ setVoiceTokenResult(tokenResult);
222
+ setVoiceSubmitMetadata(tokenResult);
223
+ await connectScribe({
224
+ token: tokenResult.token,
225
+ modelId: tokenResult.model_id,
226
+ microphone: voice.microphone
227
+ });
228
+ onSelectVoice();
229
+ } catch (startError) {
230
+ setVoiceError(resolveErrorMessage(startError, "Failed to start voice input."));
231
+ } finally {
232
+ startInFlightRef.current = false;
233
+ }
234
+ }, [
235
+ connectScribe,
236
+ createVoiceToken,
237
+ isSubmitting,
238
+ isVoiceActive,
239
+ isVoiceConnecting,
240
+ onSelectVoice,
241
+ voice.microphone
242
+ ]);
243
+ const appendTranscript = (0, import_react5.useCallback)(() => {
244
+ if (!committedTranscript.trim()) {
245
+ return;
246
+ }
247
+ onAppendTranscript(committedTranscript);
248
+ }, [committedTranscript, onAppendTranscript]);
249
+ (0, import_react5.useEffect)(() => {
250
+ if (startRequestId === 0 || startRequestId === lastStartRequestId.current) {
251
+ return;
252
+ }
253
+ lastStartRequestId.current = startRequestId;
254
+ void startVoiceInput();
255
+ }, [startRequestId, startVoiceInput]);
256
+ (0, import_react5.useEffect)(() => {
257
+ if (appendRequestId === 0 || appendRequestId === lastAppendRequestId.current) {
258
+ return;
259
+ }
260
+ lastAppendRequestId.current = appendRequestId;
261
+ appendTranscript();
262
+ }, [appendRequestId, appendTranscript]);
263
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
264
+ IssueReportVoicePanel,
265
+ {
266
+ canUseText,
267
+ effectiveInputMode,
268
+ isSubmitting,
269
+ isVoiceActive,
270
+ isVoiceConnecting,
271
+ voice,
272
+ voiceTokenResult,
273
+ committedTranscript,
274
+ voiceError,
275
+ scribeError,
276
+ onSelectText,
277
+ onSelectVoice,
278
+ onStartVoiceInput: () => void startVoiceInput(),
279
+ onStopVoiceInput: disconnectScribe,
280
+ onAppendTranscript: appendTranscript
281
+ }
282
+ );
283
+ }
284
+ var import_react3, import_react4, import_react5, import_jsx_runtime2, VoiceCapture_default;
285
+ var init_VoiceCapture = __esm({
286
+ "src/voice/VoiceCapture.tsx"() {
287
+ "use strict";
288
+ import_react3 = require("@elevenlabs/react");
289
+ import_react4 = require("@phosphor-icons/react");
290
+ import_react5 = require("react");
291
+ import_jsx_runtime2 = require("react/jsx-runtime");
292
+ VoiceCapture_default = VoiceCapture;
293
+ }
294
+ });
295
+
296
+ // src/screenshot-capture.ts
297
+ var screenshot_capture_exports = {};
298
+ __export(screenshot_capture_exports, {
299
+ ATTACHMENT_MAX_BYTES: () => ATTACHMENT_MAX_BYTES2,
300
+ SCREENSHOT_CAPTURE_REDACTION_TEXT: () => SCREENSHOT_CAPTURE_REDACTION_TEXT,
301
+ captureScreenshot: () => captureScreenshot,
302
+ maskScreenshotCaptureDocument: () => maskScreenshotCaptureDocument,
303
+ resetHtml2CanvasCache: () => resetHtml2CanvasCache
304
+ });
305
+ function loadHtml2Canvas() {
306
+ if (!html2canvasPromise) {
307
+ html2canvasPromise = import("html2canvas");
308
+ }
309
+ return html2canvasPromise;
310
+ }
311
+ function resolveFilename(config, context) {
312
+ const imageType = config.imageType ?? "image/png";
313
+ const ext = MIME_TO_EXTENSION[imageType] ?? "png";
314
+ if (typeof config.filename === "function") {
315
+ return config.filename(context);
316
+ }
317
+ if (typeof config.filename === "string") {
318
+ return config.filename;
319
+ }
320
+ return `screenshot-${context.target.componentKey}-${Date.now()}.${ext}`;
321
+ }
322
+ function resolveRootElement(config) {
323
+ if (typeof config.rootElement === "function") {
324
+ return config.rootElement();
325
+ }
326
+ if (typeof HTMLElement !== "undefined" && config.rootElement instanceof HTMLElement) {
327
+ return config.rootElement;
328
+ }
329
+ return document.documentElement;
330
+ }
331
+ function buildIgnoreSelectors(config) {
332
+ const selectors = [...DEFAULT_EXCLUDE_SELECTORS];
333
+ if (config.excludeSelectors) {
334
+ selectors.push(...config.excludeSelectors.map((selector) => selector.trim()));
335
+ }
336
+ return selectors.filter(Boolean);
337
+ }
338
+ function safelyMatchesSelector(element, selector) {
339
+ try {
340
+ return element.matches(selector);
341
+ } catch {
342
+ return false;
343
+ }
344
+ }
345
+ function buildIgnoreElementPredicate(config) {
346
+ const selectors = buildIgnoreSelectors(config);
347
+ return (element) => selectors.some((selector) => safelyMatchesSelector(element, selector));
348
+ }
349
+ function canvasToBlobAsync(canvas, mimeType, quality) {
350
+ return new Promise((resolve) => {
351
+ canvas.toBlob(resolve, mimeType, quality);
352
+ });
353
+ }
354
+ async function canvasToSizedBlob(canvas, imageType, quality, maxBytes) {
355
+ const initial = await canvasToBlobAsync(canvas, imageType, quality);
356
+ if (initial && initial.size <= maxBytes) {
357
+ return { blob: initial, scaled: false };
358
+ }
359
+ const fallbackTypes = imageType === "image/png" ? ["image/webp", "image/jpeg"] : [imageType, "image/webp", "image/jpeg"];
360
+ let currentCanvas = canvas;
361
+ let scaled = false;
362
+ for (const scale of [0.85, 0.7, 0.55, 0.4]) {
363
+ currentCanvas = scaleCanvas(currentCanvas, scale);
364
+ scaled = true;
365
+ for (const format of fallbackTypes) {
366
+ for (const candidateQuality of [0.86, 0.72, 0.58, 0.44, 0.32]) {
367
+ const attempt = await canvasToBlobAsync(
368
+ currentCanvas,
369
+ format,
370
+ candidateQuality
371
+ );
372
+ if (attempt && attempt.size <= maxBytes) {
373
+ return { blob: attempt, scaled };
374
+ }
375
+ }
376
+ }
377
+ }
378
+ throw new Error(
379
+ `Screenshot exceeds ${Math.round(maxBytes / 1024 / 1024)} MiB after compression`
380
+ );
381
+ }
382
+ async function captureScreenshot(config, context) {
383
+ if (!config.enabled) {
384
+ throw new Error("Screenshot capture is not enabled");
385
+ }
386
+ if (!canUseBrowserCapture()) {
387
+ throw new Error("Browser screenshot capture is unavailable");
388
+ }
389
+ const { default: html2canvas } = await loadHtml2Canvas();
390
+ const rootElement = resolveRootElement(config);
391
+ if (!rootElement) {
392
+ throw new Error("Screenshot root element not found");
393
+ }
394
+ const ignoreElements = buildIgnoreElementPredicate(config);
395
+ const imageType = config.imageType ?? "image/png";
396
+ const maxBytes = Math.min(config.maxBytes ?? ATTACHMENT_MAX_BYTES2, ATTACHMENT_MAX_BYTES2);
397
+ const viewport = readViewport();
398
+ const rect = resolveCaptureRect(config, viewport);
399
+ const pixelRatio = Math.min(
400
+ Math.max(config.pixelRatio ?? window.devicePixelRatio ?? 1, 1),
401
+ 2
402
+ );
403
+ const ignoreSelectors = buildIgnoreSelectors(config);
404
+ const canvas = await html2canvas(rootElement, {
405
+ useCORS: true,
406
+ allowTaint: false,
407
+ backgroundColor: null,
408
+ logging: false,
409
+ ignoreElements,
410
+ onclone: (clonedDocument) => {
411
+ maskScreenshotCaptureDocument(
412
+ clonedDocument,
413
+ ignoreSelectors,
414
+ config.maskSelectors
415
+ );
416
+ },
417
+ removeContainer: true,
418
+ scale: pixelRatio,
419
+ scrollX: window.scrollX,
420
+ scrollY: window.scrollY,
421
+ width: rect.width,
422
+ height: rect.height,
423
+ windowWidth: viewport.width,
424
+ windowHeight: viewport.height,
425
+ x: rect.left + window.scrollX,
426
+ y: rect.top + window.scrollY
427
+ });
428
+ const output = await canvasToSizedBlob(
429
+ canvas,
430
+ imageType,
431
+ config.quality,
432
+ maxBytes
433
+ );
434
+ const filename = resolveFilename(config, context);
435
+ return {
436
+ blob: new Blob([output.blob], { type: output.blob.type || imageType }),
437
+ filename: replaceFilenameExtension(
438
+ filename,
439
+ MIME_TO_EXTENSION[output.blob.type || imageType] ?? MIME_TO_EXTENSION[imageType]
440
+ ),
441
+ mimeType: output.blob.type || imageType,
442
+ byteSize: output.blob.size,
443
+ rect,
444
+ scaled: output.scaled
445
+ };
446
+ }
447
+ function resetHtml2CanvasCache() {
448
+ html2canvasPromise = null;
449
+ }
450
+ function maskScreenshotCaptureDocument(clonedDocument, excludeSelectors = DEFAULT_EXCLUDE_SELECTORS, maskSelectors = DEFAULT_MASK_SELECTORS) {
451
+ clonedDocument.querySelectorAll([...excludeSelectors, "script"].join(",")).forEach((element) => element.remove());
452
+ clonedDocument.querySelectorAll(maskSelectors.join(",")).forEach(maskCaptureElement);
453
+ redactSensitiveTextNodes(clonedDocument.body ?? clonedDocument.documentElement);
454
+ }
455
+ function canUseBrowserCapture() {
456
+ return typeof window !== "undefined" && typeof document !== "undefined" && typeof Blob !== "undefined" && typeof document.createElement === "function";
457
+ }
458
+ function readViewport() {
459
+ return {
460
+ width: Math.max(
461
+ 1,
462
+ Math.round(window.innerWidth || document.documentElement.clientWidth || 1)
463
+ ),
464
+ height: Math.max(
465
+ 1,
466
+ Math.round(window.innerHeight || document.documentElement.clientHeight || 1)
467
+ )
468
+ };
469
+ }
470
+ function resolveCaptureRect(config, viewport) {
471
+ const configuredRect = typeof config.rect === "function" ? config.rect() : config.rect;
472
+ const source = configuredRect && configuredRect.width > 0 && configuredRect.height > 0 ? configuredRect : { top: 0, left: 0, width: viewport.width, height: viewport.height };
473
+ const left = clamp(Math.round(source.left), 0, viewport.width - 1);
474
+ const top = clamp(Math.round(source.top), 0, viewport.height - 1);
475
+ const right = clamp(
476
+ Math.round(source.left + source.width),
477
+ left + 1,
478
+ viewport.width
479
+ );
480
+ const bottom = clamp(
481
+ Math.round(source.top + source.height),
482
+ top + 1,
483
+ viewport.height
484
+ );
485
+ return {
486
+ left,
487
+ top,
488
+ width: right - left,
489
+ height: bottom - top
490
+ };
491
+ }
492
+ function clamp(value, min, max) {
493
+ return Math.min(Math.max(value, min), max);
494
+ }
495
+ function maskCaptureElement(element) {
496
+ if (element instanceof HTMLInputElement) {
497
+ element.value = "";
498
+ element.setAttribute("value", "");
499
+ element.setAttribute("placeholder", SCREENSHOT_CAPTURE_REDACTION_TEXT);
500
+ } else if (element instanceof HTMLTextAreaElement) {
501
+ element.value = "";
502
+ element.textContent = SCREENSHOT_CAPTURE_REDACTION_TEXT;
503
+ element.setAttribute("placeholder", SCREENSHOT_CAPTURE_REDACTION_TEXT);
504
+ } else if (element instanceof HTMLSelectElement) {
505
+ element.querySelectorAll("option").forEach((option) => {
506
+ option.textContent = SCREENSHOT_CAPTURE_REDACTION_TEXT;
507
+ });
508
+ } else {
509
+ element.textContent = SCREENSHOT_CAPTURE_REDACTION_TEXT;
510
+ }
511
+ if (element instanceof HTMLElement) {
512
+ element.setAttribute("aria-label", SCREENSHOT_CAPTURE_REDACTION_TEXT);
513
+ element.style.color = "#1d1914";
514
+ element.style.backgroundColor = "#d8d0c1";
515
+ }
516
+ }
517
+ function redactSensitiveTextNodes(root) {
518
+ if (!root) {
519
+ return;
520
+ }
521
+ const ownerDocument = root.ownerDocument ?? document;
522
+ const walker = ownerDocument.createTreeWalker(root, NodeFilter.SHOW_TEXT);
523
+ let node = walker.nextNode();
524
+ while (node) {
525
+ if (node.textContent) {
526
+ node.textContent = redactSensitiveText(node.textContent);
527
+ }
528
+ node = walker.nextNode();
529
+ }
530
+ }
531
+ function redactSensitiveText(value) {
532
+ return value.replace(
533
+ /\b((?:access|refresh|id)_token)=([^&#\s]+)/gi,
534
+ (_, key, token) => `${key}=${redactValue(token)}`
535
+ ).replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [redacted]").replace(/\b[ps]k_(?:live|test)_[A-Za-z0-9_=-]+/g, SCREENSHOT_CAPTURE_REDACTION_TEXT).replace(/\b(?:token|secret|password|signature)[_-]?[A-Za-z0-9._~+/=-]{8,}/gi, SCREENSHOT_CAPTURE_REDACTION_TEXT);
536
+ }
537
+ function redactValue(value) {
538
+ return value ? SCREENSHOT_CAPTURE_REDACTION_TEXT : value;
539
+ }
540
+ function scaleCanvas(source, ratio) {
541
+ const canvas = document.createElement("canvas");
542
+ canvas.width = Math.max(1, Math.round(source.width * ratio));
543
+ canvas.height = Math.max(1, Math.round(source.height * ratio));
544
+ const context = canvas.getContext("2d");
545
+ if (!context) {
546
+ throw new Error("Canvas 2D context is unavailable");
547
+ }
548
+ context.drawImage(source, 0, 0, canvas.width, canvas.height);
549
+ return canvas;
550
+ }
551
+ function replaceFilenameExtension(filename, extension) {
552
+ return filename.replace(/\.(png|jpe?g|webp)$/i, `.${extension}`);
553
+ }
554
+ var DEFAULT_EXCLUDE_SELECTORS, DEFAULT_MASK_SELECTORS, SCREENSHOT_CAPTURE_REDACTION_TEXT, ATTACHMENT_MAX_BYTES2, MIME_TO_EXTENSION, html2canvasPromise;
555
+ var init_screenshot_capture = __esm({
556
+ "src/screenshot-capture.ts"() {
557
+ "use strict";
558
+ DEFAULT_EXCLUDE_SELECTORS = [
559
+ "[data-spaps-screenshot-exclude]",
560
+ "[data-spaps-private]"
561
+ ];
562
+ DEFAULT_MASK_SELECTORS = [
563
+ "input",
564
+ "option",
565
+ "select",
566
+ "textarea",
567
+ '[contenteditable="true"]'
568
+ ];
569
+ SCREENSHOT_CAPTURE_REDACTION_TEXT = "[redacted]";
570
+ ATTACHMENT_MAX_BYTES2 = 10 * 1024 * 1024;
571
+ MIME_TO_EXTENSION = {
572
+ "image/png": "png",
573
+ "image/jpeg": "jpg",
574
+ "image/webp": "webp"
575
+ };
576
+ html2canvasPromise = null;
577
+ }
578
+ });
579
+
30
580
  // src/index.ts
31
581
  var index_exports = {};
32
582
  __export(index_exports, {
@@ -61,10 +611,9 @@ module.exports = __toCommonJS(index_exports);
61
611
  // src/components.tsx
62
612
  var Dialog = __toESM(require("@radix-ui/react-dialog"));
63
613
  var Popover = __toESM(require("@radix-ui/react-popover"));
64
- var import_react3 = require("@elevenlabs/react");
65
- var import_react4 = require("@phosphor-icons/react");
614
+ var import_react6 = require("@phosphor-icons/react");
66
615
  var import_date_fns = require("date-fns");
67
- var import_react5 = __toESM(require("react"));
616
+ var import_react7 = __toESM(require("react"));
68
617
 
69
618
  // src/provider.tsx
70
619
  var import_react2 = require("react");
@@ -546,6 +1095,7 @@ function IssueReportingProvider({
546
1095
  inputModes,
547
1096
  defaultInputMode,
548
1097
  voice,
1098
+ screenshotCapture,
549
1099
  copy,
550
1100
  children
551
1101
  }) {
@@ -791,6 +1341,7 @@ function IssueReportingProvider({
791
1341
  inputModes: resolvedInputModes,
792
1342
  defaultInputMode: resolvedDefaultInputMode,
793
1343
  voice: resolvedVoiceConfig,
1344
+ screenshotCapture,
794
1345
  needsResponseIssueIds,
795
1346
  setNeedsResponse
796
1347
  }),
@@ -819,6 +1370,7 @@ function IssueReportingProvider({
819
1370
  resolvedInputModes,
820
1371
  resolvedVoiceConfig,
821
1372
  scope,
1373
+ screenshotCapture,
822
1374
  selectPanel,
823
1375
  setNeedsResponse,
824
1376
  startNewIssue
@@ -859,8 +1411,8 @@ function IssueReportingProvider({
859
1411
  }
860
1412
 
861
1413
  // src/components.tsx
862
- var import_jsx_runtime2 = require("react/jsx-runtime");
863
- function cn(...values) {
1414
+ var import_jsx_runtime3 = require("react/jsx-runtime");
1415
+ function cn2(...values) {
864
1416
  return values.filter(Boolean).join(" ");
865
1417
  }
866
1418
  var Z_FLOATING_BUTTON = "z-[65]";
@@ -875,6 +1427,9 @@ var POPOVER_SHADOW = "shadow-[0_18px_48px_rgba(15,23,42,0.18)]";
875
1427
  var MODAL_SHADOW = "shadow-[0_28px_80px_rgba(15,23,42,0.24)]";
876
1428
  var BADGE_TEXT = "text-[11px]";
877
1429
  var LABEL_TEXT = "text-[11px]";
1430
+ var VoiceCapture2 = import_react7.default.lazy(() => Promise.resolve().then(() => (init_VoiceCapture(), VoiceCapture_exports)));
1431
+ var useIsomorphicLayoutEffect = typeof window === "undefined" ? import_react7.useEffect : import_react7.default.useLayoutEffect;
1432
+ var SCREENSHOT_CAPTURE_METADATA_KEY = "screenshot_capture";
878
1433
  function truncate(value, max = 80) {
879
1434
  if (value.length <= max) {
880
1435
  return value;
@@ -886,7 +1441,7 @@ function formatRelativeTime(timestamp) {
886
1441
  addSuffix: true
887
1442
  });
888
1443
  }
889
- function resolveErrorMessage(error, fallback) {
1444
+ function resolveErrorMessage2(error, fallback) {
890
1445
  if (error instanceof Error && error.message.trim()) {
891
1446
  return error.message;
892
1447
  }
@@ -895,8 +1450,13 @@ function resolveErrorMessage(error, fallback) {
895
1450
  }
896
1451
  return fallback;
897
1452
  }
898
- function getCommittedTranscriptText(committedTranscripts) {
899
- return committedTranscripts.map((segment) => segment.text.trim()).filter(Boolean).join(" ").trim();
1453
+ function buildScreenshotCaptureMetadata(config, status) {
1454
+ return {
1455
+ status,
1456
+ scope: config.scope ?? "visible_viewport",
1457
+ image_type: config.imageType ?? "image/png",
1458
+ ...status === "failed" ? { failure_reason: "capture_failed" } : {}
1459
+ };
900
1460
  }
901
1461
  function appendTranscriptToNote(current, transcript) {
902
1462
  const normalizedCurrent = current.trim();
@@ -937,105 +1497,129 @@ function getIssueOriginText(issue, copy) {
937
1497
  const humanName = issue.reporter_display_name?.trim();
938
1498
  return humanName ? `${copy.originHumanLabel} \xB7 ${humanName}` : copy.originHumanLabel;
939
1499
  }
940
- function IssueReportVoicePanel({
1500
+ function IssueReportVoiceActivationPanel({
941
1501
  canUseText,
942
1502
  effectiveInputMode,
943
1503
  isSubmitting,
944
- isVoiceActive,
945
- isVoiceConnecting,
946
- voice,
947
- voiceTokenResult,
948
- committedTranscript,
949
- voiceError,
950
- scribeError,
951
1504
  onSelectText,
952
1505
  onSelectVoice,
953
- onStartVoiceInput,
954
- onStopVoiceInput,
955
- onAppendTranscript
1506
+ onRequestStartVoiceInput,
1507
+ onRequestAppendTranscript
956
1508
  }) {
957
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
958
- canUseText ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-5 flex gap-2", children: [
959
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1509
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1510
+ canUseText ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-5 flex gap-2", children: [
1511
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
960
1512
  "button",
961
1513
  {
962
1514
  type: "button",
963
- className: cn(
1515
+ className: cn2(
964
1516
  "inline-flex items-center gap-1 rounded-full border px-3 py-1.5 text-xs font-semibold transition",
965
1517
  effectiveInputMode === "text" ? "border-slate-900 bg-slate-900 text-white" : "border-slate-200 text-slate-700 hover:bg-slate-50"
966
1518
  ),
967
1519
  onClick: onSelectText,
968
1520
  disabled: isSubmitting,
969
1521
  children: [
970
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.TextT, { className: "h-3.5 w-3.5" }),
1522
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.TextT, { className: "h-3.5 w-3.5" }),
971
1523
  "Text Input"
972
1524
  ]
973
1525
  }
974
1526
  ),
975
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1527
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
976
1528
  "button",
977
1529
  {
978
1530
  type: "button",
979
- className: cn(
1531
+ className: cn2(
980
1532
  "inline-flex items-center gap-1 rounded-full border px-3 py-1.5 text-xs font-semibold transition",
981
1533
  effectiveInputMode === "voice" ? "border-slate-900 bg-slate-900 text-white" : "border-slate-200 text-slate-700 hover:bg-slate-50"
982
1534
  ),
983
1535
  onClick: onSelectVoice,
984
1536
  disabled: isSubmitting,
985
1537
  children: [
986
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Microphone, { className: "h-3.5 w-3.5" }),
1538
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Microphone, { className: "h-3.5 w-3.5" }),
987
1539
  "Voice Input"
988
1540
  ]
989
1541
  }
990
1542
  )
991
1543
  ] }) : null,
992
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-4 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3", children: [
993
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
994
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
995
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-xs font-medium uppercase tracking-wide text-slate-500", children: "Voice Input" }),
996
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "mt-1 text-xs text-slate-600", children: [
997
- "Provider: ",
998
- voiceTokenResult?.provider ?? voice.provider,
999
- " \xB7 Model:",
1000
- " ",
1001
- voiceTokenResult?.model_id ?? voice.modelId
1002
- ] }),
1003
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "mt-1 text-xs text-slate-500", children: voice.requireMicrophonePermission ? "Microphone access is required to transcribe." : "Microphone access policy is optional." })
1004
- ] }),
1005
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-2", children: [
1006
- isVoiceActive || isVoiceConnecting ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1007
- "button",
1008
- {
1009
- type: "button",
1010
- className: "rounded-full border border-slate-300 px-3 py-1.5 text-xs font-medium text-slate-700 transition hover:bg-slate-100",
1011
- onClick: onStopVoiceInput,
1012
- disabled: isSubmitting,
1013
- children: "Stop Voice Input"
1014
- }
1015
- ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1016
- "button",
1017
- {
1018
- type: "button",
1019
- className: "rounded-full bg-slate-900 px-3 py-1.5 text-xs font-semibold text-white transition hover:bg-slate-800",
1020
- onClick: onStartVoiceInput,
1021
- disabled: isSubmitting,
1022
- children: "Start Voice Input"
1023
- }
1024
- ),
1025
- canUseText ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1026
- "button",
1027
- {
1028
- type: "button",
1029
- className: "rounded-full border border-slate-300 px-3 py-1.5 text-xs font-medium text-slate-700 transition hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-60",
1030
- onClick: onAppendTranscript,
1031
- disabled: isSubmitting || !committedTranscript.trim(),
1032
- children: "Append Transcript"
1033
- }
1034
- ) : null
1035
- ] })
1544
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mt-4 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
1545
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
1546
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "text-xs font-medium uppercase tracking-wide text-slate-500", children: "Voice Input" }),
1547
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mt-1 text-xs text-slate-600", children: "Voice capture loads when selected." })
1036
1548
  ] }),
1037
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-3 rounded-xl border border-slate-200 bg-white px-3 py-2 text-sm text-slate-700", children: committedTranscript ? committedTranscript : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-slate-500", children: "No committed transcript yet." }) }),
1038
- voiceError || scribeError ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-2 rounded-xl border border-rose-200 bg-rose-50 px-3 py-2 text-xs text-rose-700", children: voiceError ?? scribeError }) : isVoiceConnecting ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-2 text-xs text-slate-500", children: "Connecting voice transcription..." }) : null
1549
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex gap-2", children: [
1550
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1551
+ "button",
1552
+ {
1553
+ type: "button",
1554
+ className: "rounded-full bg-slate-900 px-3 py-1.5 text-xs font-semibold text-white transition hover:bg-slate-800",
1555
+ onClick: onRequestStartVoiceInput,
1556
+ disabled: isSubmitting,
1557
+ children: "Start Voice Input"
1558
+ }
1559
+ ),
1560
+ canUseText ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1561
+ "button",
1562
+ {
1563
+ type: "button",
1564
+ className: "rounded-full border border-slate-300 px-3 py-1.5 text-xs font-medium text-slate-700 transition hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-60",
1565
+ onClick: onRequestAppendTranscript,
1566
+ disabled: isSubmitting,
1567
+ children: "Append Transcript"
1568
+ }
1569
+ ) : null
1570
+ ] })
1571
+ ] }) })
1572
+ ] });
1573
+ }
1574
+ function IssueReportVoiceFallback({
1575
+ canUseText,
1576
+ isSubmitting,
1577
+ onSelectText,
1578
+ onSelectVoice,
1579
+ onRequestStartVoiceInput
1580
+ }) {
1581
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1582
+ canUseText ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-5 flex gap-2", children: [
1583
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1584
+ "button",
1585
+ {
1586
+ type: "button",
1587
+ className: "inline-flex items-center gap-1 rounded-full border border-slate-200 px-3 py-1.5 text-xs font-semibold text-slate-700 transition hover:bg-slate-50",
1588
+ onClick: onSelectText,
1589
+ disabled: isSubmitting,
1590
+ children: [
1591
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.TextT, { className: "h-3.5 w-3.5" }),
1592
+ "Text Input"
1593
+ ]
1594
+ }
1595
+ ),
1596
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1597
+ "button",
1598
+ {
1599
+ type: "button",
1600
+ className: "inline-flex items-center gap-1 rounded-full border border-slate-900 bg-slate-900 px-3 py-1.5 text-xs font-semibold text-white transition",
1601
+ onClick: onSelectVoice,
1602
+ disabled: isSubmitting,
1603
+ children: [
1604
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Microphone, { className: "h-3.5 w-3.5" }),
1605
+ "Voice Input"
1606
+ ]
1607
+ }
1608
+ )
1609
+ ] }) : null,
1610
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-4 flex items-center gap-2 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 text-xs text-slate-600", children: [
1611
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Spinner, { className: "h-3.5 w-3.5 animate-spin" }),
1612
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "flex-1", children: "Loading voice input..." }),
1613
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1614
+ "button",
1615
+ {
1616
+ type: "button",
1617
+ className: "rounded-full bg-slate-900 px-3 py-1.5 text-xs font-semibold text-white transition hover:bg-slate-800",
1618
+ onClick: onRequestStartVoiceInput,
1619
+ disabled: isSubmitting,
1620
+ children: "Start Voice Input"
1621
+ }
1622
+ )
1039
1623
  ] })
1040
1624
  ] });
1041
1625
  }
@@ -1049,13 +1633,13 @@ function IssueReportNoteEditor({
1049
1633
  onSubmit
1050
1634
  }) {
1051
1635
  if (!canUseText) {
1052
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-5 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 text-xs text-slate-600", children: [
1053
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: getIssueNoteLengthMessage(normalizedNote, copy) }),
1054
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-1", children: "Submit is enabled after a committed transcript reaches 10-2000 characters." })
1636
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-5 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 text-xs text-slate-600", children: [
1637
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { children: getIssueNoteLengthMessage(normalizedNote, copy) }),
1638
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mt-1", children: "Submit is enabled after a committed transcript reaches 10-2000 characters." })
1055
1639
  ] });
1056
1640
  }
1057
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-5 space-y-2", children: [
1058
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1641
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-5 space-y-2", children: [
1642
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1059
1643
  "textarea",
1060
1644
  {
1061
1645
  value: note,
@@ -1072,9 +1656,9 @@ function IssueReportNoteEditor({
1072
1656
  autoFocus: true
1073
1657
  }
1074
1658
  ),
1075
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between text-xs text-slate-500", children: [
1076
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: getIssueNoteLengthMessage(note, copy) }),
1077
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: copy.keyboardShortcutHint })
1659
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between text-xs text-slate-500", children: [
1660
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: getIssueNoteLengthMessage(note, copy) }),
1661
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: copy.keyboardShortcutHint })
1078
1662
  ] })
1079
1663
  ] });
1080
1664
  }
@@ -1084,10 +1668,10 @@ function IssueReportModalDescription({
1084
1668
  target
1085
1669
  }) {
1086
1670
  if (mode === "create" && target) {
1087
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
1671
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1088
1672
  copy.createDescriptionPrefix,
1089
1673
  " ",
1090
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { className: "rounded bg-slate-100 px-1.5 py-0.5 text-xs text-slate-700", children: target.page_url })
1674
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { className: "rounded bg-slate-100 px-1.5 py-0.5 text-xs text-slate-700", children: target.page_url })
1091
1675
  ] });
1092
1676
  }
1093
1677
  return mode === "edit" ? copy.editDescription : copy.replyDescription;
@@ -1106,13 +1690,10 @@ function IssueReportModalBody({
1106
1690
  normalizedNote,
1107
1691
  isValid,
1108
1692
  isSubmitting,
1109
- isVoiceActive,
1110
- isVoiceConnecting,
1111
1693
  voice,
1112
- voiceTokenResult,
1113
- committedTranscript,
1114
- voiceError,
1115
- scribeError,
1694
+ createVoiceToken,
1695
+ voiceStartRequestId,
1696
+ voiceAppendRequestId,
1116
1697
  submitError,
1117
1698
  existingAttachments,
1118
1699
  removedExistingIds,
@@ -1123,8 +1704,10 @@ function IssueReportModalBody({
1123
1704
  onClose,
1124
1705
  onSelectText,
1125
1706
  onSelectVoice,
1126
- onStartVoiceInput,
1127
- onStopVoiceInput,
1707
+ onRequestStartVoiceInput,
1708
+ onRequestAppendTranscript,
1709
+ onTranscriptCommitted,
1710
+ onVoiceSubmitMetadataChange,
1128
1711
  onAppendTranscript,
1129
1712
  onNoteChange,
1130
1713
  onSubmit,
@@ -1133,16 +1716,16 @@ function IssueReportModalBody({
1133
1716
  onRemovePendingAttachment
1134
1717
  }) {
1135
1718
  if (isHydrating) {
1136
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-5 flex items-center gap-2 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-4 text-sm text-slate-600", children: [
1137
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Spinner, { className: "h-4 w-4 animate-spin" }),
1138
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: copy.hydrateLoading })
1719
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-5 flex items-center gap-2 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-4 text-sm text-slate-600", children: [
1720
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Spinner, { className: "h-4 w-4 animate-spin" }),
1721
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: copy.hydrateLoading })
1139
1722
  ] });
1140
1723
  }
1141
1724
  if (error) {
1142
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-5 space-y-3 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-4 text-sm text-rose-700", children: [
1143
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: error }),
1144
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-2", children: [
1145
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1725
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-5 space-y-3 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-4 text-sm text-rose-700", children: [
1726
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { children: error }),
1727
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex gap-2", children: [
1728
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1146
1729
  "button",
1147
1730
  {
1148
1731
  type: "button",
@@ -1151,7 +1734,7 @@ function IssueReportModalBody({
1151
1734
  children: copy.retryAction
1152
1735
  }
1153
1736
  ),
1154
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1737
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1155
1738
  "button",
1156
1739
  {
1157
1740
  type: "button",
@@ -1163,32 +1746,55 @@ function IssueReportModalBody({
1163
1746
  ] })
1164
1747
  ] });
1165
1748
  }
1166
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
1167
- mode === "create" && canUseVoice ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1168
- IssueReportVoicePanel,
1749
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1750
+ mode === "create" && canUseVoice ? effectiveInputMode === "voice" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1751
+ import_react7.default.Suspense,
1752
+ {
1753
+ fallback: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1754
+ IssueReportVoiceFallback,
1755
+ {
1756
+ canUseText,
1757
+ isSubmitting,
1758
+ onSelectText,
1759
+ onSelectVoice,
1760
+ onRequestStartVoiceInput
1761
+ }
1762
+ ),
1763
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1764
+ VoiceCapture2,
1765
+ {
1766
+ canUseText,
1767
+ effectiveInputMode,
1768
+ isSubmitting,
1769
+ voice,
1770
+ createVoiceToken,
1771
+ startRequestId: voiceStartRequestId,
1772
+ appendRequestId: voiceAppendRequestId,
1773
+ onSelectText,
1774
+ onSelectVoice,
1775
+ onTranscriptCommitted,
1776
+ onSubmitMetadataChange: onVoiceSubmitMetadataChange,
1777
+ onAppendTranscript
1778
+ }
1779
+ )
1780
+ }
1781
+ ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1782
+ IssueReportVoiceActivationPanel,
1169
1783
  {
1170
1784
  canUseText,
1171
1785
  effectiveInputMode,
1172
1786
  isSubmitting,
1173
- isVoiceActive,
1174
- isVoiceConnecting,
1175
- voice,
1176
- voiceTokenResult,
1177
- committedTranscript,
1178
- voiceError,
1179
- scribeError,
1180
1787
  onSelectText,
1181
1788
  onSelectVoice,
1182
- onStartVoiceInput,
1183
- onStopVoiceInput,
1184
- onAppendTranscript
1789
+ onRequestStartVoiceInput,
1790
+ onRequestAppendTranscript
1185
1791
  }
1186
1792
  ) : null,
1187
- mode === "reply" && issue ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-5 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3", children: [
1188
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-xs font-medium uppercase tracking-wide text-slate-500", children: copy.originalIssueLabel }),
1189
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "mt-1 text-sm text-slate-700", children: issue.note })
1793
+ mode === "reply" && issue ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-5 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3", children: [
1794
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "text-xs font-medium uppercase tracking-wide text-slate-500", children: copy.originalIssueLabel }),
1795
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mt-1 text-sm text-slate-700", children: issue.note })
1190
1796
  ] }) : null,
1191
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1797
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1192
1798
  IssueReportNoteEditor,
1193
1799
  {
1194
1800
  canUseText,
@@ -1200,7 +1806,7 @@ function IssueReportModalBody({
1200
1806
  onSubmit
1201
1807
  }
1202
1808
  ),
1203
- canUseAttachments && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1809
+ canUseAttachments && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1204
1810
  AttachmentPicker,
1205
1811
  {
1206
1812
  existingAttachments,
@@ -1214,9 +1820,9 @@ function IssueReportModalBody({
1214
1820
  onRemovePending: onRemovePendingAttachment
1215
1821
  }
1216
1822
  ),
1217
- submitError ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-4 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-700", children: submitError }) : null,
1218
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-6 flex justify-end gap-3", children: [
1219
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1823
+ submitError ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mt-4 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-700", children: submitError }) : null,
1824
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-6 flex justify-end gap-3", children: [
1825
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1220
1826
  "button",
1221
1827
  {
1222
1828
  type: "button",
@@ -1226,11 +1832,11 @@ function IssueReportModalBody({
1226
1832
  children: copy.cancelAction
1227
1833
  }
1228
1834
  ),
1229
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1835
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1230
1836
  "button",
1231
1837
  {
1232
1838
  type: "button",
1233
- className: cn(
1839
+ className: cn2(
1234
1840
  "rounded-full px-4 py-2 text-sm font-semibold text-white transition",
1235
1841
  isValid && !isSubmitting ? "bg-slate-900 hover:bg-slate-800" : "cursor-not-allowed bg-slate-300"
1236
1842
  ),
@@ -1240,104 +1846,9 @@ function IssueReportModalBody({
1240
1846
  }
1241
1847
  )
1242
1848
  ] }),
1243
- mode !== "create" && issue ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IssueReportMessageThread, { issueReportId: issue.id }) : null
1849
+ mode !== "create" && issue ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(IssueReportMessageThread, { issueReportId: issue.id }) : null
1244
1850
  ] });
1245
1851
  }
1246
- function useIssueReportVoiceCapture({
1247
- canUseVoice,
1248
- defaultInputMode,
1249
- isSubmitting,
1250
- voice
1251
- }) {
1252
- const [inputMode, setInputMode] = (0, import_react5.useState)(defaultInputMode);
1253
- const [voiceTokenResult, setVoiceTokenResult] = (0, import_react5.useState)(null);
1254
- const [voiceSubmitMetadata, setVoiceSubmitMetadata] = (0, import_react5.useState)(null);
1255
- const [voiceError, setVoiceError] = (0, import_react5.useState)(null);
1256
- const {
1257
- status: scribeStatus,
1258
- isConnected,
1259
- isTranscribing,
1260
- committedTranscripts,
1261
- error: scribeError,
1262
- connect: connectScribe,
1263
- disconnect: disconnectScribe
1264
- } = (0, import_react3.useScribe)({
1265
- autoConnect: false,
1266
- modelId: voice.modelId,
1267
- microphone: voice.microphone
1268
- });
1269
- const committedTranscript = (0, import_react5.useMemo)(
1270
- () => getCommittedTranscriptText(committedTranscripts),
1271
- [committedTranscripts]
1272
- );
1273
- const isVoiceConnecting = scribeStatus === "connecting";
1274
- const isVoiceActive = isConnected || isTranscribing;
1275
- const resetVoiceCapture = (0, import_react5.useCallback)(() => {
1276
- disconnectScribe();
1277
- setInputMode(defaultInputMode);
1278
- setVoiceTokenResult(null);
1279
- setVoiceSubmitMetadata(null);
1280
- setVoiceError(null);
1281
- }, [defaultInputMode, disconnectScribe]);
1282
- const startVoiceInput = (0, import_react5.useCallback)(
1283
- async (createVoiceToken) => {
1284
- if (!canUseVoice || isSubmitting || isVoiceActive || isVoiceConnecting) {
1285
- return;
1286
- }
1287
- if (!createVoiceToken) {
1288
- setVoiceError("Voice input is unavailable for this client.");
1289
- return;
1290
- }
1291
- setVoiceError(null);
1292
- try {
1293
- const tokenResult = await createVoiceToken();
1294
- setVoiceTokenResult(tokenResult);
1295
- setVoiceSubmitMetadata(tokenResult);
1296
- await connectScribe({
1297
- token: tokenResult.token,
1298
- modelId: tokenResult.model_id,
1299
- microphone: voice.microphone
1300
- });
1301
- setInputMode("voice");
1302
- } catch (startError) {
1303
- setVoiceError(resolveErrorMessage(startError, "Failed to start voice input."));
1304
- }
1305
- },
1306
- [
1307
- canUseVoice,
1308
- connectScribe,
1309
- isSubmitting,
1310
- isVoiceActive,
1311
- isVoiceConnecting,
1312
- voice.microphone
1313
- ]
1314
- );
1315
- const appendTranscript = (0, import_react5.useCallback)(
1316
- (setNote) => {
1317
- if (!committedTranscript.trim()) {
1318
- return;
1319
- }
1320
- setNote((current) => appendTranscriptToNote(current, committedTranscript));
1321
- setInputMode("text");
1322
- },
1323
- [committedTranscript]
1324
- );
1325
- return {
1326
- inputMode,
1327
- setInputMode,
1328
- voiceTokenResult,
1329
- voiceSubmitMetadata,
1330
- committedTranscript,
1331
- voiceError,
1332
- scribeError,
1333
- isVoiceConnecting,
1334
- isVoiceActive,
1335
- resetVoiceCapture,
1336
- startVoiceInput,
1337
- stopVoiceInput: disconnectScribe,
1338
- appendTranscript
1339
- };
1340
- }
1341
1852
  var INITIAL_UPLOAD_PROGRESS = {
1342
1853
  phase: "idle",
1343
1854
  uploaded: 0,
@@ -1373,23 +1884,23 @@ function AttachmentPicker({
1373
1884
  onRemoveExisting,
1374
1885
  onRemovePending
1375
1886
  }) {
1376
- const fileInputRef = (0, import_react5.useRef)(null);
1887
+ const fileInputRef = (0, import_react7.useRef)(null);
1377
1888
  const retainedExisting = existingAttachments.filter(
1378
1889
  (a) => !removedExistingIds.has(a.id)
1379
1890
  );
1380
1891
  const totalCount = retainedExisting.length + pendingFiles.length;
1381
1892
  const canAdd = totalCount < ATTACHMENT_MAX_COUNT && !disabled;
1382
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-4 space-y-3", children: [
1383
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between", children: [
1384
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-1.5 text-xs font-medium uppercase tracking-wide text-slate-500", children: [
1385
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Image, { className: "h-3.5 w-3.5" }),
1893
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-4 space-y-3", children: [
1894
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between", children: [
1895
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-1.5 text-xs font-medium uppercase tracking-wide text-slate-500", children: [
1896
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Image, { className: "h-3.5 w-3.5" }),
1386
1897
  "Screenshots (",
1387
1898
  totalCount,
1388
1899
  "/",
1389
1900
  ATTACHMENT_MAX_COUNT,
1390
1901
  ")"
1391
1902
  ] }),
1392
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1903
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1393
1904
  "input",
1394
1905
  {
1395
1906
  ref: fileInputRef,
@@ -1407,11 +1918,11 @@ function AttachmentPicker({
1407
1918
  "aria-label": "Select screenshot files"
1408
1919
  }
1409
1920
  ),
1410
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1921
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1411
1922
  "button",
1412
1923
  {
1413
1924
  type: "button",
1414
- className: cn(
1925
+ className: cn2(
1415
1926
  "rounded-full border px-3 py-1 text-xs font-medium transition",
1416
1927
  canAdd ? "border-slate-300 text-slate-700 hover:bg-slate-50" : "cursor-not-allowed border-slate-200 text-slate-400"
1417
1928
  ),
@@ -1422,7 +1933,7 @@ function AttachmentPicker({
1422
1933
  }
1423
1934
  )
1424
1935
  ] }),
1425
- validationErrors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "space-y-1", children: validationErrors.map((err, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1936
+ validationErrors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "space-y-1", children: validationErrors.map((err, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1426
1937
  "div",
1427
1938
  {
1428
1939
  className: "rounded-xl border border-rose-200 bg-rose-50 px-3 py-2 text-xs text-rose-700",
@@ -1431,15 +1942,15 @@ function AttachmentPicker({
1431
1942
  },
1432
1943
  i
1433
1944
  )) }),
1434
- uploadProgress.phase === "uploading" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2 rounded-xl border border-slate-200 bg-slate-50 px-3 py-2 text-xs text-slate-600", children: [
1435
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Spinner, { className: "h-3.5 w-3.5 animate-spin" }),
1945
+ uploadProgress.phase === "uploading" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-2 rounded-xl border border-slate-200 bg-slate-50 px-3 py-2 text-xs text-slate-600", children: [
1946
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Spinner, { className: "h-3.5 w-3.5 animate-spin" }),
1436
1947
  "Uploading ",
1437
1948
  uploadProgress.uploaded,
1438
1949
  " of ",
1439
1950
  uploadProgress.total,
1440
1951
  "..."
1441
1952
  ] }),
1442
- uploadProgress.phase === "error" && uploadProgress.error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1953
+ uploadProgress.phase === "error" && uploadProgress.error && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1443
1954
  "div",
1444
1955
  {
1445
1956
  className: "rounded-xl border border-rose-200 bg-rose-50 px-3 py-2 text-xs text-rose-700",
@@ -1447,18 +1958,18 @@ function AttachmentPicker({
1447
1958
  children: uploadProgress.error
1448
1959
  }
1449
1960
  ),
1450
- (retainedExisting.length > 0 || pendingFiles.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-wrap gap-2", children: [
1451
- retainedExisting.map((att) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1961
+ (retainedExisting.length > 0 || pendingFiles.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-wrap gap-2", children: [
1962
+ retainedExisting.map((att) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1452
1963
  "div",
1453
1964
  {
1454
1965
  className: "group relative flex items-center gap-2 rounded-xl border border-slate-200 bg-white px-3 py-2",
1455
1966
  children: [
1456
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Image, { className: "h-4 w-4 flex-shrink-0 text-slate-400" }),
1457
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "min-w-0", children: [
1458
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "max-w-[140px] truncate text-xs font-medium text-slate-700", children: att.original_filename }),
1459
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-xs text-slate-400", children: formatFileSize(att.byte_size) })
1967
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Image, { className: "h-4 w-4 flex-shrink-0 text-slate-400" }),
1968
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "min-w-0", children: [
1969
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "max-w-[140px] truncate text-xs font-medium text-slate-700", children: att.original_filename }),
1970
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "text-xs text-slate-400", children: formatFileSize(att.byte_size) })
1460
1971
  ] }),
1461
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1972
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1462
1973
  "button",
1463
1974
  {
1464
1975
  type: "button",
@@ -1466,19 +1977,19 @@ function AttachmentPicker({
1466
1977
  onClick: () => onRemoveExisting(att.id),
1467
1978
  disabled,
1468
1979
  "aria-label": `Remove ${att.original_filename}`,
1469
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Trash, { className: "h-3.5 w-3.5" })
1980
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Trash, { className: "h-3.5 w-3.5" })
1470
1981
  }
1471
1982
  )
1472
1983
  ]
1473
1984
  },
1474
1985
  att.id
1475
1986
  )),
1476
- pendingFiles.map((pf) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1987
+ pendingFiles.map((pf) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1477
1988
  "div",
1478
1989
  {
1479
1990
  className: "group relative flex items-center gap-2 rounded-xl border border-slate-200 bg-white px-3 py-2",
1480
1991
  children: [
1481
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1992
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1482
1993
  "img",
1483
1994
  {
1484
1995
  src: pf.previewUrl,
@@ -1486,11 +1997,11 @@ function AttachmentPicker({
1486
1997
  className: "h-8 w-8 flex-shrink-0 rounded object-cover"
1487
1998
  }
1488
1999
  ),
1489
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "min-w-0", children: [
1490
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "max-w-[140px] truncate text-xs font-medium text-slate-700", children: pf.file.name }),
1491
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-xs text-slate-400", children: formatFileSize(pf.file.size) })
2000
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "min-w-0", children: [
2001
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "max-w-[140px] truncate text-xs font-medium text-slate-700", children: pf.file.name }),
2002
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "text-xs text-slate-400", children: formatFileSize(pf.file.size) })
1492
2003
  ] }),
1493
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2004
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1494
2005
  "button",
1495
2006
  {
1496
2007
  type: "button",
@@ -1498,7 +2009,7 @@ function AttachmentPicker({
1498
2009
  onClick: () => onRemovePending(pf.clientId),
1499
2010
  disabled,
1500
2011
  "aria-label": `Remove ${pf.file.name}`,
1501
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Trash, { className: "h-3.5 w-3.5" })
2012
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Trash, { className: "h-3.5 w-3.5" })
1502
2013
  }
1503
2014
  )
1504
2015
  ]
@@ -1509,16 +2020,16 @@ function AttachmentPicker({
1509
2020
  ] });
1510
2021
  }
1511
2022
  function useAttachmentState(existingAttachments) {
1512
- const [pendingFiles, setPendingFiles] = (0, import_react5.useState)([]);
1513
- const [removedExistingIds, setRemovedExistingIds] = (0, import_react5.useState)(
2023
+ const [pendingFiles, setPendingFiles] = (0, import_react7.useState)([]);
2024
+ const [removedExistingIds, setRemovedExistingIds] = (0, import_react7.useState)(
1514
2025
  /* @__PURE__ */ new Set()
1515
2026
  );
1516
- const [validationErrors, setValidationErrors] = (0, import_react5.useState)([]);
1517
- const [uploadProgress, setUploadProgress] = (0, import_react5.useState)(INITIAL_UPLOAD_PROGRESS);
2027
+ const [validationErrors, setValidationErrors] = (0, import_react7.useState)([]);
2028
+ const [uploadProgress, setUploadProgress] = (0, import_react7.useState)(INITIAL_UPLOAD_PROGRESS);
1518
2029
  const retainedExistingCount = existingAttachments.filter(
1519
2030
  (a) => !removedExistingIds.has(a.id)
1520
2031
  ).length;
1521
- const reset = (0, import_react5.useCallback)(() => {
2032
+ const reset = (0, import_react7.useCallback)(() => {
1522
2033
  for (const pf of pendingFiles) {
1523
2034
  URL.revokeObjectURL(pf.previewUrl);
1524
2035
  }
@@ -1527,7 +2038,7 @@ function useAttachmentState(existingAttachments) {
1527
2038
  setValidationErrors([]);
1528
2039
  setUploadProgress(INITIAL_UPLOAD_PROGRESS);
1529
2040
  }, [pendingFiles]);
1530
- const addFiles = (0, import_react5.useCallback)(
2041
+ const addFiles = (0, import_react7.useCallback)(
1531
2042
  (fileList) => {
1532
2043
  const currentTotal = retainedExistingCount + pendingFiles.length;
1533
2044
  const errors = [];
@@ -1561,10 +2072,10 @@ function useAttachmentState(existingAttachments) {
1561
2072
  },
1562
2073
  [pendingFiles.length, retainedExistingCount]
1563
2074
  );
1564
- const removeExisting = (0, import_react5.useCallback)((id) => {
2075
+ const removeExisting = (0, import_react7.useCallback)((id) => {
1565
2076
  setRemovedExistingIds((prev) => /* @__PURE__ */ new Set([...prev, id]));
1566
2077
  }, []);
1567
- const removePending = (0, import_react5.useCallback)((clientId) => {
2078
+ const removePending = (0, import_react7.useCallback)((clientId) => {
1568
2079
  setPendingFiles((prev) => {
1569
2080
  const removed = prev.find((pf) => pf.clientId === clientId);
1570
2081
  if (removed) {
@@ -1574,7 +2085,7 @@ function useAttachmentState(existingAttachments) {
1574
2085
  });
1575
2086
  setUploadProgress(INITIAL_UPLOAD_PROGRESS);
1576
2087
  }, []);
1577
- const markPendingUploaded = (0, import_react5.useCallback)((clientId, attachmentId) => {
2088
+ const markPendingUploaded = (0, import_react7.useCallback)((clientId, attachmentId) => {
1578
2089
  setPendingFiles(
1579
2090
  (prev) => prev.map(
1580
2091
  (pf) => pf.clientId === clientId ? { ...pf, uploadedAttachmentId: attachmentId } : pf
@@ -1632,19 +2143,19 @@ function MessageBubble({
1632
2143
  const isReporter = message.actor.author_type === "reporter";
1633
2144
  const isFinal = message.kind === "final_response";
1634
2145
  const authorLabel = isReporter ? copy.threadAuthorReporter : copy.threadAuthorOperator;
1635
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2146
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1636
2147
  "li",
1637
2148
  {
1638
- className: cn(
2149
+ className: cn2(
1639
2150
  "flex flex-col gap-1",
1640
2151
  isReporter ? "items-end" : "items-start"
1641
2152
  ),
1642
2153
  children: [
1643
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2", children: [
1644
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2154
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-2", children: [
2155
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1645
2156
  "span",
1646
2157
  {
1647
- className: cn(
2158
+ className: cn2(
1648
2159
  "rounded-full px-2 py-0.5 font-medium",
1649
2160
  BADGE_TEXT,
1650
2161
  getMessageKindClassName(message.kind)
@@ -1652,8 +2163,8 @@ function MessageBubble({
1652
2163
  children: getMessageKindLabel(message.kind, copy)
1653
2164
  }
1654
2165
  ),
1655
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: cn("font-medium text-slate-600", LABEL_TEXT), children: authorLabel }),
1656
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2166
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: cn2("font-medium text-slate-600", LABEL_TEXT), children: authorLabel }),
2167
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1657
2168
  "time",
1658
2169
  {
1659
2170
  className: "text-xs text-slate-400",
@@ -1662,10 +2173,10 @@ function MessageBubble({
1662
2173
  }
1663
2174
  )
1664
2175
  ] }),
1665
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2176
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1666
2177
  "div",
1667
2178
  {
1668
- className: cn(
2179
+ className: cn2(
1669
2180
  "max-w-[85%] whitespace-pre-wrap rounded-2xl border px-3 py-2 text-sm",
1670
2181
  isReporter ? "border-slate-900 bg-slate-900 text-white" : isFinal ? "border-emerald-200 bg-emerald-50 text-emerald-950" : "border-slate-200 bg-white text-slate-800"
1671
2182
  ),
@@ -1681,9 +2192,9 @@ function ReporterResponseComposer({
1681
2192
  copy
1682
2193
  }) {
1683
2194
  const mutation = useIssueReportingMessageMutation(issueReportId);
1684
- const idempotencyKeyRef = (0, import_react5.useRef)(generateIdempotencyKey());
1685
- const [body, setBody] = (0, import_react5.useState)("");
1686
- const [submitError, setSubmitError] = (0, import_react5.useState)(null);
2195
+ const idempotencyKeyRef = (0, import_react7.useRef)(generateIdempotencyKey());
2196
+ const [body, setBody] = (0, import_react7.useState)("");
2197
+ const [submitError, setSubmitError] = (0, import_react7.useState)(null);
1687
2198
  const normalized = body.trim();
1688
2199
  const isValid = normalized.length >= REPORTER_MESSAGE_MIN_LENGTH && normalized.length <= REPORTER_MESSAGE_MAX_LENGTH;
1689
2200
  const isSubmitting = mutation.isPending;
@@ -1705,22 +2216,22 @@ function ReporterResponseComposer({
1705
2216
  idempotencyKeyRef.current = generateIdempotencyKey();
1706
2217
  return;
1707
2218
  }
1708
- setSubmitError(resolveErrorMessage(error, copy.threadResponseFailed));
2219
+ setSubmitError(resolveErrorMessage2(error, copy.threadResponseFailed));
1709
2220
  }
1710
2221
  };
1711
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-3 space-y-2", children: [
1712
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2222
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-3 space-y-2", children: [
2223
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1713
2224
  "label",
1714
2225
  {
1715
2226
  htmlFor: "issue-report-thread-response",
1716
- className: cn(
2227
+ className: cn2(
1717
2228
  "block font-medium uppercase tracking-wide text-slate-500",
1718
2229
  LABEL_TEXT
1719
2230
  ),
1720
2231
  children: copy.threadResponseLabel
1721
2232
  }
1722
2233
  ),
1723
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2234
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1724
2235
  "textarea",
1725
2236
  {
1726
2237
  id: "issue-report-thread-response",
@@ -1737,7 +2248,7 @@ function ReporterResponseComposer({
1737
2248
  disabled: isSubmitting
1738
2249
  }
1739
2250
  ),
1740
- submitError ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2251
+ submitError ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1741
2252
  "div",
1742
2253
  {
1743
2254
  className: "rounded-xl border border-rose-200 bg-rose-50 px-3 py-2 text-xs text-rose-700",
@@ -1745,11 +2256,11 @@ function ReporterResponseComposer({
1745
2256
  children: submitError
1746
2257
  }
1747
2258
  ) : null,
1748
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2259
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1749
2260
  "button",
1750
2261
  {
1751
2262
  type: "button",
1752
- className: cn(
2263
+ className: cn2(
1753
2264
  "rounded-full px-4 py-2 text-sm font-semibold text-white transition",
1754
2265
  isValid && !isSubmitting ? "bg-slate-900 hover:bg-slate-800" : "cursor-not-allowed bg-slate-300"
1755
2266
  ),
@@ -1768,7 +2279,7 @@ function IssueReportMessageThread({
1768
2279
  const supportsSubmit = Boolean(client.issueReporting.submitMessage);
1769
2280
  const query = useIssueReportingMessages(issueReportId);
1770
2281
  const needsResponse = query.data?.needs_response ?? false;
1771
- (0, import_react5.useEffect)(() => {
2282
+ (0, import_react7.useEffect)(() => {
1772
2283
  setNeedsResponse(issueReportId, needsResponse);
1773
2284
  return () => {
1774
2285
  setNeedsResponse(issueReportId, false);
@@ -1778,18 +2289,18 @@ function IssueReportMessageThread({
1778
2289
  return null;
1779
2290
  }
1780
2291
  const visibleMessages = selectReporterVisibleMessages(query.data?.items ?? []);
1781
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2292
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1782
2293
  "section",
1783
2294
  {
1784
2295
  "aria-label": copy.threadTitle,
1785
2296
  className: "mt-6 border-t border-slate-100 pt-5",
1786
2297
  children: [
1787
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [
1788
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: "text-sm font-semibold text-slate-900", children: copy.threadTitle }),
1789
- needsResponse ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2298
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [
2299
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { className: "text-sm font-semibold text-slate-900", children: copy.threadTitle }),
2300
+ needsResponse ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1790
2301
  "span",
1791
2302
  {
1792
- className: cn(
2303
+ className: cn2(
1793
2304
  "rounded-full px-2 py-0.5 font-medium",
1794
2305
  BADGE_TEXT,
1795
2306
  "bg-amber-100 text-amber-700"
@@ -1798,13 +2309,13 @@ function IssueReportMessageThread({
1798
2309
  }
1799
2310
  ) : null
1800
2311
  ] }),
1801
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "mt-1 text-xs text-slate-500", children: copy.threadDescription }),
1802
- query.isPending ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-4 flex items-center gap-2 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-4 text-sm text-slate-600", children: [
1803
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Spinner, { className: "h-4 w-4 animate-spin" }),
1804
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: copy.threadLoading })
1805
- ] }) : query.error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-4 space-y-3 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-4 text-sm text-rose-700", children: [
1806
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: resolveErrorMessage(query.error, copy.threadLoadFailed) }),
1807
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2312
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mt-1 text-xs text-slate-500", children: copy.threadDescription }),
2313
+ query.isPending ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-4 flex items-center gap-2 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-4 text-sm text-slate-600", children: [
2314
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Spinner, { className: "h-4 w-4 animate-spin" }),
2315
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: copy.threadLoading })
2316
+ ] }) : query.error ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-4 space-y-3 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-4 text-sm text-rose-700", children: [
2317
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { children: resolveErrorMessage2(query.error, copy.threadLoadFailed) }),
2318
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1808
2319
  "button",
1809
2320
  {
1810
2321
  type: "button",
@@ -1813,8 +2324,8 @@ function IssueReportMessageThread({
1813
2324
  children: copy.retryAction
1814
2325
  }
1815
2326
  )
1816
- ] }) : visibleMessages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-4 rounded-2xl border border-dashed border-slate-200 bg-slate-50 px-4 py-6 text-center text-sm text-slate-500", children: copy.threadEmpty }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("ul", { className: "mt-4 space-y-3", children: visibleMessages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MessageBubble, { message, copy }, message.id)) }),
1817
- supportsSubmit ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2327
+ ] }) : visibleMessages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mt-4 rounded-2xl border border-dashed border-slate-200 bg-slate-50 px-4 py-6 text-center text-sm text-slate-500", children: copy.threadEmpty }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ul", { className: "mt-4 space-y-3", children: visibleMessages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MessageBubble, { message, copy }, message.id)) }),
2328
+ supportsSubmit ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1818
2329
  ReporterResponseComposer,
1819
2330
  {
1820
2331
  issueReportId,
@@ -1831,10 +2342,10 @@ function IssueReportModeBanner() {
1831
2342
  if (!reportMode?.isReportMode) {
1832
2343
  return null;
1833
2344
  }
1834
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: cn("fixed inset-x-4 top-4 flex justify-center", Z_BANNER), children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "max-w-xl rounded-full border border-amber-300 bg-amber-50/95 px-4 py-3 text-sm text-amber-950 shadow-lg backdrop-blur", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-wrap items-center justify-center gap-3", children: [
1835
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "font-medium", children: copy.reportModeTitle }),
1836
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-amber-900/80", children: copy.reportModeDescription }),
1837
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2345
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: cn2("fixed inset-x-4 top-4 flex justify-center", Z_BANNER), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "max-w-xl rounded-full border border-amber-300 bg-amber-50/95 px-4 py-3 text-sm text-amber-950 shadow-lg backdrop-blur", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-wrap items-center justify-center gap-3", children: [
2346
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "font-medium", children: copy.reportModeTitle }),
2347
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "text-amber-900/80", children: copy.reportModeDescription }),
2348
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1838
2349
  "button",
1839
2350
  {
1840
2351
  type: "button",
@@ -1853,15 +2364,15 @@ function IssueList({
1853
2364
  const { copy, principalId } = useIssueReporting();
1854
2365
  const history = useIssueReportingHistory(filter);
1855
2366
  if (history.isPending) {
1856
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-5 text-sm text-slate-600", children: [
1857
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Spinner, { className: "h-4 w-4 animate-spin" }),
1858
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: copy.historyLoading })
2367
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-2 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-5 text-sm text-slate-600", children: [
2368
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Spinner, { className: "h-4 w-4 animate-spin" }),
2369
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: copy.historyLoading })
1859
2370
  ] });
1860
2371
  }
1861
2372
  if (history.error) {
1862
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "space-y-3 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-4 text-sm text-rose-700", children: [
1863
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: history.error.message || copy.historyLoadFailed }),
1864
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2373
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-3 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-4 text-sm text-rose-700", children: [
2374
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { children: history.error.message || copy.historyLoadFailed }),
2375
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1865
2376
  "button",
1866
2377
  {
1867
2378
  type: "button",
@@ -1874,26 +2385,26 @@ function IssueList({
1874
2385
  }
1875
2386
  if (history.items.length === 0) {
1876
2387
  const emptyMessage = filter === "unresolved" ? copy.emptyUnresolved : filter === "resolved" ? copy.emptyResolved : copy.emptyAll;
1877
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "rounded-2xl border border-dashed border-slate-200 bg-slate-50 px-4 py-8 text-center text-sm text-slate-500", children: emptyMessage });
2388
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "rounded-2xl border border-dashed border-slate-200 bg-slate-50 px-4 py-8 text-center text-sm text-slate-500", children: emptyMessage });
1878
2389
  }
1879
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "max-h-80 space-y-2 overflow-y-auto pr-1", children: history.items.map((issue) => {
2390
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "max-h-80 space-y-2 overflow-y-auto pr-1", children: history.items.map((issue) => {
1880
2391
  const action = getIssueAction(issue, principalId);
1881
2392
  const onAction = action === "edit" ? () => onEdit(issue.id) : action === "reply" ? () => onReply(issue.id) : null;
1882
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2393
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1883
2394
  "div",
1884
2395
  {
1885
2396
  className: "rounded-2xl border border-slate-200 bg-white px-3 py-3 shadow-sm",
1886
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-start gap-3", children: [
1887
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-0.5 flex-shrink-0", children: isClosedIssueStatus(issue.status) ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.CheckCircle, { className: "h-4 w-4 text-emerald-600", weight: "fill" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Circle, { className: "h-4 w-4 text-rose-600", weight: "fill" }) }),
1888
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "min-w-0 flex-1", children: [
1889
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-start justify-between gap-2", children: [
1890
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "min-w-0", children: [
1891
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "truncate text-sm font-semibold text-slate-900", children: issue.target.component_label }),
1892
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-1 flex flex-wrap items-center gap-2", children: [
1893
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2397
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-start gap-3", children: [
2398
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mt-0.5 flex-shrink-0", children: isClosedIssueStatus(issue.status) ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.CheckCircle, { className: "h-4 w-4 text-emerald-600", weight: "fill" }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Circle, { className: "h-4 w-4 text-rose-600", weight: "fill" }) }),
2399
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "min-w-0 flex-1", children: [
2400
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-start justify-between gap-2", children: [
2401
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "min-w-0", children: [
2402
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "truncate text-sm font-semibold text-slate-900", children: issue.target.component_label }),
2403
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-1 flex flex-wrap items-center gap-2", children: [
2404
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1894
2405
  "span",
1895
2406
  {
1896
- className: cn(
2407
+ className: cn2(
1897
2408
  "rounded-full px-2 py-0.5 font-medium",
1898
2409
  BADGE_TEXT,
1899
2410
  getIssueStatusClassName(issue.status)
@@ -1901,11 +2412,11 @@ function IssueList({
1901
2412
  children: getIssueStatusBadgeLabel(issue.status)
1902
2413
  }
1903
2414
  ),
1904
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: cn("rounded-full bg-slate-100 px-2 py-0.5 font-medium text-slate-600", BADGE_TEXT), children: getIssueOriginText(issue, copy) }),
1905
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-xs text-slate-400", children: formatRelativeTime(issue.updated_at) })
2415
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: cn2("rounded-full bg-slate-100 px-2 py-0.5 font-medium text-slate-600", BADGE_TEXT), children: getIssueOriginText(issue, copy) }),
2416
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-xs text-slate-400", children: formatRelativeTime(issue.updated_at) })
1906
2417
  ] })
1907
2418
  ] }),
1908
- action && onAction ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2419
+ action && onAction ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1909
2420
  "button",
1910
2421
  {
1911
2422
  type: "button",
@@ -1915,8 +2426,8 @@ function IssueList({
1915
2426
  }
1916
2427
  ) : null
1917
2428
  ] }),
1918
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "mt-2 text-sm text-slate-600", children: truncate(issue.note) }),
1919
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-2 truncate text-xs text-slate-400", children: issue.target.page_url })
2429
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mt-2 text-sm text-slate-600", children: truncate(issue.note) }),
2430
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mt-2 truncate text-xs text-slate-400", children: issue.target.page_url })
1920
2431
  ] })
1921
2432
  ] })
1922
2433
  },
@@ -1925,7 +2436,7 @@ function IssueList({
1925
2436
  }) });
1926
2437
  }
1927
2438
  function IssueReportPopover({ children }) {
1928
- const [filter, setFilter] = (0, import_react5.useState)("all");
2439
+ const [filter, setFilter] = (0, import_react7.useState)("all");
1929
2440
  const {
1930
2441
  copy,
1931
2442
  isPopoverOpen,
@@ -1943,7 +2454,7 @@ function IssueReportPopover({ children }) {
1943
2454
  const history = useIssueReportingHistory("all");
1944
2455
  const status = useIssueReportingStatus();
1945
2456
  const allItems = history.data?.items ?? [];
1946
- const counts = (0, import_react5.useMemo)(
2457
+ const counts = (0, import_react7.useMemo)(
1947
2458
  () => ({
1948
2459
  all: allItems.length,
1949
2460
  unresolved: filterIssueReports(allItems, "unresolved").length,
@@ -1955,37 +2466,37 @@ function IssueReportPopover({ children }) {
1955
2466
  const showSectionFirst = createMode === "surface_required" || createMode === "surface_preferred" && hasRegisteredTargets;
1956
2467
  const sectionActionDisabled = !hasRegisteredTargets;
1957
2468
  const helperText = createMode === "surface_required" ? hasRegisteredTargets ? copy.surfaceRequiredDescription : copy.specificSectionUnavailableDescription : createMode === "surface_preferred" && !hasRegisteredTargets ? copy.specificSectionUnavailableDescription : null;
1958
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2469
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1959
2470
  Popover.Root,
1960
2471
  {
1961
2472
  open: isPopoverOpen,
1962
2473
  onOpenChange: (open) => open ? openPopover() : closePopover(),
1963
2474
  children: [
1964
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Popover.Trigger, { asChild: true, children }),
1965
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Popover.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2475
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Popover.Trigger, { asChild: true, children }),
2476
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Popover.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1966
2477
  Popover.Content,
1967
2478
  {
1968
2479
  align: "end",
1969
2480
  side: "top",
1970
2481
  sideOffset: 12,
1971
- className: cn(
2482
+ className: cn2(
1972
2483
  "rounded-3xl border border-slate-200 bg-white p-4",
1973
2484
  Z_POPOVER,
1974
2485
  POPOVER_WIDTH,
1975
2486
  POPOVER_SHADOW
1976
2487
  ),
1977
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "space-y-4", children: [
1978
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between gap-3", children: [
1979
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
1980
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: "text-sm font-semibold text-slate-900", children: copy.popoverTitle }),
1981
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "mt-1 text-xs text-slate-500", children: statusSummary })
2488
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-4", children: [
2489
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between gap-3", children: [
2490
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
2491
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { className: "text-sm font-semibold text-slate-900", children: copy.popoverTitle }),
2492
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mt-1 text-xs text-slate-500", children: statusSummary })
1982
2493
  ] }),
1983
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex flex-wrap justify-end gap-2", children: showSectionFirst ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
1984
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2494
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex flex-wrap justify-end gap-2", children: showSectionFirst ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
2495
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1985
2496
  "button",
1986
2497
  {
1987
2498
  type: "button",
1988
- className: cn(
2499
+ className: cn2(
1989
2500
  "rounded-full px-3 py-2 text-xs font-semibold transition",
1990
2501
  sectionActionDisabled ? "cursor-not-allowed bg-slate-200 text-slate-500" : "bg-slate-900 text-white hover:bg-slate-800"
1991
2502
  ),
@@ -2000,7 +2511,7 @@ function IssueReportPopover({ children }) {
2000
2511
  children: createMode === "surface_required" ? copy.reportNewAction : copy.reportSpecificAction
2001
2512
  }
2002
2513
  ),
2003
- createMode !== "surface_required" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2514
+ createMode !== "surface_required" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2004
2515
  "button",
2005
2516
  {
2006
2517
  type: "button",
@@ -2012,8 +2523,8 @@ function IssueReportPopover({ children }) {
2012
2523
  children: copy.reportPageAction
2013
2524
  }
2014
2525
  ) : null
2015
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
2016
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2526
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
2527
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2017
2528
  "button",
2018
2529
  {
2019
2530
  type: "button",
@@ -2025,7 +2536,7 @@ function IssueReportPopover({ children }) {
2025
2536
  children: createMode === "general_page" ? copy.reportPageAction : copy.reportNewAction
2026
2537
  }
2027
2538
  ),
2028
- hasRegisteredTargets ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2539
+ hasRegisteredTargets ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2029
2540
  "button",
2030
2541
  {
2031
2542
  type: "button",
@@ -2039,10 +2550,10 @@ function IssueReportPopover({ children }) {
2039
2550
  ) : null
2040
2551
  ] }) })
2041
2552
  ] }),
2042
- helperText ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "rounded-2xl border border-amber-200 bg-amber-50 px-4 py-3 text-xs text-amber-900", children: helperText }) : null,
2043
- status.error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "space-y-3 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-4 text-sm text-rose-700", children: [
2044
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: status.error.message || copy.statusLoadFailed }),
2045
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2553
+ helperText ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "rounded-2xl border border-amber-200 bg-amber-50 px-4 py-3 text-xs text-amber-900", children: helperText }) : null,
2554
+ status.error ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-3 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-4 text-sm text-rose-700", children: [
2555
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { children: status.error.message || copy.statusLoadFailed }),
2556
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2046
2557
  "button",
2047
2558
  {
2048
2559
  type: "button",
@@ -2052,16 +2563,16 @@ function IssueReportPopover({ children }) {
2052
2563
  }
2053
2564
  )
2054
2565
  ] }) : null,
2055
- allowTenantScope ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "space-y-2", children: [
2056
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: cn("font-medium uppercase tracking-wide text-slate-500", LABEL_TEXT), children: copy.scopeLabel }),
2057
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex gap-2", children: [
2566
+ allowTenantScope ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-2", children: [
2567
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: cn2("font-medium uppercase tracking-wide text-slate-500", LABEL_TEXT), children: copy.scopeLabel }),
2568
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex gap-2", children: [
2058
2569
  ["tenant", copy.scopeTenant],
2059
2570
  ["mine", copy.scopeMine]
2060
- ].map(([value, label]) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2571
+ ].map(([value, label]) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2061
2572
  "button",
2062
2573
  {
2063
2574
  type: "button",
2064
- className: cn(
2575
+ className: cn2(
2065
2576
  "rounded-full px-3 py-1.5 text-xs font-medium transition",
2066
2577
  scope === value ? "bg-slate-900 text-white" : "bg-slate-100 text-slate-600 hover:bg-slate-200"
2067
2578
  ),
@@ -2071,15 +2582,15 @@ function IssueReportPopover({ children }) {
2071
2582
  value
2072
2583
  )) })
2073
2584
  ] }) : null,
2074
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex gap-2", children: [
2585
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex gap-2", children: [
2075
2586
  ["all", copy.filtersAll, counts.all],
2076
2587
  ["unresolved", copy.filtersUnresolved, counts.unresolved],
2077
2588
  ["resolved", copy.filtersResolved, counts.resolved]
2078
- ].map(([value, label, count]) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2589
+ ].map(([value, label, count]) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2079
2590
  "button",
2080
2591
  {
2081
2592
  type: "button",
2082
- className: cn(
2593
+ className: cn2(
2083
2594
  "rounded-full px-3 py-1.5 text-xs font-medium transition",
2084
2595
  filter === value ? "bg-slate-900 text-white" : "bg-slate-100 text-slate-600 hover:bg-slate-200"
2085
2596
  ),
@@ -2091,7 +2602,7 @@ function IssueReportPopover({ children }) {
2091
2602
  },
2092
2603
  value
2093
2604
  )) }),
2094
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2605
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2095
2606
  IssueList,
2096
2607
  {
2097
2608
  filter,
@@ -2099,7 +2610,7 @@ function IssueReportPopover({ children }) {
2099
2610
  onReply: (issueReportId) => openExistingIssueModal(issueReportId, "reply")
2100
2611
  }
2101
2612
  ),
2102
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "border-t border-slate-100 pt-3 text-xs leading-relaxed text-slate-500", children: copy.historyHelpText })
2613
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "border-t border-slate-100 pt-3 text-xs leading-relaxed text-slate-500", children: copy.historyHelpText })
2103
2614
  ] })
2104
2615
  }
2105
2616
  ) })
@@ -2117,42 +2628,36 @@ function IssueReportModal() {
2117
2628
  retryModalHydration,
2118
2629
  inputModes,
2119
2630
  defaultInputMode,
2120
- voice
2631
+ voice,
2632
+ screenshotCapture,
2633
+ createMode
2121
2634
  } = useIssueReporting();
2122
2635
  const { createMutation, updateMutation, replyMutation } = useIssueReportingMutations();
2123
- const [note, setNote] = (0, import_react5.useState)("");
2124
- const [submitError, setSubmitError] = (0, import_react5.useState)(null);
2636
+ const [note, setNote] = (0, import_react7.useState)("");
2637
+ const [submitError, setSubmitError] = (0, import_react7.useState)(null);
2638
+ const [inputMode, setInputMode] = (0, import_react7.useState)(defaultInputMode);
2639
+ const [committedTranscript, setCommittedTranscript] = (0, import_react7.useState)("");
2640
+ const [voiceSubmitMetadata, setVoiceSubmitMetadata] = (0, import_react7.useState)(null);
2641
+ const [voiceStartRequestId, setVoiceStartRequestId] = (0, import_react7.useState)(0);
2642
+ const [voiceAppendRequestId, setVoiceAppendRequestId] = (0, import_react7.useState)(0);
2125
2643
  const { isOpen, mode, issue, target, isHydrating, error } = modalState;
2126
2644
  const canUseVoice = mode === "create" && inputModes.includes("voice");
2127
2645
  const canUseText = mode !== "create" || inputModes.includes("text");
2128
2646
  const canUseAttachments = Boolean(client.issueReporting.uploadAttachment);
2129
- const existingAttachments = (0, import_react5.useMemo)(
2647
+ const existingAttachments = (0, import_react7.useMemo)(
2130
2648
  () => mode === "edit" && issue ? issue.attachments ?? [] : [],
2131
2649
  [issue, mode]
2132
2650
  );
2133
2651
  const attachmentState = useAttachmentState(existingAttachments);
2134
2652
  const isSubmitting = createMutation.isPending || updateMutation.isPending || replyMutation.isPending || attachmentState.uploadProgress.phase === "uploading";
2135
- const {
2136
- inputMode,
2137
- setInputMode,
2138
- voiceTokenResult,
2139
- voiceSubmitMetadata,
2140
- committedTranscript,
2141
- voiceError,
2142
- scribeError,
2143
- isVoiceConnecting,
2144
- isVoiceActive,
2145
- resetVoiceCapture,
2146
- startVoiceInput,
2147
- stopVoiceInput,
2148
- appendTranscript
2149
- } = useIssueReportVoiceCapture({
2150
- canUseVoice,
2151
- defaultInputMode,
2152
- isSubmitting,
2153
- voice
2154
- });
2155
- (0, import_react5.useEffect)(() => {
2653
+ const resetVoiceCapture = (0, import_react7.useCallback)(() => {
2654
+ setInputMode(defaultInputMode);
2655
+ setCommittedTranscript("");
2656
+ setVoiceSubmitMetadata(null);
2657
+ setVoiceStartRequestId(0);
2658
+ setVoiceAppendRequestId(0);
2659
+ }, [defaultInputMode]);
2660
+ useIsomorphicLayoutEffect(() => {
2156
2661
  if (!isOpen) {
2157
2662
  resetVoiceCapture();
2158
2663
  attachmentState.reset();
@@ -2178,14 +2683,17 @@ function IssueReportModal() {
2178
2683
  attachmentState.reset();
2179
2684
  closeModal();
2180
2685
  };
2181
- const handleStartVoiceInput = async () => {
2182
- await startVoiceInput(client.issueReporting.createVoiceToken);
2686
+ const handleRequestAppendTranscript = () => {
2687
+ setInputMode("voice");
2688
+ setVoiceAppendRequestId((current) => current + 1);
2183
2689
  };
2184
- const handleStopVoiceInput = () => {
2185
- stopVoiceInput();
2690
+ const handleRequestStartVoiceInput = () => {
2691
+ setInputMode("voice");
2692
+ setVoiceStartRequestId((current) => current + 1);
2186
2693
  };
2187
- const handleAppendTranscript = () => {
2188
- appendTranscript(setNote);
2694
+ const handleAppendTranscript = (transcript) => {
2695
+ setNote((current) => appendTranscriptToNote(current, transcript));
2696
+ setInputMode("text");
2189
2697
  };
2190
2698
  const uploadPendingFiles = async () => {
2191
2699
  const upload = client.issueReporting.uploadAttachment;
@@ -2227,8 +2735,41 @@ function IssueReportModal() {
2227
2735
  try {
2228
2736
  const noteForSubmit = normalizedNote;
2229
2737
  let newAttachmentIds = [];
2738
+ let screenshotCaptureMetadata = null;
2739
+ if (screenshotCapture?.enabled && mode === "create" && canUseAttachments && client.issueReporting.uploadAttachment) {
2740
+ try {
2741
+ const { captureScreenshot: captureScreenshot2 } = await Promise.resolve().then(() => (init_screenshot_capture(), screenshot_capture_exports));
2742
+ const captureResult = await captureScreenshot2(screenshotCapture, {
2743
+ pageUrl: target.page_url,
2744
+ createMode,
2745
+ inputMode: effectiveInputMode,
2746
+ target: {
2747
+ componentKey: target.component_key,
2748
+ componentLabel: target.component_label,
2749
+ surfaceRef: target.surface_ref,
2750
+ metadata: target.metadata
2751
+ }
2752
+ });
2753
+ const uploaded = await client.issueReporting.uploadAttachment(
2754
+ captureResult.blob,
2755
+ { filename: captureResult.filename }
2756
+ );
2757
+ newAttachmentIds.push(uploaded.id);
2758
+ screenshotCaptureMetadata = buildScreenshotCaptureMetadata(
2759
+ screenshotCapture,
2760
+ "attached"
2761
+ );
2762
+ } catch (captureError) {
2763
+ screenshotCaptureMetadata = buildScreenshotCaptureMetadata(
2764
+ screenshotCapture,
2765
+ "failed"
2766
+ );
2767
+ screenshotCapture.onCaptureError?.(captureError);
2768
+ }
2769
+ }
2230
2770
  if (canUseAttachments && attachmentState.pendingFiles.length > 0) {
2231
- newAttachmentIds = await uploadPendingFiles();
2771
+ const manualIds = await uploadPendingFiles();
2772
+ newAttachmentIds.push(...manualIds);
2232
2773
  }
2233
2774
  const effectiveVoiceMetadata = effectiveInputMode === "voice" ? voiceSubmitMetadata ?? {
2234
2775
  provider: voice.provider,
@@ -2239,20 +2780,26 @@ function IssueReportModal() {
2239
2780
  attach_transcript: true
2240
2781
  } : null;
2241
2782
  if (mode === "create") {
2783
+ const targetMetadata = {
2784
+ ...target.metadata ?? {},
2785
+ ...screenshotCaptureMetadata ? {
2786
+ [SCREENSHOT_CAPTURE_METADATA_KEY]: screenshotCaptureMetadata
2787
+ } : {},
2788
+ ...effectiveVoiceMetadata && effectiveInputMode === "voice" ? {
2789
+ capture: {
2790
+ input_mode: "voice",
2791
+ provider: effectiveVoiceMetadata.provider,
2792
+ model_id: effectiveVoiceMetadata.model_id,
2793
+ retain_audio: effectiveVoiceMetadata.retain_audio,
2794
+ attach_transcript: effectiveVoiceMetadata.attach_transcript
2795
+ }
2796
+ } : {}
2797
+ };
2242
2798
  await createMutation.mutateAsync({
2243
- target: effectiveVoiceMetadata && effectiveInputMode === "voice" ? {
2799
+ target: {
2244
2800
  ...target,
2245
- metadata: {
2246
- ...target.metadata ?? {},
2247
- capture: {
2248
- input_mode: "voice",
2249
- provider: effectiveVoiceMetadata.provider,
2250
- model_id: effectiveVoiceMetadata.model_id,
2251
- retain_audio: effectiveVoiceMetadata.retain_audio,
2252
- attach_transcript: effectiveVoiceMetadata.attach_transcript
2253
- }
2254
- }
2255
- } : target,
2801
+ metadata: targetMetadata
2802
+ },
2256
2803
  note: noteForSubmit,
2257
2804
  reporter_role_hint: reporterRoleHint,
2258
2805
  attachment_ids: newAttachmentIds.length > 0 ? newAttachmentIds : void 0
@@ -2277,7 +2824,7 @@ function IssueReportModal() {
2277
2824
  attachmentState.reset();
2278
2825
  closeModal();
2279
2826
  } catch (submissionError) {
2280
- const message = resolveErrorMessage(
2827
+ const message = resolveErrorMessage2(
2281
2828
  submissionError,
2282
2829
  "Failed to submit issue report"
2283
2830
  );
@@ -2287,12 +2834,12 @@ function IssueReportModal() {
2287
2834
  setSubmitError(message);
2288
2835
  }
2289
2836
  };
2290
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dialog.Root, { open: isOpen, onOpenChange: (open) => !open && handleCloseModal(), children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Dialog.Portal, { children: [
2291
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dialog.Overlay, { className: cn("fixed inset-0 bg-slate-950/45 backdrop-blur-sm", Z_MODAL_OVERLAY) }),
2292
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2837
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Dialog.Root, { open: isOpen, onOpenChange: (open) => !open && handleCloseModal(), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Dialog.Portal, { children: [
2838
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Dialog.Overlay, { className: cn2("fixed inset-0 bg-slate-950/45 backdrop-blur-sm", Z_MODAL_OVERLAY) }),
2839
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2293
2840
  Dialog.Content,
2294
2841
  {
2295
- className: cn(
2842
+ className: cn2(
2296
2843
  "fixed left-1/2 top-1/2 max-h-[90vh] max-w-xl -translate-x-1/2 -translate-y-1/2 overflow-y-auto border border-slate-200 bg-white p-6 focus:outline-none",
2297
2844
  Z_MODAL_CONTENT,
2298
2845
  MODAL_WIDTH,
@@ -2300,8 +2847,8 @@ function IssueReportModal() {
2300
2847
  MODAL_SHADOW
2301
2848
  ),
2302
2849
  children: [
2303
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dialog.Title, { className: "text-lg font-semibold text-slate-950", children: title }),
2304
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dialog.Description, { className: "mt-2 text-sm text-slate-600", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2850
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Dialog.Title, { className: "text-lg font-semibold text-slate-950", children: title }),
2851
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Dialog.Description, { className: "mt-2 text-sm text-slate-600", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2305
2852
  IssueReportModalDescription,
2306
2853
  {
2307
2854
  copy,
@@ -2309,7 +2856,7 @@ function IssueReportModal() {
2309
2856
  target
2310
2857
  }
2311
2858
  ) }),
2312
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2859
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2313
2860
  IssueReportModalBody,
2314
2861
  {
2315
2862
  copy,
@@ -2325,13 +2872,10 @@ function IssueReportModal() {
2325
2872
  normalizedNote,
2326
2873
  isValid,
2327
2874
  isSubmitting,
2328
- isVoiceActive,
2329
- isVoiceConnecting,
2330
2875
  voice,
2331
- voiceTokenResult,
2332
- committedTranscript,
2333
- voiceError,
2334
- scribeError,
2876
+ createVoiceToken: client.issueReporting.createVoiceToken,
2877
+ voiceStartRequestId,
2878
+ voiceAppendRequestId,
2335
2879
  submitError,
2336
2880
  existingAttachments,
2337
2881
  removedExistingIds: attachmentState.removedExistingIds,
@@ -2342,8 +2886,10 @@ function IssueReportModal() {
2342
2886
  onClose: handleCloseModal,
2343
2887
  onSelectText: () => setInputMode("text"),
2344
2888
  onSelectVoice: () => setInputMode("voice"),
2345
- onStartVoiceInput: () => void handleStartVoiceInput(),
2346
- onStopVoiceInput: handleStopVoiceInput,
2889
+ onRequestStartVoiceInput: handleRequestStartVoiceInput,
2890
+ onRequestAppendTranscript: handleRequestAppendTranscript,
2891
+ onTranscriptCommitted: setCommittedTranscript,
2892
+ onVoiceSubmitMetadataChange: setVoiceSubmitMetadata,
2347
2893
  onAppendTranscript: handleAppendTranscript,
2348
2894
  onNoteChange: setNote,
2349
2895
  onSubmit: () => void handleSubmit(),
@@ -2352,13 +2898,13 @@ function IssueReportModal() {
2352
2898
  onRemovePendingAttachment: attachmentState.removePending
2353
2899
  }
2354
2900
  ),
2355
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dialog.Close, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2901
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Dialog.Close, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2356
2902
  "button",
2357
2903
  {
2358
2904
  type: "button",
2359
2905
  className: "absolute right-4 top-4 inline-flex h-9 w-9 items-center justify-center rounded-full border border-slate-200 text-slate-500 transition hover:bg-slate-50 hover:text-slate-900",
2360
2906
  "aria-label": "Close issue report modal",
2361
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.X, { className: "h-4 w-4" })
2907
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.X, { className: "h-4 w-4" })
2362
2908
  }
2363
2909
  ) })
2364
2910
  ]
@@ -2385,28 +2931,28 @@ function FloatingIssueReportButton({
2385
2931
  if (!isEligible) {
2386
2932
  return null;
2387
2933
  }
2388
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
2389
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IssueReportModeBanner, {}),
2390
- !isReportMode ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: cn("fixed bottom-12 right-4", Z_FLOATING_BUTTON, positionClassName), children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IssueReportPopover, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2934
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
2935
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(IssueReportModeBanner, {}),
2936
+ !isReportMode ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: cn2("fixed bottom-12 right-4", Z_FLOATING_BUTTON, positionClassName), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(IssueReportPopover, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2391
2937
  "button",
2392
2938
  {
2393
2939
  type: "button",
2394
2940
  "aria-label": needsResponse ? `${copy.entryAriaLabel} (${copy.threadNeedsResponseBadge})` : copy.entryAriaLabel,
2395
2941
  onClick: () => isPopoverOpen ? closePopover() : openPopover(),
2396
- className: cn(
2942
+ className: cn2(
2397
2943
  "relative flex h-12 w-12 items-center justify-center rounded-full border border-slate-200 bg-white shadow-lg transition hover:-translate-y-0.5 hover:shadow-xl focus:outline-none focus:ring-2 focus:ring-slate-300",
2398
2944
  status.isPending && "animate-pulse",
2399
2945
  className
2400
2946
  ),
2401
2947
  children: [
2402
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2403
- import_react4.BugBeetle,
2948
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2949
+ import_react6.BugBeetle,
2404
2950
  {
2405
- className: cn("h-6 w-6", getEntryPointClassName(entryPointState)),
2951
+ className: cn2("h-6 w-6", getEntryPointClassName(entryPointState)),
2406
2952
  weight: "fill"
2407
2953
  }
2408
2954
  ),
2409
- needsResponse ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2955
+ needsResponse ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2410
2956
  "span",
2411
2957
  {
2412
2958
  "data-testid": "issue-report-needs-response-badge",
@@ -2416,7 +2962,7 @@ function FloatingIssueReportButton({
2416
2962
  ]
2417
2963
  }
2418
2964
  ) }) }) : null,
2419
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IssueReportModal, {})
2965
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(IssueReportModal, {})
2420
2966
  ] });
2421
2967
  }
2422
2968
  function ReportableSection({
@@ -2427,9 +2973,9 @@ function ReportableSection({
2427
2973
  }) {
2428
2974
  const reportMode = useReportMode();
2429
2975
  const isSelectable = Boolean(reportMode?.isReportMode);
2430
- const targetId = import_react5.default.useRef(/* @__PURE__ */ Symbol("reportable-section"));
2431
- const elementRef = import_react5.default.useRef(null);
2432
- (0, import_react5.useEffect)(() => {
2976
+ const targetId = import_react7.default.useRef(/* @__PURE__ */ Symbol("reportable-section"));
2977
+ const elementRef = import_react7.default.useRef(null);
2978
+ (0, import_react7.useEffect)(() => {
2433
2979
  if (!reportMode) {
2434
2980
  return;
2435
2981
  }
@@ -2438,13 +2984,13 @@ function ReportableSection({
2438
2984
  reportMode.unregisterTarget(targetId.current);
2439
2985
  };
2440
2986
  }, [reportMode, reportableName]);
2441
- return import_react5.default.createElement(
2987
+ return import_react7.default.createElement(
2442
2988
  Component,
2443
2989
  {
2444
2990
  ref: (node) => {
2445
2991
  elementRef.current = node;
2446
2992
  },
2447
- className: cn(
2993
+ className: cn2(
2448
2994
  className,
2449
2995
  isSelectable && "cursor-pointer ring-2 ring-amber-400 transition hover:ring-amber-500"
2450
2996
  ),