@bbearai/core 0.2.11 → 0.2.13

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
  Core client and utilities for BugBear QA platform.
4
4
 
5
- > **Note:** Most users should install `@bbearai/react` instead, which includes this package automatically.
5
+ > **Note:** Most users should install `@bbearai/react` or `@bbearai/react-native` instead, which include this package automatically.
6
6
 
7
7
  ## Installation
8
8
 
@@ -20,6 +20,7 @@ const bugbear = createBugBear({
20
20
  getCurrentUser: async () => ({
21
21
  id: 'user-123',
22
22
  email: 'user@example.com',
23
+ name: 'Jane Doe', // Optional, used for reporter identity
23
24
  }),
24
25
  });
25
26
 
@@ -28,6 +29,7 @@ await bugbear.submitReport({
28
29
  type: 'bug',
29
30
  description: 'Button does not work',
30
31
  severity: 'high',
32
+ appContext: { currentRoute: '/dashboard' },
31
33
  });
32
34
 
33
35
  // Check if user is a tester
@@ -37,13 +39,84 @@ const isTester = await bugbear.isTester();
37
39
  const assignments = await bugbear.getAssignedTests();
38
40
  ```
39
41
 
40
- ## API Reference
42
+ ## Configuration
41
43
 
42
- ### createBugBear(config)
44
+ ```typescript
45
+ interface BugBearConfig {
46
+ // Required
47
+ projectId: string;
48
+ getCurrentUser: () => Promise<{ id: string; email: string; name?: string } | null>;
49
+
50
+ // Optional — context
51
+ getAppContext?: () => AppContext; // Rich app context (userRole, etc.)
52
+ getNavigationHistory?: () => string[]; // Custom navigation tracking
53
+
54
+ // Optional — callbacks
55
+ onReportSubmitted?: (report: BugBearReport) => void;
56
+ onNavigate?: (route: string) => void; // Deep linking from test cases
57
+
58
+ // Optional — self-hosted
59
+ supabaseUrl?: string;
60
+ supabaseAnonKey?: string;
61
+ }
62
+ ```
63
+
64
+ ### AppContext
65
+
66
+ Provide `getAppContext` to attach rich context to every bug report. This helps the developer fixing the bug understand the user's state:
67
+
68
+ ```typescript
69
+ const bugbear = createBugBear({
70
+ projectId: 'your-project-id',
71
+ getCurrentUser: async () => ({ id: user.id, email: user.email }),
72
+ getAppContext: () => ({
73
+ currentRoute: window.location.pathname,
74
+ userRole: currentUser?.role, // e.g. 'owner', 'manager', 'guest'
75
+ propertyId: currentProperty?.id, // App-specific context
76
+ custom: { // Any additional context
77
+ theme: 'dark',
78
+ locale: 'en-US',
79
+ },
80
+ }),
81
+ });
82
+ ```
83
+
84
+ ## Automatic Context Capture
85
+
86
+ BugBear automatically captures debugging context when initialized via `BugBearProvider` (React or React Native). No manual setup required.
43
87
 
44
- Creates a BugBear client instance.
88
+ **What's captured automatically:**
45
89
 
46
- ### Client Methods
90
+ | Data | Details |
91
+ |------|---------|
92
+ | **Console logs** | Last 50 `console.log/warn/error/info` calls |
93
+ | **Network requests** | Last 20 `fetch()` calls with method, URL, status, duration |
94
+ | **Failed response bodies** | First 500 chars of response body for 4xx/5xx errors |
95
+ | **Navigation history** | Last 20 route changes (via `pushState`/`replaceState`/`popstate`) |
96
+ | **Performance metrics** | Page load time, memory usage (Chrome) |
97
+ | **Environment** | Language, timezone, online status, cookies, localStorage |
98
+
99
+ This data is attached to every bug report as `enhanced_context` and is available to the MCP server's `get_report_context` tool.
100
+
101
+ ### Manual Capture (framework authors)
102
+
103
+ If you're not using `BugBearProvider`, start capture manually:
104
+
105
+ ```typescript
106
+ import { contextCapture } from '@bbearai/core';
107
+
108
+ // Start capturing (call once at app init)
109
+ contextCapture.startCapture();
110
+
111
+ // For React Native: manually track screen changes
112
+ contextCapture.trackNavigation('/home');
113
+ contextCapture.trackNavigation('/settings');
114
+
115
+ // Get captured data (automatically included in submitReport)
116
+ const context = contextCapture.getEnhancedContext();
117
+ ```
118
+
119
+ ## Client Methods
47
120
 
48
121
  | Method | Description |
49
122
  |--------|-------------|
@@ -53,7 +126,9 @@ Creates a BugBear client instance.
53
126
  | `shouldShowWidget()` | Check if widget should be visible (QA enabled + is tester) |
54
127
  | `getTesterInfo()` | Get current tester's info |
55
128
  | `getAssignedTests()` | Get test cases assigned to current tester |
56
- | `trackNavigation(route)` | Track navigation for context |
129
+ | `getAppContext()` | Get current app context (uses config callback or auto-captured) |
130
+ | `getNavigationHistory()` | Get navigation history (config callback > manual > auto-captured) |
131
+ | `trackNavigation(route)` | Manually track a route change |
57
132
  | `uploadScreenshot(file)` | Upload a screenshot |
58
133
 
59
134
  ## Types
@@ -66,7 +141,15 @@ interface BugBearReport {
66
141
  severity?: 'critical' | 'high' | 'medium' | 'low';
67
142
  screenshots?: string[];
68
143
  appContext?: AppContext;
69
- // ... and more
144
+ enhancedContext?: EnhancedBugContext;
145
+ }
146
+
147
+ interface AppContext {
148
+ currentRoute: string;
149
+ screenName?: string;
150
+ userRole?: string;
151
+ propertyId?: string;
152
+ custom?: Record<string, unknown>;
70
153
  }
71
154
 
72
155
  interface TesterInfo {
@@ -96,7 +179,11 @@ interface TestAssignment {
96
179
  If you're building a BugBear integration for a different framework (Vue, Svelte, etc.), use this package as the foundation:
97
180
 
98
181
  ```typescript
99
- import { BugBearClient, createBugBear, BugBearConfig } from '@bbearai/core';
182
+ import { BugBearClient, createBugBear, contextCapture, BugBearConfig } from '@bbearai/core';
183
+
184
+ // Start context capture at app init
185
+ contextCapture.startCapture();
100
186
 
101
187
  // Create your framework-specific wrapper around the client
188
+ const client = createBugBear(config);
102
189
  ```
package/dist/index.d.mts CHANGED
@@ -39,6 +39,8 @@ interface NetworkRequest {
39
39
  duration?: number;
40
40
  timestamp: string;
41
41
  error?: string;
42
+ /** Truncated response body for failed requests (4xx/5xx) */
43
+ responseBody?: string;
42
44
  }
43
45
  /** Enhanced bug context for detailed debugging */
44
46
  interface EnhancedBugContext {
@@ -46,6 +48,8 @@ interface EnhancedBugContext {
46
48
  consoleLogs?: ConsoleLogEntry[];
47
49
  /** Recent network requests (last 20) */
48
50
  networkRequests?: NetworkRequest[];
51
+ /** Auto-captured navigation history (last 20 route changes) */
52
+ navigationHistory?: string[];
49
53
  /** Current Redux/state snapshot (serialized) */
50
54
  stateSnapshot?: Record<string, unknown>;
51
55
  /** Performance metrics */
@@ -602,13 +606,20 @@ declare class BugBearClient {
602
606
  private navigationHistory;
603
607
  constructor(config: BugBearConfig);
604
608
  /**
605
- * Track navigation for context
609
+ * Track navigation for context.
610
+ * Also forwards to contextCapture for auto-tracked navigation.
606
611
  */
607
612
  trackNavigation(route: string): void;
608
613
  /**
609
- * Get current navigation history
614
+ * Get current navigation history.
615
+ * Priority: config callback > manual tracking > auto-captured (pushState/popstate)
610
616
  */
611
617
  getNavigationHistory(): string[];
618
+ /**
619
+ * Get current app context.
620
+ * Uses config.getAppContext() callback if provided, otherwise builds from available data.
621
+ */
622
+ getAppContext(): AppContext;
612
623
  /**
613
624
  * Get current user info from host app or BugBear's own auth
614
625
  */
@@ -651,11 +662,30 @@ declare class BugBearClient {
651
662
  error?: string;
652
663
  durationSeconds?: number;
653
664
  }>;
665
+ /**
666
+ * Pass a test assignment — convenience wrapper around updateAssignmentStatus
667
+ */
668
+ passAssignment(assignmentId: string): Promise<{
669
+ success: boolean;
670
+ error?: string;
671
+ durationSeconds?: number;
672
+ }>;
673
+ /**
674
+ * Fail a test assignment — convenience wrapper around updateAssignmentStatus
675
+ */
676
+ failAssignment(assignmentId: string): Promise<{
677
+ success: boolean;
678
+ error?: string;
679
+ durationSeconds?: number;
680
+ }>;
654
681
  /**
655
682
  * Skip a test assignment with a required reason
656
683
  * Marks the assignment as 'skipped' and records why it was skipped
657
684
  */
658
- skipAssignment(assignmentId: string, reason: SkipReason, notes?: string): Promise<{
685
+ skipAssignment(assignmentId: string, reason: SkipReason | {
686
+ reason: SkipReason;
687
+ notes?: string;
688
+ }, notes?: string): Promise<{
659
689
  success: boolean;
660
690
  error?: string;
661
691
  }>;
@@ -892,16 +922,21 @@ declare function createBugBear(config: BugBearConfig): BugBearClient;
892
922
  */
893
923
 
894
924
  /**
895
- * Context capture singleton that captures console logs and network requests
925
+ * Context capture singleton that captures console logs, network requests,
926
+ * and navigation history automatically.
896
927
  */
897
928
  declare class ContextCaptureManager {
898
929
  private consoleLogs;
899
930
  private networkRequests;
931
+ private navigationHistory;
900
932
  private originalConsole;
901
933
  private originalFetch?;
934
+ private originalPushState?;
935
+ private originalReplaceState?;
936
+ private popstateHandler?;
902
937
  private isCapturing;
903
938
  /**
904
- * Start capturing console logs and network requests
939
+ * Start capturing console logs, network requests, and navigation
905
940
  */
906
941
  startCapture(): void;
907
942
  /**
@@ -912,6 +947,18 @@ declare class ContextCaptureManager {
912
947
  * Get captured context for a bug report
913
948
  */
914
949
  getEnhancedContext(): EnhancedBugContext;
950
+ /**
951
+ * Get the auto-captured navigation history
952
+ */
953
+ getNavigationHistory(): string[];
954
+ /**
955
+ * Get the current route (last entry in navigation history, or window.location)
956
+ */
957
+ getCurrentRoute(): string;
958
+ /**
959
+ * Manually track a navigation event (for React Native or custom routing)
960
+ */
961
+ trackNavigation(route: string): void;
915
962
  /**
916
963
  * Clear captured data
917
964
  */
@@ -926,6 +973,7 @@ declare class ContextCaptureManager {
926
973
  addNetworkRequest(request: NetworkRequest): void;
927
974
  private captureConsole;
928
975
  private captureFetch;
976
+ private captureNavigation;
929
977
  private getPerformanceMetrics;
930
978
  private getEnvironmentInfo;
931
979
  }
package/dist/index.d.ts CHANGED
@@ -39,6 +39,8 @@ interface NetworkRequest {
39
39
  duration?: number;
40
40
  timestamp: string;
41
41
  error?: string;
42
+ /** Truncated response body for failed requests (4xx/5xx) */
43
+ responseBody?: string;
42
44
  }
43
45
  /** Enhanced bug context for detailed debugging */
44
46
  interface EnhancedBugContext {
@@ -46,6 +48,8 @@ interface EnhancedBugContext {
46
48
  consoleLogs?: ConsoleLogEntry[];
47
49
  /** Recent network requests (last 20) */
48
50
  networkRequests?: NetworkRequest[];
51
+ /** Auto-captured navigation history (last 20 route changes) */
52
+ navigationHistory?: string[];
49
53
  /** Current Redux/state snapshot (serialized) */
50
54
  stateSnapshot?: Record<string, unknown>;
51
55
  /** Performance metrics */
@@ -602,13 +606,20 @@ declare class BugBearClient {
602
606
  private navigationHistory;
603
607
  constructor(config: BugBearConfig);
604
608
  /**
605
- * Track navigation for context
609
+ * Track navigation for context.
610
+ * Also forwards to contextCapture for auto-tracked navigation.
606
611
  */
607
612
  trackNavigation(route: string): void;
608
613
  /**
609
- * Get current navigation history
614
+ * Get current navigation history.
615
+ * Priority: config callback > manual tracking > auto-captured (pushState/popstate)
610
616
  */
611
617
  getNavigationHistory(): string[];
618
+ /**
619
+ * Get current app context.
620
+ * Uses config.getAppContext() callback if provided, otherwise builds from available data.
621
+ */
622
+ getAppContext(): AppContext;
612
623
  /**
613
624
  * Get current user info from host app or BugBear's own auth
614
625
  */
@@ -651,11 +662,30 @@ declare class BugBearClient {
651
662
  error?: string;
652
663
  durationSeconds?: number;
653
664
  }>;
665
+ /**
666
+ * Pass a test assignment — convenience wrapper around updateAssignmentStatus
667
+ */
668
+ passAssignment(assignmentId: string): Promise<{
669
+ success: boolean;
670
+ error?: string;
671
+ durationSeconds?: number;
672
+ }>;
673
+ /**
674
+ * Fail a test assignment — convenience wrapper around updateAssignmentStatus
675
+ */
676
+ failAssignment(assignmentId: string): Promise<{
677
+ success: boolean;
678
+ error?: string;
679
+ durationSeconds?: number;
680
+ }>;
654
681
  /**
655
682
  * Skip a test assignment with a required reason
656
683
  * Marks the assignment as 'skipped' and records why it was skipped
657
684
  */
658
- skipAssignment(assignmentId: string, reason: SkipReason, notes?: string): Promise<{
685
+ skipAssignment(assignmentId: string, reason: SkipReason | {
686
+ reason: SkipReason;
687
+ notes?: string;
688
+ }, notes?: string): Promise<{
659
689
  success: boolean;
660
690
  error?: string;
661
691
  }>;
@@ -892,16 +922,21 @@ declare function createBugBear(config: BugBearConfig): BugBearClient;
892
922
  */
893
923
 
894
924
  /**
895
- * Context capture singleton that captures console logs and network requests
925
+ * Context capture singleton that captures console logs, network requests,
926
+ * and navigation history automatically.
896
927
  */
897
928
  declare class ContextCaptureManager {
898
929
  private consoleLogs;
899
930
  private networkRequests;
931
+ private navigationHistory;
900
932
  private originalConsole;
901
933
  private originalFetch?;
934
+ private originalPushState?;
935
+ private originalReplaceState?;
936
+ private popstateHandler?;
902
937
  private isCapturing;
903
938
  /**
904
- * Start capturing console logs and network requests
939
+ * Start capturing console logs, network requests, and navigation
905
940
  */
906
941
  startCapture(): void;
907
942
  /**
@@ -912,6 +947,18 @@ declare class ContextCaptureManager {
912
947
  * Get captured context for a bug report
913
948
  */
914
949
  getEnhancedContext(): EnhancedBugContext;
950
+ /**
951
+ * Get the auto-captured navigation history
952
+ */
953
+ getNavigationHistory(): string[];
954
+ /**
955
+ * Get the current route (last entry in navigation history, or window.location)
956
+ */
957
+ getCurrentRoute(): string;
958
+ /**
959
+ * Manually track a navigation event (for React Native or custom routing)
960
+ */
961
+ trackNavigation(route: string): void;
915
962
  /**
916
963
  * Clear captured data
917
964
  */
@@ -926,6 +973,7 @@ declare class ContextCaptureManager {
926
973
  addNetworkRequest(request: NetworkRequest): void;
927
974
  private captureConsole;
928
975
  private captureFetch;
976
+ private captureNavigation;
929
977
  private getPerformanceMetrics;
930
978
  private getEnvironmentInfo;
931
979
  }
package/dist/index.js CHANGED
@@ -33,21 +33,25 @@ var import_supabase_js = require("@supabase/supabase-js");
33
33
  // src/capture.ts
34
34
  var MAX_CONSOLE_LOGS = 50;
35
35
  var MAX_NETWORK_REQUESTS = 20;
36
+ var MAX_NAVIGATION_HISTORY = 20;
37
+ var MAX_RESPONSE_BODY_LENGTH = 500;
36
38
  var ContextCaptureManager = class {
37
39
  constructor() {
38
40
  this.consoleLogs = [];
39
41
  this.networkRequests = [];
42
+ this.navigationHistory = [];
40
43
  this.originalConsole = {};
41
44
  this.isCapturing = false;
42
45
  }
43
46
  /**
44
- * Start capturing console logs and network requests
47
+ * Start capturing console logs, network requests, and navigation
45
48
  */
46
49
  startCapture() {
47
50
  if (this.isCapturing) return;
48
51
  this.isCapturing = true;
49
52
  this.captureConsole();
50
53
  this.captureFetch();
54
+ this.captureNavigation();
51
55
  }
52
56
  /**
53
57
  * Stop capturing and restore original functions
@@ -62,6 +66,17 @@ var ContextCaptureManager = class {
62
66
  if (this.originalFetch && typeof window !== "undefined") {
63
67
  window.fetch = this.originalFetch;
64
68
  }
69
+ if (typeof window !== "undefined" && typeof history !== "undefined") {
70
+ if (this.originalPushState) {
71
+ history.pushState = this.originalPushState;
72
+ }
73
+ if (this.originalReplaceState) {
74
+ history.replaceState = this.originalReplaceState;
75
+ }
76
+ if (this.popstateHandler) {
77
+ window.removeEventListener("popstate", this.popstateHandler);
78
+ }
79
+ }
65
80
  }
66
81
  /**
67
82
  * Get captured context for a bug report
@@ -70,16 +85,47 @@ var ContextCaptureManager = class {
70
85
  return {
71
86
  consoleLogs: [...this.consoleLogs],
72
87
  networkRequests: [...this.networkRequests],
88
+ navigationHistory: [...this.navigationHistory],
73
89
  performanceMetrics: this.getPerformanceMetrics(),
74
90
  environment: this.getEnvironmentInfo()
75
91
  };
76
92
  }
93
+ /**
94
+ * Get the auto-captured navigation history
95
+ */
96
+ getNavigationHistory() {
97
+ return [...this.navigationHistory];
98
+ }
99
+ /**
100
+ * Get the current route (last entry in navigation history, or window.location)
101
+ */
102
+ getCurrentRoute() {
103
+ if (this.navigationHistory.length > 0) {
104
+ return this.navigationHistory[this.navigationHistory.length - 1];
105
+ }
106
+ if (typeof window !== "undefined") {
107
+ return window.location.pathname;
108
+ }
109
+ return "unknown";
110
+ }
111
+ /**
112
+ * Manually track a navigation event (for React Native or custom routing)
113
+ */
114
+ trackNavigation(route) {
115
+ const last = this.navigationHistory[this.navigationHistory.length - 1];
116
+ if (route === last) return;
117
+ this.navigationHistory.push(route);
118
+ if (this.navigationHistory.length > MAX_NAVIGATION_HISTORY) {
119
+ this.navigationHistory.shift();
120
+ }
121
+ }
77
122
  /**
78
123
  * Clear captured data
79
124
  */
80
125
  clear() {
81
126
  this.consoleLogs = [];
82
127
  this.networkRequests = [];
128
+ this.navigationHistory = [];
83
129
  }
84
130
  /**
85
131
  * Add a log entry manually (for custom logging)
@@ -136,14 +182,25 @@ var ContextCaptureManager = class {
136
182
  const method = init?.method || "GET";
137
183
  try {
138
184
  const response = await self.originalFetch.call(window, input, init);
139
- self.addNetworkRequest({
185
+ const requestEntry = {
140
186
  method,
141
187
  url: url.slice(0, 200),
142
188
  // Limit URL length
143
189
  status: response.status,
144
190
  duration: Date.now() - startTime,
145
191
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
146
- });
192
+ };
193
+ if (response.status >= 400) {
194
+ try {
195
+ const cloned = response.clone();
196
+ const body = await cloned.text();
197
+ if (body) {
198
+ requestEntry.responseBody = body.slice(0, MAX_RESPONSE_BODY_LENGTH);
199
+ }
200
+ } catch {
201
+ }
202
+ }
203
+ self.addNetworkRequest(requestEntry);
147
204
  return response;
148
205
  } catch (error) {
149
206
  self.addNetworkRequest({
@@ -157,6 +214,25 @@ var ContextCaptureManager = class {
157
214
  }
158
215
  };
159
216
  }
217
+ captureNavigation() {
218
+ if (typeof window === "undefined" || typeof history === "undefined") return;
219
+ this.trackNavigation(window.location.pathname);
220
+ const self = this;
221
+ this.originalPushState = history.pushState;
222
+ history.pushState = function(...args) {
223
+ self.originalPushState.apply(history, args);
224
+ self.trackNavigation(window.location.pathname);
225
+ };
226
+ this.originalReplaceState = history.replaceState;
227
+ history.replaceState = function(...args) {
228
+ self.originalReplaceState.apply(history, args);
229
+ self.trackNavigation(window.location.pathname);
230
+ };
231
+ this.popstateHandler = () => {
232
+ self.trackNavigation(window.location.pathname);
233
+ };
234
+ window.addEventListener("popstate", this.popstateHandler);
235
+ }
160
236
  getPerformanceMetrics() {
161
237
  if (typeof window === "undefined" || typeof performance === "undefined") return void 0;
162
238
  const metrics = {};
@@ -218,22 +294,40 @@ var BugBearClient = class {
218
294
  );
219
295
  }
220
296
  /**
221
- * Track navigation for context
297
+ * Track navigation for context.
298
+ * Also forwards to contextCapture for auto-tracked navigation.
222
299
  */
223
300
  trackNavigation(route) {
224
301
  this.navigationHistory.push(route);
225
302
  if (this.navigationHistory.length > 10) {
226
303
  this.navigationHistory.shift();
227
304
  }
305
+ contextCapture.trackNavigation(route);
228
306
  }
229
307
  /**
230
- * Get current navigation history
308
+ * Get current navigation history.
309
+ * Priority: config callback > manual tracking > auto-captured (pushState/popstate)
231
310
  */
232
311
  getNavigationHistory() {
233
312
  if (this.config.getNavigationHistory) {
234
313
  return this.config.getNavigationHistory();
235
314
  }
236
- return [...this.navigationHistory];
315
+ if (this.navigationHistory.length > 0) {
316
+ return [...this.navigationHistory];
317
+ }
318
+ return contextCapture.getNavigationHistory();
319
+ }
320
+ /**
321
+ * Get current app context.
322
+ * Uses config.getAppContext() callback if provided, otherwise builds from available data.
323
+ */
324
+ getAppContext() {
325
+ if (this.config.getAppContext) {
326
+ return this.config.getAppContext();
327
+ }
328
+ return {
329
+ currentRoute: contextCapture.getCurrentRoute()
330
+ };
237
331
  }
238
332
  /**
239
333
  * Get current user info from host app or BugBear's own auth
@@ -536,19 +630,40 @@ var BugBearClient = class {
536
630
  return { success: false, error: message };
537
631
  }
538
632
  }
633
+ /**
634
+ * Pass a test assignment — convenience wrapper around updateAssignmentStatus
635
+ */
636
+ async passAssignment(assignmentId) {
637
+ return this.updateAssignmentStatus(assignmentId, "passed");
638
+ }
639
+ /**
640
+ * Fail a test assignment — convenience wrapper around updateAssignmentStatus
641
+ */
642
+ async failAssignment(assignmentId) {
643
+ return this.updateAssignmentStatus(assignmentId, "failed");
644
+ }
539
645
  /**
540
646
  * Skip a test assignment with a required reason
541
647
  * Marks the assignment as 'skipped' and records why it was skipped
542
648
  */
543
649
  async skipAssignment(assignmentId, reason, notes) {
650
+ let actualReason;
651
+ let actualNotes;
652
+ if (typeof reason === "object") {
653
+ actualReason = reason.reason;
654
+ actualNotes = reason.notes;
655
+ } else {
656
+ actualReason = reason;
657
+ actualNotes = notes;
658
+ }
544
659
  try {
545
660
  const updateData = {
546
661
  status: "skipped",
547
- skip_reason: reason,
662
+ skip_reason: actualReason,
548
663
  completed_at: (/* @__PURE__ */ new Date()).toISOString()
549
664
  };
550
- if (notes) {
551
- updateData.notes = notes;
665
+ if (actualNotes) {
666
+ updateData.notes = actualNotes;
552
667
  }
553
668
  const { error } = await this.supabase.from("test_assignments").update(updateData).eq("id", assignmentId);
554
669
  if (error) {
package/dist/index.mjs CHANGED
@@ -4,21 +4,25 @@ import { createClient } from "@supabase/supabase-js";
4
4
  // src/capture.ts
5
5
  var MAX_CONSOLE_LOGS = 50;
6
6
  var MAX_NETWORK_REQUESTS = 20;
7
+ var MAX_NAVIGATION_HISTORY = 20;
8
+ var MAX_RESPONSE_BODY_LENGTH = 500;
7
9
  var ContextCaptureManager = class {
8
10
  constructor() {
9
11
  this.consoleLogs = [];
10
12
  this.networkRequests = [];
13
+ this.navigationHistory = [];
11
14
  this.originalConsole = {};
12
15
  this.isCapturing = false;
13
16
  }
14
17
  /**
15
- * Start capturing console logs and network requests
18
+ * Start capturing console logs, network requests, and navigation
16
19
  */
17
20
  startCapture() {
18
21
  if (this.isCapturing) return;
19
22
  this.isCapturing = true;
20
23
  this.captureConsole();
21
24
  this.captureFetch();
25
+ this.captureNavigation();
22
26
  }
23
27
  /**
24
28
  * Stop capturing and restore original functions
@@ -33,6 +37,17 @@ var ContextCaptureManager = class {
33
37
  if (this.originalFetch && typeof window !== "undefined") {
34
38
  window.fetch = this.originalFetch;
35
39
  }
40
+ if (typeof window !== "undefined" && typeof history !== "undefined") {
41
+ if (this.originalPushState) {
42
+ history.pushState = this.originalPushState;
43
+ }
44
+ if (this.originalReplaceState) {
45
+ history.replaceState = this.originalReplaceState;
46
+ }
47
+ if (this.popstateHandler) {
48
+ window.removeEventListener("popstate", this.popstateHandler);
49
+ }
50
+ }
36
51
  }
37
52
  /**
38
53
  * Get captured context for a bug report
@@ -41,16 +56,47 @@ var ContextCaptureManager = class {
41
56
  return {
42
57
  consoleLogs: [...this.consoleLogs],
43
58
  networkRequests: [...this.networkRequests],
59
+ navigationHistory: [...this.navigationHistory],
44
60
  performanceMetrics: this.getPerformanceMetrics(),
45
61
  environment: this.getEnvironmentInfo()
46
62
  };
47
63
  }
64
+ /**
65
+ * Get the auto-captured navigation history
66
+ */
67
+ getNavigationHistory() {
68
+ return [...this.navigationHistory];
69
+ }
70
+ /**
71
+ * Get the current route (last entry in navigation history, or window.location)
72
+ */
73
+ getCurrentRoute() {
74
+ if (this.navigationHistory.length > 0) {
75
+ return this.navigationHistory[this.navigationHistory.length - 1];
76
+ }
77
+ if (typeof window !== "undefined") {
78
+ return window.location.pathname;
79
+ }
80
+ return "unknown";
81
+ }
82
+ /**
83
+ * Manually track a navigation event (for React Native or custom routing)
84
+ */
85
+ trackNavigation(route) {
86
+ const last = this.navigationHistory[this.navigationHistory.length - 1];
87
+ if (route === last) return;
88
+ this.navigationHistory.push(route);
89
+ if (this.navigationHistory.length > MAX_NAVIGATION_HISTORY) {
90
+ this.navigationHistory.shift();
91
+ }
92
+ }
48
93
  /**
49
94
  * Clear captured data
50
95
  */
51
96
  clear() {
52
97
  this.consoleLogs = [];
53
98
  this.networkRequests = [];
99
+ this.navigationHistory = [];
54
100
  }
55
101
  /**
56
102
  * Add a log entry manually (for custom logging)
@@ -107,14 +153,25 @@ var ContextCaptureManager = class {
107
153
  const method = init?.method || "GET";
108
154
  try {
109
155
  const response = await self.originalFetch.call(window, input, init);
110
- self.addNetworkRequest({
156
+ const requestEntry = {
111
157
  method,
112
158
  url: url.slice(0, 200),
113
159
  // Limit URL length
114
160
  status: response.status,
115
161
  duration: Date.now() - startTime,
116
162
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
117
- });
163
+ };
164
+ if (response.status >= 400) {
165
+ try {
166
+ const cloned = response.clone();
167
+ const body = await cloned.text();
168
+ if (body) {
169
+ requestEntry.responseBody = body.slice(0, MAX_RESPONSE_BODY_LENGTH);
170
+ }
171
+ } catch {
172
+ }
173
+ }
174
+ self.addNetworkRequest(requestEntry);
118
175
  return response;
119
176
  } catch (error) {
120
177
  self.addNetworkRequest({
@@ -128,6 +185,25 @@ var ContextCaptureManager = class {
128
185
  }
129
186
  };
130
187
  }
188
+ captureNavigation() {
189
+ if (typeof window === "undefined" || typeof history === "undefined") return;
190
+ this.trackNavigation(window.location.pathname);
191
+ const self = this;
192
+ this.originalPushState = history.pushState;
193
+ history.pushState = function(...args) {
194
+ self.originalPushState.apply(history, args);
195
+ self.trackNavigation(window.location.pathname);
196
+ };
197
+ this.originalReplaceState = history.replaceState;
198
+ history.replaceState = function(...args) {
199
+ self.originalReplaceState.apply(history, args);
200
+ self.trackNavigation(window.location.pathname);
201
+ };
202
+ this.popstateHandler = () => {
203
+ self.trackNavigation(window.location.pathname);
204
+ };
205
+ window.addEventListener("popstate", this.popstateHandler);
206
+ }
131
207
  getPerformanceMetrics() {
132
208
  if (typeof window === "undefined" || typeof performance === "undefined") return void 0;
133
209
  const metrics = {};
@@ -189,22 +265,40 @@ var BugBearClient = class {
189
265
  );
190
266
  }
191
267
  /**
192
- * Track navigation for context
268
+ * Track navigation for context.
269
+ * Also forwards to contextCapture for auto-tracked navigation.
193
270
  */
194
271
  trackNavigation(route) {
195
272
  this.navigationHistory.push(route);
196
273
  if (this.navigationHistory.length > 10) {
197
274
  this.navigationHistory.shift();
198
275
  }
276
+ contextCapture.trackNavigation(route);
199
277
  }
200
278
  /**
201
- * Get current navigation history
279
+ * Get current navigation history.
280
+ * Priority: config callback > manual tracking > auto-captured (pushState/popstate)
202
281
  */
203
282
  getNavigationHistory() {
204
283
  if (this.config.getNavigationHistory) {
205
284
  return this.config.getNavigationHistory();
206
285
  }
207
- return [...this.navigationHistory];
286
+ if (this.navigationHistory.length > 0) {
287
+ return [...this.navigationHistory];
288
+ }
289
+ return contextCapture.getNavigationHistory();
290
+ }
291
+ /**
292
+ * Get current app context.
293
+ * Uses config.getAppContext() callback if provided, otherwise builds from available data.
294
+ */
295
+ getAppContext() {
296
+ if (this.config.getAppContext) {
297
+ return this.config.getAppContext();
298
+ }
299
+ return {
300
+ currentRoute: contextCapture.getCurrentRoute()
301
+ };
208
302
  }
209
303
  /**
210
304
  * Get current user info from host app or BugBear's own auth
@@ -507,19 +601,40 @@ var BugBearClient = class {
507
601
  return { success: false, error: message };
508
602
  }
509
603
  }
604
+ /**
605
+ * Pass a test assignment — convenience wrapper around updateAssignmentStatus
606
+ */
607
+ async passAssignment(assignmentId) {
608
+ return this.updateAssignmentStatus(assignmentId, "passed");
609
+ }
610
+ /**
611
+ * Fail a test assignment — convenience wrapper around updateAssignmentStatus
612
+ */
613
+ async failAssignment(assignmentId) {
614
+ return this.updateAssignmentStatus(assignmentId, "failed");
615
+ }
510
616
  /**
511
617
  * Skip a test assignment with a required reason
512
618
  * Marks the assignment as 'skipped' and records why it was skipped
513
619
  */
514
620
  async skipAssignment(assignmentId, reason, notes) {
621
+ let actualReason;
622
+ let actualNotes;
623
+ if (typeof reason === "object") {
624
+ actualReason = reason.reason;
625
+ actualNotes = reason.notes;
626
+ } else {
627
+ actualReason = reason;
628
+ actualNotes = notes;
629
+ }
515
630
  try {
516
631
  const updateData = {
517
632
  status: "skipped",
518
- skip_reason: reason,
633
+ skip_reason: actualReason,
519
634
  completed_at: (/* @__PURE__ */ new Date()).toISOString()
520
635
  };
521
- if (notes) {
522
- updateData.notes = notes;
636
+ if (actualNotes) {
637
+ updateData.notes = actualNotes;
523
638
  }
524
639
  const { error } = await this.supabase.from("test_assignments").update(updateData).eq("id", assignmentId);
525
640
  if (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bbearai/core",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
4
4
  "description": "Core utilities and types for BugBear QA platform",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",