@bbearai/react-native 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +222 -24
  2. package/dist/index.mjs +227 -29
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -12071,10 +12071,11 @@ var BugBearClient = class {
12071
12071
  const pageSize = Math.min(options?.pageSize ?? 100, 100);
12072
12072
  const from = (options?.page ?? 0) * pageSize;
12073
12073
  const to = from + pageSize - 1;
12074
- const { data, error } = await this.supabase.from("test_assignments").select(`
12074
+ const selectFields = `
12075
12075
  id,
12076
12076
  status,
12077
12077
  started_at,
12078
+ completed_at,
12078
12079
  skip_reason,
12079
12080
  is_verification,
12080
12081
  original_report_id,
@@ -12109,20 +12110,24 @@ var BugBearClient = class {
12109
12110
  color,
12110
12111
  description,
12111
12112
  login_hint
12112
- )
12113
+ ),
12114
+ platforms
12113
12115
  )
12114
- `).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true }).range(from, to);
12115
- if (error) {
12116
- console.error("BugBear: Failed to fetch assignments", formatPgError(error));
12116
+ `;
12117
+ const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1e3).toISOString();
12118
+ const [pendingResult, completedResult] = await Promise.all([
12119
+ this.supabase.from("test_assignments").select(selectFields).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true }).range(from, to),
12120
+ this.supabase.from("test_assignments").select(selectFields).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["passed", "failed", "skipped", "blocked"]).gte("completed_at", twentyFourHoursAgo).order("completed_at", { ascending: false }).limit(50)
12121
+ ]);
12122
+ if (pendingResult.error) {
12123
+ console.error("BugBear: Failed to fetch assignments", formatPgError(pendingResult.error));
12117
12124
  return [];
12118
12125
  }
12119
- const mapped = (data || []).filter((item) => {
12120
- if (!item.test_case) {
12121
- console.warn("BugBear: Assignment returned without test_case", { id: item.id });
12122
- return false;
12123
- }
12124
- return true;
12125
- }).map((item) => ({
12126
+ const allData = [
12127
+ ...pendingResult.data || [],
12128
+ ...completedResult.data || []
12129
+ ];
12130
+ const mapItem = (item) => ({
12126
12131
  id: item.id,
12127
12132
  status: item.status,
12128
12133
  startedAt: item.started_at,
@@ -12160,12 +12165,24 @@ var BugBearClient = class {
12160
12165
  color: item.test_case.role.color,
12161
12166
  description: item.test_case.role.description,
12162
12167
  loginHint: item.test_case.role.login_hint
12163
- } : void 0
12168
+ } : void 0,
12169
+ platforms: item.test_case.platforms || void 0
12164
12170
  }
12165
- }));
12171
+ });
12172
+ const mapped = allData.filter((item) => {
12173
+ if (!item.test_case) {
12174
+ console.warn("BugBear: Assignment returned without test_case", { id: item.id });
12175
+ return false;
12176
+ }
12177
+ return true;
12178
+ }).map(mapItem);
12166
12179
  mapped.sort((a, b) => {
12167
12180
  if (a.isVerification && !b.isVerification) return -1;
12168
12181
  if (!a.isVerification && b.isVerification) return 1;
12182
+ const aActive = a.status === "pending" || a.status === "in_progress";
12183
+ const bActive = b.status === "pending" || b.status === "in_progress";
12184
+ if (aActive && !bActive) return -1;
12185
+ if (!aActive && bActive) return 1;
12169
12186
  return 0;
12170
12187
  });
12171
12188
  return mapped;
@@ -12330,6 +12347,36 @@ var BugBearClient = class {
12330
12347
  async failAssignment(assignmentId) {
12331
12348
  return this.updateAssignmentStatus(assignmentId, "failed");
12332
12349
  }
12350
+ /**
12351
+ * Reopen a completed assignment — sets it back to in_progress with a fresh timer.
12352
+ * Clears completed_at and duration_seconds so it can be re-evaluated.
12353
+ */
12354
+ async reopenAssignment(assignmentId) {
12355
+ try {
12356
+ const { data: current, error: fetchError } = await this.supabase.from("test_assignments").select("status").eq("id", assignmentId).single();
12357
+ if (fetchError || !current) {
12358
+ return { success: false, error: "Assignment not found" };
12359
+ }
12360
+ if (current.status === "pending" || current.status === "in_progress") {
12361
+ return { success: true };
12362
+ }
12363
+ const { error } = await this.supabase.from("test_assignments").update({
12364
+ status: "in_progress",
12365
+ started_at: (/* @__PURE__ */ new Date()).toISOString(),
12366
+ completed_at: null,
12367
+ duration_seconds: null,
12368
+ skip_reason: null
12369
+ }).eq("id", assignmentId).eq("status", current.status);
12370
+ if (error) {
12371
+ console.error("BugBear: Failed to reopen assignment", error);
12372
+ return { success: false, error: error.message };
12373
+ }
12374
+ return { success: true };
12375
+ } catch (err) {
12376
+ const message = err instanceof Error ? err.message : "Unknown error";
12377
+ return { success: false, error: message };
12378
+ }
12379
+ }
12333
12380
  /**
12334
12381
  * Skip a test assignment with a required reason
12335
12382
  * Marks the assignment as 'skipped' and records why it was skipped
@@ -14470,6 +14517,39 @@ function TestDetailScreen({ testId, nav }) {
14470
14517
  setIsSubmitting(false);
14471
14518
  }
14472
14519
  }, [client, displayedAssignment, refreshAssignments, nav, isSubmitting]);
14520
+ const handleReopen = (0, import_react5.useCallback)(async () => {
14521
+ if (!client || !displayedAssignment || isSubmitting) return;
14522
+ import_react_native5.Keyboard.dismiss();
14523
+ setIsSubmitting(true);
14524
+ try {
14525
+ await client.reopenAssignment(displayedAssignment.id);
14526
+ await refreshAssignments();
14527
+ } finally {
14528
+ setIsSubmitting(false);
14529
+ }
14530
+ }, [client, displayedAssignment, refreshAssignments, isSubmitting]);
14531
+ const handleChangeResult = (0, import_react5.useCallback)(async (newStatus) => {
14532
+ if (!client || !displayedAssignment || isSubmitting) return;
14533
+ import_react_native5.Keyboard.dismiss();
14534
+ setIsSubmitting(true);
14535
+ try {
14536
+ await client.reopenAssignment(displayedAssignment.id);
14537
+ await client.updateAssignmentStatus(displayedAssignment.id, newStatus);
14538
+ await refreshAssignments();
14539
+ if (newStatus === "failed") {
14540
+ nav.replace({
14541
+ name: "REPORT",
14542
+ prefill: {
14543
+ type: "test_fail",
14544
+ assignmentId: displayedAssignment.id,
14545
+ testCaseId: displayedAssignment.testCase.id
14546
+ }
14547
+ });
14548
+ }
14549
+ } finally {
14550
+ setIsSubmitting(false);
14551
+ }
14552
+ }, [client, displayedAssignment, refreshAssignments, nav, isSubmitting]);
14473
14553
  const handleSkip = (0, import_react5.useCallback)(async () => {
14474
14554
  if (!client || !displayedAssignment || !selectedSkipReason) return;
14475
14555
  import_react_native5.Keyboard.dismiss();
@@ -14550,7 +14630,39 @@ function TestDetailScreen({ testId, nav }) {
14550
14630
  }
14551
14631
  },
14552
14632
  /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.navigateText }, "\u{1F9ED} Go to test location")
14553
- ), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { onPress: () => setShowDetails(!showDetails), style: styles2.detailsToggle }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.detailsToggleText }, showDetails ? "\u25BC" : "\u25B6", " Details")), showDetails && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles2.detailsSection }, testCase.key && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.detailMeta }, testCase.key, " \xB7 ", testCase.priority, " \xB7 ", info.name), testCase.description && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.detailDesc }, testCase.description), testCase.group && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles2.folderProgress }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.folderName }, "\u{1F4C1} ", testCase.group.name))), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles2.actionButtons }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { style: [styles2.actionBtn, styles2.failBtn, isSubmitting && { opacity: 0.5 }], onPress: handleFail, disabled: isSubmitting }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.failBtnText }, isSubmitting ? "Failing..." : "Fail")), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { style: [styles2.actionBtn, styles2.skipBtn, isSubmitting && { opacity: 0.5 }], onPress: () => setShowSkipModal(true), disabled: isSubmitting }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.skipBtnText }, "Skip")), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { style: [styles2.actionBtn, styles2.passBtn, isSubmitting && { opacity: 0.5 }], onPress: handlePass, disabled: isSubmitting }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.passBtnText }, isSubmitting ? "Passing..." : "Pass"))), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Modal, { visible: showSkipModal, transparent: true, animationType: "fade" }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles2.modalOverlay }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles2.modalContent }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.modalTitle }, "Skip this test?"), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.modalSubtitle }, "Select a reason:"), [
14633
+ ), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { onPress: () => setShowDetails(!showDetails), style: styles2.detailsToggle }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.detailsToggleText }, showDetails ? "\u25BC" : "\u25B6", " Details")), showDetails && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles2.detailsSection }, testCase.key && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.detailMeta }, testCase.key, " \xB7 ", testCase.priority, " \xB7 ", info.name), testCase.description && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.detailDesc }, testCase.description), testCase.group && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles2.folderProgress }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.folderName }, "\u{1F4C1} ", testCase.group.name))), displayedAssignment.status === "passed" || displayedAssignment.status === "failed" || displayedAssignment.status === "skipped" || displayedAssignment.status === "blocked" ? /* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, null, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: [
14634
+ styles2.completedBanner,
14635
+ displayedAssignment.status === "passed" && styles2.completedBannerPass,
14636
+ displayedAssignment.status === "failed" && styles2.completedBannerFail
14637
+ ] }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.completedIcon }, displayedAssignment.status === "passed" ? "\u2705" : displayedAssignment.status === "failed" ? "\u274C" : displayedAssignment.status === "skipped" ? "\u23ED" : "\u{1F6AB}"), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: [
14638
+ styles2.completedLabel,
14639
+ displayedAssignment.status === "passed" && { color: colors.green },
14640
+ displayedAssignment.status === "failed" && { color: "#fca5a5" }
14641
+ ] }, "Marked as ", displayedAssignment.status.charAt(0).toUpperCase() + displayedAssignment.status.slice(1))), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: [styles2.actionButtons, { marginTop: 4 }] }, /* @__PURE__ */ import_react5.default.createElement(
14642
+ import_react_native5.TouchableOpacity,
14643
+ {
14644
+ style: [styles2.actionBtn, styles2.reopenBtn, isSubmitting && { opacity: 0.5 }],
14645
+ onPress: handleReopen,
14646
+ disabled: isSubmitting
14647
+ },
14648
+ /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.reopenBtnText }, isSubmitting ? "Reopening..." : "\u{1F504} Reopen Test")
14649
+ ), displayedAssignment.status === "passed" && /* @__PURE__ */ import_react5.default.createElement(
14650
+ import_react_native5.TouchableOpacity,
14651
+ {
14652
+ style: [styles2.actionBtn, styles2.failBtn, isSubmitting && { opacity: 0.5 }],
14653
+ onPress: () => handleChangeResult("failed"),
14654
+ disabled: isSubmitting
14655
+ },
14656
+ /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.failBtnText }, "Change to Fail")
14657
+ ), displayedAssignment.status === "failed" && /* @__PURE__ */ import_react5.default.createElement(
14658
+ import_react_native5.TouchableOpacity,
14659
+ {
14660
+ style: [styles2.actionBtn, styles2.passBtn, isSubmitting && { opacity: 0.5 }],
14661
+ onPress: () => handleChangeResult("passed"),
14662
+ disabled: isSubmitting
14663
+ },
14664
+ /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.passBtnText }, "Change to Pass")
14665
+ ))) : /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles2.actionButtons }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { style: [styles2.actionBtn, styles2.failBtn, isSubmitting && { opacity: 0.5 }], onPress: handleFail, disabled: isSubmitting }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.failBtnText }, isSubmitting ? "Failing..." : "Fail")), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { style: [styles2.actionBtn, styles2.skipBtn, isSubmitting && { opacity: 0.5 }], onPress: () => setShowSkipModal(true), disabled: isSubmitting }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.skipBtnText }, "Skip")), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { style: [styles2.actionBtn, styles2.passBtn, isSubmitting && { opacity: 0.5 }], onPress: handlePass, disabled: isSubmitting }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.passBtnText }, isSubmitting ? "Passing..." : "Pass"))), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Modal, { visible: showSkipModal, transparent: true, animationType: "fade" }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles2.modalOverlay }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles2.modalContent }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.modalTitle }, "Skip this test?"), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.modalSubtitle }, "Select a reason:"), [
14554
14666
  { reason: "blocked", label: "\u{1F6AB} Blocked by a bug" },
14555
14667
  { reason: "not_ready", label: "\u{1F6A7} Feature not ready" },
14556
14668
  { reason: "dependency", label: "\u{1F517} Needs another test first" },
@@ -14655,6 +14767,14 @@ var styles2 = import_react_native5.StyleSheet.create({
14655
14767
  detailDesc: { fontSize: 13, color: colors.textSecondary, lineHeight: 18 },
14656
14768
  folderProgress: { marginTop: 8 },
14657
14769
  folderName: { fontSize: 12, color: colors.textMuted },
14770
+ // Completed state
14771
+ completedBanner: { flexDirection: "row", alignItems: "center", justifyContent: "center", gap: 6, paddingVertical: 8, paddingHorizontal: 12, borderRadius: 8, marginTop: 8, marginBottom: 8, backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border },
14772
+ completedBannerPass: { backgroundColor: colors.greenDark, borderColor: colors.green },
14773
+ completedBannerFail: { backgroundColor: colors.redDark, borderColor: colors.red },
14774
+ completedIcon: { fontSize: 14 },
14775
+ completedLabel: { fontSize: 13, fontWeight: "600", color: colors.textSecondary },
14776
+ reopenBtn: { backgroundColor: colors.card, borderWidth: 1, borderColor: colors.blue },
14777
+ reopenBtnText: { fontSize: 14, fontWeight: "600", color: colors.blue },
14658
14778
  // Action buttons
14659
14779
  actionButtons: { flexDirection: "row", gap: 10, marginTop: 8 },
14660
14780
  actionBtn: { flex: 1, paddingVertical: 14, borderRadius: 12, alignItems: "center" },
@@ -14685,10 +14805,11 @@ var styles2 = import_react_native5.StyleSheet.create({
14685
14805
  var import_react6 = __toESM(require("react"));
14686
14806
  var import_react_native6 = require("react-native");
14687
14807
  function TestListScreen({ nav }) {
14688
- const { assignments, currentAssignment, refreshAssignments, isLoading } = useBugBear();
14808
+ const { assignments, currentAssignment, refreshAssignments, dashboardUrl, isLoading } = useBugBear();
14689
14809
  const [filter, setFilter] = (0, import_react6.useState)("all");
14690
14810
  const [roleFilter, setRoleFilter] = (0, import_react6.useState)(null);
14691
14811
  const [trackFilter, setTrackFilter] = (0, import_react6.useState)(null);
14812
+ const [platformFilter, setPlatformFilter] = (0, import_react6.useState)(import_react_native6.Platform.OS === "android" ? "android" : "ios");
14692
14813
  const [searchQuery, setSearchQuery] = (0, import_react6.useState)("");
14693
14814
  const [sortMode, setSortMode] = (0, import_react6.useState)("priority");
14694
14815
  const [collapsedFolders, setCollapsedFolders] = (0, import_react6.useState)(/* @__PURE__ */ new Set());
@@ -14709,6 +14830,13 @@ function TestListScreen({ nav }) {
14709
14830
  }
14710
14831
  return Array.from(trackMap.values());
14711
14832
  }, [assignments]);
14833
+ const availablePlatforms = (0, import_react6.useMemo)(() => {
14834
+ const set = /* @__PURE__ */ new Set();
14835
+ for (const a of assignments) {
14836
+ if (a.testCase.platforms) a.testCase.platforms.forEach((p) => set.add(p));
14837
+ }
14838
+ return Array.from(set).sort();
14839
+ }, [assignments]);
14712
14840
  const selectedRole = availableRoles.find((r) => r.id === roleFilter);
14713
14841
  const groupedAssignments = (0, import_react6.useMemo)(() => {
14714
14842
  const groups = /* @__PURE__ */ new Map();
@@ -14755,6 +14883,7 @@ function TestListScreen({ nav }) {
14755
14883
  });
14756
14884
  }, []);
14757
14885
  const filterAssignment = (0, import_react6.useCallback)((a) => {
14886
+ if (platformFilter && a.testCase.platforms && !a.testCase.platforms.includes(platformFilter)) return false;
14758
14887
  if (roleFilter && a.testCase.role?.id !== roleFilter) return false;
14759
14888
  if (trackFilter && a.testCase.track?.id !== trackFilter) return false;
14760
14889
  if (searchQuery) {
@@ -14767,7 +14896,7 @@ function TestListScreen({ nav }) {
14767
14896
  if (filter === "done") return a.status === "passed";
14768
14897
  if (filter === "reopened") return a.status === "failed";
14769
14898
  return true;
14770
- }, [roleFilter, trackFilter, searchQuery, filter]);
14899
+ }, [platformFilter, roleFilter, trackFilter, searchQuery, filter]);
14771
14900
  if (isLoading) return /* @__PURE__ */ import_react6.default.createElement(TestListScreenSkeleton, null);
14772
14901
  return /* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, null, /* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, { style: styles3.filterBar }, [
14773
14902
  { key: "all", label: "All", count: assignments.length },
@@ -14805,7 +14934,30 @@ function TestListScreen({ nav }) {
14805
14934
  placeholderTextColor: colors.textMuted,
14806
14935
  style: styles3.searchInput
14807
14936
  }
14808
- )), /* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, { style: styles3.trackSortRow }, availableTracks.length >= 2 && /* @__PURE__ */ import_react6.default.createElement(import_react_native6.ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: { flex: 1 } }, /* @__PURE__ */ import_react6.default.createElement(
14937
+ )), availablePlatforms.length >= 2 && /* @__PURE__ */ import_react6.default.createElement(import_react_native6.ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: styles3.platformBar }, /* @__PURE__ */ import_react6.default.createElement(
14938
+ import_react_native6.TouchableOpacity,
14939
+ {
14940
+ style: [styles3.platformBtn, !platformFilter && styles3.platformBtnActive],
14941
+ onPress: () => setPlatformFilter(null)
14942
+ },
14943
+ /* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: [styles3.platformBtnText, !platformFilter && styles3.platformBtnTextActive] }, "All Platforms")
14944
+ ), availablePlatforms.map((p) => {
14945
+ const isActive = platformFilter === p;
14946
+ const label = p === "ios" ? "iOS" : p === "android" ? "Android" : p === "web" ? "Web" : p;
14947
+ const icon = p === "ios" ? "\u{1F4F1}" : p === "android" ? "\u{1F916}" : p === "web" ? "\u{1F310}" : "\u{1F4CB}";
14948
+ return /* @__PURE__ */ import_react6.default.createElement(
14949
+ import_react_native6.TouchableOpacity,
14950
+ {
14951
+ key: p,
14952
+ style: [
14953
+ styles3.platformBtn,
14954
+ isActive && { backgroundColor: colors.blue + "20", borderColor: colors.blue + "60", borderWidth: 1 }
14955
+ ],
14956
+ onPress: () => setPlatformFilter(isActive ? null : p)
14957
+ },
14958
+ /* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: [styles3.platformBtnText, isActive && { color: colors.blue, fontWeight: "600" }] }, icon, " ", label)
14959
+ );
14960
+ })), /* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, { style: styles3.trackSortRow }, availableTracks.length >= 2 && /* @__PURE__ */ import_react6.default.createElement(import_react_native6.ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: { flex: 1 } }, /* @__PURE__ */ import_react6.default.createElement(
14809
14961
  import_react_native6.TouchableOpacity,
14810
14962
  {
14811
14963
  style: [styles3.trackBtn, !trackFilter && styles3.trackBtnActive],
@@ -14866,7 +15018,15 @@ function TestListScreen({ nav }) {
14866
15018
  ] }, badge.label))
14867
15019
  );
14868
15020
  }));
14869
- }), /* @__PURE__ */ import_react6.default.createElement(import_react_native6.TouchableOpacity, { style: styles3.refreshBtn, onPress: refreshAssignments }, /* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: styles3.refreshText }, "\u21BB", " Refresh")));
15021
+ }), dashboardUrl && /* @__PURE__ */ import_react6.default.createElement(
15022
+ import_react_native6.TouchableOpacity,
15023
+ {
15024
+ style: styles3.dashboardLink,
15025
+ onPress: () => import_react_native6.Linking.openURL(`${dashboardUrl}/test-cases`),
15026
+ activeOpacity: 0.7
15027
+ },
15028
+ /* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: styles3.dashboardLinkText }, "\u{1F310}", " Manage on Dashboard ", "\u2192")
15029
+ ), /* @__PURE__ */ import_react6.default.createElement(import_react_native6.TouchableOpacity, { style: styles3.refreshBtn, onPress: refreshAssignments }, /* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: styles3.refreshText }, "\u21BB", " Refresh")));
14870
15030
  }
14871
15031
  var styles3 = import_react_native6.StyleSheet.create({
14872
15032
  filterBar: { flexDirection: "row", gap: 8, marginBottom: 8 },
@@ -14905,6 +15065,11 @@ var styles3 = import_react_native6.StyleSheet.create({
14905
15065
  statusPillText: { fontSize: 10, fontWeight: "600" },
14906
15066
  searchContainer: { marginBottom: 8 },
14907
15067
  searchInput: { backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border, borderRadius: 8, paddingHorizontal: 12, paddingVertical: 8, fontSize: 13, color: colors.textPrimary },
15068
+ platformBar: { flexDirection: "row", marginBottom: 8 },
15069
+ platformBtn: { flexDirection: "row", alignItems: "center", gap: 4, paddingHorizontal: 10, paddingVertical: 4, borderRadius: 6, marginRight: 6, borderWidth: 1, borderColor: "transparent" },
15070
+ platformBtnActive: { backgroundColor: colors.card, borderColor: colors.border },
15071
+ platformBtnText: { fontSize: 11, color: colors.textMuted },
15072
+ platformBtnTextActive: { color: colors.textPrimary, fontWeight: "600" },
14908
15073
  trackSortRow: { flexDirection: "row", alignItems: "center", marginBottom: 10, gap: 8 },
14909
15074
  trackBtn: { flexDirection: "row", alignItems: "center", gap: 4, paddingHorizontal: 10, paddingVertical: 4, borderRadius: 6, marginRight: 6, borderWidth: 1, borderColor: "transparent" },
14910
15075
  trackBtnActive: { backgroundColor: colors.card, borderColor: colors.border },
@@ -14915,7 +15080,9 @@ var styles3 = import_react_native6.StyleSheet.create({
14915
15080
  sortBtnActive: { backgroundColor: colors.card, borderColor: colors.border },
14916
15081
  sortBtnText: { fontSize: 11, color: colors.textMuted },
14917
15082
  sortBtnTextActive: { color: colors.textPrimary, fontWeight: "600" },
14918
- refreshBtn: { alignItems: "center", paddingVertical: 12 },
15083
+ dashboardLink: { alignItems: "center", paddingTop: 12 },
15084
+ dashboardLinkText: { fontSize: 13, fontWeight: "500", color: colors.blue },
15085
+ refreshBtn: { alignItems: "center", paddingVertical: 8 },
14919
15086
  refreshText: { fontSize: 13, color: colors.blue }
14920
15087
  });
14921
15088
 
@@ -15639,7 +15806,7 @@ var styles9 = import_react_native12.StyleSheet.create({
15639
15806
  var import_react14 = __toESM(require("react"));
15640
15807
  var import_react_native13 = require("react-native");
15641
15808
  function MessageListScreen({ nav }) {
15642
- const { threads, unreadCount, refreshThreads, isLoading } = useBugBear();
15809
+ const { threads, unreadCount, refreshThreads, dashboardUrl, isLoading } = useBugBear();
15643
15810
  if (isLoading) return /* @__PURE__ */ import_react14.default.createElement(MessageListScreenSkeleton, null);
15644
15811
  return /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, null, /* @__PURE__ */ import_react14.default.createElement(
15645
15812
  import_react_native13.TouchableOpacity,
@@ -15657,7 +15824,15 @@ function MessageListScreen({ nav }) {
15657
15824
  },
15658
15825
  /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles10.threadLeft }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles10.threadIcon }, getThreadTypeIcon(thread.threadType)), /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles10.threadInfo }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles10.threadTitleRow }, thread.isPinned && /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles10.pinIcon }, "\u{1F4CC}"), /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles10.threadSubject, numberOfLines: 1 }, thread.subject || "No subject")), thread.lastMessage && /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles10.threadPreview, numberOfLines: 1 }, thread.lastMessage.senderName, ": ", thread.lastMessage.content))),
15659
15826
  /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles10.threadRight }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles10.threadTime }, formatRelativeTime(thread.lastMessageAt)), thread.unreadCount > 0 && /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles10.unreadBadge }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles10.unreadText }, thread.unreadCount)), thread.priority !== "normal" && /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: [styles10.priorityDot, { backgroundColor: getPriorityColor(thread.priority) }] }))
15660
- ))), /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles10.footer }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles10.footerText }, threads.length, " thread", threads.length !== 1 ? "s" : "", " \xB7 ", unreadCount, " unread"), /* @__PURE__ */ import_react14.default.createElement(import_react_native13.TouchableOpacity, { onPress: refreshThreads }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles10.refreshText }, "\u21BB Refresh"))));
15827
+ ))), dashboardUrl && /* @__PURE__ */ import_react14.default.createElement(
15828
+ import_react_native13.TouchableOpacity,
15829
+ {
15830
+ style: styles10.dashboardLink,
15831
+ onPress: () => import_react_native13.Linking.openURL(`${dashboardUrl}/discussions`),
15832
+ activeOpacity: 0.7
15833
+ },
15834
+ /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles10.dashboardLinkText }, "\u{1F310}", " View on Dashboard ", "\u2192")
15835
+ ), /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles10.footer }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles10.footerText }, threads.length, " thread", threads.length !== 1 ? "s" : "", " \xB7 ", unreadCount, " unread"), /* @__PURE__ */ import_react14.default.createElement(import_react_native13.TouchableOpacity, { onPress: refreshThreads }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles10.refreshText }, "\u21BB Refresh"))));
15661
15836
  }
15662
15837
  var styles10 = import_react_native13.StyleSheet.create({
15663
15838
  newMsgButton: { backgroundColor: colors.blue, paddingVertical: 12, borderRadius: 12, alignItems: "center", marginBottom: 16 },
@@ -15676,7 +15851,9 @@ var styles10 = import_react_native13.StyleSheet.create({
15676
15851
  unreadBadge: { backgroundColor: colors.blue, borderRadius: 10, minWidth: 20, height: 20, justifyContent: "center", alignItems: "center", paddingHorizontal: 6 },
15677
15852
  unreadText: { fontSize: 11, fontWeight: "bold", color: "#fff" },
15678
15853
  priorityDot: { width: 8, height: 8, borderRadius: 4 },
15679
- footer: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", paddingTop: 12, paddingHorizontal: 4 },
15854
+ dashboardLink: { alignItems: "center", paddingTop: 12 },
15855
+ dashboardLinkText: { fontSize: 13, fontWeight: "500", color: colors.blue },
15856
+ footer: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", paddingTop: 8, paddingHorizontal: 4 },
15680
15857
  footerText: { fontSize: 12, color: colors.textMuted },
15681
15858
  refreshText: { fontSize: 13, color: colors.blue }
15682
15859
  });
@@ -16179,9 +16356,18 @@ var SEVERITY_CONFIG = {
16179
16356
  low: { label: "Low", color: "#71717a", bg: "#27272a" }
16180
16357
  };
16181
16358
  function IssueDetailScreen({ nav, issue }) {
16359
+ const { dashboardUrl } = useBugBear();
16182
16360
  const statusConfig = STATUS_LABELS[issue.status] || { label: issue.status, bg: "#27272a", color: "#a1a1aa" };
16183
16361
  const severityConfig = issue.severity ? SEVERITY_CONFIG[issue.severity] : null;
16184
- return /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, null, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles15.badgeRow }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: [styles15.badge, { backgroundColor: statusConfig.bg }] }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: [styles15.badgeText, { color: statusConfig.color }] }, statusConfig.label)), severityConfig && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: [styles15.badge, { backgroundColor: severityConfig.bg }] }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: [styles15.badgeText, { color: severityConfig.color }] }, severityConfig.label))), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.title }, issue.title), issue.route && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.route }, issue.route), issue.description && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles15.descriptionCard }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.descriptionText }, issue.description)), issue.verifiedByName && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles15.verifiedCard }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles15.verifiedHeader }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.verifiedIcon }, "\u2705"), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.verifiedTitle }, "Retesting Proof")), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.verifiedBody }, "Verified by ", issue.verifiedByName, issue.verifiedAt && ` on ${new Date(issue.verifiedAt).toLocaleDateString(void 0, { month: "short", day: "numeric", year: "numeric" })}`)), issue.originalBugTitle && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles15.originalBugCard }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles15.originalBugHeader }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.originalBugIcon }, "\u{1F504}"), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.originalBugTitle }, "Original Bug")), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.originalBugBody }, "Retest of: ", issue.originalBugTitle)), issue.screenshotUrls && issue.screenshotUrls.length > 0 && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles15.screenshotSection }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.screenshotLabel }, "Screenshots (", issue.screenshotUrls.length, ")"), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles15.screenshotRow }, issue.screenshotUrls.map((url, i) => /* @__PURE__ */ import_react19.default.createElement(import_react_native18.TouchableOpacity, { key: i, onPress: () => import_react_native18.Linking.openURL(url), activeOpacity: 0.7 }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Image, { source: { uri: url }, style: styles15.screenshotThumb }))))), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles15.metaSection }, issue.reporterName && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.metaText }, "Reported by ", issue.reporterName), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.metaTextSmall }, "Created ", formatRelativeTime(issue.createdAt), " ", "\xB7", " Updated ", formatRelativeTime(issue.updatedAt))));
16362
+ return /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, null, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles15.badgeRow }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: [styles15.badge, { backgroundColor: statusConfig.bg }] }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: [styles15.badgeText, { color: statusConfig.color }] }, statusConfig.label)), severityConfig && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: [styles15.badge, { backgroundColor: severityConfig.bg }] }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: [styles15.badgeText, { color: severityConfig.color }] }, severityConfig.label))), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.title }, issue.title), issue.route && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.route }, issue.route), issue.description && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles15.descriptionCard }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.descriptionText }, issue.description)), issue.verifiedByName && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles15.verifiedCard }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles15.verifiedHeader }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.verifiedIcon }, "\u2705"), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.verifiedTitle }, "Retesting Proof")), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.verifiedBody }, "Verified by ", issue.verifiedByName, issue.verifiedAt && ` on ${new Date(issue.verifiedAt).toLocaleDateString(void 0, { month: "short", day: "numeric", year: "numeric" })}`)), issue.originalBugTitle && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles15.originalBugCard }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles15.originalBugHeader }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.originalBugIcon }, "\u{1F504}"), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.originalBugTitle }, "Original Bug")), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.originalBugBody }, "Retest of: ", issue.originalBugTitle)), issue.screenshotUrls && issue.screenshotUrls.length > 0 && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles15.screenshotSection }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.screenshotLabel }, "Screenshots (", issue.screenshotUrls.length, ")"), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles15.screenshotRow }, issue.screenshotUrls.map((url, i) => /* @__PURE__ */ import_react19.default.createElement(import_react_native18.TouchableOpacity, { key: i, onPress: () => import_react_native18.Linking.openURL(url), activeOpacity: 0.7 }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Image, { source: { uri: url }, style: styles15.screenshotThumb }))))), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles15.metaSection }, issue.reporterName && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.metaText }, "Reported by ", issue.reporterName), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.metaTextSmall }, "Created ", formatRelativeTime(issue.createdAt), " ", "\xB7", " Updated ", formatRelativeTime(issue.updatedAt))), dashboardUrl && /* @__PURE__ */ import_react19.default.createElement(
16363
+ import_react_native18.TouchableOpacity,
16364
+ {
16365
+ style: styles15.dashboardLink,
16366
+ onPress: () => import_react_native18.Linking.openURL(`${dashboardUrl}/reports`),
16367
+ activeOpacity: 0.7
16368
+ },
16369
+ /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.dashboardLinkText }, "\u{1F310}", " View on Dashboard ", "\u2192")
16370
+ ));
16185
16371
  }
16186
16372
  var styles15 = import_react_native18.StyleSheet.create({
16187
16373
  badgeRow: {
@@ -16310,6 +16496,18 @@ var styles15 = import_react_native18.StyleSheet.create({
16310
16496
  metaTextSmall: {
16311
16497
  fontSize: 11,
16312
16498
  color: colors.textDim
16499
+ },
16500
+ dashboardLink: {
16501
+ alignItems: "center",
16502
+ paddingVertical: 10,
16503
+ marginTop: 12,
16504
+ borderTopWidth: 1,
16505
+ borderTopColor: colors.border
16506
+ },
16507
+ dashboardLinkText: {
16508
+ fontSize: 13,
16509
+ fontWeight: "500",
16510
+ color: colors.blue
16313
16511
  }
16314
16512
  });
16315
16513
 
package/dist/index.mjs CHANGED
@@ -12038,10 +12038,11 @@ var BugBearClient = class {
12038
12038
  const pageSize = Math.min(options?.pageSize ?? 100, 100);
12039
12039
  const from = (options?.page ?? 0) * pageSize;
12040
12040
  const to = from + pageSize - 1;
12041
- const { data, error } = await this.supabase.from("test_assignments").select(`
12041
+ const selectFields = `
12042
12042
  id,
12043
12043
  status,
12044
12044
  started_at,
12045
+ completed_at,
12045
12046
  skip_reason,
12046
12047
  is_verification,
12047
12048
  original_report_id,
@@ -12076,20 +12077,24 @@ var BugBearClient = class {
12076
12077
  color,
12077
12078
  description,
12078
12079
  login_hint
12079
- )
12080
+ ),
12081
+ platforms
12080
12082
  )
12081
- `).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true }).range(from, to);
12082
- if (error) {
12083
- console.error("BugBear: Failed to fetch assignments", formatPgError(error));
12083
+ `;
12084
+ const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1e3).toISOString();
12085
+ const [pendingResult, completedResult] = await Promise.all([
12086
+ this.supabase.from("test_assignments").select(selectFields).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true }).range(from, to),
12087
+ this.supabase.from("test_assignments").select(selectFields).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["passed", "failed", "skipped", "blocked"]).gte("completed_at", twentyFourHoursAgo).order("completed_at", { ascending: false }).limit(50)
12088
+ ]);
12089
+ if (pendingResult.error) {
12090
+ console.error("BugBear: Failed to fetch assignments", formatPgError(pendingResult.error));
12084
12091
  return [];
12085
12092
  }
12086
- const mapped = (data || []).filter((item) => {
12087
- if (!item.test_case) {
12088
- console.warn("BugBear: Assignment returned without test_case", { id: item.id });
12089
- return false;
12090
- }
12091
- return true;
12092
- }).map((item) => ({
12093
+ const allData = [
12094
+ ...pendingResult.data || [],
12095
+ ...completedResult.data || []
12096
+ ];
12097
+ const mapItem = (item) => ({
12093
12098
  id: item.id,
12094
12099
  status: item.status,
12095
12100
  startedAt: item.started_at,
@@ -12127,12 +12132,24 @@ var BugBearClient = class {
12127
12132
  color: item.test_case.role.color,
12128
12133
  description: item.test_case.role.description,
12129
12134
  loginHint: item.test_case.role.login_hint
12130
- } : void 0
12135
+ } : void 0,
12136
+ platforms: item.test_case.platforms || void 0
12131
12137
  }
12132
- }));
12138
+ });
12139
+ const mapped = allData.filter((item) => {
12140
+ if (!item.test_case) {
12141
+ console.warn("BugBear: Assignment returned without test_case", { id: item.id });
12142
+ return false;
12143
+ }
12144
+ return true;
12145
+ }).map(mapItem);
12133
12146
  mapped.sort((a, b) => {
12134
12147
  if (a.isVerification && !b.isVerification) return -1;
12135
12148
  if (!a.isVerification && b.isVerification) return 1;
12149
+ const aActive = a.status === "pending" || a.status === "in_progress";
12150
+ const bActive = b.status === "pending" || b.status === "in_progress";
12151
+ if (aActive && !bActive) return -1;
12152
+ if (!aActive && bActive) return 1;
12136
12153
  return 0;
12137
12154
  });
12138
12155
  return mapped;
@@ -12297,6 +12314,36 @@ var BugBearClient = class {
12297
12314
  async failAssignment(assignmentId) {
12298
12315
  return this.updateAssignmentStatus(assignmentId, "failed");
12299
12316
  }
12317
+ /**
12318
+ * Reopen a completed assignment — sets it back to in_progress with a fresh timer.
12319
+ * Clears completed_at and duration_seconds so it can be re-evaluated.
12320
+ */
12321
+ async reopenAssignment(assignmentId) {
12322
+ try {
12323
+ const { data: current, error: fetchError } = await this.supabase.from("test_assignments").select("status").eq("id", assignmentId).single();
12324
+ if (fetchError || !current) {
12325
+ return { success: false, error: "Assignment not found" };
12326
+ }
12327
+ if (current.status === "pending" || current.status === "in_progress") {
12328
+ return { success: true };
12329
+ }
12330
+ const { error } = await this.supabase.from("test_assignments").update({
12331
+ status: "in_progress",
12332
+ started_at: (/* @__PURE__ */ new Date()).toISOString(),
12333
+ completed_at: null,
12334
+ duration_seconds: null,
12335
+ skip_reason: null
12336
+ }).eq("id", assignmentId).eq("status", current.status);
12337
+ if (error) {
12338
+ console.error("BugBear: Failed to reopen assignment", error);
12339
+ return { success: false, error: error.message };
12340
+ }
12341
+ return { success: true };
12342
+ } catch (err) {
12343
+ const message = err instanceof Error ? err.message : "Unknown error";
12344
+ return { success: false, error: message };
12345
+ }
12346
+ }
12300
12347
  /**
12301
12348
  * Skip a test assignment with a required reason
12302
12349
  * Marks the assignment as 'skipped' and records why it was skipped
@@ -13733,7 +13780,7 @@ import {
13733
13780
  StyleSheet as StyleSheet18,
13734
13781
  Dimensions as Dimensions2,
13735
13782
  KeyboardAvoidingView,
13736
- Platform as Platform4,
13783
+ Platform as Platform5,
13737
13784
  PanResponder,
13738
13785
  Animated as Animated2,
13739
13786
  ActivityIndicator as ActivityIndicator2,
@@ -14452,6 +14499,39 @@ function TestDetailScreen({ testId, nav }) {
14452
14499
  setIsSubmitting(false);
14453
14500
  }
14454
14501
  }, [client, displayedAssignment, refreshAssignments, nav, isSubmitting]);
14502
+ const handleReopen = useCallback2(async () => {
14503
+ if (!client || !displayedAssignment || isSubmitting) return;
14504
+ Keyboard.dismiss();
14505
+ setIsSubmitting(true);
14506
+ try {
14507
+ await client.reopenAssignment(displayedAssignment.id);
14508
+ await refreshAssignments();
14509
+ } finally {
14510
+ setIsSubmitting(false);
14511
+ }
14512
+ }, [client, displayedAssignment, refreshAssignments, isSubmitting]);
14513
+ const handleChangeResult = useCallback2(async (newStatus) => {
14514
+ if (!client || !displayedAssignment || isSubmitting) return;
14515
+ Keyboard.dismiss();
14516
+ setIsSubmitting(true);
14517
+ try {
14518
+ await client.reopenAssignment(displayedAssignment.id);
14519
+ await client.updateAssignmentStatus(displayedAssignment.id, newStatus);
14520
+ await refreshAssignments();
14521
+ if (newStatus === "failed") {
14522
+ nav.replace({
14523
+ name: "REPORT",
14524
+ prefill: {
14525
+ type: "test_fail",
14526
+ assignmentId: displayedAssignment.id,
14527
+ testCaseId: displayedAssignment.testCase.id
14528
+ }
14529
+ });
14530
+ }
14531
+ } finally {
14532
+ setIsSubmitting(false);
14533
+ }
14534
+ }, [client, displayedAssignment, refreshAssignments, nav, isSubmitting]);
14455
14535
  const handleSkip = useCallback2(async () => {
14456
14536
  if (!client || !displayedAssignment || !selectedSkipReason) return;
14457
14537
  Keyboard.dismiss();
@@ -14532,7 +14612,39 @@ function TestDetailScreen({ testId, nav }) {
14532
14612
  }
14533
14613
  },
14534
14614
  /* @__PURE__ */ React4.createElement(Text2, { style: styles2.navigateText }, "\u{1F9ED} Go to test location")
14535
- ), /* @__PURE__ */ React4.createElement(TouchableOpacity2, { onPress: () => setShowDetails(!showDetails), style: styles2.detailsToggle }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.detailsToggleText }, showDetails ? "\u25BC" : "\u25B6", " Details")), showDetails && /* @__PURE__ */ React4.createElement(View3, { style: styles2.detailsSection }, testCase.key && /* @__PURE__ */ React4.createElement(Text2, { style: styles2.detailMeta }, testCase.key, " \xB7 ", testCase.priority, " \xB7 ", info.name), testCase.description && /* @__PURE__ */ React4.createElement(Text2, { style: styles2.detailDesc }, testCase.description), testCase.group && /* @__PURE__ */ React4.createElement(View3, { style: styles2.folderProgress }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.folderName }, "\u{1F4C1} ", testCase.group.name))), /* @__PURE__ */ React4.createElement(View3, { style: styles2.actionButtons }, /* @__PURE__ */ React4.createElement(TouchableOpacity2, { style: [styles2.actionBtn, styles2.failBtn, isSubmitting && { opacity: 0.5 }], onPress: handleFail, disabled: isSubmitting }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.failBtnText }, isSubmitting ? "Failing..." : "Fail")), /* @__PURE__ */ React4.createElement(TouchableOpacity2, { style: [styles2.actionBtn, styles2.skipBtn, isSubmitting && { opacity: 0.5 }], onPress: () => setShowSkipModal(true), disabled: isSubmitting }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.skipBtnText }, "Skip")), /* @__PURE__ */ React4.createElement(TouchableOpacity2, { style: [styles2.actionBtn, styles2.passBtn, isSubmitting && { opacity: 0.5 }], onPress: handlePass, disabled: isSubmitting }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.passBtnText }, isSubmitting ? "Passing..." : "Pass"))), /* @__PURE__ */ React4.createElement(Modal, { visible: showSkipModal, transparent: true, animationType: "fade" }, /* @__PURE__ */ React4.createElement(View3, { style: styles2.modalOverlay }, /* @__PURE__ */ React4.createElement(View3, { style: styles2.modalContent }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.modalTitle }, "Skip this test?"), /* @__PURE__ */ React4.createElement(Text2, { style: styles2.modalSubtitle }, "Select a reason:"), [
14615
+ ), /* @__PURE__ */ React4.createElement(TouchableOpacity2, { onPress: () => setShowDetails(!showDetails), style: styles2.detailsToggle }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.detailsToggleText }, showDetails ? "\u25BC" : "\u25B6", " Details")), showDetails && /* @__PURE__ */ React4.createElement(View3, { style: styles2.detailsSection }, testCase.key && /* @__PURE__ */ React4.createElement(Text2, { style: styles2.detailMeta }, testCase.key, " \xB7 ", testCase.priority, " \xB7 ", info.name), testCase.description && /* @__PURE__ */ React4.createElement(Text2, { style: styles2.detailDesc }, testCase.description), testCase.group && /* @__PURE__ */ React4.createElement(View3, { style: styles2.folderProgress }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.folderName }, "\u{1F4C1} ", testCase.group.name))), displayedAssignment.status === "passed" || displayedAssignment.status === "failed" || displayedAssignment.status === "skipped" || displayedAssignment.status === "blocked" ? /* @__PURE__ */ React4.createElement(React4.Fragment, null, /* @__PURE__ */ React4.createElement(View3, { style: [
14616
+ styles2.completedBanner,
14617
+ displayedAssignment.status === "passed" && styles2.completedBannerPass,
14618
+ displayedAssignment.status === "failed" && styles2.completedBannerFail
14619
+ ] }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.completedIcon }, displayedAssignment.status === "passed" ? "\u2705" : displayedAssignment.status === "failed" ? "\u274C" : displayedAssignment.status === "skipped" ? "\u23ED" : "\u{1F6AB}"), /* @__PURE__ */ React4.createElement(Text2, { style: [
14620
+ styles2.completedLabel,
14621
+ displayedAssignment.status === "passed" && { color: colors.green },
14622
+ displayedAssignment.status === "failed" && { color: "#fca5a5" }
14623
+ ] }, "Marked as ", displayedAssignment.status.charAt(0).toUpperCase() + displayedAssignment.status.slice(1))), /* @__PURE__ */ React4.createElement(View3, { style: [styles2.actionButtons, { marginTop: 4 }] }, /* @__PURE__ */ React4.createElement(
14624
+ TouchableOpacity2,
14625
+ {
14626
+ style: [styles2.actionBtn, styles2.reopenBtn, isSubmitting && { opacity: 0.5 }],
14627
+ onPress: handleReopen,
14628
+ disabled: isSubmitting
14629
+ },
14630
+ /* @__PURE__ */ React4.createElement(Text2, { style: styles2.reopenBtnText }, isSubmitting ? "Reopening..." : "\u{1F504} Reopen Test")
14631
+ ), displayedAssignment.status === "passed" && /* @__PURE__ */ React4.createElement(
14632
+ TouchableOpacity2,
14633
+ {
14634
+ style: [styles2.actionBtn, styles2.failBtn, isSubmitting && { opacity: 0.5 }],
14635
+ onPress: () => handleChangeResult("failed"),
14636
+ disabled: isSubmitting
14637
+ },
14638
+ /* @__PURE__ */ React4.createElement(Text2, { style: styles2.failBtnText }, "Change to Fail")
14639
+ ), displayedAssignment.status === "failed" && /* @__PURE__ */ React4.createElement(
14640
+ TouchableOpacity2,
14641
+ {
14642
+ style: [styles2.actionBtn, styles2.passBtn, isSubmitting && { opacity: 0.5 }],
14643
+ onPress: () => handleChangeResult("passed"),
14644
+ disabled: isSubmitting
14645
+ },
14646
+ /* @__PURE__ */ React4.createElement(Text2, { style: styles2.passBtnText }, "Change to Pass")
14647
+ ))) : /* @__PURE__ */ React4.createElement(View3, { style: styles2.actionButtons }, /* @__PURE__ */ React4.createElement(TouchableOpacity2, { style: [styles2.actionBtn, styles2.failBtn, isSubmitting && { opacity: 0.5 }], onPress: handleFail, disabled: isSubmitting }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.failBtnText }, isSubmitting ? "Failing..." : "Fail")), /* @__PURE__ */ React4.createElement(TouchableOpacity2, { style: [styles2.actionBtn, styles2.skipBtn, isSubmitting && { opacity: 0.5 }], onPress: () => setShowSkipModal(true), disabled: isSubmitting }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.skipBtnText }, "Skip")), /* @__PURE__ */ React4.createElement(TouchableOpacity2, { style: [styles2.actionBtn, styles2.passBtn, isSubmitting && { opacity: 0.5 }], onPress: handlePass, disabled: isSubmitting }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.passBtnText }, isSubmitting ? "Passing..." : "Pass"))), /* @__PURE__ */ React4.createElement(Modal, { visible: showSkipModal, transparent: true, animationType: "fade" }, /* @__PURE__ */ React4.createElement(View3, { style: styles2.modalOverlay }, /* @__PURE__ */ React4.createElement(View3, { style: styles2.modalContent }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.modalTitle }, "Skip this test?"), /* @__PURE__ */ React4.createElement(Text2, { style: styles2.modalSubtitle }, "Select a reason:"), [
14536
14648
  { reason: "blocked", label: "\u{1F6AB} Blocked by a bug" },
14537
14649
  { reason: "not_ready", label: "\u{1F6A7} Feature not ready" },
14538
14650
  { reason: "dependency", label: "\u{1F517} Needs another test first" },
@@ -14637,6 +14749,14 @@ var styles2 = StyleSheet4.create({
14637
14749
  detailDesc: { fontSize: 13, color: colors.textSecondary, lineHeight: 18 },
14638
14750
  folderProgress: { marginTop: 8 },
14639
14751
  folderName: { fontSize: 12, color: colors.textMuted },
14752
+ // Completed state
14753
+ completedBanner: { flexDirection: "row", alignItems: "center", justifyContent: "center", gap: 6, paddingVertical: 8, paddingHorizontal: 12, borderRadius: 8, marginTop: 8, marginBottom: 8, backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border },
14754
+ completedBannerPass: { backgroundColor: colors.greenDark, borderColor: colors.green },
14755
+ completedBannerFail: { backgroundColor: colors.redDark, borderColor: colors.red },
14756
+ completedIcon: { fontSize: 14 },
14757
+ completedLabel: { fontSize: 13, fontWeight: "600", color: colors.textSecondary },
14758
+ reopenBtn: { backgroundColor: colors.card, borderWidth: 1, borderColor: colors.blue },
14759
+ reopenBtnText: { fontSize: 14, fontWeight: "600", color: colors.blue },
14640
14760
  // Action buttons
14641
14761
  actionButtons: { flexDirection: "row", gap: 10, marginTop: 8 },
14642
14762
  actionBtn: { flex: 1, paddingVertical: 14, borderRadius: 12, alignItems: "center" },
@@ -14665,12 +14785,13 @@ var styles2 = StyleSheet4.create({
14665
14785
 
14666
14786
  // src/widget/screens/TestListScreen.tsx
14667
14787
  import React5, { useState as useState3, useMemo as useMemo2, useCallback as useCallback3, useEffect as useEffect5 } from "react";
14668
- import { View as View4, Text as Text3, TouchableOpacity as TouchableOpacity3, StyleSheet as StyleSheet5, ScrollView, TextInput as TextInput2 } from "react-native";
14788
+ import { View as View4, Text as Text3, TouchableOpacity as TouchableOpacity3, StyleSheet as StyleSheet5, ScrollView, TextInput as TextInput2, Platform as Platform3, Linking as Linking2 } from "react-native";
14669
14789
  function TestListScreen({ nav }) {
14670
- const { assignments, currentAssignment, refreshAssignments, isLoading } = useBugBear();
14790
+ const { assignments, currentAssignment, refreshAssignments, dashboardUrl, isLoading } = useBugBear();
14671
14791
  const [filter, setFilter] = useState3("all");
14672
14792
  const [roleFilter, setRoleFilter] = useState3(null);
14673
14793
  const [trackFilter, setTrackFilter] = useState3(null);
14794
+ const [platformFilter, setPlatformFilter] = useState3(Platform3.OS === "android" ? "android" : "ios");
14674
14795
  const [searchQuery, setSearchQuery] = useState3("");
14675
14796
  const [sortMode, setSortMode] = useState3("priority");
14676
14797
  const [collapsedFolders, setCollapsedFolders] = useState3(/* @__PURE__ */ new Set());
@@ -14691,6 +14812,13 @@ function TestListScreen({ nav }) {
14691
14812
  }
14692
14813
  return Array.from(trackMap.values());
14693
14814
  }, [assignments]);
14815
+ const availablePlatforms = useMemo2(() => {
14816
+ const set = /* @__PURE__ */ new Set();
14817
+ for (const a of assignments) {
14818
+ if (a.testCase.platforms) a.testCase.platforms.forEach((p) => set.add(p));
14819
+ }
14820
+ return Array.from(set).sort();
14821
+ }, [assignments]);
14694
14822
  const selectedRole = availableRoles.find((r) => r.id === roleFilter);
14695
14823
  const groupedAssignments = useMemo2(() => {
14696
14824
  const groups = /* @__PURE__ */ new Map();
@@ -14737,6 +14865,7 @@ function TestListScreen({ nav }) {
14737
14865
  });
14738
14866
  }, []);
14739
14867
  const filterAssignment = useCallback3((a) => {
14868
+ if (platformFilter && a.testCase.platforms && !a.testCase.platforms.includes(platformFilter)) return false;
14740
14869
  if (roleFilter && a.testCase.role?.id !== roleFilter) return false;
14741
14870
  if (trackFilter && a.testCase.track?.id !== trackFilter) return false;
14742
14871
  if (searchQuery) {
@@ -14749,7 +14878,7 @@ function TestListScreen({ nav }) {
14749
14878
  if (filter === "done") return a.status === "passed";
14750
14879
  if (filter === "reopened") return a.status === "failed";
14751
14880
  return true;
14752
- }, [roleFilter, trackFilter, searchQuery, filter]);
14881
+ }, [platformFilter, roleFilter, trackFilter, searchQuery, filter]);
14753
14882
  if (isLoading) return /* @__PURE__ */ React5.createElement(TestListScreenSkeleton, null);
14754
14883
  return /* @__PURE__ */ React5.createElement(View4, null, /* @__PURE__ */ React5.createElement(View4, { style: styles3.filterBar }, [
14755
14884
  { key: "all", label: "All", count: assignments.length },
@@ -14787,7 +14916,30 @@ function TestListScreen({ nav }) {
14787
14916
  placeholderTextColor: colors.textMuted,
14788
14917
  style: styles3.searchInput
14789
14918
  }
14790
- )), /* @__PURE__ */ React5.createElement(View4, { style: styles3.trackSortRow }, availableTracks.length >= 2 && /* @__PURE__ */ React5.createElement(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: { flex: 1 } }, /* @__PURE__ */ React5.createElement(
14919
+ )), availablePlatforms.length >= 2 && /* @__PURE__ */ React5.createElement(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: styles3.platformBar }, /* @__PURE__ */ React5.createElement(
14920
+ TouchableOpacity3,
14921
+ {
14922
+ style: [styles3.platformBtn, !platformFilter && styles3.platformBtnActive],
14923
+ onPress: () => setPlatformFilter(null)
14924
+ },
14925
+ /* @__PURE__ */ React5.createElement(Text3, { style: [styles3.platformBtnText, !platformFilter && styles3.platformBtnTextActive] }, "All Platforms")
14926
+ ), availablePlatforms.map((p) => {
14927
+ const isActive = platformFilter === p;
14928
+ const label = p === "ios" ? "iOS" : p === "android" ? "Android" : p === "web" ? "Web" : p;
14929
+ const icon = p === "ios" ? "\u{1F4F1}" : p === "android" ? "\u{1F916}" : p === "web" ? "\u{1F310}" : "\u{1F4CB}";
14930
+ return /* @__PURE__ */ React5.createElement(
14931
+ TouchableOpacity3,
14932
+ {
14933
+ key: p,
14934
+ style: [
14935
+ styles3.platformBtn,
14936
+ isActive && { backgroundColor: colors.blue + "20", borderColor: colors.blue + "60", borderWidth: 1 }
14937
+ ],
14938
+ onPress: () => setPlatformFilter(isActive ? null : p)
14939
+ },
14940
+ /* @__PURE__ */ React5.createElement(Text3, { style: [styles3.platformBtnText, isActive && { color: colors.blue, fontWeight: "600" }] }, icon, " ", label)
14941
+ );
14942
+ })), /* @__PURE__ */ React5.createElement(View4, { style: styles3.trackSortRow }, availableTracks.length >= 2 && /* @__PURE__ */ React5.createElement(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: { flex: 1 } }, /* @__PURE__ */ React5.createElement(
14791
14943
  TouchableOpacity3,
14792
14944
  {
14793
14945
  style: [styles3.trackBtn, !trackFilter && styles3.trackBtnActive],
@@ -14848,7 +15000,15 @@ function TestListScreen({ nav }) {
14848
15000
  ] }, badge.label))
14849
15001
  );
14850
15002
  }));
14851
- }), /* @__PURE__ */ React5.createElement(TouchableOpacity3, { style: styles3.refreshBtn, onPress: refreshAssignments }, /* @__PURE__ */ React5.createElement(Text3, { style: styles3.refreshText }, "\u21BB", " Refresh")));
15003
+ }), dashboardUrl && /* @__PURE__ */ React5.createElement(
15004
+ TouchableOpacity3,
15005
+ {
15006
+ style: styles3.dashboardLink,
15007
+ onPress: () => Linking2.openURL(`${dashboardUrl}/test-cases`),
15008
+ activeOpacity: 0.7
15009
+ },
15010
+ /* @__PURE__ */ React5.createElement(Text3, { style: styles3.dashboardLinkText }, "\u{1F310}", " Manage on Dashboard ", "\u2192")
15011
+ ), /* @__PURE__ */ React5.createElement(TouchableOpacity3, { style: styles3.refreshBtn, onPress: refreshAssignments }, /* @__PURE__ */ React5.createElement(Text3, { style: styles3.refreshText }, "\u21BB", " Refresh")));
14852
15012
  }
14853
15013
  var styles3 = StyleSheet5.create({
14854
15014
  filterBar: { flexDirection: "row", gap: 8, marginBottom: 8 },
@@ -14887,6 +15047,11 @@ var styles3 = StyleSheet5.create({
14887
15047
  statusPillText: { fontSize: 10, fontWeight: "600" },
14888
15048
  searchContainer: { marginBottom: 8 },
14889
15049
  searchInput: { backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border, borderRadius: 8, paddingHorizontal: 12, paddingVertical: 8, fontSize: 13, color: colors.textPrimary },
15050
+ platformBar: { flexDirection: "row", marginBottom: 8 },
15051
+ platformBtn: { flexDirection: "row", alignItems: "center", gap: 4, paddingHorizontal: 10, paddingVertical: 4, borderRadius: 6, marginRight: 6, borderWidth: 1, borderColor: "transparent" },
15052
+ platformBtnActive: { backgroundColor: colors.card, borderColor: colors.border },
15053
+ platformBtnText: { fontSize: 11, color: colors.textMuted },
15054
+ platformBtnTextActive: { color: colors.textPrimary, fontWeight: "600" },
14890
15055
  trackSortRow: { flexDirection: "row", alignItems: "center", marginBottom: 10, gap: 8 },
14891
15056
  trackBtn: { flexDirection: "row", alignItems: "center", gap: 4, paddingHorizontal: 10, paddingVertical: 4, borderRadius: 6, marginRight: 6, borderWidth: 1, borderColor: "transparent" },
14892
15057
  trackBtnActive: { backgroundColor: colors.card, borderColor: colors.border },
@@ -14897,7 +15062,9 @@ var styles3 = StyleSheet5.create({
14897
15062
  sortBtnActive: { backgroundColor: colors.card, borderColor: colors.border },
14898
15063
  sortBtnText: { fontSize: 11, color: colors.textMuted },
14899
15064
  sortBtnTextActive: { color: colors.textPrimary, fontWeight: "600" },
14900
- refreshBtn: { alignItems: "center", paddingVertical: 12 },
15065
+ dashboardLink: { alignItems: "center", paddingTop: 12 },
15066
+ dashboardLinkText: { fontSize: 13, fontWeight: "500", color: colors.blue },
15067
+ refreshBtn: { alignItems: "center", paddingVertical: 8 },
14901
15068
  refreshText: { fontSize: 13, color: colors.blue }
14902
15069
  });
14903
15070
 
@@ -15619,9 +15786,9 @@ var styles9 = StyleSheet11.create({
15619
15786
 
15620
15787
  // src/widget/screens/MessageListScreen.tsx
15621
15788
  import React12 from "react";
15622
- import { View as View11, Text as Text10, TouchableOpacity as TouchableOpacity9, StyleSheet as StyleSheet12 } from "react-native";
15789
+ import { View as View11, Text as Text10, TouchableOpacity as TouchableOpacity9, StyleSheet as StyleSheet12, Linking as Linking3 } from "react-native";
15623
15790
  function MessageListScreen({ nav }) {
15624
- const { threads, unreadCount, refreshThreads, isLoading } = useBugBear();
15791
+ const { threads, unreadCount, refreshThreads, dashboardUrl, isLoading } = useBugBear();
15625
15792
  if (isLoading) return /* @__PURE__ */ React12.createElement(MessageListScreenSkeleton, null);
15626
15793
  return /* @__PURE__ */ React12.createElement(View11, null, /* @__PURE__ */ React12.createElement(
15627
15794
  TouchableOpacity9,
@@ -15639,7 +15806,15 @@ function MessageListScreen({ nav }) {
15639
15806
  },
15640
15807
  /* @__PURE__ */ React12.createElement(View11, { style: styles10.threadLeft }, /* @__PURE__ */ React12.createElement(Text10, { style: styles10.threadIcon }, getThreadTypeIcon(thread.threadType)), /* @__PURE__ */ React12.createElement(View11, { style: styles10.threadInfo }, /* @__PURE__ */ React12.createElement(View11, { style: styles10.threadTitleRow }, thread.isPinned && /* @__PURE__ */ React12.createElement(Text10, { style: styles10.pinIcon }, "\u{1F4CC}"), /* @__PURE__ */ React12.createElement(Text10, { style: styles10.threadSubject, numberOfLines: 1 }, thread.subject || "No subject")), thread.lastMessage && /* @__PURE__ */ React12.createElement(Text10, { style: styles10.threadPreview, numberOfLines: 1 }, thread.lastMessage.senderName, ": ", thread.lastMessage.content))),
15641
15808
  /* @__PURE__ */ React12.createElement(View11, { style: styles10.threadRight }, /* @__PURE__ */ React12.createElement(Text10, { style: styles10.threadTime }, formatRelativeTime(thread.lastMessageAt)), thread.unreadCount > 0 && /* @__PURE__ */ React12.createElement(View11, { style: styles10.unreadBadge }, /* @__PURE__ */ React12.createElement(Text10, { style: styles10.unreadText }, thread.unreadCount)), thread.priority !== "normal" && /* @__PURE__ */ React12.createElement(View11, { style: [styles10.priorityDot, { backgroundColor: getPriorityColor(thread.priority) }] }))
15642
- ))), /* @__PURE__ */ React12.createElement(View11, { style: styles10.footer }, /* @__PURE__ */ React12.createElement(Text10, { style: styles10.footerText }, threads.length, " thread", threads.length !== 1 ? "s" : "", " \xB7 ", unreadCount, " unread"), /* @__PURE__ */ React12.createElement(TouchableOpacity9, { onPress: refreshThreads }, /* @__PURE__ */ React12.createElement(Text10, { style: styles10.refreshText }, "\u21BB Refresh"))));
15809
+ ))), dashboardUrl && /* @__PURE__ */ React12.createElement(
15810
+ TouchableOpacity9,
15811
+ {
15812
+ style: styles10.dashboardLink,
15813
+ onPress: () => Linking3.openURL(`${dashboardUrl}/discussions`),
15814
+ activeOpacity: 0.7
15815
+ },
15816
+ /* @__PURE__ */ React12.createElement(Text10, { style: styles10.dashboardLinkText }, "\u{1F310}", " View on Dashboard ", "\u2192")
15817
+ ), /* @__PURE__ */ React12.createElement(View11, { style: styles10.footer }, /* @__PURE__ */ React12.createElement(Text10, { style: styles10.footerText }, threads.length, " thread", threads.length !== 1 ? "s" : "", " \xB7 ", unreadCount, " unread"), /* @__PURE__ */ React12.createElement(TouchableOpacity9, { onPress: refreshThreads }, /* @__PURE__ */ React12.createElement(Text10, { style: styles10.refreshText }, "\u21BB Refresh"))));
15643
15818
  }
15644
15819
  var styles10 = StyleSheet12.create({
15645
15820
  newMsgButton: { backgroundColor: colors.blue, paddingVertical: 12, borderRadius: 12, alignItems: "center", marginBottom: 16 },
@@ -15658,7 +15833,9 @@ var styles10 = StyleSheet12.create({
15658
15833
  unreadBadge: { backgroundColor: colors.blue, borderRadius: 10, minWidth: 20, height: 20, justifyContent: "center", alignItems: "center", paddingHorizontal: 6 },
15659
15834
  unreadText: { fontSize: 11, fontWeight: "bold", color: "#fff" },
15660
15835
  priorityDot: { width: 8, height: 8, borderRadius: 4 },
15661
- footer: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", paddingTop: 12, paddingHorizontal: 4 },
15836
+ dashboardLink: { alignItems: "center", paddingTop: 12 },
15837
+ dashboardLinkText: { fontSize: 13, fontWeight: "500", color: colors.blue },
15838
+ footer: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", paddingTop: 8, paddingHorizontal: 4 },
15662
15839
  footerText: { fontSize: 12, color: colors.textMuted },
15663
15840
  refreshText: { fontSize: 13, color: colors.blue }
15664
15841
  });
@@ -16139,7 +16316,7 @@ var styles14 = StyleSheet16.create({
16139
16316
 
16140
16317
  // src/widget/screens/IssueDetailScreen.tsx
16141
16318
  import React17 from "react";
16142
- import { View as View16, Text as Text15, Image as Image3, StyleSheet as StyleSheet17, Linking as Linking2, TouchableOpacity as TouchableOpacity14 } from "react-native";
16319
+ import { View as View16, Text as Text15, Image as Image3, StyleSheet as StyleSheet17, Linking as Linking4, TouchableOpacity as TouchableOpacity14 } from "react-native";
16143
16320
  var STATUS_LABELS = {
16144
16321
  new: { label: "New", bg: "#1e3a5f", color: "#60a5fa" },
16145
16322
  triaging: { label: "Triaging", bg: "#1e3a5f", color: "#60a5fa" },
@@ -16161,9 +16338,18 @@ var SEVERITY_CONFIG = {
16161
16338
  low: { label: "Low", color: "#71717a", bg: "#27272a" }
16162
16339
  };
16163
16340
  function IssueDetailScreen({ nav, issue }) {
16341
+ const { dashboardUrl } = useBugBear();
16164
16342
  const statusConfig = STATUS_LABELS[issue.status] || { label: issue.status, bg: "#27272a", color: "#a1a1aa" };
16165
16343
  const severityConfig = issue.severity ? SEVERITY_CONFIG[issue.severity] : null;
16166
- return /* @__PURE__ */ React17.createElement(View16, null, /* @__PURE__ */ React17.createElement(View16, { style: styles15.badgeRow }, /* @__PURE__ */ React17.createElement(View16, { style: [styles15.badge, { backgroundColor: statusConfig.bg }] }, /* @__PURE__ */ React17.createElement(Text15, { style: [styles15.badgeText, { color: statusConfig.color }] }, statusConfig.label)), severityConfig && /* @__PURE__ */ React17.createElement(View16, { style: [styles15.badge, { backgroundColor: severityConfig.bg }] }, /* @__PURE__ */ React17.createElement(Text15, { style: [styles15.badgeText, { color: severityConfig.color }] }, severityConfig.label))), /* @__PURE__ */ React17.createElement(Text15, { style: styles15.title }, issue.title), issue.route && /* @__PURE__ */ React17.createElement(Text15, { style: styles15.route }, issue.route), issue.description && /* @__PURE__ */ React17.createElement(View16, { style: styles15.descriptionCard }, /* @__PURE__ */ React17.createElement(Text15, { style: styles15.descriptionText }, issue.description)), issue.verifiedByName && /* @__PURE__ */ React17.createElement(View16, { style: styles15.verifiedCard }, /* @__PURE__ */ React17.createElement(View16, { style: styles15.verifiedHeader }, /* @__PURE__ */ React17.createElement(Text15, { style: styles15.verifiedIcon }, "\u2705"), /* @__PURE__ */ React17.createElement(Text15, { style: styles15.verifiedTitle }, "Retesting Proof")), /* @__PURE__ */ React17.createElement(Text15, { style: styles15.verifiedBody }, "Verified by ", issue.verifiedByName, issue.verifiedAt && ` on ${new Date(issue.verifiedAt).toLocaleDateString(void 0, { month: "short", day: "numeric", year: "numeric" })}`)), issue.originalBugTitle && /* @__PURE__ */ React17.createElement(View16, { style: styles15.originalBugCard }, /* @__PURE__ */ React17.createElement(View16, { style: styles15.originalBugHeader }, /* @__PURE__ */ React17.createElement(Text15, { style: styles15.originalBugIcon }, "\u{1F504}"), /* @__PURE__ */ React17.createElement(Text15, { style: styles15.originalBugTitle }, "Original Bug")), /* @__PURE__ */ React17.createElement(Text15, { style: styles15.originalBugBody }, "Retest of: ", issue.originalBugTitle)), issue.screenshotUrls && issue.screenshotUrls.length > 0 && /* @__PURE__ */ React17.createElement(View16, { style: styles15.screenshotSection }, /* @__PURE__ */ React17.createElement(Text15, { style: styles15.screenshotLabel }, "Screenshots (", issue.screenshotUrls.length, ")"), /* @__PURE__ */ React17.createElement(View16, { style: styles15.screenshotRow }, issue.screenshotUrls.map((url, i) => /* @__PURE__ */ React17.createElement(TouchableOpacity14, { key: i, onPress: () => Linking2.openURL(url), activeOpacity: 0.7 }, /* @__PURE__ */ React17.createElement(Image3, { source: { uri: url }, style: styles15.screenshotThumb }))))), /* @__PURE__ */ React17.createElement(View16, { style: styles15.metaSection }, issue.reporterName && /* @__PURE__ */ React17.createElement(Text15, { style: styles15.metaText }, "Reported by ", issue.reporterName), /* @__PURE__ */ React17.createElement(Text15, { style: styles15.metaTextSmall }, "Created ", formatRelativeTime(issue.createdAt), " ", "\xB7", " Updated ", formatRelativeTime(issue.updatedAt))));
16344
+ return /* @__PURE__ */ React17.createElement(View16, null, /* @__PURE__ */ React17.createElement(View16, { style: styles15.badgeRow }, /* @__PURE__ */ React17.createElement(View16, { style: [styles15.badge, { backgroundColor: statusConfig.bg }] }, /* @__PURE__ */ React17.createElement(Text15, { style: [styles15.badgeText, { color: statusConfig.color }] }, statusConfig.label)), severityConfig && /* @__PURE__ */ React17.createElement(View16, { style: [styles15.badge, { backgroundColor: severityConfig.bg }] }, /* @__PURE__ */ React17.createElement(Text15, { style: [styles15.badgeText, { color: severityConfig.color }] }, severityConfig.label))), /* @__PURE__ */ React17.createElement(Text15, { style: styles15.title }, issue.title), issue.route && /* @__PURE__ */ React17.createElement(Text15, { style: styles15.route }, issue.route), issue.description && /* @__PURE__ */ React17.createElement(View16, { style: styles15.descriptionCard }, /* @__PURE__ */ React17.createElement(Text15, { style: styles15.descriptionText }, issue.description)), issue.verifiedByName && /* @__PURE__ */ React17.createElement(View16, { style: styles15.verifiedCard }, /* @__PURE__ */ React17.createElement(View16, { style: styles15.verifiedHeader }, /* @__PURE__ */ React17.createElement(Text15, { style: styles15.verifiedIcon }, "\u2705"), /* @__PURE__ */ React17.createElement(Text15, { style: styles15.verifiedTitle }, "Retesting Proof")), /* @__PURE__ */ React17.createElement(Text15, { style: styles15.verifiedBody }, "Verified by ", issue.verifiedByName, issue.verifiedAt && ` on ${new Date(issue.verifiedAt).toLocaleDateString(void 0, { month: "short", day: "numeric", year: "numeric" })}`)), issue.originalBugTitle && /* @__PURE__ */ React17.createElement(View16, { style: styles15.originalBugCard }, /* @__PURE__ */ React17.createElement(View16, { style: styles15.originalBugHeader }, /* @__PURE__ */ React17.createElement(Text15, { style: styles15.originalBugIcon }, "\u{1F504}"), /* @__PURE__ */ React17.createElement(Text15, { style: styles15.originalBugTitle }, "Original Bug")), /* @__PURE__ */ React17.createElement(Text15, { style: styles15.originalBugBody }, "Retest of: ", issue.originalBugTitle)), issue.screenshotUrls && issue.screenshotUrls.length > 0 && /* @__PURE__ */ React17.createElement(View16, { style: styles15.screenshotSection }, /* @__PURE__ */ React17.createElement(Text15, { style: styles15.screenshotLabel }, "Screenshots (", issue.screenshotUrls.length, ")"), /* @__PURE__ */ React17.createElement(View16, { style: styles15.screenshotRow }, issue.screenshotUrls.map((url, i) => /* @__PURE__ */ React17.createElement(TouchableOpacity14, { key: i, onPress: () => Linking4.openURL(url), activeOpacity: 0.7 }, /* @__PURE__ */ React17.createElement(Image3, { source: { uri: url }, style: styles15.screenshotThumb }))))), /* @__PURE__ */ React17.createElement(View16, { style: styles15.metaSection }, issue.reporterName && /* @__PURE__ */ React17.createElement(Text15, { style: styles15.metaText }, "Reported by ", issue.reporterName), /* @__PURE__ */ React17.createElement(Text15, { style: styles15.metaTextSmall }, "Created ", formatRelativeTime(issue.createdAt), " ", "\xB7", " Updated ", formatRelativeTime(issue.updatedAt))), dashboardUrl && /* @__PURE__ */ React17.createElement(
16345
+ TouchableOpacity14,
16346
+ {
16347
+ style: styles15.dashboardLink,
16348
+ onPress: () => Linking4.openURL(`${dashboardUrl}/reports`),
16349
+ activeOpacity: 0.7
16350
+ },
16351
+ /* @__PURE__ */ React17.createElement(Text15, { style: styles15.dashboardLinkText }, "\u{1F310}", " View on Dashboard ", "\u2192")
16352
+ ));
16167
16353
  }
16168
16354
  var styles15 = StyleSheet17.create({
16169
16355
  badgeRow: {
@@ -16292,6 +16478,18 @@ var styles15 = StyleSheet17.create({
16292
16478
  metaTextSmall: {
16293
16479
  fontSize: 11,
16294
16480
  color: colors.textDim
16481
+ },
16482
+ dashboardLink: {
16483
+ alignItems: "center",
16484
+ paddingVertical: 10,
16485
+ marginTop: 12,
16486
+ borderTopWidth: 1,
16487
+ borderTopColor: colors.border
16488
+ },
16489
+ dashboardLinkText: {
16490
+ fontSize: 13,
16491
+ fontWeight: "500",
16492
+ color: colors.blue
16295
16493
  }
16296
16494
  });
16297
16495
 
@@ -16479,7 +16677,7 @@ function BugBearButton({
16479
16677
  /* @__PURE__ */ React18.createElement(
16480
16678
  KeyboardAvoidingView,
16481
16679
  {
16482
- behavior: Platform4.OS === "ios" ? "padding" : "height",
16680
+ behavior: Platform5.OS === "ios" ? "padding" : "height",
16483
16681
  style: styles16.modalOverlay
16484
16682
  },
16485
16683
  /* @__PURE__ */ React18.createElement(View17, { style: styles16.modalContainer }, /* @__PURE__ */ React18.createElement(View17, { style: styles16.header }, /* @__PURE__ */ React18.createElement(View17, { style: styles16.headerLeft }, canGoBack ? /* @__PURE__ */ React18.createElement(View17, { style: styles16.headerNavRow }, /* @__PURE__ */ React18.createElement(TouchableOpacity15, { onPress: () => nav.pop(), style: styles16.backButton }, /* @__PURE__ */ React18.createElement(Text16, { style: styles16.backText }, "\u2190 Back")), /* @__PURE__ */ React18.createElement(TouchableOpacity15, { onPress: () => nav.reset(), style: styles16.homeButton }, /* @__PURE__ */ React18.createElement(Text16, { style: styles16.homeText }, "\u{1F3E0}"))) : /* @__PURE__ */ React18.createElement(View17, { style: styles16.headerTitleRow }, /* @__PURE__ */ React18.createElement(Text16, { style: styles16.headerTitle }, "BugBear"), testerInfo && /* @__PURE__ */ React18.createElement(TouchableOpacity15, { onPress: () => push({ name: "PROFILE" }) }, /* @__PURE__ */ React18.createElement(Text16, { style: styles16.headerName }, testerInfo.name, " \u270E")))), getHeaderTitle() ? /* @__PURE__ */ React18.createElement(Text16, { style: styles16.headerScreenTitle, numberOfLines: 1 }, getHeaderTitle()) : null, /* @__PURE__ */ React18.createElement(TouchableOpacity15, { onPress: handleClose, style: styles16.closeButton }, /* @__PURE__ */ React18.createElement(Text16, { style: styles16.closeText }, "\u2715"))), /* @__PURE__ */ React18.createElement(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bbearai/react-native",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "BugBear React Native components for mobile apps",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",