@bbearai/react-native 0.8.1 → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -12197,6 +12197,7 @@ var BugBearClient = class {
12197
12197
  this.navigationHistory = [];
12198
12198
  this.reportSubmitInFlight = false;
12199
12199
  this._queue = null;
12200
+ this._sessionStorage = new LocalStorageAdapter();
12200
12201
  this.realtimeChannels = [];
12201
12202
  this.monitor = null;
12202
12203
  this.initialized = false;
@@ -12274,6 +12275,10 @@ var BugBearClient = class {
12274
12275
  this.initOfflineQueue();
12275
12276
  this.initMonitoring();
12276
12277
  }
12278
+ /** Cache key scoped to the active project. */
12279
+ get sessionCacheKey() {
12280
+ return `bugbear_session_${this.config.projectId ?? "unknown"}`;
12281
+ }
12277
12282
  /** Initialize offline queue if configured. Shared by both init paths. */
12278
12283
  initOfflineQueue() {
12279
12284
  if (this.config.offlineQueue?.enabled) {
@@ -12337,6 +12342,26 @@ var BugBearClient = class {
12337
12342
  await this.pendingInit;
12338
12343
  if (this.initError) throw this.initError;
12339
12344
  }
12345
+ /**
12346
+ * Fire-and-forget call to a dashboard notification endpoint.
12347
+ * Only works when apiKey is configured (needed for API auth).
12348
+ * Failures are silently ignored — notifications are best-effort.
12349
+ */
12350
+ async notifyDashboard(path, body) {
12351
+ if (!this.config.apiKey) return;
12352
+ try {
12353
+ const baseUrl = (this.config.apiBaseUrl || DEFAULT_API_BASE_URL).replace(/\/$/, "");
12354
+ await fetch(`${baseUrl}/api/v1/notifications/${path}`, {
12355
+ method: "POST",
12356
+ headers: {
12357
+ "Content-Type": "application/json",
12358
+ "Authorization": `Bearer ${this.config.apiKey}`
12359
+ },
12360
+ body: JSON.stringify(body)
12361
+ });
12362
+ } catch {
12363
+ }
12364
+ }
12340
12365
  // ── Offline Queue ─────────────────────────────────────────
12341
12366
  /**
12342
12367
  * Access the offline queue (if enabled).
@@ -12363,6 +12388,48 @@ var BugBearClient = class {
12363
12388
  }
12364
12389
  await this._queue.load();
12365
12390
  }
12391
+ // ── Session Cache ──────────────────────────────────────────
12392
+ /**
12393
+ * Swap the session cache storage adapter (for React Native — pass AsyncStorage).
12394
+ * Must be called before getCachedSession() for persistence across app kills.
12395
+ * Web callers don't need this — LocalStorageAdapter is the default.
12396
+ */
12397
+ setSessionStorage(adapter) {
12398
+ this._sessionStorage = adapter;
12399
+ }
12400
+ /**
12401
+ * Cache the active QA session locally for instant restore on app restart.
12402
+ * Pass null to clear the cache (e.g. after ending a session).
12403
+ */
12404
+ async cacheSession(session) {
12405
+ try {
12406
+ if (session) {
12407
+ await this._sessionStorage.setItem(this.sessionCacheKey, JSON.stringify(session));
12408
+ } else {
12409
+ await this._sessionStorage.removeItem(this.sessionCacheKey);
12410
+ }
12411
+ } catch {
12412
+ }
12413
+ }
12414
+ /**
12415
+ * Retrieve the cached QA session. Returns null if no cache, if stale (>24h),
12416
+ * or if parsing fails. The DB fetch in initializeBugBear() is the source of truth.
12417
+ */
12418
+ async getCachedSession() {
12419
+ try {
12420
+ const raw = await this._sessionStorage.getItem(this.sessionCacheKey);
12421
+ if (!raw) return null;
12422
+ const session = JSON.parse(raw);
12423
+ const age = Date.now() - new Date(session.startedAt).getTime();
12424
+ if (age > 24 * 60 * 60 * 1e3) {
12425
+ await this._sessionStorage.removeItem(this.sessionCacheKey);
12426
+ return null;
12427
+ }
12428
+ return session;
12429
+ } catch {
12430
+ return null;
12431
+ }
12432
+ }
12366
12433
  registerQueueHandlers() {
12367
12434
  if (!this._queue) return;
12368
12435
  this._queue.registerHandler("report", async (payload) => {
@@ -12380,6 +12447,11 @@ var BugBearClient = class {
12380
12447
  if (error) return { success: false, error: error.message };
12381
12448
  return { success: true };
12382
12449
  });
12450
+ this._queue.registerHandler("email_capture", async (payload) => {
12451
+ const { error } = await this.supabase.from("email_captures").insert(payload).select("id").single();
12452
+ if (error) return { success: false, error: error.message };
12453
+ return { success: true };
12454
+ });
12383
12455
  }
12384
12456
  // ── Realtime Subscriptions ─────────────────────────────────
12385
12457
  /** Whether realtime is enabled in config. */
@@ -12567,6 +12639,8 @@ var BugBearClient = class {
12567
12639
  if (this.config.onReportSubmitted) {
12568
12640
  this.config.onReportSubmitted(report);
12569
12641
  }
12642
+ this.notifyDashboard("report", { reportId: data.id }).catch(() => {
12643
+ });
12570
12644
  return { success: true, reportId: data.id };
12571
12645
  } catch (err) {
12572
12646
  const message = err instanceof Error ? err.message : "Unknown error";
@@ -12579,6 +12653,44 @@ var BugBearClient = class {
12579
12653
  this.reportSubmitInFlight = false;
12580
12654
  }
12581
12655
  }
12656
+ /**
12657
+ * Capture an email for QA testing.
12658
+ * Called by the email interceptor — not typically called directly.
12659
+ */
12660
+ async captureEmail(payload) {
12661
+ try {
12662
+ await this.ready();
12663
+ if (!payload.subject || !payload.to || payload.to.length === 0) {
12664
+ return { success: false, error: "subject and to are required" };
12665
+ }
12666
+ const record = {
12667
+ project_id: this.config.projectId,
12668
+ to_addresses: payload.to,
12669
+ from_address: payload.from || null,
12670
+ subject: payload.subject,
12671
+ html_content: payload.html || null,
12672
+ text_content: payload.text || null,
12673
+ template_id: payload.templateId || null,
12674
+ metadata: payload.metadata || {},
12675
+ capture_mode: payload.captureMode,
12676
+ was_delivered: payload.wasDelivered,
12677
+ delivery_status: payload.wasDelivered ? "sent" : "pending"
12678
+ };
12679
+ const { data, error } = await this.supabase.from("email_captures").insert(record).select("id").single();
12680
+ if (error) {
12681
+ if (this._queue && isNetworkError(error.message)) {
12682
+ await this._queue.enqueue("email_capture", record);
12683
+ return { success: false, queued: true, error: "Queued \u2014 will send when online" };
12684
+ }
12685
+ console.error("BugBear: Failed to capture email", error.message);
12686
+ return { success: false, error: error.message };
12687
+ }
12688
+ return { success: true, captureId: data.id };
12689
+ } catch (err) {
12690
+ const message = err instanceof Error ? err.message : "Unknown error";
12691
+ return { success: false, error: message };
12692
+ }
12693
+ }
12582
12694
  /**
12583
12695
  * Get assigned tests for current user
12584
12696
  * First looks up the tester by email, then fetches their assignments
@@ -13200,6 +13312,32 @@ var BugBearClient = class {
13200
13312
  return [];
13201
13313
  }
13202
13314
  }
13315
+ /**
13316
+ * Reopen a done issue that the tester believes isn't actually fixed.
13317
+ * Transitions the report from a done status back to 'confirmed'.
13318
+ */
13319
+ async reopenReport(reportId, reason) {
13320
+ try {
13321
+ const testerInfo = await this.getTesterInfo();
13322
+ if (!testerInfo) return { success: false, error: "Not authenticated as tester" };
13323
+ const { data, error } = await this.supabase.rpc("reopen_report", {
13324
+ p_report_id: reportId,
13325
+ p_tester_id: testerInfo.id,
13326
+ p_reason: reason
13327
+ });
13328
+ if (error) {
13329
+ console.error("BugBear: Failed to reopen report", formatPgError(error));
13330
+ return { success: false, error: error.message };
13331
+ }
13332
+ if (!data?.success) {
13333
+ return { success: false, error: data?.error || "Failed to reopen report" };
13334
+ }
13335
+ return { success: true };
13336
+ } catch (err) {
13337
+ console.error("BugBear: Error reopening report", err);
13338
+ return { success: false, error: "Unexpected error" };
13339
+ }
13340
+ }
13203
13341
  /**
13204
13342
  * Basic email format validation (defense in depth)
13205
13343
  */
@@ -13786,7 +13924,7 @@ var BugBearClient = class {
13786
13924
  insertData.attachments = safeAttachments;
13787
13925
  }
13788
13926
  }
13789
- const { error } = await this.supabase.from("discussion_messages").insert(insertData);
13927
+ const { data: msgData, error } = await this.supabase.from("discussion_messages").insert(insertData).select("id").single();
13790
13928
  if (error) {
13791
13929
  if (this._queue && isNetworkError(error.message)) {
13792
13930
  await this._queue.enqueue("message", insertData);
@@ -13795,6 +13933,10 @@ var BugBearClient = class {
13795
13933
  console.error("BugBear: Failed to send message", formatPgError(error));
13796
13934
  return false;
13797
13935
  }
13936
+ if (msgData?.id) {
13937
+ this.notifyDashboard("message", { threadId, messageId: msgData.id }).catch(() => {
13938
+ });
13939
+ }
13798
13940
  await this.markThreadAsRead(threadId);
13799
13941
  return true;
13800
13942
  } catch (err) {
@@ -13920,6 +14062,7 @@ var BugBearClient = class {
13920
14062
  if (!session) {
13921
14063
  return { success: false, error: "Session created but could not be fetched" };
13922
14064
  }
14065
+ await this.cacheSession(session);
13923
14066
  return { success: true, session };
13924
14067
  } catch (err) {
13925
14068
  const message = err instanceof Error ? err.message : "Unknown error";
@@ -13943,6 +14086,7 @@ var BugBearClient = class {
13943
14086
  return { success: false, error: error.message };
13944
14087
  }
13945
14088
  const session = this.transformSession(data);
14089
+ await this.cacheSession(null);
13946
14090
  return { success: true, session };
13947
14091
  } catch (err) {
13948
14092
  const message = err instanceof Error ? err.message : "Unknown error";
@@ -13958,8 +14102,13 @@ var BugBearClient = class {
13958
14102
  const testerInfo = await this.getTesterInfo();
13959
14103
  if (!testerInfo) return null;
13960
14104
  const { data, error } = await this.supabase.from("qa_sessions").select("*").eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).eq("status", "active").order("started_at", { ascending: false }).limit(1).maybeSingle();
13961
- if (error || !data) return null;
13962
- return this.transformSession(data);
14105
+ if (error || !data) {
14106
+ await this.cacheSession(null);
14107
+ return null;
14108
+ }
14109
+ const session = this.transformSession(data);
14110
+ await this.cacheSession(session);
14111
+ return session;
13963
14112
  } catch (err) {
13964
14113
  console.error("BugBear: Error fetching active session", err);
13965
14114
  return null;
@@ -14477,6 +14626,7 @@ var BugBearContext = createContext({
14477
14626
  issueCounts: { open: 0, done: 0, reopened: 0 },
14478
14627
  refreshIssueCounts: async () => {
14479
14628
  },
14629
+ reopenReport: async () => ({ success: false }),
14480
14630
  queuedCount: 0,
14481
14631
  dashboardUrl: void 0,
14482
14632
  onError: void 0
@@ -14618,6 +14768,14 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
14618
14768
  const counts = await client.getIssueCounts();
14619
14769
  setIssueCounts(counts);
14620
14770
  }, [client]);
14771
+ const reopenReport = useCallback(async (reportId, reason) => {
14772
+ if (!client) return { success: false, error: "Client not initialized" };
14773
+ const result = await client.reopenReport(reportId, reason);
14774
+ if (result.success) {
14775
+ await refreshIssueCounts();
14776
+ }
14777
+ return result;
14778
+ }, [client, refreshIssueCounts]);
14621
14779
  const initializeBugBear = useCallback(async (bugBearClient) => {
14622
14780
  setIsLoading(true);
14623
14781
  try {
@@ -14703,6 +14861,10 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
14703
14861
  setClient(newClient);
14704
14862
  (async () => {
14705
14863
  try {
14864
+ const cachedSession = await newClient.getCachedSession();
14865
+ if (cachedSession) {
14866
+ setActiveSession(cachedSession);
14867
+ }
14706
14868
  await initializeBugBear(newClient);
14707
14869
  if (newClient.monitor && config.monitoring) {
14708
14870
  const getCurrentRoute = () => contextCapture.getCurrentRoute();
@@ -14837,6 +14999,7 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
14837
14999
  // Issue tracking
14838
15000
  issueCounts,
14839
15001
  refreshIssueCounts,
15002
+ reopenReport,
14840
15003
  queuedCount,
14841
15004
  dashboardUrl: config.dashboardUrl,
14842
15005
  onError: config.onError
@@ -14854,7 +15017,7 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
14854
15017
  }
14855
15018
 
14856
15019
  // src/BugBearButton.tsx
14857
- import React21, { useState as useState16, useRef as useRef6, useMemo as useMemo16 } from "react";
15020
+ import React21, { useState as useState17, useRef as useRef6, useMemo as useMemo16 } from "react";
14858
15021
  import {
14859
15022
  View as View21,
14860
15023
  Text as Text19,
@@ -14868,7 +15031,7 @@ import {
14868
15031
  Platform as Platform5,
14869
15032
  PanResponder,
14870
15033
  Animated as Animated2,
14871
- ActivityIndicator as ActivityIndicator2,
15034
+ ActivityIndicator as ActivityIndicator3,
14872
15035
  Keyboard as Keyboard4
14873
15036
  } from "react-native";
14874
15037
 
@@ -15583,6 +15746,17 @@ function TestDetailScreen({ testId, nav }) {
15583
15746
  setShowSteps(true);
15584
15747
  setShowDetails(false);
15585
15748
  }, [displayedAssignment?.id]);
15749
+ useEffect4(() => {
15750
+ if (!client || !displayedAssignment || displayedAssignment.status !== "pending") return;
15751
+ let cancelled = false;
15752
+ (async () => {
15753
+ await client.updateAssignmentStatus(displayedAssignment.id, "in_progress");
15754
+ if (!cancelled) await refreshAssignments();
15755
+ })();
15756
+ return () => {
15757
+ cancelled = true;
15758
+ };
15759
+ }, [client, displayedAssignment?.id, displayedAssignment?.status, refreshAssignments]);
15586
15760
  useEffect4(() => {
15587
15761
  const active = displayedAssignment?.status === "in_progress" ? displayedAssignment : null;
15588
15762
  if (!active?.startedAt) {
@@ -15633,17 +15807,6 @@ function TestDetailScreen({ testId, nav }) {
15633
15807
  setIsSubmitting(false);
15634
15808
  }
15635
15809
  }, [client, displayedAssignment, refreshAssignments, nav, isSubmitting]);
15636
- const handleStart = useCallback2(async () => {
15637
- if (!client || !displayedAssignment || isSubmitting) return;
15638
- Keyboard.dismiss();
15639
- setIsSubmitting(true);
15640
- try {
15641
- await client.updateAssignmentStatus(displayedAssignment.id, "in_progress");
15642
- await refreshAssignments();
15643
- } finally {
15644
- setIsSubmitting(false);
15645
- }
15646
- }, [client, displayedAssignment, refreshAssignments, isSubmitting]);
15647
15810
  const handleReopen = useCallback2(async () => {
15648
15811
  if (!client || !displayedAssignment || isSubmitting) return;
15649
15812
  Keyboard.dismiss();
@@ -15789,15 +15952,7 @@ function TestDetailScreen({ testId, nav }) {
15789
15952
  disabled: isSubmitting
15790
15953
  },
15791
15954
  /* @__PURE__ */ React4.createElement(Text2, { style: styles5.passBtnText }, "Change to Pass")
15792
- ))) : displayedAssignment.status === "pending" ? /* @__PURE__ */ React4.createElement(
15793
- TouchableOpacity2,
15794
- {
15795
- style: [styles5.startBtn, isSubmitting && { opacity: 0.5 }],
15796
- onPress: handleStart,
15797
- disabled: isSubmitting
15798
- },
15799
- /* @__PURE__ */ React4.createElement(Text2, { style: styles5.startBtnText }, isSubmitting ? "Starting..." : displayedAssignment.isVerification ? "\u{1F50D} Start Verification" : "\u25B6 Start Test")
15800
- ) : /* @__PURE__ */ React4.createElement(View4, { style: styles5.actionButtons }, /* @__PURE__ */ React4.createElement(TouchableOpacity2, { style: [styles5.actionBtn, styles5.failBtn, isSubmitting && { opacity: 0.5 }], onPress: handleFail, disabled: isSubmitting }, /* @__PURE__ */ React4.createElement(Text2, { style: styles5.failBtnText }, isSubmitting ? displayedAssignment.isVerification ? "Reporting..." : "Failing..." : displayedAssignment.isVerification ? "\u2717 Still Broken" : "Fail")), /* @__PURE__ */ React4.createElement(TouchableOpacity2, { style: [styles5.actionBtn, styles5.skipBtn, isSubmitting && { opacity: 0.5 }], onPress: () => setShowSkipModal(true), disabled: isSubmitting }, /* @__PURE__ */ React4.createElement(Text2, { style: styles5.skipBtnText }, "Skip")), /* @__PURE__ */ React4.createElement(TouchableOpacity2, { style: [styles5.actionBtn, styles5.passBtn, isSubmitting && { opacity: 0.5 }], onPress: handlePass, disabled: isSubmitting }, /* @__PURE__ */ React4.createElement(Text2, { style: styles5.passBtnText }, isSubmitting ? displayedAssignment.isVerification ? "Verifying..." : "Passing..." : displayedAssignment.isVerification ? "\u2713 Fix Verified" : "Pass"))), /* @__PURE__ */ React4.createElement(Modal, { visible: showSkipModal, transparent: true, animationType: "fade" }, /* @__PURE__ */ React4.createElement(View4, { style: styles5.modalOverlay }, /* @__PURE__ */ React4.createElement(View4, { style: styles5.modalContent }, /* @__PURE__ */ React4.createElement(Text2, { style: styles5.modalTitle }, "Skip this test?"), /* @__PURE__ */ React4.createElement(Text2, { style: styles5.modalSubtitle }, "Select a reason:"), [
15955
+ ))) : /* @__PURE__ */ React4.createElement(View4, { style: styles5.actionButtons }, /* @__PURE__ */ React4.createElement(TouchableOpacity2, { style: [styles5.actionBtn, styles5.failBtn, isSubmitting && { opacity: 0.5 }], onPress: handleFail, disabled: isSubmitting }, /* @__PURE__ */ React4.createElement(Text2, { style: styles5.failBtnText }, isSubmitting ? displayedAssignment.isVerification ? "Reporting..." : "Failing..." : displayedAssignment.isVerification ? "\u2717 Still Broken" : "Fail")), /* @__PURE__ */ React4.createElement(TouchableOpacity2, { style: [styles5.actionBtn, styles5.skipBtn, isSubmitting && { opacity: 0.5 }], onPress: () => setShowSkipModal(true), disabled: isSubmitting }, /* @__PURE__ */ React4.createElement(Text2, { style: styles5.skipBtnText }, "Skip")), /* @__PURE__ */ React4.createElement(TouchableOpacity2, { style: [styles5.actionBtn, styles5.passBtn, isSubmitting && { opacity: 0.5 }], onPress: handlePass, disabled: isSubmitting }, /* @__PURE__ */ React4.createElement(Text2, { style: styles5.passBtnText }, isSubmitting ? displayedAssignment.isVerification ? "Verifying..." : "Passing..." : displayedAssignment.isVerification ? "\u2713 Fix Verified" : "Pass"))), /* @__PURE__ */ React4.createElement(Modal, { visible: showSkipModal, transparent: true, animationType: "fade" }, /* @__PURE__ */ React4.createElement(View4, { style: styles5.modalOverlay }, /* @__PURE__ */ React4.createElement(View4, { style: styles5.modalContent }, /* @__PURE__ */ React4.createElement(Text2, { style: styles5.modalTitle }, "Skip this test?"), /* @__PURE__ */ React4.createElement(Text2, { style: styles5.modalSubtitle }, "Select a reason:"), [
15801
15956
  { reason: "blocked", label: "\u{1F6AB} Blocked by a bug" },
15802
15957
  { reason: "not_ready", label: "\u{1F6A7} Feature not ready" },
15803
15958
  { reason: "dependency", label: "\u{1F517} Needs another test first" },
@@ -15911,9 +16066,6 @@ function createStyles2() {
15911
16066
  completedLabel: { fontSize: 13, fontWeight: "600", color: colors.textSecondary },
15912
16067
  reopenBtn: { backgroundColor: colors.card, borderWidth: 1, borderColor: colors.blue },
15913
16068
  reopenBtnText: { fontSize: 14, fontWeight: "600", color: colors.blue },
15914
- // Start Test button
15915
- startBtn: { paddingVertical: 16, borderRadius: 12, alignItems: "center", backgroundColor: colors.blueSurface, borderWidth: 1, borderColor: colors.blueAccent, marginTop: 8 },
15916
- startBtnText: { fontSize: 16, fontWeight: "600", color: colors.blueText },
15917
16069
  // Action buttons
15918
16070
  actionButtons: { flexDirection: "row", gap: 10, marginTop: 8 },
15919
16071
  actionBtn: { flex: 1, paddingVertical: 14, borderRadius: 12, alignItems: "center" },
@@ -16026,17 +16178,23 @@ function TestListScreen({ nav }) {
16026
16178
  const keyMatch = a.testCase.testKey.toLowerCase().includes(q);
16027
16179
  if (!titleMatch && !keyMatch) return false;
16028
16180
  }
16029
- if (filter === "pending") return a.status === "pending" || a.status === "in_progress";
16030
- if (filter === "done") return a.status === "passed";
16031
- if (filter === "reopened") return a.status === "failed";
16181
+ if (filter === "todo") {
16182
+ return !a.isVerification && (a.status === "pending" || a.status === "in_progress" || a.status === "failed");
16183
+ }
16184
+ if (filter === "retest") {
16185
+ return !!a.isVerification && (a.status === "pending" || a.status === "in_progress");
16186
+ }
16187
+ if (filter === "done") {
16188
+ return a.status === "passed" || a.status === "skipped" || a.status === "blocked";
16189
+ }
16032
16190
  return true;
16033
16191
  }, [platformFilter, roleFilter, trackFilter, searchQuery, filter]);
16034
16192
  if (isLoading) return /* @__PURE__ */ React5.createElement(TestListScreenSkeleton, null);
16035
16193
  return /* @__PURE__ */ React5.createElement(View5, null, /* @__PURE__ */ React5.createElement(View5, { style: styles5.filterBar }, [
16036
16194
  { key: "all", label: "All", count: assignments.length },
16037
- { key: "pending", label: "To Do", count: assignments.filter((a) => a.status === "pending" || a.status === "in_progress").length },
16038
- { key: "done", label: "Done", count: assignments.filter((a) => a.status === "passed").length },
16039
- { key: "reopened", label: "Re Opened", count: assignments.filter((a) => a.status === "failed").length }
16195
+ { key: "todo", label: "To Do", count: assignments.filter((a) => !a.isVerification && (a.status === "pending" || a.status === "in_progress" || a.status === "failed")).length },
16196
+ { key: "retest", label: "Retest", count: assignments.filter((a) => !!a.isVerification && (a.status === "pending" || a.status === "in_progress")).length },
16197
+ { key: "done", label: "Done", count: assignments.filter((a) => a.status === "passed" || a.status === "skipped" || a.status === "blocked").length }
16040
16198
  ].map((f) => /* @__PURE__ */ React5.createElement(TouchableOpacity3, { key: f.key, style: [styles5.filterBtn, filter === f.key && styles5.filterBtnActive], onPress: () => setFilter(f.key) }, /* @__PURE__ */ React5.createElement(Text3, { style: [styles5.filterBtnText, filter === f.key && styles5.filterBtnTextActive] }, f.label, " (", f.count, ")")))), availableRoles.length >= 2 && /* @__PURE__ */ React5.createElement(View5, { style: styles5.roleSection }, /* @__PURE__ */ React5.createElement(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: styles5.roleBar }, /* @__PURE__ */ React5.createElement(
16041
16199
  TouchableOpacity3,
16042
16200
  {
@@ -16139,7 +16297,13 @@ function TestListScreen({ nav }) {
16139
16297
  onPress: () => nav.push({ name: "TEST_DETAIL", testId: assignment.id })
16140
16298
  },
16141
16299
  /* @__PURE__ */ React5.createElement(Text3, { style: styles5.testBadge }, badge.icon),
16142
- /* @__PURE__ */ React5.createElement(View5, { style: styles5.testInfo }, /* @__PURE__ */ React5.createElement(Text3, { style: styles5.testTitle, numberOfLines: 1 }, assignment.testCase.title), /* @__PURE__ */ React5.createElement(View5, { style: styles5.testMetaRow }, assignment.isVerification && /* @__PURE__ */ React5.createElement(View5, { style: styles5.retestTag }, /* @__PURE__ */ React5.createElement(Text3, { style: styles5.retestTagText }, "Retest")), /* @__PURE__ */ React5.createElement(Text3, { style: styles5.testMeta }, assignment.testCase.testKey, " \xB7 ", assignment.testCase.priority), assignment.testCase.role && /* @__PURE__ */ React5.createElement(View5, { style: styles5.roleBadgeRow }, /* @__PURE__ */ React5.createElement(Text3, { style: styles5.testMeta }, " \xB7 "), /* @__PURE__ */ React5.createElement(View5, { style: [styles5.roleBadgeDot, { backgroundColor: assignment.testCase.role.color }] }), /* @__PURE__ */ React5.createElement(Text3, { style: [styles5.testMeta, { color: assignment.testCase.role.color, fontWeight: "500" }] }, assignment.testCase.role.name)))),
16300
+ /* @__PURE__ */ React5.createElement(View5, { style: styles5.testInfo }, /* @__PURE__ */ React5.createElement(Text3, { style: styles5.testTitle, numberOfLines: 1 }, assignment.testCase.title), /* @__PURE__ */ React5.createElement(View5, { style: styles5.testMetaRow }, assignment.isVerification && /* @__PURE__ */ React5.createElement(View5, { style: [
16301
+ styles5.retestTag,
16302
+ assignment.status === "passed" && { backgroundColor: colors.greenDark, borderColor: colors.greenBorder }
16303
+ ] }, /* @__PURE__ */ React5.createElement(Text3, { style: [
16304
+ styles5.retestTagText,
16305
+ assignment.status === "passed" && { color: colors.greenLight }
16306
+ ] }, assignment.status === "passed" ? "Verified" : "Retest")), /* @__PURE__ */ React5.createElement(Text3, { style: styles5.testMeta }, assignment.testCase.testKey, " \xB7 ", assignment.testCase.priority), assignment.testCase.role && /* @__PURE__ */ React5.createElement(View5, { style: styles5.roleBadgeRow }, /* @__PURE__ */ React5.createElement(Text3, { style: styles5.testMeta }, " \xB7 "), /* @__PURE__ */ React5.createElement(View5, { style: [styles5.roleBadgeDot, { backgroundColor: assignment.testCase.role.color }] }), /* @__PURE__ */ React5.createElement(Text3, { style: [styles5.testMeta, { color: assignment.testCase.role.color, fontWeight: "500" }] }, assignment.testCase.role.name)))),
16143
16307
  /* @__PURE__ */ React5.createElement(View5, { style: [
16144
16308
  styles5.statusPill,
16145
16309
  {
@@ -17653,11 +17817,17 @@ function createStyles11() {
17653
17817
  }
17654
17818
 
17655
17819
  // src/widget/screens/IssueDetailScreen.tsx
17656
- import React17, { useMemo as useMemo12 } from "react";
17657
- import { View as View17, Text as Text15, Image as Image3, StyleSheet as StyleSheet17, Linking as Linking4, TouchableOpacity as TouchableOpacity14 } from "react-native";
17820
+ import React17, { useMemo as useMemo12, useState as useState13, useCallback as useCallback5 } from "react";
17821
+ import { View as View17, Text as Text15, Image as Image3, StyleSheet as StyleSheet17, Linking as Linking4, TouchableOpacity as TouchableOpacity14, TextInput as TextInput8, ActivityIndicator as ActivityIndicator2 } from "react-native";
17822
+ var DONE_STATUSES = ["verified", "resolved", "closed", "reviewed"];
17658
17823
  function IssueDetailScreen({ nav, issue }) {
17659
- const { dashboardUrl, widgetColorScheme } = useBugBear();
17824
+ const { dashboardUrl, widgetColorScheme, reopenReport } = useBugBear();
17660
17825
  const styles5 = useMemo12(() => createStyles12(), [widgetColorScheme]);
17826
+ const [showReopenForm, setShowReopenForm] = useState13(false);
17827
+ const [reopenReason, setReopenReason] = useState13("");
17828
+ const [isSubmitting, setIsSubmitting] = useState13(false);
17829
+ const [reopenError, setReopenError] = useState13(null);
17830
+ const [wasReopened, setWasReopened] = useState13(false);
17661
17831
  const STATUS_LABELS = useMemo12(() => ({
17662
17832
  new: { label: "New", bg: colors.blueDark, color: colors.blueLight },
17663
17833
  triaging: { label: "Triaging", bg: colors.blueDark, color: colors.blueLight },
@@ -17680,7 +17850,68 @@ function IssueDetailScreen({ nav, issue }) {
17680
17850
  }), [widgetColorScheme]);
17681
17851
  const statusConfig = STATUS_LABELS[issue.status] || { label: issue.status, bg: colors.card, color: colors.textSecondary };
17682
17852
  const severityConfig = issue.severity ? SEVERITY_CONFIG[issue.severity] : null;
17683
- return /* @__PURE__ */ React17.createElement(View17, null, /* @__PURE__ */ React17.createElement(View17, { style: styles5.badgeRow }, /* @__PURE__ */ React17.createElement(View17, { style: [styles5.badge, { backgroundColor: statusConfig.bg }] }, /* @__PURE__ */ React17.createElement(Text15, { style: [styles5.badgeText, { color: statusConfig.color }] }, statusConfig.label)), severityConfig && /* @__PURE__ */ React17.createElement(View17, { style: [styles5.badge, { backgroundColor: severityConfig.bg }] }, /* @__PURE__ */ React17.createElement(Text15, { style: [styles5.badgeText, { color: severityConfig.color }] }, severityConfig.label))), /* @__PURE__ */ React17.createElement(Text15, { style: styles5.title }, issue.title), issue.route && /* @__PURE__ */ React17.createElement(Text15, { style: styles5.route }, issue.route), issue.description && /* @__PURE__ */ React17.createElement(View17, { style: styles5.descriptionCard }, /* @__PURE__ */ React17.createElement(Text15, { style: styles5.descriptionText }, issue.description)), issue.verifiedByName && /* @__PURE__ */ React17.createElement(View17, { style: styles5.verifiedCard }, /* @__PURE__ */ React17.createElement(View17, { style: styles5.verifiedHeader }, /* @__PURE__ */ React17.createElement(Text15, { style: styles5.verifiedIcon }, "\u2705"), /* @__PURE__ */ React17.createElement(Text15, { style: styles5.verifiedTitle }, "Retesting Proof")), /* @__PURE__ */ React17.createElement(Text15, { style: styles5.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(View17, { style: styles5.originalBugCard }, /* @__PURE__ */ React17.createElement(View17, { style: styles5.originalBugHeader }, /* @__PURE__ */ React17.createElement(Text15, { style: styles5.originalBugIcon }, "\u{1F504}"), /* @__PURE__ */ React17.createElement(Text15, { style: styles5.originalBugTitle }, "Original Bug")), /* @__PURE__ */ React17.createElement(Text15, { style: styles5.originalBugBody }, "Retest of: ", issue.originalBugTitle)), issue.screenshotUrls && issue.screenshotUrls.length > 0 && /* @__PURE__ */ React17.createElement(View17, { style: styles5.screenshotSection }, /* @__PURE__ */ React17.createElement(Text15, { style: styles5.screenshotLabel }, "Screenshots (", issue.screenshotUrls.length, ")"), /* @__PURE__ */ React17.createElement(View17, { style: styles5.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: styles5.screenshotThumb }))))), /* @__PURE__ */ React17.createElement(View17, { style: styles5.metaSection }, issue.reporterName && /* @__PURE__ */ React17.createElement(Text15, { style: styles5.metaText }, "Reported by ", issue.reporterName), /* @__PURE__ */ React17.createElement(Text15, { style: styles5.metaTextSmall }, "Created ", formatRelativeTime(issue.createdAt), " ", "\xB7", " Updated ", formatRelativeTime(issue.updatedAt))), dashboardUrl && /* @__PURE__ */ React17.createElement(
17853
+ const isDone = DONE_STATUSES.includes(issue.status);
17854
+ const handleReopen = useCallback5(async () => {
17855
+ if (!reopenReason.trim()) return;
17856
+ setIsSubmitting(true);
17857
+ setReopenError(null);
17858
+ const result = await reopenReport(issue.id, reopenReason.trim());
17859
+ setIsSubmitting(false);
17860
+ if (result.success) {
17861
+ setWasReopened(true);
17862
+ setShowReopenForm(false);
17863
+ } else {
17864
+ setReopenError(result.error || "Failed to reopen");
17865
+ }
17866
+ }, [reopenReason, reopenReport, issue.id]);
17867
+ return /* @__PURE__ */ React17.createElement(View17, null, /* @__PURE__ */ React17.createElement(View17, { style: styles5.badgeRow }, /* @__PURE__ */ React17.createElement(View17, { style: [styles5.badge, { backgroundColor: wasReopened ? colors.yellowDark : statusConfig.bg }] }, /* @__PURE__ */ React17.createElement(Text15, { style: [styles5.badgeText, { color: wasReopened ? colors.yellowLight : statusConfig.color }] }, wasReopened ? "Reopened" : statusConfig.label)), severityConfig && /* @__PURE__ */ React17.createElement(View17, { style: [styles5.badge, { backgroundColor: severityConfig.bg }] }, /* @__PURE__ */ React17.createElement(Text15, { style: [styles5.badgeText, { color: severityConfig.color }] }, severityConfig.label))), /* @__PURE__ */ React17.createElement(Text15, { style: styles5.title }, issue.title), issue.route && /* @__PURE__ */ React17.createElement(Text15, { style: styles5.route }, issue.route), issue.description && /* @__PURE__ */ React17.createElement(View17, { style: styles5.descriptionCard }, /* @__PURE__ */ React17.createElement(Text15, { style: styles5.descriptionText }, issue.description)), wasReopened && /* @__PURE__ */ React17.createElement(View17, { style: styles5.reopenedCard }, /* @__PURE__ */ React17.createElement(View17, { style: styles5.reopenedRow }, /* @__PURE__ */ React17.createElement(Text15, { style: styles5.reopenedIcon }, "\u{1F504}"), /* @__PURE__ */ React17.createElement(Text15, { style: styles5.reopenedText }, "Issue reopened \u2014 your team has been notified"))), issue.verifiedByName && !wasReopened && /* @__PURE__ */ React17.createElement(View17, { style: styles5.verifiedCard }, /* @__PURE__ */ React17.createElement(View17, { style: styles5.verifiedHeader }, /* @__PURE__ */ React17.createElement(Text15, { style: styles5.verifiedIcon }, "\u2705"), /* @__PURE__ */ React17.createElement(Text15, { style: styles5.verifiedTitle }, "Retesting Proof")), /* @__PURE__ */ React17.createElement(Text15, { style: styles5.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(View17, { style: styles5.originalBugCard }, /* @__PURE__ */ React17.createElement(View17, { style: styles5.originalBugHeader }, /* @__PURE__ */ React17.createElement(Text15, { style: styles5.originalBugIcon }, "\u{1F504}"), /* @__PURE__ */ React17.createElement(Text15, { style: styles5.originalBugTitleText }, "Original Bug")), /* @__PURE__ */ React17.createElement(Text15, { style: styles5.originalBugBody }, "Retest of: ", issue.originalBugTitle)), isDone && !wasReopened && !showReopenForm && /* @__PURE__ */ React17.createElement(
17868
+ TouchableOpacity14,
17869
+ {
17870
+ style: styles5.reopenButton,
17871
+ onPress: () => setShowReopenForm(true),
17872
+ activeOpacity: 0.7
17873
+ },
17874
+ /* @__PURE__ */ React17.createElement(Text15, { style: styles5.reopenButtonText }, "\u{1F504}", " Not Fixed \u2014 Reopen Issue")
17875
+ ), showReopenForm && !wasReopened && /* @__PURE__ */ React17.createElement(View17, { style: styles5.reopenForm }, /* @__PURE__ */ React17.createElement(Text15, { style: styles5.reopenFormTitle }, "Why isn't this fixed?"), /* @__PURE__ */ React17.createElement(
17876
+ TextInput8,
17877
+ {
17878
+ value: reopenReason,
17879
+ onChangeText: setReopenReason,
17880
+ placeholder: "Describe what you're still seeing...",
17881
+ placeholderTextColor: colors.textMuted,
17882
+ style: styles5.reopenInput,
17883
+ multiline: true,
17884
+ autoFocus: true
17885
+ }
17886
+ ), reopenError && /* @__PURE__ */ React17.createElement(Text15, { style: styles5.reopenErrorText }, reopenError), /* @__PURE__ */ React17.createElement(View17, { style: styles5.reopenActions }, /* @__PURE__ */ React17.createElement(
17887
+ TouchableOpacity14,
17888
+ {
17889
+ style: [
17890
+ styles5.reopenSubmitButton,
17891
+ (!reopenReason.trim() || isSubmitting) && styles5.reopenSubmitDisabled
17892
+ ],
17893
+ onPress: handleReopen,
17894
+ disabled: isSubmitting || !reopenReason.trim(),
17895
+ activeOpacity: 0.7
17896
+ },
17897
+ isSubmitting ? /* @__PURE__ */ React17.createElement(ActivityIndicator2, { size: "small", color: "#fff" }) : /* @__PURE__ */ React17.createElement(Text15, { style: [
17898
+ styles5.reopenSubmitText,
17899
+ !reopenReason.trim() && styles5.reopenSubmitTextDisabled
17900
+ ] }, "Reopen Issue")
17901
+ ), /* @__PURE__ */ React17.createElement(
17902
+ TouchableOpacity14,
17903
+ {
17904
+ style: styles5.reopenCancelButton,
17905
+ onPress: () => {
17906
+ setShowReopenForm(false);
17907
+ setReopenReason("");
17908
+ setReopenError(null);
17909
+ },
17910
+ disabled: isSubmitting,
17911
+ activeOpacity: 0.7
17912
+ },
17913
+ /* @__PURE__ */ React17.createElement(Text15, { style: styles5.reopenCancelText }, "Cancel")
17914
+ ))), issue.screenshotUrls && issue.screenshotUrls.length > 0 && /* @__PURE__ */ React17.createElement(View17, { style: styles5.screenshotSection }, /* @__PURE__ */ React17.createElement(Text15, { style: styles5.screenshotLabel }, "Screenshots (", issue.screenshotUrls.length, ")"), /* @__PURE__ */ React17.createElement(View17, { style: styles5.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: styles5.screenshotThumb }))))), /* @__PURE__ */ React17.createElement(View17, { style: styles5.metaSection }, issue.reporterName && /* @__PURE__ */ React17.createElement(Text15, { style: styles5.metaText }, "Reported by ", issue.reporterName), /* @__PURE__ */ React17.createElement(Text15, { style: styles5.metaTextSmall }, "Created ", formatRelativeTime(issue.createdAt), " ", "\xB7", " Updated ", formatRelativeTime(issue.updatedAt))), dashboardUrl && /* @__PURE__ */ React17.createElement(
17684
17915
  TouchableOpacity14,
17685
17916
  {
17686
17917
  style: styles5.dashboardLink,
@@ -17732,6 +17963,28 @@ function createStyles12() {
17732
17963
  color: colors.textSecondary,
17733
17964
  lineHeight: 19
17734
17965
  },
17966
+ reopenedCard: {
17967
+ backgroundColor: colors.yellowDark,
17968
+ borderWidth: 1,
17969
+ borderColor: colors.yellowBorder,
17970
+ borderRadius: 8,
17971
+ padding: 12,
17972
+ marginBottom: 12
17973
+ },
17974
+ reopenedRow: {
17975
+ flexDirection: "row",
17976
+ alignItems: "center",
17977
+ gap: 8
17978
+ },
17979
+ reopenedIcon: {
17980
+ fontSize: 14
17981
+ },
17982
+ reopenedText: {
17983
+ fontSize: 13,
17984
+ fontWeight: "600",
17985
+ color: colors.yellowLight,
17986
+ flex: 1
17987
+ },
17735
17988
  verifiedCard: {
17736
17989
  backgroundColor: colors.greenDark,
17737
17990
  borderWidth: 1,
@@ -17775,7 +18028,7 @@ function createStyles12() {
17775
18028
  originalBugIcon: {
17776
18029
  fontSize: 16
17777
18030
  },
17778
- originalBugTitle: {
18031
+ originalBugTitleText: {
17779
18032
  fontSize: 13,
17780
18033
  fontWeight: "600",
17781
18034
  color: colors.yellowLight
@@ -17784,6 +18037,89 @@ function createStyles12() {
17784
18037
  fontSize: 12,
17785
18038
  color: colors.yellowSubtle
17786
18039
  },
18040
+ reopenButton: {
18041
+ borderWidth: 1,
18042
+ borderColor: colors.orange,
18043
+ borderRadius: 8,
18044
+ paddingVertical: 10,
18045
+ paddingHorizontal: 16,
18046
+ alignItems: "center",
18047
+ marginBottom: 12
18048
+ },
18049
+ reopenButtonText: {
18050
+ fontSize: 13,
18051
+ fontWeight: "600",
18052
+ color: colors.orange
18053
+ },
18054
+ reopenForm: {
18055
+ backgroundColor: colors.card,
18056
+ borderWidth: 1,
18057
+ borderColor: colors.orange,
18058
+ borderRadius: 8,
18059
+ padding: 12,
18060
+ marginBottom: 12
18061
+ },
18062
+ reopenFormTitle: {
18063
+ fontSize: 13,
18064
+ fontWeight: "600",
18065
+ color: colors.orange,
18066
+ marginBottom: 8
18067
+ },
18068
+ reopenInput: {
18069
+ backgroundColor: colors.bg,
18070
+ borderWidth: 1,
18071
+ borderColor: colors.border,
18072
+ borderRadius: 6,
18073
+ padding: 8,
18074
+ color: colors.textPrimary,
18075
+ fontSize: 13,
18076
+ lineHeight: 18,
18077
+ minHeight: 60,
18078
+ textAlignVertical: "top"
18079
+ },
18080
+ reopenErrorText: {
18081
+ fontSize: 12,
18082
+ color: colors.red,
18083
+ marginTop: 6
18084
+ },
18085
+ reopenActions: {
18086
+ flexDirection: "row",
18087
+ gap: 8,
18088
+ marginTop: 8
18089
+ },
18090
+ reopenSubmitButton: {
18091
+ flex: 1,
18092
+ backgroundColor: colors.orange,
18093
+ borderRadius: 6,
18094
+ paddingVertical: 8,
18095
+ paddingHorizontal: 12,
18096
+ alignItems: "center",
18097
+ justifyContent: "center"
18098
+ },
18099
+ reopenSubmitDisabled: {
18100
+ backgroundColor: colors.card,
18101
+ opacity: 0.7
18102
+ },
18103
+ reopenSubmitText: {
18104
+ fontSize: 13,
18105
+ fontWeight: "600",
18106
+ color: "#fff"
18107
+ },
18108
+ reopenSubmitTextDisabled: {
18109
+ color: colors.textMuted
18110
+ },
18111
+ reopenCancelButton: {
18112
+ borderWidth: 1,
18113
+ borderColor: colors.border,
18114
+ borderRadius: 6,
18115
+ paddingVertical: 8,
18116
+ paddingHorizontal: 12,
18117
+ alignItems: "center"
18118
+ },
18119
+ reopenCancelText: {
18120
+ fontSize: 13,
18121
+ color: colors.textSecondary
18122
+ },
17787
18123
  screenshotSection: {
17788
18124
  marginBottom: 12
17789
18125
  },
@@ -17835,13 +18171,13 @@ function createStyles12() {
17835
18171
  }
17836
18172
 
17837
18173
  // src/widget/screens/SessionStartScreen.tsx
17838
- import React18, { useState as useState13, useMemo as useMemo13 } from "react";
17839
- import { View as View18, Text as Text16, TextInput as TextInput8, TouchableOpacity as TouchableOpacity15, StyleSheet as StyleSheet18, Keyboard as Keyboard2 } from "react-native";
18174
+ import React18, { useState as useState14, useMemo as useMemo13 } from "react";
18175
+ import { View as View18, Text as Text16, TextInput as TextInput9, TouchableOpacity as TouchableOpacity15, StyleSheet as StyleSheet18, Keyboard as Keyboard2 } from "react-native";
17840
18176
  function SessionStartScreen({ nav }) {
17841
18177
  const { startSession, assignments, widgetColorScheme } = useBugBear();
17842
18178
  const styles5 = useMemo13(() => createStyles13(), [widgetColorScheme]);
17843
- const [focusArea, setFocusArea] = useState13("");
17844
- const [isStarting, setIsStarting] = useState13(false);
18179
+ const [focusArea, setFocusArea] = useState14("");
18180
+ const [isStarting, setIsStarting] = useState14(false);
17845
18181
  const trackNames = Array.from(new Set(
17846
18182
  assignments.filter((a) => a.testCase.track?.name).map((a) => a.testCase.track.name)
17847
18183
  )).slice(0, 6);
@@ -17861,7 +18197,7 @@ function SessionStartScreen({ nav }) {
17861
18197
  }
17862
18198
  };
17863
18199
  return /* @__PURE__ */ React18.createElement(View18, null, /* @__PURE__ */ React18.createElement(View18, { style: styles5.header }, /* @__PURE__ */ React18.createElement(Text16, { style: styles5.headerIcon }, "\u{1F50D}"), /* @__PURE__ */ React18.createElement(Text16, { style: styles5.headerDesc }, "Start an exploratory QA session. Log findings as you go \u2014 bugs, concerns, suggestions, or questions.")), /* @__PURE__ */ React18.createElement(View18, { style: styles5.inputSection }, /* @__PURE__ */ React18.createElement(Text16, { style: styles5.label }, "What are you testing?"), /* @__PURE__ */ React18.createElement(
17864
- TextInput8,
18200
+ TextInput9,
17865
18201
  {
17866
18202
  value: focusArea,
17867
18203
  onChangeText: setFocusArea,
@@ -17979,7 +18315,7 @@ function createStyles13() {
17979
18315
  }
17980
18316
 
17981
18317
  // src/widget/screens/SessionActiveScreen.tsx
17982
- import React19, { useState as useState14, useEffect as useEffect11, useRef as useRef5, useMemo as useMemo14 } from "react";
18318
+ import React19, { useState as useState15, useEffect as useEffect11, useRef as useRef5, useMemo as useMemo14 } from "react";
17983
18319
  import { View as View19, Text as Text17, TouchableOpacity as TouchableOpacity16, StyleSheet as StyleSheet19 } from "react-native";
17984
18320
  function SessionActiveScreen({ nav }) {
17985
18321
  const { activeSession, sessionFindings, endSession, refreshSession, widgetColorScheme } = useBugBear();
@@ -17990,8 +18326,8 @@ function SessionActiveScreen({ nav }) {
17990
18326
  suggestion: { icon: "\u{1F4A1}", label: "Suggestion", color: colors.blue },
17991
18327
  question: { icon: "\u2753", label: "Question", color: colors.violet }
17992
18328
  }), [widgetColorScheme]);
17993
- const [isEnding, setIsEnding] = useState14(false);
17994
- const [elapsed, setElapsed] = useState14(0);
18329
+ const [isEnding, setIsEnding] = useState15(false);
18330
+ const [elapsed, setElapsed] = useState15(0);
17995
18331
  const timerRef = useRef5(null);
17996
18332
  useEffect11(() => {
17997
18333
  refreshSession();
@@ -18216,8 +18552,8 @@ function createStyles14() {
18216
18552
  }
18217
18553
 
18218
18554
  // src/widget/screens/SessionFindingScreen.tsx
18219
- import React20, { useState as useState15, useMemo as useMemo15 } from "react";
18220
- import { View as View20, Text as Text18, TextInput as TextInput9, TouchableOpacity as TouchableOpacity17, StyleSheet as StyleSheet20, Keyboard as Keyboard3 } from "react-native";
18555
+ import React20, { useState as useState16, useMemo as useMemo15 } from "react";
18556
+ import { View as View20, Text as Text18, TextInput as TextInput10, TouchableOpacity as TouchableOpacity17, StyleSheet as StyleSheet20, Keyboard as Keyboard3 } from "react-native";
18221
18557
  var FINDING_TYPES = [
18222
18558
  { value: "bug", icon: "\u{1F41B}", label: "Bug" },
18223
18559
  { value: "concern", icon: "\u26A0\uFE0F", label: "Concern" },
@@ -18234,11 +18570,11 @@ function SessionFindingScreen({ nav }) {
18234
18570
  { value: "low", label: "Low", color: colors.textMuted },
18235
18571
  { value: "observation", label: "Note", color: colors.textDim }
18236
18572
  ], [widgetColorScheme]);
18237
- const [type, setType] = useState15("bug");
18238
- const [severity, setSeverity] = useState15("medium");
18239
- const [title, setTitle] = useState15("");
18240
- const [description, setDescription] = useState15("");
18241
- const [isSubmitting, setIsSubmitting] = useState15(false);
18573
+ const [type, setType] = useState16("bug");
18574
+ const [severity, setSeverity] = useState16("medium");
18575
+ const [title, setTitle] = useState16("");
18576
+ const [description, setDescription] = useState16("");
18577
+ const [isSubmitting, setIsSubmitting] = useState16(false);
18242
18578
  const handleSubmit = async () => {
18243
18579
  if (!title.trim() || isSubmitting) return;
18244
18580
  Keyboard3.dismiss();
@@ -18281,7 +18617,7 @@ function SessionFindingScreen({ nav }) {
18281
18617
  },
18282
18618
  /* @__PURE__ */ React20.createElement(Text18, { style: [styles5.severityText, severity === s2.value && { color: s2.color }] }, s2.label)
18283
18619
  )))), /* @__PURE__ */ React20.createElement(View20, { style: styles5.inputSection }, /* @__PURE__ */ React20.createElement(
18284
- TextInput9,
18620
+ TextInput10,
18285
18621
  {
18286
18622
  value: title,
18287
18623
  onChangeText: setTitle,
@@ -18291,7 +18627,7 @@ function SessionFindingScreen({ nav }) {
18291
18627
  returnKeyType: "next"
18292
18628
  }
18293
18629
  )), /* @__PURE__ */ React20.createElement(View20, { style: styles5.inputSection }, /* @__PURE__ */ React20.createElement(
18294
- TextInput9,
18630
+ TextInput10,
18295
18631
  {
18296
18632
  value: description,
18297
18633
  onChangeText: setDescription,
@@ -18436,7 +18772,7 @@ function BugBearButton({
18436
18772
  }) {
18437
18773
  const { shouldShowWidget, testerInfo, isLoading, unreadCount, assignments, widgetMode, widgetColorScheme } = useBugBear();
18438
18774
  const { currentScreen, canGoBack, push, pop, replace, reset } = useNavigation();
18439
- const [modalVisible, setModalVisible] = useState16(false);
18775
+ const [modalVisible, setModalVisible] = useState17(false);
18440
18776
  const styles5 = useMemo16(() => createStyles16(), [widgetColorScheme]);
18441
18777
  const screenCaptureRef = useRef6(null);
18442
18778
  const openModal = () => {
@@ -18651,7 +18987,7 @@ function BugBearButton({
18651
18987
  keyboardShouldPersistTaps: "handled",
18652
18988
  showsVerticalScrollIndicator: false
18653
18989
  },
18654
- isLoading ? /* @__PURE__ */ React21.createElement(View21, { style: styles5.loadingContainer }, /* @__PURE__ */ React21.createElement(ActivityIndicator2, { size: "large", color: colors.blue }), /* @__PURE__ */ React21.createElement(Text19, { style: styles5.loadingText }, "Loading...")) : renderScreen()
18990
+ isLoading ? /* @__PURE__ */ React21.createElement(View21, { style: styles5.loadingContainer }, /* @__PURE__ */ React21.createElement(ActivityIndicator3, { size: "large", color: colors.blue }), /* @__PURE__ */ React21.createElement(Text19, { style: styles5.loadingText }, "Loading...")) : renderScreen()
18655
18991
  ))
18656
18992
  )
18657
18993
  ));