@jarve/bug-reporter 0.2.0 → 0.3.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
@@ -43,9 +43,10 @@ __export(index_exports, {
43
43
  module.exports = __toCommonJS(index_exports);
44
44
 
45
45
  // src/bug-reporter.tsx
46
- var import_react3 = require("react");
46
+ var import_react4 = require("react");
47
47
 
48
48
  // src/floating-button.tsx
49
+ var import_react = require("react");
49
50
  var import_lucide_react = require("lucide-react");
50
51
 
51
52
  // src/cn.ts
@@ -57,26 +58,63 @@ function cn(...inputs) {
57
58
 
58
59
  // src/floating-button.tsx
59
60
  var import_jsx_runtime = require("react/jsx-runtime");
60
- function FloatingButton({ isActive, onClick }) {
61
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
62
- "button",
61
+ function FloatingButton({ isActive, onClick, position = "right" }) {
62
+ const [hovered, setHovered] = (0, import_react.useState)(false);
63
+ const isLeft = position === "left";
64
+ const sideClasses = isLeft ? "left-4 md:left-6" : "right-4 md:right-6";
65
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
66
+ "div",
63
67
  {
64
- onClick,
65
- className: cn(
66
- "fixed bottom-6 right-6 z-[9999] flex h-12 w-12 items-center justify-center rounded-full shadow-lg transition-all duration-200",
67
- "hover:scale-110 focus:outline-none focus:ring-2 focus:ring-offset-2",
68
- isActive ? "bg-red-500 text-white animate-pulse focus:ring-red-400" : "bg-indigo-600 text-white hover:bg-indigo-700 focus:ring-indigo-400",
69
- "bottom-4 right-4 h-11 w-11 md:bottom-6 md:right-6 md:h-12 md:w-12"
70
- ),
71
- title: isActive ? "Cancel bug capture" : "Report a bug",
72
- "aria-label": isActive ? "Cancel bug capture" : "Report a bug",
73
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Bug, { className: "h-4 w-4 md:h-5 md:w-5" })
68
+ className: cn("fixed z-[9999]", "bottom-4 md:bottom-6", sideClasses),
69
+ onMouseEnter: () => setHovered(true),
70
+ onMouseLeave: () => setHovered(false),
71
+ children: [
72
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
73
+ "div",
74
+ {
75
+ className: cn(
76
+ "pointer-events-none absolute bottom-full mb-2 w-max max-w-[200px] rounded-lg bg-gray-900 px-3 py-2 text-xs leading-relaxed text-white shadow-lg transition-all duration-200",
77
+ isLeft ? "left-0" : "right-0",
78
+ hovered && !isActive ? "translate-y-0 opacity-100" : "translate-y-1 opacity-0"
79
+ ),
80
+ children: [
81
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "font-semibold", children: "Report a bug" }),
82
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("br", {}),
83
+ "Click to screenshot an issue and chat with AI to submit a bug report.",
84
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
85
+ "div",
86
+ {
87
+ className: cn(
88
+ "absolute top-full h-0 w-0 border-x-[6px] border-t-[6px] border-x-transparent border-t-gray-900",
89
+ isLeft ? "left-4" : "right-4"
90
+ )
91
+ }
92
+ )
93
+ ]
94
+ }
95
+ ),
96
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
97
+ "button",
98
+ {
99
+ onClick,
100
+ className: cn(
101
+ "flex items-center justify-center rounded-full shadow-lg transition-all duration-200",
102
+ "hover:scale-110 focus:ring-2 focus:ring-offset-2 focus:outline-none",
103
+ "h-11 w-11 md:h-12 md:w-12",
104
+ isActive ? "animate-pulse bg-red-500 text-white focus:ring-red-400" : "bg-indigo-600 text-white hover:bg-indigo-700 focus:ring-indigo-400"
105
+ ),
106
+ title: isActive ? "Cancel bug capture" : "Report a bug",
107
+ "aria-label": isActive ? "Cancel bug capture" : "Report a bug",
108
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Bug, { className: "h-4 w-4 md:h-5 md:w-5" })
109
+ }
110
+ )
111
+ ]
74
112
  }
75
113
  );
76
114
  }
77
115
 
78
116
  // src/capture-overlay.tsx
79
- var import_react = require("react");
117
+ var import_react2 = require("react");
80
118
  var import_html_to_image = require("html-to-image");
81
119
 
82
120
  // src/utils.ts
@@ -196,8 +234,7 @@ var errorListener = null;
196
234
  var rejectionListener = null;
197
235
  function startCapturing() {
198
236
  if (isCapturing) return;
199
- if (console.error.__bugReporterPatched)
200
- return;
237
+ if (console.error.__bugReporterPatched) return;
201
238
  isCapturing = true;
202
239
  capturedErrors = [];
203
240
  originalConsoleError = console.error;
@@ -406,23 +443,23 @@ function CaptureOverlay({
406
443
  onCapture,
407
444
  onCancel
408
445
  }) {
409
- const [hoveredElement, setHoveredElement] = (0, import_react.useState)(null);
410
- const [hoveredRect, setHoveredRect] = (0, import_react.useState)(null);
411
- const [isCapturing3, setIsCapturing] = (0, import_react.useState)(false);
412
- const [isTouchMode, setIsTouchMode] = (0, import_react.useState)(false);
413
- const [selectedSection, setSelectedSection] = (0, import_react.useState)(null);
414
- const [selectedRect, setSelectedRect] = (0, import_react.useState)(null);
415
- const [selectedTarget, setSelectedTarget] = (0, import_react.useState)(null);
416
- const overlayRef = (0, import_react.useRef)(null);
417
- const hoveredElementRef = (0, import_react.useRef)(null);
418
- const rafRef = (0, import_react.useRef)(null);
419
- const touchCoordsRef = (0, import_react.useRef)(null);
420
- (0, import_react.useEffect)(() => {
446
+ const [hoveredElement, setHoveredElement] = (0, import_react2.useState)(null);
447
+ const [hoveredRect, setHoveredRect] = (0, import_react2.useState)(null);
448
+ const [isCapturing3, setIsCapturing] = (0, import_react2.useState)(false);
449
+ const [isTouchMode, setIsTouchMode] = (0, import_react2.useState)(false);
450
+ const [selectedSection, setSelectedSection] = (0, import_react2.useState)(null);
451
+ const [selectedRect, setSelectedRect] = (0, import_react2.useState)(null);
452
+ const [selectedTarget, setSelectedTarget] = (0, import_react2.useState)(null);
453
+ const overlayRef = (0, import_react2.useRef)(null);
454
+ const hoveredElementRef = (0, import_react2.useRef)(null);
455
+ const rafRef = (0, import_react2.useRef)(null);
456
+ const touchCoordsRef = (0, import_react2.useRef)(null);
457
+ (0, import_react2.useEffect)(() => {
421
458
  if (isActive) {
422
459
  setIsTouchMode(isTouchCapable());
423
460
  }
424
461
  }, [isActive]);
425
- const captureScreenshot = (0, import_react.useCallback)(
462
+ const captureScreenshot = (0, import_react2.useCallback)(
426
463
  async (section, target, coords) => {
427
464
  const elementInfo = collectElementInfo(target, section, coords);
428
465
  setIsCapturing(true);
@@ -443,12 +480,21 @@ function CaptureOverlay({
443
480
  skipFonts: true
444
481
  });
445
482
  const blob = dataUrlToBlob(dataUrl);
446
- const metadata = collectMetadata(section, siteId, reporterName, reporterEmail, elementInfo);
483
+ const metadata = collectMetadata(
484
+ section,
485
+ siteId,
486
+ reporterName,
487
+ reporterEmail,
488
+ elementInfo
489
+ );
447
490
  const consoleErrors = getCapturedErrors();
448
491
  const networkErrors = getCapturedNetworkErrors();
449
492
  onCapture({ screenshot: blob, metadata, consoleErrors, networkErrors });
450
493
  } catch (err) {
451
- console.warn("Bug reporter: first capture attempt failed, retrying with simpler settings", err);
494
+ console.warn(
495
+ "Bug reporter: first capture attempt failed, retrying with simpler settings",
496
+ err
497
+ );
452
498
  try {
453
499
  const dataUrl = await (0, import_html_to_image.toPng)(section, {
454
500
  quality: 0.6,
@@ -457,13 +503,25 @@ function CaptureOverlay({
457
503
  cacheBust: true
458
504
  });
459
505
  const retryBlob = dataUrlToBlob(dataUrl);
460
- const metadata = collectMetadata(section, siteId, reporterName, reporterEmail, elementInfo);
506
+ const metadata = collectMetadata(
507
+ section,
508
+ siteId,
509
+ reporterName,
510
+ reporterEmail,
511
+ elementInfo
512
+ );
461
513
  const consoleErrors = getCapturedErrors();
462
514
  const networkErrors = getCapturedNetworkErrors();
463
515
  onCapture({ screenshot: retryBlob, metadata, consoleErrors, networkErrors });
464
516
  } catch (e) {
465
517
  console.error("Bug reporter: screenshot capture failed after retry");
466
- const metadata = collectMetadata(section, siteId, reporterName, reporterEmail, elementInfo);
518
+ const metadata = collectMetadata(
519
+ section,
520
+ siteId,
521
+ reporterName,
522
+ reporterEmail,
523
+ elementInfo
524
+ );
467
525
  const consoleErrors = getCapturedErrors();
468
526
  const networkErrors = getCapturedNetworkErrors();
469
527
  onCapture({
@@ -479,7 +537,7 @@ function CaptureOverlay({
479
537
  },
480
538
  [siteId, reporterName, reporterEmail, onCapture]
481
539
  );
482
- const handleMouseMove = (0, import_react.useCallback)(
540
+ const handleMouseMove = (0, import_react2.useCallback)(
483
541
  (e) => {
484
542
  if (!isActive || isCapturing3 || isTouchMode) return;
485
543
  if (rafRef.current) return;
@@ -501,7 +559,7 @@ function CaptureOverlay({
501
559
  },
502
560
  [isActive, isCapturing3, isTouchMode]
503
561
  );
504
- const handleClick = (0, import_react.useCallback)(
562
+ const handleClick = (0, import_react2.useCallback)(
505
563
  async (e) => {
506
564
  if (!isActive || isCapturing3 || isTouchMode) return;
507
565
  const target = e.target;
@@ -515,7 +573,7 @@ function CaptureOverlay({
515
573
  },
516
574
  [isActive, isCapturing3, isTouchMode, captureScreenshot]
517
575
  );
518
- const handleTouchEnd = (0, import_react.useCallback)(
576
+ const handleTouchEnd = (0, import_react2.useCallback)(
519
577
  (e) => {
520
578
  if (!isActive || isCapturing3) return;
521
579
  const touch = e.changedTouches[0];
@@ -532,11 +590,11 @@ function CaptureOverlay({
532
590
  },
533
591
  [isActive, isCapturing3]
534
592
  );
535
- const handleConfirmCapture = (0, import_react.useCallback)(async () => {
593
+ const handleConfirmCapture = (0, import_react2.useCallback)(async () => {
536
594
  if (!selectedSection || !selectedTarget || !touchCoordsRef.current) return;
537
595
  await captureScreenshot(selectedSection, selectedTarget, touchCoordsRef.current);
538
596
  }, [selectedSection, selectedTarget, captureScreenshot]);
539
- const handlePointerDown = (0, import_react.useCallback)(
597
+ const handlePointerDown = (0, import_react2.useCallback)(
540
598
  (e) => {
541
599
  if (!isActive) return;
542
600
  if (e.pointerType === "touch") {
@@ -547,7 +605,7 @@ function CaptureOverlay({
547
605
  },
548
606
  [isActive]
549
607
  );
550
- const handleKeyDown = (0, import_react.useCallback)(
608
+ const handleKeyDown = (0, import_react2.useCallback)(
551
609
  (e) => {
552
610
  if (e.key === "Escape" && isActive) {
553
611
  e.preventDefault();
@@ -557,7 +615,7 @@ function CaptureOverlay({
557
615
  },
558
616
  [isActive, onCancel]
559
617
  );
560
- const handleScroll = (0, import_react.useCallback)(() => {
618
+ const handleScroll = (0, import_react2.useCallback)(() => {
561
619
  if (hoveredElementRef.current) {
562
620
  setHoveredRect(hoveredElementRef.current.getBoundingClientRect());
563
621
  }
@@ -565,7 +623,7 @@ function CaptureOverlay({
565
623
  setSelectedRect(selectedSection.getBoundingClientRect());
566
624
  }
567
625
  }, [selectedSection]);
568
- (0, import_react.useEffect)(() => {
626
+ (0, import_react2.useEffect)(() => {
569
627
  if (!isActive) {
570
628
  setHoveredElement(null);
571
629
  setHoveredRect(null);
@@ -594,7 +652,16 @@ function CaptureOverlay({
594
652
  document.removeEventListener("click", handleClick, true);
595
653
  if (rafRef.current) cancelAnimationFrame(rafRef.current);
596
654
  };
597
- }, [isActive, isTouchMode, handleMouseMove, handleClick, handleTouchEnd, handlePointerDown, handleKeyDown, handleScroll]);
655
+ }, [
656
+ isActive,
657
+ isTouchMode,
658
+ handleMouseMove,
659
+ handleClick,
660
+ handleTouchEnd,
661
+ handlePointerDown,
662
+ handleKeyDown,
663
+ handleScroll
664
+ ]);
598
665
  const highlightRect = isTouchMode ? selectedRect : hoveredRect;
599
666
  const showHighlight = isTouchMode ? !!selectedSection : !!hoveredElement && !!hoveredRect;
600
667
  if (!isActive) return null;
@@ -605,20 +672,21 @@ function CaptureOverlay({
605
672
  "data-bug-reporter": true,
606
673
  role: "alert",
607
674
  "aria-live": "assertive",
608
- className: "fixed top-0 left-0 right-0 z-[10000] bg-indigo-600 text-white text-center py-2 px-4 text-sm font-medium flex items-center justify-center gap-3",
675
+ className: "fixed top-0 right-0 left-0 z-[10000] flex items-center justify-center gap-3 bg-indigo-600 px-4 py-2 text-center text-sm font-medium text-white",
609
676
  children: isTouchMode ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
610
677
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "Tap the section with the bug" }),
611
678
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
612
679
  "button",
613
680
  {
614
681
  onClick: onCancel,
615
- className: "px-3 py-1 min-h-[44px] bg-white/20 rounded-md text-sm font-medium",
682
+ className: "min-h-[44px] rounded-md bg-white/20 px-3 py-1 text-sm font-medium",
616
683
  children: "Cancel"
617
684
  }
618
685
  )
619
686
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
620
- "Click on the section with the bug. Press ",
621
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("kbd", { className: "px-1.5 py-0.5 bg-indigo-800 rounded text-xs mx-1", children: "Esc" }),
687
+ "Click on the section with the bug. Press",
688
+ " ",
689
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("kbd", { className: "mx-1 rounded bg-indigo-800 px-1.5 py-0.5 text-xs", children: "Esc" }),
622
690
  " to cancel."
623
691
  ] })
624
692
  }
@@ -628,7 +696,7 @@ function CaptureOverlay({
628
696
  {
629
697
  ref: overlayRef,
630
698
  "data-bug-reporter": true,
631
- className: "fixed pointer-events-none z-[9998] border-2 border-indigo-500 rounded-sm transition-all duration-150 ease-out",
699
+ className: "pointer-events-none fixed z-[9998] rounded-sm border-2 border-indigo-500 transition-all duration-150 ease-out",
632
700
  style: {
633
701
  top: highlightRect.top - 2,
634
702
  left: highlightRect.left - 2,
@@ -642,11 +710,11 @@ function CaptureOverlay({
642
710
  "div",
643
711
  {
644
712
  "data-bug-reporter": true,
645
- className: "fixed bottom-0 left-0 right-0 z-[10000] bg-white border-t border-gray-200 shadow-lg",
713
+ className: "fixed right-0 bottom-0 left-0 z-[10000] border-t border-gray-200 bg-white shadow-lg",
646
714
  style: { paddingBottom: "env(safe-area-inset-bottom, 0px)" },
647
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between px-4 py-3 gap-3", children: [
648
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-sm font-medium text-gray-900 truncate", children: "Capture this section?" }),
649
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-2 shrink-0", children: [
715
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between gap-3 px-4 py-3", children: [
716
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "truncate text-sm font-medium text-gray-900", children: "Capture this section?" }),
717
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex shrink-0 gap-2", children: [
650
718
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
651
719
  "button",
652
720
  {
@@ -656,7 +724,7 @@ function CaptureOverlay({
656
724
  setSelectedTarget(null);
657
725
  touchCoordsRef.current = null;
658
726
  },
659
- className: "px-4 min-h-[44px] rounded-md border border-gray-300 text-sm font-medium text-gray-700",
727
+ className: "min-h-[44px] rounded-md border border-gray-300 px-4 text-sm font-medium text-gray-700",
660
728
  children: "Cancel"
661
729
  }
662
730
  ),
@@ -664,7 +732,7 @@ function CaptureOverlay({
664
732
  "button",
665
733
  {
666
734
  onClick: handleConfirmCapture,
667
- className: "px-4 min-h-[44px] rounded-md bg-indigo-600 text-white text-sm font-medium",
735
+ className: "min-h-[44px] rounded-md bg-indigo-600 px-4 text-sm font-medium text-white",
668
736
  children: "Capture"
669
737
  }
670
738
  )
@@ -677,7 +745,7 @@ function CaptureOverlay({
677
745
  }
678
746
 
679
747
  // src/report-modal.tsx
680
- var import_react2 = require("react");
748
+ var import_react3 = require("react");
681
749
  var import_lucide_react2 = require("lucide-react");
682
750
  var import_jsx_runtime3 = require("react/jsx-runtime");
683
751
  function ReportModal({
@@ -688,24 +756,24 @@ function ReportModal({
688
756
  user,
689
757
  onClose
690
758
  }) {
691
- const [messages, setMessages] = (0, import_react2.useState)([]);
692
- const [input, setInput] = (0, import_react2.useState)("");
693
- const [isLoading, setIsLoading] = (0, import_react2.useState)(false);
694
- const [modalState, setModalState] = (0, import_react2.useState)("chatting");
695
- const [reportId, setReportId] = (0, import_react2.useState)(null);
696
- const [screenshotUrl, setScreenshotUrl] = (0, import_react2.useState)(null);
697
- const [errorMessage, setErrorMessage] = (0, import_react2.useState)(null);
698
- const chatEndRef = (0, import_react2.useRef)(null);
699
- const inputRef = (0, import_react2.useRef)(null);
700
- const hasInitRef = (0, import_react2.useRef)(false);
701
- const apiHeaders = (0, import_react2.useMemo)(
759
+ const [messages, setMessages] = (0, import_react3.useState)([]);
760
+ const [input, setInput] = (0, import_react3.useState)("");
761
+ const [isLoading, setIsLoading] = (0, import_react3.useState)(false);
762
+ const [modalState, setModalState] = (0, import_react3.useState)("chatting");
763
+ const [reportId, setReportId] = (0, import_react3.useState)(null);
764
+ const [screenshotUrl, setScreenshotUrl] = (0, import_react3.useState)(null);
765
+ const [errorMessage, setErrorMessage] = (0, import_react3.useState)(null);
766
+ const chatEndRef = (0, import_react3.useRef)(null);
767
+ const inputRef = (0, import_react3.useRef)(null);
768
+ const hasInitRef = (0, import_react3.useRef)(false);
769
+ const apiHeaders = (0, import_react3.useMemo)(
702
770
  () => ({
703
771
  "Content-Type": "application/json",
704
772
  "X-Bug-Reporter-Key": apiConfig.apiKey
705
773
  }),
706
774
  [apiConfig.apiKey]
707
775
  );
708
- (0, import_react2.useEffect)(() => {
776
+ (0, import_react3.useEffect)(() => {
709
777
  if ((captureResult == null ? void 0 : captureResult.screenshot) && captureResult.screenshot.size > 0) {
710
778
  const url = URL.createObjectURL(captureResult.screenshot);
711
779
  setScreenshotUrl((prev) => {
@@ -722,7 +790,7 @@ function ReportModal({
722
790
  return null;
723
791
  });
724
792
  }, [captureResult]);
725
- const sendInitialMessage = (0, import_react2.useCallback)(async () => {
793
+ const sendInitialMessage = (0, import_react3.useCallback)(async () => {
726
794
  if (!captureResult) return;
727
795
  setIsLoading(true);
728
796
  try {
@@ -738,7 +806,9 @@ function ReportModal({
738
806
  })
739
807
  });
740
808
  if (response.status === 401) {
741
- console.error("Bug reporter: invalid or missing API key. Check your BugReporter apiKey prop.");
809
+ console.error(
810
+ "Bug reporter: invalid or missing API key. Check your BugReporter apiKey prop."
811
+ );
742
812
  setMessages([
743
813
  {
744
814
  role: "assistant",
@@ -761,23 +831,23 @@ function ReportModal({
761
831
  setIsLoading(false);
762
832
  }
763
833
  }, [captureResult, apiConfig.apiUrl, apiHeaders]);
764
- (0, import_react2.useEffect)(() => {
834
+ (0, import_react3.useEffect)(() => {
765
835
  if (isOpen && captureResult && !hasInitRef.current) {
766
836
  hasInitRef.current = true;
767
837
  sendInitialMessage();
768
838
  }
769
839
  }, [isOpen, captureResult, sendInitialMessage]);
770
- (0, import_react2.useEffect)(() => {
840
+ (0, import_react3.useEffect)(() => {
771
841
  var _a;
772
842
  (_a = chatEndRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth" });
773
843
  }, [messages]);
774
- (0, import_react2.useEffect)(() => {
844
+ (0, import_react3.useEffect)(() => {
775
845
  var _a;
776
846
  if (isOpen && !isLoading) {
777
847
  (_a = inputRef.current) == null ? void 0 : _a.focus();
778
848
  }
779
849
  }, [isOpen, isLoading, messages]);
780
- const submitReport = (0, import_react2.useCallback)(
850
+ const submitReport = (0, import_react3.useCallback)(
781
851
  async (conversation, structuredReport) => {
782
852
  if (!captureResult || modalState !== "chatting") return;
783
853
  setModalState("submitting");
@@ -835,25 +905,20 @@ function ReportModal({
835
905
  setModalState("submitted");
836
906
  } catch (err) {
837
907
  console.error("Bug reporter: failed to submit report", err);
838
- setErrorMessage(
839
- err instanceof Error ? err.message : "Failed to submit report"
840
- );
908
+ setErrorMessage(err instanceof Error ? err.message : "Failed to submit report");
841
909
  setModalState("error");
842
910
  }
843
911
  },
844
912
  [captureResult, apiConfig.apiUrl, apiHeaders, modalState]
845
913
  );
846
- const handleManualSubmit = (0, import_react2.useCallback)(() => {
914
+ const handleManualSubmit = (0, import_react3.useCallback)(() => {
847
915
  submitReport(messages);
848
916
  }, [submitReport, messages]);
849
917
  async function sendMessage() {
850
918
  if (!input.trim() || isLoading || !captureResult) return;
851
919
  const userMessage = input.trim();
852
920
  setInput("");
853
- const newMessages = [
854
- ...messages,
855
- { role: "user", content: userMessage }
856
- ];
921
+ const newMessages = [...messages, { role: "user", content: userMessage }];
857
922
  setMessages(newMessages);
858
923
  setIsLoading(true);
859
924
  try {
@@ -869,7 +934,9 @@ function ReportModal({
869
934
  })
870
935
  });
871
936
  if (response.status === 401) {
872
- console.error("Bug reporter: invalid or missing API key. Check your BugReporter apiKey prop.");
937
+ console.error(
938
+ "Bug reporter: invalid or missing API key. Check your BugReporter apiKey prop."
939
+ );
873
940
  setMessages([
874
941
  ...newMessages,
875
942
  {
@@ -881,10 +948,7 @@ function ReportModal({
881
948
  }
882
949
  if (!response.ok) throw new Error("Failed to get AI response");
883
950
  const data = await response.json();
884
- setMessages([
885
- ...newMessages,
886
- { role: "assistant", content: data.message }
887
- ]);
951
+ setMessages([...newMessages, { role: "assistant", content: data.message }]);
888
952
  if (data.readyToSubmit && data.structuredReport) {
889
953
  await submitReport(
890
954
  [...newMessages, { role: "assistant", content: data.message }],
@@ -935,65 +999,66 @@ function ReportModal({
935
999
  "div",
936
1000
  {
937
1001
  className: cn(
938
- "bg-white dark:bg-gray-950 rounded-xl shadow-2xl border border-gray-200 dark:border-gray-800 flex flex-col overflow-hidden",
939
- "w-full max-w-lg mx-4",
940
- "max-[768px]:mx-0 max-[768px]:rounded-none max-[768px]:max-w-none max-[768px]:h-full",
1002
+ "flex flex-col overflow-hidden rounded-xl border border-gray-200 bg-white shadow-2xl dark:border-gray-800 dark:bg-gray-950",
1003
+ "mx-4 w-full max-w-lg",
1004
+ "max-[768px]:mx-0 max-[768px]:h-full max-[768px]:max-w-none max-[768px]:rounded-none",
941
1005
  "min-[769px]:max-h-[85vh]"
942
1006
  ),
943
1007
  children: [
944
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between px-4 py-3 border-b border-gray-200 dark:border-gray-800 bg-gray-50/30 dark:bg-gray-900/30", children: [
1008
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between border-b border-gray-200 bg-gray-50/30 px-4 py-3 dark:border-gray-800 dark:bg-gray-900/30", children: [
945
1009
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
946
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { className: "font-semibold text-sm text-gray-900 dark:text-gray-100", children: "Bug Report" }),
1010
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: "Bug Report" }),
947
1011
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: siteId })
948
1012
  ] }),
949
1013
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
950
1014
  "button",
951
1015
  {
952
1016
  onClick: handleClose,
953
- className: "p-1.5 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors",
1017
+ className: "rounded-md p-1.5 transition-colors hover:bg-gray-100 dark:hover:bg-gray-800",
954
1018
  "aria-label": "Close",
955
1019
  children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.X, { className: "h-4 w-4 text-gray-600 dark:text-gray-400" })
956
1020
  }
957
1021
  )
958
1022
  ] }),
959
- screenshotUrl && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "px-4 py-3 border-b border-gray-200 dark:border-gray-800 bg-gray-50/10 dark:bg-gray-900/10", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1023
+ screenshotUrl && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "border-b border-gray-200 bg-gray-50/10 px-4 py-3 dark:border-gray-800 dark:bg-gray-900/10", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
960
1024
  "img",
961
1025
  {
962
1026
  src: screenshotUrl,
963
1027
  alt: "Captured section",
964
- className: "w-full max-h-40 object-contain rounded-md border border-gray-200 dark:border-gray-700"
1028
+ className: "max-h-40 w-full rounded-md border border-gray-200 object-contain dark:border-gray-700"
965
1029
  }
966
1030
  ) }),
967
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex-1 overflow-y-auto px-4 py-3 space-y-3 min-h-0", children: modalState === "submitted" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-col items-center justify-center py-8 text-center", children: [
968
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.CheckCircle2, { className: "h-12 w-12 text-green-500 mb-3" }),
969
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { className: "font-semibold text-lg text-gray-900 dark:text-gray-100", children: "Report Submitted" }),
970
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: "text-sm text-gray-500 dark:text-gray-400 mt-1", children: [
971
- "Reference: ",
972
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { className: "text-xs bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 rounded", children: reportId == null ? void 0 : reportId.slice(0, 8) })
1031
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "min-h-0 flex-1 space-y-3 overflow-y-auto px-4 py-3", children: modalState === "submitted" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-col items-center justify-center py-8 text-center", children: [
1032
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.CheckCircle2, { className: "mb-3 h-12 w-12 text-green-500" }),
1033
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100", children: "Report Submitted" }),
1034
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: "mt-1 text-sm text-gray-500 dark:text-gray-400", children: [
1035
+ "Reference:",
1036
+ " ",
1037
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { className: "rounded bg-gray-100 px-1.5 py-0.5 text-xs dark:bg-gray-800", children: reportId == null ? void 0 : reportId.slice(0, 8) })
973
1038
  ] }),
974
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-sm text-gray-500 dark:text-gray-400 mt-2", children: "Thanks for the report \u2014 we'll look into it." }),
1039
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mt-2 text-sm text-gray-500 dark:text-gray-400", children: "Thanks for the report \u2014 we'll look into it." }),
975
1040
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
976
1041
  "button",
977
1042
  {
978
1043
  onClick: handleClose,
979
- className: "mt-4 px-4 py-2 bg-indigo-600 text-white rounded-md text-sm hover:bg-indigo-700 transition-colors",
1044
+ className: "mt-4 rounded-md bg-indigo-600 px-4 py-2 text-sm text-white transition-colors hover:bg-indigo-700",
980
1045
  children: "Done"
981
1046
  }
982
1047
  )
983
1048
  ] }) : modalState === "error" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-col items-center justify-center py-8 text-center", children: [
984
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.X, { className: "h-12 w-12 text-red-500 mb-3" }),
985
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { className: "font-semibold text-lg text-gray-900 dark:text-gray-100", children: "Submission Failed" }),
986
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-sm text-gray-500 dark:text-gray-400 mt-1", children: errorMessage || "Something went wrong. Please try again." }),
1049
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.X, { className: "mb-3 h-12 w-12 text-red-500" }),
1050
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100", children: "Submission Failed" }),
1051
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mt-1 text-sm text-gray-500 dark:text-gray-400", children: errorMessage || "Something went wrong. Please try again." }),
987
1052
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
988
1053
  "button",
989
1054
  {
990
1055
  onClick: () => setModalState("chatting"),
991
- className: "mt-4 px-4 py-2 bg-indigo-600 text-white rounded-md text-sm hover:bg-indigo-700 transition-colors",
1056
+ className: "mt-4 rounded-md bg-indigo-600 px-4 py-2 text-sm text-white transition-colors hover:bg-indigo-700",
992
1057
  children: "Try Again"
993
1058
  }
994
1059
  )
995
1060
  ] }) : modalState === "submitting" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-col items-center justify-center py-8", children: [
996
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.Loader2, { className: "h-8 w-8 animate-spin text-indigo-500 mb-3" }),
1061
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.Loader2, { className: "mb-3 h-8 w-8 animate-spin text-indigo-500" }),
997
1062
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: "Submitting your report..." })
998
1063
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
999
1064
  (captureResult == null ? void 0 : captureResult.screenshot.size) === 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-xs text-amber-600", children: "Screenshot could not be captured. Please describe the visual issue in detail." }),
@@ -1002,42 +1067,53 @@ function ReportModal({
1002
1067
  {
1003
1068
  className: cn(
1004
1069
  "text-sm leading-relaxed",
1005
- msg.role === "assistant" ? "bg-gray-100/50 dark:bg-gray-800/50 rounded-lg p-3 text-gray-900 dark:text-gray-100" : "bg-indigo-600 text-white rounded-lg p-3 ml-8"
1070
+ msg.role === "assistant" ? "rounded-lg bg-gray-100/50 p-3 text-gray-900 dark:bg-gray-800/50 dark:text-gray-100" : "ml-8 rounded-lg bg-indigo-600 p-3 text-white"
1006
1071
  ),
1007
1072
  children: msg.content
1008
1073
  },
1009
1074
  i
1010
1075
  )),
1011
- isLoading && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "bg-gray-100/50 dark:bg-gray-800/50 rounded-lg p-3 flex items-center gap-2", children: [
1076
+ isLoading && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-2 rounded-lg bg-gray-100/50 p-3 dark:bg-gray-800/50", children: [
1012
1077
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.Loader2, { className: "h-3.5 w-3.5 animate-spin text-gray-500" }),
1013
1078
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-sm text-gray-500 dark:text-gray-400", children: "Thinking..." })
1014
1079
  ] }),
1015
1080
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { ref: chatEndRef })
1016
1081
  ] }) }),
1017
- modalState === "chatting" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "border-t border-gray-200 dark:border-gray-800 px-4 py-3", children: [
1018
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex gap-2", children: [
1019
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1020
- "textarea",
1021
- {
1022
- ref: inputRef,
1023
- value: input,
1024
- onChange: (e) => setInput(e.target.value),
1025
- onKeyDown: handleKeyDown,
1026
- placeholder: "Describe what's wrong...",
1027
- rows: 2,
1028
- className: "flex-1 resize-none rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-950 px-3 py-2 text-sm text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:border-transparent",
1029
- disabled: isLoading
1030
- }
1031
- ),
1032
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-col gap-1", children: [
1033
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1082
+ modalState === "chatting" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "border-t border-gray-200 px-4 py-3 dark:border-gray-800", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-col gap-2", children: [
1083
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1084
+ "textarea",
1085
+ {
1086
+ ref: inputRef,
1087
+ value: input,
1088
+ onChange: (e) => setInput(e.target.value),
1089
+ onKeyDown: handleKeyDown,
1090
+ placeholder: "Describe what's wrong...",
1091
+ rows: 2,
1092
+ className: "w-full resize-none rounded-md border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 focus:border-transparent focus:ring-2 focus:ring-indigo-400 focus:outline-none dark:border-gray-700 dark:bg-gray-950 dark:text-gray-100",
1093
+ disabled: isLoading
1094
+ }
1095
+ ),
1096
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-col gap-2", children: [
1097
+ captureResult && (captureResult.consoleErrors.length > 0 || captureResult.networkErrors.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: "flex-1 text-xs text-amber-600", children: [
1098
+ [
1099
+ captureResult.consoleErrors.length > 0 ? `${captureResult.consoleErrors.length} console error${captureResult.consoleErrors.length !== 1 ? "s" : ""}` : null,
1100
+ captureResult.networkErrors.length > 0 ? `${captureResult.networkErrors.length} failed request${captureResult.networkErrors.length !== 1 ? "s" : ""}` : null
1101
+ ].filter(Boolean).join(" + "),
1102
+ " ",
1103
+ "captured \u2014 these will be included in the report."
1104
+ ] }),
1105
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex justify-end gap-2", children: [
1106
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1034
1107
  "button",
1035
1108
  {
1036
1109
  onClick: sendMessage,
1037
1110
  disabled: !input.trim() || isLoading,
1038
- className: "p-2 rounded-md bg-indigo-600 text-white hover:bg-indigo-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors",
1111
+ className: "flex items-center gap-1 rounded-md bg-indigo-600 px-3 py-2 text-nowrap text-white transition-colors hover:bg-indigo-700 disabled:cursor-not-allowed disabled:opacity-50",
1039
1112
  title: "Send message",
1040
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.Send, { className: "h-4 w-4" })
1113
+ children: [
1114
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.Send, { className: "h-4 w-4" }),
1115
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-sm font-medium", children: "Send message" })
1116
+ ]
1041
1117
  }
1042
1118
  ),
1043
1119
  messages.length >= 2 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -1045,22 +1121,14 @@ function ReportModal({
1045
1121
  {
1046
1122
  onClick: handleManualSubmit,
1047
1123
  disabled: isLoading,
1048
- className: "px-2 py-1 rounded-md bg-green-600 text-white hover:bg-green-700 disabled:opacity-50 text-xs font-medium transition-colors",
1124
+ className: "rounded-md bg-green-600 px-3 py-2 text-xs font-medium text-nowrap text-white transition-colors hover:bg-green-700 disabled:opacity-50",
1049
1125
  title: "Submit report now",
1050
- children: "Submit"
1126
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-sm font-medium", children: "Submit report" })
1051
1127
  }
1052
1128
  )
1053
1129
  ] })
1054
- ] }),
1055
- captureResult && (captureResult.consoleErrors.length > 0 || captureResult.networkErrors.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: "text-xs text-amber-600 mt-1.5", children: [
1056
- [
1057
- captureResult.consoleErrors.length > 0 ? `${captureResult.consoleErrors.length} console error${captureResult.consoleErrors.length !== 1 ? "s" : ""}` : null,
1058
- captureResult.networkErrors.length > 0 ? `${captureResult.networkErrors.length} failed request${captureResult.networkErrors.length !== 1 ? "s" : ""}` : null
1059
- ].filter(Boolean).join(" + "),
1060
- " ",
1061
- "captured \u2014 these will be included in the report."
1062
1130
  ] })
1063
- ] })
1131
+ ] }) })
1064
1132
  ]
1065
1133
  }
1066
1134
  )
@@ -1074,12 +1142,14 @@ function JarveBugReporter({
1074
1142
  apiUrl,
1075
1143
  apiKey,
1076
1144
  user,
1145
+ buttonPosition,
1077
1146
  children
1078
1147
  }) {
1079
- const [captureMode, setCaptureMode] = (0, import_react3.useState)(false);
1080
- const [captureResult, setCaptureResult] = (0, import_react3.useState)(null);
1081
- const [showModal, setShowModal] = (0, import_react3.useState)(false);
1082
- (0, import_react3.useEffect)(() => {
1148
+ const safeApiKey = apiKey || "";
1149
+ const [captureMode, setCaptureMode] = (0, import_react4.useState)(false);
1150
+ const [captureResult, setCaptureResult] = (0, import_react4.useState)(null);
1151
+ const [showModal, setShowModal] = (0, import_react4.useState)(false);
1152
+ (0, import_react4.useEffect)(() => {
1083
1153
  startCapturing();
1084
1154
  startNetworkCapture();
1085
1155
  return () => {
@@ -1087,29 +1157,36 @@ function JarveBugReporter({
1087
1157
  stopNetworkCapture();
1088
1158
  };
1089
1159
  }, []);
1090
- const toggleCaptureMode = (0, import_react3.useCallback)(() => {
1160
+ const toggleCaptureMode = (0, import_react4.useCallback)(() => {
1091
1161
  setCaptureMode((prev) => !prev);
1092
1162
  }, []);
1093
- const handleCapture = (0, import_react3.useCallback)((result) => {
1163
+ const handleCapture = (0, import_react4.useCallback)((result) => {
1094
1164
  setCaptureResult(result);
1095
1165
  setCaptureMode(false);
1096
1166
  setShowModal(true);
1097
1167
  }, []);
1098
- const handleCancelCapture = (0, import_react3.useCallback)(() => {
1168
+ const handleCancelCapture = (0, import_react4.useCallback)(() => {
1099
1169
  setCaptureMode(false);
1100
1170
  }, []);
1101
- const handleCloseModal = (0, import_react3.useCallback)(() => {
1171
+ const handleCloseModal = (0, import_react4.useCallback)(() => {
1102
1172
  setShowModal(false);
1103
1173
  setCaptureResult(null);
1104
1174
  clearCapturedErrors();
1105
1175
  clearCapturedNetworkErrors();
1106
1176
  }, []);
1107
- const siteId = apiKey.startsWith("brk_") ? apiKey.slice(4, 12) : "external";
1177
+ const siteId = safeApiKey.startsWith("brk_") ? safeApiKey.slice(4, 12) : "external";
1108
1178
  const reporterName = (user == null ? void 0 : user.name) || "Anonymous";
1109
1179
  const reporterEmail = (user == null ? void 0 : user.email) || "unknown@external";
1110
1180
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1111
1181
  children,
1112
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FloatingButton, { isActive: captureMode, onClick: toggleCaptureMode }),
1182
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1183
+ FloatingButton,
1184
+ {
1185
+ isActive: captureMode,
1186
+ onClick: toggleCaptureMode,
1187
+ position: buttonPosition
1188
+ }
1189
+ ),
1113
1190
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1114
1191
  CaptureOverlay,
1115
1192
  {