glitchgrab 1.19.2 → 1.20.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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Turn messy bugs into structured GitHub issues with AI. Drop-in SDK for Next.js apps.
4
4
 
5
- ## Install
5
+ ## How do I install Glitchgrab?
6
6
 
7
7
  ```bash
8
8
  npm install glitchgrab
@@ -10,7 +10,7 @@ npm install glitchgrab
10
10
  bun add glitchgrab
11
11
  ```
12
12
 
13
- ## Quick Start
13
+ ## How do I get started?
14
14
 
15
15
  Wrap your app with `GlitchgrabProvider`:
16
16
 
@@ -27,7 +27,7 @@ export default function RootLayout({ children }) {
27
27
  }
28
28
  ```
29
29
 
30
- ## Session (User Tracking)
30
+ ## How does user session tracking work?
31
31
 
32
32
  Pass a `session` prop so bug reports include the reporter's identity. This lets you trace which user reported each bug.
33
33
 
@@ -73,7 +73,7 @@ interface GlitchgrabSession {
73
73
 
74
74
  The `userId` is stored with every report. Use it to look up which user reported a bug in your own database.
75
75
 
76
- ## Report Button
76
+ ## How do I add a report button?
77
77
 
78
78
  ### Default floating button
79
79
 
@@ -102,7 +102,7 @@ import { ReportButton } from "glitchgrab";
102
102
 
103
103
  The modal handles screenshot capture, preview, upload, retake, and submission. Your custom button just triggers it.
104
104
 
105
- ## Programmatic Reporting
105
+ ## How do I report bugs programmatically?
106
106
 
107
107
  Use the `useGlitchgrab` hook to report bugs from code:
108
108
 
@@ -146,7 +146,7 @@ function FeedbackWidget() {
146
146
 
147
147
  Note: `openReportDialog()` requires a `<ReportButton>` to be mounted somewhere in the component tree. It triggers the same modal with screenshot capture.
148
148
 
149
- ## Keyboard Shortcuts
149
+ ## What keyboard shortcuts are available?
150
150
 
151
151
  Once `GlitchgrabProvider` is mounted, these shortcuts work globally:
152
152
 
@@ -158,7 +158,7 @@ Once `GlitchgrabProvider` is mounted, these shortcuts work globally:
158
158
 
159
159
  No configuration needed — shortcuts are active as long as the provider is in the tree.
160
160
 
161
- ## Fetching Reports by User
161
+ ## How do I fetch reports by user?
162
162
 
163
163
  ### React hook (recommended)
164
164
 
@@ -255,7 +255,7 @@ curl -H "Authorization: Bearer gg_your_token" \
255
255
 
256
256
  | Field | Description |
257
257
  |-------|-------------|
258
- | `id` | Report ID — use this for [managing issues](#managing-issues-approve--reject--close) |
258
+ | `id` | Report ID — use this for [managing issues](#how-do-i-approve-reject-or-close-issues) |
259
259
  | `source` | `SDK_AUTO` (crash) or `SDK_USER_REPORT` (user clicked report) |
260
260
  | `status` | `PENDING`, `PROCESSING`, `CREATED`, `FAILED` |
261
261
  | `reporterPrimaryKey` | The `userId` you passed in the session prop |
@@ -265,7 +265,7 @@ curl -H "Authorization: Bearer gg_your_token" \
265
265
  | `issue.severity` | AI-assigned severity |
266
266
  ```
267
267
 
268
- ## Managing Issues (Approve / Reject / Close)
268
+ ## How do I approve, reject, or close issues?
269
269
 
270
270
  ### React hook
271
271
 
@@ -365,7 +365,7 @@ curl -X POST \
365
365
 
366
366
  ### How to get the report ID
367
367
 
368
- Use the [Fetching Reports API](#fetching-reports-by-user) to list reports. Each report has an `id` field — use that as `REPORT_ID`.
368
+ Use the [Fetching Reports API](#how-do-i-fetch-reports-by-user) to list reports. Each report has an `id` field — use that as `REPORT_ID`.
369
369
 
370
370
  ### How it works
371
371
 
@@ -374,7 +374,7 @@ Use the [Fetching Reports API](#fetching-reports-by-user) to list reports. Each
374
374
  3. Approve/reject/close via API or dashboard buttons
375
375
  4. Labels and state sync directly to GitHub — GitHub is the source of truth
376
376
 
377
- ## Conversation Thread (Comments)
377
+ ## How do I add comments to a report?
378
378
 
379
379
  Each report has a conversation thread powered by GitHub issue comments. No extra database — comments live on GitHub.
380
380
 
@@ -430,7 +430,7 @@ The comment is posted to the GitHub issue with attribution: "Commented by: **Viv
430
430
 
431
431
  Click any report on the Reports page to see the full conversation thread. Reply directly from the dashboard — comments sync to GitHub.
432
432
 
433
- ## Error Boundary
433
+ ## How do I add an error boundary?
434
434
 
435
435
  Wrap components to auto-capture React errors:
436
436
 
@@ -442,7 +442,7 @@ import { GlitchgrabErrorBoundary } from "glitchgrab";
442
442
  </GlitchgrabErrorBoundary>
443
443
  ```
444
444
 
445
- ## Configuration
445
+ ## What configuration options are available?
446
446
 
447
447
  | Prop | Type | Default | Description |
448
448
  |------|------|---------|-------------|
@@ -455,7 +455,7 @@ import { GlitchgrabErrorBoundary } from "glitchgrab";
455
455
  | `onReportSent` | `(result: ReportResult) => void` | - | Called after a report is sent |
456
456
  | `fallback` | `ReactNode` | - | Error boundary fallback UI |
457
457
 
458
- ## Auto-Capture
458
+ ## How does auto-capture work?
459
459
 
460
460
  In production (`NODE_ENV=production`), the SDK automatically captures:
461
461
  - Unhandled JavaScript errors
@@ -466,7 +466,7 @@ In production (`NODE_ENV=production`), the SDK automatically captures:
466
466
 
467
467
  Auto-capture is **disabled in development** to avoid noisy issues.
468
468
 
469
- ## What gets included in each report
469
+ ## What data is included in each report?
470
470
 
471
471
  - Description from the user
472
472
  - Screenshot (auto-captured or uploaded)
package/dist/index.d.mts CHANGED
@@ -109,6 +109,12 @@ interface UseGlitchgrabReturn {
109
109
  description?: string;
110
110
  type?: ReportType;
111
111
  }) => void;
112
+ /**
113
+ * Optional: polish user-written description text via the Glitchgrab AI enhance endpoint.
114
+ * Fixes grammar / clarity only — never invents details or changes meaning.
115
+ * Returns the polished text, or the original text on any failure (never throws).
116
+ */
117
+ enhanceText: (text: string) => Promise<string>;
112
118
  /** The token being used */
113
119
  token: string;
114
120
  /** The base URL of the Glitchgrab API */
@@ -288,6 +294,12 @@ declare function captureContext(visitedPages: string[]): CapturedContext;
288
294
  * Never throws — returns null on failure.
289
295
  */
290
296
  declare function sendReport(payload: ReportPayload, baseUrl?: string): Promise<ReportResult | null>;
297
+ /**
298
+ * Polish raw description text via the Glitchgrab AI enhance endpoint.
299
+ * Returns the polished text on success, or the original text on any failure.
300
+ * Never throws.
301
+ */
302
+ declare function enhanceText(text: string, token: string, baseUrl?: string): Promise<string>;
291
303
 
292
304
  declare function computeSignature(params: {
293
305
  errorMessage: string | undefined;
@@ -297,4 +309,4 @@ declare function computeSignature(params: {
297
309
  declare function shouldSkipDuplicate(signature: string, windowMs?: number, now?: number): boolean;
298
310
  declare function clearDedupCache(): void;
299
311
 
300
- export { type Breadcrumb, type BreadcrumbType, type CapturedContext, type DeviceInfo, type GlitchgrabConfig, GlitchgrabErrorBoundary, GlitchgrabProvider, type GlitchgrabProviderProps, type GlitchgrabReport, type GlitchgrabSession, ReportButton, type ReportButtonProps, type ReportPayload, type ReportResult, type ReportSeverity, type ReportType, type UseGlitchgrabReturn, addBreadcrumb, captureContext, captureDeviceInfo, clearBreadcrumbs, clearDedupCache, computeSignature, fetchGlitchgrabReports, getBreadcrumbs, initBreadcrumbs, sanitizeUrl, sendReport, shouldSkipDuplicate, useGlitchgrab, useGlitchgrabActions, useGlitchgrabReports };
312
+ export { type Breadcrumb, type BreadcrumbType, type CapturedContext, type DeviceInfo, type GlitchgrabConfig, GlitchgrabErrorBoundary, GlitchgrabProvider, type GlitchgrabProviderProps, type GlitchgrabReport, type GlitchgrabSession, ReportButton, type ReportButtonProps, type ReportPayload, type ReportResult, type ReportSeverity, type ReportType, type UseGlitchgrabReturn, addBreadcrumb, captureContext, captureDeviceInfo, clearBreadcrumbs, clearDedupCache, computeSignature, enhanceText, fetchGlitchgrabReports, getBreadcrumbs, initBreadcrumbs, sanitizeUrl, sendReport, shouldSkipDuplicate, useGlitchgrab, useGlitchgrabActions, useGlitchgrabReports };
package/dist/index.d.ts CHANGED
@@ -109,6 +109,12 @@ interface UseGlitchgrabReturn {
109
109
  description?: string;
110
110
  type?: ReportType;
111
111
  }) => void;
112
+ /**
113
+ * Optional: polish user-written description text via the Glitchgrab AI enhance endpoint.
114
+ * Fixes grammar / clarity only — never invents details or changes meaning.
115
+ * Returns the polished text, or the original text on any failure (never throws).
116
+ */
117
+ enhanceText: (text: string) => Promise<string>;
112
118
  /** The token being used */
113
119
  token: string;
114
120
  /** The base URL of the Glitchgrab API */
@@ -288,6 +294,12 @@ declare function captureContext(visitedPages: string[]): CapturedContext;
288
294
  * Never throws — returns null on failure.
289
295
  */
290
296
  declare function sendReport(payload: ReportPayload, baseUrl?: string): Promise<ReportResult | null>;
297
+ /**
298
+ * Polish raw description text via the Glitchgrab AI enhance endpoint.
299
+ * Returns the polished text on success, or the original text on any failure.
300
+ * Never throws.
301
+ */
302
+ declare function enhanceText(text: string, token: string, baseUrl?: string): Promise<string>;
291
303
 
292
304
  declare function computeSignature(params: {
293
305
  errorMessage: string | undefined;
@@ -297,4 +309,4 @@ declare function computeSignature(params: {
297
309
  declare function shouldSkipDuplicate(signature: string, windowMs?: number, now?: number): boolean;
298
310
  declare function clearDedupCache(): void;
299
311
 
300
- export { type Breadcrumb, type BreadcrumbType, type CapturedContext, type DeviceInfo, type GlitchgrabConfig, GlitchgrabErrorBoundary, GlitchgrabProvider, type GlitchgrabProviderProps, type GlitchgrabReport, type GlitchgrabSession, ReportButton, type ReportButtonProps, type ReportPayload, type ReportResult, type ReportSeverity, type ReportType, type UseGlitchgrabReturn, addBreadcrumb, captureContext, captureDeviceInfo, clearBreadcrumbs, clearDedupCache, computeSignature, fetchGlitchgrabReports, getBreadcrumbs, initBreadcrumbs, sanitizeUrl, sendReport, shouldSkipDuplicate, useGlitchgrab, useGlitchgrabActions, useGlitchgrabReports };
312
+ export { type Breadcrumb, type BreadcrumbType, type CapturedContext, type DeviceInfo, type GlitchgrabConfig, GlitchgrabErrorBoundary, GlitchgrabProvider, type GlitchgrabProviderProps, type GlitchgrabReport, type GlitchgrabSession, ReportButton, type ReportButtonProps, type ReportPayload, type ReportResult, type ReportSeverity, type ReportType, type UseGlitchgrabReturn, addBreadcrumb, captureContext, captureDeviceInfo, clearBreadcrumbs, clearDedupCache, computeSignature, enhanceText, fetchGlitchgrabReports, getBreadcrumbs, initBreadcrumbs, sanitizeUrl, sendReport, shouldSkipDuplicate, useGlitchgrab, useGlitchgrabActions, useGlitchgrabReports };
package/dist/index.js CHANGED
@@ -40,6 +40,7 @@ __export(index_exports, {
40
40
  clearBreadcrumbs: () => clearBreadcrumbs,
41
41
  clearDedupCache: () => clearDedupCache,
42
42
  computeSignature: () => computeSignature,
43
+ enhanceText: () => enhanceText,
43
44
  fetchGlitchgrabReports: () => fetchGlitchgrabReports,
44
45
  getBreadcrumbs: () => getBreadcrumbs,
45
46
  initBreadcrumbs: () => initBreadcrumbs,
@@ -315,6 +316,30 @@ async function sendReport(payload, baseUrl) {
315
316
  return null;
316
317
  }
317
318
  }
319
+ async function enhanceText(text, token, baseUrl) {
320
+ var _a;
321
+ try {
322
+ const trimmed = text.trim();
323
+ if (!trimmed) return text;
324
+ const url = `${baseUrl != null ? baseUrl : DEFAULT_BASE_URL}/api/v1/ai/enhance-text`;
325
+ const response = await fetch(url, {
326
+ method: "POST",
327
+ headers: {
328
+ "Content-Type": "application/json",
329
+ Authorization: `Bearer ${token}`
330
+ },
331
+ body: JSON.stringify({ text: trimmed })
332
+ });
333
+ if (!response.ok) return text;
334
+ const envelope = await response.json();
335
+ if ((envelope == null ? void 0 : envelope.success) && typeof ((_a = envelope.data) == null ? void 0 : _a.text) === "string") {
336
+ return envelope.data.text;
337
+ }
338
+ return text;
339
+ } catch (e) {
340
+ return text;
341
+ }
342
+ }
318
343
 
319
344
  // src/dedup.ts
320
345
  var DEFAULT_WINDOW_MS = 5 * 60 * 1e3;
@@ -586,9 +611,11 @@ function isLowQualityText(text) {
586
611
  }
587
612
  function ReportDialog({
588
613
  report,
614
+ enhanceText: enhanceText2,
589
615
  types,
590
616
  showSeverity = true
591
617
  }) {
618
+ const [isEnhancing, setIsEnhancing] = (0, import_react.useState)(false);
592
619
  const [mounted, setMounted] = (0, import_react.useState)(false);
593
620
  (0, import_react.useEffect)(() => {
594
621
  setMounted(true);
@@ -930,38 +957,83 @@ function ReportDialog({
930
957
  type
931
958
  )) }),
932
959
  step === 2 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
933
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
934
- "textarea",
935
- {
936
- value: description,
937
- onChange: (e) => {
938
- setDescription(e.target.value);
939
- if (validationError) setValidationError(null);
940
- },
941
- placeholder: getPlaceholder(reportType),
942
- style: {
943
- width: "100%",
944
- minHeight: "100px",
945
- padding: "10px 12px",
946
- borderRadius: "8px",
947
- border: `1px solid ${t.inputBorder}`,
948
- fontSize: "14px",
949
- fontFamily: "inherit",
950
- resize: "vertical",
951
- outline: "none",
952
- boxSizing: "border-box",
953
- color: t.text,
954
- backgroundColor: t.inputBg
955
- },
956
- onFocus: (e) => {
957
- e.target.style.borderColor = t.accent;
958
- },
959
- onBlur: (e) => {
960
- e.target.style.borderColor = t.inputBorder;
961
- },
962
- autoFocus: true
963
- }
964
- ),
960
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { position: "relative" }, children: [
961
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
962
+ "textarea",
963
+ {
964
+ value: description,
965
+ onChange: (e) => {
966
+ setDescription(e.target.value);
967
+ if (validationError) setValidationError(null);
968
+ },
969
+ placeholder: getPlaceholder(reportType),
970
+ style: {
971
+ width: "100%",
972
+ minHeight: "100px",
973
+ padding: enhanceText2 ? "28px 12px 10px" : "10px 12px",
974
+ borderRadius: "8px",
975
+ border: `1px solid ${t.inputBorder}`,
976
+ fontSize: "14px",
977
+ fontFamily: "inherit",
978
+ resize: "vertical",
979
+ outline: "none",
980
+ boxSizing: "border-box",
981
+ color: t.text,
982
+ backgroundColor: t.inputBg
983
+ },
984
+ onFocus: (e) => {
985
+ e.target.style.borderColor = t.accent;
986
+ },
987
+ onBlur: (e) => {
988
+ e.target.style.borderColor = t.inputBorder;
989
+ },
990
+ autoFocus: true
991
+ }
992
+ ),
993
+ enhanceText2 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
994
+ "button",
995
+ {
996
+ type: "button",
997
+ onClick: async () => {
998
+ if (!description.trim() || isEnhancing) return;
999
+ setIsEnhancing(true);
1000
+ try {
1001
+ const polished = await enhanceText2(description);
1002
+ if (polished && polished !== description) {
1003
+ setDescription(polished);
1004
+ if (validationError) setValidationError(null);
1005
+ }
1006
+ } finally {
1007
+ setIsEnhancing(false);
1008
+ }
1009
+ },
1010
+ disabled: !description.trim() || isEnhancing,
1011
+ style: {
1012
+ position: "absolute",
1013
+ top: "6px",
1014
+ right: "6px",
1015
+ display: "inline-flex",
1016
+ alignItems: "center",
1017
+ gap: "4px",
1018
+ background: t.inputBg,
1019
+ border: "none",
1020
+ padding: "2px 6px",
1021
+ borderRadius: "4px",
1022
+ fontFamily: "inherit",
1023
+ fontSize: "11px",
1024
+ color: t.textMuted,
1025
+ cursor: !description.trim() || isEnhancing ? "default" : "pointer",
1026
+ opacity: !description.trim() ? 0.5 : 1,
1027
+ zIndex: 1
1028
+ },
1029
+ title: "Polish grammar \u2014 preserves your meaning",
1030
+ children: [
1031
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "11", height: "11", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 3l1.5 4.5L18 9l-4.5 1.5L12 15l-1.5-4.5L6 9l4.5-1.5L12 3zM19 15l.75 2.25L22 18l-2.25.75L19 21l-.75-2.25L16 18l2.25-.75L19 15z", stroke: t.textMuted, strokeWidth: "1.5", strokeLinejoin: "round" }) }),
1032
+ isEnhancing ? "Enhancing..." : "AI enhance"
1033
+ ]
1034
+ }
1035
+ )
1036
+ ] }),
965
1037
  showSeverity && reportType === "BUG" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginTop: "10px" }, children: [
966
1038
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: "12px", color: t.textMuted, marginBottom: "6px", display: "block" }, children: "Severity" }),
967
1039
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", gap: "6px" }, children: ["low", "medium", "high"].map((s) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -1382,6 +1454,10 @@ function GlitchgrabProviderInner({
1382
1454
  const handleError = (event) => {
1383
1455
  var _a, _b, _c, _d, _e, _f;
1384
1456
  try {
1457
+ const isOpaqueCrossOrigin = event.message === "Script error." || event.message === "Script error" || !event.error && !event.filename;
1458
+ if (isOpaqueCrossOrigin) {
1459
+ return;
1460
+ }
1385
1461
  const context = captureContext(visitedPagesRef.current);
1386
1462
  const sig = computeSignature({
1387
1463
  errorMessage: event.message,
@@ -1424,6 +1500,9 @@ function GlitchgrabProviderInner({
1424
1500
  const context = captureContext(visitedPagesRef.current);
1425
1501
  const reason = event.reason;
1426
1502
  const errMsg = reason instanceof Error ? reason.message : String(reason);
1503
+ if (errMsg === "Script error." || errMsg === "Script error") {
1504
+ return;
1505
+ }
1427
1506
  const errStack = reason instanceof Error ? reason.stack : void 0;
1428
1507
  const sig = computeSignature({
1429
1508
  errorMessage: errMsg,
@@ -1510,6 +1589,16 @@ function GlitchgrabProviderInner({
1510
1589
  },
1511
1590
  []
1512
1591
  );
1592
+ const enhance = (0, import_react2.useCallback)(
1593
+ async (text) => {
1594
+ try {
1595
+ return await enhanceText(text, token, baseUrl);
1596
+ } catch (e) {
1597
+ return text;
1598
+ }
1599
+ },
1600
+ [token, baseUrl]
1601
+ );
1513
1602
  const openReportDialog = (0, import_react2.useCallback)((options) => {
1514
1603
  if (typeof window !== "undefined") {
1515
1604
  window.dispatchEvent(new CustomEvent("glitchgrab:open-report", { detail: options }));
@@ -1538,7 +1627,8 @@ function GlitchgrabProviderInner({
1538
1627
  reportBug,
1539
1628
  report,
1540
1629
  addBreadcrumb: addBreadcrumb2,
1541
- openReportDialog
1630
+ openReportDialog,
1631
+ enhanceText: enhance
1542
1632
  },
1543
1633
  children: [
1544
1634
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
@@ -1553,7 +1643,7 @@ function GlitchgrabProviderInner({
1553
1643
  children
1554
1644
  }
1555
1645
  ),
1556
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ReportDialog, { report, types, showSeverity })
1646
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ReportDialog, { report, enhanceText: enhance, types, showSeverity })
1557
1647
  ]
1558
1648
  }
1559
1649
  );
@@ -1779,6 +1869,7 @@ function useGlitchgrabActions({
1779
1869
  clearBreadcrumbs,
1780
1870
  clearDedupCache,
1781
1871
  computeSignature,
1872
+ enhanceText,
1782
1873
  fetchGlitchgrabReports,
1783
1874
  getBreadcrumbs,
1784
1875
  initBreadcrumbs,