@bbearai/react-native 0.4.0 → 0.5.0

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 +133 -33
  2. package/dist/index.mjs +138 -38
  3. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -11393,7 +11393,7 @@ function shouldShowDeprecationWarning() {
11393
11393
  }
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
- // ../core/dist/index.mjs
11396
+ // node_modules/@bbearai/core/dist/index.mjs
11397
11397
  var MAX_CONSOLE_LOGS = 50;
11398
11398
  var MAX_NETWORK_REQUESTS = 20;
11399
11399
  var MAX_NAVIGATION_HISTORY = 20;
@@ -11634,6 +11634,11 @@ function captureError(error, errorInfo) {
11634
11634
  componentStack: errorInfo?.componentStack
11635
11635
  };
11636
11636
  }
11637
+ var formatPgError = (e) => {
11638
+ if (!e || typeof e !== "object") return { raw: e };
11639
+ const { message, code, details, hint } = e;
11640
+ return { message, code, details, hint };
11641
+ };
11637
11642
  var DEFAULT_SUPABASE_URL = "https://kyxgzjnqgvapvlnvqawz.supabase.co";
11638
11643
  var getEnvVar = (key) => {
11639
11644
  try {
@@ -11803,7 +11808,7 @@ var BugBearClient = class {
11803
11808
  )
11804
11809
  `).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true }).limit(100);
11805
11810
  if (error) {
11806
- console.error("BugBear: Failed to fetch assignments", error);
11811
+ console.error("BugBear: Failed to fetch assignments", formatPgError(error));
11807
11812
  return [];
11808
11813
  }
11809
11814
  const mapped = (data || []).map((item) => ({
@@ -12031,7 +12036,7 @@ var BugBearClient = class {
12031
12036
  }
12032
12037
  const { error } = await this.supabase.from("test_assignments").update(updateData).eq("id", assignmentId);
12033
12038
  if (error) {
12034
- console.error("BugBear: Failed to skip assignment", error);
12039
+ console.error("BugBear: Failed to skip assignment", formatPgError(error));
12035
12040
  return { success: false, error: error.message };
12036
12041
  }
12037
12042
  return { success: true };
@@ -12210,7 +12215,7 @@ var BugBearClient = class {
12210
12215
  p_tester_id: testerInfo.id
12211
12216
  });
12212
12217
  if (error) {
12213
- console.error("BugBear: Failed to fetch tester stats", error);
12218
+ console.error("BugBear: Failed to fetch tester stats", formatPgError(error));
12214
12219
  return null;
12215
12220
  }
12216
12221
  return data;
@@ -12361,7 +12366,7 @@ var BugBearClient = class {
12361
12366
  if (updates.platforms !== void 0) updateData.platforms = updates.platforms;
12362
12367
  const { error } = await this.supabase.from("testers").update(updateData).eq("id", testerInfo.id);
12363
12368
  if (error) {
12364
- console.error("BugBear: updateTesterProfile error", error);
12369
+ console.error("BugBear: updateTesterProfile error", formatPgError(error));
12365
12370
  return { success: false, error: error.message };
12366
12371
  }
12367
12372
  return { success: true };
@@ -12383,14 +12388,14 @@ var BugBearClient = class {
12383
12388
  */
12384
12389
  async isQAEnabled() {
12385
12390
  try {
12386
- const { data, error } = await this.supabase.from("projects").select("is_qa_enabled").eq("id", this.config.projectId).single();
12391
+ const { data, error } = await this.supabase.rpc("check_qa_enabled", {
12392
+ p_project_id: this.config.projectId
12393
+ });
12387
12394
  if (error) {
12388
- if (error.code !== "PGRST116") {
12389
- console.warn("BugBear: Could not check QA status", error.message || error.code || "Unknown error");
12390
- }
12395
+ console.warn("BugBear: Could not check QA status", error.message || error.code || "Unknown error");
12391
12396
  return true;
12392
12397
  }
12393
- return data?.is_qa_enabled ?? true;
12398
+ return data ?? true;
12394
12399
  } catch (err) {
12395
12400
  return true;
12396
12401
  }
@@ -12420,7 +12425,7 @@ var BugBearClient = class {
12420
12425
  upsert: false
12421
12426
  });
12422
12427
  if (error) {
12423
- console.error("BugBear: Failed to upload screenshot", error);
12428
+ console.error("BugBear: Failed to upload screenshot", formatPgError(error));
12424
12429
  return null;
12425
12430
  }
12426
12431
  const { data: { publicUrl } } = this.supabase.storage.from(bucket).getPublicUrl(path);
@@ -12451,7 +12456,7 @@ var BugBearClient = class {
12451
12456
  upsert: false
12452
12457
  });
12453
12458
  if (error) {
12454
- console.error("BugBear: Failed to upload image from URI", error);
12459
+ console.error("BugBear: Failed to upload image from URI", formatPgError(error));
12455
12460
  return null;
12456
12461
  }
12457
12462
  const { data: { publicUrl } } = this.supabase.storage.from(bucket).getPublicUrl(path);
@@ -12526,7 +12531,7 @@ var BugBearClient = class {
12526
12531
  }
12527
12532
  const { data, error } = await query;
12528
12533
  if (error) {
12529
- console.error("BugBear: Failed to fetch fix requests", error);
12534
+ console.error("BugBear: Failed to fetch fix requests", formatPgError(error));
12530
12535
  return [];
12531
12536
  }
12532
12537
  return (data || []).map((fr) => ({
@@ -12557,7 +12562,7 @@ var BugBearClient = class {
12557
12562
  p_tester_id: testerInfo.id
12558
12563
  });
12559
12564
  if (error) {
12560
- console.error("BugBear: Failed to fetch threads via RPC", error);
12565
+ console.error("BugBear: Failed to fetch threads via RPC", formatPgError(error));
12561
12566
  return [];
12562
12567
  }
12563
12568
  if (!data || data.length === 0) return [];
@@ -12601,7 +12606,7 @@ var BugBearClient = class {
12601
12606
  attachments
12602
12607
  `).eq("thread_id", threadId).order("created_at", { ascending: true }).limit(200);
12603
12608
  if (error) {
12604
- console.error("BugBear: Failed to fetch messages", error);
12609
+ console.error("BugBear: Failed to fetch messages", formatPgError(error));
12605
12610
  return [];
12606
12611
  }
12607
12612
  return (data || []).map((msg) => ({
@@ -12645,7 +12650,7 @@ var BugBearClient = class {
12645
12650
  }
12646
12651
  const { error } = await this.supabase.from("discussion_messages").insert(insertData);
12647
12652
  if (error) {
12648
- console.error("BugBear: Failed to send message", error);
12653
+ console.error("BugBear: Failed to send message", formatPgError(error));
12649
12654
  return false;
12650
12655
  }
12651
12656
  await this.markThreadAsRead(threadId);
@@ -12761,7 +12766,7 @@ var BugBearClient = class {
12761
12766
  p_platform: options.platform || null
12762
12767
  });
12763
12768
  if (error) {
12764
- console.error("BugBear: Failed to start session", error);
12769
+ console.error("BugBear: Failed to start session", formatPgError(error));
12765
12770
  return { success: false, error: error.message };
12766
12771
  }
12767
12772
  const session = await this.getSession(data);
@@ -12786,7 +12791,7 @@ var BugBearClient = class {
12786
12791
  p_routes_covered: options.routesCovered || null
12787
12792
  });
12788
12793
  if (error) {
12789
- console.error("BugBear: Failed to end session", error);
12794
+ console.error("BugBear: Failed to end session", formatPgError(error));
12790
12795
  return { success: false, error: error.message };
12791
12796
  }
12792
12797
  const session = this.transformSession(data);
@@ -12834,7 +12839,7 @@ var BugBearClient = class {
12834
12839
  if (!testerInfo) return [];
12835
12840
  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);
12836
12841
  if (error) {
12837
- console.error("BugBear: Failed to fetch session history", error);
12842
+ console.error("BugBear: Failed to fetch session history", formatPgError(error));
12838
12843
  return [];
12839
12844
  }
12840
12845
  return (data || []).map((s) => this.transformSession(s));
@@ -12862,7 +12867,7 @@ var BugBearClient = class {
12862
12867
  p_app_context: options.appContext || null
12863
12868
  });
12864
12869
  if (error) {
12865
- console.error("BugBear: Failed to add finding", error);
12870
+ console.error("BugBear: Failed to add finding", formatPgError(error));
12866
12871
  return { success: false, error: error.message };
12867
12872
  }
12868
12873
  const finding = this.transformFinding(data);
@@ -12880,7 +12885,7 @@ var BugBearClient = class {
12880
12885
  try {
12881
12886
  const { data, error } = await this.supabase.from("qa_findings").select("*").eq("session_id", sessionId).order("created_at", { ascending: true }).limit(100);
12882
12887
  if (error) {
12883
- console.error("BugBear: Failed to fetch findings", error);
12888
+ console.error("BugBear: Failed to fetch findings", formatPgError(error));
12884
12889
  return [];
12885
12890
  }
12886
12891
  return (data || []).map((f) => this.transformFinding(f));
@@ -12898,7 +12903,7 @@ var BugBearClient = class {
12898
12903
  p_finding_id: findingId
12899
12904
  });
12900
12905
  if (error) {
12901
- console.error("BugBear: Failed to convert finding", error);
12906
+ console.error("BugBear: Failed to convert finding", formatPgError(error));
12902
12907
  return { success: false, error: error.message };
12903
12908
  }
12904
12909
  return { success: true, bugId: data };
@@ -12919,7 +12924,7 @@ var BugBearClient = class {
12919
12924
  dismissed_at: (/* @__PURE__ */ new Date()).toISOString()
12920
12925
  }).eq("id", findingId);
12921
12926
  if (error) {
12922
- console.error("BugBear: Failed to dismiss finding", error);
12927
+ console.error("BugBear: Failed to dismiss finding", formatPgError(error));
12923
12928
  return { success: false, error: error.message };
12924
12929
  }
12925
12930
  return { success: true };
@@ -13782,7 +13787,10 @@ function TestDetailScreen({ testId, nav }) {
13782
13787
  import_react_native4.Keyboard.dismiss();
13783
13788
  setIsSubmitting(true);
13784
13789
  try {
13785
- await client.failAssignment(displayedAssignment.id);
13790
+ const result = await client.failAssignment(displayedAssignment.id);
13791
+ if (!result.success) {
13792
+ console.error("BugBear: Failed to mark assignment as failed", result.error);
13793
+ }
13786
13794
  await refreshAssignments();
13787
13795
  nav.replace({
13788
13796
  name: "REPORT",
@@ -14013,10 +14021,19 @@ var import_react_native5 = require("react-native");
14013
14021
  function TestListScreen({ nav }) {
14014
14022
  const { assignments, currentAssignment, refreshAssignments } = useBugBear();
14015
14023
  const [filter, setFilter] = (0, import_react5.useState)("all");
14024
+ const [roleFilter, setRoleFilter] = (0, import_react5.useState)(null);
14016
14025
  const [collapsedFolders, setCollapsedFolders] = (0, import_react5.useState)(/* @__PURE__ */ new Set());
14017
14026
  (0, import_react5.useEffect)(() => {
14018
14027
  refreshAssignments();
14019
14028
  }, []);
14029
+ const availableRoles = (0, import_react5.useMemo)(() => {
14030
+ const roleMap = /* @__PURE__ */ new Map();
14031
+ for (const a of assignments) {
14032
+ if (a.testCase.role) roleMap.set(a.testCase.role.id, a.testCase.role);
14033
+ }
14034
+ return Array.from(roleMap.values());
14035
+ }, [assignments]);
14036
+ const selectedRole = availableRoles.find((r) => r.id === roleFilter);
14020
14037
  const groupedAssignments = (0, import_react5.useMemo)(() => {
14021
14038
  const groups = /* @__PURE__ */ new Map();
14022
14039
  for (const assignment of assignments) {
@@ -14060,16 +14077,39 @@ function TestListScreen({ nav }) {
14060
14077
  });
14061
14078
  }, []);
14062
14079
  const filterAssignment = (a) => {
14080
+ if (roleFilter && a.testCase.role?.id !== roleFilter) return false;
14063
14081
  if (filter === "pending") return a.status === "pending" || a.status === "in_progress";
14064
14082
  if (filter === "completed") return a.status === "passed" || a.status === "failed";
14065
14083
  return true;
14066
14084
  };
14067
- return /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, null, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.filterBar }, ["all", "pending", "completed"].map((f) => /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { key: f, style: [styles3.filterBtn, filter === f && styles3.filterBtnActive], onPress: () => setFilter(f) }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: [styles3.filterBtnText, filter === f && styles3.filterBtnTextActive] }, f === "all" ? `All (${assignments.length})` : f === "pending" ? `To Do (${assignments.filter((a) => a.status === "pending" || a.status === "in_progress").length})` : `Done (${assignments.filter((a) => a.status === "passed" || a.status === "failed").length})`)))), groupedAssignments.map((folder) => {
14085
+ return /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, null, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.filterBar }, ["all", "pending", "completed"].map((f) => /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { key: f, style: [styles3.filterBtn, filter === f && styles3.filterBtnActive], onPress: () => setFilter(f) }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: [styles3.filterBtnText, filter === f && styles3.filterBtnTextActive] }, f === "all" ? `All (${assignments.length})` : f === "pending" ? `To Do (${assignments.filter((a) => a.status === "pending" || a.status === "in_progress").length})` : `Done (${assignments.filter((a) => a.status === "passed" || a.status === "failed").length})`)))), availableRoles.length >= 2 && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.roleSection }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: styles3.roleBar }, /* @__PURE__ */ import_react5.default.createElement(
14086
+ import_react_native5.TouchableOpacity,
14087
+ {
14088
+ style: [styles3.roleBtn, !roleFilter && styles3.roleBtnActive],
14089
+ onPress: () => setRoleFilter(null)
14090
+ },
14091
+ /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: [styles3.roleBtnText, !roleFilter && styles3.roleBtnTextActive] }, "All Roles")
14092
+ ), availableRoles.map((role) => {
14093
+ const isActive = roleFilter === role.id;
14094
+ return /* @__PURE__ */ import_react5.default.createElement(
14095
+ import_react_native5.TouchableOpacity,
14096
+ {
14097
+ key: role.id,
14098
+ style: [
14099
+ styles3.roleBtn,
14100
+ isActive && { backgroundColor: role.color + "20", borderColor: role.color + "60", borderWidth: 1 }
14101
+ ],
14102
+ onPress: () => setRoleFilter(isActive ? null : role.id)
14103
+ },
14104
+ /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: [styles3.roleDot, { backgroundColor: role.color }] }),
14105
+ /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: [styles3.roleBtnText, isActive && { color: role.color, fontWeight: "600" }] }, role.name)
14106
+ );
14107
+ })), selectedRole?.loginHint && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: [styles3.loginHint, { backgroundColor: selectedRole.color + "10", borderColor: selectedRole.color + "30" }] }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.loginHintText }, "Log in as: ", selectedRole.loginHint))), groupedAssignments.map((folder) => {
14068
14108
  const folderId = folder.group?.id || "ungrouped";
14069
14109
  const isCollapsed = collapsedFolders.has(folderId);
14070
14110
  const filtered = folder.assignments.filter(filterAssignment);
14071
14111
  if (filtered.length === 0 && filter !== "all") return null;
14072
- return /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { key: folderId, style: styles3.folder }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { style: styles3.folderHeader, onPress: () => toggleFolder(folderId) }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.folderToggle }, isCollapsed ? "\u25B6" : "\u25BC"), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.folderName }, folder.group?.name || "Ungrouped"), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.folderProgress }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: [styles3.folderProgressFill, { width: `${Math.round((folder.stats.passed + folder.stats.failed) / folder.stats.total * 100)}%` }] })), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.folderCount }, folder.stats.passed + folder.stats.failed, "/", folder.stats.total)), !isCollapsed && filtered.map((assignment) => {
14112
+ return /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { key: folderId, style: styles3.folder }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { style: styles3.folderHeader, onPress: () => toggleFolder(folderId) }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.folderToggle }, isCollapsed ? "\u25B6" : "\u25BC"), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.folderName, numberOfLines: 1 }, folder.group?.name || "Ungrouped"), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.folderProgress }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: [styles3.folderProgressFill, { width: `${folder.stats.total > 0 ? Math.round((folder.stats.passed + folder.stats.failed) / folder.stats.total * 100) : 0}%` }] })), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.folderCount }, folder.stats.passed + folder.stats.failed, "/", folder.stats.total)), !isCollapsed && filtered.map((assignment) => {
14073
14113
  const badge = getStatusBadge(assignment.status);
14074
14114
  const isCurrent = currentAssignment?.id === assignment.id;
14075
14115
  return /* @__PURE__ */ import_react5.default.createElement(
@@ -14080,17 +14120,28 @@ function TestListScreen({ nav }) {
14080
14120
  onPress: () => nav.push({ name: "TEST_DETAIL", testId: assignment.id })
14081
14121
  },
14082
14122
  /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.testBadge }, badge.icon),
14083
- /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.testInfo }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.testTitle, numberOfLines: 1 }, assignment.testCase.title), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.testMetaRow }, assignment.isVerification && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.retestTag }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.retestTagText }, "Retest")), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.testMeta }, assignment.testCase.key, " \xB7 ", assignment.testCase.priority)))
14123
+ /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.testInfo }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.testTitle, numberOfLines: 1 }, assignment.testCase.title), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.testMetaRow }, assignment.isVerification && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.retestTag }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.retestTagText }, "Retest")), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.testMeta }, assignment.testCase.testKey, " \xB7 ", assignment.testCase.priority), assignment.testCase.role && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.roleBadgeRow }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.testMeta }, " \xB7 "), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: [styles3.roleBadgeDot, { backgroundColor: assignment.testCase.role.color }] }), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: [styles3.testMeta, { color: assignment.testCase.role.color, fontWeight: "500" }] }, assignment.testCase.role.name))))
14084
14124
  );
14085
14125
  }));
14086
- }), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { style: styles3.refreshBtn, onPress: refreshAssignments }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.refreshText }, "\u21BB Refresh")));
14126
+ }), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { style: styles3.refreshBtn, onPress: refreshAssignments }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.refreshText }, "\u21BB", " Refresh")));
14087
14127
  }
14088
14128
  var styles3 = import_react_native5.StyleSheet.create({
14089
- filterBar: { flexDirection: "row", gap: 8, marginBottom: 16 },
14129
+ filterBar: { flexDirection: "row", gap: 8, marginBottom: 8 },
14090
14130
  filterBtn: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 8, backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border },
14091
14131
  filterBtnActive: { backgroundColor: colors.blue, borderColor: colors.blue },
14092
14132
  filterBtnText: { fontSize: 12, color: colors.textSecondary },
14093
14133
  filterBtnTextActive: { color: "#fff", fontWeight: "600" },
14134
+ roleSection: { marginBottom: 12 },
14135
+ roleBar: { flexDirection: "row", marginBottom: 6 },
14136
+ roleBtn: { flexDirection: "row", alignItems: "center", gap: 5, paddingHorizontal: 10, paddingVertical: 4, borderRadius: 6, marginRight: 6, borderWidth: 1, borderColor: "transparent" },
14137
+ roleBtnActive: { backgroundColor: colors.card, borderColor: colors.border },
14138
+ roleBtnText: { fontSize: 11, color: colors.textMuted },
14139
+ roleBtnTextActive: { color: colors.textPrimary, fontWeight: "600" },
14140
+ roleDot: { width: 6, height: 6, borderRadius: 3 },
14141
+ loginHint: { borderRadius: 6, padding: 8, borderWidth: 1 },
14142
+ loginHintText: { fontSize: 11, color: colors.textSecondary },
14143
+ roleBadgeRow: { flexDirection: "row", alignItems: "center", gap: 3 },
14144
+ roleBadgeDot: { width: 5, height: 5, borderRadius: 3 },
14094
14145
  folder: { marginBottom: 12 },
14095
14146
  folderHeader: { flexDirection: "row", alignItems: "center", gap: 8, paddingVertical: 8, paddingHorizontal: 4 },
14096
14147
  folderToggle: { fontSize: 10, color: colors.textMuted, width: 14 },
@@ -14103,7 +14154,7 @@ var styles3 = import_react_native5.StyleSheet.create({
14103
14154
  testBadge: { fontSize: 16, marginRight: 10, width: 20 },
14104
14155
  testInfo: { flex: 1 },
14105
14156
  testTitle: { fontSize: 14, color: colors.textPrimary, marginBottom: 2 },
14106
- testMetaRow: { flexDirection: "row", alignItems: "center", gap: 6 },
14157
+ testMetaRow: { flexDirection: "row", alignItems: "center", gap: 6, flexWrap: "wrap" },
14107
14158
  retestTag: { backgroundColor: "#422006", borderWidth: 1, borderColor: "#854d0e", borderRadius: 4, paddingHorizontal: 5, paddingVertical: 1 },
14108
14159
  retestTagText: { fontSize: 10, fontWeight: "600", color: "#fbbf24" },
14109
14160
  testMeta: { fontSize: 11, color: colors.textDim },
@@ -14446,6 +14497,7 @@ function ReportScreen({ nav, prefill }) {
14446
14497
  const [submitting, setSubmitting] = (0, import_react10.useState)(false);
14447
14498
  const [error, setError] = (0, import_react10.useState)(null);
14448
14499
  const images = useImageAttachments(uploadImage, 5, "screenshots");
14500
+ const isRetestFailure = prefill?.type === "test_fail";
14449
14501
  const isBugType = reportType === "bug" || reportType === "test_fail";
14450
14502
  const handleSubmit = async () => {
14451
14503
  if (!client || !description.trim()) return;
@@ -14485,7 +14537,50 @@ function ReportScreen({ nav, prefill }) {
14485
14537
  setSubmitting(false);
14486
14538
  }
14487
14539
  };
14488
- return /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, null, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: shared.label }, "What are you reporting?"), /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, { style: styles7.typeRow }, [
14540
+ return /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, null, isRetestFailure ? /* @__PURE__ */ import_react10.default.createElement(import_react10.default.Fragment, null, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, { style: styles7.retestBanner }, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: styles7.retestIcon }, "\u{1F504}"), /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, null, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: styles7.retestTitle }, "Bug Still Present"), /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: styles7.retestSubtitle }, "The fix did not resolve this issue"))), /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, { style: styles7.section }, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: shared.label }, "Severity"), /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, { style: styles7.severityRow }, [
14541
+ { sev: "critical", color: "#ef4444" },
14542
+ { sev: "high", color: "#f97316" },
14543
+ { sev: "medium", color: "#eab308" },
14544
+ { sev: "low", color: "#6b7280" }
14545
+ ].map(({ sev, color }) => /* @__PURE__ */ import_react10.default.createElement(
14546
+ import_react_native9.TouchableOpacity,
14547
+ {
14548
+ key: sev,
14549
+ style: [styles7.sevButton, severity === sev && { backgroundColor: `${color}30`, borderColor: color }],
14550
+ onPress: () => setSeverity(sev)
14551
+ },
14552
+ /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: [styles7.sevText, severity === sev && { color }] }, sev)
14553
+ )))), /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, { style: styles7.section }, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: shared.label }, "What went wrong?"), /* @__PURE__ */ import_react10.default.createElement(
14554
+ import_react_native9.TextInput,
14555
+ {
14556
+ style: styles7.descInput,
14557
+ value: description,
14558
+ onChangeText: setDescription,
14559
+ placeholder: "Describe what you observed. What still doesn't work?",
14560
+ placeholderTextColor: colors.textMuted,
14561
+ multiline: true,
14562
+ numberOfLines: 4,
14563
+ textAlignVertical: "top"
14564
+ }
14565
+ )), /* @__PURE__ */ import_react10.default.createElement(
14566
+ ImagePickerButtons,
14567
+ {
14568
+ images: images.images,
14569
+ maxImages: 5,
14570
+ onPickGallery: images.pickFromGallery,
14571
+ onPickCamera: images.pickFromCamera,
14572
+ onRemove: images.removeImage,
14573
+ label: "Attachments (optional)"
14574
+ }
14575
+ ), error && /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, { style: styles7.errorBanner }, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: styles7.errorText }, error)), /* @__PURE__ */ import_react10.default.createElement(
14576
+ import_react_native9.TouchableOpacity,
14577
+ {
14578
+ style: [shared.primaryButton, styles7.retestSubmitButton, (!description.trim() || submitting || images.isUploading) && shared.primaryButtonDisabled, { marginTop: 20 }],
14579
+ onPress: handleSubmit,
14580
+ disabled: !description.trim() || submitting || images.isUploading
14581
+ },
14582
+ /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: shared.primaryButtonText }, images.isUploading ? "Uploading images..." : submitting ? "Submitting..." : error ? "Retry" : "Submit Failed Retest")
14583
+ )) : /* @__PURE__ */ import_react10.default.createElement(import_react10.default.Fragment, null, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: shared.label }, "What are you reporting?"), /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, { style: styles7.typeRow }, [
14489
14584
  { type: "bug", label: "Bug", icon: "\u{1F41B}" },
14490
14585
  { type: "feedback", label: "Feedback", icon: "\u{1F4A1}" },
14491
14586
  { type: "suggestion", label: "Idea", icon: "\u2728" }
@@ -14550,7 +14645,7 @@ function ReportScreen({ nav, prefill }) {
14550
14645
  disabled: !description.trim() || submitting || images.isUploading
14551
14646
  },
14552
14647
  /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: shared.primaryButtonText }, images.isUploading ? "Uploading images..." : submitting ? "Submitting..." : error ? "Retry" : "Submit Report")
14553
- ));
14648
+ )));
14554
14649
  }
14555
14650
  var styles7 = import_react_native9.StyleSheet.create({
14556
14651
  typeRow: { flexDirection: "row", gap: 10, marginBottom: 20 },
@@ -14567,7 +14662,12 @@ var styles7 = import_react_native9.StyleSheet.create({
14567
14662
  screenInput: { backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border, borderRadius: 8, paddingHorizontal: 12, paddingVertical: 8, fontSize: 13, color: colors.textPrimary },
14568
14663
  screenHint: { fontSize: 11, color: colors.textMuted, marginTop: 4 },
14569
14664
  errorBanner: { backgroundColor: "#7f1d1d", borderWidth: 1, borderColor: "#991b1b", borderRadius: 8, padding: 12, marginTop: 16 },
14570
- errorText: { fontSize: 13, color: "#fca5a5", lineHeight: 18 }
14665
+ errorText: { fontSize: 13, color: "#fca5a5", lineHeight: 18 },
14666
+ retestBanner: { flexDirection: "row", alignItems: "center", gap: 10, backgroundColor: "#422006", borderWidth: 1, borderColor: "#854d0e", borderRadius: 10, paddingVertical: 12, paddingHorizontal: 14, marginBottom: 20 },
14667
+ retestIcon: { fontSize: 16 },
14668
+ retestTitle: { fontSize: 15, fontWeight: "600", color: "#fbbf24", lineHeight: 20 },
14669
+ retestSubtitle: { fontSize: 12, color: "#d97706", lineHeight: 16 },
14670
+ retestSubmitButton: { backgroundColor: "#b45309" }
14571
14671
  });
14572
14672
 
14573
14673
  // src/widget/screens/ReportSuccessScreen.tsx
package/dist/index.mjs CHANGED
@@ -11360,7 +11360,7 @@ function shouldShowDeprecationWarning() {
11360
11360
  }
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
- // ../core/dist/index.mjs
11363
+ // node_modules/@bbearai/core/dist/index.mjs
11364
11364
  var MAX_CONSOLE_LOGS = 50;
11365
11365
  var MAX_NETWORK_REQUESTS = 20;
11366
11366
  var MAX_NAVIGATION_HISTORY = 20;
@@ -11601,6 +11601,11 @@ function captureError(error, errorInfo) {
11601
11601
  componentStack: errorInfo?.componentStack
11602
11602
  };
11603
11603
  }
11604
+ var formatPgError = (e) => {
11605
+ if (!e || typeof e !== "object") return { raw: e };
11606
+ const { message, code, details, hint } = e;
11607
+ return { message, code, details, hint };
11608
+ };
11604
11609
  var DEFAULT_SUPABASE_URL = "https://kyxgzjnqgvapvlnvqawz.supabase.co";
11605
11610
  var getEnvVar = (key) => {
11606
11611
  try {
@@ -11770,7 +11775,7 @@ var BugBearClient = class {
11770
11775
  )
11771
11776
  `).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true }).limit(100);
11772
11777
  if (error) {
11773
- console.error("BugBear: Failed to fetch assignments", error);
11778
+ console.error("BugBear: Failed to fetch assignments", formatPgError(error));
11774
11779
  return [];
11775
11780
  }
11776
11781
  const mapped = (data || []).map((item) => ({
@@ -11998,7 +12003,7 @@ var BugBearClient = class {
11998
12003
  }
11999
12004
  const { error } = await this.supabase.from("test_assignments").update(updateData).eq("id", assignmentId);
12000
12005
  if (error) {
12001
- console.error("BugBear: Failed to skip assignment", error);
12006
+ console.error("BugBear: Failed to skip assignment", formatPgError(error));
12002
12007
  return { success: false, error: error.message };
12003
12008
  }
12004
12009
  return { success: true };
@@ -12177,7 +12182,7 @@ var BugBearClient = class {
12177
12182
  p_tester_id: testerInfo.id
12178
12183
  });
12179
12184
  if (error) {
12180
- console.error("BugBear: Failed to fetch tester stats", error);
12185
+ console.error("BugBear: Failed to fetch tester stats", formatPgError(error));
12181
12186
  return null;
12182
12187
  }
12183
12188
  return data;
@@ -12328,7 +12333,7 @@ var BugBearClient = class {
12328
12333
  if (updates.platforms !== void 0) updateData.platforms = updates.platforms;
12329
12334
  const { error } = await this.supabase.from("testers").update(updateData).eq("id", testerInfo.id);
12330
12335
  if (error) {
12331
- console.error("BugBear: updateTesterProfile error", error);
12336
+ console.error("BugBear: updateTesterProfile error", formatPgError(error));
12332
12337
  return { success: false, error: error.message };
12333
12338
  }
12334
12339
  return { success: true };
@@ -12350,14 +12355,14 @@ var BugBearClient = class {
12350
12355
  */
12351
12356
  async isQAEnabled() {
12352
12357
  try {
12353
- const { data, error } = await this.supabase.from("projects").select("is_qa_enabled").eq("id", this.config.projectId).single();
12358
+ const { data, error } = await this.supabase.rpc("check_qa_enabled", {
12359
+ p_project_id: this.config.projectId
12360
+ });
12354
12361
  if (error) {
12355
- if (error.code !== "PGRST116") {
12356
- console.warn("BugBear: Could not check QA status", error.message || error.code || "Unknown error");
12357
- }
12362
+ console.warn("BugBear: Could not check QA status", error.message || error.code || "Unknown error");
12358
12363
  return true;
12359
12364
  }
12360
- return data?.is_qa_enabled ?? true;
12365
+ return data ?? true;
12361
12366
  } catch (err) {
12362
12367
  return true;
12363
12368
  }
@@ -12387,7 +12392,7 @@ var BugBearClient = class {
12387
12392
  upsert: false
12388
12393
  });
12389
12394
  if (error) {
12390
- console.error("BugBear: Failed to upload screenshot", error);
12395
+ console.error("BugBear: Failed to upload screenshot", formatPgError(error));
12391
12396
  return null;
12392
12397
  }
12393
12398
  const { data: { publicUrl } } = this.supabase.storage.from(bucket).getPublicUrl(path);
@@ -12418,7 +12423,7 @@ var BugBearClient = class {
12418
12423
  upsert: false
12419
12424
  });
12420
12425
  if (error) {
12421
- console.error("BugBear: Failed to upload image from URI", error);
12426
+ console.error("BugBear: Failed to upload image from URI", formatPgError(error));
12422
12427
  return null;
12423
12428
  }
12424
12429
  const { data: { publicUrl } } = this.supabase.storage.from(bucket).getPublicUrl(path);
@@ -12493,7 +12498,7 @@ var BugBearClient = class {
12493
12498
  }
12494
12499
  const { data, error } = await query;
12495
12500
  if (error) {
12496
- console.error("BugBear: Failed to fetch fix requests", error);
12501
+ console.error("BugBear: Failed to fetch fix requests", formatPgError(error));
12497
12502
  return [];
12498
12503
  }
12499
12504
  return (data || []).map((fr) => ({
@@ -12524,7 +12529,7 @@ var BugBearClient = class {
12524
12529
  p_tester_id: testerInfo.id
12525
12530
  });
12526
12531
  if (error) {
12527
- console.error("BugBear: Failed to fetch threads via RPC", error);
12532
+ console.error("BugBear: Failed to fetch threads via RPC", formatPgError(error));
12528
12533
  return [];
12529
12534
  }
12530
12535
  if (!data || data.length === 0) return [];
@@ -12568,7 +12573,7 @@ var BugBearClient = class {
12568
12573
  attachments
12569
12574
  `).eq("thread_id", threadId).order("created_at", { ascending: true }).limit(200);
12570
12575
  if (error) {
12571
- console.error("BugBear: Failed to fetch messages", error);
12576
+ console.error("BugBear: Failed to fetch messages", formatPgError(error));
12572
12577
  return [];
12573
12578
  }
12574
12579
  return (data || []).map((msg) => ({
@@ -12612,7 +12617,7 @@ var BugBearClient = class {
12612
12617
  }
12613
12618
  const { error } = await this.supabase.from("discussion_messages").insert(insertData);
12614
12619
  if (error) {
12615
- console.error("BugBear: Failed to send message", error);
12620
+ console.error("BugBear: Failed to send message", formatPgError(error));
12616
12621
  return false;
12617
12622
  }
12618
12623
  await this.markThreadAsRead(threadId);
@@ -12728,7 +12733,7 @@ var BugBearClient = class {
12728
12733
  p_platform: options.platform || null
12729
12734
  });
12730
12735
  if (error) {
12731
- console.error("BugBear: Failed to start session", error);
12736
+ console.error("BugBear: Failed to start session", formatPgError(error));
12732
12737
  return { success: false, error: error.message };
12733
12738
  }
12734
12739
  const session = await this.getSession(data);
@@ -12753,7 +12758,7 @@ var BugBearClient = class {
12753
12758
  p_routes_covered: options.routesCovered || null
12754
12759
  });
12755
12760
  if (error) {
12756
- console.error("BugBear: Failed to end session", error);
12761
+ console.error("BugBear: Failed to end session", formatPgError(error));
12757
12762
  return { success: false, error: error.message };
12758
12763
  }
12759
12764
  const session = this.transformSession(data);
@@ -12801,7 +12806,7 @@ var BugBearClient = class {
12801
12806
  if (!testerInfo) return [];
12802
12807
  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);
12803
12808
  if (error) {
12804
- console.error("BugBear: Failed to fetch session history", error);
12809
+ console.error("BugBear: Failed to fetch session history", formatPgError(error));
12805
12810
  return [];
12806
12811
  }
12807
12812
  return (data || []).map((s) => this.transformSession(s));
@@ -12829,7 +12834,7 @@ var BugBearClient = class {
12829
12834
  p_app_context: options.appContext || null
12830
12835
  });
12831
12836
  if (error) {
12832
- console.error("BugBear: Failed to add finding", error);
12837
+ console.error("BugBear: Failed to add finding", formatPgError(error));
12833
12838
  return { success: false, error: error.message };
12834
12839
  }
12835
12840
  const finding = this.transformFinding(data);
@@ -12847,7 +12852,7 @@ var BugBearClient = class {
12847
12852
  try {
12848
12853
  const { data, error } = await this.supabase.from("qa_findings").select("*").eq("session_id", sessionId).order("created_at", { ascending: true }).limit(100);
12849
12854
  if (error) {
12850
- console.error("BugBear: Failed to fetch findings", error);
12855
+ console.error("BugBear: Failed to fetch findings", formatPgError(error));
12851
12856
  return [];
12852
12857
  }
12853
12858
  return (data || []).map((f) => this.transformFinding(f));
@@ -12865,7 +12870,7 @@ var BugBearClient = class {
12865
12870
  p_finding_id: findingId
12866
12871
  });
12867
12872
  if (error) {
12868
- console.error("BugBear: Failed to convert finding", error);
12873
+ console.error("BugBear: Failed to convert finding", formatPgError(error));
12869
12874
  return { success: false, error: error.message };
12870
12875
  }
12871
12876
  return { success: true, bugId: data };
@@ -12886,7 +12891,7 @@ var BugBearClient = class {
12886
12891
  dismissed_at: (/* @__PURE__ */ new Date()).toISOString()
12887
12892
  }).eq("id", findingId);
12888
12893
  if (error) {
12889
- console.error("BugBear: Failed to dismiss finding", error);
12894
+ console.error("BugBear: Failed to dismiss finding", formatPgError(error));
12890
12895
  return { success: false, error: error.message };
12891
12896
  }
12892
12897
  return { success: true };
@@ -13215,7 +13220,7 @@ import {
13215
13220
  Image as Image3,
13216
13221
  TouchableOpacity as TouchableOpacity12,
13217
13222
  Modal as Modal2,
13218
- ScrollView as ScrollView2,
13223
+ ScrollView as ScrollView3,
13219
13224
  StyleSheet as StyleSheet14,
13220
13225
  Dimensions as Dimensions2,
13221
13226
  KeyboardAvoidingView,
@@ -13764,7 +13769,10 @@ function TestDetailScreen({ testId, nav }) {
13764
13769
  Keyboard.dismiss();
13765
13770
  setIsSubmitting(true);
13766
13771
  try {
13767
- await client.failAssignment(displayedAssignment.id);
13772
+ const result = await client.failAssignment(displayedAssignment.id);
13773
+ if (!result.success) {
13774
+ console.error("BugBear: Failed to mark assignment as failed", result.error);
13775
+ }
13768
13776
  await refreshAssignments();
13769
13777
  nav.replace({
13770
13778
  name: "REPORT",
@@ -13991,14 +13999,23 @@ var styles2 = StyleSheet3.create({
13991
13999
 
13992
14000
  // src/widget/screens/TestListScreen.tsx
13993
14001
  import React4, { useState as useState3, useMemo as useMemo2, useCallback as useCallback3, useEffect as useEffect4 } from "react";
13994
- import { View as View3, Text as Text3, TouchableOpacity as TouchableOpacity3, StyleSheet as StyleSheet4 } from "react-native";
14002
+ import { View as View3, Text as Text3, TouchableOpacity as TouchableOpacity3, StyleSheet as StyleSheet4, ScrollView } from "react-native";
13995
14003
  function TestListScreen({ nav }) {
13996
14004
  const { assignments, currentAssignment, refreshAssignments } = useBugBear();
13997
14005
  const [filter, setFilter] = useState3("all");
14006
+ const [roleFilter, setRoleFilter] = useState3(null);
13998
14007
  const [collapsedFolders, setCollapsedFolders] = useState3(/* @__PURE__ */ new Set());
13999
14008
  useEffect4(() => {
14000
14009
  refreshAssignments();
14001
14010
  }, []);
14011
+ const availableRoles = useMemo2(() => {
14012
+ const roleMap = /* @__PURE__ */ new Map();
14013
+ for (const a of assignments) {
14014
+ if (a.testCase.role) roleMap.set(a.testCase.role.id, a.testCase.role);
14015
+ }
14016
+ return Array.from(roleMap.values());
14017
+ }, [assignments]);
14018
+ const selectedRole = availableRoles.find((r) => r.id === roleFilter);
14002
14019
  const groupedAssignments = useMemo2(() => {
14003
14020
  const groups = /* @__PURE__ */ new Map();
14004
14021
  for (const assignment of assignments) {
@@ -14042,16 +14059,39 @@ function TestListScreen({ nav }) {
14042
14059
  });
14043
14060
  }, []);
14044
14061
  const filterAssignment = (a) => {
14062
+ if (roleFilter && a.testCase.role?.id !== roleFilter) return false;
14045
14063
  if (filter === "pending") return a.status === "pending" || a.status === "in_progress";
14046
14064
  if (filter === "completed") return a.status === "passed" || a.status === "failed";
14047
14065
  return true;
14048
14066
  };
14049
- return /* @__PURE__ */ React4.createElement(View3, null, /* @__PURE__ */ React4.createElement(View3, { style: styles3.filterBar }, ["all", "pending", "completed"].map((f) => /* @__PURE__ */ React4.createElement(TouchableOpacity3, { key: f, style: [styles3.filterBtn, filter === f && styles3.filterBtnActive], onPress: () => setFilter(f) }, /* @__PURE__ */ React4.createElement(Text3, { style: [styles3.filterBtnText, filter === f && styles3.filterBtnTextActive] }, f === "all" ? `All (${assignments.length})` : f === "pending" ? `To Do (${assignments.filter((a) => a.status === "pending" || a.status === "in_progress").length})` : `Done (${assignments.filter((a) => a.status === "passed" || a.status === "failed").length})`)))), groupedAssignments.map((folder) => {
14067
+ return /* @__PURE__ */ React4.createElement(View3, null, /* @__PURE__ */ React4.createElement(View3, { style: styles3.filterBar }, ["all", "pending", "completed"].map((f) => /* @__PURE__ */ React4.createElement(TouchableOpacity3, { key: f, style: [styles3.filterBtn, filter === f && styles3.filterBtnActive], onPress: () => setFilter(f) }, /* @__PURE__ */ React4.createElement(Text3, { style: [styles3.filterBtnText, filter === f && styles3.filterBtnTextActive] }, f === "all" ? `All (${assignments.length})` : f === "pending" ? `To Do (${assignments.filter((a) => a.status === "pending" || a.status === "in_progress").length})` : `Done (${assignments.filter((a) => a.status === "passed" || a.status === "failed").length})`)))), availableRoles.length >= 2 && /* @__PURE__ */ React4.createElement(View3, { style: styles3.roleSection }, /* @__PURE__ */ React4.createElement(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: styles3.roleBar }, /* @__PURE__ */ React4.createElement(
14068
+ TouchableOpacity3,
14069
+ {
14070
+ style: [styles3.roleBtn, !roleFilter && styles3.roleBtnActive],
14071
+ onPress: () => setRoleFilter(null)
14072
+ },
14073
+ /* @__PURE__ */ React4.createElement(Text3, { style: [styles3.roleBtnText, !roleFilter && styles3.roleBtnTextActive] }, "All Roles")
14074
+ ), availableRoles.map((role) => {
14075
+ const isActive = roleFilter === role.id;
14076
+ return /* @__PURE__ */ React4.createElement(
14077
+ TouchableOpacity3,
14078
+ {
14079
+ key: role.id,
14080
+ style: [
14081
+ styles3.roleBtn,
14082
+ isActive && { backgroundColor: role.color + "20", borderColor: role.color + "60", borderWidth: 1 }
14083
+ ],
14084
+ onPress: () => setRoleFilter(isActive ? null : role.id)
14085
+ },
14086
+ /* @__PURE__ */ React4.createElement(View3, { style: [styles3.roleDot, { backgroundColor: role.color }] }),
14087
+ /* @__PURE__ */ React4.createElement(Text3, { style: [styles3.roleBtnText, isActive && { color: role.color, fontWeight: "600" }] }, role.name)
14088
+ );
14089
+ })), selectedRole?.loginHint && /* @__PURE__ */ React4.createElement(View3, { style: [styles3.loginHint, { backgroundColor: selectedRole.color + "10", borderColor: selectedRole.color + "30" }] }, /* @__PURE__ */ React4.createElement(Text3, { style: styles3.loginHintText }, "Log in as: ", selectedRole.loginHint))), groupedAssignments.map((folder) => {
14050
14090
  const folderId = folder.group?.id || "ungrouped";
14051
14091
  const isCollapsed = collapsedFolders.has(folderId);
14052
14092
  const filtered = folder.assignments.filter(filterAssignment);
14053
14093
  if (filtered.length === 0 && filter !== "all") return null;
14054
- return /* @__PURE__ */ React4.createElement(View3, { key: folderId, style: styles3.folder }, /* @__PURE__ */ React4.createElement(TouchableOpacity3, { style: styles3.folderHeader, onPress: () => toggleFolder(folderId) }, /* @__PURE__ */ React4.createElement(Text3, { style: styles3.folderToggle }, isCollapsed ? "\u25B6" : "\u25BC"), /* @__PURE__ */ React4.createElement(Text3, { style: styles3.folderName }, folder.group?.name || "Ungrouped"), /* @__PURE__ */ React4.createElement(View3, { style: styles3.folderProgress }, /* @__PURE__ */ React4.createElement(View3, { style: [styles3.folderProgressFill, { width: `${Math.round((folder.stats.passed + folder.stats.failed) / folder.stats.total * 100)}%` }] })), /* @__PURE__ */ React4.createElement(Text3, { style: styles3.folderCount }, folder.stats.passed + folder.stats.failed, "/", folder.stats.total)), !isCollapsed && filtered.map((assignment) => {
14094
+ return /* @__PURE__ */ React4.createElement(View3, { key: folderId, style: styles3.folder }, /* @__PURE__ */ React4.createElement(TouchableOpacity3, { style: styles3.folderHeader, onPress: () => toggleFolder(folderId) }, /* @__PURE__ */ React4.createElement(Text3, { style: styles3.folderToggle }, isCollapsed ? "\u25B6" : "\u25BC"), /* @__PURE__ */ React4.createElement(Text3, { style: styles3.folderName, numberOfLines: 1 }, folder.group?.name || "Ungrouped"), /* @__PURE__ */ React4.createElement(View3, { style: styles3.folderProgress }, /* @__PURE__ */ React4.createElement(View3, { style: [styles3.folderProgressFill, { width: `${folder.stats.total > 0 ? Math.round((folder.stats.passed + folder.stats.failed) / folder.stats.total * 100) : 0}%` }] })), /* @__PURE__ */ React4.createElement(Text3, { style: styles3.folderCount }, folder.stats.passed + folder.stats.failed, "/", folder.stats.total)), !isCollapsed && filtered.map((assignment) => {
14055
14095
  const badge = getStatusBadge(assignment.status);
14056
14096
  const isCurrent = currentAssignment?.id === assignment.id;
14057
14097
  return /* @__PURE__ */ React4.createElement(
@@ -14062,17 +14102,28 @@ function TestListScreen({ nav }) {
14062
14102
  onPress: () => nav.push({ name: "TEST_DETAIL", testId: assignment.id })
14063
14103
  },
14064
14104
  /* @__PURE__ */ React4.createElement(Text3, { style: styles3.testBadge }, badge.icon),
14065
- /* @__PURE__ */ React4.createElement(View3, { style: styles3.testInfo }, /* @__PURE__ */ React4.createElement(Text3, { style: styles3.testTitle, numberOfLines: 1 }, assignment.testCase.title), /* @__PURE__ */ React4.createElement(View3, { style: styles3.testMetaRow }, assignment.isVerification && /* @__PURE__ */ React4.createElement(View3, { style: styles3.retestTag }, /* @__PURE__ */ React4.createElement(Text3, { style: styles3.retestTagText }, "Retest")), /* @__PURE__ */ React4.createElement(Text3, { style: styles3.testMeta }, assignment.testCase.key, " \xB7 ", assignment.testCase.priority)))
14105
+ /* @__PURE__ */ React4.createElement(View3, { style: styles3.testInfo }, /* @__PURE__ */ React4.createElement(Text3, { style: styles3.testTitle, numberOfLines: 1 }, assignment.testCase.title), /* @__PURE__ */ React4.createElement(View3, { style: styles3.testMetaRow }, assignment.isVerification && /* @__PURE__ */ React4.createElement(View3, { style: styles3.retestTag }, /* @__PURE__ */ React4.createElement(Text3, { style: styles3.retestTagText }, "Retest")), /* @__PURE__ */ React4.createElement(Text3, { style: styles3.testMeta }, assignment.testCase.testKey, " \xB7 ", assignment.testCase.priority), assignment.testCase.role && /* @__PURE__ */ React4.createElement(View3, { style: styles3.roleBadgeRow }, /* @__PURE__ */ React4.createElement(Text3, { style: styles3.testMeta }, " \xB7 "), /* @__PURE__ */ React4.createElement(View3, { style: [styles3.roleBadgeDot, { backgroundColor: assignment.testCase.role.color }] }), /* @__PURE__ */ React4.createElement(Text3, { style: [styles3.testMeta, { color: assignment.testCase.role.color, fontWeight: "500" }] }, assignment.testCase.role.name))))
14066
14106
  );
14067
14107
  }));
14068
- }), /* @__PURE__ */ React4.createElement(TouchableOpacity3, { style: styles3.refreshBtn, onPress: refreshAssignments }, /* @__PURE__ */ React4.createElement(Text3, { style: styles3.refreshText }, "\u21BB Refresh")));
14108
+ }), /* @__PURE__ */ React4.createElement(TouchableOpacity3, { style: styles3.refreshBtn, onPress: refreshAssignments }, /* @__PURE__ */ React4.createElement(Text3, { style: styles3.refreshText }, "\u21BB", " Refresh")));
14069
14109
  }
14070
14110
  var styles3 = StyleSheet4.create({
14071
- filterBar: { flexDirection: "row", gap: 8, marginBottom: 16 },
14111
+ filterBar: { flexDirection: "row", gap: 8, marginBottom: 8 },
14072
14112
  filterBtn: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 8, backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border },
14073
14113
  filterBtnActive: { backgroundColor: colors.blue, borderColor: colors.blue },
14074
14114
  filterBtnText: { fontSize: 12, color: colors.textSecondary },
14075
14115
  filterBtnTextActive: { color: "#fff", fontWeight: "600" },
14116
+ roleSection: { marginBottom: 12 },
14117
+ roleBar: { flexDirection: "row", marginBottom: 6 },
14118
+ roleBtn: { flexDirection: "row", alignItems: "center", gap: 5, paddingHorizontal: 10, paddingVertical: 4, borderRadius: 6, marginRight: 6, borderWidth: 1, borderColor: "transparent" },
14119
+ roleBtnActive: { backgroundColor: colors.card, borderColor: colors.border },
14120
+ roleBtnText: { fontSize: 11, color: colors.textMuted },
14121
+ roleBtnTextActive: { color: colors.textPrimary, fontWeight: "600" },
14122
+ roleDot: { width: 6, height: 6, borderRadius: 3 },
14123
+ loginHint: { borderRadius: 6, padding: 8, borderWidth: 1 },
14124
+ loginHintText: { fontSize: 11, color: colors.textSecondary },
14125
+ roleBadgeRow: { flexDirection: "row", alignItems: "center", gap: 3 },
14126
+ roleBadgeDot: { width: 5, height: 5, borderRadius: 3 },
14076
14127
  folder: { marginBottom: 12 },
14077
14128
  folderHeader: { flexDirection: "row", alignItems: "center", gap: 8, paddingVertical: 8, paddingHorizontal: 4 },
14078
14129
  folderToggle: { fontSize: 10, color: colors.textMuted, width: 14 },
@@ -14085,7 +14136,7 @@ var styles3 = StyleSheet4.create({
14085
14136
  testBadge: { fontSize: 16, marginRight: 10, width: 20 },
14086
14137
  testInfo: { flex: 1 },
14087
14138
  testTitle: { fontSize: 14, color: colors.textPrimary, marginBottom: 2 },
14088
- testMetaRow: { flexDirection: "row", alignItems: "center", gap: 6 },
14139
+ testMetaRow: { flexDirection: "row", alignItems: "center", gap: 6, flexWrap: "wrap" },
14089
14140
  retestTag: { backgroundColor: "#422006", borderWidth: 1, borderColor: "#854d0e", borderRadius: 4, paddingHorizontal: 5, paddingVertical: 1 },
14090
14141
  retestTagText: { fontSize: 10, fontWeight: "600", color: "#fbbf24" },
14091
14142
  testMeta: { fontSize: 11, color: colors.textDim },
@@ -14180,10 +14231,10 @@ import { View as View5, Text as Text5, TouchableOpacity as TouchableOpacity5, St
14180
14231
 
14181
14232
  // src/widget/ImagePreviewStrip.tsx
14182
14233
  import React5 from "react";
14183
- import { View as View4, Text as Text4, TouchableOpacity as TouchableOpacity4, ScrollView, Image, ActivityIndicator, StyleSheet as StyleSheet5 } from "react-native";
14234
+ import { View as View4, Text as Text4, TouchableOpacity as TouchableOpacity4, ScrollView as ScrollView2, Image, ActivityIndicator, StyleSheet as StyleSheet5 } from "react-native";
14184
14235
  function ImagePreviewStrip({ images, onRemove }) {
14185
14236
  if (images.length === 0) return null;
14186
- return /* @__PURE__ */ React5.createElement(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: styles4.strip }, images.map((img) => /* @__PURE__ */ React5.createElement(View4, { key: img.id, style: styles4.thumbContainer }, /* @__PURE__ */ React5.createElement(Image, { source: { uri: img.localUri }, style: styles4.thumb }), img.status === "uploading" && /* @__PURE__ */ React5.createElement(View4, { style: styles4.thumbOverlay }, /* @__PURE__ */ React5.createElement(ActivityIndicator, { size: "small", color: "#fff" })), img.status === "error" && /* @__PURE__ */ React5.createElement(View4, { style: [styles4.thumbOverlay, styles4.thumbOverlayError] }, /* @__PURE__ */ React5.createElement(Text4, { style: styles4.thumbErrorText }, "!")), /* @__PURE__ */ React5.createElement(TouchableOpacity4, { style: styles4.thumbRemove, onPress: () => onRemove(img.id) }, /* @__PURE__ */ React5.createElement(Text4, { style: styles4.thumbRemoveText }, "\u2715")))));
14237
+ return /* @__PURE__ */ React5.createElement(ScrollView2, { horizontal: true, showsHorizontalScrollIndicator: false, style: styles4.strip }, images.map((img) => /* @__PURE__ */ React5.createElement(View4, { key: img.id, style: styles4.thumbContainer }, /* @__PURE__ */ React5.createElement(Image, { source: { uri: img.localUri }, style: styles4.thumb }), img.status === "uploading" && /* @__PURE__ */ React5.createElement(View4, { style: styles4.thumbOverlay }, /* @__PURE__ */ React5.createElement(ActivityIndicator, { size: "small", color: "#fff" })), img.status === "error" && /* @__PURE__ */ React5.createElement(View4, { style: [styles4.thumbOverlay, styles4.thumbOverlayError] }, /* @__PURE__ */ React5.createElement(Text4, { style: styles4.thumbErrorText }, "!")), /* @__PURE__ */ React5.createElement(TouchableOpacity4, { style: styles4.thumbRemove, onPress: () => onRemove(img.id) }, /* @__PURE__ */ React5.createElement(Text4, { style: styles4.thumbRemoveText }, "\u2715")))));
14187
14238
  }
14188
14239
  var styles4 = StyleSheet5.create({
14189
14240
  strip: {
@@ -14428,6 +14479,7 @@ function ReportScreen({ nav, prefill }) {
14428
14479
  const [submitting, setSubmitting] = useState6(false);
14429
14480
  const [error, setError] = useState6(null);
14430
14481
  const images = useImageAttachments(uploadImage, 5, "screenshots");
14482
+ const isRetestFailure = prefill?.type === "test_fail";
14431
14483
  const isBugType = reportType === "bug" || reportType === "test_fail";
14432
14484
  const handleSubmit = async () => {
14433
14485
  if (!client || !description.trim()) return;
@@ -14467,7 +14519,50 @@ function ReportScreen({ nav, prefill }) {
14467
14519
  setSubmitting(false);
14468
14520
  }
14469
14521
  };
14470
- return /* @__PURE__ */ React8.createElement(View7, null, /* @__PURE__ */ React8.createElement(Text7, { style: shared.label }, "What are you reporting?"), /* @__PURE__ */ React8.createElement(View7, { style: styles7.typeRow }, [
14522
+ return /* @__PURE__ */ React8.createElement(View7, null, isRetestFailure ? /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(View7, { style: styles7.retestBanner }, /* @__PURE__ */ React8.createElement(Text7, { style: styles7.retestIcon }, "\u{1F504}"), /* @__PURE__ */ React8.createElement(View7, null, /* @__PURE__ */ React8.createElement(Text7, { style: styles7.retestTitle }, "Bug Still Present"), /* @__PURE__ */ React8.createElement(Text7, { style: styles7.retestSubtitle }, "The fix did not resolve this issue"))), /* @__PURE__ */ React8.createElement(View7, { style: styles7.section }, /* @__PURE__ */ React8.createElement(Text7, { style: shared.label }, "Severity"), /* @__PURE__ */ React8.createElement(View7, { style: styles7.severityRow }, [
14523
+ { sev: "critical", color: "#ef4444" },
14524
+ { sev: "high", color: "#f97316" },
14525
+ { sev: "medium", color: "#eab308" },
14526
+ { sev: "low", color: "#6b7280" }
14527
+ ].map(({ sev, color }) => /* @__PURE__ */ React8.createElement(
14528
+ TouchableOpacity7,
14529
+ {
14530
+ key: sev,
14531
+ style: [styles7.sevButton, severity === sev && { backgroundColor: `${color}30`, borderColor: color }],
14532
+ onPress: () => setSeverity(sev)
14533
+ },
14534
+ /* @__PURE__ */ React8.createElement(Text7, { style: [styles7.sevText, severity === sev && { color }] }, sev)
14535
+ )))), /* @__PURE__ */ React8.createElement(View7, { style: styles7.section }, /* @__PURE__ */ React8.createElement(Text7, { style: shared.label }, "What went wrong?"), /* @__PURE__ */ React8.createElement(
14536
+ TextInput3,
14537
+ {
14538
+ style: styles7.descInput,
14539
+ value: description,
14540
+ onChangeText: setDescription,
14541
+ placeholder: "Describe what you observed. What still doesn't work?",
14542
+ placeholderTextColor: colors.textMuted,
14543
+ multiline: true,
14544
+ numberOfLines: 4,
14545
+ textAlignVertical: "top"
14546
+ }
14547
+ )), /* @__PURE__ */ React8.createElement(
14548
+ ImagePickerButtons,
14549
+ {
14550
+ images: images.images,
14551
+ maxImages: 5,
14552
+ onPickGallery: images.pickFromGallery,
14553
+ onPickCamera: images.pickFromCamera,
14554
+ onRemove: images.removeImage,
14555
+ label: "Attachments (optional)"
14556
+ }
14557
+ ), error && /* @__PURE__ */ React8.createElement(View7, { style: styles7.errorBanner }, /* @__PURE__ */ React8.createElement(Text7, { style: styles7.errorText }, error)), /* @__PURE__ */ React8.createElement(
14558
+ TouchableOpacity7,
14559
+ {
14560
+ style: [shared.primaryButton, styles7.retestSubmitButton, (!description.trim() || submitting || images.isUploading) && shared.primaryButtonDisabled, { marginTop: 20 }],
14561
+ onPress: handleSubmit,
14562
+ disabled: !description.trim() || submitting || images.isUploading
14563
+ },
14564
+ /* @__PURE__ */ React8.createElement(Text7, { style: shared.primaryButtonText }, images.isUploading ? "Uploading images..." : submitting ? "Submitting..." : error ? "Retry" : "Submit Failed Retest")
14565
+ )) : /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(Text7, { style: shared.label }, "What are you reporting?"), /* @__PURE__ */ React8.createElement(View7, { style: styles7.typeRow }, [
14471
14566
  { type: "bug", label: "Bug", icon: "\u{1F41B}" },
14472
14567
  { type: "feedback", label: "Feedback", icon: "\u{1F4A1}" },
14473
14568
  { type: "suggestion", label: "Idea", icon: "\u2728" }
@@ -14532,7 +14627,7 @@ function ReportScreen({ nav, prefill }) {
14532
14627
  disabled: !description.trim() || submitting || images.isUploading
14533
14628
  },
14534
14629
  /* @__PURE__ */ React8.createElement(Text7, { style: shared.primaryButtonText }, images.isUploading ? "Uploading images..." : submitting ? "Submitting..." : error ? "Retry" : "Submit Report")
14535
- ));
14630
+ )));
14536
14631
  }
14537
14632
  var styles7 = StyleSheet8.create({
14538
14633
  typeRow: { flexDirection: "row", gap: 10, marginBottom: 20 },
@@ -14549,7 +14644,12 @@ var styles7 = StyleSheet8.create({
14549
14644
  screenInput: { backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border, borderRadius: 8, paddingHorizontal: 12, paddingVertical: 8, fontSize: 13, color: colors.textPrimary },
14550
14645
  screenHint: { fontSize: 11, color: colors.textMuted, marginTop: 4 },
14551
14646
  errorBanner: { backgroundColor: "#7f1d1d", borderWidth: 1, borderColor: "#991b1b", borderRadius: 8, padding: 12, marginTop: 16 },
14552
- errorText: { fontSize: 13, color: "#fca5a5", lineHeight: 18 }
14647
+ errorText: { fontSize: 13, color: "#fca5a5", lineHeight: 18 },
14648
+ retestBanner: { flexDirection: "row", alignItems: "center", gap: 10, backgroundColor: "#422006", borderWidth: 1, borderColor: "#854d0e", borderRadius: 10, paddingVertical: 12, paddingHorizontal: 14, marginBottom: 20 },
14649
+ retestIcon: { fontSize: 16 },
14650
+ retestTitle: { fontSize: 15, fontWeight: "600", color: "#fbbf24", lineHeight: 20 },
14651
+ retestSubtitle: { fontSize: 12, color: "#d97706", lineHeight: 16 },
14652
+ retestSubmitButton: { backgroundColor: "#b45309" }
14553
14653
  });
14554
14654
 
14555
14655
  // src/widget/screens/ReportSuccessScreen.tsx
@@ -15100,7 +15200,7 @@ function BugBearButton({
15100
15200
  style: styles13.modalOverlay
15101
15201
  },
15102
15202
  /* @__PURE__ */ React14.createElement(View13, { style: styles13.modalContainer }, /* @__PURE__ */ React14.createElement(View13, { style: styles13.header }, /* @__PURE__ */ React14.createElement(View13, { style: styles13.headerLeft }, canGoBack ? /* @__PURE__ */ React14.createElement(View13, { style: styles13.headerNavRow }, /* @__PURE__ */ React14.createElement(TouchableOpacity12, { onPress: () => nav.pop(), style: styles13.backButton }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.backText }, "\u2190 Back")), /* @__PURE__ */ React14.createElement(TouchableOpacity12, { onPress: () => nav.reset(), style: styles13.homeButton }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.homeText }, "\u{1F3E0}"))) : /* @__PURE__ */ React14.createElement(View13, { style: styles13.headerTitleRow }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.headerTitle }, "BugBear"), testerInfo && /* @__PURE__ */ React14.createElement(TouchableOpacity12, { onPress: () => push({ name: "PROFILE" }) }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.headerName }, testerInfo.name, " \u270E")))), getHeaderTitle() ? /* @__PURE__ */ React14.createElement(Text13, { style: styles13.headerScreenTitle, numberOfLines: 1 }, getHeaderTitle()) : null, /* @__PURE__ */ React14.createElement(TouchableOpacity12, { onPress: handleClose, style: styles13.closeButton }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.closeText }, "\u2715"))), /* @__PURE__ */ React14.createElement(
15103
- ScrollView2,
15203
+ ScrollView3,
15104
15204
  {
15105
15205
  style: styles13.content,
15106
15206
  contentContainerStyle: styles13.contentContainer,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bbearai/react-native",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "BugBear React Native components for mobile apps",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -49,7 +49,7 @@
49
49
  }
50
50
  },
51
51
  "devDependencies": {
52
- "@bbearai/core": "^0.3.0",
52
+ "@bbearai/core": "^0.4.0",
53
53
  "@eslint/js": "^9.39.2",
54
54
  "@types/react": "^18.2.0",
55
55
  "eslint": "^9.39.2",