@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.js CHANGED
@@ -42,14 +42,14 @@ module.exports = __toCommonJS(index_exports);
42
42
  var import_react = __toESM(require("react"));
43
43
 
44
44
  // ../../node_modules/tslib/tslib.es6.mjs
45
- function __rest(s, e) {
45
+ function __rest(s2, e) {
46
46
  var t = {};
47
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
48
- t[p] = s[p];
49
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
50
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
51
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
52
- t[p[i]] = s[p[i]];
47
+ for (var p in s2) if (Object.prototype.hasOwnProperty.call(s2, p) && e.indexOf(p) < 0)
48
+ t[p] = s2[p];
49
+ if (s2 != null && typeof Object.getOwnPropertySymbols === "function")
50
+ for (var i = 0, p = Object.getOwnPropertySymbols(s2); i < p.length; i++) {
51
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s2, p[i]))
52
+ t[p[i]] = s2[p[i]];
53
53
  }
54
54
  return t;
55
55
  }
@@ -864,9 +864,9 @@ var PostgrestFilterBuilder = class extends PostgrestTransformBuilder {
864
864
  * @param values - The values array to filter with
865
865
  */
866
866
  in(column, values) {
867
- const cleanedValues = Array.from(new Set(values)).map((s) => {
868
- if (typeof s === "string" && PostgrestReservedCharsRegexp.test(s)) return `"${s}"`;
869
- else return `${s}`;
867
+ const cleanedValues = Array.from(new Set(values)).map((s2) => {
868
+ if (typeof s2 === "string" && PostgrestReservedCharsRegexp.test(s2)) return `"${s2}"`;
869
+ else return `${s2}`;
870
870
  }).join(",");
871
871
  this.url.searchParams.append(column, `in.(${cleanedValues})`);
872
872
  return this;
@@ -878,9 +878,9 @@ var PostgrestFilterBuilder = class extends PostgrestTransformBuilder {
878
878
  * @param values - The values array to filter with
879
879
  */
880
880
  notIn(column, values) {
881
- const cleanedValues = Array.from(new Set(values)).map((s) => {
882
- if (typeof s === "string" && PostgrestReservedCharsRegexp.test(s)) return `"${s}"`;
883
- else return `${s}`;
881
+ const cleanedValues = Array.from(new Set(values)).map((s2) => {
882
+ if (typeof s2 === "string" && PostgrestReservedCharsRegexp.test(s2)) return `"${s2}"`;
883
+ else return `${s2}`;
884
884
  }).join(",");
885
885
  this.url.searchParams.append(column, `not.in.(${cleanedValues})`);
886
886
  return this;
@@ -11635,31 +11635,306 @@ function captureError(error, errorInfo) {
11635
11635
  componentStack: errorInfo?.componentStack
11636
11636
  };
11637
11637
  }
11638
+ var LocalStorageAdapter = class {
11639
+ constructor() {
11640
+ this.fallback = /* @__PURE__ */ new Map();
11641
+ }
11642
+ get isAvailable() {
11643
+ try {
11644
+ const key = "__bugbear_test__";
11645
+ localStorage.setItem(key, "1");
11646
+ localStorage.removeItem(key);
11647
+ return true;
11648
+ } catch {
11649
+ return false;
11650
+ }
11651
+ }
11652
+ async getItem(key) {
11653
+ if (this.isAvailable) return localStorage.getItem(key);
11654
+ return this.fallback.get(key) ?? null;
11655
+ }
11656
+ async setItem(key, value) {
11657
+ if (this.isAvailable) {
11658
+ localStorage.setItem(key, value);
11659
+ } else {
11660
+ this.fallback.set(key, value);
11661
+ }
11662
+ }
11663
+ async removeItem(key) {
11664
+ if (this.isAvailable) {
11665
+ localStorage.removeItem(key);
11666
+ } else {
11667
+ this.fallback.delete(key);
11668
+ }
11669
+ }
11670
+ };
11671
+ var OfflineQueue = class {
11672
+ constructor(config) {
11673
+ this.items = [];
11674
+ this.storageKey = "bugbear_offline_queue";
11675
+ this.flushing = false;
11676
+ this.handlers = /* @__PURE__ */ new Map();
11677
+ this.maxItems = config.maxItems ?? 50;
11678
+ this.maxRetries = config.maxRetries ?? 5;
11679
+ this.storage = config.storage ?? new LocalStorageAdapter();
11680
+ }
11681
+ // ── Flush handler registration ──────────────────────────────
11682
+ /** Register a handler that replays a queued operation. */
11683
+ registerHandler(type, handler) {
11684
+ this.handlers.set(type, handler);
11685
+ }
11686
+ // ── Change listener ─────────────────────────────────────────
11687
+ /** Subscribe to queue count changes (for UI badges). */
11688
+ onChange(callback) {
11689
+ this.listener = callback;
11690
+ }
11691
+ notify() {
11692
+ this.listener?.(this.items.length);
11693
+ }
11694
+ // ── Persistence ─────────────────────────────────────────────
11695
+ /** Load queue from persistent storage. Call once after construction. */
11696
+ async load() {
11697
+ try {
11698
+ const raw = await this.storage.getItem(this.storageKey);
11699
+ if (raw) {
11700
+ this.items = JSON.parse(raw);
11701
+ }
11702
+ } catch {
11703
+ this.items = [];
11704
+ }
11705
+ this.notify();
11706
+ }
11707
+ async save() {
11708
+ try {
11709
+ await this.storage.setItem(this.storageKey, JSON.stringify(this.items));
11710
+ } catch (err) {
11711
+ console.error("BugBear: Failed to persist offline queue", err);
11712
+ }
11713
+ this.notify();
11714
+ }
11715
+ // ── Enqueue ─────────────────────────────────────────────────
11716
+ /** Add a failed operation to the queue. Returns the item ID. */
11717
+ async enqueue(type, payload) {
11718
+ if (this.items.length >= this.maxItems) {
11719
+ this.items.shift();
11720
+ }
11721
+ const id = `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
11722
+ this.items.push({ id, type, payload, createdAt: Date.now(), retries: 0 });
11723
+ await this.save();
11724
+ return id;
11725
+ }
11726
+ // ── Accessors ───────────────────────────────────────────────
11727
+ /** Number of items waiting to be flushed. */
11728
+ get count() {
11729
+ return this.items.length;
11730
+ }
11731
+ /** Read-only snapshot of pending items. */
11732
+ get pending() {
11733
+ return [...this.items];
11734
+ }
11735
+ /** Whether a flush is currently in progress. */
11736
+ get isFlushing() {
11737
+ return this.flushing;
11738
+ }
11739
+ // ── Flush ───────────────────────────────────────────────────
11740
+ /**
11741
+ * Process all queued items in FIFO order.
11742
+ * Stops early if a network error is encountered (still offline).
11743
+ */
11744
+ async flush() {
11745
+ if (this.flushing || this.items.length === 0) {
11746
+ return { flushed: 0, failed: 0 };
11747
+ }
11748
+ this.flushing = true;
11749
+ let flushed = 0;
11750
+ let failed = 0;
11751
+ const snapshot = [...this.items];
11752
+ for (const item of snapshot) {
11753
+ const handler = this.handlers.get(item.type);
11754
+ if (!handler) {
11755
+ failed++;
11756
+ continue;
11757
+ }
11758
+ try {
11759
+ const result = await handler(item.payload);
11760
+ if (result.success) {
11761
+ this.items = this.items.filter((i) => i.id !== item.id);
11762
+ flushed++;
11763
+ } else if (isNetworkError(result.error)) {
11764
+ break;
11765
+ } else {
11766
+ const idx = this.items.findIndex((i) => i.id === item.id);
11767
+ if (idx !== -1) {
11768
+ this.items[idx].retries++;
11769
+ if (this.items[idx].retries >= this.maxRetries) {
11770
+ this.items.splice(idx, 1);
11771
+ }
11772
+ }
11773
+ failed++;
11774
+ }
11775
+ } catch {
11776
+ break;
11777
+ }
11778
+ }
11779
+ await this.save();
11780
+ this.flushing = false;
11781
+ return { flushed, failed };
11782
+ }
11783
+ // ── Clear ───────────────────────────────────────────────────
11784
+ /** Drop all queued items. */
11785
+ async clear() {
11786
+ this.items = [];
11787
+ await this.save();
11788
+ }
11789
+ };
11790
+ function isNetworkError(error) {
11791
+ if (!error) return false;
11792
+ const msg = error.toLowerCase();
11793
+ 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
11794
+ msg.includes("the internet connection appears to be offline") || msg.includes("a]server with the specified hostname could not be found");
11795
+ }
11638
11796
  var formatPgError = (e) => {
11639
11797
  if (!e || typeof e !== "object") return { raw: e };
11640
11798
  const { message, code, details, hint } = e;
11641
11799
  return { message, code, details, hint };
11642
11800
  };
11643
- var DEFAULT_SUPABASE_URL = "https://kyxgzjnqgvapvlnvqawz.supabase.co";
11644
- var getEnvVar = (key) => {
11645
- try {
11646
- if (typeof process !== "undefined" && process.env) {
11647
- return process.env[key];
11648
- }
11649
- } catch {
11650
- }
11651
- return void 0;
11652
- };
11653
- var HOSTED_BUGBEAR_ANON_KEY = getEnvVar("BUGBEAR_ANON_KEY") || getEnvVar("NEXT_PUBLIC_BUGBEAR_ANON_KEY") || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imt5eGd6am5xZ3ZhcHZsbnZxYXd6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjkyNjgwNDIsImV4cCI6MjA4NDg0NDA0Mn0.NUkAlCHLFjeRoisbmNUVoGb4R6uQ8xs5LAEIX1BWTwU";
11654
11801
  var BugBearClient = class {
11655
11802
  constructor(config) {
11656
11803
  this.navigationHistory = [];
11657
11804
  this.reportSubmitInFlight = false;
11805
+ this._queue = null;
11806
+ this.realtimeChannels = [];
11807
+ if (!config.supabaseUrl) {
11808
+ throw new Error("BugBear: supabaseUrl is required. Get it from your BugBear project settings.");
11809
+ }
11810
+ if (!config.supabaseAnonKey) {
11811
+ throw new Error("BugBear: supabaseAnonKey is required. Get it from your BugBear project settings.");
11812
+ }
11658
11813
  this.config = config;
11659
- this.supabase = createClient(
11660
- config.supabaseUrl || DEFAULT_SUPABASE_URL,
11661
- config.supabaseAnonKey || HOSTED_BUGBEAR_ANON_KEY
11662
- );
11814
+ this.supabase = createClient(config.supabaseUrl, config.supabaseAnonKey);
11815
+ if (config.offlineQueue?.enabled) {
11816
+ this._queue = new OfflineQueue({
11817
+ enabled: true,
11818
+ maxItems: config.offlineQueue.maxItems,
11819
+ maxRetries: config.offlineQueue.maxRetries
11820
+ });
11821
+ this.registerQueueHandlers();
11822
+ }
11823
+ }
11824
+ // ── Offline Queue ─────────────────────────────────────────
11825
+ /**
11826
+ * Access the offline queue (if enabled).
11827
+ * Use this to check queue.count, subscribe to changes, or trigger flush.
11828
+ */
11829
+ get queue() {
11830
+ return this._queue;
11831
+ }
11832
+ /**
11833
+ * Initialize the offline queue with a platform-specific storage adapter.
11834
+ * Must be called after construction for React Native (which supplies AsyncStorage).
11835
+ * Web callers can skip this — LocalStorageAdapter is the default.
11836
+ */
11837
+ async initQueue(storage) {
11838
+ if (!this._queue) return;
11839
+ if (storage) {
11840
+ this._queue = new OfflineQueue({
11841
+ enabled: true,
11842
+ maxItems: this.config.offlineQueue?.maxItems,
11843
+ maxRetries: this.config.offlineQueue?.maxRetries,
11844
+ storage
11845
+ });
11846
+ this.registerQueueHandlers();
11847
+ }
11848
+ await this._queue.load();
11849
+ }
11850
+ registerQueueHandlers() {
11851
+ if (!this._queue) return;
11852
+ this._queue.registerHandler("report", async (payload) => {
11853
+ const { error } = await this.supabase.from("reports").insert(payload).select("id").single();
11854
+ if (error) return { success: false, error: error.message };
11855
+ return { success: true };
11856
+ });
11857
+ this._queue.registerHandler("message", async (payload) => {
11858
+ const { error } = await this.supabase.from("discussion_messages").insert(payload);
11859
+ if (error) return { success: false, error: error.message };
11860
+ return { success: true };
11861
+ });
11862
+ this._queue.registerHandler("feedback", async (payload) => {
11863
+ const { error } = await this.supabase.from("test_feedback").insert(payload);
11864
+ if (error) return { success: false, error: error.message };
11865
+ return { success: true };
11866
+ });
11867
+ }
11868
+ // ── Realtime Subscriptions ─────────────────────────────────
11869
+ /** Whether realtime is enabled in config. */
11870
+ get realtimeEnabled() {
11871
+ return !!this.config.realtime?.enabled;
11872
+ }
11873
+ /**
11874
+ * Subscribe to postgres_changes on relevant tables.
11875
+ * Each callback fires when the corresponding table has changes —
11876
+ * the provider should call its refresh function in response.
11877
+ * Returns a cleanup function that unsubscribes all channels.
11878
+ */
11879
+ subscribeToChanges(callbacks) {
11880
+ this.unsubscribeAll();
11881
+ const projectId = this.config.projectId;
11882
+ const debounce = (fn, ms = 500) => {
11883
+ let timer;
11884
+ return () => {
11885
+ clearTimeout(timer);
11886
+ timer = setTimeout(fn, ms);
11887
+ };
11888
+ };
11889
+ if (callbacks.onAssignmentChange) {
11890
+ const debouncedCb = debounce(callbacks.onAssignmentChange);
11891
+ const channel = this.supabase.channel("bugbear-assignments").on("postgres_changes", {
11892
+ event: "*",
11893
+ schema: "public",
11894
+ table: "test_assignments",
11895
+ filter: `project_id=eq.${projectId}`
11896
+ }, debouncedCb).subscribe((status) => {
11897
+ if (status === "CHANNEL_ERROR") {
11898
+ console.warn("BugBear: Realtime subscription failed for test_assignments");
11899
+ }
11900
+ });
11901
+ this.realtimeChannels.push(channel);
11902
+ }
11903
+ if (callbacks.onMessageChange) {
11904
+ const debouncedCb = debounce(callbacks.onMessageChange);
11905
+ const channel = this.supabase.channel("bugbear-messages").on("postgres_changes", {
11906
+ event: "INSERT",
11907
+ schema: "public",
11908
+ table: "discussion_messages"
11909
+ }, debouncedCb).subscribe((status) => {
11910
+ if (status === "CHANNEL_ERROR") {
11911
+ console.warn("BugBear: Realtime subscription failed for discussion_messages");
11912
+ }
11913
+ });
11914
+ this.realtimeChannels.push(channel);
11915
+ }
11916
+ if (callbacks.onReportChange) {
11917
+ const debouncedCb = debounce(callbacks.onReportChange);
11918
+ const channel = this.supabase.channel("bugbear-reports").on("postgres_changes", {
11919
+ event: "UPDATE",
11920
+ schema: "public",
11921
+ table: "reports",
11922
+ filter: `project_id=eq.${projectId}`
11923
+ }, debouncedCb).subscribe((status) => {
11924
+ if (status === "CHANNEL_ERROR") {
11925
+ console.warn("BugBear: Realtime subscription failed for reports");
11926
+ }
11927
+ });
11928
+ this.realtimeChannels.push(channel);
11929
+ }
11930
+ return () => this.unsubscribeAll();
11931
+ }
11932
+ /** Remove all active Realtime channels. */
11933
+ unsubscribeAll() {
11934
+ for (const channel of this.realtimeChannels) {
11935
+ this.supabase.removeChannel(channel);
11936
+ }
11937
+ this.realtimeChannels = [];
11663
11938
  }
11664
11939
  /**
11665
11940
  * Track navigation for context.
@@ -11719,6 +11994,7 @@ var BugBearClient = class {
11719
11994
  return { success: false, error: "A report is already being submitted" };
11720
11995
  }
11721
11996
  this.reportSubmitInFlight = true;
11997
+ let fullReport;
11722
11998
  try {
11723
11999
  const validationError = this.validateReport(report);
11724
12000
  if (validationError) {
@@ -11735,7 +12011,7 @@ var BugBearClient = class {
11735
12011
  return { success: false, error: "User not authenticated" };
11736
12012
  }
11737
12013
  const testerInfo = await this.getTesterInfo();
11738
- const fullReport = {
12014
+ fullReport = {
11739
12015
  project_id: this.config.projectId,
11740
12016
  reporter_id: userInfo.id,
11741
12017
  // User ID from host app (required)
@@ -11762,6 +12038,10 @@ var BugBearClient = class {
11762
12038
  };
11763
12039
  const { data, error } = await this.supabase.from("reports").insert(fullReport).select("id").single();
11764
12040
  if (error) {
12041
+ if (this._queue && isNetworkError(error.message)) {
12042
+ await this._queue.enqueue("report", fullReport);
12043
+ return { success: false, queued: true, error: "Queued \u2014 will send when online" };
12044
+ }
11765
12045
  console.error("BugBear: Failed to submit report", error.message);
11766
12046
  return { success: false, error: error.message };
11767
12047
  }
@@ -11771,6 +12051,10 @@ var BugBearClient = class {
11771
12051
  return { success: true, reportId: data.id };
11772
12052
  } catch (err) {
11773
12053
  const message = err instanceof Error ? err.message : "Unknown error";
12054
+ if (this._queue && fullReport && isNetworkError(message)) {
12055
+ await this._queue.enqueue("report", fullReport);
12056
+ return { success: false, queued: true, error: "Queued \u2014 will send when online" };
12057
+ }
11774
12058
  return { success: false, error: message };
11775
12059
  } finally {
11776
12060
  this.reportSubmitInFlight = false;
@@ -11780,10 +12064,13 @@ var BugBearClient = class {
11780
12064
  * Get assigned tests for current user
11781
12065
  * First looks up the tester by email, then fetches their assignments
11782
12066
  */
11783
- async getAssignedTests() {
12067
+ async getAssignedTests(options) {
11784
12068
  try {
11785
12069
  const testerInfo = await this.getTesterInfo();
11786
12070
  if (!testerInfo) return [];
12071
+ const pageSize = Math.min(options?.pageSize ?? 100, 100);
12072
+ const from = (options?.page ?? 0) * pageSize;
12073
+ const to = from + pageSize - 1;
11787
12074
  const { data, error } = await this.supabase.from("test_assignments").select(`
11788
12075
  id,
11789
12076
  status,
@@ -11824,12 +12111,18 @@ var BugBearClient = class {
11824
12111
  login_hint
11825
12112
  )
11826
12113
  )
11827
- `).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true }).limit(100);
12114
+ `).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true }).range(from, to);
11828
12115
  if (error) {
11829
12116
  console.error("BugBear: Failed to fetch assignments", formatPgError(error));
11830
12117
  return [];
11831
12118
  }
11832
- const mapped = (data || []).map((item) => ({
12119
+ const mapped = (data || []).filter((item) => {
12120
+ if (!item.test_case) {
12121
+ console.warn("BugBear: Assignment returned without test_case", { id: item.id });
12122
+ return false;
12123
+ }
12124
+ return true;
12125
+ }).map((item) => ({
11833
12126
  id: item.id,
11834
12127
  status: item.status,
11835
12128
  startedAt: item.started_at,
@@ -12077,6 +12370,7 @@ var BugBearClient = class {
12077
12370
  * This empowers testers to shape better tests over time
12078
12371
  */
12079
12372
  async submitTestFeedback(options) {
12373
+ let feedbackPayload;
12080
12374
  try {
12081
12375
  const testerInfo = await this.getTesterInfo();
12082
12376
  if (!testerInfo) {
@@ -12086,7 +12380,17 @@ var BugBearClient = class {
12086
12380
  if (feedback.rating < 1 || feedback.rating > 5) {
12087
12381
  return { success: false, error: "Rating must be between 1 and 5" };
12088
12382
  }
12089
- const { error: feedbackError } = await this.supabase.from("test_feedback").insert({
12383
+ const optionalRatings = [
12384
+ { name: "clarityRating", value: feedback.clarityRating },
12385
+ { name: "stepsRating", value: feedback.stepsRating },
12386
+ { name: "relevanceRating", value: feedback.relevanceRating }
12387
+ ];
12388
+ for (const { name, value } of optionalRatings) {
12389
+ if (value !== void 0 && value !== null && (value < 1 || value > 5)) {
12390
+ return { success: false, error: `${name} must be between 1 and 5` };
12391
+ }
12392
+ }
12393
+ feedbackPayload = {
12090
12394
  project_id: this.config.projectId,
12091
12395
  test_case_id: testCaseId,
12092
12396
  assignment_id: assignmentId || null,
@@ -12104,8 +12408,13 @@ var BugBearClient = class {
12104
12408
  platform: this.getDeviceInfo().platform,
12105
12409
  time_to_complete_seconds: timeToCompleteSeconds || null,
12106
12410
  screenshot_urls: screenshotUrls || []
12107
- });
12411
+ };
12412
+ const { error: feedbackError } = await this.supabase.from("test_feedback").insert(feedbackPayload);
12108
12413
  if (feedbackError) {
12414
+ if (this._queue && isNetworkError(feedbackError.message)) {
12415
+ await this._queue.enqueue("feedback", feedbackPayload);
12416
+ return { success: false, queued: true, error: "Queued \u2014 will send when online" };
12417
+ }
12109
12418
  console.error("BugBear: Failed to submit feedback", feedbackError);
12110
12419
  return { success: false, error: feedbackError.message };
12111
12420
  }
@@ -12122,6 +12431,10 @@ var BugBearClient = class {
12122
12431
  return { success: true };
12123
12432
  } catch (err) {
12124
12433
  const message = err instanceof Error ? err.message : "Unknown error";
12434
+ if (this._queue && feedbackPayload && isNetworkError(message)) {
12435
+ await this._queue.enqueue("feedback", feedbackPayload);
12436
+ return { success: false, queued: true, error: "Queued \u2014 will send when online" };
12437
+ }
12125
12438
  console.error("BugBear: Error submitting feedback", err);
12126
12439
  return { success: false, error: message };
12127
12440
  }
@@ -12756,6 +13069,7 @@ var BugBearClient = class {
12756
13069
  * Send a message to a thread
12757
13070
  */
12758
13071
  async sendMessage(threadId, content, attachments) {
13072
+ let insertData;
12759
13073
  try {
12760
13074
  const testerInfo = await this.getTesterInfo();
12761
13075
  if (!testerInfo) {
@@ -12767,7 +13081,7 @@ var BugBearClient = class {
12767
13081
  console.error("BugBear: Rate limit exceeded for messages");
12768
13082
  return false;
12769
13083
  }
12770
- const insertData = {
13084
+ insertData = {
12771
13085
  thread_id: threadId,
12772
13086
  sender_type: "tester",
12773
13087
  sender_tester_id: testerInfo.id,
@@ -12782,12 +13096,21 @@ var BugBearClient = class {
12782
13096
  }
12783
13097
  const { error } = await this.supabase.from("discussion_messages").insert(insertData);
12784
13098
  if (error) {
13099
+ if (this._queue && isNetworkError(error.message)) {
13100
+ await this._queue.enqueue("message", insertData);
13101
+ return false;
13102
+ }
12785
13103
  console.error("BugBear: Failed to send message", formatPgError(error));
12786
13104
  return false;
12787
13105
  }
12788
13106
  await this.markThreadAsRead(threadId);
12789
13107
  return true;
12790
13108
  } catch (err) {
13109
+ const message = err instanceof Error ? err.message : "Unknown error";
13110
+ if (this._queue && insertData && isNetworkError(message)) {
13111
+ await this._queue.enqueue("message", insertData);
13112
+ return false;
13113
+ }
12791
13114
  console.error("BugBear: Error sending message", err);
12792
13115
  return false;
12793
13116
  }
@@ -12974,7 +13297,7 @@ var BugBearClient = class {
12974
13297
  console.error("BugBear: Failed to fetch session history", formatPgError(error));
12975
13298
  return [];
12976
13299
  }
12977
- return (data || []).map((s) => this.transformSession(s));
13300
+ return (data || []).map((s2) => this.transformSession(s2));
12978
13301
  } catch (err) {
12979
13302
  console.error("BugBear: Error fetching session history", err);
12980
13303
  return [];
@@ -13163,6 +13486,7 @@ var BugBearContext = (0, import_react.createContext)({
13163
13486
  issueCounts: { open: 0, done: 0, reopened: 0 },
13164
13487
  refreshIssueCounts: async () => {
13165
13488
  },
13489
+ queuedCount: 0,
13166
13490
  dashboardUrl: void 0,
13167
13491
  onError: void 0
13168
13492
  });
@@ -13179,6 +13503,7 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
13179
13503
  const [threads, setThreads] = (0, import_react.useState)([]);
13180
13504
  const [unreadCount, setUnreadCount] = (0, import_react.useState)(0);
13181
13505
  const [issueCounts, setIssueCounts] = (0, import_react.useState)({ open: 0, done: 0, reopened: 0 });
13506
+ const [queuedCount, setQueuedCount] = (0, import_react.useState)(0);
13182
13507
  const [activeSession, setActiveSession] = (0, import_react.useState)(null);
13183
13508
  const [sessionFindings, setSessionFindings] = (0, import_react.useState)([]);
13184
13509
  const hasInitialized = (0, import_react.useRef)(false);
@@ -13338,18 +13663,46 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
13338
13663
  hasInitialized.current = true;
13339
13664
  contextCapture.startCapture();
13340
13665
  const newClient = createBugBear(config);
13666
+ if (newClient.queue) {
13667
+ newClient.queue.onChange(setQueuedCount);
13668
+ newClient.initQueue();
13669
+ }
13341
13670
  setClient(newClient);
13342
13671
  initializeBugBear(newClient);
13343
13672
  }
13344
13673
  }, [enabled, config, initializeBugBear]);
13674
+ (0, import_react.useEffect)(() => {
13675
+ if (!client?.queue) return;
13676
+ const subscription = import_react_native.AppState.addEventListener("change", (state) => {
13677
+ if (state === "active" && client.queue && client.queue.count > 0) {
13678
+ client.queue.flush();
13679
+ }
13680
+ });
13681
+ if (client.queue.count > 0) {
13682
+ client.queue.flush();
13683
+ }
13684
+ return () => subscription.remove();
13685
+ }, [client]);
13345
13686
  (0, import_react.useEffect)(() => {
13346
13687
  if (!client || !isTester || !isQAEnabled) return;
13688
+ let unsubscribe;
13689
+ if (client.realtimeEnabled) {
13690
+ unsubscribe = client.subscribeToChanges({
13691
+ onAssignmentChange: refreshAssignments,
13692
+ onMessageChange: refreshThreads,
13693
+ onReportChange: refreshIssueCounts
13694
+ });
13695
+ }
13696
+ const pollInterval = client.realtimeEnabled ? 12e4 : 3e4;
13347
13697
  const interval = setInterval(() => {
13348
13698
  refreshThreads();
13349
13699
  refreshIssueCounts();
13350
- }, 3e4);
13351
- return () => clearInterval(interval);
13352
- }, [client, isTester, isQAEnabled, refreshThreads, refreshIssueCounts]);
13700
+ }, pollInterval);
13701
+ return () => {
13702
+ clearInterval(interval);
13703
+ unsubscribe?.();
13704
+ };
13705
+ }, [client, isTester, isQAEnabled, refreshThreads, refreshIssueCounts, refreshAssignments]);
13353
13706
  const currentAssignment = assignments.find(
13354
13707
  (a) => a.status === "in_progress"
13355
13708
  ) || assignments.find(
@@ -13392,6 +13745,7 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
13392
13745
  // Issue tracking
13393
13746
  issueCounts,
13394
13747
  refreshIssueCounts,
13748
+ queuedCount,
13395
13749
  dashboardUrl: config.dashboardUrl,
13396
13750
  onError: config.onError
13397
13751
  }
@@ -13401,8 +13755,8 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
13401
13755
  }
13402
13756
 
13403
13757
  // src/BugBearButton.tsx
13404
- var import_react19 = __toESM(require("react"));
13405
- var import_react_native18 = require("react-native");
13758
+ var import_react20 = __toESM(require("react"));
13759
+ var import_react_native19 = require("react-native");
13406
13760
 
13407
13761
  // src/widget/logo.ts
13408
13762
  var BUGBEAR_LOGO_BASE64 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAAAXNSR0IArs4c6QAAAJhlWElmTU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUAAAABAAAARgEoAAMAAAABAAIAAIdpAAQAAAABAAAATgAAAAAAAABIAAAAAQAAAEgAAAABAASQBAACAAAAFAAAAISgAQADAAAAAQABAACgAgAEAAAAAQAAAGCgAwAEAAAAAQAAAGAAAAAAMjAyNjowMToyNCAxNjoyMTozOABbbVCuAAAACXBIWXMAAAsTAAALEwEAmpwYAAACo2lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczpJcHRjNHhtcEV4dD0iaHR0cDovL2lwdGMub3JnL3N0ZC9JcHRjNHhtcEV4dC8yMDA4LTAyLTI5LyIKICAgICAgICAgICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIj4KICAgICAgICAgPElwdGM0eG1wRXh0OkRpZ2l0YWxTb3VyY2VUeXBlPmh0dHA6Ly9jdi5pcHRjLm9yZy9uZXdzY29kZXMvZGlnaXRhbHNvdXJjZXR5cGUvdHJhaW5lZEFsZ29yaXRobWljTWVkaWE8L0lwdGM0eG1wRXh0OkRpZ2l0YWxTb3VyY2VUeXBlPgogICAgICAgICA8SXB0YzR4bXBFeHQ6RGlnSW1hZ2VHVUlEPmZjNzJlN2Q2LTYyYTEtNDE1ZS04MjY5LWM2NjA4MjY0OWRiMDwvSXB0YzR4bXBFeHQ6RGlnSW1hZ2VHVUlEPgogICAgICAgICA8eG1wOkNyZWF0ZURhdGU+MjAyNi0wMS0yNFQxNjoyMTozODwveG1wOkNyZWF0ZURhdGU+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+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";
@@ -13546,9 +13900,9 @@ var shared = import_react_native2.StyleSheet.create({
13546
13900
  function formatElapsedTime(seconds) {
13547
13901
  const h = Math.floor(seconds / 3600);
13548
13902
  const m = Math.floor(seconds % 3600 / 60);
13549
- const s = seconds % 60;
13550
- if (h > 0) return `${h}:${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`;
13551
- return `${m}:${s.toString().padStart(2, "0")}`;
13903
+ const s2 = seconds % 60;
13904
+ if (h > 0) return `${h}:${m.toString().padStart(2, "0")}:${s2.toString().padStart(2, "0")}`;
13905
+ return `${m}:${s2.toString().padStart(2, "0")}`;
13552
13906
  }
13553
13907
  function formatRelativeTime(dateString) {
13554
13908
  const date = new Date(dateString);
@@ -13620,11 +13974,90 @@ var templateInfo = {
13620
13974
  };
13621
13975
 
13622
13976
  // src/widget/screens/HomeScreen.tsx
13977
+ var import_react4 = __toESM(require("react"));
13978
+ var import_react_native4 = require("react-native");
13979
+
13980
+ // src/widget/Skeleton.tsx
13623
13981
  var import_react3 = __toESM(require("react"));
13624
13982
  var import_react_native3 = require("react-native");
13625
- function HomeScreen({ nav }) {
13626
- const { assignments, unreadCount, threads, refreshAssignments, refreshThreads, issueCounts, refreshIssueCounts, dashboardUrl } = useBugBear();
13983
+ function usePulse(delay = 0) {
13984
+ const opacity = (0, import_react3.useRef)(new import_react_native3.Animated.Value(0.6)).current;
13627
13985
  (0, import_react3.useEffect)(() => {
13986
+ const timeout = setTimeout(() => {
13987
+ import_react_native3.Animated.loop(
13988
+ import_react_native3.Animated.sequence([
13989
+ import_react_native3.Animated.timing(opacity, { toValue: 0.25, duration: 750, useNativeDriver: true }),
13990
+ import_react_native3.Animated.timing(opacity, { toValue: 0.6, duration: 750, useNativeDriver: true })
13991
+ ])
13992
+ ).start();
13993
+ }, delay);
13994
+ return () => clearTimeout(timeout);
13995
+ }, [opacity, delay]);
13996
+ return opacity;
13997
+ }
13998
+ function Bar({ width = "100%", height = 12, radius = 6, delay = 0 }) {
13999
+ const opacity = usePulse(delay);
14000
+ return /* @__PURE__ */ import_react3.default.createElement(
14001
+ import_react_native3.Animated.View,
14002
+ {
14003
+ style: {
14004
+ width,
14005
+ height,
14006
+ borderRadius: radius,
14007
+ backgroundColor: colors.border,
14008
+ opacity
14009
+ }
14010
+ }
14011
+ );
14012
+ }
14013
+ function Circle({ size = 20, delay = 0 }) {
14014
+ const opacity = usePulse(delay);
14015
+ return /* @__PURE__ */ import_react3.default.createElement(
14016
+ import_react_native3.Animated.View,
14017
+ {
14018
+ style: {
14019
+ width: size,
14020
+ height: size,
14021
+ borderRadius: size / 2,
14022
+ backgroundColor: colors.border,
14023
+ opacity
14024
+ }
14025
+ }
14026
+ );
14027
+ }
14028
+ function HomeScreenSkeleton() {
14029
+ return /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, null, /* @__PURE__ */ import_react3.default.createElement(Bar, { width: "100%", height: 100, radius: 16 }), /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { style: { height: 20 } }), /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { style: s.actionGrid }, [0, 1, 2, 3].map((i) => /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { key: i, style: s.actionCard }, /* @__PURE__ */ import_react3.default.createElement(Circle, { size: 28, delay: i * 80 }), /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { style: { height: 8 } }), /* @__PURE__ */ import_react3.default.createElement(Bar, { width: 60, height: 10, delay: i * 80 })))), /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { style: s.issueGrid }, [0, 1, 2].map((i) => /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { key: i, style: s.issueCard }, /* @__PURE__ */ import_react3.default.createElement(Bar, { width: 30, height: 18, delay: i * 100 }), /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { style: { height: 6 } }), /* @__PURE__ */ import_react3.default.createElement(Bar, { width: 40, height: 8, delay: i * 100 })))), /* @__PURE__ */ import_react3.default.createElement(Bar, { width: "100%", height: 6, radius: 3 }), /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { style: { height: 8 } }), /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { style: { alignItems: "center" } }, /* @__PURE__ */ import_react3.default.createElement(Bar, { width: 120, height: 10 })));
14030
+ }
14031
+ function TestItemSkeleton({ delay = 0 }) {
14032
+ return /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { style: s.testItem }, /* @__PURE__ */ import_react3.default.createElement(Circle, { size: 18, delay }), /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { style: { flex: 1, marginLeft: 10 } }, /* @__PURE__ */ import_react3.default.createElement(Bar, { width: "70%", height: 11, delay }), /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { style: { height: 4 } }), /* @__PURE__ */ import_react3.default.createElement(Bar, { width: "45%", height: 8, delay })), /* @__PURE__ */ import_react3.default.createElement(Bar, { width: 50, height: 18, radius: 6, delay }));
14033
+ }
14034
+ function TestListScreenSkeleton() {
14035
+ return /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, null, /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { style: s.filterRow }, [55, 50, 50, 70].map((w, i) => /* @__PURE__ */ import_react3.default.createElement(Bar, { key: i, width: w, height: 28, radius: 8, delay: i * 50 }))), /* @__PURE__ */ import_react3.default.createElement(Bar, { width: "100%", height: 36, radius: 8 }), /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { style: { height: 10 } }), [0, 1].map((g) => /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { key: g, style: { marginBottom: 12 } }, /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { style: s.folderHeader }, /* @__PURE__ */ import_react3.default.createElement(Bar, { width: 12, height: 10 }), /* @__PURE__ */ import_react3.default.createElement(Bar, { width: "40%", height: 12 }), /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { style: { flex: 1 } }), /* @__PURE__ */ import_react3.default.createElement(Bar, { width: 40, height: 4, radius: 2 }), /* @__PURE__ */ import_react3.default.createElement(Bar, { width: 24, height: 10 })), [0, 1, 2].map((i) => /* @__PURE__ */ import_react3.default.createElement(TestItemSkeleton, { key: i, delay: (g * 3 + i) * 80 })))));
14036
+ }
14037
+ function IssueListScreenSkeleton() {
14038
+ return /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, null, [0, 1, 2, 3].map((i) => /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { key: i, style: s.issueRow }, /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { style: s.issueRowTop }, /* @__PURE__ */ import_react3.default.createElement(Circle, { size: 8, delay: i * 100 }), /* @__PURE__ */ import_react3.default.createElement(Bar, { width: "65%", height: 11, delay: i * 100 })), /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { style: s.issueRowBottom }, /* @__PURE__ */ import_react3.default.createElement(Bar, { width: "40%", height: 8, delay: i * 100 }), /* @__PURE__ */ import_react3.default.createElement(Bar, { width: 40, height: 8, delay: i * 100 })))));
14039
+ }
14040
+ function MessageListScreenSkeleton() {
14041
+ return /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, null, /* @__PURE__ */ import_react3.default.createElement(Bar, { width: "100%", height: 44, radius: 12 }), /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { style: { height: 16 } }), [0, 1, 2, 3].map((i) => /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { key: i, style: s.threadRow }, /* @__PURE__ */ import_react3.default.createElement(Circle, { size: 20, delay: i * 100 }), /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { style: { flex: 1, marginLeft: 10 } }, /* @__PURE__ */ import_react3.default.createElement(Bar, { width: "55%", height: 11, delay: i * 100 }), /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { style: { height: 5 } }), /* @__PURE__ */ import_react3.default.createElement(Bar, { width: "80%", height: 9, delay: i * 100 })), /* @__PURE__ */ import_react3.default.createElement(Bar, { width: 30, height: 8, delay: i * 100 }))));
14042
+ }
14043
+ var s = import_react_native3.StyleSheet.create({
14044
+ actionGrid: { flexDirection: "row", flexWrap: "wrap", gap: 12, marginBottom: 20 },
14045
+ actionCard: { width: "47%", backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border, borderRadius: 12, padding: 16, alignItems: "center" },
14046
+ issueGrid: { flexDirection: "row", gap: 10, marginBottom: 20 },
14047
+ issueCard: { flex: 1, backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border, borderRadius: 10, paddingVertical: 12, paddingHorizontal: 8, alignItems: "center" },
14048
+ filterRow: { flexDirection: "row", gap: 8, marginBottom: 8 },
14049
+ folderHeader: { flexDirection: "row", alignItems: "center", gap: 8, paddingVertical: 8, paddingHorizontal: 4 },
14050
+ testItem: { flexDirection: "row", alignItems: "center", paddingVertical: 10, paddingHorizontal: 12, borderRadius: 8, marginBottom: 4, backgroundColor: colors.card },
14051
+ issueRow: { backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border, borderRadius: 10, padding: 14, marginBottom: 8 },
14052
+ issueRowTop: { flexDirection: "row", alignItems: "center", gap: 8 },
14053
+ issueRowBottom: { flexDirection: "row", justifyContent: "space-between", marginTop: 8 },
14054
+ threadRow: { flexDirection: "row", alignItems: "flex-start", padding: 12, borderRadius: 10, marginBottom: 4, backgroundColor: colors.card }
14055
+ });
14056
+
14057
+ // src/widget/screens/HomeScreen.tsx
14058
+ function HomeScreen({ nav }) {
14059
+ const { assignments, unreadCount, threads, refreshAssignments, refreshThreads, issueCounts, refreshIssueCounts, dashboardUrl, isLoading } = useBugBear();
14060
+ (0, import_react4.useEffect)(() => {
13628
14061
  refreshAssignments();
13629
14062
  refreshThreads();
13630
14063
  refreshIssueCounts();
@@ -13634,104 +14067,105 @@ function HomeScreen({ nav }) {
13634
14067
  const retestCount = pendingAssignments.filter((a) => a.isVerification).length;
13635
14068
  const completedCount = assignments.filter((a) => a.status === "passed" || a.status === "failed").length;
13636
14069
  const totalTests = assignments.length;
13637
- return /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, null, pendingCount > 0 ? /* @__PURE__ */ import_react3.default.createElement(
13638
- import_react_native3.TouchableOpacity,
14070
+ if (isLoading) return /* @__PURE__ */ import_react4.default.createElement(HomeScreenSkeleton, null);
14071
+ return /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, null, pendingCount > 0 ? /* @__PURE__ */ import_react4.default.createElement(
14072
+ import_react_native4.TouchableOpacity,
13639
14073
  {
13640
14074
  style: [styles.heroBanner, styles.heroBannerTests],
13641
14075
  onPress: () => nav.push({ name: "TEST_DETAIL" }),
13642
14076
  activeOpacity: 0.8
13643
14077
  },
13644
- /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.heroCount }, pendingCount),
13645
- /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.heroLabel }, "test", pendingCount !== 1 ? "s" : "", " waiting"),
13646
- retestCount > 0 && /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { style: styles.retestPill }, /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.retestPillText }, "\u{1F504} ", retestCount, " retest", retestCount !== 1 ? "s" : "")),
13647
- /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.heroAction }, "Start Testing \u2192")
13648
- ) : unreadCount > 0 ? /* @__PURE__ */ import_react3.default.createElement(
13649
- import_react_native3.TouchableOpacity,
14078
+ /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.heroCount }, pendingCount),
14079
+ /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.heroLabel }, "test", pendingCount !== 1 ? "s" : "", " waiting"),
14080
+ retestCount > 0 && /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles.retestPill }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.retestPillText }, "\u{1F504} ", retestCount, " retest", retestCount !== 1 ? "s" : "")),
14081
+ /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.heroAction }, "Start Testing \u2192")
14082
+ ) : unreadCount > 0 ? /* @__PURE__ */ import_react4.default.createElement(
14083
+ import_react_native4.TouchableOpacity,
13650
14084
  {
13651
14085
  style: [styles.heroBanner, styles.heroBannerMessages],
13652
14086
  onPress: () => nav.push({ name: "MESSAGE_LIST" }),
13653
14087
  activeOpacity: 0.8
13654
14088
  },
13655
- /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.heroCount }, unreadCount),
13656
- /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.heroLabel }, "unread message", unreadCount !== 1 ? "s" : ""),
13657
- /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.heroAction }, "View Messages \u2192")
13658
- ) : /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { style: [styles.heroBanner, styles.heroBannerClear] }, /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.heroClearEmoji }, "\u2705"), /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.heroClearTitle }, "All caught up!"), totalTests > 0 && /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.heroClearSub }, completedCount, "/", totalTests, " tests completed")), /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { style: styles.actionGrid }, /* @__PURE__ */ import_react3.default.createElement(
13659
- import_react_native3.TouchableOpacity,
14089
+ /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.heroCount }, unreadCount),
14090
+ /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.heroLabel }, "unread message", unreadCount !== 1 ? "s" : ""),
14091
+ /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.heroAction }, "View Messages \u2192")
14092
+ ) : /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: [styles.heroBanner, styles.heroBannerClear] }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.heroClearEmoji }, "\u2705"), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.heroClearTitle }, "All caught up!"), totalTests > 0 && /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.heroClearSub }, completedCount, "/", totalTests, " tests completed")), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles.actionGrid }, /* @__PURE__ */ import_react4.default.createElement(
14093
+ import_react_native4.TouchableOpacity,
13660
14094
  {
13661
14095
  style: styles.actionCard,
13662
14096
  onPress: () => nav.push({ name: "TEST_LIST" }),
13663
14097
  activeOpacity: 0.7
13664
14098
  },
13665
- /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.actionIcon }, "\u2705"),
13666
- /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.actionLabel }, "Tests"),
13667
- pendingCount > 0 && /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { style: styles.actionBadge }, /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.actionBadgeText }, pendingCount))
13668
- ), /* @__PURE__ */ import_react3.default.createElement(
13669
- import_react_native3.TouchableOpacity,
14099
+ /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.actionIcon }, "\u2705"),
14100
+ /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.actionLabel }, "Tests"),
14101
+ pendingCount > 0 && /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles.actionBadge }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.actionBadgeText }, pendingCount))
14102
+ ), /* @__PURE__ */ import_react4.default.createElement(
14103
+ import_react_native4.TouchableOpacity,
13670
14104
  {
13671
14105
  style: styles.actionCard,
13672
14106
  onPress: () => nav.push({ name: "REPORT", prefill: { type: "bug" } }),
13673
14107
  activeOpacity: 0.7
13674
14108
  },
13675
- /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.actionIcon }, "\u{1F41B}"),
13676
- /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.actionLabel }, "Report Bug")
13677
- ), /* @__PURE__ */ import_react3.default.createElement(
13678
- import_react_native3.TouchableOpacity,
14109
+ /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.actionIcon }, "\u{1F41B}"),
14110
+ /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.actionLabel }, "Report Bug")
14111
+ ), /* @__PURE__ */ import_react4.default.createElement(
14112
+ import_react_native4.TouchableOpacity,
13679
14113
  {
13680
14114
  style: styles.actionCard,
13681
14115
  onPress: () => nav.push({ name: "REPORT", prefill: { type: "feedback" } }),
13682
14116
  activeOpacity: 0.7
13683
14117
  },
13684
- /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.actionIcon }, "\u{1F4A1}"),
13685
- /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.actionLabel }, "Feedback")
13686
- ), /* @__PURE__ */ import_react3.default.createElement(
13687
- import_react_native3.TouchableOpacity,
14118
+ /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.actionIcon }, "\u{1F4A1}"),
14119
+ /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.actionLabel }, "Feedback")
14120
+ ), /* @__PURE__ */ import_react4.default.createElement(
14121
+ import_react_native4.TouchableOpacity,
13688
14122
  {
13689
14123
  style: styles.actionCard,
13690
14124
  onPress: () => nav.push({ name: "MESSAGE_LIST" }),
13691
14125
  activeOpacity: 0.7
13692
14126
  },
13693
- /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.actionIcon }, "\u{1F4AC}"),
13694
- /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.actionLabel }, "Messages"),
13695
- unreadCount > 0 && /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { style: [styles.actionBadge, styles.actionBadgeMsg] }, /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.actionBadgeText }, unreadCount))
13696
- )), /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { style: styles.issueGrid }, /* @__PURE__ */ import_react3.default.createElement(
13697
- import_react_native3.TouchableOpacity,
14127
+ /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.actionIcon }, "\u{1F4AC}"),
14128
+ /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.actionLabel }, "Messages"),
14129
+ unreadCount > 0 && /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: [styles.actionBadge, styles.actionBadgeMsg] }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.actionBadgeText }, unreadCount))
14130
+ )), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles.issueGrid }, /* @__PURE__ */ import_react4.default.createElement(
14131
+ import_react_native4.TouchableOpacity,
13698
14132
  {
13699
14133
  style: [styles.issueCard, styles.issueCardOpen],
13700
14134
  onPress: () => nav.push({ name: "ISSUE_LIST", category: "open" }),
13701
14135
  activeOpacity: 0.7
13702
14136
  },
13703
- /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.issueCountOpen }, issueCounts.open),
13704
- /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.issueLabel }, "Open")
13705
- ), /* @__PURE__ */ import_react3.default.createElement(
13706
- import_react_native3.TouchableOpacity,
14137
+ /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.issueCountOpen }, issueCounts.open),
14138
+ /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.issueLabel }, "Open")
14139
+ ), /* @__PURE__ */ import_react4.default.createElement(
14140
+ import_react_native4.TouchableOpacity,
13707
14141
  {
13708
14142
  style: [styles.issueCard, styles.issueCardDone],
13709
14143
  onPress: () => nav.push({ name: "ISSUE_LIST", category: "done" }),
13710
14144
  activeOpacity: 0.7
13711
14145
  },
13712
- /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.issueCountDone }, issueCounts.done),
13713
- /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.issueLabel }, "Done")
13714
- ), /* @__PURE__ */ import_react3.default.createElement(
13715
- import_react_native3.TouchableOpacity,
14146
+ /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.issueCountDone }, issueCounts.done),
14147
+ /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.issueLabel }, "Done")
14148
+ ), /* @__PURE__ */ import_react4.default.createElement(
14149
+ import_react_native4.TouchableOpacity,
13716
14150
  {
13717
14151
  style: [styles.issueCard, styles.issueCardReopened],
13718
14152
  onPress: () => nav.push({ name: "ISSUE_LIST", category: "reopened" }),
13719
14153
  activeOpacity: 0.7
13720
14154
  },
13721
- /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.issueCountReopened }, issueCounts.reopened),
13722
- /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.issueLabel }, "Reopened")
13723
- )), totalTests > 0 && /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { style: styles.progressSection }, /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { style: styles.progressBar }, /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { style: [styles.progressFill, { width: `${Math.round(completedCount / totalTests * 100)}%` }] })), /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.progressText }, completedCount, "/", totalTests, " tests completed")), dashboardUrl && /* @__PURE__ */ import_react3.default.createElement(
13724
- import_react_native3.TouchableOpacity,
14155
+ /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.issueCountReopened }, issueCounts.reopened),
14156
+ /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.issueLabel }, "Reopened")
14157
+ )), totalTests > 0 && /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles.progressSection }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles.progressBar }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: [styles.progressFill, { width: `${Math.round(completedCount / totalTests * 100)}%` }] })), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.progressText }, completedCount, "/", totalTests, " tests completed")), dashboardUrl && /* @__PURE__ */ import_react4.default.createElement(
14158
+ import_react_native4.TouchableOpacity,
13725
14159
  {
13726
14160
  style: styles.webAppLink,
13727
- onPress: () => import_react_native3.Linking.openURL(dashboardUrl),
14161
+ onPress: () => import_react_native4.Linking.openURL(dashboardUrl),
13728
14162
  activeOpacity: 0.7
13729
14163
  },
13730
- /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.webAppIcon }, "\u{1F310}"),
13731
- /* @__PURE__ */ import_react3.default.createElement(import_react_native3.View, { style: styles.webAppTextWrap }, /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.webAppTitle }, "Open Web Dashboard"), /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.webAppSub }, "View analytics, history & more")),
13732
- /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.webAppArrow }, "\u2192")
13733
- ), /* @__PURE__ */ import_react3.default.createElement(
13734
- import_react_native3.TouchableOpacity,
14164
+ /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.webAppIcon }, "\u{1F310}"),
14165
+ /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles.webAppTextWrap }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.webAppTitle }, "Open Web Dashboard"), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.webAppSub }, "View analytics, history & more")),
14166
+ /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.webAppArrow }, "\u2192")
14167
+ ), /* @__PURE__ */ import_react4.default.createElement(
14168
+ import_react_native4.TouchableOpacity,
13735
14169
  {
13736
14170
  style: styles.refreshButton,
13737
14171
  onPress: () => {
@@ -13740,10 +14174,10 @@ function HomeScreen({ nav }) {
13740
14174
  refreshIssueCounts();
13741
14175
  }
13742
14176
  },
13743
- /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.refreshText }, "\u21BB Refresh")
14177
+ /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles.refreshText }, "\u21BB Refresh")
13744
14178
  ));
13745
14179
  }
13746
- var styles = import_react_native3.StyleSheet.create({
14180
+ var styles = import_react_native4.StyleSheet.create({
13747
14181
  heroBanner: {
13748
14182
  borderRadius: 16,
13749
14183
  padding: 24,
@@ -13967,26 +14401,26 @@ var styles = import_react_native3.StyleSheet.create({
13967
14401
  });
13968
14402
 
13969
14403
  // src/widget/screens/TestDetailScreen.tsx
13970
- var import_react4 = __toESM(require("react"));
13971
- var import_react_native4 = require("react-native");
14404
+ var import_react5 = __toESM(require("react"));
14405
+ var import_react_native5 = require("react-native");
13972
14406
  function TestDetailScreen({ testId, nav }) {
13973
14407
  const { client, assignments, currentAssignment, refreshAssignments, getDeviceInfo, onNavigate } = useBugBear();
13974
14408
  const displayedAssignment = testId ? assignments.find((a) => a.id === testId) || currentAssignment : currentAssignment;
13975
- const [showSteps, setShowSteps] = (0, import_react4.useState)(true);
13976
- const [showDetails, setShowDetails] = (0, import_react4.useState)(false);
13977
- const [criteriaResults, setCriteriaResults] = (0, import_react4.useState)({});
13978
- const [assignmentElapsedTime, setAssignmentElapsedTime] = (0, import_react4.useState)(0);
13979
- const [showSkipModal, setShowSkipModal] = (0, import_react4.useState)(false);
13980
- const [selectedSkipReason, setSelectedSkipReason] = (0, import_react4.useState)(null);
13981
- const [skipNotes, setSkipNotes] = (0, import_react4.useState)("");
13982
- const [skipping, setSkipping] = (0, import_react4.useState)(false);
13983
- const [isSubmitting, setIsSubmitting] = (0, import_react4.useState)(false);
13984
- (0, import_react4.useEffect)(() => {
14409
+ const [showSteps, setShowSteps] = (0, import_react5.useState)(true);
14410
+ const [showDetails, setShowDetails] = (0, import_react5.useState)(false);
14411
+ const [criteriaResults, setCriteriaResults] = (0, import_react5.useState)({});
14412
+ const [assignmentElapsedTime, setAssignmentElapsedTime] = (0, import_react5.useState)(0);
14413
+ const [showSkipModal, setShowSkipModal] = (0, import_react5.useState)(false);
14414
+ const [selectedSkipReason, setSelectedSkipReason] = (0, import_react5.useState)(null);
14415
+ const [skipNotes, setSkipNotes] = (0, import_react5.useState)("");
14416
+ const [skipping, setSkipping] = (0, import_react5.useState)(false);
14417
+ const [isSubmitting, setIsSubmitting] = (0, import_react5.useState)(false);
14418
+ (0, import_react5.useEffect)(() => {
13985
14419
  setCriteriaResults({});
13986
14420
  setShowSteps(true);
13987
14421
  setShowDetails(false);
13988
14422
  }, [displayedAssignment?.id]);
13989
- (0, import_react4.useEffect)(() => {
14423
+ (0, import_react5.useEffect)(() => {
13990
14424
  const active = displayedAssignment?.status === "in_progress" ? displayedAssignment : null;
13991
14425
  if (!active?.startedAt) {
13992
14426
  setAssignmentElapsedTime(0);
@@ -14002,9 +14436,9 @@ function TestDetailScreen({ testId, nav }) {
14002
14436
  const pendingTests = assignments.filter((a) => a.status === "pending" || a.status === "in_progress");
14003
14437
  const allTests = assignments;
14004
14438
  const currentIndex = displayedAssignment ? assignments.indexOf(displayedAssignment) : -1;
14005
- const handlePass = (0, import_react4.useCallback)(async () => {
14439
+ const handlePass = (0, import_react5.useCallback)(async () => {
14006
14440
  if (!client || !displayedAssignment || isSubmitting) return;
14007
- import_react_native4.Keyboard.dismiss();
14441
+ import_react_native5.Keyboard.dismiss();
14008
14442
  setIsSubmitting(true);
14009
14443
  try {
14010
14444
  await client.passAssignment(displayedAssignment.id);
@@ -14014,9 +14448,9 @@ function TestDetailScreen({ testId, nav }) {
14014
14448
  setIsSubmitting(false);
14015
14449
  }
14016
14450
  }, [client, displayedAssignment, refreshAssignments, nav, isSubmitting]);
14017
- const handleFail = (0, import_react4.useCallback)(async () => {
14451
+ const handleFail = (0, import_react5.useCallback)(async () => {
14018
14452
  if (!client || !displayedAssignment || isSubmitting) return;
14019
- import_react_native4.Keyboard.dismiss();
14453
+ import_react_native5.Keyboard.dismiss();
14020
14454
  setIsSubmitting(true);
14021
14455
  try {
14022
14456
  const result = await client.failAssignment(displayedAssignment.id);
@@ -14036,9 +14470,9 @@ function TestDetailScreen({ testId, nav }) {
14036
14470
  setIsSubmitting(false);
14037
14471
  }
14038
14472
  }, [client, displayedAssignment, refreshAssignments, nav, isSubmitting]);
14039
- const handleSkip = (0, import_react4.useCallback)(async () => {
14473
+ const handleSkip = (0, import_react5.useCallback)(async () => {
14040
14474
  if (!client || !displayedAssignment || !selectedSkipReason) return;
14041
- import_react_native4.Keyboard.dismiss();
14475
+ import_react_native5.Keyboard.dismiss();
14042
14476
  setSkipping(true);
14043
14477
  await client.skipAssignment(displayedAssignment.id, {
14044
14478
  reason: selectedSkipReason,
@@ -14062,15 +14496,15 @@ function TestDetailScreen({ testId, nav }) {
14062
14496
  }
14063
14497
  }, [client, displayedAssignment, selectedSkipReason, skipNotes, refreshAssignments, assignments, nav]);
14064
14498
  if (!displayedAssignment) {
14065
- return /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: shared.emptyState }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: shared.emptyEmoji }, "\u2705"), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: shared.emptyTitle }, "No tests assigned"), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: shared.emptySubtitle }, "Check back later for new tests"), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.TouchableOpacity, { style: [shared.primaryButton, { marginTop: 20, paddingHorizontal: 24 }], onPress: () => nav.reset() }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: shared.primaryButtonText }, "Go Home")));
14499
+ return /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: shared.emptyState }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: shared.emptyEmoji }, "\u2705"), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: shared.emptyTitle }, "No tests assigned"), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: shared.emptySubtitle }, "Check back later for new tests"), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { style: [shared.primaryButton, { marginTop: 20, paddingHorizontal: 24 }], onPress: () => nav.reset() }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: shared.primaryButtonText }, "Go Home")));
14066
14500
  }
14067
14501
  const testCase = displayedAssignment.testCase;
14068
14502
  const template = testCase.track?.testTemplate || "steps";
14069
14503
  const steps = testCase.steps;
14070
14504
  const info = templateInfo[template] || templateInfo.steps;
14071
14505
  const rubricMode = testCase.track?.rubricMode || "pass_fail";
14072
- return /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles2.container }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles2.topRow }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles2.positionInfo }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.positionText }, "Test ", currentIndex + 1, " of ", allTests.length), displayedAssignment.status === "in_progress" && assignmentElapsedTime > 0 && /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles2.timerBadge }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.timerText }, formatElapsedTime(assignmentElapsedTime)))), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.TouchableOpacity, { onPress: () => nav.push({ name: "TEST_LIST" }) }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.viewAllLink }, "View All \u2192"))), displayedAssignment.isVerification && /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles2.retestBanner }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.retestIcon }, "\u{1F504}"), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.retestLabel }, "Retest"), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.retestSub }, "\u2014 Verify bug fix")), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.testTitle }, testCase.title), testCase.key && /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.testKey }, testCase.key), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.TouchableOpacity, { onPress: () => setShowSteps(!showSteps), style: styles2.sectionHeader }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.sectionHeaderText }, showSteps ? "\u25BC" : "\u25B6", " ", info.icon, " ", template === "freeform" ? "Instructions" : `${steps.length} ${template === "checklist" ? "items" : template === "rubric" ? "criteria" : "steps"}`)), showSteps && /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles2.templateContent }, template === "steps" && steps.map((step, idx) => /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { key: idx, style: styles2.step }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles2.stepNumber }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.stepNumberText }, step.stepNumber)), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles2.stepBody }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.stepAction }, step.action), step.expectedResult && /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.stepExpected }, "\u2192 ", step.expectedResult)))), template === "checklist" && /* @__PURE__ */ import_react4.default.createElement(import_react4.default.Fragment, null, steps.map((step, idx) => /* @__PURE__ */ import_react4.default.createElement(
14073
- import_react_native4.TouchableOpacity,
14506
+ return /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles2.container }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles2.topRow }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles2.positionInfo }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.positionText }, "Test ", currentIndex + 1, " of ", allTests.length), displayedAssignment.status === "in_progress" && assignmentElapsedTime > 0 && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles2.timerBadge }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.timerText }, formatElapsedTime(assignmentElapsedTime)))), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { onPress: () => nav.push({ name: "TEST_LIST" }) }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.viewAllLink }, "View All \u2192"))), displayedAssignment.isVerification && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles2.retestBanner }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.retestIcon }, "\u{1F504}"), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.retestLabel }, "Retest"), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.retestSub }, "\u2014 Verify bug fix")), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.testTitle }, testCase.title), testCase.key && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.testKey }, testCase.key), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { onPress: () => setShowSteps(!showSteps), style: styles2.sectionHeader }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.sectionHeaderText }, showSteps ? "\u25BC" : "\u25B6", " ", info.icon, " ", template === "freeform" ? "Instructions" : `${steps.length} ${template === "checklist" ? "items" : template === "rubric" ? "criteria" : "steps"}`)), showSteps && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles2.templateContent }, template === "steps" && steps.map((step, idx) => /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { key: idx, style: styles2.step }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles2.stepNumber }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.stepNumberText }, step.stepNumber)), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles2.stepBody }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.stepAction }, step.action), step.expectedResult && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.stepExpected }, "\u2192 ", step.expectedResult)))), template === "checklist" && /* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, null, steps.map((step, idx) => /* @__PURE__ */ import_react5.default.createElement(
14507
+ import_react_native5.TouchableOpacity,
14074
14508
  {
14075
14509
  key: idx,
14076
14510
  onPress: () => setCriteriaResults((prev) => {
@@ -14081,56 +14515,56 @@ function TestDetailScreen({ testId, nav }) {
14081
14515
  }),
14082
14516
  style: [styles2.checklistItem, criteriaResults[idx] === true && styles2.checklistItemChecked]
14083
14517
  },
14084
- /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: [styles2.checkbox, criteriaResults[idx] === true && styles2.checkboxChecked] }, criteriaResults[idx] === true && /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.checkmark }, "\u2713")),
14085
- /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: [styles2.checklistText, criteriaResults[idx] === true && styles2.checklistTextDone] }, step.action)
14086
- )), Object.keys(criteriaResults).length > 0 && /* @__PURE__ */ import_react4.default.createElement(import_react_native4.TouchableOpacity, { onPress: () => setCriteriaResults({}), style: styles2.resetRow }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.resetText }, "\u21BA Reset"))), template === "rubric" && steps.map((step, idx) => /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { key: idx, style: styles2.rubricItem }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.rubricTitle }, idx + 1, ". ", step.action), step.expectedResult && /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.rubricExpected }, step.expectedResult), rubricMode === "pass_fail" ? /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles2.passFailRow }, /* @__PURE__ */ import_react4.default.createElement(
14087
- import_react_native4.TouchableOpacity,
14518
+ /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: [styles2.checkbox, criteriaResults[idx] === true && styles2.checkboxChecked] }, criteriaResults[idx] === true && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.checkmark }, "\u2713")),
14519
+ /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: [styles2.checklistText, criteriaResults[idx] === true && styles2.checklistTextDone] }, step.action)
14520
+ )), Object.keys(criteriaResults).length > 0 && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { onPress: () => setCriteriaResults({}), style: styles2.resetRow }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.resetText }, "\u21BA Reset"))), template === "rubric" && steps.map((step, idx) => /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { key: idx, style: styles2.rubricItem }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.rubricTitle }, idx + 1, ". ", step.action), step.expectedResult && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.rubricExpected }, step.expectedResult), rubricMode === "pass_fail" ? /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles2.passFailRow }, /* @__PURE__ */ import_react5.default.createElement(
14521
+ import_react_native5.TouchableOpacity,
14088
14522
  {
14089
14523
  onPress: () => setCriteriaResults((prev) => ({ ...prev, [idx]: true })),
14090
14524
  style: [styles2.pfButton, criteriaResults[idx] === true && styles2.pfButtonPass]
14091
14525
  },
14092
- /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: [styles2.pfButtonText, criteriaResults[idx] === true && styles2.pfButtonTextActive] }, "\u2713 Pass")
14093
- ), /* @__PURE__ */ import_react4.default.createElement(
14094
- import_react_native4.TouchableOpacity,
14526
+ /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: [styles2.pfButtonText, criteriaResults[idx] === true && styles2.pfButtonTextActive] }, "\u2713 Pass")
14527
+ ), /* @__PURE__ */ import_react5.default.createElement(
14528
+ import_react_native5.TouchableOpacity,
14095
14529
  {
14096
14530
  onPress: () => setCriteriaResults((prev) => ({ ...prev, [idx]: false })),
14097
14531
  style: [styles2.pfButton, criteriaResults[idx] === false && styles2.pfButtonFail]
14098
14532
  },
14099
- /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: [styles2.pfButtonText, criteriaResults[idx] === false && styles2.pfButtonTextActive] }, "\u2717 Fail")
14100
- )) : /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles2.ratingRow }, [1, 2, 3, 4, 5].map((n) => /* @__PURE__ */ import_react4.default.createElement(
14101
- import_react_native4.TouchableOpacity,
14533
+ /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: [styles2.pfButtonText, criteriaResults[idx] === false && styles2.pfButtonTextActive] }, "\u2717 Fail")
14534
+ )) : /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles2.ratingRow }, [1, 2, 3, 4, 5].map((n) => /* @__PURE__ */ import_react5.default.createElement(
14535
+ import_react_native5.TouchableOpacity,
14102
14536
  {
14103
14537
  key: n,
14104
14538
  onPress: () => setCriteriaResults((prev) => ({ ...prev, [idx]: n })),
14105
14539
  style: [styles2.ratingBtn, criteriaResults[idx] === n && styles2.ratingBtnActive]
14106
14540
  },
14107
- /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: [styles2.ratingBtnText, criteriaResults[idx] === n && styles2.ratingBtnTextActive] }, n)
14108
- ))))), template === "freeform" && /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles2.freeformBox }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.freeformText }, "Review the area and note:"), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.freeformBullet }, "\u2022 What works well"), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.freeformBullet }, "\u2022 Issues or concerns"), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.freeformBullet }, "\u2022 Suggestions"))), testCase.expectedResult && /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles2.expectedBox }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.expectedLabel }, "\u2705 Expected Result"), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.expectedText }, testCase.expectedResult)), testCase.targetRoute && onNavigate && /* @__PURE__ */ import_react4.default.createElement(
14109
- import_react_native4.TouchableOpacity,
14541
+ /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: [styles2.ratingBtnText, criteriaResults[idx] === n && styles2.ratingBtnTextActive] }, n)
14542
+ ))))), template === "freeform" && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles2.freeformBox }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.freeformText }, "Review the area and note:"), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.freeformBullet }, "\u2022 What works well"), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.freeformBullet }, "\u2022 Issues or concerns"), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.freeformBullet }, "\u2022 Suggestions"))), testCase.expectedResult && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles2.expectedBox }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.expectedLabel }, "\u2705 Expected Result"), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.expectedText }, testCase.expectedResult)), testCase.targetRoute && onNavigate && /* @__PURE__ */ import_react5.default.createElement(
14543
+ import_react_native5.TouchableOpacity,
14110
14544
  {
14111
14545
  style: styles2.navigateButton,
14112
14546
  onPress: () => {
14113
- import_react_native4.Keyboard.dismiss();
14547
+ import_react_native5.Keyboard.dismiss();
14114
14548
  onNavigate(testCase.targetRoute);
14115
14549
  nav.closeWidget?.();
14116
14550
  }
14117
14551
  },
14118
- /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.navigateText }, "\u{1F9ED} Go to test location")
14119
- ), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.TouchableOpacity, { onPress: () => setShowDetails(!showDetails), style: styles2.detailsToggle }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.detailsToggleText }, showDetails ? "\u25BC" : "\u25B6", " Details")), showDetails && /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles2.detailsSection }, testCase.key && /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.detailMeta }, testCase.key, " \xB7 ", testCase.priority, " \xB7 ", info.name), testCase.description && /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.detailDesc }, testCase.description), testCase.group && /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles2.folderProgress }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.folderName }, "\u{1F4C1} ", testCase.group.name))), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles2.actionButtons }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.TouchableOpacity, { style: [styles2.actionBtn, styles2.failBtn, isSubmitting && { opacity: 0.5 }], onPress: handleFail, disabled: isSubmitting }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.failBtnText }, isSubmitting ? "Failing..." : "Fail")), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.TouchableOpacity, { style: [styles2.actionBtn, styles2.skipBtn, isSubmitting && { opacity: 0.5 }], onPress: () => setShowSkipModal(true), disabled: isSubmitting }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.skipBtnText }, "Skip")), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.TouchableOpacity, { style: [styles2.actionBtn, styles2.passBtn, isSubmitting && { opacity: 0.5 }], onPress: handlePass, disabled: isSubmitting }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.passBtnText }, isSubmitting ? "Passing..." : "Pass"))), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Modal, { visible: showSkipModal, transparent: true, animationType: "fade" }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles2.modalOverlay }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles2.modalContent }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.modalTitle }, "Skip this test?"), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.modalSubtitle }, "Select a reason:"), [
14552
+ /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.navigateText }, "\u{1F9ED} Go to test location")
14553
+ ), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { onPress: () => setShowDetails(!showDetails), style: styles2.detailsToggle }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.detailsToggleText }, showDetails ? "\u25BC" : "\u25B6", " Details")), showDetails && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles2.detailsSection }, testCase.key && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.detailMeta }, testCase.key, " \xB7 ", testCase.priority, " \xB7 ", info.name), testCase.description && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.detailDesc }, testCase.description), testCase.group && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles2.folderProgress }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.folderName }, "\u{1F4C1} ", testCase.group.name))), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles2.actionButtons }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { style: [styles2.actionBtn, styles2.failBtn, isSubmitting && { opacity: 0.5 }], onPress: handleFail, disabled: isSubmitting }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.failBtnText }, isSubmitting ? "Failing..." : "Fail")), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { style: [styles2.actionBtn, styles2.skipBtn, isSubmitting && { opacity: 0.5 }], onPress: () => setShowSkipModal(true), disabled: isSubmitting }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.skipBtnText }, "Skip")), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { style: [styles2.actionBtn, styles2.passBtn, isSubmitting && { opacity: 0.5 }], onPress: handlePass, disabled: isSubmitting }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.passBtnText }, isSubmitting ? "Passing..." : "Pass"))), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Modal, { visible: showSkipModal, transparent: true, animationType: "fade" }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles2.modalOverlay }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles2.modalContent }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.modalTitle }, "Skip this test?"), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.modalSubtitle }, "Select a reason:"), [
14120
14554
  { reason: "blocked", label: "\u{1F6AB} Blocked by a bug" },
14121
14555
  { reason: "not_ready", label: "\u{1F6A7} Feature not ready" },
14122
14556
  { reason: "dependency", label: "\u{1F517} Needs another test first" },
14123
14557
  { reason: "other", label: "\u{1F4DD} Other reason" }
14124
- ].map(({ reason, label }) => /* @__PURE__ */ import_react4.default.createElement(
14125
- import_react_native4.TouchableOpacity,
14558
+ ].map(({ reason, label }) => /* @__PURE__ */ import_react5.default.createElement(
14559
+ import_react_native5.TouchableOpacity,
14126
14560
  {
14127
14561
  key: reason,
14128
14562
  style: [styles2.skipOption, selectedSkipReason === reason && styles2.skipOptionActive],
14129
14563
  onPress: () => setSelectedSkipReason(reason)
14130
14564
  },
14131
- /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: [styles2.skipOptionText, selectedSkipReason === reason && styles2.skipOptionTextActive] }, label)
14132
- )), /* @__PURE__ */ import_react4.default.createElement(
14133
- import_react_native4.TextInput,
14565
+ /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: [styles2.skipOptionText, selectedSkipReason === reason && styles2.skipOptionTextActive] }, label)
14566
+ )), /* @__PURE__ */ import_react5.default.createElement(
14567
+ import_react_native5.TextInput,
14134
14568
  {
14135
14569
  style: styles2.skipNotes,
14136
14570
  value: skipNotes,
@@ -14139,21 +14573,21 @@ function TestDetailScreen({ testId, nav }) {
14139
14573
  placeholderTextColor: colors.textMuted,
14140
14574
  multiline: true
14141
14575
  }
14142
- ), /* @__PURE__ */ import_react4.default.createElement(import_react_native4.View, { style: styles2.skipActions }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.TouchableOpacity, { style: styles2.skipCancel, onPress: () => {
14576
+ ), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles2.skipActions }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { style: styles2.skipCancel, onPress: () => {
14143
14577
  setShowSkipModal(false);
14144
14578
  setSelectedSkipReason(null);
14145
14579
  setSkipNotes("");
14146
- } }, /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.skipCancelText }, "Cancel")), /* @__PURE__ */ import_react4.default.createElement(
14147
- import_react_native4.TouchableOpacity,
14580
+ } }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.skipCancelText }, "Cancel")), /* @__PURE__ */ import_react5.default.createElement(
14581
+ import_react_native5.TouchableOpacity,
14148
14582
  {
14149
14583
  style: [styles2.skipConfirm, !selectedSkipReason && { opacity: 0.4 }],
14150
14584
  onPress: handleSkip,
14151
14585
  disabled: !selectedSkipReason || skipping
14152
14586
  },
14153
- /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.skipConfirmText }, skipping ? "Skipping..." : "Skip Test")
14587
+ /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles2.skipConfirmText }, skipping ? "Skipping..." : "Skip Test")
14154
14588
  ))))));
14155
14589
  }
14156
- var styles2 = import_react_native4.StyleSheet.create({
14590
+ var styles2 = import_react_native5.StyleSheet.create({
14157
14591
  container: { paddingBottom: 16 },
14158
14592
  retestBanner: { flexDirection: "row", alignItems: "center", gap: 6, backgroundColor: "#422006", borderWidth: 1, borderColor: "#854d0e", borderRadius: 8, paddingVertical: 6, paddingHorizontal: 10, marginBottom: 10 },
14159
14593
  retestIcon: { fontSize: 14 },
@@ -14248,25 +14682,35 @@ var styles2 = import_react_native4.StyleSheet.create({
14248
14682
  });
14249
14683
 
14250
14684
  // src/widget/screens/TestListScreen.tsx
14251
- var import_react5 = __toESM(require("react"));
14252
- var import_react_native5 = require("react-native");
14685
+ var import_react6 = __toESM(require("react"));
14686
+ var import_react_native6 = require("react-native");
14253
14687
  function TestListScreen({ nav }) {
14254
- const { assignments, currentAssignment, refreshAssignments } = useBugBear();
14255
- const [filter, setFilter] = (0, import_react5.useState)("all");
14256
- const [roleFilter, setRoleFilter] = (0, import_react5.useState)(null);
14257
- const [collapsedFolders, setCollapsedFolders] = (0, import_react5.useState)(/* @__PURE__ */ new Set());
14258
- (0, import_react5.useEffect)(() => {
14688
+ const { assignments, currentAssignment, refreshAssignments, isLoading } = useBugBear();
14689
+ const [filter, setFilter] = (0, import_react6.useState)("all");
14690
+ const [roleFilter, setRoleFilter] = (0, import_react6.useState)(null);
14691
+ const [trackFilter, setTrackFilter] = (0, import_react6.useState)(null);
14692
+ const [searchQuery, setSearchQuery] = (0, import_react6.useState)("");
14693
+ const [sortMode, setSortMode] = (0, import_react6.useState)("priority");
14694
+ const [collapsedFolders, setCollapsedFolders] = (0, import_react6.useState)(/* @__PURE__ */ new Set());
14695
+ (0, import_react6.useEffect)(() => {
14259
14696
  refreshAssignments();
14260
14697
  }, []);
14261
- const availableRoles = (0, import_react5.useMemo)(() => {
14698
+ const availableRoles = (0, import_react6.useMemo)(() => {
14262
14699
  const roleMap = /* @__PURE__ */ new Map();
14263
14700
  for (const a of assignments) {
14264
14701
  if (a.testCase.role) roleMap.set(a.testCase.role.id, a.testCase.role);
14265
14702
  }
14266
14703
  return Array.from(roleMap.values());
14267
14704
  }, [assignments]);
14705
+ const availableTracks = (0, import_react6.useMemo)(() => {
14706
+ const trackMap = /* @__PURE__ */ new Map();
14707
+ for (const a of assignments) {
14708
+ if (a.testCase.track) trackMap.set(a.testCase.track.id, a.testCase.track);
14709
+ }
14710
+ return Array.from(trackMap.values());
14711
+ }, [assignments]);
14268
14712
  const selectedRole = availableRoles.find((r) => r.id === roleFilter);
14269
- const groupedAssignments = (0, import_react5.useMemo)(() => {
14713
+ const groupedAssignments = (0, import_react6.useMemo)(() => {
14270
14714
  const groups = /* @__PURE__ */ new Map();
14271
14715
  for (const assignment of assignments) {
14272
14716
  const groupId = assignment.testCase.group?.id || "ungrouped";
@@ -14288,6 +14732,8 @@ function TestListScreen({ nav }) {
14288
14732
  folder.assignments.sort((a, b) => {
14289
14733
  if (a.isVerification && !b.isVerification) return -1;
14290
14734
  if (!a.isVerification && b.isVerification) return 1;
14735
+ if (sortMode === "alpha") return a.testCase.title.localeCompare(b.testCase.title);
14736
+ if (sortMode === "recent") return 0;
14291
14737
  const sd = (statusOrder[a.status] ?? 5) - (statusOrder[b.status] ?? 5);
14292
14738
  if (sd !== 0) return sd;
14293
14739
  return (priorityOrder[a.testCase.priority] ?? 4) - (priorityOrder[b.testCase.priority] ?? 4);
@@ -14299,8 +14745,8 @@ function TestListScreen({ nav }) {
14299
14745
  if (!b.group) return -1;
14300
14746
  return a.group.sortOrder - b.group.sortOrder;
14301
14747
  });
14302
- }, [assignments]);
14303
- const toggleFolder = (0, import_react5.useCallback)((id) => {
14748
+ }, [assignments, sortMode]);
14749
+ const toggleFolder = (0, import_react6.useCallback)((id) => {
14304
14750
  setCollapsedFolders((prev) => {
14305
14751
  const next = new Set(prev);
14306
14752
  if (next.has(id)) next.delete(id);
@@ -14308,29 +14754,37 @@ function TestListScreen({ nav }) {
14308
14754
  return next;
14309
14755
  });
14310
14756
  }, []);
14311
- const filterAssignment = (a) => {
14757
+ const filterAssignment = (0, import_react6.useCallback)((a) => {
14312
14758
  if (roleFilter && a.testCase.role?.id !== roleFilter) return false;
14759
+ if (trackFilter && a.testCase.track?.id !== trackFilter) return false;
14760
+ if (searchQuery) {
14761
+ const q = searchQuery.toLowerCase();
14762
+ const titleMatch = a.testCase.title.toLowerCase().includes(q);
14763
+ const keyMatch = a.testCase.testKey.toLowerCase().includes(q);
14764
+ if (!titleMatch && !keyMatch) return false;
14765
+ }
14313
14766
  if (filter === "pending") return a.status === "pending" || a.status === "in_progress";
14314
14767
  if (filter === "done") return a.status === "passed";
14315
14768
  if (filter === "reopened") return a.status === "failed";
14316
14769
  return true;
14317
- };
14318
- return /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, null, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.filterBar }, [
14770
+ }, [roleFilter, trackFilter, searchQuery, filter]);
14771
+ if (isLoading) return /* @__PURE__ */ import_react6.default.createElement(TestListScreenSkeleton, null);
14772
+ return /* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, null, /* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, { style: styles3.filterBar }, [
14319
14773
  { key: "all", label: "All", count: assignments.length },
14320
14774
  { key: "pending", label: "To Do", count: assignments.filter((a) => a.status === "pending" || a.status === "in_progress").length },
14321
14775
  { key: "done", label: "Done", count: assignments.filter((a) => a.status === "passed").length },
14322
14776
  { key: "reopened", label: "Re Opened", count: assignments.filter((a) => a.status === "failed").length }
14323
- ].map((f) => /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { key: f.key, style: [styles3.filterBtn, filter === f.key && styles3.filterBtnActive], onPress: () => setFilter(f.key) }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: [styles3.filterBtnText, filter === f.key && styles3.filterBtnTextActive] }, f.label, " (", f.count, ")")))), availableRoles.length >= 2 && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.roleSection }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: styles3.roleBar }, /* @__PURE__ */ import_react5.default.createElement(
14324
- import_react_native5.TouchableOpacity,
14777
+ ].map((f) => /* @__PURE__ */ import_react6.default.createElement(import_react_native6.TouchableOpacity, { key: f.key, style: [styles3.filterBtn, filter === f.key && styles3.filterBtnActive], onPress: () => setFilter(f.key) }, /* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: [styles3.filterBtnText, filter === f.key && styles3.filterBtnTextActive] }, f.label, " (", f.count, ")")))), availableRoles.length >= 2 && /* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, { style: styles3.roleSection }, /* @__PURE__ */ import_react6.default.createElement(import_react_native6.ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: styles3.roleBar }, /* @__PURE__ */ import_react6.default.createElement(
14778
+ import_react_native6.TouchableOpacity,
14325
14779
  {
14326
14780
  style: [styles3.roleBtn, !roleFilter && styles3.roleBtnActive],
14327
14781
  onPress: () => setRoleFilter(null)
14328
14782
  },
14329
- /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: [styles3.roleBtnText, !roleFilter && styles3.roleBtnTextActive] }, "All Roles")
14783
+ /* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: [styles3.roleBtnText, !roleFilter && styles3.roleBtnTextActive] }, "All Roles")
14330
14784
  ), availableRoles.map((role) => {
14331
14785
  const isActive = roleFilter === role.id;
14332
- return /* @__PURE__ */ import_react5.default.createElement(
14333
- import_react_native5.TouchableOpacity,
14786
+ return /* @__PURE__ */ import_react6.default.createElement(
14787
+ import_react_native6.TouchableOpacity,
14334
14788
  {
14335
14789
  key: role.id,
14336
14790
  style: [
@@ -14339,33 +14793,72 @@ function TestListScreen({ nav }) {
14339
14793
  ],
14340
14794
  onPress: () => setRoleFilter(isActive ? null : role.id)
14341
14795
  },
14342
- /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: [styles3.roleDot, { backgroundColor: role.color }] }),
14343
- /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: [styles3.roleBtnText, isActive && { color: role.color, fontWeight: "600" }] }, role.name)
14796
+ /* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, { style: [styles3.roleDot, { backgroundColor: role.color }] }),
14797
+ /* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: [styles3.roleBtnText, isActive && { color: role.color, fontWeight: "600" }] }, role.name)
14344
14798
  );
14345
- })), selectedRole?.loginHint && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: [styles3.loginHint, { backgroundColor: selectedRole.color + "10", borderColor: selectedRole.color + "30" }] }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.loginHintText }, "Log in as: ", selectedRole.loginHint))), groupedAssignments.map((folder) => {
14799
+ })), selectedRole?.loginHint && /* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, { style: [styles3.loginHint, { backgroundColor: selectedRole.color + "10", borderColor: selectedRole.color + "30" }] }, /* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: styles3.loginHintText }, "Log in as: ", selectedRole.loginHint))), /* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, { style: styles3.searchContainer }, /* @__PURE__ */ import_react6.default.createElement(
14800
+ import_react_native6.TextInput,
14801
+ {
14802
+ value: searchQuery,
14803
+ onChangeText: setSearchQuery,
14804
+ placeholder: "Search tests...",
14805
+ placeholderTextColor: colors.textMuted,
14806
+ style: styles3.searchInput
14807
+ }
14808
+ )), /* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, { style: styles3.trackSortRow }, availableTracks.length >= 2 && /* @__PURE__ */ import_react6.default.createElement(import_react_native6.ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: { flex: 1 } }, /* @__PURE__ */ import_react6.default.createElement(
14809
+ import_react_native6.TouchableOpacity,
14810
+ {
14811
+ style: [styles3.trackBtn, !trackFilter && styles3.trackBtnActive],
14812
+ onPress: () => setTrackFilter(null)
14813
+ },
14814
+ /* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: [styles3.trackBtnText, !trackFilter && styles3.trackBtnTextActive] }, "All Tracks")
14815
+ ), availableTracks.map((track) => {
14816
+ const isActive = trackFilter === track.id;
14817
+ return /* @__PURE__ */ import_react6.default.createElement(
14818
+ import_react_native6.TouchableOpacity,
14819
+ {
14820
+ key: track.id,
14821
+ style: [styles3.trackBtn, isActive && { backgroundColor: track.color + "20", borderColor: track.color + "60", borderWidth: 1 }],
14822
+ onPress: () => setTrackFilter(isActive ? null : track.id)
14823
+ },
14824
+ /* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: [styles3.trackBtnText, isActive && { color: track.color, fontWeight: "600" }] }, track.icon, " ", track.name)
14825
+ );
14826
+ })), /* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, { style: styles3.sortGroup }, [
14827
+ { key: "priority", label: "\u2195" },
14828
+ { key: "recent", label: "\u{1F550}" },
14829
+ { key: "alpha", label: "AZ" }
14830
+ ].map((s2) => /* @__PURE__ */ import_react6.default.createElement(
14831
+ import_react_native6.TouchableOpacity,
14832
+ {
14833
+ key: s2.key,
14834
+ style: [styles3.sortBtn, sortMode === s2.key && styles3.sortBtnActive],
14835
+ onPress: () => setSortMode(s2.key)
14836
+ },
14837
+ /* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: [styles3.sortBtnText, sortMode === s2.key && styles3.sortBtnTextActive] }, s2.label)
14838
+ )))), groupedAssignments.map((folder) => {
14346
14839
  const folderId = folder.group?.id || "ungrouped";
14347
14840
  const isCollapsed = collapsedFolders.has(folderId);
14348
14841
  const filtered = folder.assignments.filter(filterAssignment);
14349
14842
  if (filtered.length === 0 && filter !== "all") return null;
14350
- return /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { key: folderId, style: styles3.folder }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { style: styles3.folderHeader, onPress: () => toggleFolder(folderId) }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.folderToggle }, isCollapsed ? "\u25B6" : "\u25BC"), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.folderName, numberOfLines: 1 }, folder.group?.name || "Ungrouped"), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.folderProgress }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: [styles3.folderProgressFill, { width: `${folder.stats.total > 0 ? Math.round((folder.stats.passed + folder.stats.failed) / folder.stats.total * 100) : 0}%` }] })), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.folderCount }, folder.stats.passed + folder.stats.failed, "/", folder.stats.total)), !isCollapsed && filtered.map((assignment) => {
14843
+ return /* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, { key: folderId, style: styles3.folder }, /* @__PURE__ */ import_react6.default.createElement(import_react_native6.TouchableOpacity, { style: styles3.folderHeader, onPress: () => toggleFolder(folderId) }, /* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: styles3.folderToggle }, isCollapsed ? "\u25B6" : "\u25BC"), /* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: styles3.folderName, numberOfLines: 1 }, folder.group?.name || "Ungrouped"), /* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, { style: styles3.folderProgress }, /* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, { style: [styles3.folderProgressFill, { width: `${folder.stats.total > 0 ? Math.round((folder.stats.passed + folder.stats.failed) / folder.stats.total * 100) : 0}%` }] })), /* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: styles3.folderCount }, folder.stats.passed + folder.stats.failed, "/", folder.stats.total)), !isCollapsed && filtered.map((assignment) => {
14351
14844
  const badge = getStatusBadge(assignment.status);
14352
14845
  const isCurrent = currentAssignment?.id === assignment.id;
14353
- return /* @__PURE__ */ import_react5.default.createElement(
14354
- import_react_native5.TouchableOpacity,
14846
+ return /* @__PURE__ */ import_react6.default.createElement(
14847
+ import_react_native6.TouchableOpacity,
14355
14848
  {
14356
14849
  key: assignment.id,
14357
14850
  style: [styles3.testItem, isCurrent && styles3.testItemCurrent],
14358
14851
  onPress: () => nav.push({ name: "TEST_DETAIL", testId: assignment.id })
14359
14852
  },
14360
- /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.testBadge }, badge.icon),
14361
- /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.testInfo }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.testTitle, numberOfLines: 1 }, assignment.testCase.title), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.testMetaRow }, assignment.isVerification && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.retestTag }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.retestTagText }, "Retest")), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.testMeta }, assignment.testCase.testKey, " \xB7 ", assignment.testCase.priority), assignment.testCase.role && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.roleBadgeRow }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.testMeta }, " \xB7 "), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: [styles3.roleBadgeDot, { backgroundColor: assignment.testCase.role.color }] }), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: [styles3.testMeta, { color: assignment.testCase.role.color, fontWeight: "500" }] }, assignment.testCase.role.name)))),
14362
- /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: [
14853
+ /* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: styles3.testBadge }, badge.icon),
14854
+ /* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, { style: styles3.testInfo }, /* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: styles3.testTitle, numberOfLines: 1 }, assignment.testCase.title), /* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, { style: styles3.testMetaRow }, assignment.isVerification && /* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, { style: styles3.retestTag }, /* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: styles3.retestTagText }, "Retest")), /* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: styles3.testMeta }, assignment.testCase.testKey, " \xB7 ", assignment.testCase.priority), assignment.testCase.role && /* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, { style: styles3.roleBadgeRow }, /* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: styles3.testMeta }, " \xB7 "), /* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, { style: [styles3.roleBadgeDot, { backgroundColor: assignment.testCase.role.color }] }), /* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: [styles3.testMeta, { color: assignment.testCase.role.color, fontWeight: "500" }] }, assignment.testCase.role.name)))),
14855
+ /* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, { style: [
14363
14856
  styles3.statusPill,
14364
14857
  {
14365
14858
  backgroundColor: assignment.status === "passed" ? "#14532d" : assignment.status === "failed" ? "#450a0a" : assignment.status === "in_progress" ? "#172554" : "#27272a",
14366
14859
  borderColor: assignment.status === "passed" ? "#166534" : assignment.status === "failed" ? "#7f1d1d" : assignment.status === "in_progress" ? "#1e3a5f" : "#3f3f46"
14367
14860
  }
14368
- ] }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: [
14861
+ ] }, /* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: [
14369
14862
  styles3.statusPillText,
14370
14863
  {
14371
14864
  color: assignment.status === "passed" ? "#4ade80" : assignment.status === "failed" ? "#f87171" : assignment.status === "in_progress" ? "#60a5fa" : "#d4d4d8"
@@ -14373,9 +14866,9 @@ function TestListScreen({ nav }) {
14373
14866
  ] }, badge.label))
14374
14867
  );
14375
14868
  }));
14376
- }), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { style: styles3.refreshBtn, onPress: refreshAssignments }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.refreshText }, "\u21BB", " Refresh")));
14869
+ }), /* @__PURE__ */ import_react6.default.createElement(import_react_native6.TouchableOpacity, { style: styles3.refreshBtn, onPress: refreshAssignments }, /* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: styles3.refreshText }, "\u21BB", " Refresh")));
14377
14870
  }
14378
- var styles3 = import_react_native5.StyleSheet.create({
14871
+ var styles3 = import_react_native6.StyleSheet.create({
14379
14872
  filterBar: { flexDirection: "row", gap: 8, marginBottom: 8 },
14380
14873
  filterBtn: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 8, backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border },
14381
14874
  filterBtnActive: { backgroundColor: colors.blue, borderColor: colors.blue },
@@ -14410,16 +14903,28 @@ var styles3 = import_react_native5.StyleSheet.create({
14410
14903
  testMeta: { fontSize: 11, color: colors.textDim },
14411
14904
  statusPill: { paddingHorizontal: 8, paddingVertical: 3, borderRadius: 6, borderWidth: 1, marginLeft: 8 },
14412
14905
  statusPillText: { fontSize: 10, fontWeight: "600" },
14906
+ searchContainer: { marginBottom: 8 },
14907
+ searchInput: { backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border, borderRadius: 8, paddingHorizontal: 12, paddingVertical: 8, fontSize: 13, color: colors.textPrimary },
14908
+ trackSortRow: { flexDirection: "row", alignItems: "center", marginBottom: 10, gap: 8 },
14909
+ trackBtn: { flexDirection: "row", alignItems: "center", gap: 4, paddingHorizontal: 10, paddingVertical: 4, borderRadius: 6, marginRight: 6, borderWidth: 1, borderColor: "transparent" },
14910
+ trackBtnActive: { backgroundColor: colors.card, borderColor: colors.border },
14911
+ trackBtnText: { fontSize: 11, color: colors.textMuted },
14912
+ trackBtnTextActive: { color: colors.textPrimary, fontWeight: "600" },
14913
+ sortGroup: { flexDirection: "row", gap: 2 },
14914
+ sortBtn: { paddingHorizontal: 8, paddingVertical: 4, borderRadius: 6, borderWidth: 1, borderColor: "transparent" },
14915
+ sortBtnActive: { backgroundColor: colors.card, borderColor: colors.border },
14916
+ sortBtnText: { fontSize: 11, color: colors.textMuted },
14917
+ sortBtnTextActive: { color: colors.textPrimary, fontWeight: "600" },
14413
14918
  refreshBtn: { alignItems: "center", paddingVertical: 12 },
14414
14919
  refreshText: { fontSize: 13, color: colors.blue }
14415
14920
  });
14416
14921
 
14417
14922
  // src/widget/screens/TestFeedbackScreen.tsx
14418
- var import_react9 = __toESM(require("react"));
14419
- var import_react_native8 = require("react-native");
14923
+ var import_react10 = __toESM(require("react"));
14924
+ var import_react_native9 = require("react-native");
14420
14925
 
14421
14926
  // src/widget/useImageAttachments.ts
14422
- var import_react6 = require("react");
14927
+ var import_react7 = require("react");
14423
14928
  var launchImageLibrary = null;
14424
14929
  var launchCamera = null;
14425
14930
  try {
@@ -14430,8 +14935,8 @@ try {
14430
14935
  }
14431
14936
  var IMAGE_PICKER_AVAILABLE = launchImageLibrary !== null;
14432
14937
  function useImageAttachments(uploadFn, maxImages, bucket = "screenshots") {
14433
- const [images, setImages] = (0, import_react6.useState)([]);
14434
- const pickFromGallery = (0, import_react6.useCallback)(async () => {
14938
+ const [images, setImages] = (0, import_react7.useState)([]);
14939
+ const pickFromGallery = (0, import_react7.useCallback)(async () => {
14435
14940
  if (!launchImageLibrary || images.length >= maxImages) return;
14436
14941
  launchImageLibrary(
14437
14942
  { mediaType: "photo", quality: 0.7, maxWidth: 1920, maxHeight: 1920, selectionLimit: maxImages - images.length },
@@ -14465,7 +14970,7 @@ function useImageAttachments(uploadFn, maxImages, bucket = "screenshots") {
14465
14970
  }
14466
14971
  );
14467
14972
  }, [images.length, maxImages, uploadFn, bucket]);
14468
- const pickFromCamera = (0, import_react6.useCallback)(async () => {
14973
+ const pickFromCamera = (0, import_react7.useCallback)(async () => {
14469
14974
  if (!launchCamera || images.length >= maxImages) return;
14470
14975
  launchCamera(
14471
14976
  { mediaType: "photo", quality: 0.7, maxWidth: 1920, maxHeight: 1920 },
@@ -14498,35 +15003,35 @@ function useImageAttachments(uploadFn, maxImages, bucket = "screenshots") {
14498
15003
  }
14499
15004
  );
14500
15005
  }, [images.length, maxImages, uploadFn, bucket]);
14501
- const removeImage = (0, import_react6.useCallback)((id) => {
15006
+ const removeImage = (0, import_react7.useCallback)((id) => {
14502
15007
  setImages((prev) => prev.filter((img) => img.id !== id));
14503
15008
  }, []);
14504
- const clear = (0, import_react6.useCallback)(() => {
15009
+ const clear = (0, import_react7.useCallback)(() => {
14505
15010
  setImages([]);
14506
15011
  }, []);
14507
15012
  const isUploading = images.some((img) => img.status === "uploading");
14508
15013
  const hasError = images.some((img) => img.status === "error");
14509
- const getAttachments = (0, import_react6.useCallback)(() => {
15014
+ const getAttachments = (0, import_react7.useCallback)(() => {
14510
15015
  return images.filter((img) => img.status === "done" && img.remoteUrl).map((img) => ({ type: "image", url: img.remoteUrl, name: img.name }));
14511
15016
  }, [images]);
14512
- const getScreenshotUrls = (0, import_react6.useCallback)(() => {
15017
+ const getScreenshotUrls = (0, import_react7.useCallback)(() => {
14513
15018
  return images.filter((img) => img.status === "done" && img.remoteUrl).map((img) => img.remoteUrl);
14514
15019
  }, [images]);
14515
15020
  return { images, pickFromGallery, pickFromCamera, removeImage, clear, isUploading, hasError, getAttachments, getScreenshotUrls };
14516
15021
  }
14517
15022
 
14518
15023
  // src/widget/ImagePickerButtons.tsx
14519
- var import_react8 = __toESM(require("react"));
14520
- var import_react_native7 = require("react-native");
15024
+ var import_react9 = __toESM(require("react"));
15025
+ var import_react_native8 = require("react-native");
14521
15026
 
14522
15027
  // src/widget/ImagePreviewStrip.tsx
14523
- var import_react7 = __toESM(require("react"));
14524
- var import_react_native6 = require("react-native");
15028
+ var import_react8 = __toESM(require("react"));
15029
+ var import_react_native7 = require("react-native");
14525
15030
  function ImagePreviewStrip({ images, onRemove }) {
14526
15031
  if (images.length === 0) return null;
14527
- return /* @__PURE__ */ import_react7.default.createElement(import_react_native6.ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: styles4.strip }, images.map((img) => /* @__PURE__ */ import_react7.default.createElement(import_react_native6.View, { key: img.id, style: styles4.thumbContainer }, /* @__PURE__ */ import_react7.default.createElement(import_react_native6.Image, { source: { uri: img.localUri }, style: styles4.thumb }), img.status === "uploading" && /* @__PURE__ */ import_react7.default.createElement(import_react_native6.View, { style: styles4.thumbOverlay }, /* @__PURE__ */ import_react7.default.createElement(import_react_native6.ActivityIndicator, { size: "small", color: "#fff" })), img.status === "error" && /* @__PURE__ */ import_react7.default.createElement(import_react_native6.View, { style: [styles4.thumbOverlay, styles4.thumbOverlayError] }, /* @__PURE__ */ import_react7.default.createElement(import_react_native6.Text, { style: styles4.thumbErrorText }, "!")), /* @__PURE__ */ import_react7.default.createElement(import_react_native6.TouchableOpacity, { style: styles4.thumbRemove, onPress: () => onRemove(img.id) }, /* @__PURE__ */ import_react7.default.createElement(import_react_native6.Text, { style: styles4.thumbRemoveText }, "\u2715")))));
15032
+ return /* @__PURE__ */ import_react8.default.createElement(import_react_native7.ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: styles4.strip }, images.map((img) => /* @__PURE__ */ import_react8.default.createElement(import_react_native7.View, { key: img.id, style: styles4.thumbContainer }, /* @__PURE__ */ import_react8.default.createElement(import_react_native7.Image, { source: { uri: img.localUri }, style: styles4.thumb }), img.status === "uploading" && /* @__PURE__ */ import_react8.default.createElement(import_react_native7.View, { style: styles4.thumbOverlay }, /* @__PURE__ */ import_react8.default.createElement(import_react_native7.ActivityIndicator, { size: "small", color: "#fff" })), img.status === "error" && /* @__PURE__ */ import_react8.default.createElement(import_react_native7.View, { style: [styles4.thumbOverlay, styles4.thumbOverlayError] }, /* @__PURE__ */ import_react8.default.createElement(import_react_native7.Text, { style: styles4.thumbErrorText }, "!")), /* @__PURE__ */ import_react8.default.createElement(import_react_native7.TouchableOpacity, { style: styles4.thumbRemove, onPress: () => onRemove(img.id) }, /* @__PURE__ */ import_react8.default.createElement(import_react_native7.Text, { style: styles4.thumbRemoveText }, "\u2715")))));
14528
15033
  }
14529
- var styles4 = import_react_native6.StyleSheet.create({
15034
+ var styles4 = import_react_native7.StyleSheet.create({
14530
15035
  strip: {
14531
15036
  flexDirection: "row",
14532
15037
  marginTop: 4
@@ -14545,7 +15050,7 @@ var styles4 = import_react_native6.StyleSheet.create({
14545
15050
  borderRadius: 8
14546
15051
  },
14547
15052
  thumbOverlay: {
14548
- ...import_react_native6.StyleSheet.absoluteFillObject,
15053
+ ...import_react_native7.StyleSheet.absoluteFillObject,
14549
15054
  backgroundColor: "rgba(0,0,0,0.5)",
14550
15055
  justifyContent: "center",
14551
15056
  alignItems: "center",
@@ -14580,25 +15085,25 @@ var styles4 = import_react_native6.StyleSheet.create({
14580
15085
  // src/widget/ImagePickerButtons.tsx
14581
15086
  function ImagePickerButtons({ images, maxImages, onPickGallery, onPickCamera, onRemove, label }) {
14582
15087
  if (!IMAGE_PICKER_AVAILABLE) return null;
14583
- return /* @__PURE__ */ import_react8.default.createElement(import_react_native7.View, { style: styles5.section }, label && /* @__PURE__ */ import_react8.default.createElement(import_react_native7.Text, { style: styles5.label }, label), /* @__PURE__ */ import_react8.default.createElement(import_react_native7.View, { style: styles5.buttonRow }, /* @__PURE__ */ import_react8.default.createElement(
14584
- import_react_native7.TouchableOpacity,
15088
+ return /* @__PURE__ */ import_react9.default.createElement(import_react_native8.View, { style: styles5.section }, label && /* @__PURE__ */ import_react9.default.createElement(import_react_native8.Text, { style: styles5.label }, label), /* @__PURE__ */ import_react9.default.createElement(import_react_native8.View, { style: styles5.buttonRow }, /* @__PURE__ */ import_react9.default.createElement(
15089
+ import_react_native8.TouchableOpacity,
14585
15090
  {
14586
15091
  style: styles5.pickButton,
14587
15092
  onPress: onPickGallery,
14588
15093
  disabled: images.length >= maxImages
14589
15094
  },
14590
- /* @__PURE__ */ import_react8.default.createElement(import_react_native7.Text, { style: [styles5.pickButtonText, images.length >= maxImages && styles5.pickButtonDisabled] }, "Gallery")
14591
- ), /* @__PURE__ */ import_react8.default.createElement(
14592
- import_react_native7.TouchableOpacity,
15095
+ /* @__PURE__ */ import_react9.default.createElement(import_react_native8.Text, { style: [styles5.pickButtonText, images.length >= maxImages && styles5.pickButtonDisabled] }, "Gallery")
15096
+ ), /* @__PURE__ */ import_react9.default.createElement(
15097
+ import_react_native8.TouchableOpacity,
14593
15098
  {
14594
15099
  style: styles5.pickButton,
14595
15100
  onPress: onPickCamera,
14596
15101
  disabled: images.length >= maxImages
14597
15102
  },
14598
- /* @__PURE__ */ import_react8.default.createElement(import_react_native7.Text, { style: [styles5.pickButtonText, images.length >= maxImages && styles5.pickButtonDisabled] }, "Camera")
14599
- ), /* @__PURE__ */ import_react8.default.createElement(import_react_native7.Text, { style: styles5.countText }, images.length, "/", maxImages)), /* @__PURE__ */ import_react8.default.createElement(ImagePreviewStrip, { images, onRemove }));
15103
+ /* @__PURE__ */ import_react9.default.createElement(import_react_native8.Text, { style: [styles5.pickButtonText, images.length >= maxImages && styles5.pickButtonDisabled] }, "Camera")
15104
+ ), /* @__PURE__ */ import_react9.default.createElement(import_react_native8.Text, { style: styles5.countText }, images.length, "/", maxImages)), /* @__PURE__ */ import_react9.default.createElement(ImagePreviewStrip, { images, onRemove }));
14600
15105
  }
14601
- var styles5 = import_react_native7.StyleSheet.create({
15106
+ var styles5 = import_react_native8.StyleSheet.create({
14602
15107
  section: {
14603
15108
  marginTop: 12,
14604
15109
  marginBottom: 4
@@ -14641,13 +15146,14 @@ var styles5 = import_react_native7.StyleSheet.create({
14641
15146
  function TestFeedbackScreen({ status, assignmentId, nav }) {
14642
15147
  const { client, assignments, refreshAssignments, uploadImage } = useBugBear();
14643
15148
  const images = useImageAttachments(uploadImage, 3, "screenshots");
14644
- const [rating, setRating] = (0, import_react9.useState)(5);
14645
- const [note, setNote] = (0, import_react9.useState)("");
14646
- const [flags, setFlags] = (0, import_react9.useState)({ isOutdated: false, needsMoreDetail: false, stepsUnclear: false, expectedResultUnclear: false });
14647
- const [submitting, setSubmitting] = (0, import_react9.useState)(false);
15149
+ const [rating, setRating] = (0, import_react10.useState)(5);
15150
+ const [note, setNote] = (0, import_react10.useState)("");
15151
+ const [flags, setFlags] = (0, import_react10.useState)({ isOutdated: false, needsMoreDetail: false, stepsUnclear: false, expectedResultUnclear: false });
15152
+ const [submitting, setSubmitting] = (0, import_react10.useState)(false);
14648
15153
  const assignment = assignments.find((a) => a.id === assignmentId);
14649
15154
  const showFlags = rating < 4;
14650
15155
  const handleSubmit = async () => {
15156
+ if (submitting || images.isUploading) return;
14651
15157
  setSubmitting(true);
14652
15158
  if (client && assignment) {
14653
15159
  const screenshotUrls = images.getScreenshotUrls();
@@ -14698,22 +15204,22 @@ function TestFeedbackScreen({ status, assignmentId, nav }) {
14698
15204
  }
14699
15205
  }
14700
15206
  };
14701
- return /* @__PURE__ */ import_react9.default.createElement(import_react_native8.View, { style: styles6.container }, /* @__PURE__ */ import_react9.default.createElement(import_react_native8.Text, { style: styles6.header }, status === "passed" ? "\u2705 Test Passed!" : "\u274C Test Failed"), /* @__PURE__ */ import_react9.default.createElement(import_react_native8.Text, { style: styles6.subheader }, "Rate this test case"), /* @__PURE__ */ import_react9.default.createElement(import_react_native8.View, { style: styles6.starRow }, [1, 2, 3, 4, 5].map((n) => /* @__PURE__ */ import_react9.default.createElement(import_react_native8.TouchableOpacity, { key: n, onPress: () => setRating(n), style: styles6.starButton }, /* @__PURE__ */ import_react9.default.createElement(import_react_native8.Text, { style: [styles6.star, n <= rating && styles6.starActive] }, n <= rating ? "\u2605" : "\u2606")))), showFlags && /* @__PURE__ */ import_react9.default.createElement(import_react_native8.View, { style: styles6.flagsSection }, /* @__PURE__ */ import_react9.default.createElement(import_react_native8.Text, { style: styles6.flagsLabel }, "What could be improved?"), [
15207
+ return /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, { style: styles6.container }, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: styles6.header }, status === "passed" ? "\u2705 Test Passed!" : "\u274C Test Failed"), /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: styles6.subheader }, "Rate this test case"), /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, { style: styles6.starRow }, [1, 2, 3, 4, 5].map((n) => /* @__PURE__ */ import_react10.default.createElement(import_react_native9.TouchableOpacity, { key: n, onPress: () => setRating(n), style: styles6.starButton }, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: [styles6.star, n <= rating && styles6.starActive] }, n <= rating ? "\u2605" : "\u2606")))), showFlags && /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, { style: styles6.flagsSection }, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: styles6.flagsLabel }, "What could be improved?"), [
14702
15208
  { key: "isOutdated", label: "Test is outdated" },
14703
15209
  { key: "needsMoreDetail", label: "Needs more detail" },
14704
15210
  { key: "stepsUnclear", label: "Steps are unclear" },
14705
15211
  { key: "expectedResultUnclear", label: "Expected result unclear" }
14706
- ].map(({ key, label }) => /* @__PURE__ */ import_react9.default.createElement(
14707
- import_react_native8.TouchableOpacity,
15212
+ ].map(({ key, label }) => /* @__PURE__ */ import_react10.default.createElement(
15213
+ import_react_native9.TouchableOpacity,
14708
15214
  {
14709
15215
  key,
14710
15216
  style: [styles6.flagItem, flags[key] && styles6.flagItemActive],
14711
15217
  onPress: () => setFlags((prev) => ({ ...prev, [key]: !prev[key] }))
14712
15218
  },
14713
- /* @__PURE__ */ import_react9.default.createElement(import_react_native8.View, { style: [styles6.flagCheck, flags[key] && styles6.flagCheckActive] }, flags[key] && /* @__PURE__ */ import_react9.default.createElement(import_react_native8.Text, { style: styles6.flagCheckmark }, "\u2713")),
14714
- /* @__PURE__ */ import_react9.default.createElement(import_react_native8.Text, { style: [styles6.flagText, flags[key] && styles6.flagTextActive] }, label)
14715
- ))), /* @__PURE__ */ import_react9.default.createElement(
14716
- import_react_native8.TextInput,
15219
+ /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, { style: [styles6.flagCheck, flags[key] && styles6.flagCheckActive] }, flags[key] && /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: styles6.flagCheckmark }, "\u2713")),
15220
+ /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: [styles6.flagText, flags[key] && styles6.flagTextActive] }, label)
15221
+ ))), /* @__PURE__ */ import_react10.default.createElement(
15222
+ import_react_native9.TextInput,
14717
15223
  {
14718
15224
  style: styles6.noteInput,
14719
15225
  value: note,
@@ -14722,7 +15228,7 @@ function TestFeedbackScreen({ status, assignmentId, nav }) {
14722
15228
  placeholderTextColor: colors.textMuted,
14723
15229
  multiline: true
14724
15230
  }
14725
- ), /* @__PURE__ */ import_react9.default.createElement(
15231
+ ), /* @__PURE__ */ import_react10.default.createElement(
14726
15232
  ImagePickerButtons,
14727
15233
  {
14728
15234
  images: images.images,
@@ -14732,9 +15238,9 @@ function TestFeedbackScreen({ status, assignmentId, nav }) {
14732
15238
  onRemove: images.removeImage,
14733
15239
  label: "Screenshots (optional)"
14734
15240
  }
14735
- ), /* @__PURE__ */ import_react9.default.createElement(import_react_native8.View, { style: styles6.actions }, /* @__PURE__ */ import_react9.default.createElement(import_react_native8.TouchableOpacity, { style: styles6.skipButton, onPress: handleSkip }, /* @__PURE__ */ import_react9.default.createElement(import_react_native8.Text, { style: styles6.skipText }, "Skip")), /* @__PURE__ */ import_react9.default.createElement(import_react_native8.TouchableOpacity, { style: [shared.primaryButton, { flex: 2, opacity: submitting || images.isUploading ? 0.5 : 1 }], onPress: handleSubmit, disabled: submitting || images.isUploading }, /* @__PURE__ */ import_react9.default.createElement(import_react_native8.Text, { style: shared.primaryButtonText }, images.isUploading ? "Uploading..." : submitting ? "Submitting..." : "Submit"))));
15241
+ ), /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, { style: styles6.actions }, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.TouchableOpacity, { style: styles6.skipButton, onPress: handleSkip }, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: styles6.skipText }, "Skip")), /* @__PURE__ */ import_react10.default.createElement(import_react_native9.TouchableOpacity, { style: [shared.primaryButton, { flex: 2, opacity: submitting || images.isUploading ? 0.5 : 1 }], onPress: handleSubmit, disabled: submitting || images.isUploading }, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: shared.primaryButtonText }, images.isUploading ? "Uploading..." : submitting ? "Submitting..." : "Submit"))));
14736
15242
  }
14737
- var styles6 = import_react_native8.StyleSheet.create({
15243
+ var styles6 = import_react_native9.StyleSheet.create({
14738
15244
  container: { paddingTop: 8 },
14739
15245
  header: { fontSize: 22, fontWeight: "700", color: colors.textPrimary, textAlign: "center", marginBottom: 4 },
14740
15246
  subheader: { fontSize: 14, color: colors.textMuted, textAlign: "center", marginBottom: 20 },
@@ -14758,12 +15264,12 @@ var styles6 = import_react_native8.StyleSheet.create({
14758
15264
  });
14759
15265
 
14760
15266
  // src/widget/screens/ReportScreen.tsx
14761
- var import_react11 = __toESM(require("react"));
14762
- var import_react_native10 = require("react-native");
15267
+ var import_react12 = __toESM(require("react"));
15268
+ var import_react_native11 = require("react-native");
14763
15269
 
14764
15270
  // src/widget/CategoryPicker.tsx
14765
- var import_react10 = __toESM(require("react"));
14766
- var import_react_native9 = require("react-native");
15271
+ var import_react11 = __toESM(require("react"));
15272
+ var import_react_native10 = require("react-native");
14767
15273
  var categoryOptions = [
14768
15274
  { value: "ui_ux", label: "UI/UX", icon: "\u{1F3A8}" },
14769
15275
  { value: "functional", label: "Functional", icon: "\u2699\uFE0F" },
@@ -14772,64 +15278,64 @@ var categoryOptions = [
14772
15278
  { value: "other", label: "Other", icon: "\u{1F4DD}" }
14773
15279
  ];
14774
15280
  function CategoryPicker({ value, onChange, optional = true }) {
14775
- const [modalVisible, setModalVisible] = (0, import_react10.useState)(false);
15281
+ const [modalVisible, setModalVisible] = (0, import_react11.useState)(false);
14776
15282
  const selectedOption = value ? categoryOptions.find((o) => o.value === value) : null;
14777
15283
  const handleSelect = (category) => {
14778
15284
  onChange(category);
14779
15285
  setModalVisible(false);
14780
15286
  };
14781
- return /* @__PURE__ */ import_react10.default.createElement(import_react10.default.Fragment, null, /* @__PURE__ */ import_react10.default.createElement(
14782
- import_react_native9.TouchableOpacity,
15287
+ return /* @__PURE__ */ import_react11.default.createElement(import_react11.default.Fragment, null, /* @__PURE__ */ import_react11.default.createElement(
15288
+ import_react_native10.TouchableOpacity,
14783
15289
  {
14784
15290
  style: styles7.trigger,
14785
15291
  onPress: () => setModalVisible(true)
14786
15292
  },
14787
- /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: selectedOption ? styles7.triggerTextSelected : styles7.triggerTextPlaceholder }, selectedOption ? `${selectedOption.icon} ${selectedOption.label}` : optional ? "Select category (optional)" : "Select category"),
14788
- /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: styles7.chevron }, "\u25BC")
14789
- ), /* @__PURE__ */ import_react10.default.createElement(
14790
- import_react_native9.Modal,
15293
+ /* @__PURE__ */ import_react11.default.createElement(import_react_native10.Text, { style: selectedOption ? styles7.triggerTextSelected : styles7.triggerTextPlaceholder }, selectedOption ? `${selectedOption.icon} ${selectedOption.label}` : optional ? "Select category (optional)" : "Select category"),
15294
+ /* @__PURE__ */ import_react11.default.createElement(import_react_native10.Text, { style: styles7.chevron }, "\u25BC")
15295
+ ), /* @__PURE__ */ import_react11.default.createElement(
15296
+ import_react_native10.Modal,
14791
15297
  {
14792
15298
  visible: modalVisible,
14793
15299
  transparent: true,
14794
15300
  animationType: "fade",
14795
15301
  onRequestClose: () => setModalVisible(false)
14796
15302
  },
14797
- /* @__PURE__ */ import_react10.default.createElement(
14798
- import_react_native9.TouchableOpacity,
15303
+ /* @__PURE__ */ import_react11.default.createElement(
15304
+ import_react_native10.TouchableOpacity,
14799
15305
  {
14800
15306
  style: styles7.overlay,
14801
15307
  activeOpacity: 1,
14802
15308
  onPress: () => setModalVisible(false)
14803
15309
  },
14804
- /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, { style: styles7.modal, onStartShouldSetResponder: () => true }, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: styles7.modalTitle }, "Select Category"), optional && /* @__PURE__ */ import_react10.default.createElement(
14805
- import_react_native9.TouchableOpacity,
15310
+ /* @__PURE__ */ import_react11.default.createElement(import_react_native10.View, { style: styles7.modal, onStartShouldSetResponder: () => true }, /* @__PURE__ */ import_react11.default.createElement(import_react_native10.Text, { style: styles7.modalTitle }, "Select Category"), optional && /* @__PURE__ */ import_react11.default.createElement(
15311
+ import_react_native10.TouchableOpacity,
14806
15312
  {
14807
15313
  style: [styles7.option, !value && styles7.optionSelected],
14808
15314
  onPress: () => handleSelect(null)
14809
15315
  },
14810
- /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: styles7.optionText }, "\u2014 None \u2014")
14811
- ), categoryOptions.map(({ value: optValue, label, icon }) => /* @__PURE__ */ import_react10.default.createElement(
14812
- import_react_native9.TouchableOpacity,
15316
+ /* @__PURE__ */ import_react11.default.createElement(import_react_native10.Text, { style: styles7.optionText }, "\u2014 None \u2014")
15317
+ ), categoryOptions.map(({ value: optValue, label, icon }) => /* @__PURE__ */ import_react11.default.createElement(
15318
+ import_react_native10.TouchableOpacity,
14813
15319
  {
14814
15320
  key: optValue,
14815
15321
  style: [styles7.option, value === optValue && styles7.optionSelected],
14816
15322
  onPress: () => handleSelect(optValue)
14817
15323
  },
14818
- /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: styles7.optionIcon }, icon),
14819
- /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: styles7.optionText }, label),
14820
- value === optValue && /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: styles7.checkmark }, "\u2713")
14821
- )), /* @__PURE__ */ import_react10.default.createElement(
14822
- import_react_native9.TouchableOpacity,
15324
+ /* @__PURE__ */ import_react11.default.createElement(import_react_native10.Text, { style: styles7.optionIcon }, icon),
15325
+ /* @__PURE__ */ import_react11.default.createElement(import_react_native10.Text, { style: styles7.optionText }, label),
15326
+ value === optValue && /* @__PURE__ */ import_react11.default.createElement(import_react_native10.Text, { style: styles7.checkmark }, "\u2713")
15327
+ )), /* @__PURE__ */ import_react11.default.createElement(
15328
+ import_react_native10.TouchableOpacity,
14823
15329
  {
14824
15330
  style: styles7.cancelButton,
14825
15331
  onPress: () => setModalVisible(false)
14826
15332
  },
14827
- /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: styles7.cancelText }, "Cancel")
15333
+ /* @__PURE__ */ import_react11.default.createElement(import_react_native10.Text, { style: styles7.cancelText }, "Cancel")
14828
15334
  ))
14829
15335
  )
14830
15336
  ));
14831
15337
  }
14832
- var styles7 = import_react_native9.StyleSheet.create({
15338
+ var styles7 = import_react_native10.StyleSheet.create({
14833
15339
  trigger: {
14834
15340
  flexDirection: "row",
14835
15341
  alignItems: "center",
@@ -14919,18 +15425,18 @@ var styles7 = import_react_native9.StyleSheet.create({
14919
15425
  // src/widget/screens/ReportScreen.tsx
14920
15426
  function ReportScreen({ nav, prefill }) {
14921
15427
  const { client, getDeviceInfo, uploadImage, refreshAssignments } = useBugBear();
14922
- const [reportType, setReportType] = (0, import_react11.useState)(prefill?.type || "bug");
14923
- const [severity, setSeverity] = (0, import_react11.useState)("medium");
14924
- const [category, setCategory] = (0, import_react11.useState)(null);
14925
- const [description, setDescription] = (0, import_react11.useState)("");
14926
- const [affectedScreen, setAffectedScreen] = (0, import_react11.useState)("");
14927
- const [submitting, setSubmitting] = (0, import_react11.useState)(false);
14928
- const [error, setError] = (0, import_react11.useState)(null);
14929
- const submittingRef = (0, import_react11.useRef)(false);
15428
+ const [reportType, setReportType] = (0, import_react12.useState)(prefill?.type || "bug");
15429
+ const [severity, setSeverity] = (0, import_react12.useState)("medium");
15430
+ const [category, setCategory] = (0, import_react12.useState)(null);
15431
+ const [description, setDescription] = (0, import_react12.useState)("");
15432
+ const [affectedScreen, setAffectedScreen] = (0, import_react12.useState)("");
15433
+ const [submitting, setSubmitting] = (0, import_react12.useState)(false);
15434
+ const [error, setError] = (0, import_react12.useState)(null);
15435
+ const submittingRef = (0, import_react12.useRef)(false);
14930
15436
  const images = useImageAttachments(uploadImage, 5, "screenshots");
14931
15437
  const isRetestFailure = prefill?.type === "test_fail";
14932
15438
  const isBugType = reportType === "bug" || reportType === "test_fail";
14933
- (0, import_react11.useEffect)(() => {
15439
+ (0, import_react12.useEffect)(() => {
14934
15440
  if (reportType === "feedback" || reportType === "suggestion") {
14935
15441
  setCategory("other");
14936
15442
  } else {
@@ -14938,7 +15444,7 @@ function ReportScreen({ nav, prefill }) {
14938
15444
  }
14939
15445
  }, [reportType]);
14940
15446
  const handleSubmit = async () => {
14941
- if (!client || !description.trim()) return;
15447
+ if (!client || !description.trim() || images.isUploading) return;
14942
15448
  if (submittingRef.current) return;
14943
15449
  submittingRef.current = true;
14944
15450
  setSubmitting(true);
@@ -14979,21 +15485,21 @@ function ReportScreen({ nav, prefill }) {
14979
15485
  submittingRef.current = false;
14980
15486
  }
14981
15487
  };
14982
- return /* @__PURE__ */ import_react11.default.createElement(import_react_native10.View, null, isRetestFailure ? /* @__PURE__ */ import_react11.default.createElement(import_react11.default.Fragment, null, /* @__PURE__ */ import_react11.default.createElement(import_react_native10.View, { style: styles8.retestBanner }, /* @__PURE__ */ import_react11.default.createElement(import_react_native10.Text, { style: styles8.retestIcon }, "\u{1F504}"), /* @__PURE__ */ import_react11.default.createElement(import_react_native10.View, null, /* @__PURE__ */ import_react11.default.createElement(import_react_native10.Text, { style: styles8.retestTitle }, "Bug Still Present"), /* @__PURE__ */ import_react11.default.createElement(import_react_native10.Text, { style: styles8.retestSubtitle }, "The fix did not resolve this issue"))), /* @__PURE__ */ import_react11.default.createElement(import_react_native10.View, { style: styles8.section }, /* @__PURE__ */ import_react11.default.createElement(import_react_native10.Text, { style: shared.label }, "Severity"), /* @__PURE__ */ import_react11.default.createElement(import_react_native10.View, { style: styles8.severityRow }, [
15488
+ return /* @__PURE__ */ import_react12.default.createElement(import_react_native11.View, null, isRetestFailure ? /* @__PURE__ */ import_react12.default.createElement(import_react12.default.Fragment, null, /* @__PURE__ */ import_react12.default.createElement(import_react_native11.View, { style: styles8.retestBanner }, /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: styles8.retestIcon }, "\u{1F504}"), /* @__PURE__ */ import_react12.default.createElement(import_react_native11.View, null, /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: styles8.retestTitle }, "Bug Still Present"), /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: styles8.retestSubtitle }, "The fix did not resolve this issue"))), /* @__PURE__ */ import_react12.default.createElement(import_react_native11.View, { style: styles8.section }, /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: shared.label }, "Severity"), /* @__PURE__ */ import_react12.default.createElement(import_react_native11.View, { style: styles8.severityRow }, [
14983
15489
  { sev: "critical", color: "#ef4444" },
14984
15490
  { sev: "high", color: "#f97316" },
14985
15491
  { sev: "medium", color: "#eab308" },
14986
15492
  { sev: "low", color: "#6b7280" }
14987
- ].map(({ sev, color }) => /* @__PURE__ */ import_react11.default.createElement(
14988
- import_react_native10.TouchableOpacity,
15493
+ ].map(({ sev, color }) => /* @__PURE__ */ import_react12.default.createElement(
15494
+ import_react_native11.TouchableOpacity,
14989
15495
  {
14990
15496
  key: sev,
14991
15497
  style: [styles8.sevButton, severity === sev && { backgroundColor: `${color}30`, borderColor: color }],
14992
15498
  onPress: () => setSeverity(sev)
14993
15499
  },
14994
- /* @__PURE__ */ import_react11.default.createElement(import_react_native10.Text, { style: [styles8.sevText, severity === sev && { color }] }, sev)
14995
- )))), /* @__PURE__ */ import_react11.default.createElement(import_react_native10.View, { style: styles8.section }, /* @__PURE__ */ import_react11.default.createElement(import_react_native10.Text, { style: shared.label }, "Category (optional)"), /* @__PURE__ */ import_react11.default.createElement(CategoryPicker, { value: category, onChange: setCategory, optional: true })), /* @__PURE__ */ import_react11.default.createElement(import_react_native10.View, { style: styles8.section }, /* @__PURE__ */ import_react11.default.createElement(import_react_native10.Text, { style: shared.label }, "What went wrong?"), /* @__PURE__ */ import_react11.default.createElement(
14996
- import_react_native10.TextInput,
15500
+ /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: [styles8.sevText, severity === sev && { color }] }, sev)
15501
+ )))), /* @__PURE__ */ import_react12.default.createElement(import_react_native11.View, { style: styles8.section }, /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: shared.label }, "Category (optional)"), /* @__PURE__ */ import_react12.default.createElement(CategoryPicker, { value: category, onChange: setCategory, optional: true })), /* @__PURE__ */ import_react12.default.createElement(import_react_native11.View, { style: styles8.section }, /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: shared.label }, "What went wrong?"), /* @__PURE__ */ import_react12.default.createElement(
15502
+ import_react_native11.TextInput,
14997
15503
  {
14998
15504
  style: styles8.descInput,
14999
15505
  value: description,
@@ -15004,7 +15510,7 @@ function ReportScreen({ nav, prefill }) {
15004
15510
  numberOfLines: 4,
15005
15511
  textAlignVertical: "top"
15006
15512
  }
15007
- )), /* @__PURE__ */ import_react11.default.createElement(
15513
+ )), /* @__PURE__ */ import_react12.default.createElement(
15008
15514
  ImagePickerButtons,
15009
15515
  {
15010
15516
  images: images.images,
@@ -15014,42 +15520,42 @@ function ReportScreen({ nav, prefill }) {
15014
15520
  onRemove: images.removeImage,
15015
15521
  label: "Attachments (optional)"
15016
15522
  }
15017
- ), error && /* @__PURE__ */ import_react11.default.createElement(import_react_native10.View, { style: styles8.errorBanner }, /* @__PURE__ */ import_react11.default.createElement(import_react_native10.Text, { style: styles8.errorText }, error)), /* @__PURE__ */ import_react11.default.createElement(
15018
- import_react_native10.TouchableOpacity,
15523
+ ), error && /* @__PURE__ */ import_react12.default.createElement(import_react_native11.View, { style: styles8.errorBanner }, /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: styles8.errorText }, error)), /* @__PURE__ */ import_react12.default.createElement(
15524
+ import_react_native11.TouchableOpacity,
15019
15525
  {
15020
15526
  style: [shared.primaryButton, styles8.retestSubmitButton, (!description.trim() || submitting || images.isUploading) && shared.primaryButtonDisabled, { marginTop: 20 }],
15021
15527
  onPress: handleSubmit,
15022
15528
  disabled: !description.trim() || submitting || images.isUploading
15023
15529
  },
15024
- /* @__PURE__ */ import_react11.default.createElement(import_react_native10.Text, { style: shared.primaryButtonText }, images.isUploading ? "Uploading images..." : submitting ? "Submitting..." : error ? "Retry" : "Submit Failed Retest")
15025
- )) : /* @__PURE__ */ import_react11.default.createElement(import_react11.default.Fragment, null, /* @__PURE__ */ import_react11.default.createElement(import_react_native10.Text, { style: shared.label }, "What are you reporting?"), /* @__PURE__ */ import_react11.default.createElement(import_react_native10.View, { style: styles8.typeRow }, [
15530
+ /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: shared.primaryButtonText }, images.isUploading ? "Uploading images..." : submitting ? "Submitting..." : error ? "Retry" : "Submit Failed Retest")
15531
+ )) : /* @__PURE__ */ import_react12.default.createElement(import_react12.default.Fragment, null, /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: shared.label }, "What are you reporting?"), /* @__PURE__ */ import_react12.default.createElement(import_react_native11.View, { style: styles8.typeRow }, [
15026
15532
  { type: "bug", label: "Bug", icon: "\u{1F41B}" },
15027
15533
  { type: "feedback", label: "Feedback", icon: "\u{1F4A1}" },
15028
15534
  { type: "suggestion", label: "Idea", icon: "\u2728" }
15029
- ].map(({ type, label, icon }) => /* @__PURE__ */ import_react11.default.createElement(
15030
- import_react_native10.TouchableOpacity,
15535
+ ].map(({ type, label, icon }) => /* @__PURE__ */ import_react12.default.createElement(
15536
+ import_react_native11.TouchableOpacity,
15031
15537
  {
15032
15538
  key: type,
15033
15539
  style: [styles8.typeCard, reportType === type && styles8.typeCardActive],
15034
15540
  onPress: () => setReportType(type)
15035
15541
  },
15036
- /* @__PURE__ */ import_react11.default.createElement(import_react_native10.Text, { style: styles8.typeIcon }, icon),
15037
- /* @__PURE__ */ import_react11.default.createElement(import_react_native10.Text, { style: [styles8.typeLabel, reportType === type && styles8.typeLabelActive] }, label)
15038
- ))), isBugType && /* @__PURE__ */ import_react11.default.createElement(import_react_native10.View, { style: styles8.section }, /* @__PURE__ */ import_react11.default.createElement(import_react_native10.Text, { style: shared.label }, "Severity"), /* @__PURE__ */ import_react11.default.createElement(import_react_native10.View, { style: styles8.severityRow }, [
15542
+ /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: styles8.typeIcon }, icon),
15543
+ /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: [styles8.typeLabel, reportType === type && styles8.typeLabelActive] }, label)
15544
+ ))), isBugType && /* @__PURE__ */ import_react12.default.createElement(import_react_native11.View, { style: styles8.section }, /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: shared.label }, "Severity"), /* @__PURE__ */ import_react12.default.createElement(import_react_native11.View, { style: styles8.severityRow }, [
15039
15545
  { sev: "critical", color: "#ef4444" },
15040
15546
  { sev: "high", color: "#f97316" },
15041
15547
  { sev: "medium", color: "#eab308" },
15042
15548
  { sev: "low", color: "#6b7280" }
15043
- ].map(({ sev, color }) => /* @__PURE__ */ import_react11.default.createElement(
15044
- import_react_native10.TouchableOpacity,
15549
+ ].map(({ sev, color }) => /* @__PURE__ */ import_react12.default.createElement(
15550
+ import_react_native11.TouchableOpacity,
15045
15551
  {
15046
15552
  key: sev,
15047
15553
  style: [styles8.sevButton, severity === sev && { backgroundColor: `${color}30`, borderColor: color }],
15048
15554
  onPress: () => setSeverity(sev)
15049
15555
  },
15050
- /* @__PURE__ */ import_react11.default.createElement(import_react_native10.Text, { style: [styles8.sevText, severity === sev && { color }] }, sev)
15051
- )))), isBugType && /* @__PURE__ */ import_react11.default.createElement(import_react_native10.View, { style: styles8.section }, /* @__PURE__ */ import_react11.default.createElement(import_react_native10.Text, { style: shared.label }, "Category (optional)"), /* @__PURE__ */ import_react11.default.createElement(CategoryPicker, { value: category, onChange: setCategory, optional: true })), /* @__PURE__ */ import_react11.default.createElement(import_react_native10.View, { style: styles8.section }, /* @__PURE__ */ import_react11.default.createElement(import_react_native10.Text, { style: shared.label }, "What happened?"), /* @__PURE__ */ import_react11.default.createElement(
15052
- import_react_native10.TextInput,
15556
+ /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: [styles8.sevText, severity === sev && { color }] }, sev)
15557
+ )))), isBugType && /* @__PURE__ */ import_react12.default.createElement(import_react_native11.View, { style: styles8.section }, /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: shared.label }, "Category (optional)"), /* @__PURE__ */ import_react12.default.createElement(CategoryPicker, { value: category, onChange: setCategory, optional: true })), /* @__PURE__ */ import_react12.default.createElement(import_react_native11.View, { style: styles8.section }, /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: shared.label }, "What happened?"), /* @__PURE__ */ import_react12.default.createElement(
15558
+ import_react_native11.TextInput,
15053
15559
  {
15054
15560
  style: styles8.descInput,
15055
15561
  value: description,
@@ -15060,8 +15566,8 @@ function ReportScreen({ nav, prefill }) {
15060
15566
  numberOfLines: 4,
15061
15567
  textAlignVertical: "top"
15062
15568
  }
15063
- )), isBugType && /* @__PURE__ */ import_react11.default.createElement(import_react_native10.View, { style: styles8.section }, /* @__PURE__ */ import_react11.default.createElement(import_react_native10.Text, { style: shared.label }, "Which screen?"), /* @__PURE__ */ import_react11.default.createElement(
15064
- import_react_native10.TextInput,
15569
+ )), isBugType && /* @__PURE__ */ import_react12.default.createElement(import_react_native11.View, { style: styles8.section }, /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: shared.label }, "Which screen?"), /* @__PURE__ */ import_react12.default.createElement(
15570
+ import_react_native11.TextInput,
15065
15571
  {
15066
15572
  style: styles8.screenInput,
15067
15573
  value: affectedScreen,
@@ -15069,7 +15575,7 @@ function ReportScreen({ nav, prefill }) {
15069
15575
  placeholder: "e.g. Reservations, Settings...",
15070
15576
  placeholderTextColor: colors.textMuted
15071
15577
  }
15072
- ), /* @__PURE__ */ import_react11.default.createElement(import_react_native10.Text, { style: styles8.screenHint }, "Which screen or area was the bug on? (optional)")), /* @__PURE__ */ import_react11.default.createElement(
15578
+ ), /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: styles8.screenHint }, "Which screen or area was the bug on? (optional)")), /* @__PURE__ */ import_react12.default.createElement(
15073
15579
  ImagePickerButtons,
15074
15580
  {
15075
15581
  images: images.images,
@@ -15079,17 +15585,17 @@ function ReportScreen({ nav, prefill }) {
15079
15585
  onRemove: images.removeImage,
15080
15586
  label: "Screenshots (optional)"
15081
15587
  }
15082
- ), error && /* @__PURE__ */ import_react11.default.createElement(import_react_native10.View, { style: styles8.errorBanner }, /* @__PURE__ */ import_react11.default.createElement(import_react_native10.Text, { style: styles8.errorText }, error)), /* @__PURE__ */ import_react11.default.createElement(
15083
- import_react_native10.TouchableOpacity,
15588
+ ), error && /* @__PURE__ */ import_react12.default.createElement(import_react_native11.View, { style: styles8.errorBanner }, /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: styles8.errorText }, error)), /* @__PURE__ */ import_react12.default.createElement(
15589
+ import_react_native11.TouchableOpacity,
15084
15590
  {
15085
15591
  style: [shared.primaryButton, (!description.trim() || submitting || images.isUploading) && shared.primaryButtonDisabled, { marginTop: 20 }],
15086
15592
  onPress: handleSubmit,
15087
15593
  disabled: !description.trim() || submitting || images.isUploading
15088
15594
  },
15089
- /* @__PURE__ */ import_react11.default.createElement(import_react_native10.Text, { style: shared.primaryButtonText }, images.isUploading ? "Uploading images..." : submitting ? "Submitting..." : error ? "Retry" : "Submit Report")
15595
+ /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: shared.primaryButtonText }, images.isUploading ? "Uploading images..." : submitting ? "Submitting..." : error ? "Retry" : "Submit Report")
15090
15596
  )));
15091
15597
  }
15092
- var styles8 = import_react_native10.StyleSheet.create({
15598
+ var styles8 = import_react_native11.StyleSheet.create({
15093
15599
  typeRow: { flexDirection: "row", gap: 10, marginBottom: 20 },
15094
15600
  typeCard: { flex: 1, alignItems: "center", paddingVertical: 16, borderRadius: 12, backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border },
15095
15601
  typeCardActive: { borderColor: colors.blue, backgroundColor: "#172554" },
@@ -15113,16 +15619,16 @@ var styles8 = import_react_native10.StyleSheet.create({
15113
15619
  });
15114
15620
 
15115
15621
  // src/widget/screens/ReportSuccessScreen.tsx
15116
- var import_react12 = __toESM(require("react"));
15117
- var import_react_native11 = require("react-native");
15622
+ var import_react13 = __toESM(require("react"));
15623
+ var import_react_native12 = require("react-native");
15118
15624
  function ReportSuccessScreen({ nav }) {
15119
- (0, import_react12.useEffect)(() => {
15625
+ (0, import_react13.useEffect)(() => {
15120
15626
  const timer = setTimeout(() => nav.reset(), 2e3);
15121
15627
  return () => clearTimeout(timer);
15122
15628
  }, [nav]);
15123
- return /* @__PURE__ */ import_react12.default.createElement(import_react_native11.View, { style: styles9.container }, /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: styles9.emoji }, "\u{1F389}"), /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: styles9.title }, "Report submitted!"), /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: styles9.subtitle }, "Thank you for your feedback"));
15629
+ return /* @__PURE__ */ import_react13.default.createElement(import_react_native12.View, { style: styles9.container }, /* @__PURE__ */ import_react13.default.createElement(import_react_native12.Text, { style: styles9.emoji }, "\u{1F389}"), /* @__PURE__ */ import_react13.default.createElement(import_react_native12.Text, { style: styles9.title }, "Report submitted!"), /* @__PURE__ */ import_react13.default.createElement(import_react_native12.Text, { style: styles9.subtitle }, "Thank you for your feedback"));
15124
15630
  }
15125
- var styles9 = import_react_native11.StyleSheet.create({
15631
+ var styles9 = import_react_native12.StyleSheet.create({
15126
15632
  container: { alignItems: "center", paddingVertical: 60 },
15127
15633
  emoji: { fontSize: 48, marginBottom: 16 },
15128
15634
  title: { fontSize: 22, fontWeight: "700", color: colors.textPrimary, marginBottom: 6 },
@@ -15130,29 +15636,30 @@ var styles9 = import_react_native11.StyleSheet.create({
15130
15636
  });
15131
15637
 
15132
15638
  // src/widget/screens/MessageListScreen.tsx
15133
- var import_react13 = __toESM(require("react"));
15134
- var import_react_native12 = require("react-native");
15639
+ var import_react14 = __toESM(require("react"));
15640
+ var import_react_native13 = require("react-native");
15135
15641
  function MessageListScreen({ nav }) {
15136
- const { threads, unreadCount, refreshThreads } = useBugBear();
15137
- return /* @__PURE__ */ import_react13.default.createElement(import_react_native12.View, null, /* @__PURE__ */ import_react13.default.createElement(
15138
- import_react_native12.TouchableOpacity,
15642
+ const { threads, unreadCount, refreshThreads, isLoading } = useBugBear();
15643
+ if (isLoading) return /* @__PURE__ */ import_react14.default.createElement(MessageListScreenSkeleton, null);
15644
+ return /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, null, /* @__PURE__ */ import_react14.default.createElement(
15645
+ import_react_native13.TouchableOpacity,
15139
15646
  {
15140
15647
  style: styles10.newMsgButton,
15141
15648
  onPress: () => nav.push({ name: "COMPOSE_MESSAGE" })
15142
15649
  },
15143
- /* @__PURE__ */ import_react13.default.createElement(import_react_native12.Text, { style: styles10.newMsgText }, "\u2709\uFE0F New Message")
15144
- ), threads.length === 0 ? /* @__PURE__ */ import_react13.default.createElement(import_react_native12.View, { style: shared.emptyState }, /* @__PURE__ */ import_react13.default.createElement(import_react_native12.Text, { style: shared.emptyEmoji }, "\u{1F4AC}"), /* @__PURE__ */ import_react13.default.createElement(import_react_native12.Text, { style: shared.emptyTitle }, "No messages yet"), /* @__PURE__ */ import_react13.default.createElement(import_react_native12.Text, { style: shared.emptySubtitle }, "Start a conversation or wait for messages from admins")) : /* @__PURE__ */ import_react13.default.createElement(import_react_native12.View, null, threads.map((thread) => /* @__PURE__ */ import_react13.default.createElement(
15145
- import_react_native12.TouchableOpacity,
15650
+ /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles10.newMsgText }, "\u2709\uFE0F New Message")
15651
+ ), threads.length === 0 ? /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: shared.emptyState }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: shared.emptyEmoji }, "\u{1F4AC}"), /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: shared.emptyTitle }, "No messages yet"), /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: shared.emptySubtitle }, "Start a conversation or wait for messages from admins")) : /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, null, threads.map((thread) => /* @__PURE__ */ import_react14.default.createElement(
15652
+ import_react_native13.TouchableOpacity,
15146
15653
  {
15147
15654
  key: thread.id,
15148
15655
  style: [styles10.threadItem, thread.unreadCount > 0 && styles10.threadItemUnread],
15149
15656
  onPress: () => nav.push({ name: "THREAD_DETAIL", thread })
15150
15657
  },
15151
- /* @__PURE__ */ import_react13.default.createElement(import_react_native12.View, { style: styles10.threadLeft }, /* @__PURE__ */ import_react13.default.createElement(import_react_native12.Text, { style: styles10.threadIcon }, getThreadTypeIcon(thread.threadType)), /* @__PURE__ */ import_react13.default.createElement(import_react_native12.View, { style: styles10.threadInfo }, /* @__PURE__ */ import_react13.default.createElement(import_react_native12.View, { style: styles10.threadTitleRow }, thread.isPinned && /* @__PURE__ */ import_react13.default.createElement(import_react_native12.Text, { style: styles10.pinIcon }, "\u{1F4CC}"), /* @__PURE__ */ import_react13.default.createElement(import_react_native12.Text, { style: styles10.threadSubject, numberOfLines: 1 }, thread.subject || "No subject")), thread.lastMessage && /* @__PURE__ */ import_react13.default.createElement(import_react_native12.Text, { style: styles10.threadPreview, numberOfLines: 1 }, thread.lastMessage.senderName, ": ", thread.lastMessage.content))),
15152
- /* @__PURE__ */ import_react13.default.createElement(import_react_native12.View, { style: styles10.threadRight }, /* @__PURE__ */ import_react13.default.createElement(import_react_native12.Text, { style: styles10.threadTime }, formatRelativeTime(thread.lastMessageAt)), thread.unreadCount > 0 && /* @__PURE__ */ import_react13.default.createElement(import_react_native12.View, { style: styles10.unreadBadge }, /* @__PURE__ */ import_react13.default.createElement(import_react_native12.Text, { style: styles10.unreadText }, thread.unreadCount)), thread.priority !== "normal" && /* @__PURE__ */ import_react13.default.createElement(import_react_native12.View, { style: [styles10.priorityDot, { backgroundColor: getPriorityColor(thread.priority) }] }))
15153
- ))), /* @__PURE__ */ import_react13.default.createElement(import_react_native12.View, { style: styles10.footer }, /* @__PURE__ */ import_react13.default.createElement(import_react_native12.Text, { style: styles10.footerText }, threads.length, " thread", threads.length !== 1 ? "s" : "", " \xB7 ", unreadCount, " unread"), /* @__PURE__ */ import_react13.default.createElement(import_react_native12.TouchableOpacity, { onPress: refreshThreads }, /* @__PURE__ */ import_react13.default.createElement(import_react_native12.Text, { style: styles10.refreshText }, "\u21BB Refresh"))));
15658
+ /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles10.threadLeft }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles10.threadIcon }, getThreadTypeIcon(thread.threadType)), /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles10.threadInfo }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles10.threadTitleRow }, thread.isPinned && /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles10.pinIcon }, "\u{1F4CC}"), /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles10.threadSubject, numberOfLines: 1 }, thread.subject || "No subject")), thread.lastMessage && /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles10.threadPreview, numberOfLines: 1 }, thread.lastMessage.senderName, ": ", thread.lastMessage.content))),
15659
+ /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles10.threadRight }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles10.threadTime }, formatRelativeTime(thread.lastMessageAt)), thread.unreadCount > 0 && /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles10.unreadBadge }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles10.unreadText }, thread.unreadCount)), thread.priority !== "normal" && /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: [styles10.priorityDot, { backgroundColor: getPriorityColor(thread.priority) }] }))
15660
+ ))), /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles10.footer }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles10.footerText }, threads.length, " thread", threads.length !== 1 ? "s" : "", " \xB7 ", unreadCount, " unread"), /* @__PURE__ */ import_react14.default.createElement(import_react_native13.TouchableOpacity, { onPress: refreshThreads }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles10.refreshText }, "\u21BB Refresh"))));
15154
15661
  }
15155
- var styles10 = import_react_native12.StyleSheet.create({
15662
+ var styles10 = import_react_native13.StyleSheet.create({
15156
15663
  newMsgButton: { backgroundColor: colors.blue, paddingVertical: 12, borderRadius: 12, alignItems: "center", marginBottom: 16 },
15157
15664
  newMsgText: { fontSize: 15, fontWeight: "600", color: "#fff" },
15158
15665
  threadItem: { flexDirection: "row", justifyContent: "space-between", paddingVertical: 12, paddingHorizontal: 12, borderRadius: 10, marginBottom: 4, backgroundColor: colors.card },
@@ -15175,26 +15682,39 @@ var styles10 = import_react_native12.StyleSheet.create({
15175
15682
  });
15176
15683
 
15177
15684
  // src/widget/screens/ThreadDetailScreen.tsx
15178
- var import_react14 = __toESM(require("react"));
15179
- var import_react_native13 = require("react-native");
15685
+ var import_react15 = __toESM(require("react"));
15686
+ var import_react_native14 = require("react-native");
15180
15687
  function ThreadDetailScreen({ thread, nav }) {
15181
15688
  const { getThreadMessages, sendMessage, markAsRead, uploadImage } = useBugBear();
15182
- const [messages, setMessages] = (0, import_react14.useState)([]);
15183
- const [loading, setLoading] = (0, import_react14.useState)(true);
15184
- const [replyText, setReplyText] = (0, import_react14.useState)("");
15185
- const [sending, setSending] = (0, import_react14.useState)(false);
15186
- const [sendError, setSendError] = (0, import_react14.useState)(false);
15689
+ const [messages, setMessages] = (0, import_react15.useState)([]);
15690
+ const [loading, setLoading] = (0, import_react15.useState)(true);
15691
+ const [replyText, setReplyText] = (0, import_react15.useState)("");
15692
+ const [sending, setSending] = (0, import_react15.useState)(false);
15693
+ const [sendError, setSendError] = (0, import_react15.useState)(false);
15187
15694
  const replyImages = useImageAttachments(uploadImage, 3, "discussion-attachments");
15188
- (0, import_react14.useEffect)(() => {
15695
+ (0, import_react15.useEffect)(() => {
15696
+ let cancelled = false;
15697
+ setLoading(true);
15189
15698
  (async () => {
15190
- setLoading(true);
15191
- const msgs = await getThreadMessages(thread.id);
15192
- setMessages(msgs);
15193
- setLoading(false);
15194
- if (thread.unreadCount > 0) {
15195
- await markAsRead(thread.id);
15699
+ try {
15700
+ const msgs = await getThreadMessages(thread.id);
15701
+ if (!cancelled) {
15702
+ setMessages(msgs);
15703
+ }
15704
+ if (thread.unreadCount > 0) {
15705
+ await markAsRead(thread.id);
15706
+ }
15707
+ } catch (err) {
15708
+ console.error("BugBear: Failed to load thread messages", err);
15709
+ } finally {
15710
+ if (!cancelled) {
15711
+ setLoading(false);
15712
+ }
15196
15713
  }
15197
15714
  })();
15715
+ return () => {
15716
+ cancelled = true;
15717
+ };
15198
15718
  }, [thread.id]);
15199
15719
  const handleSend = async () => {
15200
15720
  if (!replyText.trim() && replyImages.images.length === 0 || sending || replyImages.isUploading) return;
@@ -15217,18 +15737,18 @@ function ThreadDetailScreen({ thread, nav }) {
15217
15737
  }
15218
15738
  setSending(false);
15219
15739
  };
15220
- return /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles11.container }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles11.header }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles11.headerIcon }, getThreadTypeIcon(thread.threadType)), /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles11.headerSubject, numberOfLines: 2 }, thread.subject || "No subject")), loading ? /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles11.loadingContainer }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles11.loadingText }, "Loading messages...")) : /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles11.messagesContainer }, messages.map((msg) => /* @__PURE__ */ import_react14.default.createElement(
15221
- import_react_native13.View,
15740
+ return /* @__PURE__ */ import_react15.default.createElement(import_react_native14.View, { style: styles11.container }, /* @__PURE__ */ import_react15.default.createElement(import_react_native14.View, { style: styles11.header }, /* @__PURE__ */ import_react15.default.createElement(import_react_native14.Text, { style: styles11.headerIcon }, getThreadTypeIcon(thread.threadType)), /* @__PURE__ */ import_react15.default.createElement(import_react_native14.Text, { style: styles11.headerSubject, numberOfLines: 2 }, thread.subject || "No subject")), loading ? /* @__PURE__ */ import_react15.default.createElement(import_react_native14.View, { style: styles11.loadingContainer }, /* @__PURE__ */ import_react15.default.createElement(import_react_native14.Text, { style: styles11.loadingText }, "Loading messages...")) : /* @__PURE__ */ import_react15.default.createElement(import_react_native14.View, { style: styles11.messagesContainer }, messages.map((msg) => /* @__PURE__ */ import_react15.default.createElement(
15741
+ import_react_native14.View,
15222
15742
  {
15223
15743
  key: msg.id,
15224
15744
  style: [styles11.bubble, msg.senderType === "tester" ? styles11.bubbleTester : styles11.bubbleAdmin]
15225
15745
  },
15226
- /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: [styles11.sender, msg.senderType === "tester" && styles11.senderTester] }, msg.senderType === "tester" ? "You" : msg.senderName),
15227
- /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: [styles11.content, msg.senderType === "tester" && styles11.contentTester] }, msg.content),
15228
- msg.attachments && msg.attachments.length > 0 && /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles11.attachments }, msg.attachments.filter((a) => a.type === "image").map((att, idx) => /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Image, { key: idx, source: { uri: att.url }, style: styles11.attachmentImage, resizeMode: "cover" }))),
15229
- /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: [styles11.time, msg.senderType === "tester" && styles11.timeTester] }, formatMessageTime(msg.createdAt))
15230
- ))), sendError && /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles11.errorBar }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles11.errorText }, "Failed to send. Tap Send to retry.")), replyImages.images.length > 0 && /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles11.replyPreview }, /* @__PURE__ */ import_react14.default.createElement(ImagePreviewStrip, { images: replyImages.images, onRemove: replyImages.removeImage })), /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles11.composer }, IMAGE_PICKER_AVAILABLE && /* @__PURE__ */ import_react14.default.createElement(import_react_native13.TouchableOpacity, { style: styles11.attachBtn, onPress: replyImages.pickFromGallery, disabled: replyImages.images.length >= 3 }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles11.attachBtnText }, "\u{1F4CE}")), /* @__PURE__ */ import_react14.default.createElement(
15231
- import_react_native13.TextInput,
15746
+ /* @__PURE__ */ import_react15.default.createElement(import_react_native14.Text, { style: [styles11.sender, msg.senderType === "tester" && styles11.senderTester] }, msg.senderType === "tester" ? "You" : msg.senderName),
15747
+ /* @__PURE__ */ import_react15.default.createElement(import_react_native14.Text, { style: [styles11.content, msg.senderType === "tester" && styles11.contentTester] }, msg.content),
15748
+ msg.attachments && msg.attachments.length > 0 && /* @__PURE__ */ import_react15.default.createElement(import_react_native14.View, { style: styles11.attachments }, msg.attachments.filter((a) => a.type === "image").map((att, idx) => /* @__PURE__ */ import_react15.default.createElement(import_react_native14.Image, { key: idx, source: { uri: att.url }, style: styles11.attachmentImage, resizeMode: "cover" }))),
15749
+ /* @__PURE__ */ import_react15.default.createElement(import_react_native14.Text, { style: [styles11.time, msg.senderType === "tester" && styles11.timeTester] }, formatMessageTime(msg.createdAt))
15750
+ ))), sendError && /* @__PURE__ */ import_react15.default.createElement(import_react_native14.View, { style: styles11.errorBar }, /* @__PURE__ */ import_react15.default.createElement(import_react_native14.Text, { style: styles11.errorText }, "Failed to send. Tap Send to retry.")), replyImages.images.length > 0 && /* @__PURE__ */ import_react15.default.createElement(import_react_native14.View, { style: styles11.replyPreview }, /* @__PURE__ */ import_react15.default.createElement(ImagePreviewStrip, { images: replyImages.images, onRemove: replyImages.removeImage })), /* @__PURE__ */ import_react15.default.createElement(import_react_native14.View, { style: styles11.composer }, IMAGE_PICKER_AVAILABLE && /* @__PURE__ */ import_react15.default.createElement(import_react_native14.TouchableOpacity, { style: styles11.attachBtn, onPress: replyImages.pickFromGallery, disabled: replyImages.images.length >= 3 }, /* @__PURE__ */ import_react15.default.createElement(import_react_native14.Text, { style: styles11.attachBtnText }, "\u{1F4CE}")), /* @__PURE__ */ import_react15.default.createElement(
15751
+ import_react_native14.TextInput,
15232
15752
  {
15233
15753
  style: styles11.replyInput,
15234
15754
  value: replyText,
@@ -15238,17 +15758,17 @@ function ThreadDetailScreen({ thread, nav }) {
15238
15758
  multiline: true,
15239
15759
  maxLength: 1e3
15240
15760
  }
15241
- ), /* @__PURE__ */ import_react14.default.createElement(
15242
- import_react_native13.TouchableOpacity,
15761
+ ), /* @__PURE__ */ import_react15.default.createElement(
15762
+ import_react_native14.TouchableOpacity,
15243
15763
  {
15244
- style: [styles11.sendBtn, (!replyText.trim() || sending || replyImages.isUploading) && styles11.sendBtnDisabled],
15764
+ style: [styles11.sendBtn, (!replyText.trim() && replyImages.images.length === 0 || sending || replyImages.isUploading) && styles11.sendBtnDisabled],
15245
15765
  onPress: handleSend,
15246
- disabled: !replyText.trim() || sending || replyImages.isUploading
15766
+ disabled: !replyText.trim() && replyImages.images.length === 0 || sending || replyImages.isUploading
15247
15767
  },
15248
- /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles11.sendBtnText }, sending ? "..." : "Send")
15768
+ /* @__PURE__ */ import_react15.default.createElement(import_react_native14.Text, { style: styles11.sendBtnText }, sending ? "..." : "Send")
15249
15769
  )));
15250
15770
  }
15251
- var styles11 = import_react_native13.StyleSheet.create({
15771
+ var styles11 = import_react_native14.StyleSheet.create({
15252
15772
  container: { flex: 1 },
15253
15773
  header: { flexDirection: "row", alignItems: "center", gap: 8, marginBottom: 16, paddingBottom: 12, borderBottomWidth: 1, borderBottomColor: colors.border },
15254
15774
  headerIcon: { fontSize: 20 },
@@ -15280,16 +15800,16 @@ var styles11 = import_react_native13.StyleSheet.create({
15280
15800
  });
15281
15801
 
15282
15802
  // src/widget/screens/ComposeMessageScreen.tsx
15283
- var import_react15 = __toESM(require("react"));
15284
- var import_react_native14 = require("react-native");
15803
+ var import_react16 = __toESM(require("react"));
15804
+ var import_react_native15 = require("react-native");
15285
15805
  function ComposeMessageScreen({ nav }) {
15286
15806
  const { createThread, uploadImage } = useBugBear();
15287
- const [subject, setSubject] = (0, import_react15.useState)("");
15288
- const [message, setMessage] = (0, import_react15.useState)("");
15289
- const [sending, setSending] = (0, import_react15.useState)(false);
15807
+ const [subject, setSubject] = (0, import_react16.useState)("");
15808
+ const [message, setMessage] = (0, import_react16.useState)("");
15809
+ const [sending, setSending] = (0, import_react16.useState)(false);
15290
15810
  const images = useImageAttachments(uploadImage, 3, "discussion-attachments");
15291
15811
  const handleSend = async () => {
15292
- if (!subject.trim() || !message.trim()) return;
15812
+ if (!subject.trim() || !message.trim() || sending || images.isUploading) return;
15293
15813
  setSending(true);
15294
15814
  const attachments = images.getAttachments();
15295
15815
  const result = await createThread({
@@ -15302,8 +15822,8 @@ function ComposeMessageScreen({ nav }) {
15302
15822
  nav.pop();
15303
15823
  }
15304
15824
  };
15305
- return /* @__PURE__ */ import_react15.default.createElement(import_react_native14.View, null, /* @__PURE__ */ import_react15.default.createElement(import_react_native14.View, { style: styles12.header }, /* @__PURE__ */ import_react15.default.createElement(import_react_native14.Text, { style: styles12.title }, "New Message"), /* @__PURE__ */ import_react15.default.createElement(import_react_native14.Text, { style: styles12.subtitle }, "Send a message to the QA team")), /* @__PURE__ */ import_react15.default.createElement(import_react_native14.View, { style: styles12.form }, /* @__PURE__ */ import_react15.default.createElement(import_react_native14.Text, { style: shared.label }, "Subject"), /* @__PURE__ */ import_react15.default.createElement(
15306
- import_react_native14.TextInput,
15825
+ return /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, null, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles12.header }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles12.title }, "New Message"), /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles12.subtitle }, "Send a message to the QA team")), /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles12.form }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: shared.label }, "Subject"), /* @__PURE__ */ import_react16.default.createElement(
15826
+ import_react_native15.TextInput,
15307
15827
  {
15308
15828
  style: styles12.subjectInput,
15309
15829
  value: subject,
@@ -15312,8 +15832,8 @@ function ComposeMessageScreen({ nav }) {
15312
15832
  placeholderTextColor: colors.textMuted,
15313
15833
  maxLength: 100
15314
15834
  }
15315
- ), /* @__PURE__ */ import_react15.default.createElement(import_react_native14.Text, { style: [shared.label, { marginTop: 16 }] }, "Message"), /* @__PURE__ */ import_react15.default.createElement(
15316
- import_react_native14.TextInput,
15835
+ ), /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: [shared.label, { marginTop: 16 }] }, "Message"), /* @__PURE__ */ import_react16.default.createElement(
15836
+ import_react_native15.TextInput,
15317
15837
  {
15318
15838
  style: styles12.messageInput,
15319
15839
  value: message,
@@ -15325,7 +15845,7 @@ function ComposeMessageScreen({ nav }) {
15325
15845
  textAlignVertical: "top",
15326
15846
  maxLength: 2e3
15327
15847
  }
15328
- ), /* @__PURE__ */ import_react15.default.createElement(
15848
+ ), /* @__PURE__ */ import_react16.default.createElement(
15329
15849
  ImagePickerButtons,
15330
15850
  {
15331
15851
  images: images.images,
@@ -15334,17 +15854,17 @@ function ComposeMessageScreen({ nav }) {
15334
15854
  onPickCamera: images.pickFromCamera,
15335
15855
  onRemove: images.removeImage
15336
15856
  }
15337
- ), /* @__PURE__ */ import_react15.default.createElement(
15338
- import_react_native14.TouchableOpacity,
15857
+ ), /* @__PURE__ */ import_react16.default.createElement(
15858
+ import_react_native15.TouchableOpacity,
15339
15859
  {
15340
15860
  style: [shared.primaryButton, (!subject.trim() || !message.trim() || sending || images.isUploading) && shared.primaryButtonDisabled, { marginTop: 20 }],
15341
15861
  onPress: handleSend,
15342
15862
  disabled: !subject.trim() || !message.trim() || sending || images.isUploading
15343
15863
  },
15344
- /* @__PURE__ */ import_react15.default.createElement(import_react_native14.Text, { style: shared.primaryButtonText }, images.isUploading ? "Uploading..." : sending ? "Sending..." : "Send Message")
15864
+ /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: shared.primaryButtonText }, images.isUploading ? "Uploading..." : sending ? "Sending..." : "Send Message")
15345
15865
  )));
15346
15866
  }
15347
- var styles12 = import_react_native14.StyleSheet.create({
15867
+ var styles12 = import_react_native15.StyleSheet.create({
15348
15868
  header: { marginBottom: 20 },
15349
15869
  title: { fontSize: 20, fontWeight: "600", color: colors.textPrimary, marginBottom: 4 },
15350
15870
  subtitle: { fontSize: 14, color: colors.textMuted },
@@ -15354,20 +15874,20 @@ var styles12 = import_react_native14.StyleSheet.create({
15354
15874
  });
15355
15875
 
15356
15876
  // src/widget/screens/ProfileScreen.tsx
15357
- var import_react16 = __toESM(require("react"));
15358
- var import_react_native15 = require("react-native");
15877
+ var import_react17 = __toESM(require("react"));
15878
+ var import_react_native16 = require("react-native");
15359
15879
  function ProfileScreen({ nav }) {
15360
15880
  const { testerInfo, assignments, updateTesterProfile, refreshTesterInfo } = useBugBear();
15361
- const [editing, setEditing] = (0, import_react16.useState)(false);
15362
- const [name, setName] = (0, import_react16.useState)(testerInfo?.name || "");
15363
- const [additionalEmails, setAdditionalEmails] = (0, import_react16.useState)(testerInfo?.additionalEmails || []);
15364
- const [newEmailInput, setNewEmailInput] = (0, import_react16.useState)("");
15365
- const [platforms, setPlatforms] = (0, import_react16.useState)(testerInfo?.platforms || []);
15366
- const [saving, setSaving] = (0, import_react16.useState)(false);
15367
- const [saved, setSaved] = (0, import_react16.useState)(false);
15368
- const [showDetails, setShowDetails] = (0, import_react16.useState)(false);
15881
+ const [editing, setEditing] = (0, import_react17.useState)(false);
15882
+ const [name, setName] = (0, import_react17.useState)(testerInfo?.name || "");
15883
+ const [additionalEmails, setAdditionalEmails] = (0, import_react17.useState)(testerInfo?.additionalEmails || []);
15884
+ const [newEmailInput, setNewEmailInput] = (0, import_react17.useState)("");
15885
+ const [platforms, setPlatforms] = (0, import_react17.useState)(testerInfo?.platforms || []);
15886
+ const [saving, setSaving] = (0, import_react17.useState)(false);
15887
+ const [saved, setSaved] = (0, import_react17.useState)(false);
15888
+ const [showDetails, setShowDetails] = (0, import_react17.useState)(false);
15369
15889
  const completedCount = assignments.filter((a) => a.status === "passed" || a.status === "failed").length;
15370
- (0, import_react16.useEffect)(() => {
15890
+ (0, import_react17.useEffect)(() => {
15371
15891
  if (testerInfo) {
15372
15892
  setName(testerInfo.name);
15373
15893
  setAdditionalEmails(testerInfo.additionalEmails || []);
@@ -15375,6 +15895,7 @@ function ProfileScreen({ nav }) {
15375
15895
  }
15376
15896
  }, [testerInfo]);
15377
15897
  const handleSave = async () => {
15898
+ if (saving) return;
15378
15899
  setSaving(true);
15379
15900
  const updates = {
15380
15901
  name: name.trim(),
@@ -15401,17 +15922,17 @@ function ProfileScreen({ nav }) {
15401
15922
  }
15402
15923
  };
15403
15924
  if (saved) {
15404
- return /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: shared.emptyState }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: shared.emptyEmoji }, "\u2705"), /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: shared.emptyTitle }, "Profile saved!"));
15925
+ return /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, { style: shared.emptyState }, /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: shared.emptyEmoji }, "\u2705"), /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: shared.emptyTitle }, "Profile saved!"));
15405
15926
  }
15406
15927
  if (!testerInfo) {
15407
- return /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: shared.emptyState }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: shared.emptyEmoji }, "\u{1F464}"), /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: shared.emptyTitle }, "No profile found"));
15928
+ return /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, { style: shared.emptyState }, /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: shared.emptyEmoji }, "\u{1F464}"), /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: shared.emptyTitle }, "No profile found"));
15408
15929
  }
15409
15930
  if (editing) {
15410
- return /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, null, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.editHeader }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.editTitle }, "Edit Profile"), /* @__PURE__ */ import_react16.default.createElement(import_react_native15.TouchableOpacity, { onPress: () => {
15931
+ return /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, null, /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, { style: styles13.editHeader }, /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: styles13.editTitle }, "Edit Profile"), /* @__PURE__ */ import_react17.default.createElement(import_react_native16.TouchableOpacity, { onPress: () => {
15411
15932
  setEditing(false);
15412
15933
  setNewEmailInput("");
15413
- } }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.cancelText }, "Cancel"))), /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.field }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: shared.label }, "Name"), /* @__PURE__ */ import_react16.default.createElement(import_react_native15.TextInput, { style: styles13.input, value: name, onChangeText: setName, placeholder: "Your name", placeholderTextColor: colors.textMuted })), /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.field }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: shared.label }, "Primary Email"), /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.emailFixed }, testerInfo.email)), /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.field }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: shared.label }, "Additional Emails"), additionalEmails.map((email) => /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { key: email, style: styles13.emailRow }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.emailText }, email), /* @__PURE__ */ import_react16.default.createElement(import_react_native15.TouchableOpacity, { onPress: () => setAdditionalEmails(additionalEmails.filter((e) => e !== email)) }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.removeEmail }, "\u2715")))), /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.addEmailRow }, /* @__PURE__ */ import_react16.default.createElement(
15414
- import_react_native15.TextInput,
15934
+ } }, /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: styles13.cancelText }, "Cancel"))), /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, { style: styles13.field }, /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: shared.label }, "Name"), /* @__PURE__ */ import_react17.default.createElement(import_react_native16.TextInput, { style: styles13.input, value: name, onChangeText: setName, placeholder: "Your name", placeholderTextColor: colors.textMuted })), /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, { style: styles13.field }, /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: shared.label }, "Primary Email"), /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: styles13.emailFixed }, testerInfo.email)), /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, { style: styles13.field }, /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: shared.label }, "Additional Emails"), additionalEmails.map((email) => /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, { key: email, style: styles13.emailRow }, /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: styles13.emailText }, email), /* @__PURE__ */ import_react17.default.createElement(import_react_native16.TouchableOpacity, { onPress: () => setAdditionalEmails(additionalEmails.filter((e) => e !== email)) }, /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: styles13.removeEmail }, "\u2715")))), /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, { style: styles13.addEmailRow }, /* @__PURE__ */ import_react17.default.createElement(
15935
+ import_react_native16.TextInput,
15415
15936
  {
15416
15937
  style: [styles13.input, { flex: 1, marginRight: 8 }],
15417
15938
  value: newEmailInput,
@@ -15421,26 +15942,26 @@ function ProfileScreen({ nav }) {
15421
15942
  keyboardType: "email-address",
15422
15943
  autoCapitalize: "none"
15423
15944
  }
15424
- ), /* @__PURE__ */ import_react16.default.createElement(import_react_native15.TouchableOpacity, { style: styles13.addButton, onPress: handleAddEmail }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.addButtonText }, "Add")))), /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.field }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: shared.label }, "Testing Platforms"), /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { 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__ */ import_react16.default.createElement(
15425
- import_react_native15.TouchableOpacity,
15945
+ ), /* @__PURE__ */ import_react17.default.createElement(import_react_native16.TouchableOpacity, { style: styles13.addButton, onPress: handleAddEmail }, /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: styles13.addButtonText }, "Add")))), /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, { style: styles13.field }, /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: shared.label }, "Testing Platforms"), /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, { 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__ */ import_react17.default.createElement(
15946
+ import_react_native16.TouchableOpacity,
15426
15947
  {
15427
15948
  key,
15428
15949
  style: [styles13.platformBtn, platforms.includes(key) && styles13.platformBtnActive],
15429
15950
  onPress: () => setPlatforms((prev) => prev.includes(key) ? prev.filter((p) => p !== key) : [...prev, key])
15430
15951
  },
15431
- /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: [styles13.platformText, platforms.includes(key) && styles13.platformTextActive] }, label)
15432
- )))), /* @__PURE__ */ import_react16.default.createElement(import_react_native15.TouchableOpacity, { style: [shared.primaryButton, { marginTop: 20 }], onPress: handleSave, disabled: saving }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: shared.primaryButtonText }, saving ? "Saving..." : "Save Profile")));
15952
+ /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: [styles13.platformText, platforms.includes(key) && styles13.platformTextActive] }, label)
15953
+ )))), /* @__PURE__ */ import_react17.default.createElement(import_react_native16.TouchableOpacity, { style: [shared.primaryButton, { marginTop: 20 }], onPress: handleSave, disabled: saving }, /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: shared.primaryButtonText }, saving ? "Saving..." : "Save Profile")));
15433
15954
  }
15434
- return /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, null, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.profileCard }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.avatar }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.avatarText }, testerInfo.name.charAt(0).toUpperCase())), /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.profileName }, testerInfo.name), /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.profileEmail }, testerInfo.email)), /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.statsRow }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.statItem }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.statNumber }, completedCount), /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.statLabel }, "Completed")), /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.statDivider }), /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.statItem }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.statNumber }, assignments.length), /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.statLabel }, "Total Assigned"))), /* @__PURE__ */ import_react16.default.createElement(import_react_native15.TouchableOpacity, { onPress: () => setShowDetails(!showDetails), style: styles13.detailsToggle }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.detailsToggleText }, showDetails ? "\u25BC" : "\u25B6", " Details")), showDetails && /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.detailsSection }, additionalEmails.length > 0 && /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.detailBlock }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.detailLabel }, "Additional Emails"), additionalEmails.map((e) => /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { key: e, style: styles13.detailValue }, e))), platforms.length > 0 && /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.detailBlock }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.detailLabel }, "Platforms"), /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.platformTags }, platforms.map((p) => /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { key: p, style: styles13.platformTag }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.platformTagText }, p === "ios" ? "\u{1F4F1} iOS" : p === "android" ? "\u{1F916} Android" : "\u{1F310} Web")))))), /* @__PURE__ */ import_react16.default.createElement(
15435
- import_react_native15.TouchableOpacity,
15955
+ return /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, null, /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, { style: styles13.profileCard }, /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, { style: styles13.avatar }, /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: styles13.avatarText }, testerInfo.name.charAt(0).toUpperCase())), /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: styles13.profileName }, testerInfo.name), /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: styles13.profileEmail }, testerInfo.email)), /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, { style: styles13.statsRow }, /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, { style: styles13.statItem }, /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: styles13.statNumber }, completedCount), /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: styles13.statLabel }, "Completed")), /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, { style: styles13.statDivider }), /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, { style: styles13.statItem }, /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: styles13.statNumber }, assignments.length), /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: styles13.statLabel }, "Total Assigned"))), /* @__PURE__ */ import_react17.default.createElement(import_react_native16.TouchableOpacity, { onPress: () => setShowDetails(!showDetails), style: styles13.detailsToggle }, /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: styles13.detailsToggleText }, showDetails ? "\u25BC" : "\u25B6", " Details")), showDetails && /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, { style: styles13.detailsSection }, additionalEmails.length > 0 && /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, { style: styles13.detailBlock }, /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: styles13.detailLabel }, "Additional Emails"), additionalEmails.map((e) => /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { key: e, style: styles13.detailValue }, e))), platforms.length > 0 && /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, { style: styles13.detailBlock }, /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: styles13.detailLabel }, "Platforms"), /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, { style: styles13.platformTags }, platforms.map((p) => /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, { key: p, style: styles13.platformTag }, /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: styles13.platformTagText }, p === "ios" ? "\u{1F4F1} iOS" : p === "android" ? "\u{1F916} Android" : "\u{1F310} Web")))))), /* @__PURE__ */ import_react17.default.createElement(
15956
+ import_react_native16.TouchableOpacity,
15436
15957
  {
15437
15958
  style: [shared.primaryButton, { marginTop: 20 }],
15438
15959
  onPress: () => setEditing(true)
15439
15960
  },
15440
- /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: shared.primaryButtonText }, "Edit Profile")
15961
+ /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: shared.primaryButtonText }, "Edit Profile")
15441
15962
  ));
15442
15963
  }
15443
- var styles13 = import_react_native15.StyleSheet.create({
15964
+ var styles13 = import_react_native16.StyleSheet.create({
15444
15965
  profileCard: { alignItems: "center", backgroundColor: colors.card, borderRadius: 16, padding: 24, marginBottom: 16 },
15445
15966
  avatar: { width: 64, height: 64, borderRadius: 32, backgroundColor: colors.blue, justifyContent: "center", alignItems: "center", marginBottom: 12 },
15446
15967
  avatarText: { fontSize: 28, fontWeight: "700", color: "#fff" },
@@ -15481,8 +16002,8 @@ var styles13 = import_react_native15.StyleSheet.create({
15481
16002
  });
15482
16003
 
15483
16004
  // src/widget/screens/IssueListScreen.tsx
15484
- var import_react17 = __toESM(require("react"));
15485
- var import_react_native16 = require("react-native");
16005
+ var import_react18 = __toESM(require("react"));
16006
+ var import_react_native17 = require("react-native");
15486
16007
  var CATEGORY_CONFIG = {
15487
16008
  open: { label: "Open Issues", accent: "#f97316", emptyIcon: "\u2705", emptyText: "No open issues" },
15488
16009
  done: { label: "Done", accent: "#22c55e", emptyIcon: "\u{1F389}", emptyText: "No completed issues yet" },
@@ -15496,18 +16017,28 @@ var SEVERITY_COLORS = {
15496
16017
  };
15497
16018
  function IssueListScreen({ nav, category }) {
15498
16019
  const { client } = useBugBear();
15499
- const [issues, setIssues] = (0, import_react17.useState)([]);
15500
- const [loading, setLoading] = (0, import_react17.useState)(true);
16020
+ const [issues, setIssues] = (0, import_react18.useState)([]);
16021
+ const [loading, setLoading] = (0, import_react18.useState)(true);
15501
16022
  const config = CATEGORY_CONFIG[category];
15502
- (0, import_react17.useEffect)(() => {
16023
+ (0, import_react18.useEffect)(() => {
15503
16024
  let cancelled = false;
15504
16025
  setLoading(true);
15505
16026
  (async () => {
15506
- if (!client) return;
15507
- const data = await client.getIssues(category);
15508
- if (!cancelled) {
15509
- setIssues(data);
16027
+ if (!client) {
15510
16028
  setLoading(false);
16029
+ return;
16030
+ }
16031
+ try {
16032
+ const data = await client.getIssues(category);
16033
+ if (!cancelled) {
16034
+ setIssues(data);
16035
+ }
16036
+ } catch (err) {
16037
+ console.error("BugBear: Failed to load issues", err);
16038
+ } finally {
16039
+ if (!cancelled) {
16040
+ setLoading(false);
16041
+ }
15511
16042
  }
15512
16043
  })();
15513
16044
  return () => {
@@ -15515,26 +16046,26 @@ function IssueListScreen({ nav, category }) {
15515
16046
  };
15516
16047
  }, [client, category]);
15517
16048
  if (loading) {
15518
- return /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, { style: styles14.emptyContainer }, /* @__PURE__ */ import_react17.default.createElement(import_react_native16.ActivityIndicator, { size: "small", color: colors.textMuted }), /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: styles14.emptyText }, "Loading..."));
16049
+ return /* @__PURE__ */ import_react18.default.createElement(IssueListScreenSkeleton, null);
15519
16050
  }
15520
16051
  if (issues.length === 0) {
15521
- return /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, { style: styles14.emptyContainer }, /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: styles14.emptyIcon }, config.emptyIcon), /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: styles14.emptyText }, config.emptyText));
16052
+ return /* @__PURE__ */ import_react18.default.createElement(import_react_native17.View, { style: styles14.emptyContainer }, /* @__PURE__ */ import_react18.default.createElement(import_react_native17.Text, { style: styles14.emptyIcon }, config.emptyIcon), /* @__PURE__ */ import_react18.default.createElement(import_react_native17.Text, { style: styles14.emptyText }, config.emptyText));
15522
16053
  }
15523
- return /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, null, issues.map((issue) => /* @__PURE__ */ import_react17.default.createElement(
15524
- import_react_native16.TouchableOpacity,
16054
+ return /* @__PURE__ */ import_react18.default.createElement(import_react_native17.View, null, issues.map((issue) => /* @__PURE__ */ import_react18.default.createElement(
16055
+ import_react_native17.TouchableOpacity,
15525
16056
  {
15526
16057
  key: issue.id,
15527
16058
  style: styles14.issueCard,
15528
16059
  onPress: () => nav.push({ name: "ISSUE_DETAIL", issue }),
15529
16060
  activeOpacity: 0.7
15530
16061
  },
15531
- /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, { style: styles14.topRow }, issue.severity && /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, { style: [styles14.severityDot, { backgroundColor: SEVERITY_COLORS[issue.severity] || colors.textDim }] }), /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: styles14.issueTitle, numberOfLines: 1 }, issue.title)),
15532
- /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, { style: styles14.bottomRow }, issue.route && /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: styles14.routeText, numberOfLines: 1 }, issue.route), /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: styles14.timeText }, formatRelativeTime(issue.updatedAt))),
15533
- category === "done" && issue.verifiedByName && /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, { style: styles14.verifiedBadge }, /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: styles14.verifiedBadgeText }, "\u2714", " Verified by ", issue.verifiedByName)),
15534
- category === "reopened" && issue.originalBugTitle && /* @__PURE__ */ import_react17.default.createElement(import_react_native16.View, { style: styles14.reopenedBadge }, /* @__PURE__ */ import_react17.default.createElement(import_react_native16.Text, { style: styles14.reopenedBadgeText, numberOfLines: 1 }, "\u{1F504}", " Retest of: ", issue.originalBugTitle))
16062
+ /* @__PURE__ */ import_react18.default.createElement(import_react_native17.View, { style: styles14.topRow }, issue.severity && /* @__PURE__ */ import_react18.default.createElement(import_react_native17.View, { style: [styles14.severityDot, { backgroundColor: SEVERITY_COLORS[issue.severity] || colors.textDim }] }), /* @__PURE__ */ import_react18.default.createElement(import_react_native17.Text, { style: styles14.issueTitle, numberOfLines: 1 }, issue.title)),
16063
+ /* @__PURE__ */ import_react18.default.createElement(import_react_native17.View, { style: styles14.bottomRow }, issue.route && /* @__PURE__ */ import_react18.default.createElement(import_react_native17.Text, { style: styles14.routeText, numberOfLines: 1 }, issue.route), /* @__PURE__ */ import_react18.default.createElement(import_react_native17.Text, { style: styles14.timeText }, formatRelativeTime(issue.updatedAt))),
16064
+ category === "done" && issue.verifiedByName && /* @__PURE__ */ import_react18.default.createElement(import_react_native17.View, { style: styles14.verifiedBadge }, /* @__PURE__ */ import_react18.default.createElement(import_react_native17.Text, { style: styles14.verifiedBadgeText }, "\u2714", " Verified by ", issue.verifiedByName)),
16065
+ category === "reopened" && issue.originalBugTitle && /* @__PURE__ */ import_react18.default.createElement(import_react_native17.View, { style: styles14.reopenedBadge }, /* @__PURE__ */ import_react18.default.createElement(import_react_native17.Text, { style: styles14.reopenedBadgeText, numberOfLines: 1 }, "\u{1F504}", " Retest of: ", issue.originalBugTitle))
15535
16066
  )));
15536
16067
  }
15537
- var styles14 = import_react_native16.StyleSheet.create({
16068
+ var styles14 = import_react_native17.StyleSheet.create({
15538
16069
  emptyContainer: {
15539
16070
  alignItems: "center",
15540
16071
  paddingVertical: 40
@@ -15625,8 +16156,8 @@ var styles14 = import_react_native16.StyleSheet.create({
15625
16156
  });
15626
16157
 
15627
16158
  // src/widget/screens/IssueDetailScreen.tsx
15628
- var import_react18 = __toESM(require("react"));
15629
- var import_react_native17 = require("react-native");
16159
+ var import_react19 = __toESM(require("react"));
16160
+ var import_react_native18 = require("react-native");
15630
16161
  var STATUS_LABELS = {
15631
16162
  new: { label: "New", bg: "#1e3a5f", color: "#60a5fa" },
15632
16163
  triaging: { label: "Triaging", bg: "#1e3a5f", color: "#60a5fa" },
@@ -15650,9 +16181,9 @@ var SEVERITY_CONFIG = {
15650
16181
  function IssueDetailScreen({ nav, issue }) {
15651
16182
  const statusConfig = STATUS_LABELS[issue.status] || { label: issue.status, bg: "#27272a", color: "#a1a1aa" };
15652
16183
  const severityConfig = issue.severity ? SEVERITY_CONFIG[issue.severity] : null;
15653
- return /* @__PURE__ */ import_react18.default.createElement(import_react_native17.View, null, /* @__PURE__ */ import_react18.default.createElement(import_react_native17.View, { style: styles15.badgeRow }, /* @__PURE__ */ import_react18.default.createElement(import_react_native17.View, { style: [styles15.badge, { backgroundColor: statusConfig.bg }] }, /* @__PURE__ */ import_react18.default.createElement(import_react_native17.Text, { style: [styles15.badgeText, { color: statusConfig.color }] }, statusConfig.label)), severityConfig && /* @__PURE__ */ import_react18.default.createElement(import_react_native17.View, { style: [styles15.badge, { backgroundColor: severityConfig.bg }] }, /* @__PURE__ */ import_react18.default.createElement(import_react_native17.Text, { style: [styles15.badgeText, { color: severityConfig.color }] }, severityConfig.label))), /* @__PURE__ */ import_react18.default.createElement(import_react_native17.Text, { style: styles15.title }, issue.title), issue.route && /* @__PURE__ */ import_react18.default.createElement(import_react_native17.Text, { style: styles15.route }, issue.route), issue.description && /* @__PURE__ */ import_react18.default.createElement(import_react_native17.View, { style: styles15.descriptionCard }, /* @__PURE__ */ import_react18.default.createElement(import_react_native17.Text, { style: styles15.descriptionText }, issue.description)), issue.verifiedByName && /* @__PURE__ */ import_react18.default.createElement(import_react_native17.View, { style: styles15.verifiedCard }, /* @__PURE__ */ import_react18.default.createElement(import_react_native17.View, { style: styles15.verifiedHeader }, /* @__PURE__ */ import_react18.default.createElement(import_react_native17.Text, { style: styles15.verifiedIcon }, "\u2705"), /* @__PURE__ */ import_react18.default.createElement(import_react_native17.Text, { style: styles15.verifiedTitle }, "Retesting Proof")), /* @__PURE__ */ import_react18.default.createElement(import_react_native17.Text, { style: styles15.verifiedBody }, "Verified by ", issue.verifiedByName, issue.verifiedAt && ` on ${new Date(issue.verifiedAt).toLocaleDateString(void 0, { month: "short", day: "numeric", year: "numeric" })}`)), issue.originalBugTitle && /* @__PURE__ */ import_react18.default.createElement(import_react_native17.View, { style: styles15.originalBugCard }, /* @__PURE__ */ import_react18.default.createElement(import_react_native17.View, { style: styles15.originalBugHeader }, /* @__PURE__ */ import_react18.default.createElement(import_react_native17.Text, { style: styles15.originalBugIcon }, "\u{1F504}"), /* @__PURE__ */ import_react18.default.createElement(import_react_native17.Text, { style: styles15.originalBugTitle }, "Original Bug")), /* @__PURE__ */ import_react18.default.createElement(import_react_native17.Text, { style: styles15.originalBugBody }, "Retest of: ", issue.originalBugTitle)), issue.screenshotUrls && issue.screenshotUrls.length > 0 && /* @__PURE__ */ import_react18.default.createElement(import_react_native17.View, { style: styles15.screenshotSection }, /* @__PURE__ */ import_react18.default.createElement(import_react_native17.Text, { style: styles15.screenshotLabel }, "Screenshots (", issue.screenshotUrls.length, ")"), /* @__PURE__ */ import_react18.default.createElement(import_react_native17.View, { style: styles15.screenshotRow }, issue.screenshotUrls.map((url, i) => /* @__PURE__ */ import_react18.default.createElement(import_react_native17.TouchableOpacity, { key: i, onPress: () => import_react_native17.Linking.openURL(url), activeOpacity: 0.7 }, /* @__PURE__ */ import_react18.default.createElement(import_react_native17.Image, { source: { uri: url }, style: styles15.screenshotThumb }))))), /* @__PURE__ */ import_react18.default.createElement(import_react_native17.View, { style: styles15.metaSection }, issue.reporterName && /* @__PURE__ */ import_react18.default.createElement(import_react_native17.Text, { style: styles15.metaText }, "Reported by ", issue.reporterName), /* @__PURE__ */ import_react18.default.createElement(import_react_native17.Text, { style: styles15.metaTextSmall }, "Created ", formatRelativeTime(issue.createdAt), " ", "\xB7", " Updated ", formatRelativeTime(issue.updatedAt))));
16184
+ return /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, null, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles15.badgeRow }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: [styles15.badge, { backgroundColor: statusConfig.bg }] }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: [styles15.badgeText, { color: statusConfig.color }] }, statusConfig.label)), severityConfig && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: [styles15.badge, { backgroundColor: severityConfig.bg }] }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: [styles15.badgeText, { color: severityConfig.color }] }, severityConfig.label))), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.title }, issue.title), issue.route && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.route }, issue.route), issue.description && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles15.descriptionCard }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.descriptionText }, issue.description)), issue.verifiedByName && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles15.verifiedCard }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles15.verifiedHeader }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.verifiedIcon }, "\u2705"), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.verifiedTitle }, "Retesting Proof")), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.verifiedBody }, "Verified by ", issue.verifiedByName, issue.verifiedAt && ` on ${new Date(issue.verifiedAt).toLocaleDateString(void 0, { month: "short", day: "numeric", year: "numeric" })}`)), issue.originalBugTitle && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles15.originalBugCard }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles15.originalBugHeader }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.originalBugIcon }, "\u{1F504}"), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.originalBugTitle }, "Original Bug")), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.originalBugBody }, "Retest of: ", issue.originalBugTitle)), issue.screenshotUrls && issue.screenshotUrls.length > 0 && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles15.screenshotSection }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.screenshotLabel }, "Screenshots (", issue.screenshotUrls.length, ")"), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles15.screenshotRow }, issue.screenshotUrls.map((url, i) => /* @__PURE__ */ import_react19.default.createElement(import_react_native18.TouchableOpacity, { key: i, onPress: () => import_react_native18.Linking.openURL(url), activeOpacity: 0.7 }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Image, { source: { uri: url }, style: styles15.screenshotThumb }))))), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles15.metaSection }, issue.reporterName && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.metaText }, "Reported by ", issue.reporterName), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles15.metaTextSmall }, "Created ", formatRelativeTime(issue.createdAt), " ", "\xB7", " Updated ", formatRelativeTime(issue.updatedAt))));
15654
16185
  }
15655
- var styles15 = import_react_native17.StyleSheet.create({
16186
+ var styles15 = import_react_native18.StyleSheet.create({
15656
16187
  badgeRow: {
15657
16188
  flexDirection: "row",
15658
16189
  gap: 8,
@@ -15783,8 +16314,8 @@ var styles15 = import_react_native17.StyleSheet.create({
15783
16314
  });
15784
16315
 
15785
16316
  // src/BugBearButton.tsx
15786
- var screenWidth = import_react_native18.Dimensions.get("window").width;
15787
- var screenHeight = import_react_native18.Dimensions.get("window").height;
16317
+ var screenWidth = import_react_native19.Dimensions.get("window").width;
16318
+ var screenHeight = import_react_native19.Dimensions.get("window").height;
15788
16319
  function BugBearButton({
15789
16320
  position = "bottom-right",
15790
16321
  buttonStyle,
@@ -15796,7 +16327,7 @@ function BugBearButton({
15796
16327
  }) {
15797
16328
  const { shouldShowWidget, testerInfo, isLoading, unreadCount, assignments } = useBugBear();
15798
16329
  const { currentScreen, canGoBack, push, pop, replace, reset } = useNavigation();
15799
- const [modalVisible, setModalVisible] = (0, import_react19.useState)(false);
16330
+ const [modalVisible, setModalVisible] = (0, import_react20.useState)(false);
15800
16331
  const getInitialPosition = () => {
15801
16332
  const buttonSize = 56;
15802
16333
  const margin = 16;
@@ -15808,10 +16339,10 @@ function BugBearButton({
15808
16339
  return { x, y };
15809
16340
  };
15810
16341
  const initialPos = getInitialPosition();
15811
- const pan = (0, import_react19.useRef)(new import_react_native18.Animated.ValueXY(initialPos)).current;
15812
- const isDragging = (0, import_react19.useRef)(false);
15813
- const panResponder = (0, import_react19.useRef)(
15814
- import_react_native18.PanResponder.create({
16342
+ const pan = (0, import_react20.useRef)(new import_react_native19.Animated.ValueXY(initialPos)).current;
16343
+ const isDragging = (0, import_react20.useRef)(false);
16344
+ const panResponder = (0, import_react20.useRef)(
16345
+ import_react_native19.PanResponder.create({
15815
16346
  onStartShouldSetPanResponder: () => draggable,
15816
16347
  onMoveShouldSetPanResponder: (_, gs) => draggable && (Math.abs(gs.dx) > 5 || Math.abs(gs.dy) > 5),
15817
16348
  onPanResponderGrant: () => {
@@ -15826,7 +16357,7 @@ function BugBearButton({
15826
16357
  if (Math.abs(gs.dx) > 5 || Math.abs(gs.dy) > 5) {
15827
16358
  isDragging.current = true;
15828
16359
  }
15829
- import_react_native18.Animated.event(
16360
+ import_react_native19.Animated.event(
15830
16361
  [null, { dx: pan.x, dy: pan.y }],
15831
16362
  { useNativeDriver: false }
15832
16363
  )(_, gs);
@@ -15839,7 +16370,7 @@ function BugBearButton({
15839
16370
  const margin = 16;
15840
16371
  const snapX = currentX < screenWidth / 2 ? margin : screenWidth - buttonSize - margin;
15841
16372
  const snapY = Math.max(minY, Math.min(currentY, screenHeight - maxYOffset));
15842
- import_react_native18.Animated.spring(pan, {
16373
+ import_react_native19.Animated.spring(pan, {
15843
16374
  toValue: { x: snapX, y: snapY },
15844
16375
  useNativeDriver: false,
15845
16376
  friction: 7,
@@ -15886,24 +16417,24 @@ function BugBearButton({
15886
16417
  }
15887
16418
  };
15888
16419
  const handleClose = () => {
15889
- import_react_native18.Keyboard.dismiss();
16420
+ import_react_native19.Keyboard.dismiss();
15890
16421
  setModalVisible(false);
15891
16422
  };
15892
16423
  const nav = {
15893
16424
  push: (screen) => {
15894
- import_react_native18.Keyboard.dismiss();
16425
+ import_react_native19.Keyboard.dismiss();
15895
16426
  push(screen);
15896
16427
  },
15897
16428
  pop: () => {
15898
- import_react_native18.Keyboard.dismiss();
16429
+ import_react_native19.Keyboard.dismiss();
15899
16430
  pop();
15900
16431
  },
15901
16432
  replace: (screen) => {
15902
- import_react_native18.Keyboard.dismiss();
16433
+ import_react_native19.Keyboard.dismiss();
15903
16434
  replace(screen);
15904
16435
  },
15905
16436
  reset: () => {
15906
- import_react_native18.Keyboard.dismiss();
16437
+ import_react_native19.Keyboard.dismiss();
15907
16438
  reset();
15908
16439
  },
15909
16440
  canGoBack,
@@ -15912,77 +16443,77 @@ function BugBearButton({
15912
16443
  const renderScreen = () => {
15913
16444
  switch (currentScreen.name) {
15914
16445
  case "HOME":
15915
- return /* @__PURE__ */ import_react19.default.createElement(HomeScreen, { nav });
16446
+ return /* @__PURE__ */ import_react20.default.createElement(HomeScreen, { nav });
15916
16447
  case "TEST_DETAIL":
15917
- return /* @__PURE__ */ import_react19.default.createElement(TestDetailScreen, { testId: currentScreen.testId, nav });
16448
+ return /* @__PURE__ */ import_react20.default.createElement(TestDetailScreen, { testId: currentScreen.testId, nav });
15918
16449
  case "TEST_LIST":
15919
- return /* @__PURE__ */ import_react19.default.createElement(TestListScreen, { nav });
16450
+ return /* @__PURE__ */ import_react20.default.createElement(TestListScreen, { nav });
15920
16451
  case "TEST_FEEDBACK":
15921
- return /* @__PURE__ */ import_react19.default.createElement(TestFeedbackScreen, { status: currentScreen.status, assignmentId: currentScreen.assignmentId, nav });
16452
+ return /* @__PURE__ */ import_react20.default.createElement(TestFeedbackScreen, { status: currentScreen.status, assignmentId: currentScreen.assignmentId, nav });
15922
16453
  case "REPORT":
15923
- return /* @__PURE__ */ import_react19.default.createElement(ReportScreen, { nav, prefill: currentScreen.prefill });
16454
+ return /* @__PURE__ */ import_react20.default.createElement(ReportScreen, { nav, prefill: currentScreen.prefill });
15924
16455
  case "REPORT_SUCCESS":
15925
- return /* @__PURE__ */ import_react19.default.createElement(ReportSuccessScreen, { nav });
16456
+ return /* @__PURE__ */ import_react20.default.createElement(ReportSuccessScreen, { nav });
15926
16457
  case "MESSAGE_LIST":
15927
- return /* @__PURE__ */ import_react19.default.createElement(MessageListScreen, { nav });
16458
+ return /* @__PURE__ */ import_react20.default.createElement(MessageListScreen, { nav });
15928
16459
  case "THREAD_DETAIL":
15929
- return /* @__PURE__ */ import_react19.default.createElement(ThreadDetailScreen, { thread: currentScreen.thread, nav });
16460
+ return /* @__PURE__ */ import_react20.default.createElement(ThreadDetailScreen, { thread: currentScreen.thread, nav });
15930
16461
  case "COMPOSE_MESSAGE":
15931
- return /* @__PURE__ */ import_react19.default.createElement(ComposeMessageScreen, { nav });
16462
+ return /* @__PURE__ */ import_react20.default.createElement(ComposeMessageScreen, { nav });
15932
16463
  case "ISSUE_LIST":
15933
- return /* @__PURE__ */ import_react19.default.createElement(IssueListScreen, { nav, category: currentScreen.category });
16464
+ return /* @__PURE__ */ import_react20.default.createElement(IssueListScreen, { nav, category: currentScreen.category });
15934
16465
  case "ISSUE_DETAIL":
15935
- return /* @__PURE__ */ import_react19.default.createElement(IssueDetailScreen, { nav, issue: currentScreen.issue });
16466
+ return /* @__PURE__ */ import_react20.default.createElement(IssueDetailScreen, { nav, issue: currentScreen.issue });
15936
16467
  case "PROFILE":
15937
- return /* @__PURE__ */ import_react19.default.createElement(ProfileScreen, { nav });
16468
+ return /* @__PURE__ */ import_react20.default.createElement(ProfileScreen, { nav });
15938
16469
  default:
15939
- return /* @__PURE__ */ import_react19.default.createElement(HomeScreen, { nav });
16470
+ return /* @__PURE__ */ import_react20.default.createElement(HomeScreen, { nav });
15940
16471
  }
15941
16472
  };
15942
- return /* @__PURE__ */ import_react19.default.createElement(import_react19.default.Fragment, null, /* @__PURE__ */ import_react19.default.createElement(
15943
- import_react_native18.Animated.View,
16473
+ return /* @__PURE__ */ import_react20.default.createElement(import_react20.default.Fragment, null, /* @__PURE__ */ import_react20.default.createElement(
16474
+ import_react_native19.Animated.View,
15944
16475
  {
15945
16476
  style: [styles16.fabContainer, { transform: pan.getTranslateTransform() }, buttonStyle],
15946
16477
  ...panResponder.panHandlers
15947
16478
  },
15948
- /* @__PURE__ */ import_react19.default.createElement(
15949
- import_react_native18.TouchableOpacity,
16479
+ /* @__PURE__ */ import_react20.default.createElement(
16480
+ import_react_native19.TouchableOpacity,
15950
16481
  {
15951
16482
  style: styles16.fab,
15952
16483
  onPress: () => setModalVisible(true),
15953
16484
  activeOpacity: draggable ? 1 : 0.7
15954
16485
  },
15955
- /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Image, { source: { uri: BUGBEAR_LOGO_BASE64 }, style: styles16.fabIcon }),
15956
- badgeCount > 0 && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles16.badge }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles16.badgeText }, badgeCount > 9 ? "9+" : badgeCount))
16486
+ /* @__PURE__ */ import_react20.default.createElement(import_react_native19.Image, { source: { uri: BUGBEAR_LOGO_BASE64 }, style: styles16.fabIcon }),
16487
+ badgeCount > 0 && /* @__PURE__ */ import_react20.default.createElement(import_react_native19.View, { style: styles16.badge }, /* @__PURE__ */ import_react20.default.createElement(import_react_native19.Text, { style: styles16.badgeText }, badgeCount > 9 ? "9+" : badgeCount))
15957
16488
  )
15958
- ), /* @__PURE__ */ import_react19.default.createElement(
15959
- import_react_native18.Modal,
16489
+ ), /* @__PURE__ */ import_react20.default.createElement(
16490
+ import_react_native19.Modal,
15960
16491
  {
15961
16492
  visible: modalVisible,
15962
16493
  animationType: "slide",
15963
16494
  transparent: true,
15964
16495
  onRequestClose: handleClose
15965
16496
  },
15966
- /* @__PURE__ */ import_react19.default.createElement(
15967
- import_react_native18.KeyboardAvoidingView,
16497
+ /* @__PURE__ */ import_react20.default.createElement(
16498
+ import_react_native19.KeyboardAvoidingView,
15968
16499
  {
15969
- behavior: import_react_native18.Platform.OS === "ios" ? "padding" : "height",
16500
+ behavior: import_react_native19.Platform.OS === "ios" ? "padding" : "height",
15970
16501
  style: styles16.modalOverlay
15971
16502
  },
15972
- /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles16.modalContainer }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles16.header }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles16.headerLeft }, canGoBack ? /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles16.headerNavRow }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.TouchableOpacity, { onPress: () => nav.pop(), style: styles16.backButton }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles16.backText }, "\u2190 Back")), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.TouchableOpacity, { onPress: () => nav.reset(), style: styles16.homeButton }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles16.homeText }, "\u{1F3E0}"))) : /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles16.headerTitleRow }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles16.headerTitle }, "BugBear"), testerInfo && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.TouchableOpacity, { onPress: () => push({ name: "PROFILE" }) }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles16.headerName }, testerInfo.name, " \u270E")))), getHeaderTitle() ? /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles16.headerScreenTitle, numberOfLines: 1 }, getHeaderTitle()) : null, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.TouchableOpacity, { onPress: handleClose, style: styles16.closeButton }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles16.closeText }, "\u2715"))), /* @__PURE__ */ import_react19.default.createElement(
15973
- import_react_native18.ScrollView,
16503
+ /* @__PURE__ */ import_react20.default.createElement(import_react_native19.View, { style: styles16.modalContainer }, /* @__PURE__ */ import_react20.default.createElement(import_react_native19.View, { style: styles16.header }, /* @__PURE__ */ import_react20.default.createElement(import_react_native19.View, { style: styles16.headerLeft }, canGoBack ? /* @__PURE__ */ import_react20.default.createElement(import_react_native19.View, { style: styles16.headerNavRow }, /* @__PURE__ */ import_react20.default.createElement(import_react_native19.TouchableOpacity, { onPress: () => nav.pop(), style: styles16.backButton }, /* @__PURE__ */ import_react20.default.createElement(import_react_native19.Text, { style: styles16.backText }, "\u2190 Back")), /* @__PURE__ */ import_react20.default.createElement(import_react_native19.TouchableOpacity, { onPress: () => nav.reset(), style: styles16.homeButton }, /* @__PURE__ */ import_react20.default.createElement(import_react_native19.Text, { style: styles16.homeText }, "\u{1F3E0}"))) : /* @__PURE__ */ import_react20.default.createElement(import_react_native19.View, { style: styles16.headerTitleRow }, /* @__PURE__ */ import_react20.default.createElement(import_react_native19.Text, { style: styles16.headerTitle }, "BugBear"), testerInfo && /* @__PURE__ */ import_react20.default.createElement(import_react_native19.TouchableOpacity, { onPress: () => push({ name: "PROFILE" }) }, /* @__PURE__ */ import_react20.default.createElement(import_react_native19.Text, { style: styles16.headerName }, testerInfo.name, " \u270E")))), getHeaderTitle() ? /* @__PURE__ */ import_react20.default.createElement(import_react_native19.Text, { style: styles16.headerScreenTitle, numberOfLines: 1 }, getHeaderTitle()) : null, /* @__PURE__ */ import_react20.default.createElement(import_react_native19.TouchableOpacity, { onPress: handleClose, style: styles16.closeButton }, /* @__PURE__ */ import_react20.default.createElement(import_react_native19.Text, { style: styles16.closeText }, "\u2715"))), /* @__PURE__ */ import_react20.default.createElement(
16504
+ import_react_native19.ScrollView,
15974
16505
  {
15975
16506
  style: styles16.content,
15976
16507
  contentContainerStyle: styles16.contentContainer,
15977
16508
  keyboardShouldPersistTaps: "handled",
15978
16509
  showsVerticalScrollIndicator: false
15979
16510
  },
15980
- isLoading ? /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles16.loadingContainer }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.ActivityIndicator, { size: "large", color: colors.blue }), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles16.loadingText }, "Loading...")) : renderScreen()
16511
+ isLoading ? /* @__PURE__ */ import_react20.default.createElement(import_react_native19.View, { style: styles16.loadingContainer }, /* @__PURE__ */ import_react20.default.createElement(import_react_native19.ActivityIndicator, { size: "large", color: colors.blue }), /* @__PURE__ */ import_react20.default.createElement(import_react_native19.Text, { style: styles16.loadingText }, "Loading...")) : renderScreen()
15981
16512
  ))
15982
16513
  )
15983
16514
  ));
15984
16515
  }
15985
- var styles16 = import_react_native18.StyleSheet.create({
16516
+ var styles16 = import_react_native19.StyleSheet.create({
15986
16517
  // FAB
15987
16518
  fabContainer: {
15988
16519
  position: "absolute",
@@ -16124,9 +16655,9 @@ var styles16 = import_react_native18.StyleSheet.create({
16124
16655
  });
16125
16656
 
16126
16657
  // src/BugBearErrorBoundary.tsx
16127
- var import_react20 = __toESM(require("react"));
16128
- var import_react_native19 = require("react-native");
16129
- var BugBearErrorBoundary = class extends import_react20.Component {
16658
+ var import_react21 = __toESM(require("react"));
16659
+ var import_react_native20 = require("react-native");
16660
+ var BugBearErrorBoundary = class extends import_react21.Component {
16130
16661
  constructor(props) {
16131
16662
  super(props);
16132
16663
  this.reset = () => {
@@ -16170,7 +16701,7 @@ var BugBearErrorBoundary = class extends import_react20.Component {
16170
16701
  if (fallback) {
16171
16702
  return fallback;
16172
16703
  }
16173
- return /* @__PURE__ */ import_react20.default.createElement(import_react_native19.View, { style: styles17.container }, /* @__PURE__ */ import_react20.default.createElement(import_react_native19.Text, { style: styles17.title }, "Something went wrong"), /* @__PURE__ */ import_react20.default.createElement(import_react_native19.Text, { style: styles17.message }, error.message), /* @__PURE__ */ import_react20.default.createElement(import_react_native19.TouchableOpacity, { style: styles17.button, onPress: this.reset }, /* @__PURE__ */ import_react20.default.createElement(import_react_native19.Text, { style: styles17.buttonText }, "Try Again")), /* @__PURE__ */ import_react20.default.createElement(import_react_native19.Text, { style: styles17.caption }, "The error has been captured by BugBear"));
16704
+ return /* @__PURE__ */ import_react21.default.createElement(import_react_native20.View, { style: styles17.container }, /* @__PURE__ */ import_react21.default.createElement(import_react_native20.Text, { style: styles17.title }, "Something went wrong"), /* @__PURE__ */ import_react21.default.createElement(import_react_native20.Text, { style: styles17.message }, error.message), /* @__PURE__ */ import_react21.default.createElement(import_react_native20.TouchableOpacity, { style: styles17.button, onPress: this.reset }, /* @__PURE__ */ import_react21.default.createElement(import_react_native20.Text, { style: styles17.buttonText }, "Try Again")), /* @__PURE__ */ import_react21.default.createElement(import_react_native20.Text, { style: styles17.caption }, "The error has been captured by BugBear"));
16174
16705
  }
16175
16706
  return children;
16176
16707
  }
@@ -16181,7 +16712,7 @@ function useErrorContext() {
16181
16712
  getEnhancedContext: () => contextCapture.getEnhancedContext()
16182
16713
  };
16183
16714
  }
16184
- var styles17 = import_react_native19.StyleSheet.create({
16715
+ var styles17 = import_react_native20.StyleSheet.create({
16185
16716
  container: {
16186
16717
  padding: 20,
16187
16718
  margin: 20,