@bbearai/core 0.2.15 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -3,7 +3,7 @@
3
3
  */
4
4
  type ReportType = 'bug' | 'feedback' | 'suggestion' | 'test_pass' | 'test_fail';
5
5
  type Severity = 'critical' | 'high' | 'medium' | 'low';
6
- type ReportStatus = 'new' | 'triaging' | 'confirmed' | 'in_progress' | 'fixed' | 'verified' | 'wont_fix' | 'duplicate';
6
+ type ReportStatus = 'new' | 'triaging' | 'confirmed' | 'in_progress' | 'fixed' | 'ready_to_test' | 'verified' | 'resolved' | 'reviewed' | 'closed' | 'wont_fix' | 'duplicate';
7
7
  interface AppContext {
8
8
  /** Current route/screen path */
9
9
  currentRoute: string;
@@ -160,6 +160,11 @@ interface BugBearConfig {
160
160
  * the full web experience (analytics, discussions, history, etc.)
161
161
  */
162
162
  dashboardUrl?: string;
163
+ /**
164
+ * Called when an error occurs inside the BugBear SDK.
165
+ * Wire this to your error reporting service (e.g. Sentry.captureException).
166
+ */
167
+ onError?: (error: Error, context?: Record<string, unknown>) => void;
163
168
  }
164
169
  interface BugBearTheme {
165
170
  /** Primary brand color */
@@ -710,6 +715,19 @@ declare class BugBearClient {
710
715
  * Uses parameterized RPC function to prevent SQL injection
711
716
  */
712
717
  getTesterInfo(): Promise<TesterInfo | null>;
718
+ /**
719
+ * Get detailed assignment stats for the current tester via RPC.
720
+ * Returns counts by status (pending, in_progress, passed, failed, blocked, skipped, total).
721
+ */
722
+ getTesterStats(): Promise<{
723
+ pending: number;
724
+ in_progress: number;
725
+ passed: number;
726
+ failed: number;
727
+ blocked: number;
728
+ skipped: number;
729
+ total: number;
730
+ } | null>;
713
731
  /**
714
732
  * Basic email format validation (defense in depth)
715
733
  */
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  */
4
4
  type ReportType = 'bug' | 'feedback' | 'suggestion' | 'test_pass' | 'test_fail';
5
5
  type Severity = 'critical' | 'high' | 'medium' | 'low';
6
- type ReportStatus = 'new' | 'triaging' | 'confirmed' | 'in_progress' | 'fixed' | 'verified' | 'wont_fix' | 'duplicate';
6
+ type ReportStatus = 'new' | 'triaging' | 'confirmed' | 'in_progress' | 'fixed' | 'ready_to_test' | 'verified' | 'resolved' | 'reviewed' | 'closed' | 'wont_fix' | 'duplicate';
7
7
  interface AppContext {
8
8
  /** Current route/screen path */
9
9
  currentRoute: string;
@@ -160,6 +160,11 @@ interface BugBearConfig {
160
160
  * the full web experience (analytics, discussions, history, etc.)
161
161
  */
162
162
  dashboardUrl?: string;
163
+ /**
164
+ * Called when an error occurs inside the BugBear SDK.
165
+ * Wire this to your error reporting service (e.g. Sentry.captureException).
166
+ */
167
+ onError?: (error: Error, context?: Record<string, unknown>) => void;
163
168
  }
164
169
  interface BugBearTheme {
165
170
  /** Primary brand color */
@@ -710,6 +715,19 @@ declare class BugBearClient {
710
715
  * Uses parameterized RPC function to prevent SQL injection
711
716
  */
712
717
  getTesterInfo(): Promise<TesterInfo | null>;
718
+ /**
719
+ * Get detailed assignment stats for the current tester via RPC.
720
+ * Returns counts by status (pending, in_progress, passed, failed, blocked, skipped, total).
721
+ */
722
+ getTesterStats(): Promise<{
723
+ pending: number;
724
+ in_progress: number;
725
+ passed: number;
726
+ failed: number;
727
+ blocked: number;
728
+ skipped: number;
729
+ total: number;
730
+ } | null>;
713
731
  /**
714
732
  * Basic email format validation (defense in depth)
715
733
  */
package/dist/index.js CHANGED
@@ -273,6 +273,11 @@ function captureError(error, errorInfo) {
273
273
  }
274
274
 
275
275
  // src/client.ts
276
+ var formatPgError = (e) => {
277
+ if (!e || typeof e !== "object") return { raw: e };
278
+ const { message, code, details, hint } = e;
279
+ return { message, code, details, hint };
280
+ };
276
281
  var DEFAULT_SUPABASE_URL = "https://kyxgzjnqgvapvlnvqawz.supabase.co";
277
282
  var getEnvVar = (key) => {
278
283
  try {
@@ -440,9 +445,9 @@ var BugBearClient = class {
440
445
  sort_order
441
446
  )
442
447
  )
443
- `).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true });
448
+ `).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true }).limit(100);
444
449
  if (error) {
445
- console.error("BugBear: Failed to fetch assignments", error);
450
+ console.error("BugBear: Failed to fetch assignments", formatPgError(error));
446
451
  return [];
447
452
  }
448
453
  const mapped = (data || []).map((item) => ({
@@ -594,7 +599,7 @@ var BugBearClient = class {
594
599
  if (options?.testResult) {
595
600
  updateData.test_result = options.testResult;
596
601
  }
597
- const { error } = await this.supabase.from("test_assignments").update(updateData).eq("id", assignmentId);
602
+ const { data: updatedRow, error } = await this.supabase.from("test_assignments").update(updateData).eq("id", assignmentId).eq("status", currentAssignment.status).select("id").maybeSingle();
598
603
  if (error) {
599
604
  console.error("BugBear: Failed to update assignment status", {
600
605
  message: error.message,
@@ -607,6 +612,9 @@ var BugBearClient = class {
607
612
  });
608
613
  return { success: false, error: error.message };
609
614
  }
615
+ if (!updatedRow) {
616
+ return { success: false, error: "Assignment status has changed. Please refresh and try again." };
617
+ }
610
618
  if (options?.feedback && ["passed", "failed", "blocked"].includes(status)) {
611
619
  const { data: assignmentData, error: fetchError2 } = await this.supabase.from("test_assignments").select("test_case_id").eq("id", assignmentId).single();
612
620
  if (fetchError2) {
@@ -667,7 +675,7 @@ var BugBearClient = class {
667
675
  }
668
676
  const { error } = await this.supabase.from("test_assignments").update(updateData).eq("id", assignmentId);
669
677
  if (error) {
670
- console.error("BugBear: Failed to skip assignment", error);
678
+ console.error("BugBear: Failed to skip assignment", formatPgError(error));
671
679
  return { success: false, error: error.message };
672
680
  }
673
681
  return { success: true };
@@ -833,6 +841,28 @@ var BugBearClient = class {
833
841
  return null;
834
842
  }
835
843
  }
844
+ /**
845
+ * Get detailed assignment stats for the current tester via RPC.
846
+ * Returns counts by status (pending, in_progress, passed, failed, blocked, skipped, total).
847
+ */
848
+ async getTesterStats() {
849
+ try {
850
+ const testerInfo = await this.getTesterInfo();
851
+ if (!testerInfo) return null;
852
+ const { data, error } = await this.supabase.rpc("get_tester_stats", {
853
+ p_project_id: this.config.projectId,
854
+ p_tester_id: testerInfo.id
855
+ });
856
+ if (error) {
857
+ console.error("BugBear: Failed to fetch tester stats", formatPgError(error));
858
+ return null;
859
+ }
860
+ return data;
861
+ } catch (err) {
862
+ console.error("BugBear: Error fetching tester stats", err);
863
+ return null;
864
+ }
865
+ }
836
866
  /**
837
867
  * Basic email format validation (defense in depth)
838
868
  */
@@ -975,7 +1005,7 @@ var BugBearClient = class {
975
1005
  if (updates.platforms !== void 0) updateData.platforms = updates.platforms;
976
1006
  const { error } = await this.supabase.from("testers").update(updateData).eq("id", testerInfo.id);
977
1007
  if (error) {
978
- console.error("BugBear: updateTesterProfile error", error);
1008
+ console.error("BugBear: updateTesterProfile error", formatPgError(error));
979
1009
  return { success: false, error: error.message };
980
1010
  }
981
1011
  return { success: true };
@@ -997,14 +1027,14 @@ var BugBearClient = class {
997
1027
  */
998
1028
  async isQAEnabled() {
999
1029
  try {
1000
- const { data, error } = await this.supabase.from("projects").select("is_qa_enabled").eq("id", this.config.projectId).single();
1030
+ const { data, error } = await this.supabase.rpc("check_qa_enabled", {
1031
+ p_project_id: this.config.projectId
1032
+ });
1001
1033
  if (error) {
1002
- if (error.code !== "PGRST116") {
1003
- console.warn("BugBear: Could not check QA status", error.message || error.code || "Unknown error");
1004
- }
1034
+ console.warn("BugBear: Could not check QA status", error.message || error.code || "Unknown error");
1005
1035
  return true;
1006
1036
  }
1007
- return data?.is_qa_enabled ?? true;
1037
+ return data ?? true;
1008
1038
  } catch (err) {
1009
1039
  return true;
1010
1040
  }
@@ -1034,7 +1064,7 @@ var BugBearClient = class {
1034
1064
  upsert: false
1035
1065
  });
1036
1066
  if (error) {
1037
- console.error("BugBear: Failed to upload screenshot", error);
1067
+ console.error("BugBear: Failed to upload screenshot", formatPgError(error));
1038
1068
  return null;
1039
1069
  }
1040
1070
  const { data: { publicUrl } } = this.supabase.storage.from(bucket).getPublicUrl(path);
@@ -1065,7 +1095,7 @@ var BugBearClient = class {
1065
1095
  upsert: false
1066
1096
  });
1067
1097
  if (error) {
1068
- console.error("BugBear: Failed to upload image from URI", error);
1098
+ console.error("BugBear: Failed to upload image from URI", formatPgError(error));
1069
1099
  return null;
1070
1100
  }
1071
1101
  const { data: { publicUrl } } = this.supabase.storage.from(bucket).getPublicUrl(path);
@@ -1140,7 +1170,7 @@ var BugBearClient = class {
1140
1170
  }
1141
1171
  const { data, error } = await query;
1142
1172
  if (error) {
1143
- console.error("BugBear: Failed to fetch fix requests", error);
1173
+ console.error("BugBear: Failed to fetch fix requests", formatPgError(error));
1144
1174
  return [];
1145
1175
  }
1146
1176
  return (data || []).map((fr) => ({
@@ -1166,75 +1196,35 @@ var BugBearClient = class {
1166
1196
  try {
1167
1197
  const testerInfo = await this.getTesterInfo();
1168
1198
  if (!testerInfo) return [];
1169
- const { data: threads, error } = await this.supabase.from("discussion_threads").select(`
1170
- id,
1171
- subject,
1172
- thread_type,
1173
- priority,
1174
- is_pinned,
1175
- is_resolved,
1176
- last_message_at,
1177
- created_at
1178
- `).eq("project_id", this.config.projectId).or(`audience.eq.all,audience_tester_ids.cs.{${testerInfo.id}}`).order("is_pinned", { ascending: false }).order("last_message_at", { ascending: false });
1199
+ const { data, error } = await this.supabase.rpc("get_threads_with_unread", {
1200
+ p_project_id: this.config.projectId,
1201
+ p_tester_id: testerInfo.id
1202
+ });
1179
1203
  if (error) {
1180
- console.error("BugBear: Failed to fetch threads", error);
1204
+ console.error("BugBear: Failed to fetch threads via RPC", formatPgError(error));
1181
1205
  return [];
1182
1206
  }
1183
- if (!threads || threads.length === 0) return [];
1184
- const threadIds = threads.map((t) => t.id);
1185
- const { data: readStatuses } = await this.supabase.from("discussion_read_status").select("thread_id, last_read_at, last_read_message_id").eq("tester_id", testerInfo.id).in("thread_id", threadIds);
1186
- const readStatusMap = new Map(
1187
- (readStatuses || []).map((rs) => [rs.thread_id, rs])
1188
- );
1189
- const { data: lastMessages } = await this.supabase.from("discussion_messages").select(`
1190
- id,
1191
- thread_id,
1192
- sender_type,
1193
- sender_name,
1194
- content,
1195
- created_at,
1196
- attachments
1197
- `).in("thread_id", threadIds).order("created_at", { ascending: false });
1198
- const lastMessageMap = /* @__PURE__ */ new Map();
1199
- for (const msg of lastMessages || []) {
1200
- if (!lastMessageMap.has(msg.thread_id)) {
1201
- lastMessageMap.set(msg.thread_id, msg);
1202
- }
1203
- }
1204
- const unreadCounts = await Promise.all(
1205
- threads.map(async (thread) => {
1206
- const readStatus = readStatusMap.get(thread.id);
1207
- const lastReadAt = readStatus?.last_read_at || "1970-01-01T00:00:00Z";
1208
- const { count, error: countError } = await this.supabase.from("discussion_messages").select("*", { count: "exact", head: true }).eq("thread_id", thread.id).gt("created_at", lastReadAt);
1209
- return { threadId: thread.id, count: countError ? 0 : count || 0 };
1210
- })
1211
- );
1212
- const unreadCountMap = new Map(
1213
- unreadCounts.map((uc) => [uc.threadId, uc.count])
1214
- );
1215
- return threads.map((thread) => {
1216
- const lastMsg = lastMessageMap.get(thread.id);
1217
- return {
1218
- id: thread.id,
1219
- subject: thread.subject,
1220
- threadType: thread.thread_type,
1221
- priority: thread.priority,
1222
- isPinned: thread.is_pinned,
1223
- isResolved: thread.is_resolved,
1224
- lastMessageAt: thread.last_message_at,
1225
- createdAt: thread.created_at,
1226
- unreadCount: unreadCountMap.get(thread.id) || 0,
1227
- lastMessage: lastMsg ? {
1228
- id: lastMsg.id,
1229
- threadId: lastMsg.thread_id,
1230
- senderType: lastMsg.sender_type,
1231
- senderName: lastMsg.sender_name,
1232
- content: lastMsg.content,
1233
- createdAt: lastMsg.created_at,
1234
- attachments: lastMsg.attachments || []
1235
- } : void 0
1236
- };
1237
- });
1207
+ if (!data || data.length === 0) return [];
1208
+ return data.map((row) => ({
1209
+ id: row.thread_id,
1210
+ subject: row.thread_subject,
1211
+ threadType: row.thread_type,
1212
+ priority: row.thread_priority,
1213
+ isPinned: row.is_pinned,
1214
+ isResolved: row.is_resolved,
1215
+ lastMessageAt: row.last_message_at,
1216
+ createdAt: row.created_at,
1217
+ unreadCount: Number(row.unread_count) || 0,
1218
+ lastMessage: row.last_message_preview ? {
1219
+ id: "",
1220
+ threadId: row.thread_id,
1221
+ senderType: row.last_message_sender_type || "system",
1222
+ senderName: row.last_message_sender_name || "",
1223
+ content: row.last_message_preview,
1224
+ createdAt: row.last_message_at,
1225
+ attachments: []
1226
+ } : void 0
1227
+ }));
1238
1228
  } catch (err) {
1239
1229
  console.error("BugBear: Error fetching threads", err);
1240
1230
  return [];
@@ -1253,9 +1243,9 @@ var BugBearClient = class {
1253
1243
  content,
1254
1244
  created_at,
1255
1245
  attachments
1256
- `).eq("thread_id", threadId).order("created_at", { ascending: true });
1246
+ `).eq("thread_id", threadId).order("created_at", { ascending: true }).limit(200);
1257
1247
  if (error) {
1258
- console.error("BugBear: Failed to fetch messages", error);
1248
+ console.error("BugBear: Failed to fetch messages", formatPgError(error));
1259
1249
  return [];
1260
1250
  }
1261
1251
  return (data || []).map((msg) => ({
@@ -1299,10 +1289,9 @@ var BugBearClient = class {
1299
1289
  }
1300
1290
  const { error } = await this.supabase.from("discussion_messages").insert(insertData);
1301
1291
  if (error) {
1302
- console.error("BugBear: Failed to send message", error);
1292
+ console.error("BugBear: Failed to send message", formatPgError(error));
1303
1293
  return false;
1304
1294
  }
1305
- await this.supabase.from("discussion_threads").update({ last_message_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", threadId);
1306
1295
  await this.markThreadAsRead(threadId);
1307
1296
  return true;
1308
1297
  } catch (err) {
@@ -1416,7 +1405,7 @@ var BugBearClient = class {
1416
1405
  p_platform: options.platform || null
1417
1406
  });
1418
1407
  if (error) {
1419
- console.error("BugBear: Failed to start session", error);
1408
+ console.error("BugBear: Failed to start session", formatPgError(error));
1420
1409
  return { success: false, error: error.message };
1421
1410
  }
1422
1411
  const session = await this.getSession(data);
@@ -1441,7 +1430,7 @@ var BugBearClient = class {
1441
1430
  p_routes_covered: options.routesCovered || null
1442
1431
  });
1443
1432
  if (error) {
1444
- console.error("BugBear: Failed to end session", error);
1433
+ console.error("BugBear: Failed to end session", formatPgError(error));
1445
1434
  return { success: false, error: error.message };
1446
1435
  }
1447
1436
  const session = this.transformSession(data);
@@ -1489,7 +1478,7 @@ var BugBearClient = class {
1489
1478
  if (!testerInfo) return [];
1490
1479
  const { data, error } = await this.supabase.from("qa_sessions").select("*").eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).order("started_at", { ascending: false }).limit(limit);
1491
1480
  if (error) {
1492
- console.error("BugBear: Failed to fetch session history", error);
1481
+ console.error("BugBear: Failed to fetch session history", formatPgError(error));
1493
1482
  return [];
1494
1483
  }
1495
1484
  return (data || []).map((s) => this.transformSession(s));
@@ -1517,7 +1506,7 @@ var BugBearClient = class {
1517
1506
  p_app_context: options.appContext || null
1518
1507
  });
1519
1508
  if (error) {
1520
- console.error("BugBear: Failed to add finding", error);
1509
+ console.error("BugBear: Failed to add finding", formatPgError(error));
1521
1510
  return { success: false, error: error.message };
1522
1511
  }
1523
1512
  const finding = this.transformFinding(data);
@@ -1533,9 +1522,9 @@ var BugBearClient = class {
1533
1522
  */
1534
1523
  async getSessionFindings(sessionId) {
1535
1524
  try {
1536
- const { data, error } = await this.supabase.from("qa_findings").select("*").eq("session_id", sessionId).order("created_at", { ascending: true });
1525
+ const { data, error } = await this.supabase.from("qa_findings").select("*").eq("session_id", sessionId).order("created_at", { ascending: true }).limit(100);
1537
1526
  if (error) {
1538
- console.error("BugBear: Failed to fetch findings", error);
1527
+ console.error("BugBear: Failed to fetch findings", formatPgError(error));
1539
1528
  return [];
1540
1529
  }
1541
1530
  return (data || []).map((f) => this.transformFinding(f));
@@ -1553,7 +1542,7 @@ var BugBearClient = class {
1553
1542
  p_finding_id: findingId
1554
1543
  });
1555
1544
  if (error) {
1556
- console.error("BugBear: Failed to convert finding", error);
1545
+ console.error("BugBear: Failed to convert finding", formatPgError(error));
1557
1546
  return { success: false, error: error.message };
1558
1547
  }
1559
1548
  return { success: true, bugId: data };
@@ -1574,7 +1563,7 @@ var BugBearClient = class {
1574
1563
  dismissed_at: (/* @__PURE__ */ new Date()).toISOString()
1575
1564
  }).eq("id", findingId);
1576
1565
  if (error) {
1577
- console.error("BugBear: Failed to dismiss finding", error);
1566
+ console.error("BugBear: Failed to dismiss finding", formatPgError(error));
1578
1567
  return { success: false, error: error.message };
1579
1568
  }
1580
1569
  return { success: true };
package/dist/index.mjs CHANGED
@@ -244,6 +244,11 @@ function captureError(error, errorInfo) {
244
244
  }
245
245
 
246
246
  // src/client.ts
247
+ var formatPgError = (e) => {
248
+ if (!e || typeof e !== "object") return { raw: e };
249
+ const { message, code, details, hint } = e;
250
+ return { message, code, details, hint };
251
+ };
247
252
  var DEFAULT_SUPABASE_URL = "https://kyxgzjnqgvapvlnvqawz.supabase.co";
248
253
  var getEnvVar = (key) => {
249
254
  try {
@@ -411,9 +416,9 @@ var BugBearClient = class {
411
416
  sort_order
412
417
  )
413
418
  )
414
- `).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true });
419
+ `).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true }).limit(100);
415
420
  if (error) {
416
- console.error("BugBear: Failed to fetch assignments", error);
421
+ console.error("BugBear: Failed to fetch assignments", formatPgError(error));
417
422
  return [];
418
423
  }
419
424
  const mapped = (data || []).map((item) => ({
@@ -565,7 +570,7 @@ var BugBearClient = class {
565
570
  if (options?.testResult) {
566
571
  updateData.test_result = options.testResult;
567
572
  }
568
- const { error } = await this.supabase.from("test_assignments").update(updateData).eq("id", assignmentId);
573
+ const { data: updatedRow, error } = await this.supabase.from("test_assignments").update(updateData).eq("id", assignmentId).eq("status", currentAssignment.status).select("id").maybeSingle();
569
574
  if (error) {
570
575
  console.error("BugBear: Failed to update assignment status", {
571
576
  message: error.message,
@@ -578,6 +583,9 @@ var BugBearClient = class {
578
583
  });
579
584
  return { success: false, error: error.message };
580
585
  }
586
+ if (!updatedRow) {
587
+ return { success: false, error: "Assignment status has changed. Please refresh and try again." };
588
+ }
581
589
  if (options?.feedback && ["passed", "failed", "blocked"].includes(status)) {
582
590
  const { data: assignmentData, error: fetchError2 } = await this.supabase.from("test_assignments").select("test_case_id").eq("id", assignmentId).single();
583
591
  if (fetchError2) {
@@ -638,7 +646,7 @@ var BugBearClient = class {
638
646
  }
639
647
  const { error } = await this.supabase.from("test_assignments").update(updateData).eq("id", assignmentId);
640
648
  if (error) {
641
- console.error("BugBear: Failed to skip assignment", error);
649
+ console.error("BugBear: Failed to skip assignment", formatPgError(error));
642
650
  return { success: false, error: error.message };
643
651
  }
644
652
  return { success: true };
@@ -804,6 +812,28 @@ var BugBearClient = class {
804
812
  return null;
805
813
  }
806
814
  }
815
+ /**
816
+ * Get detailed assignment stats for the current tester via RPC.
817
+ * Returns counts by status (pending, in_progress, passed, failed, blocked, skipped, total).
818
+ */
819
+ async getTesterStats() {
820
+ try {
821
+ const testerInfo = await this.getTesterInfo();
822
+ if (!testerInfo) return null;
823
+ const { data, error } = await this.supabase.rpc("get_tester_stats", {
824
+ p_project_id: this.config.projectId,
825
+ p_tester_id: testerInfo.id
826
+ });
827
+ if (error) {
828
+ console.error("BugBear: Failed to fetch tester stats", formatPgError(error));
829
+ return null;
830
+ }
831
+ return data;
832
+ } catch (err) {
833
+ console.error("BugBear: Error fetching tester stats", err);
834
+ return null;
835
+ }
836
+ }
807
837
  /**
808
838
  * Basic email format validation (defense in depth)
809
839
  */
@@ -946,7 +976,7 @@ var BugBearClient = class {
946
976
  if (updates.platforms !== void 0) updateData.platforms = updates.platforms;
947
977
  const { error } = await this.supabase.from("testers").update(updateData).eq("id", testerInfo.id);
948
978
  if (error) {
949
- console.error("BugBear: updateTesterProfile error", error);
979
+ console.error("BugBear: updateTesterProfile error", formatPgError(error));
950
980
  return { success: false, error: error.message };
951
981
  }
952
982
  return { success: true };
@@ -968,14 +998,14 @@ var BugBearClient = class {
968
998
  */
969
999
  async isQAEnabled() {
970
1000
  try {
971
- const { data, error } = await this.supabase.from("projects").select("is_qa_enabled").eq("id", this.config.projectId).single();
1001
+ const { data, error } = await this.supabase.rpc("check_qa_enabled", {
1002
+ p_project_id: this.config.projectId
1003
+ });
972
1004
  if (error) {
973
- if (error.code !== "PGRST116") {
974
- console.warn("BugBear: Could not check QA status", error.message || error.code || "Unknown error");
975
- }
1005
+ console.warn("BugBear: Could not check QA status", error.message || error.code || "Unknown error");
976
1006
  return true;
977
1007
  }
978
- return data?.is_qa_enabled ?? true;
1008
+ return data ?? true;
979
1009
  } catch (err) {
980
1010
  return true;
981
1011
  }
@@ -1005,7 +1035,7 @@ var BugBearClient = class {
1005
1035
  upsert: false
1006
1036
  });
1007
1037
  if (error) {
1008
- console.error("BugBear: Failed to upload screenshot", error);
1038
+ console.error("BugBear: Failed to upload screenshot", formatPgError(error));
1009
1039
  return null;
1010
1040
  }
1011
1041
  const { data: { publicUrl } } = this.supabase.storage.from(bucket).getPublicUrl(path);
@@ -1036,7 +1066,7 @@ var BugBearClient = class {
1036
1066
  upsert: false
1037
1067
  });
1038
1068
  if (error) {
1039
- console.error("BugBear: Failed to upload image from URI", error);
1069
+ console.error("BugBear: Failed to upload image from URI", formatPgError(error));
1040
1070
  return null;
1041
1071
  }
1042
1072
  const { data: { publicUrl } } = this.supabase.storage.from(bucket).getPublicUrl(path);
@@ -1111,7 +1141,7 @@ var BugBearClient = class {
1111
1141
  }
1112
1142
  const { data, error } = await query;
1113
1143
  if (error) {
1114
- console.error("BugBear: Failed to fetch fix requests", error);
1144
+ console.error("BugBear: Failed to fetch fix requests", formatPgError(error));
1115
1145
  return [];
1116
1146
  }
1117
1147
  return (data || []).map((fr) => ({
@@ -1137,75 +1167,35 @@ var BugBearClient = class {
1137
1167
  try {
1138
1168
  const testerInfo = await this.getTesterInfo();
1139
1169
  if (!testerInfo) return [];
1140
- const { data: threads, error } = await this.supabase.from("discussion_threads").select(`
1141
- id,
1142
- subject,
1143
- thread_type,
1144
- priority,
1145
- is_pinned,
1146
- is_resolved,
1147
- last_message_at,
1148
- created_at
1149
- `).eq("project_id", this.config.projectId).or(`audience.eq.all,audience_tester_ids.cs.{${testerInfo.id}}`).order("is_pinned", { ascending: false }).order("last_message_at", { ascending: false });
1170
+ const { data, error } = await this.supabase.rpc("get_threads_with_unread", {
1171
+ p_project_id: this.config.projectId,
1172
+ p_tester_id: testerInfo.id
1173
+ });
1150
1174
  if (error) {
1151
- console.error("BugBear: Failed to fetch threads", error);
1175
+ console.error("BugBear: Failed to fetch threads via RPC", formatPgError(error));
1152
1176
  return [];
1153
1177
  }
1154
- if (!threads || threads.length === 0) return [];
1155
- const threadIds = threads.map((t) => t.id);
1156
- const { data: readStatuses } = await this.supabase.from("discussion_read_status").select("thread_id, last_read_at, last_read_message_id").eq("tester_id", testerInfo.id).in("thread_id", threadIds);
1157
- const readStatusMap = new Map(
1158
- (readStatuses || []).map((rs) => [rs.thread_id, rs])
1159
- );
1160
- const { data: lastMessages } = await this.supabase.from("discussion_messages").select(`
1161
- id,
1162
- thread_id,
1163
- sender_type,
1164
- sender_name,
1165
- content,
1166
- created_at,
1167
- attachments
1168
- `).in("thread_id", threadIds).order("created_at", { ascending: false });
1169
- const lastMessageMap = /* @__PURE__ */ new Map();
1170
- for (const msg of lastMessages || []) {
1171
- if (!lastMessageMap.has(msg.thread_id)) {
1172
- lastMessageMap.set(msg.thread_id, msg);
1173
- }
1174
- }
1175
- const unreadCounts = await Promise.all(
1176
- threads.map(async (thread) => {
1177
- const readStatus = readStatusMap.get(thread.id);
1178
- const lastReadAt = readStatus?.last_read_at || "1970-01-01T00:00:00Z";
1179
- const { count, error: countError } = await this.supabase.from("discussion_messages").select("*", { count: "exact", head: true }).eq("thread_id", thread.id).gt("created_at", lastReadAt);
1180
- return { threadId: thread.id, count: countError ? 0 : count || 0 };
1181
- })
1182
- );
1183
- const unreadCountMap = new Map(
1184
- unreadCounts.map((uc) => [uc.threadId, uc.count])
1185
- );
1186
- return threads.map((thread) => {
1187
- const lastMsg = lastMessageMap.get(thread.id);
1188
- return {
1189
- id: thread.id,
1190
- subject: thread.subject,
1191
- threadType: thread.thread_type,
1192
- priority: thread.priority,
1193
- isPinned: thread.is_pinned,
1194
- isResolved: thread.is_resolved,
1195
- lastMessageAt: thread.last_message_at,
1196
- createdAt: thread.created_at,
1197
- unreadCount: unreadCountMap.get(thread.id) || 0,
1198
- lastMessage: lastMsg ? {
1199
- id: lastMsg.id,
1200
- threadId: lastMsg.thread_id,
1201
- senderType: lastMsg.sender_type,
1202
- senderName: lastMsg.sender_name,
1203
- content: lastMsg.content,
1204
- createdAt: lastMsg.created_at,
1205
- attachments: lastMsg.attachments || []
1206
- } : void 0
1207
- };
1208
- });
1178
+ if (!data || data.length === 0) return [];
1179
+ return data.map((row) => ({
1180
+ id: row.thread_id,
1181
+ subject: row.thread_subject,
1182
+ threadType: row.thread_type,
1183
+ priority: row.thread_priority,
1184
+ isPinned: row.is_pinned,
1185
+ isResolved: row.is_resolved,
1186
+ lastMessageAt: row.last_message_at,
1187
+ createdAt: row.created_at,
1188
+ unreadCount: Number(row.unread_count) || 0,
1189
+ lastMessage: row.last_message_preview ? {
1190
+ id: "",
1191
+ threadId: row.thread_id,
1192
+ senderType: row.last_message_sender_type || "system",
1193
+ senderName: row.last_message_sender_name || "",
1194
+ content: row.last_message_preview,
1195
+ createdAt: row.last_message_at,
1196
+ attachments: []
1197
+ } : void 0
1198
+ }));
1209
1199
  } catch (err) {
1210
1200
  console.error("BugBear: Error fetching threads", err);
1211
1201
  return [];
@@ -1224,9 +1214,9 @@ var BugBearClient = class {
1224
1214
  content,
1225
1215
  created_at,
1226
1216
  attachments
1227
- `).eq("thread_id", threadId).order("created_at", { ascending: true });
1217
+ `).eq("thread_id", threadId).order("created_at", { ascending: true }).limit(200);
1228
1218
  if (error) {
1229
- console.error("BugBear: Failed to fetch messages", error);
1219
+ console.error("BugBear: Failed to fetch messages", formatPgError(error));
1230
1220
  return [];
1231
1221
  }
1232
1222
  return (data || []).map((msg) => ({
@@ -1270,10 +1260,9 @@ var BugBearClient = class {
1270
1260
  }
1271
1261
  const { error } = await this.supabase.from("discussion_messages").insert(insertData);
1272
1262
  if (error) {
1273
- console.error("BugBear: Failed to send message", error);
1263
+ console.error("BugBear: Failed to send message", formatPgError(error));
1274
1264
  return false;
1275
1265
  }
1276
- await this.supabase.from("discussion_threads").update({ last_message_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", threadId);
1277
1266
  await this.markThreadAsRead(threadId);
1278
1267
  return true;
1279
1268
  } catch (err) {
@@ -1387,7 +1376,7 @@ var BugBearClient = class {
1387
1376
  p_platform: options.platform || null
1388
1377
  });
1389
1378
  if (error) {
1390
- console.error("BugBear: Failed to start session", error);
1379
+ console.error("BugBear: Failed to start session", formatPgError(error));
1391
1380
  return { success: false, error: error.message };
1392
1381
  }
1393
1382
  const session = await this.getSession(data);
@@ -1412,7 +1401,7 @@ var BugBearClient = class {
1412
1401
  p_routes_covered: options.routesCovered || null
1413
1402
  });
1414
1403
  if (error) {
1415
- console.error("BugBear: Failed to end session", error);
1404
+ console.error("BugBear: Failed to end session", formatPgError(error));
1416
1405
  return { success: false, error: error.message };
1417
1406
  }
1418
1407
  const session = this.transformSession(data);
@@ -1460,7 +1449,7 @@ var BugBearClient = class {
1460
1449
  if (!testerInfo) return [];
1461
1450
  const { data, error } = await this.supabase.from("qa_sessions").select("*").eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).order("started_at", { ascending: false }).limit(limit);
1462
1451
  if (error) {
1463
- console.error("BugBear: Failed to fetch session history", error);
1452
+ console.error("BugBear: Failed to fetch session history", formatPgError(error));
1464
1453
  return [];
1465
1454
  }
1466
1455
  return (data || []).map((s) => this.transformSession(s));
@@ -1488,7 +1477,7 @@ var BugBearClient = class {
1488
1477
  p_app_context: options.appContext || null
1489
1478
  });
1490
1479
  if (error) {
1491
- console.error("BugBear: Failed to add finding", error);
1480
+ console.error("BugBear: Failed to add finding", formatPgError(error));
1492
1481
  return { success: false, error: error.message };
1493
1482
  }
1494
1483
  const finding = this.transformFinding(data);
@@ -1504,9 +1493,9 @@ var BugBearClient = class {
1504
1493
  */
1505
1494
  async getSessionFindings(sessionId) {
1506
1495
  try {
1507
- const { data, error } = await this.supabase.from("qa_findings").select("*").eq("session_id", sessionId).order("created_at", { ascending: true });
1496
+ const { data, error } = await this.supabase.from("qa_findings").select("*").eq("session_id", sessionId).order("created_at", { ascending: true }).limit(100);
1508
1497
  if (error) {
1509
- console.error("BugBear: Failed to fetch findings", error);
1498
+ console.error("BugBear: Failed to fetch findings", formatPgError(error));
1510
1499
  return [];
1511
1500
  }
1512
1501
  return (data || []).map((f) => this.transformFinding(f));
@@ -1524,7 +1513,7 @@ var BugBearClient = class {
1524
1513
  p_finding_id: findingId
1525
1514
  });
1526
1515
  if (error) {
1527
- console.error("BugBear: Failed to convert finding", error);
1516
+ console.error("BugBear: Failed to convert finding", formatPgError(error));
1528
1517
  return { success: false, error: error.message };
1529
1518
  }
1530
1519
  return { success: true, bugId: data };
@@ -1545,7 +1534,7 @@ var BugBearClient = class {
1545
1534
  dismissed_at: (/* @__PURE__ */ new Date()).toISOString()
1546
1535
  }).eq("id", findingId);
1547
1536
  if (error) {
1548
- console.error("BugBear: Failed to dismiss finding", error);
1537
+ console.error("BugBear: Failed to dismiss finding", formatPgError(error));
1549
1538
  return { success: false, error: error.message };
1550
1539
  }
1551
1540
  return { success: true };
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@bbearai/core",
3
- "version": "0.2.15",
3
+ "version": "0.3.1",
4
4
  "description": "Core utilities and types for BugBear QA platform",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
7
7
  "types": "./dist/index.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
+ "types": "./dist/index.d.ts",
10
11
  "import": "./dist/index.mjs",
11
- "require": "./dist/index.js",
12
- "types": "./dist/index.d.ts"
12
+ "require": "./dist/index.js"
13
13
  }
14
14
  },
15
15
  "files": [