@bbearai/react-native 0.5.6 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -9,14 +9,14 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
9
9
  import React, { createContext, useContext, useEffect, useState, useCallback, useRef } from "react";
10
10
 
11
11
  // ../../node_modules/tslib/tslib.es6.mjs
12
- function __rest(s, e) {
12
+ function __rest(s2, e) {
13
13
  var t = {};
14
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
15
- t[p] = s[p];
16
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
17
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
18
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
19
- t[p[i]] = s[p[i]];
14
+ for (var p in s2) if (Object.prototype.hasOwnProperty.call(s2, p) && e.indexOf(p) < 0)
15
+ t[p] = s2[p];
16
+ if (s2 != null && typeof Object.getOwnPropertySymbols === "function")
17
+ for (var i = 0, p = Object.getOwnPropertySymbols(s2); i < p.length; i++) {
18
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s2, p[i]))
19
+ t[p[i]] = s2[p[i]];
20
20
  }
21
21
  return t;
22
22
  }
@@ -831,9 +831,9 @@ var PostgrestFilterBuilder = class extends PostgrestTransformBuilder {
831
831
  * @param values - The values array to filter with
832
832
  */
833
833
  in(column, values) {
834
- const cleanedValues = Array.from(new Set(values)).map((s) => {
835
- if (typeof s === "string" && PostgrestReservedCharsRegexp.test(s)) return `"${s}"`;
836
- else return `${s}`;
834
+ const cleanedValues = Array.from(new Set(values)).map((s2) => {
835
+ if (typeof s2 === "string" && PostgrestReservedCharsRegexp.test(s2)) return `"${s2}"`;
836
+ else return `${s2}`;
837
837
  }).join(",");
838
838
  this.url.searchParams.append(column, `in.(${cleanedValues})`);
839
839
  return this;
@@ -845,9 +845,9 @@ var PostgrestFilterBuilder = class extends PostgrestTransformBuilder {
845
845
  * @param values - The values array to filter with
846
846
  */
847
847
  notIn(column, values) {
848
- const cleanedValues = Array.from(new Set(values)).map((s) => {
849
- if (typeof s === "string" && PostgrestReservedCharsRegexp.test(s)) return `"${s}"`;
850
- else return `${s}`;
848
+ const cleanedValues = Array.from(new Set(values)).map((s2) => {
849
+ if (typeof s2 === "string" && PostgrestReservedCharsRegexp.test(s2)) return `"${s2}"`;
850
+ else return `${s2}`;
851
851
  }).join(",");
852
852
  this.url.searchParams.append(column, `not.in.(${cleanedValues})`);
853
853
  return this;
@@ -11602,31 +11602,306 @@ function captureError(error, errorInfo) {
11602
11602
  componentStack: errorInfo?.componentStack
11603
11603
  };
11604
11604
  }
11605
+ var LocalStorageAdapter = class {
11606
+ constructor() {
11607
+ this.fallback = /* @__PURE__ */ new Map();
11608
+ }
11609
+ get isAvailable() {
11610
+ try {
11611
+ const key = "__bugbear_test__";
11612
+ localStorage.setItem(key, "1");
11613
+ localStorage.removeItem(key);
11614
+ return true;
11615
+ } catch {
11616
+ return false;
11617
+ }
11618
+ }
11619
+ async getItem(key) {
11620
+ if (this.isAvailable) return localStorage.getItem(key);
11621
+ return this.fallback.get(key) ?? null;
11622
+ }
11623
+ async setItem(key, value) {
11624
+ if (this.isAvailable) {
11625
+ localStorage.setItem(key, value);
11626
+ } else {
11627
+ this.fallback.set(key, value);
11628
+ }
11629
+ }
11630
+ async removeItem(key) {
11631
+ if (this.isAvailable) {
11632
+ localStorage.removeItem(key);
11633
+ } else {
11634
+ this.fallback.delete(key);
11635
+ }
11636
+ }
11637
+ };
11638
+ var OfflineQueue = class {
11639
+ constructor(config) {
11640
+ this.items = [];
11641
+ this.storageKey = "bugbear_offline_queue";
11642
+ this.flushing = false;
11643
+ this.handlers = /* @__PURE__ */ new Map();
11644
+ this.maxItems = config.maxItems ?? 50;
11645
+ this.maxRetries = config.maxRetries ?? 5;
11646
+ this.storage = config.storage ?? new LocalStorageAdapter();
11647
+ }
11648
+ // ── Flush handler registration ──────────────────────────────
11649
+ /** Register a handler that replays a queued operation. */
11650
+ registerHandler(type, handler) {
11651
+ this.handlers.set(type, handler);
11652
+ }
11653
+ // ── Change listener ─────────────────────────────────────────
11654
+ /** Subscribe to queue count changes (for UI badges). */
11655
+ onChange(callback) {
11656
+ this.listener = callback;
11657
+ }
11658
+ notify() {
11659
+ this.listener?.(this.items.length);
11660
+ }
11661
+ // ── Persistence ─────────────────────────────────────────────
11662
+ /** Load queue from persistent storage. Call once after construction. */
11663
+ async load() {
11664
+ try {
11665
+ const raw = await this.storage.getItem(this.storageKey);
11666
+ if (raw) {
11667
+ this.items = JSON.parse(raw);
11668
+ }
11669
+ } catch {
11670
+ this.items = [];
11671
+ }
11672
+ this.notify();
11673
+ }
11674
+ async save() {
11675
+ try {
11676
+ await this.storage.setItem(this.storageKey, JSON.stringify(this.items));
11677
+ } catch (err) {
11678
+ console.error("BugBear: Failed to persist offline queue", err);
11679
+ }
11680
+ this.notify();
11681
+ }
11682
+ // ── Enqueue ─────────────────────────────────────────────────
11683
+ /** Add a failed operation to the queue. Returns the item ID. */
11684
+ async enqueue(type, payload) {
11685
+ if (this.items.length >= this.maxItems) {
11686
+ this.items.shift();
11687
+ }
11688
+ const id = `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
11689
+ this.items.push({ id, type, payload, createdAt: Date.now(), retries: 0 });
11690
+ await this.save();
11691
+ return id;
11692
+ }
11693
+ // ── Accessors ───────────────────────────────────────────────
11694
+ /** Number of items waiting to be flushed. */
11695
+ get count() {
11696
+ return this.items.length;
11697
+ }
11698
+ /** Read-only snapshot of pending items. */
11699
+ get pending() {
11700
+ return [...this.items];
11701
+ }
11702
+ /** Whether a flush is currently in progress. */
11703
+ get isFlushing() {
11704
+ return this.flushing;
11705
+ }
11706
+ // ── Flush ───────────────────────────────────────────────────
11707
+ /**
11708
+ * Process all queued items in FIFO order.
11709
+ * Stops early if a network error is encountered (still offline).
11710
+ */
11711
+ async flush() {
11712
+ if (this.flushing || this.items.length === 0) {
11713
+ return { flushed: 0, failed: 0 };
11714
+ }
11715
+ this.flushing = true;
11716
+ let flushed = 0;
11717
+ let failed = 0;
11718
+ const snapshot = [...this.items];
11719
+ for (const item of snapshot) {
11720
+ const handler = this.handlers.get(item.type);
11721
+ if (!handler) {
11722
+ failed++;
11723
+ continue;
11724
+ }
11725
+ try {
11726
+ const result = await handler(item.payload);
11727
+ if (result.success) {
11728
+ this.items = this.items.filter((i) => i.id !== item.id);
11729
+ flushed++;
11730
+ } else if (isNetworkError(result.error)) {
11731
+ break;
11732
+ } else {
11733
+ const idx = this.items.findIndex((i) => i.id === item.id);
11734
+ if (idx !== -1) {
11735
+ this.items[idx].retries++;
11736
+ if (this.items[idx].retries >= this.maxRetries) {
11737
+ this.items.splice(idx, 1);
11738
+ }
11739
+ }
11740
+ failed++;
11741
+ }
11742
+ } catch {
11743
+ break;
11744
+ }
11745
+ }
11746
+ await this.save();
11747
+ this.flushing = false;
11748
+ return { flushed, failed };
11749
+ }
11750
+ // ── Clear ───────────────────────────────────────────────────
11751
+ /** Drop all queued items. */
11752
+ async clear() {
11753
+ this.items = [];
11754
+ await this.save();
11755
+ }
11756
+ };
11757
+ function isNetworkError(error) {
11758
+ if (!error) return false;
11759
+ const msg = error.toLowerCase();
11760
+ return msg.includes("failed to fetch") || msg.includes("networkerror") || msg.includes("network request failed") || msg.includes("timeout") || msg.includes("econnrefused") || msg.includes("enotfound") || msg.includes("load failed") || // Safari
11761
+ msg.includes("the internet connection appears to be offline") || msg.includes("a]server with the specified hostname could not be found");
11762
+ }
11605
11763
  var formatPgError = (e) => {
11606
11764
  if (!e || typeof e !== "object") return { raw: e };
11607
11765
  const { message, code, details, hint } = e;
11608
11766
  return { message, code, details, hint };
11609
11767
  };
11610
- var DEFAULT_SUPABASE_URL = "https://kyxgzjnqgvapvlnvqawz.supabase.co";
11611
- var getEnvVar = (key) => {
11612
- try {
11613
- if (typeof process !== "undefined" && process.env) {
11614
- return process.env[key];
11615
- }
11616
- } catch {
11617
- }
11618
- return void 0;
11619
- };
11620
- var HOSTED_BUGBEAR_ANON_KEY = getEnvVar("BUGBEAR_ANON_KEY") || getEnvVar("NEXT_PUBLIC_BUGBEAR_ANON_KEY") || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imt5eGd6am5xZ3ZhcHZsbnZxYXd6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjkyNjgwNDIsImV4cCI6MjA4NDg0NDA0Mn0.NUkAlCHLFjeRoisbmNUVoGb4R6uQ8xs5LAEIX1BWTwU";
11621
11768
  var BugBearClient = class {
11622
11769
  constructor(config) {
11623
11770
  this.navigationHistory = [];
11624
11771
  this.reportSubmitInFlight = false;
11772
+ this._queue = null;
11773
+ this.realtimeChannels = [];
11774
+ if (!config.supabaseUrl) {
11775
+ throw new Error("BugBear: supabaseUrl is required. Get it from your BugBear project settings.");
11776
+ }
11777
+ if (!config.supabaseAnonKey) {
11778
+ throw new Error("BugBear: supabaseAnonKey is required. Get it from your BugBear project settings.");
11779
+ }
11625
11780
  this.config = config;
11626
- this.supabase = createClient(
11627
- config.supabaseUrl || DEFAULT_SUPABASE_URL,
11628
- config.supabaseAnonKey || HOSTED_BUGBEAR_ANON_KEY
11629
- );
11781
+ this.supabase = createClient(config.supabaseUrl, config.supabaseAnonKey);
11782
+ if (config.offlineQueue?.enabled) {
11783
+ this._queue = new OfflineQueue({
11784
+ enabled: true,
11785
+ maxItems: config.offlineQueue.maxItems,
11786
+ maxRetries: config.offlineQueue.maxRetries
11787
+ });
11788
+ this.registerQueueHandlers();
11789
+ }
11790
+ }
11791
+ // ── Offline Queue ─────────────────────────────────────────
11792
+ /**
11793
+ * Access the offline queue (if enabled).
11794
+ * Use this to check queue.count, subscribe to changes, or trigger flush.
11795
+ */
11796
+ get queue() {
11797
+ return this._queue;
11798
+ }
11799
+ /**
11800
+ * Initialize the offline queue with a platform-specific storage adapter.
11801
+ * Must be called after construction for React Native (which supplies AsyncStorage).
11802
+ * Web callers can skip this — LocalStorageAdapter is the default.
11803
+ */
11804
+ async initQueue(storage) {
11805
+ if (!this._queue) return;
11806
+ if (storage) {
11807
+ this._queue = new OfflineQueue({
11808
+ enabled: true,
11809
+ maxItems: this.config.offlineQueue?.maxItems,
11810
+ maxRetries: this.config.offlineQueue?.maxRetries,
11811
+ storage
11812
+ });
11813
+ this.registerQueueHandlers();
11814
+ }
11815
+ await this._queue.load();
11816
+ }
11817
+ registerQueueHandlers() {
11818
+ if (!this._queue) return;
11819
+ this._queue.registerHandler("report", async (payload) => {
11820
+ const { error } = await this.supabase.from("reports").insert(payload).select("id").single();
11821
+ if (error) return { success: false, error: error.message };
11822
+ return { success: true };
11823
+ });
11824
+ this._queue.registerHandler("message", async (payload) => {
11825
+ const { error } = await this.supabase.from("discussion_messages").insert(payload);
11826
+ if (error) return { success: false, error: error.message };
11827
+ return { success: true };
11828
+ });
11829
+ this._queue.registerHandler("feedback", async (payload) => {
11830
+ const { error } = await this.supabase.from("test_feedback").insert(payload);
11831
+ if (error) return { success: false, error: error.message };
11832
+ return { success: true };
11833
+ });
11834
+ }
11835
+ // ── Realtime Subscriptions ─────────────────────────────────
11836
+ /** Whether realtime is enabled in config. */
11837
+ get realtimeEnabled() {
11838
+ return !!this.config.realtime?.enabled;
11839
+ }
11840
+ /**
11841
+ * Subscribe to postgres_changes on relevant tables.
11842
+ * Each callback fires when the corresponding table has changes —
11843
+ * the provider should call its refresh function in response.
11844
+ * Returns a cleanup function that unsubscribes all channels.
11845
+ */
11846
+ subscribeToChanges(callbacks) {
11847
+ this.unsubscribeAll();
11848
+ const projectId = this.config.projectId;
11849
+ const debounce = (fn, ms = 500) => {
11850
+ let timer;
11851
+ return () => {
11852
+ clearTimeout(timer);
11853
+ timer = setTimeout(fn, ms);
11854
+ };
11855
+ };
11856
+ if (callbacks.onAssignmentChange) {
11857
+ const debouncedCb = debounce(callbacks.onAssignmentChange);
11858
+ const channel = this.supabase.channel("bugbear-assignments").on("postgres_changes", {
11859
+ event: "*",
11860
+ schema: "public",
11861
+ table: "test_assignments",
11862
+ filter: `project_id=eq.${projectId}`
11863
+ }, debouncedCb).subscribe((status) => {
11864
+ if (status === "CHANNEL_ERROR") {
11865
+ console.warn("BugBear: Realtime subscription failed for test_assignments");
11866
+ }
11867
+ });
11868
+ this.realtimeChannels.push(channel);
11869
+ }
11870
+ if (callbacks.onMessageChange) {
11871
+ const debouncedCb = debounce(callbacks.onMessageChange);
11872
+ const channel = this.supabase.channel("bugbear-messages").on("postgres_changes", {
11873
+ event: "INSERT",
11874
+ schema: "public",
11875
+ table: "discussion_messages"
11876
+ }, debouncedCb).subscribe((status) => {
11877
+ if (status === "CHANNEL_ERROR") {
11878
+ console.warn("BugBear: Realtime subscription failed for discussion_messages");
11879
+ }
11880
+ });
11881
+ this.realtimeChannels.push(channel);
11882
+ }
11883
+ if (callbacks.onReportChange) {
11884
+ const debouncedCb = debounce(callbacks.onReportChange);
11885
+ const channel = this.supabase.channel("bugbear-reports").on("postgres_changes", {
11886
+ event: "UPDATE",
11887
+ schema: "public",
11888
+ table: "reports",
11889
+ filter: `project_id=eq.${projectId}`
11890
+ }, debouncedCb).subscribe((status) => {
11891
+ if (status === "CHANNEL_ERROR") {
11892
+ console.warn("BugBear: Realtime subscription failed for reports");
11893
+ }
11894
+ });
11895
+ this.realtimeChannels.push(channel);
11896
+ }
11897
+ return () => this.unsubscribeAll();
11898
+ }
11899
+ /** Remove all active Realtime channels. */
11900
+ unsubscribeAll() {
11901
+ for (const channel of this.realtimeChannels) {
11902
+ this.supabase.removeChannel(channel);
11903
+ }
11904
+ this.realtimeChannels = [];
11630
11905
  }
11631
11906
  /**
11632
11907
  * Track navigation for context.
@@ -11686,6 +11961,7 @@ var BugBearClient = class {
11686
11961
  return { success: false, error: "A report is already being submitted" };
11687
11962
  }
11688
11963
  this.reportSubmitInFlight = true;
11964
+ let fullReport;
11689
11965
  try {
11690
11966
  const validationError = this.validateReport(report);
11691
11967
  if (validationError) {
@@ -11702,7 +11978,7 @@ var BugBearClient = class {
11702
11978
  return { success: false, error: "User not authenticated" };
11703
11979
  }
11704
11980
  const testerInfo = await this.getTesterInfo();
11705
- const fullReport = {
11981
+ fullReport = {
11706
11982
  project_id: this.config.projectId,
11707
11983
  reporter_id: userInfo.id,
11708
11984
  // User ID from host app (required)
@@ -11729,6 +12005,10 @@ var BugBearClient = class {
11729
12005
  };
11730
12006
  const { data, error } = await this.supabase.from("reports").insert(fullReport).select("id").single();
11731
12007
  if (error) {
12008
+ if (this._queue && isNetworkError(error.message)) {
12009
+ await this._queue.enqueue("report", fullReport);
12010
+ return { success: false, queued: true, error: "Queued \u2014 will send when online" };
12011
+ }
11732
12012
  console.error("BugBear: Failed to submit report", error.message);
11733
12013
  return { success: false, error: error.message };
11734
12014
  }
@@ -11738,6 +12018,10 @@ var BugBearClient = class {
11738
12018
  return { success: true, reportId: data.id };
11739
12019
  } catch (err) {
11740
12020
  const message = err instanceof Error ? err.message : "Unknown error";
12021
+ if (this._queue && fullReport && isNetworkError(message)) {
12022
+ await this._queue.enqueue("report", fullReport);
12023
+ return { success: false, queued: true, error: "Queued \u2014 will send when online" };
12024
+ }
11741
12025
  return { success: false, error: message };
11742
12026
  } finally {
11743
12027
  this.reportSubmitInFlight = false;
@@ -11747,10 +12031,13 @@ var BugBearClient = class {
11747
12031
  * Get assigned tests for current user
11748
12032
  * First looks up the tester by email, then fetches their assignments
11749
12033
  */
11750
- async getAssignedTests() {
12034
+ async getAssignedTests(options) {
11751
12035
  try {
11752
12036
  const testerInfo = await this.getTesterInfo();
11753
12037
  if (!testerInfo) return [];
12038
+ const pageSize = Math.min(options?.pageSize ?? 100, 100);
12039
+ const from = (options?.page ?? 0) * pageSize;
12040
+ const to = from + pageSize - 1;
11754
12041
  const { data, error } = await this.supabase.from("test_assignments").select(`
11755
12042
  id,
11756
12043
  status,
@@ -11791,12 +12078,18 @@ var BugBearClient = class {
11791
12078
  login_hint
11792
12079
  )
11793
12080
  )
11794
- `).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true }).limit(100);
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);
11795
12082
  if (error) {
11796
12083
  console.error("BugBear: Failed to fetch assignments", formatPgError(error));
11797
12084
  return [];
11798
12085
  }
11799
- const mapped = (data || []).map((item) => ({
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) => ({
11800
12093
  id: item.id,
11801
12094
  status: item.status,
11802
12095
  startedAt: item.started_at,
@@ -12044,6 +12337,7 @@ var BugBearClient = class {
12044
12337
  * This empowers testers to shape better tests over time
12045
12338
  */
12046
12339
  async submitTestFeedback(options) {
12340
+ let feedbackPayload;
12047
12341
  try {
12048
12342
  const testerInfo = await this.getTesterInfo();
12049
12343
  if (!testerInfo) {
@@ -12053,7 +12347,17 @@ var BugBearClient = class {
12053
12347
  if (feedback.rating < 1 || feedback.rating > 5) {
12054
12348
  return { success: false, error: "Rating must be between 1 and 5" };
12055
12349
  }
12056
- const { error: feedbackError } = await this.supabase.from("test_feedback").insert({
12350
+ const optionalRatings = [
12351
+ { name: "clarityRating", value: feedback.clarityRating },
12352
+ { name: "stepsRating", value: feedback.stepsRating },
12353
+ { name: "relevanceRating", value: feedback.relevanceRating }
12354
+ ];
12355
+ for (const { name, value } of optionalRatings) {
12356
+ if (value !== void 0 && value !== null && (value < 1 || value > 5)) {
12357
+ return { success: false, error: `${name} must be between 1 and 5` };
12358
+ }
12359
+ }
12360
+ feedbackPayload = {
12057
12361
  project_id: this.config.projectId,
12058
12362
  test_case_id: testCaseId,
12059
12363
  assignment_id: assignmentId || null,
@@ -12071,8 +12375,13 @@ var BugBearClient = class {
12071
12375
  platform: this.getDeviceInfo().platform,
12072
12376
  time_to_complete_seconds: timeToCompleteSeconds || null,
12073
12377
  screenshot_urls: screenshotUrls || []
12074
- });
12378
+ };
12379
+ const { error: feedbackError } = await this.supabase.from("test_feedback").insert(feedbackPayload);
12075
12380
  if (feedbackError) {
12381
+ if (this._queue && isNetworkError(feedbackError.message)) {
12382
+ await this._queue.enqueue("feedback", feedbackPayload);
12383
+ return { success: false, queued: true, error: "Queued \u2014 will send when online" };
12384
+ }
12076
12385
  console.error("BugBear: Failed to submit feedback", feedbackError);
12077
12386
  return { success: false, error: feedbackError.message };
12078
12387
  }
@@ -12089,6 +12398,10 @@ var BugBearClient = class {
12089
12398
  return { success: true };
12090
12399
  } catch (err) {
12091
12400
  const message = err instanceof Error ? err.message : "Unknown error";
12401
+ if (this._queue && feedbackPayload && isNetworkError(message)) {
12402
+ await this._queue.enqueue("feedback", feedbackPayload);
12403
+ return { success: false, queued: true, error: "Queued \u2014 will send when online" };
12404
+ }
12092
12405
  console.error("BugBear: Error submitting feedback", err);
12093
12406
  return { success: false, error: message };
12094
12407
  }
@@ -12723,6 +13036,7 @@ var BugBearClient = class {
12723
13036
  * Send a message to a thread
12724
13037
  */
12725
13038
  async sendMessage(threadId, content, attachments) {
13039
+ let insertData;
12726
13040
  try {
12727
13041
  const testerInfo = await this.getTesterInfo();
12728
13042
  if (!testerInfo) {
@@ -12734,7 +13048,7 @@ var BugBearClient = class {
12734
13048
  console.error("BugBear: Rate limit exceeded for messages");
12735
13049
  return false;
12736
13050
  }
12737
- const insertData = {
13051
+ insertData = {
12738
13052
  thread_id: threadId,
12739
13053
  sender_type: "tester",
12740
13054
  sender_tester_id: testerInfo.id,
@@ -12749,12 +13063,21 @@ var BugBearClient = class {
12749
13063
  }
12750
13064
  const { error } = await this.supabase.from("discussion_messages").insert(insertData);
12751
13065
  if (error) {
13066
+ if (this._queue && isNetworkError(error.message)) {
13067
+ await this._queue.enqueue("message", insertData);
13068
+ return false;
13069
+ }
12752
13070
  console.error("BugBear: Failed to send message", formatPgError(error));
12753
13071
  return false;
12754
13072
  }
12755
13073
  await this.markThreadAsRead(threadId);
12756
13074
  return true;
12757
13075
  } catch (err) {
13076
+ const message = err instanceof Error ? err.message : "Unknown error";
13077
+ if (this._queue && insertData && isNetworkError(message)) {
13078
+ await this._queue.enqueue("message", insertData);
13079
+ return false;
13080
+ }
12758
13081
  console.error("BugBear: Error sending message", err);
12759
13082
  return false;
12760
13083
  }
@@ -12941,7 +13264,7 @@ var BugBearClient = class {
12941
13264
  console.error("BugBear: Failed to fetch session history", formatPgError(error));
12942
13265
  return [];
12943
13266
  }
12944
- return (data || []).map((s) => this.transformSession(s));
13267
+ return (data || []).map((s2) => this.transformSession(s2));
12945
13268
  } catch (err) {
12946
13269
  console.error("BugBear: Error fetching session history", err);
12947
13270
  return [];
@@ -13089,7 +13412,7 @@ function createBugBear(config) {
13089
13412
  }
13090
13413
 
13091
13414
  // src/BugBearProvider.tsx
13092
- import { Platform, Dimensions } from "react-native";
13415
+ import { Platform, Dimensions, AppState } from "react-native";
13093
13416
  var BugBearContext = createContext({
13094
13417
  client: null,
13095
13418
  isTester: false,
@@ -13130,6 +13453,7 @@ var BugBearContext = createContext({
13130
13453
  issueCounts: { open: 0, done: 0, reopened: 0 },
13131
13454
  refreshIssueCounts: async () => {
13132
13455
  },
13456
+ queuedCount: 0,
13133
13457
  dashboardUrl: void 0,
13134
13458
  onError: void 0
13135
13459
  });
@@ -13146,6 +13470,7 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
13146
13470
  const [threads, setThreads] = useState([]);
13147
13471
  const [unreadCount, setUnreadCount] = useState(0);
13148
13472
  const [issueCounts, setIssueCounts] = useState({ open: 0, done: 0, reopened: 0 });
13473
+ const [queuedCount, setQueuedCount] = useState(0);
13149
13474
  const [activeSession, setActiveSession] = useState(null);
13150
13475
  const [sessionFindings, setSessionFindings] = useState([]);
13151
13476
  const hasInitialized = useRef(false);
@@ -13305,18 +13630,46 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
13305
13630
  hasInitialized.current = true;
13306
13631
  contextCapture.startCapture();
13307
13632
  const newClient = createBugBear(config);
13633
+ if (newClient.queue) {
13634
+ newClient.queue.onChange(setQueuedCount);
13635
+ newClient.initQueue();
13636
+ }
13308
13637
  setClient(newClient);
13309
13638
  initializeBugBear(newClient);
13310
13639
  }
13311
13640
  }, [enabled, config, initializeBugBear]);
13641
+ useEffect(() => {
13642
+ if (!client?.queue) return;
13643
+ const subscription = AppState.addEventListener("change", (state) => {
13644
+ if (state === "active" && client.queue && client.queue.count > 0) {
13645
+ client.queue.flush();
13646
+ }
13647
+ });
13648
+ if (client.queue.count > 0) {
13649
+ client.queue.flush();
13650
+ }
13651
+ return () => subscription.remove();
13652
+ }, [client]);
13312
13653
  useEffect(() => {
13313
13654
  if (!client || !isTester || !isQAEnabled) return;
13655
+ let unsubscribe;
13656
+ if (client.realtimeEnabled) {
13657
+ unsubscribe = client.subscribeToChanges({
13658
+ onAssignmentChange: refreshAssignments,
13659
+ onMessageChange: refreshThreads,
13660
+ onReportChange: refreshIssueCounts
13661
+ });
13662
+ }
13663
+ const pollInterval = client.realtimeEnabled ? 12e4 : 3e4;
13314
13664
  const interval = setInterval(() => {
13315
13665
  refreshThreads();
13316
13666
  refreshIssueCounts();
13317
- }, 3e4);
13318
- return () => clearInterval(interval);
13319
- }, [client, isTester, isQAEnabled, refreshThreads, refreshIssueCounts]);
13667
+ }, pollInterval);
13668
+ return () => {
13669
+ clearInterval(interval);
13670
+ unsubscribe?.();
13671
+ };
13672
+ }, [client, isTester, isQAEnabled, refreshThreads, refreshIssueCounts, refreshAssignments]);
13320
13673
  const currentAssignment = assignments.find(
13321
13674
  (a) => a.status === "in_progress"
13322
13675
  ) || assignments.find(
@@ -13359,6 +13712,7 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
13359
13712
  // Issue tracking
13360
13713
  issueCounts,
13361
13714
  refreshIssueCounts,
13715
+ queuedCount,
13362
13716
  dashboardUrl: config.dashboardUrl,
13363
13717
  onError: config.onError
13364
13718
  }
@@ -13368,21 +13722,21 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
13368
13722
  }
13369
13723
 
13370
13724
  // src/BugBearButton.tsx
13371
- import React17, { useState as useState12, useRef as useRef3 } from "react";
13725
+ import React18, { useState as useState12, useRef as useRef4 } from "react";
13372
13726
  import {
13373
- View as View16,
13727
+ View as View17,
13374
13728
  Text as Text16,
13375
13729
  Image as Image4,
13376
13730
  TouchableOpacity as TouchableOpacity15,
13377
13731
  Modal as Modal3,
13378
13732
  ScrollView as ScrollView3,
13379
- StyleSheet as StyleSheet17,
13733
+ StyleSheet as StyleSheet18,
13380
13734
  Dimensions as Dimensions2,
13381
13735
  KeyboardAvoidingView,
13382
13736
  Platform as Platform4,
13383
13737
  PanResponder,
13384
- Animated,
13385
- ActivityIndicator as ActivityIndicator3,
13738
+ Animated as Animated2,
13739
+ ActivityIndicator as ActivityIndicator2,
13386
13740
  Keyboard as Keyboard2
13387
13741
  } from "react-native";
13388
13742
 
@@ -13528,9 +13882,9 @@ var shared = StyleSheet.create({
13528
13882
  function formatElapsedTime(seconds) {
13529
13883
  const h = Math.floor(seconds / 3600);
13530
13884
  const m = Math.floor(seconds % 3600 / 60);
13531
- const s = seconds % 60;
13532
- if (h > 0) return `${h}:${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`;
13533
- return `${m}:${s.toString().padStart(2, "0")}`;
13885
+ const s2 = seconds % 60;
13886
+ if (h > 0) return `${h}:${m.toString().padStart(2, "0")}:${s2.toString().padStart(2, "0")}`;
13887
+ return `${m}:${s2.toString().padStart(2, "0")}`;
13534
13888
  }
13535
13889
  function formatRelativeTime(dateString) {
13536
13890
  const date = new Date(dateString);
@@ -13602,11 +13956,90 @@ var templateInfo = {
13602
13956
  };
13603
13957
 
13604
13958
  // src/widget/screens/HomeScreen.tsx
13605
- import React2, { useEffect as useEffect2 } from "react";
13606
- import { View, Text, TouchableOpacity, StyleSheet as StyleSheet2, Linking } from "react-native";
13607
- function HomeScreen({ nav }) {
13608
- const { assignments, unreadCount, threads, refreshAssignments, refreshThreads, issueCounts, refreshIssueCounts, dashboardUrl } = useBugBear();
13959
+ import React3, { useEffect as useEffect3 } from "react";
13960
+ import { View as View2, Text, TouchableOpacity, StyleSheet as StyleSheet3, Linking } from "react-native";
13961
+
13962
+ // src/widget/Skeleton.tsx
13963
+ import React2, { useEffect as useEffect2, useRef as useRef2 } from "react";
13964
+ import { View, Animated, StyleSheet as StyleSheet2 } from "react-native";
13965
+ function usePulse(delay = 0) {
13966
+ const opacity = useRef2(new Animated.Value(0.6)).current;
13609
13967
  useEffect2(() => {
13968
+ const timeout = setTimeout(() => {
13969
+ Animated.loop(
13970
+ Animated.sequence([
13971
+ Animated.timing(opacity, { toValue: 0.25, duration: 750, useNativeDriver: true }),
13972
+ Animated.timing(opacity, { toValue: 0.6, duration: 750, useNativeDriver: true })
13973
+ ])
13974
+ ).start();
13975
+ }, delay);
13976
+ return () => clearTimeout(timeout);
13977
+ }, [opacity, delay]);
13978
+ return opacity;
13979
+ }
13980
+ function Bar({ width = "100%", height = 12, radius = 6, delay = 0 }) {
13981
+ const opacity = usePulse(delay);
13982
+ return /* @__PURE__ */ React2.createElement(
13983
+ Animated.View,
13984
+ {
13985
+ style: {
13986
+ width,
13987
+ height,
13988
+ borderRadius: radius,
13989
+ backgroundColor: colors.border,
13990
+ opacity
13991
+ }
13992
+ }
13993
+ );
13994
+ }
13995
+ function Circle({ size = 20, delay = 0 }) {
13996
+ const opacity = usePulse(delay);
13997
+ return /* @__PURE__ */ React2.createElement(
13998
+ Animated.View,
13999
+ {
14000
+ style: {
14001
+ width: size,
14002
+ height: size,
14003
+ borderRadius: size / 2,
14004
+ backgroundColor: colors.border,
14005
+ opacity
14006
+ }
14007
+ }
14008
+ );
14009
+ }
14010
+ function HomeScreenSkeleton() {
14011
+ return /* @__PURE__ */ React2.createElement(View, null, /* @__PURE__ */ React2.createElement(Bar, { width: "100%", height: 100, radius: 16 }), /* @__PURE__ */ React2.createElement(View, { style: { height: 20 } }), /* @__PURE__ */ React2.createElement(View, { style: s.actionGrid }, [0, 1, 2, 3].map((i) => /* @__PURE__ */ React2.createElement(View, { key: i, style: s.actionCard }, /* @__PURE__ */ React2.createElement(Circle, { size: 28, delay: i * 80 }), /* @__PURE__ */ React2.createElement(View, { style: { height: 8 } }), /* @__PURE__ */ React2.createElement(Bar, { width: 60, height: 10, delay: i * 80 })))), /* @__PURE__ */ React2.createElement(View, { style: s.issueGrid }, [0, 1, 2].map((i) => /* @__PURE__ */ React2.createElement(View, { key: i, style: s.issueCard }, /* @__PURE__ */ React2.createElement(Bar, { width: 30, height: 18, delay: i * 100 }), /* @__PURE__ */ React2.createElement(View, { style: { height: 6 } }), /* @__PURE__ */ React2.createElement(Bar, { width: 40, height: 8, delay: i * 100 })))), /* @__PURE__ */ React2.createElement(Bar, { width: "100%", height: 6, radius: 3 }), /* @__PURE__ */ React2.createElement(View, { style: { height: 8 } }), /* @__PURE__ */ React2.createElement(View, { style: { alignItems: "center" } }, /* @__PURE__ */ React2.createElement(Bar, { width: 120, height: 10 })));
14012
+ }
14013
+ function TestItemSkeleton({ delay = 0 }) {
14014
+ return /* @__PURE__ */ React2.createElement(View, { style: s.testItem }, /* @__PURE__ */ React2.createElement(Circle, { size: 18, delay }), /* @__PURE__ */ React2.createElement(View, { style: { flex: 1, marginLeft: 10 } }, /* @__PURE__ */ React2.createElement(Bar, { width: "70%", height: 11, delay }), /* @__PURE__ */ React2.createElement(View, { style: { height: 4 } }), /* @__PURE__ */ React2.createElement(Bar, { width: "45%", height: 8, delay })), /* @__PURE__ */ React2.createElement(Bar, { width: 50, height: 18, radius: 6, delay }));
14015
+ }
14016
+ function TestListScreenSkeleton() {
14017
+ return /* @__PURE__ */ React2.createElement(View, null, /* @__PURE__ */ React2.createElement(View, { style: s.filterRow }, [55, 50, 50, 70].map((w, i) => /* @__PURE__ */ React2.createElement(Bar, { key: i, width: w, height: 28, radius: 8, delay: i * 50 }))), /* @__PURE__ */ React2.createElement(Bar, { width: "100%", height: 36, radius: 8 }), /* @__PURE__ */ React2.createElement(View, { style: { height: 10 } }), [0, 1].map((g) => /* @__PURE__ */ React2.createElement(View, { key: g, style: { marginBottom: 12 } }, /* @__PURE__ */ React2.createElement(View, { style: s.folderHeader }, /* @__PURE__ */ React2.createElement(Bar, { width: 12, height: 10 }), /* @__PURE__ */ React2.createElement(Bar, { width: "40%", height: 12 }), /* @__PURE__ */ React2.createElement(View, { style: { flex: 1 } }), /* @__PURE__ */ React2.createElement(Bar, { width: 40, height: 4, radius: 2 }), /* @__PURE__ */ React2.createElement(Bar, { width: 24, height: 10 })), [0, 1, 2].map((i) => /* @__PURE__ */ React2.createElement(TestItemSkeleton, { key: i, delay: (g * 3 + i) * 80 })))));
14018
+ }
14019
+ function IssueListScreenSkeleton() {
14020
+ return /* @__PURE__ */ React2.createElement(View, null, [0, 1, 2, 3].map((i) => /* @__PURE__ */ React2.createElement(View, { key: i, style: s.issueRow }, /* @__PURE__ */ React2.createElement(View, { style: s.issueRowTop }, /* @__PURE__ */ React2.createElement(Circle, { size: 8, delay: i * 100 }), /* @__PURE__ */ React2.createElement(Bar, { width: "65%", height: 11, delay: i * 100 })), /* @__PURE__ */ React2.createElement(View, { style: s.issueRowBottom }, /* @__PURE__ */ React2.createElement(Bar, { width: "40%", height: 8, delay: i * 100 }), /* @__PURE__ */ React2.createElement(Bar, { width: 40, height: 8, delay: i * 100 })))));
14021
+ }
14022
+ function MessageListScreenSkeleton() {
14023
+ return /* @__PURE__ */ React2.createElement(View, null, /* @__PURE__ */ React2.createElement(Bar, { width: "100%", height: 44, radius: 12 }), /* @__PURE__ */ React2.createElement(View, { style: { height: 16 } }), [0, 1, 2, 3].map((i) => /* @__PURE__ */ React2.createElement(View, { key: i, style: s.threadRow }, /* @__PURE__ */ React2.createElement(Circle, { size: 20, delay: i * 100 }), /* @__PURE__ */ React2.createElement(View, { style: { flex: 1, marginLeft: 10 } }, /* @__PURE__ */ React2.createElement(Bar, { width: "55%", height: 11, delay: i * 100 }), /* @__PURE__ */ React2.createElement(View, { style: { height: 5 } }), /* @__PURE__ */ React2.createElement(Bar, { width: "80%", height: 9, delay: i * 100 })), /* @__PURE__ */ React2.createElement(Bar, { width: 30, height: 8, delay: i * 100 }))));
14024
+ }
14025
+ var s = StyleSheet2.create({
14026
+ actionGrid: { flexDirection: "row", flexWrap: "wrap", gap: 12, marginBottom: 20 },
14027
+ actionCard: { width: "47%", backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border, borderRadius: 12, padding: 16, alignItems: "center" },
14028
+ issueGrid: { flexDirection: "row", gap: 10, marginBottom: 20 },
14029
+ issueCard: { flex: 1, backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border, borderRadius: 10, paddingVertical: 12, paddingHorizontal: 8, alignItems: "center" },
14030
+ filterRow: { flexDirection: "row", gap: 8, marginBottom: 8 },
14031
+ folderHeader: { flexDirection: "row", alignItems: "center", gap: 8, paddingVertical: 8, paddingHorizontal: 4 },
14032
+ testItem: { flexDirection: "row", alignItems: "center", paddingVertical: 10, paddingHorizontal: 12, borderRadius: 8, marginBottom: 4, backgroundColor: colors.card },
14033
+ issueRow: { backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border, borderRadius: 10, padding: 14, marginBottom: 8 },
14034
+ issueRowTop: { flexDirection: "row", alignItems: "center", gap: 8 },
14035
+ issueRowBottom: { flexDirection: "row", justifyContent: "space-between", marginTop: 8 },
14036
+ threadRow: { flexDirection: "row", alignItems: "flex-start", padding: 12, borderRadius: 10, marginBottom: 4, backgroundColor: colors.card }
14037
+ });
14038
+
14039
+ // src/widget/screens/HomeScreen.tsx
14040
+ function HomeScreen({ nav }) {
14041
+ const { assignments, unreadCount, threads, refreshAssignments, refreshThreads, issueCounts, refreshIssueCounts, dashboardUrl, isLoading } = useBugBear();
14042
+ useEffect3(() => {
13610
14043
  refreshAssignments();
13611
14044
  refreshThreads();
13612
14045
  refreshIssueCounts();
@@ -13616,103 +14049,104 @@ function HomeScreen({ nav }) {
13616
14049
  const retestCount = pendingAssignments.filter((a) => a.isVerification).length;
13617
14050
  const completedCount = assignments.filter((a) => a.status === "passed" || a.status === "failed").length;
13618
14051
  const totalTests = assignments.length;
13619
- return /* @__PURE__ */ React2.createElement(View, null, pendingCount > 0 ? /* @__PURE__ */ React2.createElement(
14052
+ if (isLoading) return /* @__PURE__ */ React3.createElement(HomeScreenSkeleton, null);
14053
+ return /* @__PURE__ */ React3.createElement(View2, null, pendingCount > 0 ? /* @__PURE__ */ React3.createElement(
13620
14054
  TouchableOpacity,
13621
14055
  {
13622
14056
  style: [styles.heroBanner, styles.heroBannerTests],
13623
14057
  onPress: () => nav.push({ name: "TEST_DETAIL" }),
13624
14058
  activeOpacity: 0.8
13625
14059
  },
13626
- /* @__PURE__ */ React2.createElement(Text, { style: styles.heroCount }, pendingCount),
13627
- /* @__PURE__ */ React2.createElement(Text, { style: styles.heroLabel }, "test", pendingCount !== 1 ? "s" : "", " waiting"),
13628
- retestCount > 0 && /* @__PURE__ */ React2.createElement(View, { style: styles.retestPill }, /* @__PURE__ */ React2.createElement(Text, { style: styles.retestPillText }, "\u{1F504} ", retestCount, " retest", retestCount !== 1 ? "s" : "")),
13629
- /* @__PURE__ */ React2.createElement(Text, { style: styles.heroAction }, "Start Testing \u2192")
13630
- ) : unreadCount > 0 ? /* @__PURE__ */ React2.createElement(
14060
+ /* @__PURE__ */ React3.createElement(Text, { style: styles.heroCount }, pendingCount),
14061
+ /* @__PURE__ */ React3.createElement(Text, { style: styles.heroLabel }, "test", pendingCount !== 1 ? "s" : "", " waiting"),
14062
+ retestCount > 0 && /* @__PURE__ */ React3.createElement(View2, { style: styles.retestPill }, /* @__PURE__ */ React3.createElement(Text, { style: styles.retestPillText }, "\u{1F504} ", retestCount, " retest", retestCount !== 1 ? "s" : "")),
14063
+ /* @__PURE__ */ React3.createElement(Text, { style: styles.heroAction }, "Start Testing \u2192")
14064
+ ) : unreadCount > 0 ? /* @__PURE__ */ React3.createElement(
13631
14065
  TouchableOpacity,
13632
14066
  {
13633
14067
  style: [styles.heroBanner, styles.heroBannerMessages],
13634
14068
  onPress: () => nav.push({ name: "MESSAGE_LIST" }),
13635
14069
  activeOpacity: 0.8
13636
14070
  },
13637
- /* @__PURE__ */ React2.createElement(Text, { style: styles.heroCount }, unreadCount),
13638
- /* @__PURE__ */ React2.createElement(Text, { style: styles.heroLabel }, "unread message", unreadCount !== 1 ? "s" : ""),
13639
- /* @__PURE__ */ React2.createElement(Text, { style: styles.heroAction }, "View Messages \u2192")
13640
- ) : /* @__PURE__ */ React2.createElement(View, { style: [styles.heroBanner, styles.heroBannerClear] }, /* @__PURE__ */ React2.createElement(Text, { style: styles.heroClearEmoji }, "\u2705"), /* @__PURE__ */ React2.createElement(Text, { style: styles.heroClearTitle }, "All caught up!"), totalTests > 0 && /* @__PURE__ */ React2.createElement(Text, { style: styles.heroClearSub }, completedCount, "/", totalTests, " tests completed")), /* @__PURE__ */ React2.createElement(View, { style: styles.actionGrid }, /* @__PURE__ */ React2.createElement(
14071
+ /* @__PURE__ */ React3.createElement(Text, { style: styles.heroCount }, unreadCount),
14072
+ /* @__PURE__ */ React3.createElement(Text, { style: styles.heroLabel }, "unread message", unreadCount !== 1 ? "s" : ""),
14073
+ /* @__PURE__ */ React3.createElement(Text, { style: styles.heroAction }, "View Messages \u2192")
14074
+ ) : /* @__PURE__ */ React3.createElement(View2, { style: [styles.heroBanner, styles.heroBannerClear] }, /* @__PURE__ */ React3.createElement(Text, { style: styles.heroClearEmoji }, "\u2705"), /* @__PURE__ */ React3.createElement(Text, { style: styles.heroClearTitle }, "All caught up!"), totalTests > 0 && /* @__PURE__ */ React3.createElement(Text, { style: styles.heroClearSub }, completedCount, "/", totalTests, " tests completed")), /* @__PURE__ */ React3.createElement(View2, { style: styles.actionGrid }, /* @__PURE__ */ React3.createElement(
13641
14075
  TouchableOpacity,
13642
14076
  {
13643
14077
  style: styles.actionCard,
13644
14078
  onPress: () => nav.push({ name: "TEST_LIST" }),
13645
14079
  activeOpacity: 0.7
13646
14080
  },
13647
- /* @__PURE__ */ React2.createElement(Text, { style: styles.actionIcon }, "\u2705"),
13648
- /* @__PURE__ */ React2.createElement(Text, { style: styles.actionLabel }, "Tests"),
13649
- pendingCount > 0 && /* @__PURE__ */ React2.createElement(View, { style: styles.actionBadge }, /* @__PURE__ */ React2.createElement(Text, { style: styles.actionBadgeText }, pendingCount))
13650
- ), /* @__PURE__ */ React2.createElement(
14081
+ /* @__PURE__ */ React3.createElement(Text, { style: styles.actionIcon }, "\u2705"),
14082
+ /* @__PURE__ */ React3.createElement(Text, { style: styles.actionLabel }, "Tests"),
14083
+ pendingCount > 0 && /* @__PURE__ */ React3.createElement(View2, { style: styles.actionBadge }, /* @__PURE__ */ React3.createElement(Text, { style: styles.actionBadgeText }, pendingCount))
14084
+ ), /* @__PURE__ */ React3.createElement(
13651
14085
  TouchableOpacity,
13652
14086
  {
13653
14087
  style: styles.actionCard,
13654
14088
  onPress: () => nav.push({ name: "REPORT", prefill: { type: "bug" } }),
13655
14089
  activeOpacity: 0.7
13656
14090
  },
13657
- /* @__PURE__ */ React2.createElement(Text, { style: styles.actionIcon }, "\u{1F41B}"),
13658
- /* @__PURE__ */ React2.createElement(Text, { style: styles.actionLabel }, "Report Bug")
13659
- ), /* @__PURE__ */ React2.createElement(
14091
+ /* @__PURE__ */ React3.createElement(Text, { style: styles.actionIcon }, "\u{1F41B}"),
14092
+ /* @__PURE__ */ React3.createElement(Text, { style: styles.actionLabel }, "Report Bug")
14093
+ ), /* @__PURE__ */ React3.createElement(
13660
14094
  TouchableOpacity,
13661
14095
  {
13662
14096
  style: styles.actionCard,
13663
14097
  onPress: () => nav.push({ name: "REPORT", prefill: { type: "feedback" } }),
13664
14098
  activeOpacity: 0.7
13665
14099
  },
13666
- /* @__PURE__ */ React2.createElement(Text, { style: styles.actionIcon }, "\u{1F4A1}"),
13667
- /* @__PURE__ */ React2.createElement(Text, { style: styles.actionLabel }, "Feedback")
13668
- ), /* @__PURE__ */ React2.createElement(
14100
+ /* @__PURE__ */ React3.createElement(Text, { style: styles.actionIcon }, "\u{1F4A1}"),
14101
+ /* @__PURE__ */ React3.createElement(Text, { style: styles.actionLabel }, "Feedback")
14102
+ ), /* @__PURE__ */ React3.createElement(
13669
14103
  TouchableOpacity,
13670
14104
  {
13671
14105
  style: styles.actionCard,
13672
14106
  onPress: () => nav.push({ name: "MESSAGE_LIST" }),
13673
14107
  activeOpacity: 0.7
13674
14108
  },
13675
- /* @__PURE__ */ React2.createElement(Text, { style: styles.actionIcon }, "\u{1F4AC}"),
13676
- /* @__PURE__ */ React2.createElement(Text, { style: styles.actionLabel }, "Messages"),
13677
- unreadCount > 0 && /* @__PURE__ */ React2.createElement(View, { style: [styles.actionBadge, styles.actionBadgeMsg] }, /* @__PURE__ */ React2.createElement(Text, { style: styles.actionBadgeText }, unreadCount))
13678
- )), /* @__PURE__ */ React2.createElement(View, { style: styles.issueGrid }, /* @__PURE__ */ React2.createElement(
14109
+ /* @__PURE__ */ React3.createElement(Text, { style: styles.actionIcon }, "\u{1F4AC}"),
14110
+ /* @__PURE__ */ React3.createElement(Text, { style: styles.actionLabel }, "Messages"),
14111
+ unreadCount > 0 && /* @__PURE__ */ React3.createElement(View2, { style: [styles.actionBadge, styles.actionBadgeMsg] }, /* @__PURE__ */ React3.createElement(Text, { style: styles.actionBadgeText }, unreadCount))
14112
+ )), /* @__PURE__ */ React3.createElement(View2, { style: styles.issueGrid }, /* @__PURE__ */ React3.createElement(
13679
14113
  TouchableOpacity,
13680
14114
  {
13681
14115
  style: [styles.issueCard, styles.issueCardOpen],
13682
14116
  onPress: () => nav.push({ name: "ISSUE_LIST", category: "open" }),
13683
14117
  activeOpacity: 0.7
13684
14118
  },
13685
- /* @__PURE__ */ React2.createElement(Text, { style: styles.issueCountOpen }, issueCounts.open),
13686
- /* @__PURE__ */ React2.createElement(Text, { style: styles.issueLabel }, "Open")
13687
- ), /* @__PURE__ */ React2.createElement(
14119
+ /* @__PURE__ */ React3.createElement(Text, { style: styles.issueCountOpen }, issueCounts.open),
14120
+ /* @__PURE__ */ React3.createElement(Text, { style: styles.issueLabel }, "Open")
14121
+ ), /* @__PURE__ */ React3.createElement(
13688
14122
  TouchableOpacity,
13689
14123
  {
13690
14124
  style: [styles.issueCard, styles.issueCardDone],
13691
14125
  onPress: () => nav.push({ name: "ISSUE_LIST", category: "done" }),
13692
14126
  activeOpacity: 0.7
13693
14127
  },
13694
- /* @__PURE__ */ React2.createElement(Text, { style: styles.issueCountDone }, issueCounts.done),
13695
- /* @__PURE__ */ React2.createElement(Text, { style: styles.issueLabel }, "Done")
13696
- ), /* @__PURE__ */ React2.createElement(
14128
+ /* @__PURE__ */ React3.createElement(Text, { style: styles.issueCountDone }, issueCounts.done),
14129
+ /* @__PURE__ */ React3.createElement(Text, { style: styles.issueLabel }, "Done")
14130
+ ), /* @__PURE__ */ React3.createElement(
13697
14131
  TouchableOpacity,
13698
14132
  {
13699
14133
  style: [styles.issueCard, styles.issueCardReopened],
13700
14134
  onPress: () => nav.push({ name: "ISSUE_LIST", category: "reopened" }),
13701
14135
  activeOpacity: 0.7
13702
14136
  },
13703
- /* @__PURE__ */ React2.createElement(Text, { style: styles.issueCountReopened }, issueCounts.reopened),
13704
- /* @__PURE__ */ React2.createElement(Text, { style: styles.issueLabel }, "Reopened")
13705
- )), totalTests > 0 && /* @__PURE__ */ React2.createElement(View, { style: styles.progressSection }, /* @__PURE__ */ React2.createElement(View, { style: styles.progressBar }, /* @__PURE__ */ React2.createElement(View, { style: [styles.progressFill, { width: `${Math.round(completedCount / totalTests * 100)}%` }] })), /* @__PURE__ */ React2.createElement(Text, { style: styles.progressText }, completedCount, "/", totalTests, " tests completed")), dashboardUrl && /* @__PURE__ */ React2.createElement(
14137
+ /* @__PURE__ */ React3.createElement(Text, { style: styles.issueCountReopened }, issueCounts.reopened),
14138
+ /* @__PURE__ */ React3.createElement(Text, { style: styles.issueLabel }, "Reopened")
14139
+ )), totalTests > 0 && /* @__PURE__ */ React3.createElement(View2, { style: styles.progressSection }, /* @__PURE__ */ React3.createElement(View2, { style: styles.progressBar }, /* @__PURE__ */ React3.createElement(View2, { style: [styles.progressFill, { width: `${Math.round(completedCount / totalTests * 100)}%` }] })), /* @__PURE__ */ React3.createElement(Text, { style: styles.progressText }, completedCount, "/", totalTests, " tests completed")), dashboardUrl && /* @__PURE__ */ React3.createElement(
13706
14140
  TouchableOpacity,
13707
14141
  {
13708
14142
  style: styles.webAppLink,
13709
14143
  onPress: () => Linking.openURL(dashboardUrl),
13710
14144
  activeOpacity: 0.7
13711
14145
  },
13712
- /* @__PURE__ */ React2.createElement(Text, { style: styles.webAppIcon }, "\u{1F310}"),
13713
- /* @__PURE__ */ React2.createElement(View, { style: styles.webAppTextWrap }, /* @__PURE__ */ React2.createElement(Text, { style: styles.webAppTitle }, "Open Web Dashboard"), /* @__PURE__ */ React2.createElement(Text, { style: styles.webAppSub }, "View analytics, history & more")),
13714
- /* @__PURE__ */ React2.createElement(Text, { style: styles.webAppArrow }, "\u2192")
13715
- ), /* @__PURE__ */ React2.createElement(
14146
+ /* @__PURE__ */ React3.createElement(Text, { style: styles.webAppIcon }, "\u{1F310}"),
14147
+ /* @__PURE__ */ React3.createElement(View2, { style: styles.webAppTextWrap }, /* @__PURE__ */ React3.createElement(Text, { style: styles.webAppTitle }, "Open Web Dashboard"), /* @__PURE__ */ React3.createElement(Text, { style: styles.webAppSub }, "View analytics, history & more")),
14148
+ /* @__PURE__ */ React3.createElement(Text, { style: styles.webAppArrow }, "\u2192")
14149
+ ), /* @__PURE__ */ React3.createElement(
13716
14150
  TouchableOpacity,
13717
14151
  {
13718
14152
  style: styles.refreshButton,
@@ -13722,10 +14156,10 @@ function HomeScreen({ nav }) {
13722
14156
  refreshIssueCounts();
13723
14157
  }
13724
14158
  },
13725
- /* @__PURE__ */ React2.createElement(Text, { style: styles.refreshText }, "\u21BB Refresh")
14159
+ /* @__PURE__ */ React3.createElement(Text, { style: styles.refreshText }, "\u21BB Refresh")
13726
14160
  ));
13727
14161
  }
13728
- var styles = StyleSheet2.create({
14162
+ var styles = StyleSheet3.create({
13729
14163
  heroBanner: {
13730
14164
  borderRadius: 16,
13731
14165
  padding: 24,
@@ -13949,8 +14383,8 @@ var styles = StyleSheet2.create({
13949
14383
  });
13950
14384
 
13951
14385
  // src/widget/screens/TestDetailScreen.tsx
13952
- import React3, { useState as useState2, useEffect as useEffect3, useCallback as useCallback2 } from "react";
13953
- import { View as View2, Text as Text2, TouchableOpacity as TouchableOpacity2, StyleSheet as StyleSheet3, Modal, TextInput, Keyboard } from "react-native";
14386
+ import React4, { useState as useState2, useEffect as useEffect4, useCallback as useCallback2 } from "react";
14387
+ import { View as View3, Text as Text2, TouchableOpacity as TouchableOpacity2, StyleSheet as StyleSheet4, Modal, TextInput, Keyboard } from "react-native";
13954
14388
  function TestDetailScreen({ testId, nav }) {
13955
14389
  const { client, assignments, currentAssignment, refreshAssignments, getDeviceInfo, onNavigate } = useBugBear();
13956
14390
  const displayedAssignment = testId ? assignments.find((a) => a.id === testId) || currentAssignment : currentAssignment;
@@ -13963,12 +14397,12 @@ function TestDetailScreen({ testId, nav }) {
13963
14397
  const [skipNotes, setSkipNotes] = useState2("");
13964
14398
  const [skipping, setSkipping] = useState2(false);
13965
14399
  const [isSubmitting, setIsSubmitting] = useState2(false);
13966
- useEffect3(() => {
14400
+ useEffect4(() => {
13967
14401
  setCriteriaResults({});
13968
14402
  setShowSteps(true);
13969
14403
  setShowDetails(false);
13970
14404
  }, [displayedAssignment?.id]);
13971
- useEffect3(() => {
14405
+ useEffect4(() => {
13972
14406
  const active = displayedAssignment?.status === "in_progress" ? displayedAssignment : null;
13973
14407
  if (!active?.startedAt) {
13974
14408
  setAssignmentElapsedTime(0);
@@ -14044,14 +14478,14 @@ function TestDetailScreen({ testId, nav }) {
14044
14478
  }
14045
14479
  }, [client, displayedAssignment, selectedSkipReason, skipNotes, refreshAssignments, assignments, nav]);
14046
14480
  if (!displayedAssignment) {
14047
- return /* @__PURE__ */ React3.createElement(View2, { style: shared.emptyState }, /* @__PURE__ */ React3.createElement(Text2, { style: shared.emptyEmoji }, "\u2705"), /* @__PURE__ */ React3.createElement(Text2, { style: shared.emptyTitle }, "No tests assigned"), /* @__PURE__ */ React3.createElement(Text2, { style: shared.emptySubtitle }, "Check back later for new tests"), /* @__PURE__ */ React3.createElement(TouchableOpacity2, { style: [shared.primaryButton, { marginTop: 20, paddingHorizontal: 24 }], onPress: () => nav.reset() }, /* @__PURE__ */ React3.createElement(Text2, { style: shared.primaryButtonText }, "Go Home")));
14481
+ return /* @__PURE__ */ React4.createElement(View3, { style: shared.emptyState }, /* @__PURE__ */ React4.createElement(Text2, { style: shared.emptyEmoji }, "\u2705"), /* @__PURE__ */ React4.createElement(Text2, { style: shared.emptyTitle }, "No tests assigned"), /* @__PURE__ */ React4.createElement(Text2, { style: shared.emptySubtitle }, "Check back later for new tests"), /* @__PURE__ */ React4.createElement(TouchableOpacity2, { style: [shared.primaryButton, { marginTop: 20, paddingHorizontal: 24 }], onPress: () => nav.reset() }, /* @__PURE__ */ React4.createElement(Text2, { style: shared.primaryButtonText }, "Go Home")));
14048
14482
  }
14049
14483
  const testCase = displayedAssignment.testCase;
14050
14484
  const template = testCase.track?.testTemplate || "steps";
14051
14485
  const steps = testCase.steps;
14052
14486
  const info = templateInfo[template] || templateInfo.steps;
14053
14487
  const rubricMode = testCase.track?.rubricMode || "pass_fail";
14054
- return /* @__PURE__ */ React3.createElement(View2, { style: styles2.container }, /* @__PURE__ */ React3.createElement(View2, { style: styles2.topRow }, /* @__PURE__ */ React3.createElement(View2, { style: styles2.positionInfo }, /* @__PURE__ */ React3.createElement(Text2, { style: styles2.positionText }, "Test ", currentIndex + 1, " of ", allTests.length), displayedAssignment.status === "in_progress" && assignmentElapsedTime > 0 && /* @__PURE__ */ React3.createElement(View2, { style: styles2.timerBadge }, /* @__PURE__ */ React3.createElement(Text2, { style: styles2.timerText }, formatElapsedTime(assignmentElapsedTime)))), /* @__PURE__ */ React3.createElement(TouchableOpacity2, { onPress: () => nav.push({ name: "TEST_LIST" }) }, /* @__PURE__ */ React3.createElement(Text2, { style: styles2.viewAllLink }, "View All \u2192"))), displayedAssignment.isVerification && /* @__PURE__ */ React3.createElement(View2, { style: styles2.retestBanner }, /* @__PURE__ */ React3.createElement(Text2, { style: styles2.retestIcon }, "\u{1F504}"), /* @__PURE__ */ React3.createElement(Text2, { style: styles2.retestLabel }, "Retest"), /* @__PURE__ */ React3.createElement(Text2, { style: styles2.retestSub }, "\u2014 Verify bug fix")), /* @__PURE__ */ React3.createElement(Text2, { style: styles2.testTitle }, testCase.title), testCase.key && /* @__PURE__ */ React3.createElement(Text2, { style: styles2.testKey }, testCase.key), /* @__PURE__ */ React3.createElement(TouchableOpacity2, { onPress: () => setShowSteps(!showSteps), style: styles2.sectionHeader }, /* @__PURE__ */ React3.createElement(Text2, { style: styles2.sectionHeaderText }, showSteps ? "\u25BC" : "\u25B6", " ", info.icon, " ", template === "freeform" ? "Instructions" : `${steps.length} ${template === "checklist" ? "items" : template === "rubric" ? "criteria" : "steps"}`)), showSteps && /* @__PURE__ */ React3.createElement(View2, { style: styles2.templateContent }, template === "steps" && steps.map((step, idx) => /* @__PURE__ */ React3.createElement(View2, { key: idx, style: styles2.step }, /* @__PURE__ */ React3.createElement(View2, { style: styles2.stepNumber }, /* @__PURE__ */ React3.createElement(Text2, { style: styles2.stepNumberText }, step.stepNumber)), /* @__PURE__ */ React3.createElement(View2, { style: styles2.stepBody }, /* @__PURE__ */ React3.createElement(Text2, { style: styles2.stepAction }, step.action), step.expectedResult && /* @__PURE__ */ React3.createElement(Text2, { style: styles2.stepExpected }, "\u2192 ", step.expectedResult)))), template === "checklist" && /* @__PURE__ */ React3.createElement(React3.Fragment, null, steps.map((step, idx) => /* @__PURE__ */ React3.createElement(
14488
+ return /* @__PURE__ */ React4.createElement(View3, { style: styles2.container }, /* @__PURE__ */ React4.createElement(View3, { style: styles2.topRow }, /* @__PURE__ */ React4.createElement(View3, { style: styles2.positionInfo }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.positionText }, "Test ", currentIndex + 1, " of ", allTests.length), displayedAssignment.status === "in_progress" && assignmentElapsedTime > 0 && /* @__PURE__ */ React4.createElement(View3, { style: styles2.timerBadge }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.timerText }, formatElapsedTime(assignmentElapsedTime)))), /* @__PURE__ */ React4.createElement(TouchableOpacity2, { onPress: () => nav.push({ name: "TEST_LIST" }) }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.viewAllLink }, "View All \u2192"))), displayedAssignment.isVerification && /* @__PURE__ */ React4.createElement(View3, { style: styles2.retestBanner }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.retestIcon }, "\u{1F504}"), /* @__PURE__ */ React4.createElement(Text2, { style: styles2.retestLabel }, "Retest"), /* @__PURE__ */ React4.createElement(Text2, { style: styles2.retestSub }, "\u2014 Verify bug fix")), /* @__PURE__ */ React4.createElement(Text2, { style: styles2.testTitle }, testCase.title), testCase.key && /* @__PURE__ */ React4.createElement(Text2, { style: styles2.testKey }, testCase.key), /* @__PURE__ */ React4.createElement(TouchableOpacity2, { onPress: () => setShowSteps(!showSteps), style: styles2.sectionHeader }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.sectionHeaderText }, showSteps ? "\u25BC" : "\u25B6", " ", info.icon, " ", template === "freeform" ? "Instructions" : `${steps.length} ${template === "checklist" ? "items" : template === "rubric" ? "criteria" : "steps"}`)), showSteps && /* @__PURE__ */ React4.createElement(View3, { style: styles2.templateContent }, template === "steps" && steps.map((step, idx) => /* @__PURE__ */ React4.createElement(View3, { key: idx, style: styles2.step }, /* @__PURE__ */ React4.createElement(View3, { style: styles2.stepNumber }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.stepNumberText }, step.stepNumber)), /* @__PURE__ */ React4.createElement(View3, { style: styles2.stepBody }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.stepAction }, step.action), step.expectedResult && /* @__PURE__ */ React4.createElement(Text2, { style: styles2.stepExpected }, "\u2192 ", step.expectedResult)))), template === "checklist" && /* @__PURE__ */ React4.createElement(React4.Fragment, null, steps.map((step, idx) => /* @__PURE__ */ React4.createElement(
14055
14489
  TouchableOpacity2,
14056
14490
  {
14057
14491
  key: idx,
@@ -14063,31 +14497,31 @@ function TestDetailScreen({ testId, nav }) {
14063
14497
  }),
14064
14498
  style: [styles2.checklistItem, criteriaResults[idx] === true && styles2.checklistItemChecked]
14065
14499
  },
14066
- /* @__PURE__ */ React3.createElement(View2, { style: [styles2.checkbox, criteriaResults[idx] === true && styles2.checkboxChecked] }, criteriaResults[idx] === true && /* @__PURE__ */ React3.createElement(Text2, { style: styles2.checkmark }, "\u2713")),
14067
- /* @__PURE__ */ React3.createElement(Text2, { style: [styles2.checklistText, criteriaResults[idx] === true && styles2.checklistTextDone] }, step.action)
14068
- )), Object.keys(criteriaResults).length > 0 && /* @__PURE__ */ React3.createElement(TouchableOpacity2, { onPress: () => setCriteriaResults({}), style: styles2.resetRow }, /* @__PURE__ */ React3.createElement(Text2, { style: styles2.resetText }, "\u21BA Reset"))), template === "rubric" && steps.map((step, idx) => /* @__PURE__ */ React3.createElement(View2, { key: idx, style: styles2.rubricItem }, /* @__PURE__ */ React3.createElement(Text2, { style: styles2.rubricTitle }, idx + 1, ". ", step.action), step.expectedResult && /* @__PURE__ */ React3.createElement(Text2, { style: styles2.rubricExpected }, step.expectedResult), rubricMode === "pass_fail" ? /* @__PURE__ */ React3.createElement(View2, { style: styles2.passFailRow }, /* @__PURE__ */ React3.createElement(
14500
+ /* @__PURE__ */ React4.createElement(View3, { style: [styles2.checkbox, criteriaResults[idx] === true && styles2.checkboxChecked] }, criteriaResults[idx] === true && /* @__PURE__ */ React4.createElement(Text2, { style: styles2.checkmark }, "\u2713")),
14501
+ /* @__PURE__ */ React4.createElement(Text2, { style: [styles2.checklistText, criteriaResults[idx] === true && styles2.checklistTextDone] }, step.action)
14502
+ )), Object.keys(criteriaResults).length > 0 && /* @__PURE__ */ React4.createElement(TouchableOpacity2, { onPress: () => setCriteriaResults({}), style: styles2.resetRow }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.resetText }, "\u21BA Reset"))), template === "rubric" && steps.map((step, idx) => /* @__PURE__ */ React4.createElement(View3, { key: idx, style: styles2.rubricItem }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.rubricTitle }, idx + 1, ". ", step.action), step.expectedResult && /* @__PURE__ */ React4.createElement(Text2, { style: styles2.rubricExpected }, step.expectedResult), rubricMode === "pass_fail" ? /* @__PURE__ */ React4.createElement(View3, { style: styles2.passFailRow }, /* @__PURE__ */ React4.createElement(
14069
14503
  TouchableOpacity2,
14070
14504
  {
14071
14505
  onPress: () => setCriteriaResults((prev) => ({ ...prev, [idx]: true })),
14072
14506
  style: [styles2.pfButton, criteriaResults[idx] === true && styles2.pfButtonPass]
14073
14507
  },
14074
- /* @__PURE__ */ React3.createElement(Text2, { style: [styles2.pfButtonText, criteriaResults[idx] === true && styles2.pfButtonTextActive] }, "\u2713 Pass")
14075
- ), /* @__PURE__ */ React3.createElement(
14508
+ /* @__PURE__ */ React4.createElement(Text2, { style: [styles2.pfButtonText, criteriaResults[idx] === true && styles2.pfButtonTextActive] }, "\u2713 Pass")
14509
+ ), /* @__PURE__ */ React4.createElement(
14076
14510
  TouchableOpacity2,
14077
14511
  {
14078
14512
  onPress: () => setCriteriaResults((prev) => ({ ...prev, [idx]: false })),
14079
14513
  style: [styles2.pfButton, criteriaResults[idx] === false && styles2.pfButtonFail]
14080
14514
  },
14081
- /* @__PURE__ */ React3.createElement(Text2, { style: [styles2.pfButtonText, criteriaResults[idx] === false && styles2.pfButtonTextActive] }, "\u2717 Fail")
14082
- )) : /* @__PURE__ */ React3.createElement(View2, { style: styles2.ratingRow }, [1, 2, 3, 4, 5].map((n) => /* @__PURE__ */ React3.createElement(
14515
+ /* @__PURE__ */ React4.createElement(Text2, { style: [styles2.pfButtonText, criteriaResults[idx] === false && styles2.pfButtonTextActive] }, "\u2717 Fail")
14516
+ )) : /* @__PURE__ */ React4.createElement(View3, { style: styles2.ratingRow }, [1, 2, 3, 4, 5].map((n) => /* @__PURE__ */ React4.createElement(
14083
14517
  TouchableOpacity2,
14084
14518
  {
14085
14519
  key: n,
14086
14520
  onPress: () => setCriteriaResults((prev) => ({ ...prev, [idx]: n })),
14087
14521
  style: [styles2.ratingBtn, criteriaResults[idx] === n && styles2.ratingBtnActive]
14088
14522
  },
14089
- /* @__PURE__ */ React3.createElement(Text2, { style: [styles2.ratingBtnText, criteriaResults[idx] === n && styles2.ratingBtnTextActive] }, n)
14090
- ))))), template === "freeform" && /* @__PURE__ */ React3.createElement(View2, { style: styles2.freeformBox }, /* @__PURE__ */ React3.createElement(Text2, { style: styles2.freeformText }, "Review the area and note:"), /* @__PURE__ */ React3.createElement(Text2, { style: styles2.freeformBullet }, "\u2022 What works well"), /* @__PURE__ */ React3.createElement(Text2, { style: styles2.freeformBullet }, "\u2022 Issues or concerns"), /* @__PURE__ */ React3.createElement(Text2, { style: styles2.freeformBullet }, "\u2022 Suggestions"))), testCase.expectedResult && /* @__PURE__ */ React3.createElement(View2, { style: styles2.expectedBox }, /* @__PURE__ */ React3.createElement(Text2, { style: styles2.expectedLabel }, "\u2705 Expected Result"), /* @__PURE__ */ React3.createElement(Text2, { style: styles2.expectedText }, testCase.expectedResult)), testCase.targetRoute && onNavigate && /* @__PURE__ */ React3.createElement(
14523
+ /* @__PURE__ */ React4.createElement(Text2, { style: [styles2.ratingBtnText, criteriaResults[idx] === n && styles2.ratingBtnTextActive] }, n)
14524
+ ))))), template === "freeform" && /* @__PURE__ */ React4.createElement(View3, { style: styles2.freeformBox }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.freeformText }, "Review the area and note:"), /* @__PURE__ */ React4.createElement(Text2, { style: styles2.freeformBullet }, "\u2022 What works well"), /* @__PURE__ */ React4.createElement(Text2, { style: styles2.freeformBullet }, "\u2022 Issues or concerns"), /* @__PURE__ */ React4.createElement(Text2, { style: styles2.freeformBullet }, "\u2022 Suggestions"))), testCase.expectedResult && /* @__PURE__ */ React4.createElement(View3, { style: styles2.expectedBox }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.expectedLabel }, "\u2705 Expected Result"), /* @__PURE__ */ React4.createElement(Text2, { style: styles2.expectedText }, testCase.expectedResult)), testCase.targetRoute && onNavigate && /* @__PURE__ */ React4.createElement(
14091
14525
  TouchableOpacity2,
14092
14526
  {
14093
14527
  style: styles2.navigateButton,
@@ -14097,21 +14531,21 @@ function TestDetailScreen({ testId, nav }) {
14097
14531
  nav.closeWidget?.();
14098
14532
  }
14099
14533
  },
14100
- /* @__PURE__ */ React3.createElement(Text2, { style: styles2.navigateText }, "\u{1F9ED} Go to test location")
14101
- ), /* @__PURE__ */ React3.createElement(TouchableOpacity2, { onPress: () => setShowDetails(!showDetails), style: styles2.detailsToggle }, /* @__PURE__ */ React3.createElement(Text2, { style: styles2.detailsToggleText }, showDetails ? "\u25BC" : "\u25B6", " Details")), showDetails && /* @__PURE__ */ React3.createElement(View2, { style: styles2.detailsSection }, testCase.key && /* @__PURE__ */ React3.createElement(Text2, { style: styles2.detailMeta }, testCase.key, " \xB7 ", testCase.priority, " \xB7 ", info.name), testCase.description && /* @__PURE__ */ React3.createElement(Text2, { style: styles2.detailDesc }, testCase.description), testCase.group && /* @__PURE__ */ React3.createElement(View2, { style: styles2.folderProgress }, /* @__PURE__ */ React3.createElement(Text2, { style: styles2.folderName }, "\u{1F4C1} ", testCase.group.name))), /* @__PURE__ */ React3.createElement(View2, { style: styles2.actionButtons }, /* @__PURE__ */ React3.createElement(TouchableOpacity2, { style: [styles2.actionBtn, styles2.failBtn, isSubmitting && { opacity: 0.5 }], onPress: handleFail, disabled: isSubmitting }, /* @__PURE__ */ React3.createElement(Text2, { style: styles2.failBtnText }, isSubmitting ? "Failing..." : "Fail")), /* @__PURE__ */ React3.createElement(TouchableOpacity2, { style: [styles2.actionBtn, styles2.skipBtn, isSubmitting && { opacity: 0.5 }], onPress: () => setShowSkipModal(true), disabled: isSubmitting }, /* @__PURE__ */ React3.createElement(Text2, { style: styles2.skipBtnText }, "Skip")), /* @__PURE__ */ React3.createElement(TouchableOpacity2, { style: [styles2.actionBtn, styles2.passBtn, isSubmitting && { opacity: 0.5 }], onPress: handlePass, disabled: isSubmitting }, /* @__PURE__ */ React3.createElement(Text2, { style: styles2.passBtnText }, isSubmitting ? "Passing..." : "Pass"))), /* @__PURE__ */ React3.createElement(Modal, { visible: showSkipModal, transparent: true, animationType: "fade" }, /* @__PURE__ */ React3.createElement(View2, { style: styles2.modalOverlay }, /* @__PURE__ */ React3.createElement(View2, { style: styles2.modalContent }, /* @__PURE__ */ React3.createElement(Text2, { style: styles2.modalTitle }, "Skip this test?"), /* @__PURE__ */ React3.createElement(Text2, { style: styles2.modalSubtitle }, "Select a reason:"), [
14534
+ /* @__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:"), [
14102
14536
  { reason: "blocked", label: "\u{1F6AB} Blocked by a bug" },
14103
14537
  { reason: "not_ready", label: "\u{1F6A7} Feature not ready" },
14104
14538
  { reason: "dependency", label: "\u{1F517} Needs another test first" },
14105
14539
  { reason: "other", label: "\u{1F4DD} Other reason" }
14106
- ].map(({ reason, label }) => /* @__PURE__ */ React3.createElement(
14540
+ ].map(({ reason, label }) => /* @__PURE__ */ React4.createElement(
14107
14541
  TouchableOpacity2,
14108
14542
  {
14109
14543
  key: reason,
14110
14544
  style: [styles2.skipOption, selectedSkipReason === reason && styles2.skipOptionActive],
14111
14545
  onPress: () => setSelectedSkipReason(reason)
14112
14546
  },
14113
- /* @__PURE__ */ React3.createElement(Text2, { style: [styles2.skipOptionText, selectedSkipReason === reason && styles2.skipOptionTextActive] }, label)
14114
- )), /* @__PURE__ */ React3.createElement(
14547
+ /* @__PURE__ */ React4.createElement(Text2, { style: [styles2.skipOptionText, selectedSkipReason === reason && styles2.skipOptionTextActive] }, label)
14548
+ )), /* @__PURE__ */ React4.createElement(
14115
14549
  TextInput,
14116
14550
  {
14117
14551
  style: styles2.skipNotes,
@@ -14121,21 +14555,21 @@ function TestDetailScreen({ testId, nav }) {
14121
14555
  placeholderTextColor: colors.textMuted,
14122
14556
  multiline: true
14123
14557
  }
14124
- ), /* @__PURE__ */ React3.createElement(View2, { style: styles2.skipActions }, /* @__PURE__ */ React3.createElement(TouchableOpacity2, { style: styles2.skipCancel, onPress: () => {
14558
+ ), /* @__PURE__ */ React4.createElement(View3, { style: styles2.skipActions }, /* @__PURE__ */ React4.createElement(TouchableOpacity2, { style: styles2.skipCancel, onPress: () => {
14125
14559
  setShowSkipModal(false);
14126
14560
  setSelectedSkipReason(null);
14127
14561
  setSkipNotes("");
14128
- } }, /* @__PURE__ */ React3.createElement(Text2, { style: styles2.skipCancelText }, "Cancel")), /* @__PURE__ */ React3.createElement(
14562
+ } }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.skipCancelText }, "Cancel")), /* @__PURE__ */ React4.createElement(
14129
14563
  TouchableOpacity2,
14130
14564
  {
14131
14565
  style: [styles2.skipConfirm, !selectedSkipReason && { opacity: 0.4 }],
14132
14566
  onPress: handleSkip,
14133
14567
  disabled: !selectedSkipReason || skipping
14134
14568
  },
14135
- /* @__PURE__ */ React3.createElement(Text2, { style: styles2.skipConfirmText }, skipping ? "Skipping..." : "Skip Test")
14569
+ /* @__PURE__ */ React4.createElement(Text2, { style: styles2.skipConfirmText }, skipping ? "Skipping..." : "Skip Test")
14136
14570
  ))))));
14137
14571
  }
14138
- var styles2 = StyleSheet3.create({
14572
+ var styles2 = StyleSheet4.create({
14139
14573
  container: { paddingBottom: 16 },
14140
14574
  retestBanner: { flexDirection: "row", alignItems: "center", gap: 6, backgroundColor: "#422006", borderWidth: 1, borderColor: "#854d0e", borderRadius: 8, paddingVertical: 6, paddingHorizontal: 10, marginBottom: 10 },
14141
14575
  retestIcon: { fontSize: 14 },
@@ -14230,14 +14664,17 @@ var styles2 = StyleSheet3.create({
14230
14664
  });
14231
14665
 
14232
14666
  // src/widget/screens/TestListScreen.tsx
14233
- import React4, { useState as useState3, useMemo as useMemo2, useCallback as useCallback3, useEffect as useEffect4 } from "react";
14234
- import { View as View3, Text as Text3, TouchableOpacity as TouchableOpacity3, StyleSheet as StyleSheet4, ScrollView } from "react-native";
14667
+ 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";
14235
14669
  function TestListScreen({ nav }) {
14236
- const { assignments, currentAssignment, refreshAssignments } = useBugBear();
14670
+ const { assignments, currentAssignment, refreshAssignments, isLoading } = useBugBear();
14237
14671
  const [filter, setFilter] = useState3("all");
14238
14672
  const [roleFilter, setRoleFilter] = useState3(null);
14673
+ const [trackFilter, setTrackFilter] = useState3(null);
14674
+ const [searchQuery, setSearchQuery] = useState3("");
14675
+ const [sortMode, setSortMode] = useState3("priority");
14239
14676
  const [collapsedFolders, setCollapsedFolders] = useState3(/* @__PURE__ */ new Set());
14240
- useEffect4(() => {
14677
+ useEffect5(() => {
14241
14678
  refreshAssignments();
14242
14679
  }, []);
14243
14680
  const availableRoles = useMemo2(() => {
@@ -14247,6 +14684,13 @@ function TestListScreen({ nav }) {
14247
14684
  }
14248
14685
  return Array.from(roleMap.values());
14249
14686
  }, [assignments]);
14687
+ const availableTracks = useMemo2(() => {
14688
+ const trackMap = /* @__PURE__ */ new Map();
14689
+ for (const a of assignments) {
14690
+ if (a.testCase.track) trackMap.set(a.testCase.track.id, a.testCase.track);
14691
+ }
14692
+ return Array.from(trackMap.values());
14693
+ }, [assignments]);
14250
14694
  const selectedRole = availableRoles.find((r) => r.id === roleFilter);
14251
14695
  const groupedAssignments = useMemo2(() => {
14252
14696
  const groups = /* @__PURE__ */ new Map();
@@ -14270,6 +14714,8 @@ function TestListScreen({ nav }) {
14270
14714
  folder.assignments.sort((a, b) => {
14271
14715
  if (a.isVerification && !b.isVerification) return -1;
14272
14716
  if (!a.isVerification && b.isVerification) return 1;
14717
+ if (sortMode === "alpha") return a.testCase.title.localeCompare(b.testCase.title);
14718
+ if (sortMode === "recent") return 0;
14273
14719
  const sd = (statusOrder[a.status] ?? 5) - (statusOrder[b.status] ?? 5);
14274
14720
  if (sd !== 0) return sd;
14275
14721
  return (priorityOrder[a.testCase.priority] ?? 4) - (priorityOrder[b.testCase.priority] ?? 4);
@@ -14281,7 +14727,7 @@ function TestListScreen({ nav }) {
14281
14727
  if (!b.group) return -1;
14282
14728
  return a.group.sortOrder - b.group.sortOrder;
14283
14729
  });
14284
- }, [assignments]);
14730
+ }, [assignments, sortMode]);
14285
14731
  const toggleFolder = useCallback3((id) => {
14286
14732
  setCollapsedFolders((prev) => {
14287
14733
  const next = new Set(prev);
@@ -14290,28 +14736,36 @@ function TestListScreen({ nav }) {
14290
14736
  return next;
14291
14737
  });
14292
14738
  }, []);
14293
- const filterAssignment = (a) => {
14739
+ const filterAssignment = useCallback3((a) => {
14294
14740
  if (roleFilter && a.testCase.role?.id !== roleFilter) return false;
14741
+ if (trackFilter && a.testCase.track?.id !== trackFilter) return false;
14742
+ if (searchQuery) {
14743
+ const q = searchQuery.toLowerCase();
14744
+ const titleMatch = a.testCase.title.toLowerCase().includes(q);
14745
+ const keyMatch = a.testCase.testKey.toLowerCase().includes(q);
14746
+ if (!titleMatch && !keyMatch) return false;
14747
+ }
14295
14748
  if (filter === "pending") return a.status === "pending" || a.status === "in_progress";
14296
14749
  if (filter === "done") return a.status === "passed";
14297
14750
  if (filter === "reopened") return a.status === "failed";
14298
14751
  return true;
14299
- };
14300
- return /* @__PURE__ */ React4.createElement(View3, null, /* @__PURE__ */ React4.createElement(View3, { style: styles3.filterBar }, [
14752
+ }, [roleFilter, trackFilter, searchQuery, filter]);
14753
+ if (isLoading) return /* @__PURE__ */ React5.createElement(TestListScreenSkeleton, null);
14754
+ return /* @__PURE__ */ React5.createElement(View4, null, /* @__PURE__ */ React5.createElement(View4, { style: styles3.filterBar }, [
14301
14755
  { key: "all", label: "All", count: assignments.length },
14302
14756
  { key: "pending", label: "To Do", count: assignments.filter((a) => a.status === "pending" || a.status === "in_progress").length },
14303
14757
  { key: "done", label: "Done", count: assignments.filter((a) => a.status === "passed").length },
14304
14758
  { key: "reopened", label: "Re Opened", count: assignments.filter((a) => a.status === "failed").length }
14305
- ].map((f) => /* @__PURE__ */ React4.createElement(TouchableOpacity3, { key: f.key, style: [styles3.filterBtn, filter === f.key && styles3.filterBtnActive], onPress: () => setFilter(f.key) }, /* @__PURE__ */ React4.createElement(Text3, { style: [styles3.filterBtnText, filter === f.key && styles3.filterBtnTextActive] }, f.label, " (", f.count, ")")))), availableRoles.length >= 2 && /* @__PURE__ */ React4.createElement(View3, { style: styles3.roleSection }, /* @__PURE__ */ React4.createElement(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: styles3.roleBar }, /* @__PURE__ */ React4.createElement(
14759
+ ].map((f) => /* @__PURE__ */ React5.createElement(TouchableOpacity3, { key: f.key, style: [styles3.filterBtn, filter === f.key && styles3.filterBtnActive], onPress: () => setFilter(f.key) }, /* @__PURE__ */ React5.createElement(Text3, { style: [styles3.filterBtnText, filter === f.key && styles3.filterBtnTextActive] }, f.label, " (", f.count, ")")))), availableRoles.length >= 2 && /* @__PURE__ */ React5.createElement(View4, { style: styles3.roleSection }, /* @__PURE__ */ React5.createElement(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: styles3.roleBar }, /* @__PURE__ */ React5.createElement(
14306
14760
  TouchableOpacity3,
14307
14761
  {
14308
14762
  style: [styles3.roleBtn, !roleFilter && styles3.roleBtnActive],
14309
14763
  onPress: () => setRoleFilter(null)
14310
14764
  },
14311
- /* @__PURE__ */ React4.createElement(Text3, { style: [styles3.roleBtnText, !roleFilter && styles3.roleBtnTextActive] }, "All Roles")
14765
+ /* @__PURE__ */ React5.createElement(Text3, { style: [styles3.roleBtnText, !roleFilter && styles3.roleBtnTextActive] }, "All Roles")
14312
14766
  ), availableRoles.map((role) => {
14313
14767
  const isActive = roleFilter === role.id;
14314
- return /* @__PURE__ */ React4.createElement(
14768
+ return /* @__PURE__ */ React5.createElement(
14315
14769
  TouchableOpacity3,
14316
14770
  {
14317
14771
  key: role.id,
@@ -14321,33 +14775,72 @@ function TestListScreen({ nav }) {
14321
14775
  ],
14322
14776
  onPress: () => setRoleFilter(isActive ? null : role.id)
14323
14777
  },
14324
- /* @__PURE__ */ React4.createElement(View3, { style: [styles3.roleDot, { backgroundColor: role.color }] }),
14325
- /* @__PURE__ */ React4.createElement(Text3, { style: [styles3.roleBtnText, isActive && { color: role.color, fontWeight: "600" }] }, role.name)
14778
+ /* @__PURE__ */ React5.createElement(View4, { style: [styles3.roleDot, { backgroundColor: role.color }] }),
14779
+ /* @__PURE__ */ React5.createElement(Text3, { style: [styles3.roleBtnText, isActive && { color: role.color, fontWeight: "600" }] }, role.name)
14326
14780
  );
14327
- })), selectedRole?.loginHint && /* @__PURE__ */ React4.createElement(View3, { style: [styles3.loginHint, { backgroundColor: selectedRole.color + "10", borderColor: selectedRole.color + "30" }] }, /* @__PURE__ */ React4.createElement(Text3, { style: styles3.loginHintText }, "Log in as: ", selectedRole.loginHint))), groupedAssignments.map((folder) => {
14781
+ })), selectedRole?.loginHint && /* @__PURE__ */ React5.createElement(View4, { style: [styles3.loginHint, { backgroundColor: selectedRole.color + "10", borderColor: selectedRole.color + "30" }] }, /* @__PURE__ */ React5.createElement(Text3, { style: styles3.loginHintText }, "Log in as: ", selectedRole.loginHint))), /* @__PURE__ */ React5.createElement(View4, { style: styles3.searchContainer }, /* @__PURE__ */ React5.createElement(
14782
+ TextInput2,
14783
+ {
14784
+ value: searchQuery,
14785
+ onChangeText: setSearchQuery,
14786
+ placeholder: "Search tests...",
14787
+ placeholderTextColor: colors.textMuted,
14788
+ style: styles3.searchInput
14789
+ }
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(
14791
+ TouchableOpacity3,
14792
+ {
14793
+ style: [styles3.trackBtn, !trackFilter && styles3.trackBtnActive],
14794
+ onPress: () => setTrackFilter(null)
14795
+ },
14796
+ /* @__PURE__ */ React5.createElement(Text3, { style: [styles3.trackBtnText, !trackFilter && styles3.trackBtnTextActive] }, "All Tracks")
14797
+ ), availableTracks.map((track) => {
14798
+ const isActive = trackFilter === track.id;
14799
+ return /* @__PURE__ */ React5.createElement(
14800
+ TouchableOpacity3,
14801
+ {
14802
+ key: track.id,
14803
+ style: [styles3.trackBtn, isActive && { backgroundColor: track.color + "20", borderColor: track.color + "60", borderWidth: 1 }],
14804
+ onPress: () => setTrackFilter(isActive ? null : track.id)
14805
+ },
14806
+ /* @__PURE__ */ React5.createElement(Text3, { style: [styles3.trackBtnText, isActive && { color: track.color, fontWeight: "600" }] }, track.icon, " ", track.name)
14807
+ );
14808
+ })), /* @__PURE__ */ React5.createElement(View4, { style: styles3.sortGroup }, [
14809
+ { key: "priority", label: "\u2195" },
14810
+ { key: "recent", label: "\u{1F550}" },
14811
+ { key: "alpha", label: "AZ" }
14812
+ ].map((s2) => /* @__PURE__ */ React5.createElement(
14813
+ TouchableOpacity3,
14814
+ {
14815
+ key: s2.key,
14816
+ style: [styles3.sortBtn, sortMode === s2.key && styles3.sortBtnActive],
14817
+ onPress: () => setSortMode(s2.key)
14818
+ },
14819
+ /* @__PURE__ */ React5.createElement(Text3, { style: [styles3.sortBtnText, sortMode === s2.key && styles3.sortBtnTextActive] }, s2.label)
14820
+ )))), groupedAssignments.map((folder) => {
14328
14821
  const folderId = folder.group?.id || "ungrouped";
14329
14822
  const isCollapsed = collapsedFolders.has(folderId);
14330
14823
  const filtered = folder.assignments.filter(filterAssignment);
14331
14824
  if (filtered.length === 0 && filter !== "all") return null;
14332
- return /* @__PURE__ */ React4.createElement(View3, { key: folderId, style: styles3.folder }, /* @__PURE__ */ React4.createElement(TouchableOpacity3, { style: styles3.folderHeader, onPress: () => toggleFolder(folderId) }, /* @__PURE__ */ React4.createElement(Text3, { style: styles3.folderToggle }, isCollapsed ? "\u25B6" : "\u25BC"), /* @__PURE__ */ React4.createElement(Text3, { style: styles3.folderName, numberOfLines: 1 }, folder.group?.name || "Ungrouped"), /* @__PURE__ */ React4.createElement(View3, { style: styles3.folderProgress }, /* @__PURE__ */ React4.createElement(View3, { style: [styles3.folderProgressFill, { width: `${folder.stats.total > 0 ? Math.round((folder.stats.passed + folder.stats.failed) / folder.stats.total * 100) : 0}%` }] })), /* @__PURE__ */ React4.createElement(Text3, { style: styles3.folderCount }, folder.stats.passed + folder.stats.failed, "/", folder.stats.total)), !isCollapsed && filtered.map((assignment) => {
14825
+ return /* @__PURE__ */ React5.createElement(View4, { key: folderId, style: styles3.folder }, /* @__PURE__ */ React5.createElement(TouchableOpacity3, { style: styles3.folderHeader, onPress: () => toggleFolder(folderId) }, /* @__PURE__ */ React5.createElement(Text3, { style: styles3.folderToggle }, isCollapsed ? "\u25B6" : "\u25BC"), /* @__PURE__ */ React5.createElement(Text3, { style: styles3.folderName, numberOfLines: 1 }, folder.group?.name || "Ungrouped"), /* @__PURE__ */ React5.createElement(View4, { style: styles3.folderProgress }, /* @__PURE__ */ React5.createElement(View4, { style: [styles3.folderProgressFill, { width: `${folder.stats.total > 0 ? Math.round((folder.stats.passed + folder.stats.failed) / folder.stats.total * 100) : 0}%` }] })), /* @__PURE__ */ React5.createElement(Text3, { style: styles3.folderCount }, folder.stats.passed + folder.stats.failed, "/", folder.stats.total)), !isCollapsed && filtered.map((assignment) => {
14333
14826
  const badge = getStatusBadge(assignment.status);
14334
14827
  const isCurrent = currentAssignment?.id === assignment.id;
14335
- return /* @__PURE__ */ React4.createElement(
14828
+ return /* @__PURE__ */ React5.createElement(
14336
14829
  TouchableOpacity3,
14337
14830
  {
14338
14831
  key: assignment.id,
14339
14832
  style: [styles3.testItem, isCurrent && styles3.testItemCurrent],
14340
14833
  onPress: () => nav.push({ name: "TEST_DETAIL", testId: assignment.id })
14341
14834
  },
14342
- /* @__PURE__ */ React4.createElement(Text3, { style: styles3.testBadge }, badge.icon),
14343
- /* @__PURE__ */ React4.createElement(View3, { style: styles3.testInfo }, /* @__PURE__ */ React4.createElement(Text3, { style: styles3.testTitle, numberOfLines: 1 }, assignment.testCase.title), /* @__PURE__ */ React4.createElement(View3, { style: styles3.testMetaRow }, assignment.isVerification && /* @__PURE__ */ React4.createElement(View3, { style: styles3.retestTag }, /* @__PURE__ */ React4.createElement(Text3, { style: styles3.retestTagText }, "Retest")), /* @__PURE__ */ React4.createElement(Text3, { style: styles3.testMeta }, assignment.testCase.testKey, " \xB7 ", assignment.testCase.priority), assignment.testCase.role && /* @__PURE__ */ React4.createElement(View3, { style: styles3.roleBadgeRow }, /* @__PURE__ */ React4.createElement(Text3, { style: styles3.testMeta }, " \xB7 "), /* @__PURE__ */ React4.createElement(View3, { style: [styles3.roleBadgeDot, { backgroundColor: assignment.testCase.role.color }] }), /* @__PURE__ */ React4.createElement(Text3, { style: [styles3.testMeta, { color: assignment.testCase.role.color, fontWeight: "500" }] }, assignment.testCase.role.name)))),
14344
- /* @__PURE__ */ React4.createElement(View3, { style: [
14835
+ /* @__PURE__ */ React5.createElement(Text3, { style: styles3.testBadge }, badge.icon),
14836
+ /* @__PURE__ */ React5.createElement(View4, { style: styles3.testInfo }, /* @__PURE__ */ React5.createElement(Text3, { style: styles3.testTitle, numberOfLines: 1 }, assignment.testCase.title), /* @__PURE__ */ React5.createElement(View4, { style: styles3.testMetaRow }, assignment.isVerification && /* @__PURE__ */ React5.createElement(View4, { style: styles3.retestTag }, /* @__PURE__ */ React5.createElement(Text3, { style: styles3.retestTagText }, "Retest")), /* @__PURE__ */ React5.createElement(Text3, { style: styles3.testMeta }, assignment.testCase.testKey, " \xB7 ", assignment.testCase.priority), assignment.testCase.role && /* @__PURE__ */ React5.createElement(View4, { style: styles3.roleBadgeRow }, /* @__PURE__ */ React5.createElement(Text3, { style: styles3.testMeta }, " \xB7 "), /* @__PURE__ */ React5.createElement(View4, { style: [styles3.roleBadgeDot, { backgroundColor: assignment.testCase.role.color }] }), /* @__PURE__ */ React5.createElement(Text3, { style: [styles3.testMeta, { color: assignment.testCase.role.color, fontWeight: "500" }] }, assignment.testCase.role.name)))),
14837
+ /* @__PURE__ */ React5.createElement(View4, { style: [
14345
14838
  styles3.statusPill,
14346
14839
  {
14347
14840
  backgroundColor: assignment.status === "passed" ? "#14532d" : assignment.status === "failed" ? "#450a0a" : assignment.status === "in_progress" ? "#172554" : "#27272a",
14348
14841
  borderColor: assignment.status === "passed" ? "#166534" : assignment.status === "failed" ? "#7f1d1d" : assignment.status === "in_progress" ? "#1e3a5f" : "#3f3f46"
14349
14842
  }
14350
- ] }, /* @__PURE__ */ React4.createElement(Text3, { style: [
14843
+ ] }, /* @__PURE__ */ React5.createElement(Text3, { style: [
14351
14844
  styles3.statusPillText,
14352
14845
  {
14353
14846
  color: assignment.status === "passed" ? "#4ade80" : assignment.status === "failed" ? "#f87171" : assignment.status === "in_progress" ? "#60a5fa" : "#d4d4d8"
@@ -14355,9 +14848,9 @@ function TestListScreen({ nav }) {
14355
14848
  ] }, badge.label))
14356
14849
  );
14357
14850
  }));
14358
- }), /* @__PURE__ */ React4.createElement(TouchableOpacity3, { style: styles3.refreshBtn, onPress: refreshAssignments }, /* @__PURE__ */ React4.createElement(Text3, { style: styles3.refreshText }, "\u21BB", " Refresh")));
14851
+ }), /* @__PURE__ */ React5.createElement(TouchableOpacity3, { style: styles3.refreshBtn, onPress: refreshAssignments }, /* @__PURE__ */ React5.createElement(Text3, { style: styles3.refreshText }, "\u21BB", " Refresh")));
14359
14852
  }
14360
- var styles3 = StyleSheet4.create({
14853
+ var styles3 = StyleSheet5.create({
14361
14854
  filterBar: { flexDirection: "row", gap: 8, marginBottom: 8 },
14362
14855
  filterBtn: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 8, backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border },
14363
14856
  filterBtnActive: { backgroundColor: colors.blue, borderColor: colors.blue },
@@ -14392,13 +14885,25 @@ var styles3 = StyleSheet4.create({
14392
14885
  testMeta: { fontSize: 11, color: colors.textDim },
14393
14886
  statusPill: { paddingHorizontal: 8, paddingVertical: 3, borderRadius: 6, borderWidth: 1, marginLeft: 8 },
14394
14887
  statusPillText: { fontSize: 10, fontWeight: "600" },
14888
+ searchContainer: { marginBottom: 8 },
14889
+ searchInput: { backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border, borderRadius: 8, paddingHorizontal: 12, paddingVertical: 8, fontSize: 13, color: colors.textPrimary },
14890
+ trackSortRow: { flexDirection: "row", alignItems: "center", marginBottom: 10, gap: 8 },
14891
+ trackBtn: { flexDirection: "row", alignItems: "center", gap: 4, paddingHorizontal: 10, paddingVertical: 4, borderRadius: 6, marginRight: 6, borderWidth: 1, borderColor: "transparent" },
14892
+ trackBtnActive: { backgroundColor: colors.card, borderColor: colors.border },
14893
+ trackBtnText: { fontSize: 11, color: colors.textMuted },
14894
+ trackBtnTextActive: { color: colors.textPrimary, fontWeight: "600" },
14895
+ sortGroup: { flexDirection: "row", gap: 2 },
14896
+ sortBtn: { paddingHorizontal: 8, paddingVertical: 4, borderRadius: 6, borderWidth: 1, borderColor: "transparent" },
14897
+ sortBtnActive: { backgroundColor: colors.card, borderColor: colors.border },
14898
+ sortBtnText: { fontSize: 11, color: colors.textMuted },
14899
+ sortBtnTextActive: { color: colors.textPrimary, fontWeight: "600" },
14395
14900
  refreshBtn: { alignItems: "center", paddingVertical: 12 },
14396
14901
  refreshText: { fontSize: 13, color: colors.blue }
14397
14902
  });
14398
14903
 
14399
14904
  // src/widget/screens/TestFeedbackScreen.tsx
14400
- import React7, { useState as useState5 } from "react";
14401
- import { View as View6, Text as Text6, TouchableOpacity as TouchableOpacity6, TextInput as TextInput2, StyleSheet as StyleSheet7 } from "react-native";
14905
+ import React8, { useState as useState5 } from "react";
14906
+ import { View as View7, Text as Text6, TouchableOpacity as TouchableOpacity6, TextInput as TextInput3, StyleSheet as StyleSheet8 } from "react-native";
14402
14907
 
14403
14908
  // src/widget/useImageAttachments.ts
14404
14909
  import { useState as useState4, useCallback as useCallback4 } from "react";
@@ -14498,17 +15003,17 @@ function useImageAttachments(uploadFn, maxImages, bucket = "screenshots") {
14498
15003
  }
14499
15004
 
14500
15005
  // src/widget/ImagePickerButtons.tsx
14501
- import React6 from "react";
14502
- import { View as View5, Text as Text5, TouchableOpacity as TouchableOpacity5, StyleSheet as StyleSheet6 } from "react-native";
15006
+ import React7 from "react";
15007
+ import { View as View6, Text as Text5, TouchableOpacity as TouchableOpacity5, StyleSheet as StyleSheet7 } from "react-native";
14503
15008
 
14504
15009
  // src/widget/ImagePreviewStrip.tsx
14505
- import React5 from "react";
14506
- import { View as View4, Text as Text4, TouchableOpacity as TouchableOpacity4, ScrollView as ScrollView2, Image, ActivityIndicator, StyleSheet as StyleSheet5 } from "react-native";
15010
+ import React6 from "react";
15011
+ import { View as View5, Text as Text4, TouchableOpacity as TouchableOpacity4, ScrollView as ScrollView2, Image, ActivityIndicator, StyleSheet as StyleSheet6 } from "react-native";
14507
15012
  function ImagePreviewStrip({ images, onRemove }) {
14508
15013
  if (images.length === 0) return null;
14509
- return /* @__PURE__ */ React5.createElement(ScrollView2, { horizontal: true, showsHorizontalScrollIndicator: false, style: styles4.strip }, images.map((img) => /* @__PURE__ */ React5.createElement(View4, { key: img.id, style: styles4.thumbContainer }, /* @__PURE__ */ React5.createElement(Image, { source: { uri: img.localUri }, style: styles4.thumb }), img.status === "uploading" && /* @__PURE__ */ React5.createElement(View4, { style: styles4.thumbOverlay }, /* @__PURE__ */ React5.createElement(ActivityIndicator, { size: "small", color: "#fff" })), img.status === "error" && /* @__PURE__ */ React5.createElement(View4, { style: [styles4.thumbOverlay, styles4.thumbOverlayError] }, /* @__PURE__ */ React5.createElement(Text4, { style: styles4.thumbErrorText }, "!")), /* @__PURE__ */ React5.createElement(TouchableOpacity4, { style: styles4.thumbRemove, onPress: () => onRemove(img.id) }, /* @__PURE__ */ React5.createElement(Text4, { style: styles4.thumbRemoveText }, "\u2715")))));
15014
+ return /* @__PURE__ */ React6.createElement(ScrollView2, { horizontal: true, showsHorizontalScrollIndicator: false, style: styles4.strip }, images.map((img) => /* @__PURE__ */ React6.createElement(View5, { key: img.id, style: styles4.thumbContainer }, /* @__PURE__ */ React6.createElement(Image, { source: { uri: img.localUri }, style: styles4.thumb }), img.status === "uploading" && /* @__PURE__ */ React6.createElement(View5, { style: styles4.thumbOverlay }, /* @__PURE__ */ React6.createElement(ActivityIndicator, { size: "small", color: "#fff" })), img.status === "error" && /* @__PURE__ */ React6.createElement(View5, { style: [styles4.thumbOverlay, styles4.thumbOverlayError] }, /* @__PURE__ */ React6.createElement(Text4, { style: styles4.thumbErrorText }, "!")), /* @__PURE__ */ React6.createElement(TouchableOpacity4, { style: styles4.thumbRemove, onPress: () => onRemove(img.id) }, /* @__PURE__ */ React6.createElement(Text4, { style: styles4.thumbRemoveText }, "\u2715")))));
14510
15015
  }
14511
- var styles4 = StyleSheet5.create({
15016
+ var styles4 = StyleSheet6.create({
14512
15017
  strip: {
14513
15018
  flexDirection: "row",
14514
15019
  marginTop: 4
@@ -14527,7 +15032,7 @@ var styles4 = StyleSheet5.create({
14527
15032
  borderRadius: 8
14528
15033
  },
14529
15034
  thumbOverlay: {
14530
- ...StyleSheet5.absoluteFillObject,
15035
+ ...StyleSheet6.absoluteFillObject,
14531
15036
  backgroundColor: "rgba(0,0,0,0.5)",
14532
15037
  justifyContent: "center",
14533
15038
  alignItems: "center",
@@ -14562,25 +15067,25 @@ var styles4 = StyleSheet5.create({
14562
15067
  // src/widget/ImagePickerButtons.tsx
14563
15068
  function ImagePickerButtons({ images, maxImages, onPickGallery, onPickCamera, onRemove, label }) {
14564
15069
  if (!IMAGE_PICKER_AVAILABLE) return null;
14565
- return /* @__PURE__ */ React6.createElement(View5, { style: styles5.section }, label && /* @__PURE__ */ React6.createElement(Text5, { style: styles5.label }, label), /* @__PURE__ */ React6.createElement(View5, { style: styles5.buttonRow }, /* @__PURE__ */ React6.createElement(
15070
+ return /* @__PURE__ */ React7.createElement(View6, { style: styles5.section }, label && /* @__PURE__ */ React7.createElement(Text5, { style: styles5.label }, label), /* @__PURE__ */ React7.createElement(View6, { style: styles5.buttonRow }, /* @__PURE__ */ React7.createElement(
14566
15071
  TouchableOpacity5,
14567
15072
  {
14568
15073
  style: styles5.pickButton,
14569
15074
  onPress: onPickGallery,
14570
15075
  disabled: images.length >= maxImages
14571
15076
  },
14572
- /* @__PURE__ */ React6.createElement(Text5, { style: [styles5.pickButtonText, images.length >= maxImages && styles5.pickButtonDisabled] }, "Gallery")
14573
- ), /* @__PURE__ */ React6.createElement(
15077
+ /* @__PURE__ */ React7.createElement(Text5, { style: [styles5.pickButtonText, images.length >= maxImages && styles5.pickButtonDisabled] }, "Gallery")
15078
+ ), /* @__PURE__ */ React7.createElement(
14574
15079
  TouchableOpacity5,
14575
15080
  {
14576
15081
  style: styles5.pickButton,
14577
15082
  onPress: onPickCamera,
14578
15083
  disabled: images.length >= maxImages
14579
15084
  },
14580
- /* @__PURE__ */ React6.createElement(Text5, { style: [styles5.pickButtonText, images.length >= maxImages && styles5.pickButtonDisabled] }, "Camera")
14581
- ), /* @__PURE__ */ React6.createElement(Text5, { style: styles5.countText }, images.length, "/", maxImages)), /* @__PURE__ */ React6.createElement(ImagePreviewStrip, { images, onRemove }));
15085
+ /* @__PURE__ */ React7.createElement(Text5, { style: [styles5.pickButtonText, images.length >= maxImages && styles5.pickButtonDisabled] }, "Camera")
15086
+ ), /* @__PURE__ */ React7.createElement(Text5, { style: styles5.countText }, images.length, "/", maxImages)), /* @__PURE__ */ React7.createElement(ImagePreviewStrip, { images, onRemove }));
14582
15087
  }
14583
- var styles5 = StyleSheet6.create({
15088
+ var styles5 = StyleSheet7.create({
14584
15089
  section: {
14585
15090
  marginTop: 12,
14586
15091
  marginBottom: 4
@@ -14630,6 +15135,7 @@ function TestFeedbackScreen({ status, assignmentId, nav }) {
14630
15135
  const assignment = assignments.find((a) => a.id === assignmentId);
14631
15136
  const showFlags = rating < 4;
14632
15137
  const handleSubmit = async () => {
15138
+ if (submitting || images.isUploading) return;
14633
15139
  setSubmitting(true);
14634
15140
  if (client && assignment) {
14635
15141
  const screenshotUrls = images.getScreenshotUrls();
@@ -14680,22 +15186,22 @@ function TestFeedbackScreen({ status, assignmentId, nav }) {
14680
15186
  }
14681
15187
  }
14682
15188
  };
14683
- return /* @__PURE__ */ React7.createElement(View6, { style: styles6.container }, /* @__PURE__ */ React7.createElement(Text6, { style: styles6.header }, status === "passed" ? "\u2705 Test Passed!" : "\u274C Test Failed"), /* @__PURE__ */ React7.createElement(Text6, { style: styles6.subheader }, "Rate this test case"), /* @__PURE__ */ React7.createElement(View6, { style: styles6.starRow }, [1, 2, 3, 4, 5].map((n) => /* @__PURE__ */ React7.createElement(TouchableOpacity6, { key: n, onPress: () => setRating(n), style: styles6.starButton }, /* @__PURE__ */ React7.createElement(Text6, { style: [styles6.star, n <= rating && styles6.starActive] }, n <= rating ? "\u2605" : "\u2606")))), showFlags && /* @__PURE__ */ React7.createElement(View6, { style: styles6.flagsSection }, /* @__PURE__ */ React7.createElement(Text6, { style: styles6.flagsLabel }, "What could be improved?"), [
15189
+ return /* @__PURE__ */ React8.createElement(View7, { style: styles6.container }, /* @__PURE__ */ React8.createElement(Text6, { style: styles6.header }, status === "passed" ? "\u2705 Test Passed!" : "\u274C Test Failed"), /* @__PURE__ */ React8.createElement(Text6, { style: styles6.subheader }, "Rate this test case"), /* @__PURE__ */ React8.createElement(View7, { style: styles6.starRow }, [1, 2, 3, 4, 5].map((n) => /* @__PURE__ */ React8.createElement(TouchableOpacity6, { key: n, onPress: () => setRating(n), style: styles6.starButton }, /* @__PURE__ */ React8.createElement(Text6, { style: [styles6.star, n <= rating && styles6.starActive] }, n <= rating ? "\u2605" : "\u2606")))), showFlags && /* @__PURE__ */ React8.createElement(View7, { style: styles6.flagsSection }, /* @__PURE__ */ React8.createElement(Text6, { style: styles6.flagsLabel }, "What could be improved?"), [
14684
15190
  { key: "isOutdated", label: "Test is outdated" },
14685
15191
  { key: "needsMoreDetail", label: "Needs more detail" },
14686
15192
  { key: "stepsUnclear", label: "Steps are unclear" },
14687
15193
  { key: "expectedResultUnclear", label: "Expected result unclear" }
14688
- ].map(({ key, label }) => /* @__PURE__ */ React7.createElement(
15194
+ ].map(({ key, label }) => /* @__PURE__ */ React8.createElement(
14689
15195
  TouchableOpacity6,
14690
15196
  {
14691
15197
  key,
14692
15198
  style: [styles6.flagItem, flags[key] && styles6.flagItemActive],
14693
15199
  onPress: () => setFlags((prev) => ({ ...prev, [key]: !prev[key] }))
14694
15200
  },
14695
- /* @__PURE__ */ React7.createElement(View6, { style: [styles6.flagCheck, flags[key] && styles6.flagCheckActive] }, flags[key] && /* @__PURE__ */ React7.createElement(Text6, { style: styles6.flagCheckmark }, "\u2713")),
14696
- /* @__PURE__ */ React7.createElement(Text6, { style: [styles6.flagText, flags[key] && styles6.flagTextActive] }, label)
14697
- ))), /* @__PURE__ */ React7.createElement(
14698
- TextInput2,
15201
+ /* @__PURE__ */ React8.createElement(View7, { style: [styles6.flagCheck, flags[key] && styles6.flagCheckActive] }, flags[key] && /* @__PURE__ */ React8.createElement(Text6, { style: styles6.flagCheckmark }, "\u2713")),
15202
+ /* @__PURE__ */ React8.createElement(Text6, { style: [styles6.flagText, flags[key] && styles6.flagTextActive] }, label)
15203
+ ))), /* @__PURE__ */ React8.createElement(
15204
+ TextInput3,
14699
15205
  {
14700
15206
  style: styles6.noteInput,
14701
15207
  value: note,
@@ -14704,7 +15210,7 @@ function TestFeedbackScreen({ status, assignmentId, nav }) {
14704
15210
  placeholderTextColor: colors.textMuted,
14705
15211
  multiline: true
14706
15212
  }
14707
- ), /* @__PURE__ */ React7.createElement(
15213
+ ), /* @__PURE__ */ React8.createElement(
14708
15214
  ImagePickerButtons,
14709
15215
  {
14710
15216
  images: images.images,
@@ -14714,9 +15220,9 @@ function TestFeedbackScreen({ status, assignmentId, nav }) {
14714
15220
  onRemove: images.removeImage,
14715
15221
  label: "Screenshots (optional)"
14716
15222
  }
14717
- ), /* @__PURE__ */ React7.createElement(View6, { style: styles6.actions }, /* @__PURE__ */ React7.createElement(TouchableOpacity6, { style: styles6.skipButton, onPress: handleSkip }, /* @__PURE__ */ React7.createElement(Text6, { style: styles6.skipText }, "Skip")), /* @__PURE__ */ React7.createElement(TouchableOpacity6, { style: [shared.primaryButton, { flex: 2, opacity: submitting || images.isUploading ? 0.5 : 1 }], onPress: handleSubmit, disabled: submitting || images.isUploading }, /* @__PURE__ */ React7.createElement(Text6, { style: shared.primaryButtonText }, images.isUploading ? "Uploading..." : submitting ? "Submitting..." : "Submit"))));
15223
+ ), /* @__PURE__ */ React8.createElement(View7, { style: styles6.actions }, /* @__PURE__ */ React8.createElement(TouchableOpacity6, { style: styles6.skipButton, onPress: handleSkip }, /* @__PURE__ */ React8.createElement(Text6, { style: styles6.skipText }, "Skip")), /* @__PURE__ */ React8.createElement(TouchableOpacity6, { style: [shared.primaryButton, { flex: 2, opacity: submitting || images.isUploading ? 0.5 : 1 }], onPress: handleSubmit, disabled: submitting || images.isUploading }, /* @__PURE__ */ React8.createElement(Text6, { style: shared.primaryButtonText }, images.isUploading ? "Uploading..." : submitting ? "Submitting..." : "Submit"))));
14718
15224
  }
14719
- var styles6 = StyleSheet7.create({
15225
+ var styles6 = StyleSheet8.create({
14720
15226
  container: { paddingTop: 8 },
14721
15227
  header: { fontSize: 22, fontWeight: "700", color: colors.textPrimary, textAlign: "center", marginBottom: 4 },
14722
15228
  subheader: { fontSize: 14, color: colors.textMuted, textAlign: "center", marginBottom: 20 },
@@ -14740,12 +15246,12 @@ var styles6 = StyleSheet7.create({
14740
15246
  });
14741
15247
 
14742
15248
  // src/widget/screens/ReportScreen.tsx
14743
- import React9, { useState as useState7, useRef as useRef2, useEffect as useEffect5 } from "react";
14744
- import { View as View8, Text as Text8, TouchableOpacity as TouchableOpacity8, TextInput as TextInput3, StyleSheet as StyleSheet9 } from "react-native";
15249
+ import React10, { useState as useState7, useRef as useRef3, useEffect as useEffect6 } from "react";
15250
+ import { View as View9, Text as Text8, TouchableOpacity as TouchableOpacity8, TextInput as TextInput4, StyleSheet as StyleSheet10 } from "react-native";
14745
15251
 
14746
15252
  // src/widget/CategoryPicker.tsx
14747
- import React8, { useState as useState6 } from "react";
14748
- import { View as View7, Text as Text7, TouchableOpacity as TouchableOpacity7, Modal as Modal2, StyleSheet as StyleSheet8 } from "react-native";
15253
+ import React9, { useState as useState6 } from "react";
15254
+ import { View as View8, Text as Text7, TouchableOpacity as TouchableOpacity7, Modal as Modal2, StyleSheet as StyleSheet9 } from "react-native";
14749
15255
  var categoryOptions = [
14750
15256
  { value: "ui_ux", label: "UI/UX", icon: "\u{1F3A8}" },
14751
15257
  { value: "functional", label: "Functional", icon: "\u2699\uFE0F" },
@@ -14760,15 +15266,15 @@ function CategoryPicker({ value, onChange, optional = true }) {
14760
15266
  onChange(category);
14761
15267
  setModalVisible(false);
14762
15268
  };
14763
- return /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(
15269
+ return /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(
14764
15270
  TouchableOpacity7,
14765
15271
  {
14766
15272
  style: styles7.trigger,
14767
15273
  onPress: () => setModalVisible(true)
14768
15274
  },
14769
- /* @__PURE__ */ React8.createElement(Text7, { style: selectedOption ? styles7.triggerTextSelected : styles7.triggerTextPlaceholder }, selectedOption ? `${selectedOption.icon} ${selectedOption.label}` : optional ? "Select category (optional)" : "Select category"),
14770
- /* @__PURE__ */ React8.createElement(Text7, { style: styles7.chevron }, "\u25BC")
14771
- ), /* @__PURE__ */ React8.createElement(
15275
+ /* @__PURE__ */ React9.createElement(Text7, { style: selectedOption ? styles7.triggerTextSelected : styles7.triggerTextPlaceholder }, selectedOption ? `${selectedOption.icon} ${selectedOption.label}` : optional ? "Select category (optional)" : "Select category"),
15276
+ /* @__PURE__ */ React9.createElement(Text7, { style: styles7.chevron }, "\u25BC")
15277
+ ), /* @__PURE__ */ React9.createElement(
14772
15278
  Modal2,
14773
15279
  {
14774
15280
  visible: modalVisible,
@@ -14776,42 +15282,42 @@ function CategoryPicker({ value, onChange, optional = true }) {
14776
15282
  animationType: "fade",
14777
15283
  onRequestClose: () => setModalVisible(false)
14778
15284
  },
14779
- /* @__PURE__ */ React8.createElement(
15285
+ /* @__PURE__ */ React9.createElement(
14780
15286
  TouchableOpacity7,
14781
15287
  {
14782
15288
  style: styles7.overlay,
14783
15289
  activeOpacity: 1,
14784
15290
  onPress: () => setModalVisible(false)
14785
15291
  },
14786
- /* @__PURE__ */ React8.createElement(View7, { style: styles7.modal, onStartShouldSetResponder: () => true }, /* @__PURE__ */ React8.createElement(Text7, { style: styles7.modalTitle }, "Select Category"), optional && /* @__PURE__ */ React8.createElement(
15292
+ /* @__PURE__ */ React9.createElement(View8, { style: styles7.modal, onStartShouldSetResponder: () => true }, /* @__PURE__ */ React9.createElement(Text7, { style: styles7.modalTitle }, "Select Category"), optional && /* @__PURE__ */ React9.createElement(
14787
15293
  TouchableOpacity7,
14788
15294
  {
14789
15295
  style: [styles7.option, !value && styles7.optionSelected],
14790
15296
  onPress: () => handleSelect(null)
14791
15297
  },
14792
- /* @__PURE__ */ React8.createElement(Text7, { style: styles7.optionText }, "\u2014 None \u2014")
14793
- ), categoryOptions.map(({ value: optValue, label, icon }) => /* @__PURE__ */ React8.createElement(
15298
+ /* @__PURE__ */ React9.createElement(Text7, { style: styles7.optionText }, "\u2014 None \u2014")
15299
+ ), categoryOptions.map(({ value: optValue, label, icon }) => /* @__PURE__ */ React9.createElement(
14794
15300
  TouchableOpacity7,
14795
15301
  {
14796
15302
  key: optValue,
14797
15303
  style: [styles7.option, value === optValue && styles7.optionSelected],
14798
15304
  onPress: () => handleSelect(optValue)
14799
15305
  },
14800
- /* @__PURE__ */ React8.createElement(Text7, { style: styles7.optionIcon }, icon),
14801
- /* @__PURE__ */ React8.createElement(Text7, { style: styles7.optionText }, label),
14802
- value === optValue && /* @__PURE__ */ React8.createElement(Text7, { style: styles7.checkmark }, "\u2713")
14803
- )), /* @__PURE__ */ React8.createElement(
15306
+ /* @__PURE__ */ React9.createElement(Text7, { style: styles7.optionIcon }, icon),
15307
+ /* @__PURE__ */ React9.createElement(Text7, { style: styles7.optionText }, label),
15308
+ value === optValue && /* @__PURE__ */ React9.createElement(Text7, { style: styles7.checkmark }, "\u2713")
15309
+ )), /* @__PURE__ */ React9.createElement(
14804
15310
  TouchableOpacity7,
14805
15311
  {
14806
15312
  style: styles7.cancelButton,
14807
15313
  onPress: () => setModalVisible(false)
14808
15314
  },
14809
- /* @__PURE__ */ React8.createElement(Text7, { style: styles7.cancelText }, "Cancel")
15315
+ /* @__PURE__ */ React9.createElement(Text7, { style: styles7.cancelText }, "Cancel")
14810
15316
  ))
14811
15317
  )
14812
15318
  ));
14813
15319
  }
14814
- var styles7 = StyleSheet8.create({
15320
+ var styles7 = StyleSheet9.create({
14815
15321
  trigger: {
14816
15322
  flexDirection: "row",
14817
15323
  alignItems: "center",
@@ -14908,11 +15414,11 @@ function ReportScreen({ nav, prefill }) {
14908
15414
  const [affectedScreen, setAffectedScreen] = useState7("");
14909
15415
  const [submitting, setSubmitting] = useState7(false);
14910
15416
  const [error, setError] = useState7(null);
14911
- const submittingRef = useRef2(false);
15417
+ const submittingRef = useRef3(false);
14912
15418
  const images = useImageAttachments(uploadImage, 5, "screenshots");
14913
15419
  const isRetestFailure = prefill?.type === "test_fail";
14914
15420
  const isBugType = reportType === "bug" || reportType === "test_fail";
14915
- useEffect5(() => {
15421
+ useEffect6(() => {
14916
15422
  if (reportType === "feedback" || reportType === "suggestion") {
14917
15423
  setCategory("other");
14918
15424
  } else {
@@ -14920,7 +15426,7 @@ function ReportScreen({ nav, prefill }) {
14920
15426
  }
14921
15427
  }, [reportType]);
14922
15428
  const handleSubmit = async () => {
14923
- if (!client || !description.trim()) return;
15429
+ if (!client || !description.trim() || images.isUploading) return;
14924
15430
  if (submittingRef.current) return;
14925
15431
  submittingRef.current = true;
14926
15432
  setSubmitting(true);
@@ -14961,21 +15467,21 @@ function ReportScreen({ nav, prefill }) {
14961
15467
  submittingRef.current = false;
14962
15468
  }
14963
15469
  };
14964
- return /* @__PURE__ */ React9.createElement(View8, null, isRetestFailure ? /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(View8, { style: styles8.retestBanner }, /* @__PURE__ */ React9.createElement(Text8, { style: styles8.retestIcon }, "\u{1F504}"), /* @__PURE__ */ React9.createElement(View8, null, /* @__PURE__ */ React9.createElement(Text8, { style: styles8.retestTitle }, "Bug Still Present"), /* @__PURE__ */ React9.createElement(Text8, { style: styles8.retestSubtitle }, "The fix did not resolve this issue"))), /* @__PURE__ */ React9.createElement(View8, { style: styles8.section }, /* @__PURE__ */ React9.createElement(Text8, { style: shared.label }, "Severity"), /* @__PURE__ */ React9.createElement(View8, { style: styles8.severityRow }, [
15470
+ return /* @__PURE__ */ React10.createElement(View9, null, isRetestFailure ? /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement(View9, { style: styles8.retestBanner }, /* @__PURE__ */ React10.createElement(Text8, { style: styles8.retestIcon }, "\u{1F504}"), /* @__PURE__ */ React10.createElement(View9, null, /* @__PURE__ */ React10.createElement(Text8, { style: styles8.retestTitle }, "Bug Still Present"), /* @__PURE__ */ React10.createElement(Text8, { style: styles8.retestSubtitle }, "The fix did not resolve this issue"))), /* @__PURE__ */ React10.createElement(View9, { style: styles8.section }, /* @__PURE__ */ React10.createElement(Text8, { style: shared.label }, "Severity"), /* @__PURE__ */ React10.createElement(View9, { style: styles8.severityRow }, [
14965
15471
  { sev: "critical", color: "#ef4444" },
14966
15472
  { sev: "high", color: "#f97316" },
14967
15473
  { sev: "medium", color: "#eab308" },
14968
15474
  { sev: "low", color: "#6b7280" }
14969
- ].map(({ sev, color }) => /* @__PURE__ */ React9.createElement(
15475
+ ].map(({ sev, color }) => /* @__PURE__ */ React10.createElement(
14970
15476
  TouchableOpacity8,
14971
15477
  {
14972
15478
  key: sev,
14973
15479
  style: [styles8.sevButton, severity === sev && { backgroundColor: `${color}30`, borderColor: color }],
14974
15480
  onPress: () => setSeverity(sev)
14975
15481
  },
14976
- /* @__PURE__ */ React9.createElement(Text8, { style: [styles8.sevText, severity === sev && { color }] }, sev)
14977
- )))), /* @__PURE__ */ React9.createElement(View8, { style: styles8.section }, /* @__PURE__ */ React9.createElement(Text8, { style: shared.label }, "Category (optional)"), /* @__PURE__ */ React9.createElement(CategoryPicker, { value: category, onChange: setCategory, optional: true })), /* @__PURE__ */ React9.createElement(View8, { style: styles8.section }, /* @__PURE__ */ React9.createElement(Text8, { style: shared.label }, "What went wrong?"), /* @__PURE__ */ React9.createElement(
14978
- TextInput3,
15482
+ /* @__PURE__ */ React10.createElement(Text8, { style: [styles8.sevText, severity === sev && { color }] }, sev)
15483
+ )))), /* @__PURE__ */ React10.createElement(View9, { style: styles8.section }, /* @__PURE__ */ React10.createElement(Text8, { style: shared.label }, "Category (optional)"), /* @__PURE__ */ React10.createElement(CategoryPicker, { value: category, onChange: setCategory, optional: true })), /* @__PURE__ */ React10.createElement(View9, { style: styles8.section }, /* @__PURE__ */ React10.createElement(Text8, { style: shared.label }, "What went wrong?"), /* @__PURE__ */ React10.createElement(
15484
+ TextInput4,
14979
15485
  {
14980
15486
  style: styles8.descInput,
14981
15487
  value: description,
@@ -14986,7 +15492,7 @@ function ReportScreen({ nav, prefill }) {
14986
15492
  numberOfLines: 4,
14987
15493
  textAlignVertical: "top"
14988
15494
  }
14989
- )), /* @__PURE__ */ React9.createElement(
15495
+ )), /* @__PURE__ */ React10.createElement(
14990
15496
  ImagePickerButtons,
14991
15497
  {
14992
15498
  images: images.images,
@@ -14996,42 +15502,42 @@ function ReportScreen({ nav, prefill }) {
14996
15502
  onRemove: images.removeImage,
14997
15503
  label: "Attachments (optional)"
14998
15504
  }
14999
- ), error && /* @__PURE__ */ React9.createElement(View8, { style: styles8.errorBanner }, /* @__PURE__ */ React9.createElement(Text8, { style: styles8.errorText }, error)), /* @__PURE__ */ React9.createElement(
15505
+ ), error && /* @__PURE__ */ React10.createElement(View9, { style: styles8.errorBanner }, /* @__PURE__ */ React10.createElement(Text8, { style: styles8.errorText }, error)), /* @__PURE__ */ React10.createElement(
15000
15506
  TouchableOpacity8,
15001
15507
  {
15002
15508
  style: [shared.primaryButton, styles8.retestSubmitButton, (!description.trim() || submitting || images.isUploading) && shared.primaryButtonDisabled, { marginTop: 20 }],
15003
15509
  onPress: handleSubmit,
15004
15510
  disabled: !description.trim() || submitting || images.isUploading
15005
15511
  },
15006
- /* @__PURE__ */ React9.createElement(Text8, { style: shared.primaryButtonText }, images.isUploading ? "Uploading images..." : submitting ? "Submitting..." : error ? "Retry" : "Submit Failed Retest")
15007
- )) : /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(Text8, { style: shared.label }, "What are you reporting?"), /* @__PURE__ */ React9.createElement(View8, { style: styles8.typeRow }, [
15512
+ /* @__PURE__ */ React10.createElement(Text8, { style: shared.primaryButtonText }, images.isUploading ? "Uploading images..." : submitting ? "Submitting..." : error ? "Retry" : "Submit Failed Retest")
15513
+ )) : /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement(Text8, { style: shared.label }, "What are you reporting?"), /* @__PURE__ */ React10.createElement(View9, { style: styles8.typeRow }, [
15008
15514
  { type: "bug", label: "Bug", icon: "\u{1F41B}" },
15009
15515
  { type: "feedback", label: "Feedback", icon: "\u{1F4A1}" },
15010
15516
  { type: "suggestion", label: "Idea", icon: "\u2728" }
15011
- ].map(({ type, label, icon }) => /* @__PURE__ */ React9.createElement(
15517
+ ].map(({ type, label, icon }) => /* @__PURE__ */ React10.createElement(
15012
15518
  TouchableOpacity8,
15013
15519
  {
15014
15520
  key: type,
15015
15521
  style: [styles8.typeCard, reportType === type && styles8.typeCardActive],
15016
15522
  onPress: () => setReportType(type)
15017
15523
  },
15018
- /* @__PURE__ */ React9.createElement(Text8, { style: styles8.typeIcon }, icon),
15019
- /* @__PURE__ */ React9.createElement(Text8, { style: [styles8.typeLabel, reportType === type && styles8.typeLabelActive] }, label)
15020
- ))), isBugType && /* @__PURE__ */ React9.createElement(View8, { style: styles8.section }, /* @__PURE__ */ React9.createElement(Text8, { style: shared.label }, "Severity"), /* @__PURE__ */ React9.createElement(View8, { style: styles8.severityRow }, [
15524
+ /* @__PURE__ */ React10.createElement(Text8, { style: styles8.typeIcon }, icon),
15525
+ /* @__PURE__ */ React10.createElement(Text8, { style: [styles8.typeLabel, reportType === type && styles8.typeLabelActive] }, label)
15526
+ ))), isBugType && /* @__PURE__ */ React10.createElement(View9, { style: styles8.section }, /* @__PURE__ */ React10.createElement(Text8, { style: shared.label }, "Severity"), /* @__PURE__ */ React10.createElement(View9, { style: styles8.severityRow }, [
15021
15527
  { sev: "critical", color: "#ef4444" },
15022
15528
  { sev: "high", color: "#f97316" },
15023
15529
  { sev: "medium", color: "#eab308" },
15024
15530
  { sev: "low", color: "#6b7280" }
15025
- ].map(({ sev, color }) => /* @__PURE__ */ React9.createElement(
15531
+ ].map(({ sev, color }) => /* @__PURE__ */ React10.createElement(
15026
15532
  TouchableOpacity8,
15027
15533
  {
15028
15534
  key: sev,
15029
15535
  style: [styles8.sevButton, severity === sev && { backgroundColor: `${color}30`, borderColor: color }],
15030
15536
  onPress: () => setSeverity(sev)
15031
15537
  },
15032
- /* @__PURE__ */ React9.createElement(Text8, { style: [styles8.sevText, severity === sev && { color }] }, sev)
15033
- )))), isBugType && /* @__PURE__ */ React9.createElement(View8, { style: styles8.section }, /* @__PURE__ */ React9.createElement(Text8, { style: shared.label }, "Category (optional)"), /* @__PURE__ */ React9.createElement(CategoryPicker, { value: category, onChange: setCategory, optional: true })), /* @__PURE__ */ React9.createElement(View8, { style: styles8.section }, /* @__PURE__ */ React9.createElement(Text8, { style: shared.label }, "What happened?"), /* @__PURE__ */ React9.createElement(
15034
- TextInput3,
15538
+ /* @__PURE__ */ React10.createElement(Text8, { style: [styles8.sevText, severity === sev && { color }] }, sev)
15539
+ )))), isBugType && /* @__PURE__ */ React10.createElement(View9, { style: styles8.section }, /* @__PURE__ */ React10.createElement(Text8, { style: shared.label }, "Category (optional)"), /* @__PURE__ */ React10.createElement(CategoryPicker, { value: category, onChange: setCategory, optional: true })), /* @__PURE__ */ React10.createElement(View9, { style: styles8.section }, /* @__PURE__ */ React10.createElement(Text8, { style: shared.label }, "What happened?"), /* @__PURE__ */ React10.createElement(
15540
+ TextInput4,
15035
15541
  {
15036
15542
  style: styles8.descInput,
15037
15543
  value: description,
@@ -15042,8 +15548,8 @@ function ReportScreen({ nav, prefill }) {
15042
15548
  numberOfLines: 4,
15043
15549
  textAlignVertical: "top"
15044
15550
  }
15045
- )), isBugType && /* @__PURE__ */ React9.createElement(View8, { style: styles8.section }, /* @__PURE__ */ React9.createElement(Text8, { style: shared.label }, "Which screen?"), /* @__PURE__ */ React9.createElement(
15046
- TextInput3,
15551
+ )), isBugType && /* @__PURE__ */ React10.createElement(View9, { style: styles8.section }, /* @__PURE__ */ React10.createElement(Text8, { style: shared.label }, "Which screen?"), /* @__PURE__ */ React10.createElement(
15552
+ TextInput4,
15047
15553
  {
15048
15554
  style: styles8.screenInput,
15049
15555
  value: affectedScreen,
@@ -15051,7 +15557,7 @@ function ReportScreen({ nav, prefill }) {
15051
15557
  placeholder: "e.g. Reservations, Settings...",
15052
15558
  placeholderTextColor: colors.textMuted
15053
15559
  }
15054
- ), /* @__PURE__ */ React9.createElement(Text8, { style: styles8.screenHint }, "Which screen or area was the bug on? (optional)")), /* @__PURE__ */ React9.createElement(
15560
+ ), /* @__PURE__ */ React10.createElement(Text8, { style: styles8.screenHint }, "Which screen or area was the bug on? (optional)")), /* @__PURE__ */ React10.createElement(
15055
15561
  ImagePickerButtons,
15056
15562
  {
15057
15563
  images: images.images,
@@ -15061,17 +15567,17 @@ function ReportScreen({ nav, prefill }) {
15061
15567
  onRemove: images.removeImage,
15062
15568
  label: "Screenshots (optional)"
15063
15569
  }
15064
- ), error && /* @__PURE__ */ React9.createElement(View8, { style: styles8.errorBanner }, /* @__PURE__ */ React9.createElement(Text8, { style: styles8.errorText }, error)), /* @__PURE__ */ React9.createElement(
15570
+ ), error && /* @__PURE__ */ React10.createElement(View9, { style: styles8.errorBanner }, /* @__PURE__ */ React10.createElement(Text8, { style: styles8.errorText }, error)), /* @__PURE__ */ React10.createElement(
15065
15571
  TouchableOpacity8,
15066
15572
  {
15067
15573
  style: [shared.primaryButton, (!description.trim() || submitting || images.isUploading) && shared.primaryButtonDisabled, { marginTop: 20 }],
15068
15574
  onPress: handleSubmit,
15069
15575
  disabled: !description.trim() || submitting || images.isUploading
15070
15576
  },
15071
- /* @__PURE__ */ React9.createElement(Text8, { style: shared.primaryButtonText }, images.isUploading ? "Uploading images..." : submitting ? "Submitting..." : error ? "Retry" : "Submit Report")
15577
+ /* @__PURE__ */ React10.createElement(Text8, { style: shared.primaryButtonText }, images.isUploading ? "Uploading images..." : submitting ? "Submitting..." : error ? "Retry" : "Submit Report")
15072
15578
  )));
15073
15579
  }
15074
- var styles8 = StyleSheet9.create({
15580
+ var styles8 = StyleSheet10.create({
15075
15581
  typeRow: { flexDirection: "row", gap: 10, marginBottom: 20 },
15076
15582
  typeCard: { flex: 1, alignItems: "center", paddingVertical: 16, borderRadius: 12, backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border },
15077
15583
  typeCardActive: { borderColor: colors.blue, backgroundColor: "#172554" },
@@ -15095,16 +15601,16 @@ var styles8 = StyleSheet9.create({
15095
15601
  });
15096
15602
 
15097
15603
  // src/widget/screens/ReportSuccessScreen.tsx
15098
- import React10, { useEffect as useEffect6 } from "react";
15099
- import { View as View9, Text as Text9, StyleSheet as StyleSheet10 } from "react-native";
15604
+ import React11, { useEffect as useEffect7 } from "react";
15605
+ import { View as View10, Text as Text9, StyleSheet as StyleSheet11 } from "react-native";
15100
15606
  function ReportSuccessScreen({ nav }) {
15101
- useEffect6(() => {
15607
+ useEffect7(() => {
15102
15608
  const timer = setTimeout(() => nav.reset(), 2e3);
15103
15609
  return () => clearTimeout(timer);
15104
15610
  }, [nav]);
15105
- return /* @__PURE__ */ React10.createElement(View9, { style: styles9.container }, /* @__PURE__ */ React10.createElement(Text9, { style: styles9.emoji }, "\u{1F389}"), /* @__PURE__ */ React10.createElement(Text9, { style: styles9.title }, "Report submitted!"), /* @__PURE__ */ React10.createElement(Text9, { style: styles9.subtitle }, "Thank you for your feedback"));
15611
+ return /* @__PURE__ */ React11.createElement(View10, { style: styles9.container }, /* @__PURE__ */ React11.createElement(Text9, { style: styles9.emoji }, "\u{1F389}"), /* @__PURE__ */ React11.createElement(Text9, { style: styles9.title }, "Report submitted!"), /* @__PURE__ */ React11.createElement(Text9, { style: styles9.subtitle }, "Thank you for your feedback"));
15106
15612
  }
15107
- var styles9 = StyleSheet10.create({
15613
+ var styles9 = StyleSheet11.create({
15108
15614
  container: { alignItems: "center", paddingVertical: 60 },
15109
15615
  emoji: { fontSize: 48, marginBottom: 16 },
15110
15616
  title: { fontSize: 22, fontWeight: "700", color: colors.textPrimary, marginBottom: 6 },
@@ -15112,29 +15618,30 @@ var styles9 = StyleSheet10.create({
15112
15618
  });
15113
15619
 
15114
15620
  // src/widget/screens/MessageListScreen.tsx
15115
- import React11 from "react";
15116
- import { View as View10, Text as Text10, TouchableOpacity as TouchableOpacity9, StyleSheet as StyleSheet11 } from "react-native";
15621
+ import React12 from "react";
15622
+ import { View as View11, Text as Text10, TouchableOpacity as TouchableOpacity9, StyleSheet as StyleSheet12 } from "react-native";
15117
15623
  function MessageListScreen({ nav }) {
15118
- const { threads, unreadCount, refreshThreads } = useBugBear();
15119
- return /* @__PURE__ */ React11.createElement(View10, null, /* @__PURE__ */ React11.createElement(
15624
+ const { threads, unreadCount, refreshThreads, isLoading } = useBugBear();
15625
+ if (isLoading) return /* @__PURE__ */ React12.createElement(MessageListScreenSkeleton, null);
15626
+ return /* @__PURE__ */ React12.createElement(View11, null, /* @__PURE__ */ React12.createElement(
15120
15627
  TouchableOpacity9,
15121
15628
  {
15122
15629
  style: styles10.newMsgButton,
15123
15630
  onPress: () => nav.push({ name: "COMPOSE_MESSAGE" })
15124
15631
  },
15125
- /* @__PURE__ */ React11.createElement(Text10, { style: styles10.newMsgText }, "\u2709\uFE0F New Message")
15126
- ), threads.length === 0 ? /* @__PURE__ */ React11.createElement(View10, { style: shared.emptyState }, /* @__PURE__ */ React11.createElement(Text10, { style: shared.emptyEmoji }, "\u{1F4AC}"), /* @__PURE__ */ React11.createElement(Text10, { style: shared.emptyTitle }, "No messages yet"), /* @__PURE__ */ React11.createElement(Text10, { style: shared.emptySubtitle }, "Start a conversation or wait for messages from admins")) : /* @__PURE__ */ React11.createElement(View10, null, threads.map((thread) => /* @__PURE__ */ React11.createElement(
15632
+ /* @__PURE__ */ React12.createElement(Text10, { style: styles10.newMsgText }, "\u2709\uFE0F New Message")
15633
+ ), threads.length === 0 ? /* @__PURE__ */ React12.createElement(View11, { style: shared.emptyState }, /* @__PURE__ */ React12.createElement(Text10, { style: shared.emptyEmoji }, "\u{1F4AC}"), /* @__PURE__ */ React12.createElement(Text10, { style: shared.emptyTitle }, "No messages yet"), /* @__PURE__ */ React12.createElement(Text10, { style: shared.emptySubtitle }, "Start a conversation or wait for messages from admins")) : /* @__PURE__ */ React12.createElement(View11, null, threads.map((thread) => /* @__PURE__ */ React12.createElement(
15127
15634
  TouchableOpacity9,
15128
15635
  {
15129
15636
  key: thread.id,
15130
15637
  style: [styles10.threadItem, thread.unreadCount > 0 && styles10.threadItemUnread],
15131
15638
  onPress: () => nav.push({ name: "THREAD_DETAIL", thread })
15132
15639
  },
15133
- /* @__PURE__ */ React11.createElement(View10, { style: styles10.threadLeft }, /* @__PURE__ */ React11.createElement(Text10, { style: styles10.threadIcon }, getThreadTypeIcon(thread.threadType)), /* @__PURE__ */ React11.createElement(View10, { style: styles10.threadInfo }, /* @__PURE__ */ React11.createElement(View10, { style: styles10.threadTitleRow }, thread.isPinned && /* @__PURE__ */ React11.createElement(Text10, { style: styles10.pinIcon }, "\u{1F4CC}"), /* @__PURE__ */ React11.createElement(Text10, { style: styles10.threadSubject, numberOfLines: 1 }, thread.subject || "No subject")), thread.lastMessage && /* @__PURE__ */ React11.createElement(Text10, { style: styles10.threadPreview, numberOfLines: 1 }, thread.lastMessage.senderName, ": ", thread.lastMessage.content))),
15134
- /* @__PURE__ */ React11.createElement(View10, { style: styles10.threadRight }, /* @__PURE__ */ React11.createElement(Text10, { style: styles10.threadTime }, formatRelativeTime(thread.lastMessageAt)), thread.unreadCount > 0 && /* @__PURE__ */ React11.createElement(View10, { style: styles10.unreadBadge }, /* @__PURE__ */ React11.createElement(Text10, { style: styles10.unreadText }, thread.unreadCount)), thread.priority !== "normal" && /* @__PURE__ */ React11.createElement(View10, { style: [styles10.priorityDot, { backgroundColor: getPriorityColor(thread.priority) }] }))
15135
- ))), /* @__PURE__ */ React11.createElement(View10, { style: styles10.footer }, /* @__PURE__ */ React11.createElement(Text10, { style: styles10.footerText }, threads.length, " thread", threads.length !== 1 ? "s" : "", " \xB7 ", unreadCount, " unread"), /* @__PURE__ */ React11.createElement(TouchableOpacity9, { onPress: refreshThreads }, /* @__PURE__ */ React11.createElement(Text10, { style: styles10.refreshText }, "\u21BB Refresh"))));
15640
+ /* @__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
+ /* @__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"))));
15136
15643
  }
15137
- var styles10 = StyleSheet11.create({
15644
+ var styles10 = StyleSheet12.create({
15138
15645
  newMsgButton: { backgroundColor: colors.blue, paddingVertical: 12, borderRadius: 12, alignItems: "center", marginBottom: 16 },
15139
15646
  newMsgText: { fontSize: 15, fontWeight: "600", color: "#fff" },
15140
15647
  threadItem: { flexDirection: "row", justifyContent: "space-between", paddingVertical: 12, paddingHorizontal: 12, borderRadius: 10, marginBottom: 4, backgroundColor: colors.card },
@@ -15157,8 +15664,8 @@ var styles10 = StyleSheet11.create({
15157
15664
  });
15158
15665
 
15159
15666
  // src/widget/screens/ThreadDetailScreen.tsx
15160
- import React12, { useState as useState8, useEffect as useEffect7 } from "react";
15161
- import { View as View11, Text as Text11, TouchableOpacity as TouchableOpacity10, TextInput as TextInput4, StyleSheet as StyleSheet12, Image as Image2 } from "react-native";
15667
+ import React13, { useState as useState8, useEffect as useEffect8 } from "react";
15668
+ import { View as View12, Text as Text11, TouchableOpacity as TouchableOpacity10, TextInput as TextInput5, StyleSheet as StyleSheet13, Image as Image2 } from "react-native";
15162
15669
  function ThreadDetailScreen({ thread, nav }) {
15163
15670
  const { getThreadMessages, sendMessage, markAsRead, uploadImage } = useBugBear();
15164
15671
  const [messages, setMessages] = useState8([]);
@@ -15167,16 +15674,29 @@ function ThreadDetailScreen({ thread, nav }) {
15167
15674
  const [sending, setSending] = useState8(false);
15168
15675
  const [sendError, setSendError] = useState8(false);
15169
15676
  const replyImages = useImageAttachments(uploadImage, 3, "discussion-attachments");
15170
- useEffect7(() => {
15677
+ useEffect8(() => {
15678
+ let cancelled = false;
15679
+ setLoading(true);
15171
15680
  (async () => {
15172
- setLoading(true);
15173
- const msgs = await getThreadMessages(thread.id);
15174
- setMessages(msgs);
15175
- setLoading(false);
15176
- if (thread.unreadCount > 0) {
15177
- await markAsRead(thread.id);
15681
+ try {
15682
+ const msgs = await getThreadMessages(thread.id);
15683
+ if (!cancelled) {
15684
+ setMessages(msgs);
15685
+ }
15686
+ if (thread.unreadCount > 0) {
15687
+ await markAsRead(thread.id);
15688
+ }
15689
+ } catch (err) {
15690
+ console.error("BugBear: Failed to load thread messages", err);
15691
+ } finally {
15692
+ if (!cancelled) {
15693
+ setLoading(false);
15694
+ }
15178
15695
  }
15179
15696
  })();
15697
+ return () => {
15698
+ cancelled = true;
15699
+ };
15180
15700
  }, [thread.id]);
15181
15701
  const handleSend = async () => {
15182
15702
  if (!replyText.trim() && replyImages.images.length === 0 || sending || replyImages.isUploading) return;
@@ -15199,18 +15719,18 @@ function ThreadDetailScreen({ thread, nav }) {
15199
15719
  }
15200
15720
  setSending(false);
15201
15721
  };
15202
- return /* @__PURE__ */ React12.createElement(View11, { style: styles11.container }, /* @__PURE__ */ React12.createElement(View11, { style: styles11.header }, /* @__PURE__ */ React12.createElement(Text11, { style: styles11.headerIcon }, getThreadTypeIcon(thread.threadType)), /* @__PURE__ */ React12.createElement(Text11, { style: styles11.headerSubject, numberOfLines: 2 }, thread.subject || "No subject")), loading ? /* @__PURE__ */ React12.createElement(View11, { style: styles11.loadingContainer }, /* @__PURE__ */ React12.createElement(Text11, { style: styles11.loadingText }, "Loading messages...")) : /* @__PURE__ */ React12.createElement(View11, { style: styles11.messagesContainer }, messages.map((msg) => /* @__PURE__ */ React12.createElement(
15203
- View11,
15722
+ return /* @__PURE__ */ React13.createElement(View12, { style: styles11.container }, /* @__PURE__ */ React13.createElement(View12, { style: styles11.header }, /* @__PURE__ */ React13.createElement(Text11, { style: styles11.headerIcon }, getThreadTypeIcon(thread.threadType)), /* @__PURE__ */ React13.createElement(Text11, { style: styles11.headerSubject, numberOfLines: 2 }, thread.subject || "No subject")), loading ? /* @__PURE__ */ React13.createElement(View12, { style: styles11.loadingContainer }, /* @__PURE__ */ React13.createElement(Text11, { style: styles11.loadingText }, "Loading messages...")) : /* @__PURE__ */ React13.createElement(View12, { style: styles11.messagesContainer }, messages.map((msg) => /* @__PURE__ */ React13.createElement(
15723
+ View12,
15204
15724
  {
15205
15725
  key: msg.id,
15206
15726
  style: [styles11.bubble, msg.senderType === "tester" ? styles11.bubbleTester : styles11.bubbleAdmin]
15207
15727
  },
15208
- /* @__PURE__ */ React12.createElement(Text11, { style: [styles11.sender, msg.senderType === "tester" && styles11.senderTester] }, msg.senderType === "tester" ? "You" : msg.senderName),
15209
- /* @__PURE__ */ React12.createElement(Text11, { style: [styles11.content, msg.senderType === "tester" && styles11.contentTester] }, msg.content),
15210
- msg.attachments && msg.attachments.length > 0 && /* @__PURE__ */ React12.createElement(View11, { style: styles11.attachments }, msg.attachments.filter((a) => a.type === "image").map((att, idx) => /* @__PURE__ */ React12.createElement(Image2, { key: idx, source: { uri: att.url }, style: styles11.attachmentImage, resizeMode: "cover" }))),
15211
- /* @__PURE__ */ React12.createElement(Text11, { style: [styles11.time, msg.senderType === "tester" && styles11.timeTester] }, formatMessageTime(msg.createdAt))
15212
- ))), sendError && /* @__PURE__ */ React12.createElement(View11, { style: styles11.errorBar }, /* @__PURE__ */ React12.createElement(Text11, { style: styles11.errorText }, "Failed to send. Tap Send to retry.")), replyImages.images.length > 0 && /* @__PURE__ */ React12.createElement(View11, { style: styles11.replyPreview }, /* @__PURE__ */ React12.createElement(ImagePreviewStrip, { images: replyImages.images, onRemove: replyImages.removeImage })), /* @__PURE__ */ React12.createElement(View11, { style: styles11.composer }, IMAGE_PICKER_AVAILABLE && /* @__PURE__ */ React12.createElement(TouchableOpacity10, { style: styles11.attachBtn, onPress: replyImages.pickFromGallery, disabled: replyImages.images.length >= 3 }, /* @__PURE__ */ React12.createElement(Text11, { style: styles11.attachBtnText }, "\u{1F4CE}")), /* @__PURE__ */ React12.createElement(
15213
- TextInput4,
15728
+ /* @__PURE__ */ React13.createElement(Text11, { style: [styles11.sender, msg.senderType === "tester" && styles11.senderTester] }, msg.senderType === "tester" ? "You" : msg.senderName),
15729
+ /* @__PURE__ */ React13.createElement(Text11, { style: [styles11.content, msg.senderType === "tester" && styles11.contentTester] }, msg.content),
15730
+ msg.attachments && msg.attachments.length > 0 && /* @__PURE__ */ React13.createElement(View12, { style: styles11.attachments }, msg.attachments.filter((a) => a.type === "image").map((att, idx) => /* @__PURE__ */ React13.createElement(Image2, { key: idx, source: { uri: att.url }, style: styles11.attachmentImage, resizeMode: "cover" }))),
15731
+ /* @__PURE__ */ React13.createElement(Text11, { style: [styles11.time, msg.senderType === "tester" && styles11.timeTester] }, formatMessageTime(msg.createdAt))
15732
+ ))), sendError && /* @__PURE__ */ React13.createElement(View12, { style: styles11.errorBar }, /* @__PURE__ */ React13.createElement(Text11, { style: styles11.errorText }, "Failed to send. Tap Send to retry.")), replyImages.images.length > 0 && /* @__PURE__ */ React13.createElement(View12, { style: styles11.replyPreview }, /* @__PURE__ */ React13.createElement(ImagePreviewStrip, { images: replyImages.images, onRemove: replyImages.removeImage })), /* @__PURE__ */ React13.createElement(View12, { style: styles11.composer }, IMAGE_PICKER_AVAILABLE && /* @__PURE__ */ React13.createElement(TouchableOpacity10, { style: styles11.attachBtn, onPress: replyImages.pickFromGallery, disabled: replyImages.images.length >= 3 }, /* @__PURE__ */ React13.createElement(Text11, { style: styles11.attachBtnText }, "\u{1F4CE}")), /* @__PURE__ */ React13.createElement(
15733
+ TextInput5,
15214
15734
  {
15215
15735
  style: styles11.replyInput,
15216
15736
  value: replyText,
@@ -15220,17 +15740,17 @@ function ThreadDetailScreen({ thread, nav }) {
15220
15740
  multiline: true,
15221
15741
  maxLength: 1e3
15222
15742
  }
15223
- ), /* @__PURE__ */ React12.createElement(
15743
+ ), /* @__PURE__ */ React13.createElement(
15224
15744
  TouchableOpacity10,
15225
15745
  {
15226
- style: [styles11.sendBtn, (!replyText.trim() || sending || replyImages.isUploading) && styles11.sendBtnDisabled],
15746
+ style: [styles11.sendBtn, (!replyText.trim() && replyImages.images.length === 0 || sending || replyImages.isUploading) && styles11.sendBtnDisabled],
15227
15747
  onPress: handleSend,
15228
- disabled: !replyText.trim() || sending || replyImages.isUploading
15748
+ disabled: !replyText.trim() && replyImages.images.length === 0 || sending || replyImages.isUploading
15229
15749
  },
15230
- /* @__PURE__ */ React12.createElement(Text11, { style: styles11.sendBtnText }, sending ? "..." : "Send")
15750
+ /* @__PURE__ */ React13.createElement(Text11, { style: styles11.sendBtnText }, sending ? "..." : "Send")
15231
15751
  )));
15232
15752
  }
15233
- var styles11 = StyleSheet12.create({
15753
+ var styles11 = StyleSheet13.create({
15234
15754
  container: { flex: 1 },
15235
15755
  header: { flexDirection: "row", alignItems: "center", gap: 8, marginBottom: 16, paddingBottom: 12, borderBottomWidth: 1, borderBottomColor: colors.border },
15236
15756
  headerIcon: { fontSize: 20 },
@@ -15262,8 +15782,8 @@ var styles11 = StyleSheet12.create({
15262
15782
  });
15263
15783
 
15264
15784
  // src/widget/screens/ComposeMessageScreen.tsx
15265
- import React13, { useState as useState9 } from "react";
15266
- import { View as View12, Text as Text12, TextInput as TextInput5, TouchableOpacity as TouchableOpacity11, StyleSheet as StyleSheet13 } from "react-native";
15785
+ import React14, { useState as useState9 } from "react";
15786
+ import { View as View13, Text as Text12, TextInput as TextInput6, TouchableOpacity as TouchableOpacity11, StyleSheet as StyleSheet14 } from "react-native";
15267
15787
  function ComposeMessageScreen({ nav }) {
15268
15788
  const { createThread, uploadImage } = useBugBear();
15269
15789
  const [subject, setSubject] = useState9("");
@@ -15271,7 +15791,7 @@ function ComposeMessageScreen({ nav }) {
15271
15791
  const [sending, setSending] = useState9(false);
15272
15792
  const images = useImageAttachments(uploadImage, 3, "discussion-attachments");
15273
15793
  const handleSend = async () => {
15274
- if (!subject.trim() || !message.trim()) return;
15794
+ if (!subject.trim() || !message.trim() || sending || images.isUploading) return;
15275
15795
  setSending(true);
15276
15796
  const attachments = images.getAttachments();
15277
15797
  const result = await createThread({
@@ -15284,8 +15804,8 @@ function ComposeMessageScreen({ nav }) {
15284
15804
  nav.pop();
15285
15805
  }
15286
15806
  };
15287
- return /* @__PURE__ */ React13.createElement(View12, null, /* @__PURE__ */ React13.createElement(View12, { style: styles12.header }, /* @__PURE__ */ React13.createElement(Text12, { style: styles12.title }, "New Message"), /* @__PURE__ */ React13.createElement(Text12, { style: styles12.subtitle }, "Send a message to the QA team")), /* @__PURE__ */ React13.createElement(View12, { style: styles12.form }, /* @__PURE__ */ React13.createElement(Text12, { style: shared.label }, "Subject"), /* @__PURE__ */ React13.createElement(
15288
- TextInput5,
15807
+ return /* @__PURE__ */ React14.createElement(View13, null, /* @__PURE__ */ React14.createElement(View13, { style: styles12.header }, /* @__PURE__ */ React14.createElement(Text12, { style: styles12.title }, "New Message"), /* @__PURE__ */ React14.createElement(Text12, { style: styles12.subtitle }, "Send a message to the QA team")), /* @__PURE__ */ React14.createElement(View13, { style: styles12.form }, /* @__PURE__ */ React14.createElement(Text12, { style: shared.label }, "Subject"), /* @__PURE__ */ React14.createElement(
15808
+ TextInput6,
15289
15809
  {
15290
15810
  style: styles12.subjectInput,
15291
15811
  value: subject,
@@ -15294,8 +15814,8 @@ function ComposeMessageScreen({ nav }) {
15294
15814
  placeholderTextColor: colors.textMuted,
15295
15815
  maxLength: 100
15296
15816
  }
15297
- ), /* @__PURE__ */ React13.createElement(Text12, { style: [shared.label, { marginTop: 16 }] }, "Message"), /* @__PURE__ */ React13.createElement(
15298
- TextInput5,
15817
+ ), /* @__PURE__ */ React14.createElement(Text12, { style: [shared.label, { marginTop: 16 }] }, "Message"), /* @__PURE__ */ React14.createElement(
15818
+ TextInput6,
15299
15819
  {
15300
15820
  style: styles12.messageInput,
15301
15821
  value: message,
@@ -15307,7 +15827,7 @@ function ComposeMessageScreen({ nav }) {
15307
15827
  textAlignVertical: "top",
15308
15828
  maxLength: 2e3
15309
15829
  }
15310
- ), /* @__PURE__ */ React13.createElement(
15830
+ ), /* @__PURE__ */ React14.createElement(
15311
15831
  ImagePickerButtons,
15312
15832
  {
15313
15833
  images: images.images,
@@ -15316,17 +15836,17 @@ function ComposeMessageScreen({ nav }) {
15316
15836
  onPickCamera: images.pickFromCamera,
15317
15837
  onRemove: images.removeImage
15318
15838
  }
15319
- ), /* @__PURE__ */ React13.createElement(
15839
+ ), /* @__PURE__ */ React14.createElement(
15320
15840
  TouchableOpacity11,
15321
15841
  {
15322
15842
  style: [shared.primaryButton, (!subject.trim() || !message.trim() || sending || images.isUploading) && shared.primaryButtonDisabled, { marginTop: 20 }],
15323
15843
  onPress: handleSend,
15324
15844
  disabled: !subject.trim() || !message.trim() || sending || images.isUploading
15325
15845
  },
15326
- /* @__PURE__ */ React13.createElement(Text12, { style: shared.primaryButtonText }, images.isUploading ? "Uploading..." : sending ? "Sending..." : "Send Message")
15846
+ /* @__PURE__ */ React14.createElement(Text12, { style: shared.primaryButtonText }, images.isUploading ? "Uploading..." : sending ? "Sending..." : "Send Message")
15327
15847
  )));
15328
15848
  }
15329
- var styles12 = StyleSheet13.create({
15849
+ var styles12 = StyleSheet14.create({
15330
15850
  header: { marginBottom: 20 },
15331
15851
  title: { fontSize: 20, fontWeight: "600", color: colors.textPrimary, marginBottom: 4 },
15332
15852
  subtitle: { fontSize: 14, color: colors.textMuted },
@@ -15336,8 +15856,8 @@ var styles12 = StyleSheet13.create({
15336
15856
  });
15337
15857
 
15338
15858
  // src/widget/screens/ProfileScreen.tsx
15339
- import React14, { useState as useState10, useEffect as useEffect8 } from "react";
15340
- import { View as View13, Text as Text13, TouchableOpacity as TouchableOpacity12, TextInput as TextInput6, StyleSheet as StyleSheet14 } from "react-native";
15859
+ import React15, { useState as useState10, useEffect as useEffect9 } from "react";
15860
+ import { View as View14, Text as Text13, TouchableOpacity as TouchableOpacity12, TextInput as TextInput7, StyleSheet as StyleSheet15 } from "react-native";
15341
15861
  function ProfileScreen({ nav }) {
15342
15862
  const { testerInfo, assignments, updateTesterProfile, refreshTesterInfo } = useBugBear();
15343
15863
  const [editing, setEditing] = useState10(false);
@@ -15349,7 +15869,7 @@ function ProfileScreen({ nav }) {
15349
15869
  const [saved, setSaved] = useState10(false);
15350
15870
  const [showDetails, setShowDetails] = useState10(false);
15351
15871
  const completedCount = assignments.filter((a) => a.status === "passed" || a.status === "failed").length;
15352
- useEffect8(() => {
15872
+ useEffect9(() => {
15353
15873
  if (testerInfo) {
15354
15874
  setName(testerInfo.name);
15355
15875
  setAdditionalEmails(testerInfo.additionalEmails || []);
@@ -15357,6 +15877,7 @@ function ProfileScreen({ nav }) {
15357
15877
  }
15358
15878
  }, [testerInfo]);
15359
15879
  const handleSave = async () => {
15880
+ if (saving) return;
15360
15881
  setSaving(true);
15361
15882
  const updates = {
15362
15883
  name: name.trim(),
@@ -15383,17 +15904,17 @@ function ProfileScreen({ nav }) {
15383
15904
  }
15384
15905
  };
15385
15906
  if (saved) {
15386
- return /* @__PURE__ */ React14.createElement(View13, { style: shared.emptyState }, /* @__PURE__ */ React14.createElement(Text13, { style: shared.emptyEmoji }, "\u2705"), /* @__PURE__ */ React14.createElement(Text13, { style: shared.emptyTitle }, "Profile saved!"));
15907
+ return /* @__PURE__ */ React15.createElement(View14, { style: shared.emptyState }, /* @__PURE__ */ React15.createElement(Text13, { style: shared.emptyEmoji }, "\u2705"), /* @__PURE__ */ React15.createElement(Text13, { style: shared.emptyTitle }, "Profile saved!"));
15387
15908
  }
15388
15909
  if (!testerInfo) {
15389
- return /* @__PURE__ */ React14.createElement(View13, { style: shared.emptyState }, /* @__PURE__ */ React14.createElement(Text13, { style: shared.emptyEmoji }, "\u{1F464}"), /* @__PURE__ */ React14.createElement(Text13, { style: shared.emptyTitle }, "No profile found"));
15910
+ return /* @__PURE__ */ React15.createElement(View14, { style: shared.emptyState }, /* @__PURE__ */ React15.createElement(Text13, { style: shared.emptyEmoji }, "\u{1F464}"), /* @__PURE__ */ React15.createElement(Text13, { style: shared.emptyTitle }, "No profile found"));
15390
15911
  }
15391
15912
  if (editing) {
15392
- return /* @__PURE__ */ React14.createElement(View13, null, /* @__PURE__ */ React14.createElement(View13, { style: styles13.editHeader }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.editTitle }, "Edit Profile"), /* @__PURE__ */ React14.createElement(TouchableOpacity12, { onPress: () => {
15913
+ return /* @__PURE__ */ React15.createElement(View14, null, /* @__PURE__ */ React15.createElement(View14, { style: styles13.editHeader }, /* @__PURE__ */ React15.createElement(Text13, { style: styles13.editTitle }, "Edit Profile"), /* @__PURE__ */ React15.createElement(TouchableOpacity12, { onPress: () => {
15393
15914
  setEditing(false);
15394
15915
  setNewEmailInput("");
15395
- } }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.cancelText }, "Cancel"))), /* @__PURE__ */ React14.createElement(View13, { style: styles13.field }, /* @__PURE__ */ React14.createElement(Text13, { style: shared.label }, "Name"), /* @__PURE__ */ React14.createElement(TextInput6, { style: styles13.input, value: name, onChangeText: setName, placeholder: "Your name", placeholderTextColor: colors.textMuted })), /* @__PURE__ */ React14.createElement(View13, { style: styles13.field }, /* @__PURE__ */ React14.createElement(Text13, { style: shared.label }, "Primary Email"), /* @__PURE__ */ React14.createElement(Text13, { style: styles13.emailFixed }, testerInfo.email)), /* @__PURE__ */ React14.createElement(View13, { style: styles13.field }, /* @__PURE__ */ React14.createElement(Text13, { style: shared.label }, "Additional Emails"), additionalEmails.map((email) => /* @__PURE__ */ React14.createElement(View13, { key: email, style: styles13.emailRow }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.emailText }, email), /* @__PURE__ */ React14.createElement(TouchableOpacity12, { onPress: () => setAdditionalEmails(additionalEmails.filter((e) => e !== email)) }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.removeEmail }, "\u2715")))), /* @__PURE__ */ React14.createElement(View13, { style: styles13.addEmailRow }, /* @__PURE__ */ React14.createElement(
15396
- TextInput6,
15916
+ } }, /* @__PURE__ */ React15.createElement(Text13, { style: styles13.cancelText }, "Cancel"))), /* @__PURE__ */ React15.createElement(View14, { style: styles13.field }, /* @__PURE__ */ React15.createElement(Text13, { style: shared.label }, "Name"), /* @__PURE__ */ React15.createElement(TextInput7, { style: styles13.input, value: name, onChangeText: setName, placeholder: "Your name", placeholderTextColor: colors.textMuted })), /* @__PURE__ */ React15.createElement(View14, { style: styles13.field }, /* @__PURE__ */ React15.createElement(Text13, { style: shared.label }, "Primary Email"), /* @__PURE__ */ React15.createElement(Text13, { style: styles13.emailFixed }, testerInfo.email)), /* @__PURE__ */ React15.createElement(View14, { style: styles13.field }, /* @__PURE__ */ React15.createElement(Text13, { style: shared.label }, "Additional Emails"), additionalEmails.map((email) => /* @__PURE__ */ React15.createElement(View14, { key: email, style: styles13.emailRow }, /* @__PURE__ */ React15.createElement(Text13, { style: styles13.emailText }, email), /* @__PURE__ */ React15.createElement(TouchableOpacity12, { onPress: () => setAdditionalEmails(additionalEmails.filter((e) => e !== email)) }, /* @__PURE__ */ React15.createElement(Text13, { style: styles13.removeEmail }, "\u2715")))), /* @__PURE__ */ React15.createElement(View14, { style: styles13.addEmailRow }, /* @__PURE__ */ React15.createElement(
15917
+ TextInput7,
15397
15918
  {
15398
15919
  style: [styles13.input, { flex: 1, marginRight: 8 }],
15399
15920
  value: newEmailInput,
@@ -15403,26 +15924,26 @@ function ProfileScreen({ nav }) {
15403
15924
  keyboardType: "email-address",
15404
15925
  autoCapitalize: "none"
15405
15926
  }
15406
- ), /* @__PURE__ */ React14.createElement(TouchableOpacity12, { style: styles13.addButton, onPress: handleAddEmail }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.addButtonText }, "Add")))), /* @__PURE__ */ React14.createElement(View13, { style: styles13.field }, /* @__PURE__ */ React14.createElement(Text13, { style: shared.label }, "Testing Platforms"), /* @__PURE__ */ React14.createElement(View13, { style: styles13.platformRow }, [{ key: "ios", label: "\u{1F4F1} iOS" }, { key: "android", label: "\u{1F916} Android" }, { key: "web", label: "\u{1F310} Web" }].map(({ key, label }) => /* @__PURE__ */ React14.createElement(
15927
+ ), /* @__PURE__ */ React15.createElement(TouchableOpacity12, { style: styles13.addButton, onPress: handleAddEmail }, /* @__PURE__ */ React15.createElement(Text13, { style: styles13.addButtonText }, "Add")))), /* @__PURE__ */ React15.createElement(View14, { style: styles13.field }, /* @__PURE__ */ React15.createElement(Text13, { style: shared.label }, "Testing Platforms"), /* @__PURE__ */ React15.createElement(View14, { style: styles13.platformRow }, [{ key: "ios", label: "\u{1F4F1} iOS" }, { key: "android", label: "\u{1F916} Android" }, { key: "web", label: "\u{1F310} Web" }].map(({ key, label }) => /* @__PURE__ */ React15.createElement(
15407
15928
  TouchableOpacity12,
15408
15929
  {
15409
15930
  key,
15410
15931
  style: [styles13.platformBtn, platforms.includes(key) && styles13.platformBtnActive],
15411
15932
  onPress: () => setPlatforms((prev) => prev.includes(key) ? prev.filter((p) => p !== key) : [...prev, key])
15412
15933
  },
15413
- /* @__PURE__ */ React14.createElement(Text13, { style: [styles13.platformText, platforms.includes(key) && styles13.platformTextActive] }, label)
15414
- )))), /* @__PURE__ */ React14.createElement(TouchableOpacity12, { style: [shared.primaryButton, { marginTop: 20 }], onPress: handleSave, disabled: saving }, /* @__PURE__ */ React14.createElement(Text13, { style: shared.primaryButtonText }, saving ? "Saving..." : "Save Profile")));
15934
+ /* @__PURE__ */ React15.createElement(Text13, { style: [styles13.platformText, platforms.includes(key) && styles13.platformTextActive] }, label)
15935
+ )))), /* @__PURE__ */ React15.createElement(TouchableOpacity12, { style: [shared.primaryButton, { marginTop: 20 }], onPress: handleSave, disabled: saving }, /* @__PURE__ */ React15.createElement(Text13, { style: shared.primaryButtonText }, saving ? "Saving..." : "Save Profile")));
15415
15936
  }
15416
- return /* @__PURE__ */ React14.createElement(View13, null, /* @__PURE__ */ React14.createElement(View13, { style: styles13.profileCard }, /* @__PURE__ */ React14.createElement(View13, { style: styles13.avatar }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.avatarText }, testerInfo.name.charAt(0).toUpperCase())), /* @__PURE__ */ React14.createElement(Text13, { style: styles13.profileName }, testerInfo.name), /* @__PURE__ */ React14.createElement(Text13, { style: styles13.profileEmail }, testerInfo.email)), /* @__PURE__ */ React14.createElement(View13, { style: styles13.statsRow }, /* @__PURE__ */ React14.createElement(View13, { style: styles13.statItem }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.statNumber }, completedCount), /* @__PURE__ */ React14.createElement(Text13, { style: styles13.statLabel }, "Completed")), /* @__PURE__ */ React14.createElement(View13, { style: styles13.statDivider }), /* @__PURE__ */ React14.createElement(View13, { style: styles13.statItem }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.statNumber }, assignments.length), /* @__PURE__ */ React14.createElement(Text13, { style: styles13.statLabel }, "Total Assigned"))), /* @__PURE__ */ React14.createElement(TouchableOpacity12, { onPress: () => setShowDetails(!showDetails), style: styles13.detailsToggle }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.detailsToggleText }, showDetails ? "\u25BC" : "\u25B6", " Details")), showDetails && /* @__PURE__ */ React14.createElement(View13, { style: styles13.detailsSection }, additionalEmails.length > 0 && /* @__PURE__ */ React14.createElement(View13, { style: styles13.detailBlock }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.detailLabel }, "Additional Emails"), additionalEmails.map((e) => /* @__PURE__ */ React14.createElement(Text13, { key: e, style: styles13.detailValue }, e))), platforms.length > 0 && /* @__PURE__ */ React14.createElement(View13, { style: styles13.detailBlock }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.detailLabel }, "Platforms"), /* @__PURE__ */ React14.createElement(View13, { style: styles13.platformTags }, platforms.map((p) => /* @__PURE__ */ React14.createElement(View13, { key: p, style: styles13.platformTag }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.platformTagText }, p === "ios" ? "\u{1F4F1} iOS" : p === "android" ? "\u{1F916} Android" : "\u{1F310} Web")))))), /* @__PURE__ */ React14.createElement(
15937
+ return /* @__PURE__ */ React15.createElement(View14, null, /* @__PURE__ */ React15.createElement(View14, { style: styles13.profileCard }, /* @__PURE__ */ React15.createElement(View14, { style: styles13.avatar }, /* @__PURE__ */ React15.createElement(Text13, { style: styles13.avatarText }, testerInfo.name.charAt(0).toUpperCase())), /* @__PURE__ */ React15.createElement(Text13, { style: styles13.profileName }, testerInfo.name), /* @__PURE__ */ React15.createElement(Text13, { style: styles13.profileEmail }, testerInfo.email)), /* @__PURE__ */ React15.createElement(View14, { style: styles13.statsRow }, /* @__PURE__ */ React15.createElement(View14, { style: styles13.statItem }, /* @__PURE__ */ React15.createElement(Text13, { style: styles13.statNumber }, completedCount), /* @__PURE__ */ React15.createElement(Text13, { style: styles13.statLabel }, "Completed")), /* @__PURE__ */ React15.createElement(View14, { style: styles13.statDivider }), /* @__PURE__ */ React15.createElement(View14, { style: styles13.statItem }, /* @__PURE__ */ React15.createElement(Text13, { style: styles13.statNumber }, assignments.length), /* @__PURE__ */ React15.createElement(Text13, { style: styles13.statLabel }, "Total Assigned"))), /* @__PURE__ */ React15.createElement(TouchableOpacity12, { onPress: () => setShowDetails(!showDetails), style: styles13.detailsToggle }, /* @__PURE__ */ React15.createElement(Text13, { style: styles13.detailsToggleText }, showDetails ? "\u25BC" : "\u25B6", " Details")), showDetails && /* @__PURE__ */ React15.createElement(View14, { style: styles13.detailsSection }, additionalEmails.length > 0 && /* @__PURE__ */ React15.createElement(View14, { style: styles13.detailBlock }, /* @__PURE__ */ React15.createElement(Text13, { style: styles13.detailLabel }, "Additional Emails"), additionalEmails.map((e) => /* @__PURE__ */ React15.createElement(Text13, { key: e, style: styles13.detailValue }, e))), platforms.length > 0 && /* @__PURE__ */ React15.createElement(View14, { style: styles13.detailBlock }, /* @__PURE__ */ React15.createElement(Text13, { style: styles13.detailLabel }, "Platforms"), /* @__PURE__ */ React15.createElement(View14, { style: styles13.platformTags }, platforms.map((p) => /* @__PURE__ */ React15.createElement(View14, { key: p, style: styles13.platformTag }, /* @__PURE__ */ React15.createElement(Text13, { style: styles13.platformTagText }, p === "ios" ? "\u{1F4F1} iOS" : p === "android" ? "\u{1F916} Android" : "\u{1F310} Web")))))), /* @__PURE__ */ React15.createElement(
15417
15938
  TouchableOpacity12,
15418
15939
  {
15419
15940
  style: [shared.primaryButton, { marginTop: 20 }],
15420
15941
  onPress: () => setEditing(true)
15421
15942
  },
15422
- /* @__PURE__ */ React14.createElement(Text13, { style: shared.primaryButtonText }, "Edit Profile")
15943
+ /* @__PURE__ */ React15.createElement(Text13, { style: shared.primaryButtonText }, "Edit Profile")
15423
15944
  ));
15424
15945
  }
15425
- var styles13 = StyleSheet14.create({
15946
+ var styles13 = StyleSheet15.create({
15426
15947
  profileCard: { alignItems: "center", backgroundColor: colors.card, borderRadius: 16, padding: 24, marginBottom: 16 },
15427
15948
  avatar: { width: 64, height: 64, borderRadius: 32, backgroundColor: colors.blue, justifyContent: "center", alignItems: "center", marginBottom: 12 },
15428
15949
  avatarText: { fontSize: 28, fontWeight: "700", color: "#fff" },
@@ -15463,8 +15984,8 @@ var styles13 = StyleSheet14.create({
15463
15984
  });
15464
15985
 
15465
15986
  // src/widget/screens/IssueListScreen.tsx
15466
- import React15, { useState as useState11, useEffect as useEffect9 } from "react";
15467
- import { View as View14, Text as Text14, TouchableOpacity as TouchableOpacity13, StyleSheet as StyleSheet15, ActivityIndicator as ActivityIndicator2 } from "react-native";
15987
+ import React16, { useState as useState11, useEffect as useEffect10 } from "react";
15988
+ import { View as View15, Text as Text14, TouchableOpacity as TouchableOpacity13, StyleSheet as StyleSheet16 } from "react-native";
15468
15989
  var CATEGORY_CONFIG = {
15469
15990
  open: { label: "Open Issues", accent: "#f97316", emptyIcon: "\u2705", emptyText: "No open issues" },
15470
15991
  done: { label: "Done", accent: "#22c55e", emptyIcon: "\u{1F389}", emptyText: "No completed issues yet" },
@@ -15481,15 +16002,25 @@ function IssueListScreen({ nav, category }) {
15481
16002
  const [issues, setIssues] = useState11([]);
15482
16003
  const [loading, setLoading] = useState11(true);
15483
16004
  const config = CATEGORY_CONFIG[category];
15484
- useEffect9(() => {
16005
+ useEffect10(() => {
15485
16006
  let cancelled = false;
15486
16007
  setLoading(true);
15487
16008
  (async () => {
15488
- if (!client) return;
15489
- const data = await client.getIssues(category);
15490
- if (!cancelled) {
15491
- setIssues(data);
16009
+ if (!client) {
15492
16010
  setLoading(false);
16011
+ return;
16012
+ }
16013
+ try {
16014
+ const data = await client.getIssues(category);
16015
+ if (!cancelled) {
16016
+ setIssues(data);
16017
+ }
16018
+ } catch (err) {
16019
+ console.error("BugBear: Failed to load issues", err);
16020
+ } finally {
16021
+ if (!cancelled) {
16022
+ setLoading(false);
16023
+ }
15493
16024
  }
15494
16025
  })();
15495
16026
  return () => {
@@ -15497,12 +16028,12 @@ function IssueListScreen({ nav, category }) {
15497
16028
  };
15498
16029
  }, [client, category]);
15499
16030
  if (loading) {
15500
- return /* @__PURE__ */ React15.createElement(View14, { style: styles14.emptyContainer }, /* @__PURE__ */ React15.createElement(ActivityIndicator2, { size: "small", color: colors.textMuted }), /* @__PURE__ */ React15.createElement(Text14, { style: styles14.emptyText }, "Loading..."));
16031
+ return /* @__PURE__ */ React16.createElement(IssueListScreenSkeleton, null);
15501
16032
  }
15502
16033
  if (issues.length === 0) {
15503
- return /* @__PURE__ */ React15.createElement(View14, { style: styles14.emptyContainer }, /* @__PURE__ */ React15.createElement(Text14, { style: styles14.emptyIcon }, config.emptyIcon), /* @__PURE__ */ React15.createElement(Text14, { style: styles14.emptyText }, config.emptyText));
16034
+ return /* @__PURE__ */ React16.createElement(View15, { style: styles14.emptyContainer }, /* @__PURE__ */ React16.createElement(Text14, { style: styles14.emptyIcon }, config.emptyIcon), /* @__PURE__ */ React16.createElement(Text14, { style: styles14.emptyText }, config.emptyText));
15504
16035
  }
15505
- return /* @__PURE__ */ React15.createElement(View14, null, issues.map((issue) => /* @__PURE__ */ React15.createElement(
16036
+ return /* @__PURE__ */ React16.createElement(View15, null, issues.map((issue) => /* @__PURE__ */ React16.createElement(
15506
16037
  TouchableOpacity13,
15507
16038
  {
15508
16039
  key: issue.id,
@@ -15510,13 +16041,13 @@ function IssueListScreen({ nav, category }) {
15510
16041
  onPress: () => nav.push({ name: "ISSUE_DETAIL", issue }),
15511
16042
  activeOpacity: 0.7
15512
16043
  },
15513
- /* @__PURE__ */ React15.createElement(View14, { style: styles14.topRow }, issue.severity && /* @__PURE__ */ React15.createElement(View14, { style: [styles14.severityDot, { backgroundColor: SEVERITY_COLORS[issue.severity] || colors.textDim }] }), /* @__PURE__ */ React15.createElement(Text14, { style: styles14.issueTitle, numberOfLines: 1 }, issue.title)),
15514
- /* @__PURE__ */ React15.createElement(View14, { style: styles14.bottomRow }, issue.route && /* @__PURE__ */ React15.createElement(Text14, { style: styles14.routeText, numberOfLines: 1 }, issue.route), /* @__PURE__ */ React15.createElement(Text14, { style: styles14.timeText }, formatRelativeTime(issue.updatedAt))),
15515
- category === "done" && issue.verifiedByName && /* @__PURE__ */ React15.createElement(View14, { style: styles14.verifiedBadge }, /* @__PURE__ */ React15.createElement(Text14, { style: styles14.verifiedBadgeText }, "\u2714", " Verified by ", issue.verifiedByName)),
15516
- category === "reopened" && issue.originalBugTitle && /* @__PURE__ */ React15.createElement(View14, { style: styles14.reopenedBadge }, /* @__PURE__ */ React15.createElement(Text14, { style: styles14.reopenedBadgeText, numberOfLines: 1 }, "\u{1F504}", " Retest of: ", issue.originalBugTitle))
16044
+ /* @__PURE__ */ React16.createElement(View15, { style: styles14.topRow }, issue.severity && /* @__PURE__ */ React16.createElement(View15, { style: [styles14.severityDot, { backgroundColor: SEVERITY_COLORS[issue.severity] || colors.textDim }] }), /* @__PURE__ */ React16.createElement(Text14, { style: styles14.issueTitle, numberOfLines: 1 }, issue.title)),
16045
+ /* @__PURE__ */ React16.createElement(View15, { style: styles14.bottomRow }, issue.route && /* @__PURE__ */ React16.createElement(Text14, { style: styles14.routeText, numberOfLines: 1 }, issue.route), /* @__PURE__ */ React16.createElement(Text14, { style: styles14.timeText }, formatRelativeTime(issue.updatedAt))),
16046
+ category === "done" && issue.verifiedByName && /* @__PURE__ */ React16.createElement(View15, { style: styles14.verifiedBadge }, /* @__PURE__ */ React16.createElement(Text14, { style: styles14.verifiedBadgeText }, "\u2714", " Verified by ", issue.verifiedByName)),
16047
+ category === "reopened" && issue.originalBugTitle && /* @__PURE__ */ React16.createElement(View15, { style: styles14.reopenedBadge }, /* @__PURE__ */ React16.createElement(Text14, { style: styles14.reopenedBadgeText, numberOfLines: 1 }, "\u{1F504}", " Retest of: ", issue.originalBugTitle))
15517
16048
  )));
15518
16049
  }
15519
- var styles14 = StyleSheet15.create({
16050
+ var styles14 = StyleSheet16.create({
15520
16051
  emptyContainer: {
15521
16052
  alignItems: "center",
15522
16053
  paddingVertical: 40
@@ -15607,8 +16138,8 @@ var styles14 = StyleSheet15.create({
15607
16138
  });
15608
16139
 
15609
16140
  // src/widget/screens/IssueDetailScreen.tsx
15610
- import React16 from "react";
15611
- import { View as View15, Text as Text15, Image as Image3, StyleSheet as StyleSheet16, Linking as Linking2, TouchableOpacity as TouchableOpacity14 } from "react-native";
16141
+ 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";
15612
16143
  var STATUS_LABELS = {
15613
16144
  new: { label: "New", bg: "#1e3a5f", color: "#60a5fa" },
15614
16145
  triaging: { label: "Triaging", bg: "#1e3a5f", color: "#60a5fa" },
@@ -15632,9 +16163,9 @@ var SEVERITY_CONFIG = {
15632
16163
  function IssueDetailScreen({ nav, issue }) {
15633
16164
  const statusConfig = STATUS_LABELS[issue.status] || { label: issue.status, bg: "#27272a", color: "#a1a1aa" };
15634
16165
  const severityConfig = issue.severity ? SEVERITY_CONFIG[issue.severity] : null;
15635
- return /* @__PURE__ */ React16.createElement(View15, null, /* @__PURE__ */ React16.createElement(View15, { style: styles15.badgeRow }, /* @__PURE__ */ React16.createElement(View15, { style: [styles15.badge, { backgroundColor: statusConfig.bg }] }, /* @__PURE__ */ React16.createElement(Text15, { style: [styles15.badgeText, { color: statusConfig.color }] }, statusConfig.label)), severityConfig && /* @__PURE__ */ React16.createElement(View15, { style: [styles15.badge, { backgroundColor: severityConfig.bg }] }, /* @__PURE__ */ React16.createElement(Text15, { style: [styles15.badgeText, { color: severityConfig.color }] }, severityConfig.label))), /* @__PURE__ */ React16.createElement(Text15, { style: styles15.title }, issue.title), issue.route && /* @__PURE__ */ React16.createElement(Text15, { style: styles15.route }, issue.route), issue.description && /* @__PURE__ */ React16.createElement(View15, { style: styles15.descriptionCard }, /* @__PURE__ */ React16.createElement(Text15, { style: styles15.descriptionText }, issue.description)), issue.verifiedByName && /* @__PURE__ */ React16.createElement(View15, { style: styles15.verifiedCard }, /* @__PURE__ */ React16.createElement(View15, { style: styles15.verifiedHeader }, /* @__PURE__ */ React16.createElement(Text15, { style: styles15.verifiedIcon }, "\u2705"), /* @__PURE__ */ React16.createElement(Text15, { style: styles15.verifiedTitle }, "Retesting Proof")), /* @__PURE__ */ React16.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__ */ React16.createElement(View15, { style: styles15.originalBugCard }, /* @__PURE__ */ React16.createElement(View15, { style: styles15.originalBugHeader }, /* @__PURE__ */ React16.createElement(Text15, { style: styles15.originalBugIcon }, "\u{1F504}"), /* @__PURE__ */ React16.createElement(Text15, { style: styles15.originalBugTitle }, "Original Bug")), /* @__PURE__ */ React16.createElement(Text15, { style: styles15.originalBugBody }, "Retest of: ", issue.originalBugTitle)), issue.screenshotUrls && issue.screenshotUrls.length > 0 && /* @__PURE__ */ React16.createElement(View15, { style: styles15.screenshotSection }, /* @__PURE__ */ React16.createElement(Text15, { style: styles15.screenshotLabel }, "Screenshots (", issue.screenshotUrls.length, ")"), /* @__PURE__ */ React16.createElement(View15, { style: styles15.screenshotRow }, issue.screenshotUrls.map((url, i) => /* @__PURE__ */ React16.createElement(TouchableOpacity14, { key: i, onPress: () => Linking2.openURL(url), activeOpacity: 0.7 }, /* @__PURE__ */ React16.createElement(Image3, { source: { uri: url }, style: styles15.screenshotThumb }))))), /* @__PURE__ */ React16.createElement(View15, { style: styles15.metaSection }, issue.reporterName && /* @__PURE__ */ React16.createElement(Text15, { style: styles15.metaText }, "Reported by ", issue.reporterName), /* @__PURE__ */ React16.createElement(Text15, { style: styles15.metaTextSmall }, "Created ", formatRelativeTime(issue.createdAt), " ", "\xB7", " Updated ", formatRelativeTime(issue.updatedAt))));
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))));
15636
16167
  }
15637
- var styles15 = StyleSheet16.create({
16168
+ var styles15 = StyleSheet17.create({
15638
16169
  badgeRow: {
15639
16170
  flexDirection: "row",
15640
16171
  gap: 8,
@@ -15790,9 +16321,9 @@ function BugBearButton({
15790
16321
  return { x, y };
15791
16322
  };
15792
16323
  const initialPos = getInitialPosition();
15793
- const pan = useRef3(new Animated.ValueXY(initialPos)).current;
15794
- const isDragging = useRef3(false);
15795
- const panResponder = useRef3(
16324
+ const pan = useRef4(new Animated2.ValueXY(initialPos)).current;
16325
+ const isDragging = useRef4(false);
16326
+ const panResponder = useRef4(
15796
16327
  PanResponder.create({
15797
16328
  onStartShouldSetPanResponder: () => draggable,
15798
16329
  onMoveShouldSetPanResponder: (_, gs) => draggable && (Math.abs(gs.dx) > 5 || Math.abs(gs.dy) > 5),
@@ -15808,7 +16339,7 @@ function BugBearButton({
15808
16339
  if (Math.abs(gs.dx) > 5 || Math.abs(gs.dy) > 5) {
15809
16340
  isDragging.current = true;
15810
16341
  }
15811
- Animated.event(
16342
+ Animated2.event(
15812
16343
  [null, { dx: pan.x, dy: pan.y }],
15813
16344
  { useNativeDriver: false }
15814
16345
  )(_, gs);
@@ -15821,7 +16352,7 @@ function BugBearButton({
15821
16352
  const margin = 16;
15822
16353
  const snapX = currentX < screenWidth / 2 ? margin : screenWidth - buttonSize - margin;
15823
16354
  const snapY = Math.max(minY, Math.min(currentY, screenHeight - maxYOffset));
15824
- Animated.spring(pan, {
16355
+ Animated2.spring(pan, {
15825
16356
  toValue: { x: snapX, y: snapY },
15826
16357
  useNativeDriver: false,
15827
16358
  friction: 7,
@@ -15894,50 +16425,50 @@ function BugBearButton({
15894
16425
  const renderScreen = () => {
15895
16426
  switch (currentScreen.name) {
15896
16427
  case "HOME":
15897
- return /* @__PURE__ */ React17.createElement(HomeScreen, { nav });
16428
+ return /* @__PURE__ */ React18.createElement(HomeScreen, { nav });
15898
16429
  case "TEST_DETAIL":
15899
- return /* @__PURE__ */ React17.createElement(TestDetailScreen, { testId: currentScreen.testId, nav });
16430
+ return /* @__PURE__ */ React18.createElement(TestDetailScreen, { testId: currentScreen.testId, nav });
15900
16431
  case "TEST_LIST":
15901
- return /* @__PURE__ */ React17.createElement(TestListScreen, { nav });
16432
+ return /* @__PURE__ */ React18.createElement(TestListScreen, { nav });
15902
16433
  case "TEST_FEEDBACK":
15903
- return /* @__PURE__ */ React17.createElement(TestFeedbackScreen, { status: currentScreen.status, assignmentId: currentScreen.assignmentId, nav });
16434
+ return /* @__PURE__ */ React18.createElement(TestFeedbackScreen, { status: currentScreen.status, assignmentId: currentScreen.assignmentId, nav });
15904
16435
  case "REPORT":
15905
- return /* @__PURE__ */ React17.createElement(ReportScreen, { nav, prefill: currentScreen.prefill });
16436
+ return /* @__PURE__ */ React18.createElement(ReportScreen, { nav, prefill: currentScreen.prefill });
15906
16437
  case "REPORT_SUCCESS":
15907
- return /* @__PURE__ */ React17.createElement(ReportSuccessScreen, { nav });
16438
+ return /* @__PURE__ */ React18.createElement(ReportSuccessScreen, { nav });
15908
16439
  case "MESSAGE_LIST":
15909
- return /* @__PURE__ */ React17.createElement(MessageListScreen, { nav });
16440
+ return /* @__PURE__ */ React18.createElement(MessageListScreen, { nav });
15910
16441
  case "THREAD_DETAIL":
15911
- return /* @__PURE__ */ React17.createElement(ThreadDetailScreen, { thread: currentScreen.thread, nav });
16442
+ return /* @__PURE__ */ React18.createElement(ThreadDetailScreen, { thread: currentScreen.thread, nav });
15912
16443
  case "COMPOSE_MESSAGE":
15913
- return /* @__PURE__ */ React17.createElement(ComposeMessageScreen, { nav });
16444
+ return /* @__PURE__ */ React18.createElement(ComposeMessageScreen, { nav });
15914
16445
  case "ISSUE_LIST":
15915
- return /* @__PURE__ */ React17.createElement(IssueListScreen, { nav, category: currentScreen.category });
16446
+ return /* @__PURE__ */ React18.createElement(IssueListScreen, { nav, category: currentScreen.category });
15916
16447
  case "ISSUE_DETAIL":
15917
- return /* @__PURE__ */ React17.createElement(IssueDetailScreen, { nav, issue: currentScreen.issue });
16448
+ return /* @__PURE__ */ React18.createElement(IssueDetailScreen, { nav, issue: currentScreen.issue });
15918
16449
  case "PROFILE":
15919
- return /* @__PURE__ */ React17.createElement(ProfileScreen, { nav });
16450
+ return /* @__PURE__ */ React18.createElement(ProfileScreen, { nav });
15920
16451
  default:
15921
- return /* @__PURE__ */ React17.createElement(HomeScreen, { nav });
16452
+ return /* @__PURE__ */ React18.createElement(HomeScreen, { nav });
15922
16453
  }
15923
16454
  };
15924
- return /* @__PURE__ */ React17.createElement(React17.Fragment, null, /* @__PURE__ */ React17.createElement(
15925
- Animated.View,
16455
+ return /* @__PURE__ */ React18.createElement(React18.Fragment, null, /* @__PURE__ */ React18.createElement(
16456
+ Animated2.View,
15926
16457
  {
15927
16458
  style: [styles16.fabContainer, { transform: pan.getTranslateTransform() }, buttonStyle],
15928
16459
  ...panResponder.panHandlers
15929
16460
  },
15930
- /* @__PURE__ */ React17.createElement(
16461
+ /* @__PURE__ */ React18.createElement(
15931
16462
  TouchableOpacity15,
15932
16463
  {
15933
16464
  style: styles16.fab,
15934
16465
  onPress: () => setModalVisible(true),
15935
16466
  activeOpacity: draggable ? 1 : 0.7
15936
16467
  },
15937
- /* @__PURE__ */ React17.createElement(Image4, { source: { uri: BUGBEAR_LOGO_BASE64 }, style: styles16.fabIcon }),
15938
- badgeCount > 0 && /* @__PURE__ */ React17.createElement(View16, { style: styles16.badge }, /* @__PURE__ */ React17.createElement(Text16, { style: styles16.badgeText }, badgeCount > 9 ? "9+" : badgeCount))
16468
+ /* @__PURE__ */ React18.createElement(Image4, { source: { uri: BUGBEAR_LOGO_BASE64 }, style: styles16.fabIcon }),
16469
+ badgeCount > 0 && /* @__PURE__ */ React18.createElement(View17, { style: styles16.badge }, /* @__PURE__ */ React18.createElement(Text16, { style: styles16.badgeText }, badgeCount > 9 ? "9+" : badgeCount))
15939
16470
  )
15940
- ), /* @__PURE__ */ React17.createElement(
16471
+ ), /* @__PURE__ */ React18.createElement(
15941
16472
  Modal3,
15942
16473
  {
15943
16474
  visible: modalVisible,
@@ -15945,13 +16476,13 @@ function BugBearButton({
15945
16476
  transparent: true,
15946
16477
  onRequestClose: handleClose
15947
16478
  },
15948
- /* @__PURE__ */ React17.createElement(
16479
+ /* @__PURE__ */ React18.createElement(
15949
16480
  KeyboardAvoidingView,
15950
16481
  {
15951
16482
  behavior: Platform4.OS === "ios" ? "padding" : "height",
15952
16483
  style: styles16.modalOverlay
15953
16484
  },
15954
- /* @__PURE__ */ React17.createElement(View16, { style: styles16.modalContainer }, /* @__PURE__ */ React17.createElement(View16, { style: styles16.header }, /* @__PURE__ */ React17.createElement(View16, { style: styles16.headerLeft }, canGoBack ? /* @__PURE__ */ React17.createElement(View16, { style: styles16.headerNavRow }, /* @__PURE__ */ React17.createElement(TouchableOpacity15, { onPress: () => nav.pop(), style: styles16.backButton }, /* @__PURE__ */ React17.createElement(Text16, { style: styles16.backText }, "\u2190 Back")), /* @__PURE__ */ React17.createElement(TouchableOpacity15, { onPress: () => nav.reset(), style: styles16.homeButton }, /* @__PURE__ */ React17.createElement(Text16, { style: styles16.homeText }, "\u{1F3E0}"))) : /* @__PURE__ */ React17.createElement(View16, { style: styles16.headerTitleRow }, /* @__PURE__ */ React17.createElement(Text16, { style: styles16.headerTitle }, "BugBear"), testerInfo && /* @__PURE__ */ React17.createElement(TouchableOpacity15, { onPress: () => push({ name: "PROFILE" }) }, /* @__PURE__ */ React17.createElement(Text16, { style: styles16.headerName }, testerInfo.name, " \u270E")))), getHeaderTitle() ? /* @__PURE__ */ React17.createElement(Text16, { style: styles16.headerScreenTitle, numberOfLines: 1 }, getHeaderTitle()) : null, /* @__PURE__ */ React17.createElement(TouchableOpacity15, { onPress: handleClose, style: styles16.closeButton }, /* @__PURE__ */ React17.createElement(Text16, { style: styles16.closeText }, "\u2715"))), /* @__PURE__ */ React17.createElement(
16485
+ /* @__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(
15955
16486
  ScrollView3,
15956
16487
  {
15957
16488
  style: styles16.content,
@@ -15959,12 +16490,12 @@ function BugBearButton({
15959
16490
  keyboardShouldPersistTaps: "handled",
15960
16491
  showsVerticalScrollIndicator: false
15961
16492
  },
15962
- isLoading ? /* @__PURE__ */ React17.createElement(View16, { style: styles16.loadingContainer }, /* @__PURE__ */ React17.createElement(ActivityIndicator3, { size: "large", color: colors.blue }), /* @__PURE__ */ React17.createElement(Text16, { style: styles16.loadingText }, "Loading...")) : renderScreen()
16493
+ isLoading ? /* @__PURE__ */ React18.createElement(View17, { style: styles16.loadingContainer }, /* @__PURE__ */ React18.createElement(ActivityIndicator2, { size: "large", color: colors.blue }), /* @__PURE__ */ React18.createElement(Text16, { style: styles16.loadingText }, "Loading...")) : renderScreen()
15963
16494
  ))
15964
16495
  )
15965
16496
  ));
15966
16497
  }
15967
- var styles16 = StyleSheet17.create({
16498
+ var styles16 = StyleSheet18.create({
15968
16499
  // FAB
15969
16500
  fabContainer: {
15970
16501
  position: "absolute",
@@ -16106,8 +16637,8 @@ var styles16 = StyleSheet17.create({
16106
16637
  });
16107
16638
 
16108
16639
  // src/BugBearErrorBoundary.tsx
16109
- import React18, { Component } from "react";
16110
- import { View as View17, Text as Text17, TouchableOpacity as TouchableOpacity16, StyleSheet as StyleSheet18 } from "react-native";
16640
+ import React19, { Component } from "react";
16641
+ import { View as View18, Text as Text17, TouchableOpacity as TouchableOpacity16, StyleSheet as StyleSheet19 } from "react-native";
16111
16642
  var BugBearErrorBoundary = class extends Component {
16112
16643
  constructor(props) {
16113
16644
  super(props);
@@ -16152,7 +16683,7 @@ var BugBearErrorBoundary = class extends Component {
16152
16683
  if (fallback) {
16153
16684
  return fallback;
16154
16685
  }
16155
- return /* @__PURE__ */ React18.createElement(View17, { style: styles17.container }, /* @__PURE__ */ React18.createElement(Text17, { style: styles17.title }, "Something went wrong"), /* @__PURE__ */ React18.createElement(Text17, { style: styles17.message }, error.message), /* @__PURE__ */ React18.createElement(TouchableOpacity16, { style: styles17.button, onPress: this.reset }, /* @__PURE__ */ React18.createElement(Text17, { style: styles17.buttonText }, "Try Again")), /* @__PURE__ */ React18.createElement(Text17, { style: styles17.caption }, "The error has been captured by BugBear"));
16686
+ return /* @__PURE__ */ React19.createElement(View18, { style: styles17.container }, /* @__PURE__ */ React19.createElement(Text17, { style: styles17.title }, "Something went wrong"), /* @__PURE__ */ React19.createElement(Text17, { style: styles17.message }, error.message), /* @__PURE__ */ React19.createElement(TouchableOpacity16, { style: styles17.button, onPress: this.reset }, /* @__PURE__ */ React19.createElement(Text17, { style: styles17.buttonText }, "Try Again")), /* @__PURE__ */ React19.createElement(Text17, { style: styles17.caption }, "The error has been captured by BugBear"));
16156
16687
  }
16157
16688
  return children;
16158
16689
  }
@@ -16163,7 +16694,7 @@ function useErrorContext() {
16163
16694
  getEnhancedContext: () => contextCapture.getEnhancedContext()
16164
16695
  };
16165
16696
  }
16166
- var styles17 = StyleSheet18.create({
16697
+ var styles17 = StyleSheet19.create({
16167
16698
  container: {
16168
16699
  padding: 20,
16169
16700
  margin: 20,