@bbearai/core 0.9.3 → 0.9.5

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.d.mts CHANGED
@@ -72,6 +72,14 @@ interface EnhancedBugContext {
72
72
  pageLoadTime?: number;
73
73
  memoryUsage?: number;
74
74
  fps?: number;
75
+ /** First Contentful Paint (ms) */
76
+ fcp?: number;
77
+ /** Largest Contentful Paint (ms) */
78
+ lcp?: number;
79
+ /** Cumulative Layout Shift (score) */
80
+ cls?: number;
81
+ /** Time to First Byte (ms) */
82
+ ttfb?: number;
75
83
  };
76
84
  /** Browser/environment info */
77
85
  environment?: {
@@ -80,6 +88,10 @@ interface EnhancedBugContext {
80
88
  cookiesEnabled?: boolean;
81
89
  localStorage?: boolean;
82
90
  online?: boolean;
91
+ /** Effective connection type (e.g. '4g', '3g') */
92
+ connectionType?: string;
93
+ /** Downlink speed in Mbps */
94
+ connectionDownlink?: number;
83
95
  };
84
96
  }
85
97
  interface DeviceInfo {
@@ -96,6 +108,13 @@ interface DeviceInfo {
96
108
  width: number;
97
109
  height: number;
98
110
  };
111
+ /** Viewport dimensions (visible area) */
112
+ viewport?: {
113
+ width: number;
114
+ height: number;
115
+ };
116
+ /** Device pixel ratio */
117
+ devicePixelRatio?: number;
99
118
  /** User agent (web) */
100
119
  userAgent?: string;
101
120
  }
@@ -296,6 +315,17 @@ interface MonitoringEvent {
296
315
  clickCount?: number;
297
316
  /** CSS selector of the clicked element (rage clicks only) */
298
317
  targetSelector?: string;
318
+ /** Whether the tab was visible when the event fired */
319
+ tabVisible?: boolean;
320
+ /** CSS selector of the focused element at event time */
321
+ focusedElement?: string;
322
+ /** Rage click coordinates */
323
+ clickCoordinates?: {
324
+ x: number;
325
+ y: number;
326
+ };
327
+ /** Response body snippet for API failures (max 300 chars) */
328
+ apiErrorBody?: string;
299
329
  /** Sentry event ID — populated by @bbearai/sentry adapter */
300
330
  sentryEventId?: string;
301
331
  /** Sentry breadcrumbs — populated by @bbearai/sentry adapter */
@@ -1359,8 +1389,9 @@ declare class BugBearClient {
1359
1389
  /**
1360
1390
  * Get issue counts for the tester (Open, Done, Reopened)
1361
1391
  * Used by the widget HomeScreen cards
1392
+ * @param mineOnly - If true (default), only counts the tester's own reports. If false, counts all project reports.
1362
1393
  */
1363
- getIssueCounts(): Promise<{
1394
+ getIssueCounts(mineOnly?: boolean): Promise<{
1364
1395
  open: number;
1365
1396
  done: number;
1366
1397
  reopened: number;
@@ -1369,8 +1400,9 @@ declare class BugBearClient {
1369
1400
  * Get issues for the tester by category.
1370
1401
  * Returns enriched data: done issues include verification proof,
1371
1402
  * reopened issues include original bug context.
1403
+ * @param mineOnly - If true (default), only returns the tester's own reports. If false, returns all project reports.
1372
1404
  */
1373
- getIssues(category: 'open' | 'done' | 'reopened'): Promise<TesterIssue[]>;
1405
+ getIssues(category: 'open' | 'done' | 'reopened', mineOnly?: boolean): Promise<TesterIssue[]>;
1374
1406
  /**
1375
1407
  * Reopen a done issue that the tester believes isn't actually fixed.
1376
1408
  * Transitions the report from a done status back to 'confirmed'.
package/dist/index.d.ts CHANGED
@@ -72,6 +72,14 @@ interface EnhancedBugContext {
72
72
  pageLoadTime?: number;
73
73
  memoryUsage?: number;
74
74
  fps?: number;
75
+ /** First Contentful Paint (ms) */
76
+ fcp?: number;
77
+ /** Largest Contentful Paint (ms) */
78
+ lcp?: number;
79
+ /** Cumulative Layout Shift (score) */
80
+ cls?: number;
81
+ /** Time to First Byte (ms) */
82
+ ttfb?: number;
75
83
  };
76
84
  /** Browser/environment info */
77
85
  environment?: {
@@ -80,6 +88,10 @@ interface EnhancedBugContext {
80
88
  cookiesEnabled?: boolean;
81
89
  localStorage?: boolean;
82
90
  online?: boolean;
91
+ /** Effective connection type (e.g. '4g', '3g') */
92
+ connectionType?: string;
93
+ /** Downlink speed in Mbps */
94
+ connectionDownlink?: number;
83
95
  };
84
96
  }
85
97
  interface DeviceInfo {
@@ -96,6 +108,13 @@ interface DeviceInfo {
96
108
  width: number;
97
109
  height: number;
98
110
  };
111
+ /** Viewport dimensions (visible area) */
112
+ viewport?: {
113
+ width: number;
114
+ height: number;
115
+ };
116
+ /** Device pixel ratio */
117
+ devicePixelRatio?: number;
99
118
  /** User agent (web) */
100
119
  userAgent?: string;
101
120
  }
@@ -296,6 +315,17 @@ interface MonitoringEvent {
296
315
  clickCount?: number;
297
316
  /** CSS selector of the clicked element (rage clicks only) */
298
317
  targetSelector?: string;
318
+ /** Whether the tab was visible when the event fired */
319
+ tabVisible?: boolean;
320
+ /** CSS selector of the focused element at event time */
321
+ focusedElement?: string;
322
+ /** Rage click coordinates */
323
+ clickCoordinates?: {
324
+ x: number;
325
+ y: number;
326
+ };
327
+ /** Response body snippet for API failures (max 300 chars) */
328
+ apiErrorBody?: string;
299
329
  /** Sentry event ID — populated by @bbearai/sentry adapter */
300
330
  sentryEventId?: string;
301
331
  /** Sentry breadcrumbs — populated by @bbearai/sentry adapter */
@@ -1359,8 +1389,9 @@ declare class BugBearClient {
1359
1389
  /**
1360
1390
  * Get issue counts for the tester (Open, Done, Reopened)
1361
1391
  * Used by the widget HomeScreen cards
1392
+ * @param mineOnly - If true (default), only counts the tester's own reports. If false, counts all project reports.
1362
1393
  */
1363
- getIssueCounts(): Promise<{
1394
+ getIssueCounts(mineOnly?: boolean): Promise<{
1364
1395
  open: number;
1365
1396
  done: number;
1366
1397
  reopened: number;
@@ -1369,8 +1400,9 @@ declare class BugBearClient {
1369
1400
  * Get issues for the tester by category.
1370
1401
  * Returns enriched data: done issues include verification proof,
1371
1402
  * reopened issues include original bug context.
1403
+ * @param mineOnly - If true (default), only returns the tester's own reports. If false, returns all project reports.
1372
1404
  */
1373
- getIssues(category: 'open' | 'done' | 'reopened'): Promise<TesterIssue[]>;
1405
+ getIssues(category: 'open' | 'done' | 'reopened', mineOnly?: boolean): Promise<TesterIssue[]>;
1374
1406
  /**
1375
1407
  * Reopen a done issue that the tester believes isn't actually fixed.
1376
1408
  * Transitions the report from a done status back to 'confirmed'.
package/dist/index.js CHANGED
@@ -279,17 +279,43 @@ var ContextCaptureManager = class {
279
279
  }
280
280
  } catch {
281
281
  }
282
+ try {
283
+ const navigation = performance.getEntriesByType("navigation")[0];
284
+ if (navigation) {
285
+ metrics.ttfb = Math.round(navigation.responseStart - navigation.startTime);
286
+ }
287
+ const paintEntries = performance.getEntriesByType("paint");
288
+ const fcp = paintEntries.find((e) => e.name === "first-contentful-paint");
289
+ if (fcp) metrics.fcp = Math.round(fcp.startTime);
290
+ const lcpEntries = performance.getEntriesByType("largest-contentful-paint");
291
+ if (lcpEntries.length > 0) {
292
+ metrics.lcp = Math.round(lcpEntries[lcpEntries.length - 1].startTime);
293
+ }
294
+ const clsEntries = performance.getEntriesByType("layout-shift");
295
+ if (clsEntries.length > 0) {
296
+ metrics.cls = parseFloat(
297
+ clsEntries.filter((e) => !e.hadRecentInput).reduce((sum, e) => sum + (e.value ?? 0), 0).toFixed(4)
298
+ );
299
+ }
300
+ } catch {
301
+ }
282
302
  return Object.keys(metrics).length > 0 ? metrics : void 0;
283
303
  }
284
304
  getEnvironmentInfo() {
285
305
  if (typeof window === "undefined" || typeof navigator === "undefined" || typeof document === "undefined") return void 0;
286
- return {
306
+ const env = {
287
307
  language: navigator.language,
288
308
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
289
309
  cookiesEnabled: navigator.cookieEnabled ?? false,
290
310
  localStorage: typeof localStorage !== "undefined",
291
311
  online: navigator.onLine ?? true
292
312
  };
313
+ const conn = navigator.connection;
314
+ if (conn) {
315
+ env.connectionType = conn.effectiveType;
316
+ env.connectionDownlink = conn.downlink;
317
+ }
318
+ return env;
293
319
  }
294
320
  };
295
321
  var contextCapture = new ContextCaptureManager();
@@ -571,6 +597,10 @@ var ErrorMonitor = class {
571
597
  ...event.requestMethod ? { requestMethod: event.requestMethod } : {},
572
598
  ...event.clickCount ? { clickCount: event.clickCount } : {},
573
599
  ...event.targetSelector ? { targetSelector: event.targetSelector } : {},
600
+ ...event.tabVisible !== void 0 ? { tabVisible: event.tabVisible } : {},
601
+ ...event.focusedElement ? { focusedElement: event.focusedElement } : {},
602
+ ...event.clickCoordinates ? { clickCoordinates: event.clickCoordinates } : {},
603
+ ...event.apiErrorBody ? { apiErrorBody: event.apiErrorBody } : {},
574
604
  ...event.sentryEventId ? { sentryEventId: event.sentryEventId } : {}
575
605
  }
576
606
  },
@@ -664,7 +694,9 @@ var WebCrashHandler = class {
664
694
  message: msg,
665
695
  route,
666
696
  timestamp: Date.now(),
667
- error: error ?? new Error(msg)
697
+ error: error ?? new Error(msg),
698
+ tabVisible: !document.hidden,
699
+ focusedElement: document.activeElement instanceof Element ? getSelector(document.activeElement) : void 0
668
700
  });
669
701
  if (typeof this.prevOnError === "function") {
670
702
  return this.prevOnError(message, source, lineno, colno, error);
@@ -682,7 +714,9 @@ var WebCrashHandler = class {
682
714
  message: msg,
683
715
  route,
684
716
  timestamp: Date.now(),
685
- error
717
+ error,
718
+ tabVisible: !document.hidden,
719
+ focusedElement: document.activeElement instanceof Element ? getSelector(document.activeElement) : void 0
686
720
  });
687
721
  };
688
722
  window.addEventListener("unhandledrejection", this.rejectionHandler);
@@ -711,6 +745,13 @@ var WebApiFailureHandler = class {
711
745
  const method = (init?.method ?? "GET").toUpperCase();
712
746
  const route = currentRoute();
713
747
  const msg = `${method} ${url} failed with ${response.status}`;
748
+ let apiErrorBody;
749
+ try {
750
+ const cloned = response.clone();
751
+ const text = await cloned.text();
752
+ if (text) apiErrorBody = text.slice(0, 300);
753
+ } catch {
754
+ }
714
755
  onEvent({
715
756
  source: "api_failure",
716
757
  fingerprint: generateFingerprint("api_failure", msg, route),
@@ -719,7 +760,9 @@ var WebApiFailureHandler = class {
719
760
  timestamp: Date.now(),
720
761
  requestUrl: url,
721
762
  requestMethod: method,
722
- statusCode: response.status
763
+ statusCode: response.status,
764
+ tabVisible: !document.hidden,
765
+ apiErrorBody
723
766
  });
724
767
  }
725
768
  return response;
@@ -745,7 +788,8 @@ var WebRageClickHandler = class {
745
788
  route,
746
789
  timestamp: Date.now(),
747
790
  clickCount: rageEvent.clickCount,
748
- targetSelector: rageEvent.targetSelector
791
+ targetSelector: rageEvent.targetSelector,
792
+ clickCoordinates: { x: rageEvent.x, y: rageEvent.y }
749
793
  });
750
794
  });
751
795
  }
@@ -1993,14 +2037,16 @@ var BugBearClient = class {
1993
2037
  /**
1994
2038
  * Get issue counts for the tester (Open, Done, Reopened)
1995
2039
  * Used by the widget HomeScreen cards
2040
+ * @param mineOnly - If true (default), only counts the tester's own reports. If false, counts all project reports.
1996
2041
  */
1997
- async getIssueCounts() {
2042
+ async getIssueCounts(mineOnly = true) {
1998
2043
  try {
1999
2044
  const testerInfo = await this.getTesterInfo();
2000
2045
  if (!testerInfo) return { open: 0, done: 0, reopened: 0 };
2001
2046
  const { data, error } = await this.supabase.rpc("get_tester_issue_counts", {
2002
2047
  p_project_id: this.config.projectId,
2003
- p_tester_id: testerInfo.id
2048
+ p_tester_id: testerInfo.id,
2049
+ p_mine_only: mineOnly
2004
2050
  });
2005
2051
  if (error) {
2006
2052
  console.error("BugBear: Failed to fetch issue counts", formatPgError(error));
@@ -2020,15 +2066,17 @@ var BugBearClient = class {
2020
2066
  * Get issues for the tester by category.
2021
2067
  * Returns enriched data: done issues include verification proof,
2022
2068
  * reopened issues include original bug context.
2069
+ * @param mineOnly - If true (default), only returns the tester's own reports. If false, returns all project reports.
2023
2070
  */
2024
- async getIssues(category) {
2071
+ async getIssues(category, mineOnly = true) {
2025
2072
  try {
2026
2073
  const testerInfo = await this.getTesterInfo();
2027
2074
  if (!testerInfo) return [];
2028
2075
  const { data, error } = await this.supabase.rpc("get_tester_issues", {
2029
2076
  p_project_id: this.config.projectId,
2030
2077
  p_tester_id: testerInfo.id,
2031
- p_category: category
2078
+ p_category: category,
2079
+ p_mine_only: mineOnly
2032
2080
  });
2033
2081
  if (error) {
2034
2082
  console.error("BugBear: Failed to fetch issues", formatPgError(error));
@@ -2505,6 +2553,8 @@ var BugBearClient = class {
2505
2553
  height: window.screen.height
2506
2554
  };
2507
2555
  }
2556
+ info.viewport = { width: window.innerWidth, height: window.innerHeight };
2557
+ info.devicePixelRatio = window.devicePixelRatio;
2508
2558
  return info;
2509
2559
  }
2510
2560
  return { platform: "web" };
package/dist/index.mjs CHANGED
@@ -233,17 +233,43 @@ var ContextCaptureManager = class {
233
233
  }
234
234
  } catch {
235
235
  }
236
+ try {
237
+ const navigation = performance.getEntriesByType("navigation")[0];
238
+ if (navigation) {
239
+ metrics.ttfb = Math.round(navigation.responseStart - navigation.startTime);
240
+ }
241
+ const paintEntries = performance.getEntriesByType("paint");
242
+ const fcp = paintEntries.find((e) => e.name === "first-contentful-paint");
243
+ if (fcp) metrics.fcp = Math.round(fcp.startTime);
244
+ const lcpEntries = performance.getEntriesByType("largest-contentful-paint");
245
+ if (lcpEntries.length > 0) {
246
+ metrics.lcp = Math.round(lcpEntries[lcpEntries.length - 1].startTime);
247
+ }
248
+ const clsEntries = performance.getEntriesByType("layout-shift");
249
+ if (clsEntries.length > 0) {
250
+ metrics.cls = parseFloat(
251
+ clsEntries.filter((e) => !e.hadRecentInput).reduce((sum, e) => sum + (e.value ?? 0), 0).toFixed(4)
252
+ );
253
+ }
254
+ } catch {
255
+ }
236
256
  return Object.keys(metrics).length > 0 ? metrics : void 0;
237
257
  }
238
258
  getEnvironmentInfo() {
239
259
  if (typeof window === "undefined" || typeof navigator === "undefined" || typeof document === "undefined") return void 0;
240
- return {
260
+ const env = {
241
261
  language: navigator.language,
242
262
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
243
263
  cookiesEnabled: navigator.cookieEnabled ?? false,
244
264
  localStorage: typeof localStorage !== "undefined",
245
265
  online: navigator.onLine ?? true
246
266
  };
267
+ const conn = navigator.connection;
268
+ if (conn) {
269
+ env.connectionType = conn.effectiveType;
270
+ env.connectionDownlink = conn.downlink;
271
+ }
272
+ return env;
247
273
  }
248
274
  };
249
275
  var contextCapture = new ContextCaptureManager();
@@ -525,6 +551,10 @@ var ErrorMonitor = class {
525
551
  ...event.requestMethod ? { requestMethod: event.requestMethod } : {},
526
552
  ...event.clickCount ? { clickCount: event.clickCount } : {},
527
553
  ...event.targetSelector ? { targetSelector: event.targetSelector } : {},
554
+ ...event.tabVisible !== void 0 ? { tabVisible: event.tabVisible } : {},
555
+ ...event.focusedElement ? { focusedElement: event.focusedElement } : {},
556
+ ...event.clickCoordinates ? { clickCoordinates: event.clickCoordinates } : {},
557
+ ...event.apiErrorBody ? { apiErrorBody: event.apiErrorBody } : {},
528
558
  ...event.sentryEventId ? { sentryEventId: event.sentryEventId } : {}
529
559
  }
530
560
  },
@@ -618,7 +648,9 @@ var WebCrashHandler = class {
618
648
  message: msg,
619
649
  route,
620
650
  timestamp: Date.now(),
621
- error: error ?? new Error(msg)
651
+ error: error ?? new Error(msg),
652
+ tabVisible: !document.hidden,
653
+ focusedElement: document.activeElement instanceof Element ? getSelector(document.activeElement) : void 0
622
654
  });
623
655
  if (typeof this.prevOnError === "function") {
624
656
  return this.prevOnError(message, source, lineno, colno, error);
@@ -636,7 +668,9 @@ var WebCrashHandler = class {
636
668
  message: msg,
637
669
  route,
638
670
  timestamp: Date.now(),
639
- error
671
+ error,
672
+ tabVisible: !document.hidden,
673
+ focusedElement: document.activeElement instanceof Element ? getSelector(document.activeElement) : void 0
640
674
  });
641
675
  };
642
676
  window.addEventListener("unhandledrejection", this.rejectionHandler);
@@ -665,6 +699,13 @@ var WebApiFailureHandler = class {
665
699
  const method = (init?.method ?? "GET").toUpperCase();
666
700
  const route = currentRoute();
667
701
  const msg = `${method} ${url} failed with ${response.status}`;
702
+ let apiErrorBody;
703
+ try {
704
+ const cloned = response.clone();
705
+ const text = await cloned.text();
706
+ if (text) apiErrorBody = text.slice(0, 300);
707
+ } catch {
708
+ }
668
709
  onEvent({
669
710
  source: "api_failure",
670
711
  fingerprint: generateFingerprint("api_failure", msg, route),
@@ -673,7 +714,9 @@ var WebApiFailureHandler = class {
673
714
  timestamp: Date.now(),
674
715
  requestUrl: url,
675
716
  requestMethod: method,
676
- statusCode: response.status
717
+ statusCode: response.status,
718
+ tabVisible: !document.hidden,
719
+ apiErrorBody
677
720
  });
678
721
  }
679
722
  return response;
@@ -699,7 +742,8 @@ var WebRageClickHandler = class {
699
742
  route,
700
743
  timestamp: Date.now(),
701
744
  clickCount: rageEvent.clickCount,
702
- targetSelector: rageEvent.targetSelector
745
+ targetSelector: rageEvent.targetSelector,
746
+ clickCoordinates: { x: rageEvent.x, y: rageEvent.y }
703
747
  });
704
748
  });
705
749
  }
@@ -1947,14 +1991,16 @@ var BugBearClient = class {
1947
1991
  /**
1948
1992
  * Get issue counts for the tester (Open, Done, Reopened)
1949
1993
  * Used by the widget HomeScreen cards
1994
+ * @param mineOnly - If true (default), only counts the tester's own reports. If false, counts all project reports.
1950
1995
  */
1951
- async getIssueCounts() {
1996
+ async getIssueCounts(mineOnly = true) {
1952
1997
  try {
1953
1998
  const testerInfo = await this.getTesterInfo();
1954
1999
  if (!testerInfo) return { open: 0, done: 0, reopened: 0 };
1955
2000
  const { data, error } = await this.supabase.rpc("get_tester_issue_counts", {
1956
2001
  p_project_id: this.config.projectId,
1957
- p_tester_id: testerInfo.id
2002
+ p_tester_id: testerInfo.id,
2003
+ p_mine_only: mineOnly
1958
2004
  });
1959
2005
  if (error) {
1960
2006
  console.error("BugBear: Failed to fetch issue counts", formatPgError(error));
@@ -1974,15 +2020,17 @@ var BugBearClient = class {
1974
2020
  * Get issues for the tester by category.
1975
2021
  * Returns enriched data: done issues include verification proof,
1976
2022
  * reopened issues include original bug context.
2023
+ * @param mineOnly - If true (default), only returns the tester's own reports. If false, returns all project reports.
1977
2024
  */
1978
- async getIssues(category) {
2025
+ async getIssues(category, mineOnly = true) {
1979
2026
  try {
1980
2027
  const testerInfo = await this.getTesterInfo();
1981
2028
  if (!testerInfo) return [];
1982
2029
  const { data, error } = await this.supabase.rpc("get_tester_issues", {
1983
2030
  p_project_id: this.config.projectId,
1984
2031
  p_tester_id: testerInfo.id,
1985
- p_category: category
2032
+ p_category: category,
2033
+ p_mine_only: mineOnly
1986
2034
  });
1987
2035
  if (error) {
1988
2036
  console.error("BugBear: Failed to fetch issues", formatPgError(error));
@@ -2459,6 +2507,8 @@ var BugBearClient = class {
2459
2507
  height: window.screen.height
2460
2508
  };
2461
2509
  }
2510
+ info.viewport = { width: window.innerWidth, height: window.innerHeight };
2511
+ info.devicePixelRatio = window.devicePixelRatio;
2462
2512
  return info;
2463
2513
  }
2464
2514
  return { platform: "web" };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bbearai/core",
3
- "version": "0.9.3",
3
+ "version": "0.9.5",
4
4
  "description": "Core utilities and types for BugBear QA platform",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",