@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.mjs CHANGED
@@ -20,9 +20,10 @@ var __spreadValues = (a, b) => {
20
20
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
21
21
 
22
22
  // src/bug-reporter.tsx
23
- import { useState as useState3, useEffect as useEffect3, useCallback as useCallback3 } from "react";
23
+ import { useState as useState4, useEffect as useEffect3, useCallback as useCallback3 } from "react";
24
24
 
25
25
  // src/floating-button.tsx
26
+ import { useState } from "react";
26
27
  import { Bug } from "lucide-react";
27
28
 
28
29
  // src/cn.ts
@@ -33,27 +34,64 @@ function cn(...inputs) {
33
34
  }
34
35
 
35
36
  // src/floating-button.tsx
36
- import { jsx } from "react/jsx-runtime";
37
- function FloatingButton({ isActive, onClick }) {
38
- return /* @__PURE__ */ jsx(
39
- "button",
37
+ import { jsx, jsxs } from "react/jsx-runtime";
38
+ function FloatingButton({ isActive, onClick, position = "right" }) {
39
+ const [hovered, setHovered] = useState(false);
40
+ const isLeft = position === "left";
41
+ const sideClasses = isLeft ? "left-4 md:left-6" : "right-4 md:right-6";
42
+ return /* @__PURE__ */ jsxs(
43
+ "div",
40
44
  {
41
- onClick,
42
- className: cn(
43
- "fixed bottom-6 right-6 z-[9999] flex h-12 w-12 items-center justify-center rounded-full shadow-lg transition-all duration-200",
44
- "hover:scale-110 focus:outline-none focus:ring-2 focus:ring-offset-2",
45
- 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",
46
- "bottom-4 right-4 h-11 w-11 md:bottom-6 md:right-6 md:h-12 md:w-12"
47
- ),
48
- title: isActive ? "Cancel bug capture" : "Report a bug",
49
- "aria-label": isActive ? "Cancel bug capture" : "Report a bug",
50
- children: /* @__PURE__ */ jsx(Bug, { className: "h-4 w-4 md:h-5 md:w-5" })
45
+ className: cn("fixed z-[9999]", "bottom-4 md:bottom-6", sideClasses),
46
+ onMouseEnter: () => setHovered(true),
47
+ onMouseLeave: () => setHovered(false),
48
+ children: [
49
+ /* @__PURE__ */ jsxs(
50
+ "div",
51
+ {
52
+ className: cn(
53
+ "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",
54
+ isLeft ? "left-0" : "right-0",
55
+ hovered && !isActive ? "translate-y-0 opacity-100" : "translate-y-1 opacity-0"
56
+ ),
57
+ children: [
58
+ /* @__PURE__ */ jsx("span", { className: "font-semibold", children: "Report a bug" }),
59
+ /* @__PURE__ */ jsx("br", {}),
60
+ "Click to screenshot an issue and chat with AI to submit a bug report.",
61
+ /* @__PURE__ */ jsx(
62
+ "div",
63
+ {
64
+ className: cn(
65
+ "absolute top-full h-0 w-0 border-x-[6px] border-t-[6px] border-x-transparent border-t-gray-900",
66
+ isLeft ? "left-4" : "right-4"
67
+ )
68
+ }
69
+ )
70
+ ]
71
+ }
72
+ ),
73
+ /* @__PURE__ */ jsx(
74
+ "button",
75
+ {
76
+ onClick,
77
+ className: cn(
78
+ "flex items-center justify-center rounded-full shadow-lg transition-all duration-200",
79
+ "hover:scale-110 focus:ring-2 focus:ring-offset-2 focus:outline-none",
80
+ "h-11 w-11 md:h-12 md:w-12",
81
+ 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"
82
+ ),
83
+ title: isActive ? "Cancel bug capture" : "Report a bug",
84
+ "aria-label": isActive ? "Cancel bug capture" : "Report a bug",
85
+ children: /* @__PURE__ */ jsx(Bug, { className: "h-4 w-4 md:h-5 md:w-5" })
86
+ }
87
+ )
88
+ ]
51
89
  }
52
90
  );
53
91
  }
54
92
 
55
93
  // src/capture-overlay.tsx
56
- import { useEffect, useState, useCallback, useRef } from "react";
94
+ import { useEffect, useState as useState2, useCallback, useRef } from "react";
57
95
  import { toPng } from "html-to-image";
58
96
 
59
97
  // src/utils.ts
@@ -173,8 +211,7 @@ var errorListener = null;
173
211
  var rejectionListener = null;
174
212
  function startCapturing() {
175
213
  if (isCapturing) return;
176
- if (console.error.__bugReporterPatched)
177
- return;
214
+ if (console.error.__bugReporterPatched) return;
178
215
  isCapturing = true;
179
216
  capturedErrors = [];
180
217
  originalConsoleError = console.error;
@@ -365,7 +402,7 @@ function clearCapturedNetworkErrors() {
365
402
  }
366
403
 
367
404
  // src/capture-overlay.tsx
368
- import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
405
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
369
406
  function dataUrlToBlob(dataUrl) {
370
407
  var _a;
371
408
  const [header, base64] = dataUrl.split(",");
@@ -383,13 +420,13 @@ function CaptureOverlay({
383
420
  onCapture,
384
421
  onCancel
385
422
  }) {
386
- const [hoveredElement, setHoveredElement] = useState(null);
387
- const [hoveredRect, setHoveredRect] = useState(null);
388
- const [isCapturing3, setIsCapturing] = useState(false);
389
- const [isTouchMode, setIsTouchMode] = useState(false);
390
- const [selectedSection, setSelectedSection] = useState(null);
391
- const [selectedRect, setSelectedRect] = useState(null);
392
- const [selectedTarget, setSelectedTarget] = useState(null);
423
+ const [hoveredElement, setHoveredElement] = useState2(null);
424
+ const [hoveredRect, setHoveredRect] = useState2(null);
425
+ const [isCapturing3, setIsCapturing] = useState2(false);
426
+ const [isTouchMode, setIsTouchMode] = useState2(false);
427
+ const [selectedSection, setSelectedSection] = useState2(null);
428
+ const [selectedRect, setSelectedRect] = useState2(null);
429
+ const [selectedTarget, setSelectedTarget] = useState2(null);
393
430
  const overlayRef = useRef(null);
394
431
  const hoveredElementRef = useRef(null);
395
432
  const rafRef = useRef(null);
@@ -420,12 +457,21 @@ function CaptureOverlay({
420
457
  skipFonts: true
421
458
  });
422
459
  const blob = dataUrlToBlob(dataUrl);
423
- const metadata = collectMetadata(section, siteId, reporterName, reporterEmail, elementInfo);
460
+ const metadata = collectMetadata(
461
+ section,
462
+ siteId,
463
+ reporterName,
464
+ reporterEmail,
465
+ elementInfo
466
+ );
424
467
  const consoleErrors = getCapturedErrors();
425
468
  const networkErrors = getCapturedNetworkErrors();
426
469
  onCapture({ screenshot: blob, metadata, consoleErrors, networkErrors });
427
470
  } catch (err) {
428
- console.warn("Bug reporter: first capture attempt failed, retrying with simpler settings", err);
471
+ console.warn(
472
+ "Bug reporter: first capture attempt failed, retrying with simpler settings",
473
+ err
474
+ );
429
475
  try {
430
476
  const dataUrl = await toPng(section, {
431
477
  quality: 0.6,
@@ -434,13 +480,25 @@ function CaptureOverlay({
434
480
  cacheBust: true
435
481
  });
436
482
  const retryBlob = dataUrlToBlob(dataUrl);
437
- const metadata = collectMetadata(section, siteId, reporterName, reporterEmail, elementInfo);
483
+ const metadata = collectMetadata(
484
+ section,
485
+ siteId,
486
+ reporterName,
487
+ reporterEmail,
488
+ elementInfo
489
+ );
438
490
  const consoleErrors = getCapturedErrors();
439
491
  const networkErrors = getCapturedNetworkErrors();
440
492
  onCapture({ screenshot: retryBlob, metadata, consoleErrors, networkErrors });
441
493
  } catch (e) {
442
494
  console.error("Bug reporter: screenshot capture failed after retry");
443
- const metadata = collectMetadata(section, siteId, reporterName, reporterEmail, elementInfo);
495
+ const metadata = collectMetadata(
496
+ section,
497
+ siteId,
498
+ reporterName,
499
+ reporterEmail,
500
+ elementInfo
501
+ );
444
502
  const consoleErrors = getCapturedErrors();
445
503
  const networkErrors = getCapturedNetworkErrors();
446
504
  onCapture({
@@ -571,31 +629,41 @@ function CaptureOverlay({
571
629
  document.removeEventListener("click", handleClick, true);
572
630
  if (rafRef.current) cancelAnimationFrame(rafRef.current);
573
631
  };
574
- }, [isActive, isTouchMode, handleMouseMove, handleClick, handleTouchEnd, handlePointerDown, handleKeyDown, handleScroll]);
632
+ }, [
633
+ isActive,
634
+ isTouchMode,
635
+ handleMouseMove,
636
+ handleClick,
637
+ handleTouchEnd,
638
+ handlePointerDown,
639
+ handleKeyDown,
640
+ handleScroll
641
+ ]);
575
642
  const highlightRect = isTouchMode ? selectedRect : hoveredRect;
576
643
  const showHighlight = isTouchMode ? !!selectedSection : !!hoveredElement && !!hoveredRect;
577
644
  if (!isActive) return null;
578
- return /* @__PURE__ */ jsxs(Fragment, { children: [
645
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
579
646
  /* @__PURE__ */ jsx2(
580
647
  "div",
581
648
  {
582
649
  "data-bug-reporter": true,
583
650
  role: "alert",
584
651
  "aria-live": "assertive",
585
- 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",
586
- children: isTouchMode ? /* @__PURE__ */ jsxs(Fragment, { children: [
652
+ 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",
653
+ children: isTouchMode ? /* @__PURE__ */ jsxs2(Fragment, { children: [
587
654
  /* @__PURE__ */ jsx2("span", { children: "Tap the section with the bug" }),
588
655
  /* @__PURE__ */ jsx2(
589
656
  "button",
590
657
  {
591
658
  onClick: onCancel,
592
- className: "px-3 py-1 min-h-[44px] bg-white/20 rounded-md text-sm font-medium",
659
+ className: "min-h-[44px] rounded-md bg-white/20 px-3 py-1 text-sm font-medium",
593
660
  children: "Cancel"
594
661
  }
595
662
  )
596
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
597
- "Click on the section with the bug. Press ",
598
- /* @__PURE__ */ jsx2("kbd", { className: "px-1.5 py-0.5 bg-indigo-800 rounded text-xs mx-1", children: "Esc" }),
663
+ ] }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
664
+ "Click on the section with the bug. Press",
665
+ " ",
666
+ /* @__PURE__ */ jsx2("kbd", { className: "mx-1 rounded bg-indigo-800 px-1.5 py-0.5 text-xs", children: "Esc" }),
599
667
  " to cancel."
600
668
  ] })
601
669
  }
@@ -605,7 +673,7 @@ function CaptureOverlay({
605
673
  {
606
674
  ref: overlayRef,
607
675
  "data-bug-reporter": true,
608
- className: "fixed pointer-events-none z-[9998] border-2 border-indigo-500 rounded-sm transition-all duration-150 ease-out",
676
+ className: "pointer-events-none fixed z-[9998] rounded-sm border-2 border-indigo-500 transition-all duration-150 ease-out",
609
677
  style: {
610
678
  top: highlightRect.top - 2,
611
679
  left: highlightRect.left - 2,
@@ -619,11 +687,11 @@ function CaptureOverlay({
619
687
  "div",
620
688
  {
621
689
  "data-bug-reporter": true,
622
- className: "fixed bottom-0 left-0 right-0 z-[10000] bg-white border-t border-gray-200 shadow-lg",
690
+ className: "fixed right-0 bottom-0 left-0 z-[10000] border-t border-gray-200 bg-white shadow-lg",
623
691
  style: { paddingBottom: "env(safe-area-inset-bottom, 0px)" },
624
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-3 gap-3", children: [
625
- /* @__PURE__ */ jsx2("span", { className: "text-sm font-medium text-gray-900 truncate", children: "Capture this section?" }),
626
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2 shrink-0", children: [
692
+ children: /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between gap-3 px-4 py-3", children: [
693
+ /* @__PURE__ */ jsx2("span", { className: "truncate text-sm font-medium text-gray-900", children: "Capture this section?" }),
694
+ /* @__PURE__ */ jsxs2("div", { className: "flex shrink-0 gap-2", children: [
627
695
  /* @__PURE__ */ jsx2(
628
696
  "button",
629
697
  {
@@ -633,7 +701,7 @@ function CaptureOverlay({
633
701
  setSelectedTarget(null);
634
702
  touchCoordsRef.current = null;
635
703
  },
636
- className: "px-4 min-h-[44px] rounded-md border border-gray-300 text-sm font-medium text-gray-700",
704
+ className: "min-h-[44px] rounded-md border border-gray-300 px-4 text-sm font-medium text-gray-700",
637
705
  children: "Cancel"
638
706
  }
639
707
  ),
@@ -641,7 +709,7 @@ function CaptureOverlay({
641
709
  "button",
642
710
  {
643
711
  onClick: handleConfirmCapture,
644
- className: "px-4 min-h-[44px] rounded-md bg-indigo-600 text-white text-sm font-medium",
712
+ className: "min-h-[44px] rounded-md bg-indigo-600 px-4 text-sm font-medium text-white",
645
713
  children: "Capture"
646
714
  }
647
715
  )
@@ -654,9 +722,9 @@ function CaptureOverlay({
654
722
  }
655
723
 
656
724
  // src/report-modal.tsx
657
- import { useState as useState2, useRef as useRef2, useEffect as useEffect2, useCallback as useCallback2, useMemo } from "react";
725
+ import { useState as useState3, useRef as useRef2, useEffect as useEffect2, useCallback as useCallback2, useMemo } from "react";
658
726
  import { X, Send, Loader2, CheckCircle2 } from "lucide-react";
659
- import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
727
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
660
728
  function ReportModal({
661
729
  isOpen,
662
730
  captureResult,
@@ -665,13 +733,13 @@ function ReportModal({
665
733
  user,
666
734
  onClose
667
735
  }) {
668
- const [messages, setMessages] = useState2([]);
669
- const [input, setInput] = useState2("");
670
- const [isLoading, setIsLoading] = useState2(false);
671
- const [modalState, setModalState] = useState2("chatting");
672
- const [reportId, setReportId] = useState2(null);
673
- const [screenshotUrl, setScreenshotUrl] = useState2(null);
674
- const [errorMessage, setErrorMessage] = useState2(null);
736
+ const [messages, setMessages] = useState3([]);
737
+ const [input, setInput] = useState3("");
738
+ const [isLoading, setIsLoading] = useState3(false);
739
+ const [modalState, setModalState] = useState3("chatting");
740
+ const [reportId, setReportId] = useState3(null);
741
+ const [screenshotUrl, setScreenshotUrl] = useState3(null);
742
+ const [errorMessage, setErrorMessage] = useState3(null);
675
743
  const chatEndRef = useRef2(null);
676
744
  const inputRef = useRef2(null);
677
745
  const hasInitRef = useRef2(false);
@@ -715,7 +783,9 @@ function ReportModal({
715
783
  })
716
784
  });
717
785
  if (response.status === 401) {
718
- console.error("Bug reporter: invalid or missing API key. Check your BugReporter apiKey prop.");
786
+ console.error(
787
+ "Bug reporter: invalid or missing API key. Check your BugReporter apiKey prop."
788
+ );
719
789
  setMessages([
720
790
  {
721
791
  role: "assistant",
@@ -812,9 +882,7 @@ function ReportModal({
812
882
  setModalState("submitted");
813
883
  } catch (err) {
814
884
  console.error("Bug reporter: failed to submit report", err);
815
- setErrorMessage(
816
- err instanceof Error ? err.message : "Failed to submit report"
817
- );
885
+ setErrorMessage(err instanceof Error ? err.message : "Failed to submit report");
818
886
  setModalState("error");
819
887
  }
820
888
  },
@@ -827,10 +895,7 @@ function ReportModal({
827
895
  if (!input.trim() || isLoading || !captureResult) return;
828
896
  const userMessage = input.trim();
829
897
  setInput("");
830
- const newMessages = [
831
- ...messages,
832
- { role: "user", content: userMessage }
833
- ];
898
+ const newMessages = [...messages, { role: "user", content: userMessage }];
834
899
  setMessages(newMessages);
835
900
  setIsLoading(true);
836
901
  try {
@@ -846,7 +911,9 @@ function ReportModal({
846
911
  })
847
912
  });
848
913
  if (response.status === 401) {
849
- console.error("Bug reporter: invalid or missing API key. Check your BugReporter apiKey prop.");
914
+ console.error(
915
+ "Bug reporter: invalid or missing API key. Check your BugReporter apiKey prop."
916
+ );
850
917
  setMessages([
851
918
  ...newMessages,
852
919
  {
@@ -858,10 +925,7 @@ function ReportModal({
858
925
  }
859
926
  if (!response.ok) throw new Error("Failed to get AI response");
860
927
  const data = await response.json();
861
- setMessages([
862
- ...newMessages,
863
- { role: "assistant", content: data.message }
864
- ]);
928
+ setMessages([...newMessages, { role: "assistant", content: data.message }]);
865
929
  if (data.readyToSubmit && data.structuredReport) {
866
930
  await submitReport(
867
931
  [...newMessages, { role: "assistant", content: data.message }],
@@ -908,113 +972,125 @@ function ReportModal({
908
972
  onClick: (e) => {
909
973
  if (e.target === e.currentTarget) handleClose();
910
974
  },
911
- children: /* @__PURE__ */ jsxs2(
975
+ children: /* @__PURE__ */ jsxs3(
912
976
  "div",
913
977
  {
914
978
  className: cn(
915
- "bg-white dark:bg-gray-950 rounded-xl shadow-2xl border border-gray-200 dark:border-gray-800 flex flex-col overflow-hidden",
916
- "w-full max-w-lg mx-4",
917
- "max-[768px]:mx-0 max-[768px]:rounded-none max-[768px]:max-w-none max-[768px]:h-full",
979
+ "flex flex-col overflow-hidden rounded-xl border border-gray-200 bg-white shadow-2xl dark:border-gray-800 dark:bg-gray-950",
980
+ "mx-4 w-full max-w-lg",
981
+ "max-[768px]:mx-0 max-[768px]:h-full max-[768px]:max-w-none max-[768px]:rounded-none",
918
982
  "min-[769px]:max-h-[85vh]"
919
983
  ),
920
984
  children: [
921
- /* @__PURE__ */ jsxs2("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: [
922
- /* @__PURE__ */ jsxs2("div", { children: [
923
- /* @__PURE__ */ jsx3("h2", { className: "font-semibold text-sm text-gray-900 dark:text-gray-100", children: "Bug Report" }),
985
+ /* @__PURE__ */ jsxs3("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: [
986
+ /* @__PURE__ */ jsxs3("div", { children: [
987
+ /* @__PURE__ */ jsx3("h2", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: "Bug Report" }),
924
988
  /* @__PURE__ */ jsx3("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: siteId })
925
989
  ] }),
926
990
  /* @__PURE__ */ jsx3(
927
991
  "button",
928
992
  {
929
993
  onClick: handleClose,
930
- className: "p-1.5 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors",
994
+ className: "rounded-md p-1.5 transition-colors hover:bg-gray-100 dark:hover:bg-gray-800",
931
995
  "aria-label": "Close",
932
996
  children: /* @__PURE__ */ jsx3(X, { className: "h-4 w-4 text-gray-600 dark:text-gray-400" })
933
997
  }
934
998
  )
935
999
  ] }),
936
- screenshotUrl && /* @__PURE__ */ jsx3("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__ */ jsx3(
1000
+ screenshotUrl && /* @__PURE__ */ jsx3("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__ */ jsx3(
937
1001
  "img",
938
1002
  {
939
1003
  src: screenshotUrl,
940
1004
  alt: "Captured section",
941
- className: "w-full max-h-40 object-contain rounded-md border border-gray-200 dark:border-gray-700"
1005
+ className: "max-h-40 w-full rounded-md border border-gray-200 object-contain dark:border-gray-700"
942
1006
  }
943
1007
  ) }),
944
- /* @__PURE__ */ jsx3("div", { className: "flex-1 overflow-y-auto px-4 py-3 space-y-3 min-h-0", children: modalState === "submitted" ? /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center justify-center py-8 text-center", children: [
945
- /* @__PURE__ */ jsx3(CheckCircle2, { className: "h-12 w-12 text-green-500 mb-3" }),
946
- /* @__PURE__ */ jsx3("h3", { className: "font-semibold text-lg text-gray-900 dark:text-gray-100", children: "Report Submitted" }),
947
- /* @__PURE__ */ jsxs2("p", { className: "text-sm text-gray-500 dark:text-gray-400 mt-1", children: [
948
- "Reference: ",
949
- /* @__PURE__ */ jsx3("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) })
1008
+ /* @__PURE__ */ jsx3("div", { className: "min-h-0 flex-1 space-y-3 overflow-y-auto px-4 py-3", children: modalState === "submitted" ? /* @__PURE__ */ jsxs3("div", { className: "flex flex-col items-center justify-center py-8 text-center", children: [
1009
+ /* @__PURE__ */ jsx3(CheckCircle2, { className: "mb-3 h-12 w-12 text-green-500" }),
1010
+ /* @__PURE__ */ jsx3("h3", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100", children: "Report Submitted" }),
1011
+ /* @__PURE__ */ jsxs3("p", { className: "mt-1 text-sm text-gray-500 dark:text-gray-400", children: [
1012
+ "Reference:",
1013
+ " ",
1014
+ /* @__PURE__ */ jsx3("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) })
950
1015
  ] }),
951
- /* @__PURE__ */ jsx3("p", { className: "text-sm text-gray-500 dark:text-gray-400 mt-2", children: "Thanks for the report \u2014 we'll look into it." }),
1016
+ /* @__PURE__ */ jsx3("p", { className: "mt-2 text-sm text-gray-500 dark:text-gray-400", children: "Thanks for the report \u2014 we'll look into it." }),
952
1017
  /* @__PURE__ */ jsx3(
953
1018
  "button",
954
1019
  {
955
1020
  onClick: handleClose,
956
- className: "mt-4 px-4 py-2 bg-indigo-600 text-white rounded-md text-sm hover:bg-indigo-700 transition-colors",
1021
+ className: "mt-4 rounded-md bg-indigo-600 px-4 py-2 text-sm text-white transition-colors hover:bg-indigo-700",
957
1022
  children: "Done"
958
1023
  }
959
1024
  )
960
- ] }) : modalState === "error" ? /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center justify-center py-8 text-center", children: [
961
- /* @__PURE__ */ jsx3(X, { className: "h-12 w-12 text-red-500 mb-3" }),
962
- /* @__PURE__ */ jsx3("h3", { className: "font-semibold text-lg text-gray-900 dark:text-gray-100", children: "Submission Failed" }),
963
- /* @__PURE__ */ jsx3("p", { className: "text-sm text-gray-500 dark:text-gray-400 mt-1", children: errorMessage || "Something went wrong. Please try again." }),
1025
+ ] }) : modalState === "error" ? /* @__PURE__ */ jsxs3("div", { className: "flex flex-col items-center justify-center py-8 text-center", children: [
1026
+ /* @__PURE__ */ jsx3(X, { className: "mb-3 h-12 w-12 text-red-500" }),
1027
+ /* @__PURE__ */ jsx3("h3", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100", children: "Submission Failed" }),
1028
+ /* @__PURE__ */ jsx3("p", { className: "mt-1 text-sm text-gray-500 dark:text-gray-400", children: errorMessage || "Something went wrong. Please try again." }),
964
1029
  /* @__PURE__ */ jsx3(
965
1030
  "button",
966
1031
  {
967
1032
  onClick: () => setModalState("chatting"),
968
- className: "mt-4 px-4 py-2 bg-indigo-600 text-white rounded-md text-sm hover:bg-indigo-700 transition-colors",
1033
+ className: "mt-4 rounded-md bg-indigo-600 px-4 py-2 text-sm text-white transition-colors hover:bg-indigo-700",
969
1034
  children: "Try Again"
970
1035
  }
971
1036
  )
972
- ] }) : modalState === "submitting" ? /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center justify-center py-8", children: [
973
- /* @__PURE__ */ jsx3(Loader2, { className: "h-8 w-8 animate-spin text-indigo-500 mb-3" }),
1037
+ ] }) : modalState === "submitting" ? /* @__PURE__ */ jsxs3("div", { className: "flex flex-col items-center justify-center py-8", children: [
1038
+ /* @__PURE__ */ jsx3(Loader2, { className: "mb-3 h-8 w-8 animate-spin text-indigo-500" }),
974
1039
  /* @__PURE__ */ jsx3("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: "Submitting your report..." })
975
- ] }) : /* @__PURE__ */ jsxs2(Fragment2, { children: [
1040
+ ] }) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
976
1041
  (captureResult == null ? void 0 : captureResult.screenshot.size) === 0 && /* @__PURE__ */ jsx3("p", { className: "text-xs text-amber-600", children: "Screenshot could not be captured. Please describe the visual issue in detail." }),
977
1042
  messages.map((msg, i) => /* @__PURE__ */ jsx3(
978
1043
  "div",
979
1044
  {
980
1045
  className: cn(
981
1046
  "text-sm leading-relaxed",
982
- 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"
1047
+ 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"
983
1048
  ),
984
1049
  children: msg.content
985
1050
  },
986
1051
  i
987
1052
  )),
988
- isLoading && /* @__PURE__ */ jsxs2("div", { className: "bg-gray-100/50 dark:bg-gray-800/50 rounded-lg p-3 flex items-center gap-2", children: [
1053
+ isLoading && /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2 rounded-lg bg-gray-100/50 p-3 dark:bg-gray-800/50", children: [
989
1054
  /* @__PURE__ */ jsx3(Loader2, { className: "h-3.5 w-3.5 animate-spin text-gray-500" }),
990
1055
  /* @__PURE__ */ jsx3("span", { className: "text-sm text-gray-500 dark:text-gray-400", children: "Thinking..." })
991
1056
  ] }),
992
1057
  /* @__PURE__ */ jsx3("div", { ref: chatEndRef })
993
1058
  ] }) }),
994
- modalState === "chatting" && /* @__PURE__ */ jsxs2("div", { className: "border-t border-gray-200 dark:border-gray-800 px-4 py-3", children: [
995
- /* @__PURE__ */ jsxs2("div", { className: "flex gap-2", children: [
996
- /* @__PURE__ */ jsx3(
997
- "textarea",
998
- {
999
- ref: inputRef,
1000
- value: input,
1001
- onChange: (e) => setInput(e.target.value),
1002
- onKeyDown: handleKeyDown,
1003
- placeholder: "Describe what's wrong...",
1004
- rows: 2,
1005
- 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",
1006
- disabled: isLoading
1007
- }
1008
- ),
1009
- /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-1", children: [
1010
- /* @__PURE__ */ jsx3(
1059
+ modalState === "chatting" && /* @__PURE__ */ jsx3("div", { className: "border-t border-gray-200 px-4 py-3 dark:border-gray-800", children: /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-2", children: [
1060
+ /* @__PURE__ */ jsx3(
1061
+ "textarea",
1062
+ {
1063
+ ref: inputRef,
1064
+ value: input,
1065
+ onChange: (e) => setInput(e.target.value),
1066
+ onKeyDown: handleKeyDown,
1067
+ placeholder: "Describe what's wrong...",
1068
+ rows: 2,
1069
+ 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",
1070
+ disabled: isLoading
1071
+ }
1072
+ ),
1073
+ /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-2", children: [
1074
+ captureResult && (captureResult.consoleErrors.length > 0 || captureResult.networkErrors.length > 0) && /* @__PURE__ */ jsxs3("p", { className: "flex-1 text-xs text-amber-600", children: [
1075
+ [
1076
+ captureResult.consoleErrors.length > 0 ? `${captureResult.consoleErrors.length} console error${captureResult.consoleErrors.length !== 1 ? "s" : ""}` : null,
1077
+ captureResult.networkErrors.length > 0 ? `${captureResult.networkErrors.length} failed request${captureResult.networkErrors.length !== 1 ? "s" : ""}` : null
1078
+ ].filter(Boolean).join(" + "),
1079
+ " ",
1080
+ "captured \u2014 these will be included in the report."
1081
+ ] }),
1082
+ /* @__PURE__ */ jsxs3("div", { className: "flex justify-end gap-2", children: [
1083
+ /* @__PURE__ */ jsxs3(
1011
1084
  "button",
1012
1085
  {
1013
1086
  onClick: sendMessage,
1014
1087
  disabled: !input.trim() || isLoading,
1015
- className: "p-2 rounded-md bg-indigo-600 text-white hover:bg-indigo-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors",
1088
+ 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",
1016
1089
  title: "Send message",
1017
- children: /* @__PURE__ */ jsx3(Send, { className: "h-4 w-4" })
1090
+ children: [
1091
+ /* @__PURE__ */ jsx3(Send, { className: "h-4 w-4" }),
1092
+ /* @__PURE__ */ jsx3("span", { className: "text-sm font-medium", children: "Send message" })
1093
+ ]
1018
1094
  }
1019
1095
  ),
1020
1096
  messages.length >= 2 && /* @__PURE__ */ jsx3(
@@ -1022,22 +1098,14 @@ function ReportModal({
1022
1098
  {
1023
1099
  onClick: handleManualSubmit,
1024
1100
  disabled: isLoading,
1025
- 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",
1101
+ 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",
1026
1102
  title: "Submit report now",
1027
- children: "Submit"
1103
+ children: /* @__PURE__ */ jsx3("span", { className: "text-sm font-medium", children: "Submit report" })
1028
1104
  }
1029
1105
  )
1030
1106
  ] })
1031
- ] }),
1032
- captureResult && (captureResult.consoleErrors.length > 0 || captureResult.networkErrors.length > 0) && /* @__PURE__ */ jsxs2("p", { className: "text-xs text-amber-600 mt-1.5", children: [
1033
- [
1034
- captureResult.consoleErrors.length > 0 ? `${captureResult.consoleErrors.length} console error${captureResult.consoleErrors.length !== 1 ? "s" : ""}` : null,
1035
- captureResult.networkErrors.length > 0 ? `${captureResult.networkErrors.length} failed request${captureResult.networkErrors.length !== 1 ? "s" : ""}` : null
1036
- ].filter(Boolean).join(" + "),
1037
- " ",
1038
- "captured \u2014 these will be included in the report."
1039
1107
  ] })
1040
- ] })
1108
+ ] }) })
1041
1109
  ]
1042
1110
  }
1043
1111
  )
@@ -1046,16 +1114,18 @@ function ReportModal({
1046
1114
  }
1047
1115
 
1048
1116
  // src/bug-reporter.tsx
1049
- import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1117
+ import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1050
1118
  function JarveBugReporter({
1051
1119
  apiUrl,
1052
1120
  apiKey,
1053
1121
  user,
1122
+ buttonPosition,
1054
1123
  children
1055
1124
  }) {
1056
- const [captureMode, setCaptureMode] = useState3(false);
1057
- const [captureResult, setCaptureResult] = useState3(null);
1058
- const [showModal, setShowModal] = useState3(false);
1125
+ const safeApiKey = apiKey || "";
1126
+ const [captureMode, setCaptureMode] = useState4(false);
1127
+ const [captureResult, setCaptureResult] = useState4(null);
1128
+ const [showModal, setShowModal] = useState4(false);
1059
1129
  useEffect3(() => {
1060
1130
  startCapturing();
1061
1131
  startNetworkCapture();
@@ -1081,12 +1151,19 @@ function JarveBugReporter({
1081
1151
  clearCapturedErrors();
1082
1152
  clearCapturedNetworkErrors();
1083
1153
  }, []);
1084
- const siteId = apiKey.startsWith("brk_") ? apiKey.slice(4, 12) : "external";
1154
+ const siteId = safeApiKey.startsWith("brk_") ? safeApiKey.slice(4, 12) : "external";
1085
1155
  const reporterName = (user == null ? void 0 : user.name) || "Anonymous";
1086
1156
  const reporterEmail = (user == null ? void 0 : user.email) || "unknown@external";
1087
- return /* @__PURE__ */ jsxs3(Fragment3, { children: [
1157
+ return /* @__PURE__ */ jsxs4(Fragment3, { children: [
1088
1158
  children,
1089
- /* @__PURE__ */ jsx4(FloatingButton, { isActive: captureMode, onClick: toggleCaptureMode }),
1159
+ /* @__PURE__ */ jsx4(
1160
+ FloatingButton,
1161
+ {
1162
+ isActive: captureMode,
1163
+ onClick: toggleCaptureMode,
1164
+ position: buttonPosition
1165
+ }
1166
+ ),
1090
1167
  /* @__PURE__ */ jsx4(
1091
1168
  CaptureOverlay,
1092
1169
  {