@bbearai/react-native 0.5.5 → 0.5.7

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.
Files changed (3) hide show
  1. package/dist/index.js +119 -20
  2. package/dist/index.mjs +119 -20
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -11394,6 +11394,7 @@ function shouldShowDeprecationWarning() {
11394
11394
  if (shouldShowDeprecationWarning()) console.warn("\u26A0\uFE0F Node.js 18 and below are deprecated and will no longer be supported in future versions of @supabase/supabase-js. Please upgrade to Node.js 20 or later. For more information, visit: https://github.com/orgs/supabase/discussions/37217");
11395
11395
 
11396
11396
  // ../core/dist/index.mjs
11397
+ var BUG_CATEGORIES = ["ui_ux", "functional", "crash", "security", "other"];
11397
11398
  var MAX_CONSOLE_LOGS = 50;
11398
11399
  var MAX_NETWORK_REQUESTS = 20;
11399
11400
  var MAX_NAVIGATION_HISTORY = 20;
@@ -11828,7 +11829,13 @@ var BugBearClient = class {
11828
11829
  console.error("BugBear: Failed to fetch assignments", formatPgError(error));
11829
11830
  return [];
11830
11831
  }
11831
- const mapped = (data || []).map((item) => ({
11832
+ const mapped = (data || []).filter((item) => {
11833
+ if (!item.test_case) {
11834
+ console.warn("BugBear: Assignment returned without test_case", { id: item.id });
11835
+ return false;
11836
+ }
11837
+ return true;
11838
+ }).map((item) => ({
11832
11839
  id: item.id,
11833
11840
  status: item.status,
11834
11841
  startedAt: item.started_at,
@@ -12085,6 +12092,16 @@ var BugBearClient = class {
12085
12092
  if (feedback.rating < 1 || feedback.rating > 5) {
12086
12093
  return { success: false, error: "Rating must be between 1 and 5" };
12087
12094
  }
12095
+ const optionalRatings = [
12096
+ { name: "clarityRating", value: feedback.clarityRating },
12097
+ { name: "stepsRating", value: feedback.stepsRating },
12098
+ { name: "relevanceRating", value: feedback.relevanceRating }
12099
+ ];
12100
+ for (const { name, value } of optionalRatings) {
12101
+ if (value !== void 0 && value !== null && (value < 1 || value > 5)) {
12102
+ return { success: false, error: `${name} must be between 1 and 5` };
12103
+ }
12104
+ }
12088
12105
  const { error: feedbackError } = await this.supabase.from("test_feedback").insert({
12089
12106
  project_id: this.config.projectId,
12090
12107
  test_case_id: testCaseId,
@@ -12336,6 +12353,9 @@ var BugBearClient = class {
12336
12353
  if (report.severity && !validSeverities.includes(report.severity)) {
12337
12354
  return `Invalid severity: ${report.severity}. Must be one of: ${validSeverities.join(", ")}`;
12338
12355
  }
12356
+ if (report.category && !BUG_CATEGORIES.includes(report.category)) {
12357
+ return `Invalid category: ${report.category}. Must be one of: ${BUG_CATEGORIES.join(", ")}`;
12358
+ }
12339
12359
  if (report.title && report.title.length > 500) {
12340
12360
  return "Title must be 500 characters or less";
12341
12361
  }
@@ -12408,6 +12428,10 @@ var BugBearClient = class {
12408
12428
  });
12409
12429
  if (error) {
12410
12430
  console.warn("BugBear: Rate limit check failed, allowing request", error.message);
12431
+ this.config.onError?.(new Error(`Rate limit check failed: ${error.message}`), {
12432
+ projectId: this.config.projectId,
12433
+ context: "rate_limit_check_failed_open"
12434
+ });
12411
12435
  return { allowed: true };
12412
12436
  }
12413
12437
  if (!data.allowed) {
@@ -12424,7 +12448,12 @@ var BugBearClient = class {
12424
12448
  resetAt: data.reset_at
12425
12449
  };
12426
12450
  } catch (err) {
12451
+ const message = err instanceof Error ? err.message : "Unknown rate limit error";
12427
12452
  console.warn("BugBear: Rate limit check error", err);
12453
+ this.config.onError?.(err instanceof Error ? err : new Error(message), {
12454
+ projectId: this.config.projectId,
12455
+ context: "rate_limit_check_failed_open"
12456
+ });
12428
12457
  return { allowed: true };
12429
12458
  }
12430
12459
  }
@@ -12488,6 +12517,9 @@ var BugBearClient = class {
12488
12517
  }
12489
12518
  return data ?? true;
12490
12519
  } catch (err) {
12520
+ const message = err instanceof Error ? err.message : "Unknown error checking QA status";
12521
+ console.error("BugBear: Error checking QA status", err);
12522
+ this.config.onError?.(err instanceof Error ? err : new Error(message), { projectId: this.config.projectId });
12491
12523
  return true;
12492
12524
  }
12493
12525
  }
@@ -12516,13 +12548,24 @@ var BugBearClient = class {
12516
12548
  upsert: false
12517
12549
  });
12518
12550
  if (error) {
12519
- console.error("BugBear: Failed to upload screenshot", formatPgError(error));
12551
+ const formattedError = formatPgError(error);
12552
+ const errorMessage = formattedError.message || "Failed to upload screenshot";
12553
+ console.error("BugBear: Failed to upload screenshot", formattedError);
12554
+ this.config.onError?.(new Error(errorMessage), {
12555
+ projectId: this.config.projectId,
12556
+ context: "screenshot_upload_failed"
12557
+ });
12520
12558
  return null;
12521
12559
  }
12522
12560
  const { data: { publicUrl } } = this.supabase.storage.from(bucket).getPublicUrl(path);
12523
12561
  return publicUrl;
12524
12562
  } catch (err) {
12563
+ const message = err instanceof Error ? err.message : "Unknown error uploading screenshot";
12525
12564
  console.error("BugBear: Error uploading screenshot", err);
12565
+ this.config.onError?.(err instanceof Error ? err : new Error(message), {
12566
+ projectId: this.config.projectId,
12567
+ context: "screenshot_upload_failed"
12568
+ });
12526
12569
  return null;
12527
12570
  }
12528
12571
  }
@@ -12547,13 +12590,24 @@ var BugBearClient = class {
12547
12590
  upsert: false
12548
12591
  });
12549
12592
  if (error) {
12550
- console.error("BugBear: Failed to upload image from URI", formatPgError(error));
12593
+ const formattedError = formatPgError(error);
12594
+ const errorMessage = formattedError.message || "Failed to upload image";
12595
+ console.error("BugBear: Failed to upload image from URI", formattedError);
12596
+ this.config.onError?.(new Error(errorMessage), {
12597
+ projectId: this.config.projectId,
12598
+ context: "image_upload_failed"
12599
+ });
12551
12600
  return null;
12552
12601
  }
12553
12602
  const { data: { publicUrl } } = this.supabase.storage.from(bucket).getPublicUrl(path);
12554
12603
  return publicUrl;
12555
12604
  } catch (err) {
12605
+ const message = err instanceof Error ? err.message : "Unknown error uploading image";
12556
12606
  console.error("BugBear: Error uploading image from URI", err);
12607
+ this.config.onError?.(err instanceof Error ? err : new Error(message), {
12608
+ projectId: this.config.projectId,
12609
+ context: "image_upload_failed"
12610
+ });
12557
12611
  return null;
12558
12612
  }
12559
12613
  }
@@ -14398,7 +14452,12 @@ function useImageAttachments(uploadFn, maxImages, bucket = "screenshots") {
14398
14452
  launchImageLibrary(
14399
14453
  { mediaType: "photo", quality: 0.7, maxWidth: 1920, maxHeight: 1920, selectionLimit: maxImages - images.length },
14400
14454
  async (response) => {
14401
- if (response.didCancel || response.errorCode || !response.assets) return;
14455
+ if (response.didCancel) return;
14456
+ if (response.errorCode) {
14457
+ console.error("BugBear: Image picker error", response.errorCode, response.errorMessage);
14458
+ return;
14459
+ }
14460
+ if (!response.assets) return;
14402
14461
  for (const asset of response.assets) {
14403
14462
  const uri = asset.uri;
14404
14463
  if (!uri) continue;
@@ -14412,6 +14471,11 @@ function useImageAttachments(uploadFn, maxImages, bucket = "screenshots") {
14412
14471
  setImages((prev) => prev.map(
14413
14472
  (img) => img.id === id ? { ...img, remoteUrl: url, status: url ? "done" : "error" } : img
14414
14473
  ));
14474
+ }).catch((err) => {
14475
+ console.error("BugBear: Image upload failed", err);
14476
+ setImages((prev) => prev.map(
14477
+ (img) => img.id === id ? { ...img, status: "error" } : img
14478
+ ));
14415
14479
  });
14416
14480
  }
14417
14481
  }
@@ -14422,7 +14486,12 @@ function useImageAttachments(uploadFn, maxImages, bucket = "screenshots") {
14422
14486
  launchCamera(
14423
14487
  { mediaType: "photo", quality: 0.7, maxWidth: 1920, maxHeight: 1920 },
14424
14488
  async (response) => {
14425
- if (response.didCancel || response.errorCode || !response.assets?.[0]) return;
14489
+ if (response.didCancel) return;
14490
+ if (response.errorCode) {
14491
+ console.error("BugBear: Camera error", response.errorCode, response.errorMessage);
14492
+ return;
14493
+ }
14494
+ if (!response.assets?.[0]) return;
14426
14495
  const asset = response.assets[0];
14427
14496
  const uri = asset.uri;
14428
14497
  if (!uri) return;
@@ -14436,6 +14505,11 @@ function useImageAttachments(uploadFn, maxImages, bucket = "screenshots") {
14436
14505
  setImages((prev) => prev.map(
14437
14506
  (img) => img.id === id ? { ...img, remoteUrl: url, status: url ? "done" : "error" } : img
14438
14507
  ));
14508
+ }).catch((err) => {
14509
+ console.error("BugBear: Image upload failed", err);
14510
+ setImages((prev) => prev.map(
14511
+ (img) => img.id === id ? { ...img, status: "error" } : img
14512
+ ));
14439
14513
  });
14440
14514
  }
14441
14515
  );
@@ -14590,6 +14664,7 @@ function TestFeedbackScreen({ status, assignmentId, nav }) {
14590
14664
  const assignment = assignments.find((a) => a.id === assignmentId);
14591
14665
  const showFlags = rating < 4;
14592
14666
  const handleSubmit = async () => {
14667
+ if (submitting || images.isUploading) return;
14593
14668
  setSubmitting(true);
14594
14669
  if (client && assignment) {
14595
14670
  const screenshotUrls = images.getScreenshotUrls();
@@ -14880,7 +14955,7 @@ function ReportScreen({ nav, prefill }) {
14880
14955
  }
14881
14956
  }, [reportType]);
14882
14957
  const handleSubmit = async () => {
14883
- if (!client || !description.trim()) return;
14958
+ if (!client || !description.trim() || images.isUploading) return;
14884
14959
  if (submittingRef.current) return;
14885
14960
  submittingRef.current = true;
14886
14961
  setSubmitting(true);
@@ -15128,18 +15203,31 @@ function ThreadDetailScreen({ thread, nav }) {
15128
15203
  const [sendError, setSendError] = (0, import_react14.useState)(false);
15129
15204
  const replyImages = useImageAttachments(uploadImage, 3, "discussion-attachments");
15130
15205
  (0, import_react14.useEffect)(() => {
15206
+ let cancelled = false;
15207
+ setLoading(true);
15131
15208
  (async () => {
15132
- setLoading(true);
15133
- const msgs = await getThreadMessages(thread.id);
15134
- setMessages(msgs);
15135
- setLoading(false);
15136
- if (thread.unreadCount > 0) {
15137
- await markAsRead(thread.id);
15209
+ try {
15210
+ const msgs = await getThreadMessages(thread.id);
15211
+ if (!cancelled) {
15212
+ setMessages(msgs);
15213
+ }
15214
+ if (thread.unreadCount > 0) {
15215
+ await markAsRead(thread.id);
15216
+ }
15217
+ } catch (err) {
15218
+ console.error("BugBear: Failed to load thread messages", err);
15219
+ } finally {
15220
+ if (!cancelled) {
15221
+ setLoading(false);
15222
+ }
15138
15223
  }
15139
15224
  })();
15225
+ return () => {
15226
+ cancelled = true;
15227
+ };
15140
15228
  }, [thread.id]);
15141
15229
  const handleSend = async () => {
15142
- if (!replyText.trim()) return;
15230
+ if (!replyText.trim() && replyImages.images.length === 0 || sending || replyImages.isUploading) return;
15143
15231
  setSending(true);
15144
15232
  setSendError(false);
15145
15233
  const attachments = replyImages.getAttachments();
@@ -15183,9 +15271,9 @@ function ThreadDetailScreen({ thread, nav }) {
15183
15271
  ), /* @__PURE__ */ import_react14.default.createElement(
15184
15272
  import_react_native13.TouchableOpacity,
15185
15273
  {
15186
- style: [styles11.sendBtn, (!replyText.trim() || sending || replyImages.isUploading) && styles11.sendBtnDisabled],
15274
+ style: [styles11.sendBtn, (!replyText.trim() && replyImages.images.length === 0 || sending || replyImages.isUploading) && styles11.sendBtnDisabled],
15187
15275
  onPress: handleSend,
15188
- disabled: !replyText.trim() || sending || replyImages.isUploading
15276
+ disabled: !replyText.trim() && replyImages.images.length === 0 || sending || replyImages.isUploading
15189
15277
  },
15190
15278
  /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles11.sendBtnText }, sending ? "..." : "Send")
15191
15279
  )));
@@ -15231,7 +15319,7 @@ function ComposeMessageScreen({ nav }) {
15231
15319
  const [sending, setSending] = (0, import_react15.useState)(false);
15232
15320
  const images = useImageAttachments(uploadImage, 3, "discussion-attachments");
15233
15321
  const handleSend = async () => {
15234
- if (!subject.trim() || !message.trim()) return;
15322
+ if (!subject.trim() || !message.trim() || sending || images.isUploading) return;
15235
15323
  setSending(true);
15236
15324
  const attachments = images.getAttachments();
15237
15325
  const result = await createThread({
@@ -15317,6 +15405,7 @@ function ProfileScreen({ nav }) {
15317
15405
  }
15318
15406
  }, [testerInfo]);
15319
15407
  const handleSave = async () => {
15408
+ if (saving) return;
15320
15409
  setSaving(true);
15321
15410
  const updates = {
15322
15411
  name: name.trim(),
@@ -15445,11 +15534,21 @@ function IssueListScreen({ nav, category }) {
15445
15534
  let cancelled = false;
15446
15535
  setLoading(true);
15447
15536
  (async () => {
15448
- if (!client) return;
15449
- const data = await client.getIssues(category);
15450
- if (!cancelled) {
15451
- setIssues(data);
15537
+ if (!client) {
15452
15538
  setLoading(false);
15539
+ return;
15540
+ }
15541
+ try {
15542
+ const data = await client.getIssues(category);
15543
+ if (!cancelled) {
15544
+ setIssues(data);
15545
+ }
15546
+ } catch (err) {
15547
+ console.error("BugBear: Failed to load issues", err);
15548
+ } finally {
15549
+ if (!cancelled) {
15550
+ setLoading(false);
15551
+ }
15453
15552
  }
15454
15553
  })();
15455
15554
  return () => {
package/dist/index.mjs CHANGED
@@ -11361,6 +11361,7 @@ function shouldShowDeprecationWarning() {
11361
11361
  if (shouldShowDeprecationWarning()) console.warn("\u26A0\uFE0F Node.js 18 and below are deprecated and will no longer be supported in future versions of @supabase/supabase-js. Please upgrade to Node.js 20 or later. For more information, visit: https://github.com/orgs/supabase/discussions/37217");
11362
11362
 
11363
11363
  // ../core/dist/index.mjs
11364
+ var BUG_CATEGORIES = ["ui_ux", "functional", "crash", "security", "other"];
11364
11365
  var MAX_CONSOLE_LOGS = 50;
11365
11366
  var MAX_NETWORK_REQUESTS = 20;
11366
11367
  var MAX_NAVIGATION_HISTORY = 20;
@@ -11795,7 +11796,13 @@ var BugBearClient = class {
11795
11796
  console.error("BugBear: Failed to fetch assignments", formatPgError(error));
11796
11797
  return [];
11797
11798
  }
11798
- const mapped = (data || []).map((item) => ({
11799
+ const mapped = (data || []).filter((item) => {
11800
+ if (!item.test_case) {
11801
+ console.warn("BugBear: Assignment returned without test_case", { id: item.id });
11802
+ return false;
11803
+ }
11804
+ return true;
11805
+ }).map((item) => ({
11799
11806
  id: item.id,
11800
11807
  status: item.status,
11801
11808
  startedAt: item.started_at,
@@ -12052,6 +12059,16 @@ var BugBearClient = class {
12052
12059
  if (feedback.rating < 1 || feedback.rating > 5) {
12053
12060
  return { success: false, error: "Rating must be between 1 and 5" };
12054
12061
  }
12062
+ const optionalRatings = [
12063
+ { name: "clarityRating", value: feedback.clarityRating },
12064
+ { name: "stepsRating", value: feedback.stepsRating },
12065
+ { name: "relevanceRating", value: feedback.relevanceRating }
12066
+ ];
12067
+ for (const { name, value } of optionalRatings) {
12068
+ if (value !== void 0 && value !== null && (value < 1 || value > 5)) {
12069
+ return { success: false, error: `${name} must be between 1 and 5` };
12070
+ }
12071
+ }
12055
12072
  const { error: feedbackError } = await this.supabase.from("test_feedback").insert({
12056
12073
  project_id: this.config.projectId,
12057
12074
  test_case_id: testCaseId,
@@ -12303,6 +12320,9 @@ var BugBearClient = class {
12303
12320
  if (report.severity && !validSeverities.includes(report.severity)) {
12304
12321
  return `Invalid severity: ${report.severity}. Must be one of: ${validSeverities.join(", ")}`;
12305
12322
  }
12323
+ if (report.category && !BUG_CATEGORIES.includes(report.category)) {
12324
+ return `Invalid category: ${report.category}. Must be one of: ${BUG_CATEGORIES.join(", ")}`;
12325
+ }
12306
12326
  if (report.title && report.title.length > 500) {
12307
12327
  return "Title must be 500 characters or less";
12308
12328
  }
@@ -12375,6 +12395,10 @@ var BugBearClient = class {
12375
12395
  });
12376
12396
  if (error) {
12377
12397
  console.warn("BugBear: Rate limit check failed, allowing request", error.message);
12398
+ this.config.onError?.(new Error(`Rate limit check failed: ${error.message}`), {
12399
+ projectId: this.config.projectId,
12400
+ context: "rate_limit_check_failed_open"
12401
+ });
12378
12402
  return { allowed: true };
12379
12403
  }
12380
12404
  if (!data.allowed) {
@@ -12391,7 +12415,12 @@ var BugBearClient = class {
12391
12415
  resetAt: data.reset_at
12392
12416
  };
12393
12417
  } catch (err) {
12418
+ const message = err instanceof Error ? err.message : "Unknown rate limit error";
12394
12419
  console.warn("BugBear: Rate limit check error", err);
12420
+ this.config.onError?.(err instanceof Error ? err : new Error(message), {
12421
+ projectId: this.config.projectId,
12422
+ context: "rate_limit_check_failed_open"
12423
+ });
12395
12424
  return { allowed: true };
12396
12425
  }
12397
12426
  }
@@ -12455,6 +12484,9 @@ var BugBearClient = class {
12455
12484
  }
12456
12485
  return data ?? true;
12457
12486
  } catch (err) {
12487
+ const message = err instanceof Error ? err.message : "Unknown error checking QA status";
12488
+ console.error("BugBear: Error checking QA status", err);
12489
+ this.config.onError?.(err instanceof Error ? err : new Error(message), { projectId: this.config.projectId });
12458
12490
  return true;
12459
12491
  }
12460
12492
  }
@@ -12483,13 +12515,24 @@ var BugBearClient = class {
12483
12515
  upsert: false
12484
12516
  });
12485
12517
  if (error) {
12486
- console.error("BugBear: Failed to upload screenshot", formatPgError(error));
12518
+ const formattedError = formatPgError(error);
12519
+ const errorMessage = formattedError.message || "Failed to upload screenshot";
12520
+ console.error("BugBear: Failed to upload screenshot", formattedError);
12521
+ this.config.onError?.(new Error(errorMessage), {
12522
+ projectId: this.config.projectId,
12523
+ context: "screenshot_upload_failed"
12524
+ });
12487
12525
  return null;
12488
12526
  }
12489
12527
  const { data: { publicUrl } } = this.supabase.storage.from(bucket).getPublicUrl(path);
12490
12528
  return publicUrl;
12491
12529
  } catch (err) {
12530
+ const message = err instanceof Error ? err.message : "Unknown error uploading screenshot";
12492
12531
  console.error("BugBear: Error uploading screenshot", err);
12532
+ this.config.onError?.(err instanceof Error ? err : new Error(message), {
12533
+ projectId: this.config.projectId,
12534
+ context: "screenshot_upload_failed"
12535
+ });
12493
12536
  return null;
12494
12537
  }
12495
12538
  }
@@ -12514,13 +12557,24 @@ var BugBearClient = class {
12514
12557
  upsert: false
12515
12558
  });
12516
12559
  if (error) {
12517
- console.error("BugBear: Failed to upload image from URI", formatPgError(error));
12560
+ const formattedError = formatPgError(error);
12561
+ const errorMessage = formattedError.message || "Failed to upload image";
12562
+ console.error("BugBear: Failed to upload image from URI", formattedError);
12563
+ this.config.onError?.(new Error(errorMessage), {
12564
+ projectId: this.config.projectId,
12565
+ context: "image_upload_failed"
12566
+ });
12518
12567
  return null;
12519
12568
  }
12520
12569
  const { data: { publicUrl } } = this.supabase.storage.from(bucket).getPublicUrl(path);
12521
12570
  return publicUrl;
12522
12571
  } catch (err) {
12572
+ const message = err instanceof Error ? err.message : "Unknown error uploading image";
12523
12573
  console.error("BugBear: Error uploading image from URI", err);
12574
+ this.config.onError?.(err instanceof Error ? err : new Error(message), {
12575
+ projectId: this.config.projectId,
12576
+ context: "image_upload_failed"
12577
+ });
12524
12578
  return null;
12525
12579
  }
12526
12580
  }
@@ -14380,7 +14434,12 @@ function useImageAttachments(uploadFn, maxImages, bucket = "screenshots") {
14380
14434
  launchImageLibrary(
14381
14435
  { mediaType: "photo", quality: 0.7, maxWidth: 1920, maxHeight: 1920, selectionLimit: maxImages - images.length },
14382
14436
  async (response) => {
14383
- if (response.didCancel || response.errorCode || !response.assets) return;
14437
+ if (response.didCancel) return;
14438
+ if (response.errorCode) {
14439
+ console.error("BugBear: Image picker error", response.errorCode, response.errorMessage);
14440
+ return;
14441
+ }
14442
+ if (!response.assets) return;
14384
14443
  for (const asset of response.assets) {
14385
14444
  const uri = asset.uri;
14386
14445
  if (!uri) continue;
@@ -14394,6 +14453,11 @@ function useImageAttachments(uploadFn, maxImages, bucket = "screenshots") {
14394
14453
  setImages((prev) => prev.map(
14395
14454
  (img) => img.id === id ? { ...img, remoteUrl: url, status: url ? "done" : "error" } : img
14396
14455
  ));
14456
+ }).catch((err) => {
14457
+ console.error("BugBear: Image upload failed", err);
14458
+ setImages((prev) => prev.map(
14459
+ (img) => img.id === id ? { ...img, status: "error" } : img
14460
+ ));
14397
14461
  });
14398
14462
  }
14399
14463
  }
@@ -14404,7 +14468,12 @@ function useImageAttachments(uploadFn, maxImages, bucket = "screenshots") {
14404
14468
  launchCamera(
14405
14469
  { mediaType: "photo", quality: 0.7, maxWidth: 1920, maxHeight: 1920 },
14406
14470
  async (response) => {
14407
- if (response.didCancel || response.errorCode || !response.assets?.[0]) return;
14471
+ if (response.didCancel) return;
14472
+ if (response.errorCode) {
14473
+ console.error("BugBear: Camera error", response.errorCode, response.errorMessage);
14474
+ return;
14475
+ }
14476
+ if (!response.assets?.[0]) return;
14408
14477
  const asset = response.assets[0];
14409
14478
  const uri = asset.uri;
14410
14479
  if (!uri) return;
@@ -14418,6 +14487,11 @@ function useImageAttachments(uploadFn, maxImages, bucket = "screenshots") {
14418
14487
  setImages((prev) => prev.map(
14419
14488
  (img) => img.id === id ? { ...img, remoteUrl: url, status: url ? "done" : "error" } : img
14420
14489
  ));
14490
+ }).catch((err) => {
14491
+ console.error("BugBear: Image upload failed", err);
14492
+ setImages((prev) => prev.map(
14493
+ (img) => img.id === id ? { ...img, status: "error" } : img
14494
+ ));
14421
14495
  });
14422
14496
  }
14423
14497
  );
@@ -14572,6 +14646,7 @@ function TestFeedbackScreen({ status, assignmentId, nav }) {
14572
14646
  const assignment = assignments.find((a) => a.id === assignmentId);
14573
14647
  const showFlags = rating < 4;
14574
14648
  const handleSubmit = async () => {
14649
+ if (submitting || images.isUploading) return;
14575
14650
  setSubmitting(true);
14576
14651
  if (client && assignment) {
14577
14652
  const screenshotUrls = images.getScreenshotUrls();
@@ -14862,7 +14937,7 @@ function ReportScreen({ nav, prefill }) {
14862
14937
  }
14863
14938
  }, [reportType]);
14864
14939
  const handleSubmit = async () => {
14865
- if (!client || !description.trim()) return;
14940
+ if (!client || !description.trim() || images.isUploading) return;
14866
14941
  if (submittingRef.current) return;
14867
14942
  submittingRef.current = true;
14868
14943
  setSubmitting(true);
@@ -15110,18 +15185,31 @@ function ThreadDetailScreen({ thread, nav }) {
15110
15185
  const [sendError, setSendError] = useState8(false);
15111
15186
  const replyImages = useImageAttachments(uploadImage, 3, "discussion-attachments");
15112
15187
  useEffect7(() => {
15188
+ let cancelled = false;
15189
+ setLoading(true);
15113
15190
  (async () => {
15114
- setLoading(true);
15115
- const msgs = await getThreadMessages(thread.id);
15116
- setMessages(msgs);
15117
- setLoading(false);
15118
- if (thread.unreadCount > 0) {
15119
- await markAsRead(thread.id);
15191
+ try {
15192
+ const msgs = await getThreadMessages(thread.id);
15193
+ if (!cancelled) {
15194
+ setMessages(msgs);
15195
+ }
15196
+ if (thread.unreadCount > 0) {
15197
+ await markAsRead(thread.id);
15198
+ }
15199
+ } catch (err) {
15200
+ console.error("BugBear: Failed to load thread messages", err);
15201
+ } finally {
15202
+ if (!cancelled) {
15203
+ setLoading(false);
15204
+ }
15120
15205
  }
15121
15206
  })();
15207
+ return () => {
15208
+ cancelled = true;
15209
+ };
15122
15210
  }, [thread.id]);
15123
15211
  const handleSend = async () => {
15124
- if (!replyText.trim()) return;
15212
+ if (!replyText.trim() && replyImages.images.length === 0 || sending || replyImages.isUploading) return;
15125
15213
  setSending(true);
15126
15214
  setSendError(false);
15127
15215
  const attachments = replyImages.getAttachments();
@@ -15165,9 +15253,9 @@ function ThreadDetailScreen({ thread, nav }) {
15165
15253
  ), /* @__PURE__ */ React12.createElement(
15166
15254
  TouchableOpacity10,
15167
15255
  {
15168
- style: [styles11.sendBtn, (!replyText.trim() || sending || replyImages.isUploading) && styles11.sendBtnDisabled],
15256
+ style: [styles11.sendBtn, (!replyText.trim() && replyImages.images.length === 0 || sending || replyImages.isUploading) && styles11.sendBtnDisabled],
15169
15257
  onPress: handleSend,
15170
- disabled: !replyText.trim() || sending || replyImages.isUploading
15258
+ disabled: !replyText.trim() && replyImages.images.length === 0 || sending || replyImages.isUploading
15171
15259
  },
15172
15260
  /* @__PURE__ */ React12.createElement(Text11, { style: styles11.sendBtnText }, sending ? "..." : "Send")
15173
15261
  )));
@@ -15213,7 +15301,7 @@ function ComposeMessageScreen({ nav }) {
15213
15301
  const [sending, setSending] = useState9(false);
15214
15302
  const images = useImageAttachments(uploadImage, 3, "discussion-attachments");
15215
15303
  const handleSend = async () => {
15216
- if (!subject.trim() || !message.trim()) return;
15304
+ if (!subject.trim() || !message.trim() || sending || images.isUploading) return;
15217
15305
  setSending(true);
15218
15306
  const attachments = images.getAttachments();
15219
15307
  const result = await createThread({
@@ -15299,6 +15387,7 @@ function ProfileScreen({ nav }) {
15299
15387
  }
15300
15388
  }, [testerInfo]);
15301
15389
  const handleSave = async () => {
15390
+ if (saving) return;
15302
15391
  setSaving(true);
15303
15392
  const updates = {
15304
15393
  name: name.trim(),
@@ -15427,11 +15516,21 @@ function IssueListScreen({ nav, category }) {
15427
15516
  let cancelled = false;
15428
15517
  setLoading(true);
15429
15518
  (async () => {
15430
- if (!client) return;
15431
- const data = await client.getIssues(category);
15432
- if (!cancelled) {
15433
- setIssues(data);
15519
+ if (!client) {
15434
15520
  setLoading(false);
15521
+ return;
15522
+ }
15523
+ try {
15524
+ const data = await client.getIssues(category);
15525
+ if (!cancelled) {
15526
+ setIssues(data);
15527
+ }
15528
+ } catch (err) {
15529
+ console.error("BugBear: Failed to load issues", err);
15530
+ } finally {
15531
+ if (!cancelled) {
15532
+ setLoading(false);
15533
+ }
15435
15534
  }
15436
15535
  })();
15437
15536
  return () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bbearai/react-native",
3
- "version": "0.5.5",
3
+ "version": "0.5.7",
4
4
  "description": "BugBear React Native components for mobile apps",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",