spaps-issue-reporting-react 0.6.1 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,13 +4,25 @@
4
4
 
5
5
  - No changes yet.
6
6
 
7
+ ## [0.6.2] - 2026-06-20
8
+
9
+ - Maintenance: align release metadata with the package currently published on
10
+ npm. The registry now serves `spaps-issue-reporting-react@0.6.2`, so
11
+ downstream consumers should install from the registry instead of the earlier
12
+ local proof tarballs.
13
+
14
+ ## [0.6.1] - 2026-06-18
15
+
16
+ - Maintenance: align package README metadata and changelog top version with the
17
+ package version.
18
+
7
19
  ## [0.6.0] - 2026-06-07
8
20
 
9
21
  - Lazy-load voice input (`@elevenlabs/react`) behind a dynamic `import()` so consumers no longer eager-load `livekit-client` (~492KB raw / ~140KB gzip) on first paint. The Scribe voice-capture UI/logic now lives in a code-split `VoiceCapture` chunk that is only fetched when the reporter switches to voice input (`inputMode === "voice"` and voice is permitted); text-only reporting never touches `@elevenlabs`/livekit. Public API (`IssueReportingProvider`, `FloatingIssueReportButton`, `IssueReportingPageConfig`, `ReportableSection`, `useIssueReporting`) and behavior are unchanged.
10
22
  - Added opt-in automatic same-origin DOM screenshot capture through `IssueReportingProvider.screenshotCapture`. Capture lazy-loads `html2canvas` only during create submit when enabled, uploads through the host client's `issueReporting.uploadAttachment(file, { filename })`, records coarse `target.metadata.screenshot_capture` status, and continues issue submission without a screenshot if capture/render/upload fails.
11
23
  - Added the public `spaps-issue-reporting-react/screenshot-capture` subpath so app-local adapters such as custom ref-area tools can reuse the shared capture, masking, compression, and filename logic without copying generic `html2canvas` code.
12
24
  - Added optional `html2canvas >=1.4.0` peer metadata and documented the privacy controls: `data-spaps-screenshot-exclude`, `data-spaps-private`, host `excludeSelectors`, host `maskSelectors`, visible-viewport capture, private SPAPS attachment storage, 10 MiB file limit, and five-attachment cap.
13
- - Release handoff: `spaps-issue-reporting-react@0.6.0` is still operator-gated because npm currently serves `0.5.0`. Local consumer proofs used `/tmp/spaps-issue-reporting-react-0.6.0-normalized.tgz`, with `spaps-types@1.5.0` and `spaps-sdk@1.11.0` already available on npm. After operator approval, publish `spaps-issue-reporting-react@0.6.0`, reinstall downstream consumers from the registry, and re-run their focused screenshot checks before deployment.
25
+ - Initial release handoff used `/tmp/spaps-issue-reporting-react-0.6.0-normalized.tgz`, with `spaps-types@1.5.0` and `spaps-sdk@1.11.0` already available on npm. The registry publication step has since been superseded by the `0.6.2` release.
14
26
 
15
27
  ## [0.4.2] - 2026-06-04
16
28
 
package/README.md CHANGED
@@ -400,7 +400,7 @@ and revisit trigger.
400
400
  ## Metadata
401
401
 
402
402
  - `package_name`: `spaps-issue-reporting-react`
403
- - `latest_version`: `0.6.0`
403
+ - `latest_version`: `0.6.2`
404
404
  - `minimum_runtime`: `Node.js >=18.0.0`
405
405
  - `api_base_url`: `https://api.sweetpotato.dev`
406
406
 
@@ -141,6 +141,7 @@ function VoiceCapture({
141
141
  const [voiceSubmitMetadata, setVoiceSubmitMetadata] = useState(null);
142
142
  const [voiceError, setVoiceError] = useState(null);
143
143
  const startInFlightRef = useRef(false);
144
+ const mountedRef = useRef(true);
144
145
  const lastStartRequestId = useRef(0);
145
146
  const lastAppendRequestId = useRef(0);
146
147
  const {
@@ -168,6 +169,12 @@ function VoiceCapture({
168
169
  useEffect(() => {
169
170
  onSubmitMetadataChange(voiceSubmitMetadata);
170
171
  }, [onSubmitMetadataChange, voiceSubmitMetadata]);
172
+ useEffect(() => {
173
+ mountedRef.current = true;
174
+ return () => {
175
+ mountedRef.current = false;
176
+ };
177
+ }, []);
171
178
  useEffect(() => {
172
179
  return () => {
173
180
  disconnectScribe();
@@ -185,6 +192,9 @@ function VoiceCapture({
185
192
  setVoiceError(null);
186
193
  try {
187
194
  const tokenResult = await createVoiceToken();
195
+ if (!mountedRef.current) {
196
+ return;
197
+ }
188
198
  setVoiceTokenResult(tokenResult);
189
199
  setVoiceSubmitMetadata(tokenResult);
190
200
  await connectScribe({
@@ -192,13 +202,21 @@ function VoiceCapture({
192
202
  modelId: tokenResult.model_id,
193
203
  microphone: voice.microphone
194
204
  });
205
+ if (!mountedRef.current) {
206
+ disconnectScribe();
207
+ return;
208
+ }
195
209
  onSelectVoice();
196
210
  } catch (startError) {
211
+ if (!mountedRef.current) {
212
+ return;
213
+ }
197
214
  setVoiceError(resolveErrorMessage(startError, "Failed to start voice input."));
198
215
  } finally {
199
216
  startInFlightRef.current = false;
200
217
  }
201
218
  }, [
219
+ disconnectScribe,
202
220
  connectScribe,
203
221
  createVoiceToken,
204
222
  isSubmitting,
package/dist/index.js CHANGED
@@ -174,6 +174,7 @@ function VoiceCapture({
174
174
  const [voiceSubmitMetadata, setVoiceSubmitMetadata] = (0, import_react5.useState)(null);
175
175
  const [voiceError, setVoiceError] = (0, import_react5.useState)(null);
176
176
  const startInFlightRef = (0, import_react5.useRef)(false);
177
+ const mountedRef = (0, import_react5.useRef)(true);
177
178
  const lastStartRequestId = (0, import_react5.useRef)(0);
178
179
  const lastAppendRequestId = (0, import_react5.useRef)(0);
179
180
  const {
@@ -201,6 +202,12 @@ function VoiceCapture({
201
202
  (0, import_react5.useEffect)(() => {
202
203
  onSubmitMetadataChange(voiceSubmitMetadata);
203
204
  }, [onSubmitMetadataChange, voiceSubmitMetadata]);
205
+ (0, import_react5.useEffect)(() => {
206
+ mountedRef.current = true;
207
+ return () => {
208
+ mountedRef.current = false;
209
+ };
210
+ }, []);
204
211
  (0, import_react5.useEffect)(() => {
205
212
  return () => {
206
213
  disconnectScribe();
@@ -218,6 +225,9 @@ function VoiceCapture({
218
225
  setVoiceError(null);
219
226
  try {
220
227
  const tokenResult = await createVoiceToken();
228
+ if (!mountedRef.current) {
229
+ return;
230
+ }
221
231
  setVoiceTokenResult(tokenResult);
222
232
  setVoiceSubmitMetadata(tokenResult);
223
233
  await connectScribe({
@@ -225,13 +235,21 @@ function VoiceCapture({
225
235
  modelId: tokenResult.model_id,
226
236
  microphone: voice.microphone
227
237
  });
238
+ if (!mountedRef.current) {
239
+ disconnectScribe();
240
+ return;
241
+ }
228
242
  onSelectVoice();
229
243
  } catch (startError) {
244
+ if (!mountedRef.current) {
245
+ return;
246
+ }
230
247
  setVoiceError(resolveErrorMessage(startError, "Failed to start voice input."));
231
248
  } finally {
232
249
  startInFlightRef.current = false;
233
250
  }
234
251
  }, [
252
+ disconnectScribe,
235
253
  connectScribe,
236
254
  createVoiceToken,
237
255
  isSubmitting,
@@ -360,7 +378,7 @@ async function canvasToSizedBlob(canvas, imageType, quality, maxBytes) {
360
378
  let currentCanvas = canvas;
361
379
  let scaled = false;
362
380
  for (const scale of [0.85, 0.7, 0.55, 0.4]) {
363
- currentCanvas = scaleCanvas(currentCanvas, scale);
381
+ currentCanvas = scaleCanvas(canvas, scale);
364
382
  scaled = true;
365
383
  for (const format of fallbackTypes) {
366
384
  for (const candidateQuality of [0.86, 0.72, 0.58, 0.44, 0.32]) {
@@ -447,9 +465,14 @@ async function captureScreenshot(config, context) {
447
465
  function resetHtml2CanvasCache() {
448
466
  html2canvasPromise = null;
449
467
  }
450
- function maskScreenshotCaptureDocument(clonedDocument, excludeSelectors = DEFAULT_EXCLUDE_SELECTORS, maskSelectors = DEFAULT_MASK_SELECTORS) {
468
+ function maskScreenshotCaptureDocument(clonedDocument, excludeSelectors = DEFAULT_EXCLUDE_SELECTORS, maskSelectors = []) {
451
469
  clonedDocument.querySelectorAll([...excludeSelectors, "script"].join(",")).forEach((element) => element.remove());
452
- clonedDocument.querySelectorAll(maskSelectors.join(",")).forEach(maskCaptureElement);
470
+ for (const selector of [...DEFAULT_MASK_SELECTORS, ...maskSelectors]) {
471
+ try {
472
+ clonedDocument.querySelectorAll(selector).forEach(maskCaptureElement);
473
+ } catch {
474
+ }
475
+ }
453
476
  redactSensitiveTextNodes(clonedDocument.body ?? clonedDocument.documentElement);
454
477
  }
455
478
  function canUseBrowserCapture() {
@@ -564,7 +587,11 @@ var init_screenshot_capture = __esm({
564
587
  "option",
565
588
  "select",
566
589
  "textarea",
567
- '[contenteditable="true"]'
590
+ // Match every editable region, not just the explicit `contenteditable="true"`
591
+ // form. Bare (`contenteditable`), empty-string (`contenteditable=""`), and
592
+ // `contenteditable="plaintext-only"` editors are all editable and must be
593
+ // masked; only the explicitly disabled `contenteditable="false"` is excluded.
594
+ '[contenteditable]:not([contenteditable="false"])'
568
595
  ];
569
596
  SCREENSHOT_CAPTURE_REDACTION_TEXT = "[redacted]";
570
597
  ATTACHMENT_MAX_BYTES2 = 10 * 1024 * 1024;
@@ -2026,6 +2053,15 @@ function useAttachmentState(existingAttachments) {
2026
2053
  );
2027
2054
  const [validationErrors, setValidationErrors] = (0, import_react7.useState)([]);
2028
2055
  const [uploadProgress, setUploadProgress] = (0, import_react7.useState)(INITIAL_UPLOAD_PROGRESS);
2056
+ const pendingFilesRef = (0, import_react7.useRef)(pendingFiles);
2057
+ pendingFilesRef.current = pendingFiles;
2058
+ (0, import_react7.useEffect)(() => {
2059
+ return () => {
2060
+ for (const pf of pendingFilesRef.current) {
2061
+ URL.revokeObjectURL(pf.previewUrl);
2062
+ }
2063
+ };
2064
+ }, []);
2029
2065
  const retainedExistingCount = existingAttachments.filter(
2030
2066
  (a) => !removedExistingIds.has(a.id)
2031
2067
  ).length;
@@ -2342,19 +2378,26 @@ function IssueReportModeBanner() {
2342
2378
  if (!reportMode?.isReportMode) {
2343
2379
  return null;
2344
2380
  }
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)(
2349
- "button",
2350
- {
2351
- type: "button",
2352
- className: "rounded-full border border-amber-400 px-3 py-1 font-medium text-amber-900 transition hover:bg-amber-100",
2353
- onClick: reportMode.cancelReportMode,
2354
- children: copy.reportModeCancelAction
2355
- }
2356
- )
2357
- ] }) }) });
2381
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2382
+ "div",
2383
+ {
2384
+ "data-spaps-screenshot-exclude": "",
2385
+ className: cn2("fixed inset-x-4 top-4 flex justify-center", Z_BANNER),
2386
+ 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: [
2387
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "font-medium", children: copy.reportModeTitle }),
2388
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "text-amber-900/80", children: copy.reportModeDescription }),
2389
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2390
+ "button",
2391
+ {
2392
+ type: "button",
2393
+ className: "rounded-full border border-amber-400 px-3 py-1 font-medium text-amber-900 transition hover:bg-amber-100",
2394
+ onClick: reportMode.cancelReportMode,
2395
+ children: copy.reportModeCancelAction
2396
+ }
2397
+ )
2398
+ ] }) })
2399
+ }
2400
+ );
2358
2401
  }
2359
2402
  function IssueList({
2360
2403
  filter,
@@ -2476,6 +2519,7 @@ function IssueReportPopover({ children }) {
2476
2519
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Popover.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2477
2520
  Popover.Content,
2478
2521
  {
2522
+ "data-spaps-screenshot-exclude": "",
2479
2523
  align: "end",
2480
2524
  side: "top",
2481
2525
  sideOffset: 12,
@@ -2635,6 +2679,9 @@ function IssueReportModal() {
2635
2679
  const { createMutation, updateMutation, replyMutation } = useIssueReportingMutations();
2636
2680
  const [note, setNote] = (0, import_react7.useState)("");
2637
2681
  const [submitError, setSubmitError] = (0, import_react7.useState)(null);
2682
+ const submitInFlightRef = (0, import_react7.useRef)(false);
2683
+ const screenshotCacheRef = (0, import_react7.useRef)(null);
2684
+ const [isSubmitInFlight, setIsSubmitInFlight] = (0, import_react7.useState)(false);
2638
2685
  const [inputMode, setInputMode] = (0, import_react7.useState)(defaultInputMode);
2639
2686
  const [committedTranscript, setCommittedTranscript] = (0, import_react7.useState)("");
2640
2687
  const [voiceSubmitMetadata, setVoiceSubmitMetadata] = (0, import_react7.useState)(null);
@@ -2649,7 +2696,7 @@ function IssueReportModal() {
2649
2696
  [issue, mode]
2650
2697
  );
2651
2698
  const attachmentState = useAttachmentState(existingAttachments);
2652
- const isSubmitting = createMutation.isPending || updateMutation.isPending || replyMutation.isPending || attachmentState.uploadProgress.phase === "uploading";
2699
+ const isSubmitting = isSubmitInFlight || createMutation.isPending || updateMutation.isPending || replyMutation.isPending || attachmentState.uploadProgress.phase === "uploading";
2653
2700
  const resetVoiceCapture = (0, import_react7.useCallback)(() => {
2654
2701
  setInputMode(defaultInputMode);
2655
2702
  setCommittedTranscript("");
@@ -2658,6 +2705,7 @@ function IssueReportModal() {
2658
2705
  setVoiceAppendRequestId(0);
2659
2706
  }, [defaultInputMode]);
2660
2707
  useIsomorphicLayoutEffect(() => {
2708
+ screenshotCacheRef.current = null;
2661
2709
  if (!isOpen) {
2662
2710
  resetVoiceCapture();
2663
2711
  attachmentState.reset();
@@ -2691,9 +2739,16 @@ function IssueReportModal() {
2691
2739
  setInputMode("voice");
2692
2740
  setVoiceStartRequestId((current) => current + 1);
2693
2741
  };
2742
+ const handleSelectText = () => {
2743
+ setInputMode("text");
2744
+ setVoiceStartRequestId(0);
2745
+ setVoiceAppendRequestId(0);
2746
+ };
2694
2747
  const handleAppendTranscript = (transcript) => {
2695
2748
  setNote((current) => appendTranscriptToNote(current, transcript));
2696
2749
  setInputMode("text");
2750
+ setVoiceStartRequestId(0);
2751
+ setVoiceAppendRequestId(0);
2697
2752
  };
2698
2753
  const uploadPendingFiles = async () => {
2699
2754
  const upload = client.issueReporting.uploadAttachment;
@@ -2731,40 +2786,54 @@ function IssueReportModal() {
2731
2786
  if (!target || !isValid || isSubmitting) {
2732
2787
  return;
2733
2788
  }
2789
+ if (submitInFlightRef.current) {
2790
+ return;
2791
+ }
2792
+ submitInFlightRef.current = true;
2793
+ setIsSubmitInFlight(true);
2734
2794
  setSubmitError(null);
2735
2795
  try {
2736
2796
  const noteForSubmit = normalizedNote;
2737
2797
  let newAttachmentIds = [];
2738
2798
  let screenshotCaptureMetadata = null;
2739
2799
  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);
2800
+ if (screenshotCacheRef.current) {
2801
+ newAttachmentIds.push(screenshotCacheRef.current.attachmentId);
2802
+ screenshotCaptureMetadata = screenshotCacheRef.current.metadata;
2803
+ } else {
2804
+ try {
2805
+ const { captureScreenshot: captureScreenshot2 } = await Promise.resolve().then(() => (init_screenshot_capture(), screenshot_capture_exports));
2806
+ const captureResult = await captureScreenshot2(screenshotCapture, {
2807
+ pageUrl: target.page_url,
2808
+ createMode,
2809
+ inputMode: effectiveInputMode,
2810
+ target: {
2811
+ componentKey: target.component_key,
2812
+ componentLabel: target.component_label,
2813
+ surfaceRef: target.surface_ref,
2814
+ metadata: target.metadata
2815
+ }
2816
+ });
2817
+ const uploaded = await client.issueReporting.uploadAttachment(
2818
+ captureResult.blob,
2819
+ { filename: captureResult.filename }
2820
+ );
2821
+ newAttachmentIds.push(uploaded.id);
2822
+ screenshotCaptureMetadata = buildScreenshotCaptureMetadata(
2823
+ screenshotCapture,
2824
+ "attached"
2825
+ );
2826
+ screenshotCacheRef.current = {
2827
+ attachmentId: uploaded.id,
2828
+ metadata: screenshotCaptureMetadata
2829
+ };
2830
+ } catch (captureError) {
2831
+ screenshotCaptureMetadata = buildScreenshotCaptureMetadata(
2832
+ screenshotCapture,
2833
+ "failed"
2834
+ );
2835
+ screenshotCapture.onCaptureError?.(captureError);
2836
+ }
2768
2837
  }
2769
2838
  }
2770
2839
  if (canUseAttachments && attachmentState.pendingFiles.length > 0) {
@@ -2832,13 +2901,23 @@ function IssueReportModal() {
2832
2901
  (current) => current.phase === "uploading" ? { ...current, phase: "error", error: message } : current
2833
2902
  );
2834
2903
  setSubmitError(message);
2904
+ } finally {
2905
+ submitInFlightRef.current = false;
2906
+ setIsSubmitInFlight(false);
2835
2907
  }
2836
2908
  };
2837
2909
  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) }),
2910
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2911
+ Dialog.Overlay,
2912
+ {
2913
+ "data-spaps-screenshot-exclude": "",
2914
+ className: cn2("fixed inset-0 bg-slate-950/45 backdrop-blur-sm", Z_MODAL_OVERLAY)
2915
+ }
2916
+ ),
2839
2917
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2840
2918
  Dialog.Content,
2841
2919
  {
2920
+ "data-spaps-screenshot-exclude": "",
2842
2921
  className: cn2(
2843
2922
  "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",
2844
2923
  Z_MODAL_CONTENT,
@@ -2884,7 +2963,7 @@ function IssueReportModal() {
2884
2963
  attachmentValidationErrors: attachmentState.validationErrors,
2885
2964
  onRetryHydration: () => void retryModalHydration(),
2886
2965
  onClose: handleCloseModal,
2887
- onSelectText: () => setInputMode("text"),
2966
+ onSelectText: handleSelectText,
2888
2967
  onSelectVoice: () => setInputMode("voice"),
2889
2968
  onRequestStartVoiceInput: handleRequestStartVoiceInput,
2890
2969
  onRequestAppendTranscript: handleRequestAppendTranscript,
@@ -2933,35 +3012,42 @@ function FloatingIssueReportButton({
2933
3012
  }
2934
3013
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
2935
3014
  /* @__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)(
2937
- "button",
3015
+ !isReportMode ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3016
+ "div",
2938
3017
  {
2939
- type: "button",
2940
- "aria-label": needsResponse ? `${copy.entryAriaLabel} (${copy.threadNeedsResponseBadge})` : copy.entryAriaLabel,
2941
- onClick: () => isPopoverOpen ? closePopover() : openPopover(),
2942
- className: cn2(
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",
2944
- status.isPending && "animate-pulse",
2945
- className
2946
- ),
2947
- children: [
2948
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2949
- import_react6.BugBeetle,
2950
- {
2951
- className: cn2("h-6 w-6", getEntryPointClassName(entryPointState)),
2952
- weight: "fill"
2953
- }
2954
- ),
2955
- needsResponse ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2956
- "span",
2957
- {
2958
- "data-testid": "issue-report-needs-response-badge",
2959
- className: "absolute -right-0.5 -top-0.5 h-3 w-3 rounded-full border-2 border-white bg-amber-500"
2960
- }
2961
- ) : null
2962
- ]
3018
+ "data-spaps-screenshot-exclude": "",
3019
+ className: cn2("fixed bottom-12 right-4", Z_FLOATING_BUTTON, positionClassName),
3020
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(IssueReportPopover, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3021
+ "button",
3022
+ {
3023
+ type: "button",
3024
+ "aria-label": needsResponse ? `${copy.entryAriaLabel} (${copy.threadNeedsResponseBadge})` : copy.entryAriaLabel,
3025
+ onClick: () => isPopoverOpen ? closePopover() : openPopover(),
3026
+ className: cn2(
3027
+ "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",
3028
+ status.isPending && "animate-pulse",
3029
+ className
3030
+ ),
3031
+ children: [
3032
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3033
+ import_react6.BugBeetle,
3034
+ {
3035
+ className: cn2("h-6 w-6", getEntryPointClassName(entryPointState)),
3036
+ weight: "fill"
3037
+ }
3038
+ ),
3039
+ needsResponse ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3040
+ "span",
3041
+ {
3042
+ "data-testid": "issue-report-needs-response-badge",
3043
+ className: "absolute -right-0.5 -top-0.5 h-3 w-3 rounded-full border-2 border-white bg-amber-500"
3044
+ }
3045
+ ) : null
3046
+ ]
3047
+ }
3048
+ ) })
2963
3049
  }
2964
- ) }) }) : null,
3050
+ ) : null,
2965
3051
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(IssueReportModal, {})
2966
3052
  ] });
2967
3053
  }
package/dist/index.mjs CHANGED
@@ -839,7 +839,7 @@ var POPOVER_SHADOW = "shadow-[0_18px_48px_rgba(15,23,42,0.18)]";
839
839
  var MODAL_SHADOW = "shadow-[0_28px_80px_rgba(15,23,42,0.24)]";
840
840
  var BADGE_TEXT = "text-[11px]";
841
841
  var LABEL_TEXT = "text-[11px]";
842
- var VoiceCapture = React2.lazy(() => import("./VoiceCapture-WX7J6BWQ.mjs"));
842
+ var VoiceCapture = React2.lazy(() => import("./VoiceCapture-HLUVONFL.mjs"));
843
843
  var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect2 : React2.useLayoutEffect;
844
844
  var SCREENSHOT_CAPTURE_METADATA_KEY = "screenshot_capture";
845
845
  function truncate(value, max = 80) {
@@ -1438,6 +1438,15 @@ function useAttachmentState(existingAttachments) {
1438
1438
  );
1439
1439
  const [validationErrors, setValidationErrors] = useState2([]);
1440
1440
  const [uploadProgress, setUploadProgress] = useState2(INITIAL_UPLOAD_PROGRESS);
1441
+ const pendingFilesRef = useRef2(pendingFiles);
1442
+ pendingFilesRef.current = pendingFiles;
1443
+ useEffect2(() => {
1444
+ return () => {
1445
+ for (const pf of pendingFilesRef.current) {
1446
+ URL.revokeObjectURL(pf.previewUrl);
1447
+ }
1448
+ };
1449
+ }, []);
1441
1450
  const retainedExistingCount = existingAttachments.filter(
1442
1451
  (a) => !removedExistingIds.has(a.id)
1443
1452
  ).length;
@@ -1754,19 +1763,26 @@ function IssueReportModeBanner() {
1754
1763
  if (!reportMode?.isReportMode) {
1755
1764
  return null;
1756
1765
  }
1757
- return /* @__PURE__ */ jsx2("div", { className: cn("fixed inset-x-4 top-4 flex justify-center", Z_BANNER), children: /* @__PURE__ */ jsx2("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__ */ jsxs("div", { className: "flex flex-wrap items-center justify-center gap-3", children: [
1758
- /* @__PURE__ */ jsx2("div", { className: "font-medium", children: copy.reportModeTitle }),
1759
- /* @__PURE__ */ jsx2("div", { className: "text-amber-900/80", children: copy.reportModeDescription }),
1760
- /* @__PURE__ */ jsx2(
1761
- "button",
1762
- {
1763
- type: "button",
1764
- className: "rounded-full border border-amber-400 px-3 py-1 font-medium text-amber-900 transition hover:bg-amber-100",
1765
- onClick: reportMode.cancelReportMode,
1766
- children: copy.reportModeCancelAction
1767
- }
1768
- )
1769
- ] }) }) });
1766
+ return /* @__PURE__ */ jsx2(
1767
+ "div",
1768
+ {
1769
+ "data-spaps-screenshot-exclude": "",
1770
+ className: cn("fixed inset-x-4 top-4 flex justify-center", Z_BANNER),
1771
+ children: /* @__PURE__ */ jsx2("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__ */ jsxs("div", { className: "flex flex-wrap items-center justify-center gap-3", children: [
1772
+ /* @__PURE__ */ jsx2("div", { className: "font-medium", children: copy.reportModeTitle }),
1773
+ /* @__PURE__ */ jsx2("div", { className: "text-amber-900/80", children: copy.reportModeDescription }),
1774
+ /* @__PURE__ */ jsx2(
1775
+ "button",
1776
+ {
1777
+ type: "button",
1778
+ className: "rounded-full border border-amber-400 px-3 py-1 font-medium text-amber-900 transition hover:bg-amber-100",
1779
+ onClick: reportMode.cancelReportMode,
1780
+ children: copy.reportModeCancelAction
1781
+ }
1782
+ )
1783
+ ] }) })
1784
+ }
1785
+ );
1770
1786
  }
1771
1787
  function IssueList({
1772
1788
  filter,
@@ -1888,6 +1904,7 @@ function IssueReportPopover({ children }) {
1888
1904
  /* @__PURE__ */ jsx2(Popover.Portal, { children: /* @__PURE__ */ jsx2(
1889
1905
  Popover.Content,
1890
1906
  {
1907
+ "data-spaps-screenshot-exclude": "",
1891
1908
  align: "end",
1892
1909
  side: "top",
1893
1910
  sideOffset: 12,
@@ -2047,6 +2064,9 @@ function IssueReportModal() {
2047
2064
  const { createMutation, updateMutation, replyMutation } = useIssueReportingMutations();
2048
2065
  const [note, setNote] = useState2("");
2049
2066
  const [submitError, setSubmitError] = useState2(null);
2067
+ const submitInFlightRef = useRef2(false);
2068
+ const screenshotCacheRef = useRef2(null);
2069
+ const [isSubmitInFlight, setIsSubmitInFlight] = useState2(false);
2050
2070
  const [inputMode, setInputMode] = useState2(defaultInputMode);
2051
2071
  const [committedTranscript, setCommittedTranscript] = useState2("");
2052
2072
  const [voiceSubmitMetadata, setVoiceSubmitMetadata] = useState2(null);
@@ -2061,7 +2081,7 @@ function IssueReportModal() {
2061
2081
  [issue, mode]
2062
2082
  );
2063
2083
  const attachmentState = useAttachmentState(existingAttachments);
2064
- const isSubmitting = createMutation.isPending || updateMutation.isPending || replyMutation.isPending || attachmentState.uploadProgress.phase === "uploading";
2084
+ const isSubmitting = isSubmitInFlight || createMutation.isPending || updateMutation.isPending || replyMutation.isPending || attachmentState.uploadProgress.phase === "uploading";
2065
2085
  const resetVoiceCapture = useCallback2(() => {
2066
2086
  setInputMode(defaultInputMode);
2067
2087
  setCommittedTranscript("");
@@ -2070,6 +2090,7 @@ function IssueReportModal() {
2070
2090
  setVoiceAppendRequestId(0);
2071
2091
  }, [defaultInputMode]);
2072
2092
  useIsomorphicLayoutEffect(() => {
2093
+ screenshotCacheRef.current = null;
2073
2094
  if (!isOpen) {
2074
2095
  resetVoiceCapture();
2075
2096
  attachmentState.reset();
@@ -2103,9 +2124,16 @@ function IssueReportModal() {
2103
2124
  setInputMode("voice");
2104
2125
  setVoiceStartRequestId((current) => current + 1);
2105
2126
  };
2127
+ const handleSelectText = () => {
2128
+ setInputMode("text");
2129
+ setVoiceStartRequestId(0);
2130
+ setVoiceAppendRequestId(0);
2131
+ };
2106
2132
  const handleAppendTranscript = (transcript) => {
2107
2133
  setNote((current) => appendTranscriptToNote(current, transcript));
2108
2134
  setInputMode("text");
2135
+ setVoiceStartRequestId(0);
2136
+ setVoiceAppendRequestId(0);
2109
2137
  };
2110
2138
  const uploadPendingFiles = async () => {
2111
2139
  const upload = client.issueReporting.uploadAttachment;
@@ -2143,40 +2171,54 @@ function IssueReportModal() {
2143
2171
  if (!target || !isValid || isSubmitting) {
2144
2172
  return;
2145
2173
  }
2174
+ if (submitInFlightRef.current) {
2175
+ return;
2176
+ }
2177
+ submitInFlightRef.current = true;
2178
+ setIsSubmitInFlight(true);
2146
2179
  setSubmitError(null);
2147
2180
  try {
2148
2181
  const noteForSubmit = normalizedNote;
2149
2182
  let newAttachmentIds = [];
2150
2183
  let screenshotCaptureMetadata = null;
2151
2184
  if (screenshotCapture?.enabled && mode === "create" && canUseAttachments && client.issueReporting.uploadAttachment) {
2152
- try {
2153
- const { captureScreenshot } = await import("./screenshot-capture.mjs");
2154
- const captureResult = await captureScreenshot(screenshotCapture, {
2155
- pageUrl: target.page_url,
2156
- createMode,
2157
- inputMode: effectiveInputMode,
2158
- target: {
2159
- componentKey: target.component_key,
2160
- componentLabel: target.component_label,
2161
- surfaceRef: target.surface_ref,
2162
- metadata: target.metadata
2163
- }
2164
- });
2165
- const uploaded = await client.issueReporting.uploadAttachment(
2166
- captureResult.blob,
2167
- { filename: captureResult.filename }
2168
- );
2169
- newAttachmentIds.push(uploaded.id);
2170
- screenshotCaptureMetadata = buildScreenshotCaptureMetadata(
2171
- screenshotCapture,
2172
- "attached"
2173
- );
2174
- } catch (captureError) {
2175
- screenshotCaptureMetadata = buildScreenshotCaptureMetadata(
2176
- screenshotCapture,
2177
- "failed"
2178
- );
2179
- screenshotCapture.onCaptureError?.(captureError);
2185
+ if (screenshotCacheRef.current) {
2186
+ newAttachmentIds.push(screenshotCacheRef.current.attachmentId);
2187
+ screenshotCaptureMetadata = screenshotCacheRef.current.metadata;
2188
+ } else {
2189
+ try {
2190
+ const { captureScreenshot } = await import("./screenshot-capture.mjs");
2191
+ const captureResult = await captureScreenshot(screenshotCapture, {
2192
+ pageUrl: target.page_url,
2193
+ createMode,
2194
+ inputMode: effectiveInputMode,
2195
+ target: {
2196
+ componentKey: target.component_key,
2197
+ componentLabel: target.component_label,
2198
+ surfaceRef: target.surface_ref,
2199
+ metadata: target.metadata
2200
+ }
2201
+ });
2202
+ const uploaded = await client.issueReporting.uploadAttachment(
2203
+ captureResult.blob,
2204
+ { filename: captureResult.filename }
2205
+ );
2206
+ newAttachmentIds.push(uploaded.id);
2207
+ screenshotCaptureMetadata = buildScreenshotCaptureMetadata(
2208
+ screenshotCapture,
2209
+ "attached"
2210
+ );
2211
+ screenshotCacheRef.current = {
2212
+ attachmentId: uploaded.id,
2213
+ metadata: screenshotCaptureMetadata
2214
+ };
2215
+ } catch (captureError) {
2216
+ screenshotCaptureMetadata = buildScreenshotCaptureMetadata(
2217
+ screenshotCapture,
2218
+ "failed"
2219
+ );
2220
+ screenshotCapture.onCaptureError?.(captureError);
2221
+ }
2180
2222
  }
2181
2223
  }
2182
2224
  if (canUseAttachments && attachmentState.pendingFiles.length > 0) {
@@ -2244,13 +2286,23 @@ function IssueReportModal() {
2244
2286
  (current) => current.phase === "uploading" ? { ...current, phase: "error", error: message } : current
2245
2287
  );
2246
2288
  setSubmitError(message);
2289
+ } finally {
2290
+ submitInFlightRef.current = false;
2291
+ setIsSubmitInFlight(false);
2247
2292
  }
2248
2293
  };
2249
2294
  return /* @__PURE__ */ jsx2(Dialog.Root, { open: isOpen, onOpenChange: (open) => !open && handleCloseModal(), children: /* @__PURE__ */ jsxs(Dialog.Portal, { children: [
2250
- /* @__PURE__ */ jsx2(Dialog.Overlay, { className: cn("fixed inset-0 bg-slate-950/45 backdrop-blur-sm", Z_MODAL_OVERLAY) }),
2295
+ /* @__PURE__ */ jsx2(
2296
+ Dialog.Overlay,
2297
+ {
2298
+ "data-spaps-screenshot-exclude": "",
2299
+ className: cn("fixed inset-0 bg-slate-950/45 backdrop-blur-sm", Z_MODAL_OVERLAY)
2300
+ }
2301
+ ),
2251
2302
  /* @__PURE__ */ jsxs(
2252
2303
  Dialog.Content,
2253
2304
  {
2305
+ "data-spaps-screenshot-exclude": "",
2254
2306
  className: cn(
2255
2307
  "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",
2256
2308
  Z_MODAL_CONTENT,
@@ -2296,7 +2348,7 @@ function IssueReportModal() {
2296
2348
  attachmentValidationErrors: attachmentState.validationErrors,
2297
2349
  onRetryHydration: () => void retryModalHydration(),
2298
2350
  onClose: handleCloseModal,
2299
- onSelectText: () => setInputMode("text"),
2351
+ onSelectText: handleSelectText,
2300
2352
  onSelectVoice: () => setInputMode("voice"),
2301
2353
  onRequestStartVoiceInput: handleRequestStartVoiceInput,
2302
2354
  onRequestAppendTranscript: handleRequestAppendTranscript,
@@ -2345,35 +2397,42 @@ function FloatingIssueReportButton({
2345
2397
  }
2346
2398
  return /* @__PURE__ */ jsxs(Fragment, { children: [
2347
2399
  /* @__PURE__ */ jsx2(IssueReportModeBanner, {}),
2348
- !isReportMode ? /* @__PURE__ */ jsx2("div", { className: cn("fixed bottom-12 right-4", Z_FLOATING_BUTTON, positionClassName), children: /* @__PURE__ */ jsx2(IssueReportPopover, { children: /* @__PURE__ */ jsxs(
2349
- "button",
2400
+ !isReportMode ? /* @__PURE__ */ jsx2(
2401
+ "div",
2350
2402
  {
2351
- type: "button",
2352
- "aria-label": needsResponse ? `${copy.entryAriaLabel} (${copy.threadNeedsResponseBadge})` : copy.entryAriaLabel,
2353
- onClick: () => isPopoverOpen ? closePopover() : openPopover(),
2354
- className: cn(
2355
- "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",
2356
- status.isPending && "animate-pulse",
2357
- className
2358
- ),
2359
- children: [
2360
- /* @__PURE__ */ jsx2(
2361
- BugBeetle,
2362
- {
2363
- className: cn("h-6 w-6", getEntryPointClassName(entryPointState)),
2364
- weight: "fill"
2365
- }
2366
- ),
2367
- needsResponse ? /* @__PURE__ */ jsx2(
2368
- "span",
2369
- {
2370
- "data-testid": "issue-report-needs-response-badge",
2371
- className: "absolute -right-0.5 -top-0.5 h-3 w-3 rounded-full border-2 border-white bg-amber-500"
2372
- }
2373
- ) : null
2374
- ]
2403
+ "data-spaps-screenshot-exclude": "",
2404
+ className: cn("fixed bottom-12 right-4", Z_FLOATING_BUTTON, positionClassName),
2405
+ children: /* @__PURE__ */ jsx2(IssueReportPopover, { children: /* @__PURE__ */ jsxs(
2406
+ "button",
2407
+ {
2408
+ type: "button",
2409
+ "aria-label": needsResponse ? `${copy.entryAriaLabel} (${copy.threadNeedsResponseBadge})` : copy.entryAriaLabel,
2410
+ onClick: () => isPopoverOpen ? closePopover() : openPopover(),
2411
+ className: cn(
2412
+ "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",
2413
+ status.isPending && "animate-pulse",
2414
+ className
2415
+ ),
2416
+ children: [
2417
+ /* @__PURE__ */ jsx2(
2418
+ BugBeetle,
2419
+ {
2420
+ className: cn("h-6 w-6", getEntryPointClassName(entryPointState)),
2421
+ weight: "fill"
2422
+ }
2423
+ ),
2424
+ needsResponse ? /* @__PURE__ */ jsx2(
2425
+ "span",
2426
+ {
2427
+ "data-testid": "issue-report-needs-response-badge",
2428
+ className: "absolute -right-0.5 -top-0.5 h-3 w-3 rounded-full border-2 border-white bg-amber-500"
2429
+ }
2430
+ ) : null
2431
+ ]
2432
+ }
2433
+ ) })
2375
2434
  }
2376
- ) }) }) : null,
2435
+ ) : null,
2377
2436
  /* @__PURE__ */ jsx2(IssueReportModal, {})
2378
2437
  ] });
2379
2438
  }
@@ -46,7 +46,11 @@ var DEFAULT_MASK_SELECTORS = [
46
46
  "option",
47
47
  "select",
48
48
  "textarea",
49
- '[contenteditable="true"]'
49
+ // Match every editable region, not just the explicit `contenteditable="true"`
50
+ // form. Bare (`contenteditable`), empty-string (`contenteditable=""`), and
51
+ // `contenteditable="plaintext-only"` editors are all editable and must be
52
+ // masked; only the explicitly disabled `contenteditable="false"` is excluded.
53
+ '[contenteditable]:not([contenteditable="false"])'
50
54
  ];
51
55
  var SCREENSHOT_CAPTURE_REDACTION_TEXT = "[redacted]";
52
56
  var ATTACHMENT_MAX_BYTES = 10 * 1024 * 1024;
@@ -114,7 +118,7 @@ async function canvasToSizedBlob(canvas, imageType, quality, maxBytes) {
114
118
  let currentCanvas = canvas;
115
119
  let scaled = false;
116
120
  for (const scale of [0.85, 0.7, 0.55, 0.4]) {
117
- currentCanvas = scaleCanvas(currentCanvas, scale);
121
+ currentCanvas = scaleCanvas(canvas, scale);
118
122
  scaled = true;
119
123
  for (const format of fallbackTypes) {
120
124
  for (const candidateQuality of [0.86, 0.72, 0.58, 0.44, 0.32]) {
@@ -201,9 +205,14 @@ async function captureScreenshot(config, context) {
201
205
  function resetHtml2CanvasCache() {
202
206
  html2canvasPromise = null;
203
207
  }
204
- function maskScreenshotCaptureDocument(clonedDocument, excludeSelectors = DEFAULT_EXCLUDE_SELECTORS, maskSelectors = DEFAULT_MASK_SELECTORS) {
208
+ function maskScreenshotCaptureDocument(clonedDocument, excludeSelectors = DEFAULT_EXCLUDE_SELECTORS, maskSelectors = []) {
205
209
  clonedDocument.querySelectorAll([...excludeSelectors, "script"].join(",")).forEach((element) => element.remove());
206
- clonedDocument.querySelectorAll(maskSelectors.join(",")).forEach(maskCaptureElement);
210
+ for (const selector of [...DEFAULT_MASK_SELECTORS, ...maskSelectors]) {
211
+ try {
212
+ clonedDocument.querySelectorAll(selector).forEach(maskCaptureElement);
213
+ } catch {
214
+ }
215
+ }
207
216
  redactSensitiveTextNodes(clonedDocument.body ?? clonedDocument.documentElement);
208
217
  }
209
218
  function canUseBrowserCapture() {
@@ -8,7 +8,11 @@ var DEFAULT_MASK_SELECTORS = [
8
8
  "option",
9
9
  "select",
10
10
  "textarea",
11
- '[contenteditable="true"]'
11
+ // Match every editable region, not just the explicit `contenteditable="true"`
12
+ // form. Bare (`contenteditable`), empty-string (`contenteditable=""`), and
13
+ // `contenteditable="plaintext-only"` editors are all editable and must be
14
+ // masked; only the explicitly disabled `contenteditable="false"` is excluded.
15
+ '[contenteditable]:not([contenteditable="false"])'
12
16
  ];
13
17
  var SCREENSHOT_CAPTURE_REDACTION_TEXT = "[redacted]";
14
18
  var ATTACHMENT_MAX_BYTES = 10 * 1024 * 1024;
@@ -76,7 +80,7 @@ async function canvasToSizedBlob(canvas, imageType, quality, maxBytes) {
76
80
  let currentCanvas = canvas;
77
81
  let scaled = false;
78
82
  for (const scale of [0.85, 0.7, 0.55, 0.4]) {
79
- currentCanvas = scaleCanvas(currentCanvas, scale);
83
+ currentCanvas = scaleCanvas(canvas, scale);
80
84
  scaled = true;
81
85
  for (const format of fallbackTypes) {
82
86
  for (const candidateQuality of [0.86, 0.72, 0.58, 0.44, 0.32]) {
@@ -163,9 +167,14 @@ async function captureScreenshot(config, context) {
163
167
  function resetHtml2CanvasCache() {
164
168
  html2canvasPromise = null;
165
169
  }
166
- function maskScreenshotCaptureDocument(clonedDocument, excludeSelectors = DEFAULT_EXCLUDE_SELECTORS, maskSelectors = DEFAULT_MASK_SELECTORS) {
170
+ function maskScreenshotCaptureDocument(clonedDocument, excludeSelectors = DEFAULT_EXCLUDE_SELECTORS, maskSelectors = []) {
167
171
  clonedDocument.querySelectorAll([...excludeSelectors, "script"].join(",")).forEach((element) => element.remove());
168
- clonedDocument.querySelectorAll(maskSelectors.join(",")).forEach(maskCaptureElement);
172
+ for (const selector of [...DEFAULT_MASK_SELECTORS, ...maskSelectors]) {
173
+ try {
174
+ clonedDocument.querySelectorAll(selector).forEach(maskCaptureElement);
175
+ } catch {
176
+ }
177
+ }
169
178
  redactSensitiveTextNodes(clonedDocument.body ?? clonedDocument.documentElement);
170
179
  }
171
180
  function canUseBrowserCapture() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spaps-issue-reporting-react",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "description": "Shared React issue-reporting UI for Sweet Potato platform consumers",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -52,7 +52,7 @@
52
52
  "@radix-ui/react-dialog": "^1.1.15",
53
53
  "@radix-ui/react-popover": "^1.1.15",
54
54
  "date-fns": "^4.1.0",
55
- "spaps-types": "^1.5.0"
55
+ "spaps-types": "^1.5.2"
56
56
  },
57
57
  "peerDependencies": {
58
58
  "@tanstack/react-query": ">=5.0.0",