@jarve/bug-reporter 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,6 +14,7 @@ Bug reporter widget for Next.js apps. Reports flow to your JARVE Agency dashboar
14
14
  ### 1. Register your site
15
15
 
16
16
  Go to your JARVE Agency dashboard at `/admin/bug-reports/sites` and:
17
+
17
18
  - Click **Add Site**
18
19
  - Enter your site name and color
19
20
  - Click **Generate API Key**
@@ -34,7 +35,7 @@ The widget uses Tailwind CSS classes. Add the package to your Tailwind content c
34
35
  **Tailwind v4** (add `@source` in your CSS file):
35
36
 
36
37
  ```css
37
- @source "../node_modules/@jarve/bug-reporter/dist";
38
+ @source '../node_modules/@jarve/bug-reporter/dist';
38
39
  ```
39
40
 
40
41
  **Tailwind v3** (add to `content` in `tailwind.config.ts`):
@@ -72,12 +73,12 @@ Click the blue bug icon (bottom-right) to report bugs. They'll appear in your JA
72
73
 
73
74
  ## Props
74
75
 
75
- | Prop | Type | Required | Description |
76
- |------|------|----------|-------------|
77
- | `apiUrl` | `string` | Yes | Base URL for the external bug reporter API |
78
- | `apiKey` | `string` | Yes | Your site's API key (starts with `brk_`) |
79
- | `user` | `{ name: string, email: string }` | No | User info. If omitted, the AI will ask during conversation |
80
- | `children` | `ReactNode` | Yes | Your app content |
76
+ | Prop | Type | Required | Description |
77
+ | ---------- | --------------------------------- | -------- | ---------------------------------------------------------- |
78
+ | `apiUrl` | `string` | Yes | Base URL for the external bug reporter API |
79
+ | `apiKey` | `string` | Yes | Your site's API key (starts with `brk_`) |
80
+ | `user` | `{ name: string, email: string }` | No | User info. If omitted, the AI will ask during conversation |
81
+ | `children` | `ReactNode` | Yes | Your app content |
81
82
 
82
83
  ## What gets captured
83
84
 
package/dist/index.d.mts CHANGED
@@ -1,5 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
 
3
+ type FloatingButtonPosition = 'left' | 'right';
4
+
3
5
  interface BugReporterUser {
4
6
  name: string;
5
7
  email: string;
@@ -14,10 +16,12 @@ interface JarveBugReporterProps {
14
16
  apiUrl: string;
15
17
  /** API key for your site (starts with "brk_") */
16
18
  apiKey: string;
19
+ /** Optional position for the floating button (default: 'right') */
20
+ buttonPosition?: FloatingButtonPosition;
17
21
  /** Optional user info. If not provided, the AI will ask for name/email during the conversation. */
18
22
  user?: BugReporterUser;
19
23
  children: React.ReactNode;
20
24
  }
21
- declare function JarveBugReporter({ apiUrl, apiKey, user, children, }: JarveBugReporterProps): react_jsx_runtime.JSX.Element;
25
+ declare function JarveBugReporter({ apiUrl, apiKey, user, buttonPosition, children, }: JarveBugReporterProps): react_jsx_runtime.JSX.Element;
22
26
 
23
27
  export { type BugReporterApiConfig, type BugReporterUser, JarveBugReporter };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
 
3
+ type FloatingButtonPosition = 'left' | 'right';
4
+
3
5
  interface BugReporterUser {
4
6
  name: string;
5
7
  email: string;
@@ -14,10 +16,12 @@ interface JarveBugReporterProps {
14
16
  apiUrl: string;
15
17
  /** API key for your site (starts with "brk_") */
16
18
  apiKey: string;
19
+ /** Optional position for the floating button (default: 'right') */
20
+ buttonPosition?: FloatingButtonPosition;
17
21
  /** Optional user info. If not provided, the AI will ask for name/email during the conversation. */
18
22
  user?: BugReporterUser;
19
23
  children: React.ReactNode;
20
24
  }
21
- declare function JarveBugReporter({ apiUrl, apiKey, user, children, }: JarveBugReporterProps): react_jsx_runtime.JSX.Element;
25
+ declare function JarveBugReporter({ apiUrl, apiKey, user, buttonPosition, children, }: JarveBugReporterProps): react_jsx_runtime.JSX.Element;
22
26
 
23
27
  export { type BugReporterApiConfig, type BugReporterUser, JarveBugReporter };
package/dist/index.js CHANGED
@@ -57,16 +57,21 @@ function cn(...inputs) {
57
57
 
58
58
  // src/floating-button.tsx
59
59
  var import_jsx_runtime = require("react/jsx-runtime");
60
- function FloatingButton({ isActive, onClick }) {
60
+ function FloatingButton({ isActive, onClick, position = "right" }) {
61
+ const sideClasses = position === "left" ? "left-4 md:left-6" : "right-4 md:right-6";
61
62
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
62
63
  "button",
63
64
  {
64
65
  onClick,
65
66
  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"
67
+ "fixed z-[9999] flex items-center justify-center rounded-full shadow-lg transition-all duration-200",
68
+ "hover:scale-110 focus:ring-2 focus:ring-offset-2 focus:outline-none",
69
+ // size + vertical position
70
+ "bottom-4 h-11 w-11 md:bottom-6 md:h-12 md:w-12",
71
+ // horizontal side
72
+ sideClasses,
73
+ // active vs idle colors
74
+ 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"
70
75
  ),
71
76
  title: isActive ? "Cancel bug capture" : "Report a bug",
72
77
  "aria-label": isActive ? "Cancel bug capture" : "Report a bug",
@@ -196,8 +201,7 @@ var errorListener = null;
196
201
  var rejectionListener = null;
197
202
  function startCapturing() {
198
203
  if (isCapturing) return;
199
- if (console.error.__bugReporterPatched)
200
- return;
204
+ if (console.error.__bugReporterPatched) return;
201
205
  isCapturing = true;
202
206
  capturedErrors = [];
203
207
  originalConsoleError = console.error;
@@ -443,12 +447,21 @@ function CaptureOverlay({
443
447
  skipFonts: true
444
448
  });
445
449
  const blob = dataUrlToBlob(dataUrl);
446
- const metadata = collectMetadata(section, siteId, reporterName, reporterEmail, elementInfo);
450
+ const metadata = collectMetadata(
451
+ section,
452
+ siteId,
453
+ reporterName,
454
+ reporterEmail,
455
+ elementInfo
456
+ );
447
457
  const consoleErrors = getCapturedErrors();
448
458
  const networkErrors = getCapturedNetworkErrors();
449
459
  onCapture({ screenshot: blob, metadata, consoleErrors, networkErrors });
450
460
  } catch (err) {
451
- console.warn("Bug reporter: first capture attempt failed, retrying with simpler settings", err);
461
+ console.warn(
462
+ "Bug reporter: first capture attempt failed, retrying with simpler settings",
463
+ err
464
+ );
452
465
  try {
453
466
  const dataUrl = await (0, import_html_to_image.toPng)(section, {
454
467
  quality: 0.6,
@@ -457,13 +470,25 @@ function CaptureOverlay({
457
470
  cacheBust: true
458
471
  });
459
472
  const retryBlob = dataUrlToBlob(dataUrl);
460
- const metadata = collectMetadata(section, siteId, reporterName, reporterEmail, elementInfo);
473
+ const metadata = collectMetadata(
474
+ section,
475
+ siteId,
476
+ reporterName,
477
+ reporterEmail,
478
+ elementInfo
479
+ );
461
480
  const consoleErrors = getCapturedErrors();
462
481
  const networkErrors = getCapturedNetworkErrors();
463
482
  onCapture({ screenshot: retryBlob, metadata, consoleErrors, networkErrors });
464
483
  } catch (e) {
465
484
  console.error("Bug reporter: screenshot capture failed after retry");
466
- const metadata = collectMetadata(section, siteId, reporterName, reporterEmail, elementInfo);
485
+ const metadata = collectMetadata(
486
+ section,
487
+ siteId,
488
+ reporterName,
489
+ reporterEmail,
490
+ elementInfo
491
+ );
467
492
  const consoleErrors = getCapturedErrors();
468
493
  const networkErrors = getCapturedNetworkErrors();
469
494
  onCapture({
@@ -594,7 +619,16 @@ function CaptureOverlay({
594
619
  document.removeEventListener("click", handleClick, true);
595
620
  if (rafRef.current) cancelAnimationFrame(rafRef.current);
596
621
  };
597
- }, [isActive, isTouchMode, handleMouseMove, handleClick, handleTouchEnd, handlePointerDown, handleKeyDown, handleScroll]);
622
+ }, [
623
+ isActive,
624
+ isTouchMode,
625
+ handleMouseMove,
626
+ handleClick,
627
+ handleTouchEnd,
628
+ handlePointerDown,
629
+ handleKeyDown,
630
+ handleScroll
631
+ ]);
598
632
  const highlightRect = isTouchMode ? selectedRect : hoveredRect;
599
633
  const showHighlight = isTouchMode ? !!selectedSection : !!hoveredElement && !!hoveredRect;
600
634
  if (!isActive) return null;
@@ -605,20 +639,21 @@ function CaptureOverlay({
605
639
  "data-bug-reporter": true,
606
640
  role: "alert",
607
641
  "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",
642
+ 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
643
  children: isTouchMode ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
610
644
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "Tap the section with the bug" }),
611
645
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
612
646
  "button",
613
647
  {
614
648
  onClick: onCancel,
615
- className: "px-3 py-1 min-h-[44px] bg-white/20 rounded-md text-sm font-medium",
649
+ className: "min-h-[44px] rounded-md bg-white/20 px-3 py-1 text-sm font-medium",
616
650
  children: "Cancel"
617
651
  }
618
652
  )
619
653
  ] }) : /* @__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" }),
654
+ "Click on the section with the bug. Press",
655
+ " ",
656
+ /* @__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
657
  " to cancel."
623
658
  ] })
624
659
  }
@@ -628,7 +663,7 @@ function CaptureOverlay({
628
663
  {
629
664
  ref: overlayRef,
630
665
  "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",
666
+ className: "pointer-events-none fixed z-[9998] rounded-sm border-2 border-indigo-500 transition-all duration-150 ease-out",
632
667
  style: {
633
668
  top: highlightRect.top - 2,
634
669
  left: highlightRect.left - 2,
@@ -642,11 +677,11 @@ function CaptureOverlay({
642
677
  "div",
643
678
  {
644
679
  "data-bug-reporter": true,
645
- className: "fixed bottom-0 left-0 right-0 z-[10000] bg-white border-t border-gray-200 shadow-lg",
680
+ className: "fixed right-0 bottom-0 left-0 z-[10000] border-t border-gray-200 bg-white shadow-lg",
646
681
  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: [
682
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between gap-3 px-4 py-3", children: [
683
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "truncate text-sm font-medium text-gray-900", children: "Capture this section?" }),
684
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex shrink-0 gap-2", children: [
650
685
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
651
686
  "button",
652
687
  {
@@ -656,7 +691,7 @@ function CaptureOverlay({
656
691
  setSelectedTarget(null);
657
692
  touchCoordsRef.current = null;
658
693
  },
659
- className: "px-4 min-h-[44px] rounded-md border border-gray-300 text-sm font-medium text-gray-700",
694
+ className: "min-h-[44px] rounded-md border border-gray-300 px-4 text-sm font-medium text-gray-700",
660
695
  children: "Cancel"
661
696
  }
662
697
  ),
@@ -664,7 +699,7 @@ function CaptureOverlay({
664
699
  "button",
665
700
  {
666
701
  onClick: handleConfirmCapture,
667
- className: "px-4 min-h-[44px] rounded-md bg-indigo-600 text-white text-sm font-medium",
702
+ className: "min-h-[44px] rounded-md bg-indigo-600 px-4 text-sm font-medium text-white",
668
703
  children: "Capture"
669
704
  }
670
705
  )
@@ -738,7 +773,9 @@ function ReportModal({
738
773
  })
739
774
  });
740
775
  if (response.status === 401) {
741
- console.error("Bug reporter: invalid or missing API key. Check your BugReporter apiKey prop.");
776
+ console.error(
777
+ "Bug reporter: invalid or missing API key. Check your BugReporter apiKey prop."
778
+ );
742
779
  setMessages([
743
780
  {
744
781
  role: "assistant",
@@ -835,9 +872,7 @@ function ReportModal({
835
872
  setModalState("submitted");
836
873
  } catch (err) {
837
874
  console.error("Bug reporter: failed to submit report", err);
838
- setErrorMessage(
839
- err instanceof Error ? err.message : "Failed to submit report"
840
- );
875
+ setErrorMessage(err instanceof Error ? err.message : "Failed to submit report");
841
876
  setModalState("error");
842
877
  }
843
878
  },
@@ -850,10 +885,7 @@ function ReportModal({
850
885
  if (!input.trim() || isLoading || !captureResult) return;
851
886
  const userMessage = input.trim();
852
887
  setInput("");
853
- const newMessages = [
854
- ...messages,
855
- { role: "user", content: userMessage }
856
- ];
888
+ const newMessages = [...messages, { role: "user", content: userMessage }];
857
889
  setMessages(newMessages);
858
890
  setIsLoading(true);
859
891
  try {
@@ -869,7 +901,9 @@ function ReportModal({
869
901
  })
870
902
  });
871
903
  if (response.status === 401) {
872
- console.error("Bug reporter: invalid or missing API key. Check your BugReporter apiKey prop.");
904
+ console.error(
905
+ "Bug reporter: invalid or missing API key. Check your BugReporter apiKey prop."
906
+ );
873
907
  setMessages([
874
908
  ...newMessages,
875
909
  {
@@ -881,10 +915,7 @@ function ReportModal({
881
915
  }
882
916
  if (!response.ok) throw new Error("Failed to get AI response");
883
917
  const data = await response.json();
884
- setMessages([
885
- ...newMessages,
886
- { role: "assistant", content: data.message }
887
- ]);
918
+ setMessages([...newMessages, { role: "assistant", content: data.message }]);
888
919
  if (data.readyToSubmit && data.structuredReport) {
889
920
  await submitReport(
890
921
  [...newMessages, { role: "assistant", content: data.message }],
@@ -935,65 +966,66 @@ function ReportModal({
935
966
  "div",
936
967
  {
937
968
  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",
969
+ "flex flex-col overflow-hidden rounded-xl border border-gray-200 bg-white shadow-2xl dark:border-gray-800 dark:bg-gray-950",
970
+ "mx-4 w-full max-w-lg",
971
+ "max-[768px]:mx-0 max-[768px]:h-full max-[768px]:max-w-none max-[768px]:rounded-none",
941
972
  "min-[769px]:max-h-[85vh]"
942
973
  ),
943
974
  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: [
975
+ /* @__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
976
  /* @__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" }),
977
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: "Bug Report" }),
947
978
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: siteId })
948
979
  ] }),
949
980
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
950
981
  "button",
951
982
  {
952
983
  onClick: handleClose,
953
- className: "p-1.5 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors",
984
+ className: "rounded-md p-1.5 transition-colors hover:bg-gray-100 dark:hover:bg-gray-800",
954
985
  "aria-label": "Close",
955
986
  children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.X, { className: "h-4 w-4 text-gray-600 dark:text-gray-400" })
956
987
  }
957
988
  )
958
989
  ] }),
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)(
990
+ 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
991
  "img",
961
992
  {
962
993
  src: screenshotUrl,
963
994
  alt: "Captured section",
964
- className: "w-full max-h-40 object-contain rounded-md border border-gray-200 dark:border-gray-700"
995
+ className: "max-h-40 w-full rounded-md border border-gray-200 object-contain dark:border-gray-700"
965
996
  }
966
997
  ) }),
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) })
998
+ /* @__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: [
999
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.CheckCircle2, { className: "mb-3 h-12 w-12 text-green-500" }),
1000
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100", children: "Report Submitted" }),
1001
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: "mt-1 text-sm text-gray-500 dark:text-gray-400", children: [
1002
+ "Reference:",
1003
+ " ",
1004
+ /* @__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
1005
  ] }),
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." }),
1006
+ /* @__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
1007
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
976
1008
  "button",
977
1009
  {
978
1010
  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",
1011
+ className: "mt-4 rounded-md bg-indigo-600 px-4 py-2 text-sm text-white transition-colors hover:bg-indigo-700",
980
1012
  children: "Done"
981
1013
  }
982
1014
  )
983
1015
  ] }) : 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." }),
1016
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.X, { className: "mb-3 h-12 w-12 text-red-500" }),
1017
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100", children: "Submission Failed" }),
1018
+ /* @__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
1019
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
988
1020
  "button",
989
1021
  {
990
1022
  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",
1023
+ className: "mt-4 rounded-md bg-indigo-600 px-4 py-2 text-sm text-white transition-colors hover:bg-indigo-700",
992
1024
  children: "Try Again"
993
1025
  }
994
1026
  )
995
1027
  ] }) : 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" }),
1028
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.Loader2, { className: "mb-3 h-8 w-8 animate-spin text-indigo-500" }),
997
1029
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: "Submitting your report..." })
998
1030
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
999
1031
  (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 +1034,53 @@ function ReportModal({
1002
1034
  {
1003
1035
  className: cn(
1004
1036
  "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"
1037
+ 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
1038
  ),
1007
1039
  children: msg.content
1008
1040
  },
1009
1041
  i
1010
1042
  )),
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: [
1043
+ 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
1044
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.Loader2, { className: "h-3.5 w-3.5 animate-spin text-gray-500" }),
1013
1045
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-sm text-gray-500 dark:text-gray-400", children: "Thinking..." })
1014
1046
  ] }),
1015
1047
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { ref: chatEndRef })
1016
1048
  ] }) }),
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)(
1049
+ 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: [
1050
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1051
+ "textarea",
1052
+ {
1053
+ ref: inputRef,
1054
+ value: input,
1055
+ onChange: (e) => setInput(e.target.value),
1056
+ onKeyDown: handleKeyDown,
1057
+ placeholder: "Describe what's wrong...",
1058
+ rows: 2,
1059
+ 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",
1060
+ disabled: isLoading
1061
+ }
1062
+ ),
1063
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-col gap-2", children: [
1064
+ 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: [
1065
+ [
1066
+ captureResult.consoleErrors.length > 0 ? `${captureResult.consoleErrors.length} console error${captureResult.consoleErrors.length !== 1 ? "s" : ""}` : null,
1067
+ captureResult.networkErrors.length > 0 ? `${captureResult.networkErrors.length} failed request${captureResult.networkErrors.length !== 1 ? "s" : ""}` : null
1068
+ ].filter(Boolean).join(" + "),
1069
+ " ",
1070
+ "captured \u2014 these will be included in the report."
1071
+ ] }),
1072
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex justify-end gap-2", children: [
1073
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1034
1074
  "button",
1035
1075
  {
1036
1076
  onClick: sendMessage,
1037
1077
  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",
1078
+ 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
1079
  title: "Send message",
1040
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.Send, { className: "h-4 w-4" })
1080
+ children: [
1081
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.Send, { className: "h-4 w-4" }),
1082
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-sm font-medium", children: "Send message" })
1083
+ ]
1041
1084
  }
1042
1085
  ),
1043
1086
  messages.length >= 2 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -1045,22 +1088,14 @@ function ReportModal({
1045
1088
  {
1046
1089
  onClick: handleManualSubmit,
1047
1090
  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",
1091
+ 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
1092
  title: "Submit report now",
1050
- children: "Submit"
1093
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-sm font-medium", children: "Submit report" })
1051
1094
  }
1052
1095
  )
1053
1096
  ] })
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
1097
  ] })
1063
- ] })
1098
+ ] }) })
1064
1099
  ]
1065
1100
  }
1066
1101
  )
@@ -1074,8 +1109,10 @@ function JarveBugReporter({
1074
1109
  apiUrl,
1075
1110
  apiKey,
1076
1111
  user,
1112
+ buttonPosition,
1077
1113
  children
1078
1114
  }) {
1115
+ const safeApiKey = apiKey || "";
1079
1116
  const [captureMode, setCaptureMode] = (0, import_react3.useState)(false);
1080
1117
  const [captureResult, setCaptureResult] = (0, import_react3.useState)(null);
1081
1118
  const [showModal, setShowModal] = (0, import_react3.useState)(false);
@@ -1104,12 +1141,19 @@ function JarveBugReporter({
1104
1141
  clearCapturedErrors();
1105
1142
  clearCapturedNetworkErrors();
1106
1143
  }, []);
1107
- const siteId = apiKey.startsWith("brk_") ? apiKey.slice(4, 12) : "external";
1144
+ const siteId = safeApiKey.startsWith("brk_") ? safeApiKey.slice(4, 12) : "external";
1108
1145
  const reporterName = (user == null ? void 0 : user.name) || "Anonymous";
1109
1146
  const reporterEmail = (user == null ? void 0 : user.email) || "unknown@external";
1110
1147
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1111
1148
  children,
1112
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FloatingButton, { isActive: captureMode, onClick: toggleCaptureMode }),
1149
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1150
+ FloatingButton,
1151
+ {
1152
+ isActive: captureMode,
1153
+ onClick: toggleCaptureMode,
1154
+ position: buttonPosition
1155
+ }
1156
+ ),
1113
1157
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1114
1158
  CaptureOverlay,
1115
1159
  {