@bbearai/react-native 0.8.3 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +438 -36
  2. package/dist/index.mjs +496 -94
  3. package/package.json +2 -2
package/dist/index.mjs CHANGED
@@ -12202,6 +12202,9 @@ var BugBearClient = class {
12202
12202
  this.monitor = null;
12203
12203
  this.initialized = false;
12204
12204
  this.initError = null;
12205
+ this._presenceSessionId = null;
12206
+ this._presenceInterval = null;
12207
+ this._presencePaused = false;
12205
12208
  this.config = config;
12206
12209
  if (config.apiKey) {
12207
12210
  this.pendingInit = this.resolveFromApiKey(config.apiKey);
@@ -12899,7 +12902,7 @@ var BugBearClient = class {
12899
12902
  async updateAssignmentStatus(assignmentId, status, options) {
12900
12903
  try {
12901
12904
  await this.ensureReady();
12902
- const { data: currentAssignment, error: fetchError } = await this.supabase.from("test_assignments").select("status, started_at").eq("id", assignmentId).single();
12905
+ const { data: currentAssignment, error: fetchError } = await this.supabase.from("test_assignments").select("status, started_at, tester_id, project_id").eq("id", assignmentId).single();
12903
12906
  if (fetchError || !currentAssignment) {
12904
12907
  console.error("BugBear: Assignment not found", {
12905
12908
  message: fetchError?.message,
@@ -12920,6 +12923,19 @@ var BugBearClient = class {
12920
12923
  const completedAt = /* @__PURE__ */ new Date();
12921
12924
  durationSeconds = Math.round((completedAt.getTime() - startedAt.getTime()) / 1e3);
12922
12925
  updateData.duration_seconds = durationSeconds;
12926
+ if (currentAssignment.tester_id && currentAssignment.project_id) {
12927
+ try {
12928
+ const { data: activeTime } = await this.supabase.rpc("compute_assignment_active_time", {
12929
+ p_tester_id: currentAssignment.tester_id,
12930
+ p_project_id: currentAssignment.project_id,
12931
+ p_started_at: currentAssignment.started_at,
12932
+ p_completed_at: updateData.completed_at
12933
+ });
12934
+ updateData.active_seconds = typeof activeTime === "number" ? activeTime : Math.min(durationSeconds, 1800);
12935
+ } catch {
12936
+ updateData.active_seconds = Math.min(durationSeconds, 1800);
12937
+ }
12938
+ }
12923
12939
  }
12924
12940
  }
12925
12941
  if (options?.notes) {
@@ -12998,6 +13014,7 @@ var BugBearClient = class {
12998
13014
  started_at: (/* @__PURE__ */ new Date()).toISOString(),
12999
13015
  completed_at: null,
13000
13016
  duration_seconds: null,
13017
+ active_seconds: null,
13001
13018
  skip_reason: null
13002
13019
  }).eq("id", assignmentId).eq("status", current.status);
13003
13020
  if (error) {
@@ -13847,6 +13864,7 @@ var BugBearClient = class {
13847
13864
  lastMessageAt: row.last_message_at,
13848
13865
  createdAt: row.created_at,
13849
13866
  unreadCount: Number(row.unread_count) || 0,
13867
+ reporterName: row.reporter_name || void 0,
13850
13868
  lastMessage: row.last_message_preview ? {
13851
13869
  id: "",
13852
13870
  threadId: row.thread_id,
@@ -14286,6 +14304,93 @@ var BugBearClient = class {
14286
14304
  updatedAt: data.updated_at
14287
14305
  };
14288
14306
  }
14307
+ // ─── Passive Presence Tracking ──────────────────────────────
14308
+ /** Current presence session ID (null if not tracking). */
14309
+ get presenceSessionId() {
14310
+ return this._presenceSessionId;
14311
+ }
14312
+ /**
14313
+ * Start passive presence tracking for this tester.
14314
+ * Idempotent — reuses an existing active session if one exists.
14315
+ */
14316
+ async startPresence(platform) {
14317
+ try {
14318
+ await this.ensureReady();
14319
+ const testerInfo = await this.getTesterInfo();
14320
+ if (!testerInfo) return null;
14321
+ const { data, error } = await this.supabase.rpc("upsert_tester_presence", {
14322
+ p_project_id: this.config.projectId,
14323
+ p_tester_id: testerInfo.id,
14324
+ p_platform: platform
14325
+ });
14326
+ if (error) {
14327
+ console.error("BugBear: Failed to start presence", formatPgError(error));
14328
+ return null;
14329
+ }
14330
+ this._presenceSessionId = data;
14331
+ this._presencePaused = false;
14332
+ this.startPresenceHeartbeat();
14333
+ return data;
14334
+ } catch (err) {
14335
+ console.error("BugBear: Error starting presence", err);
14336
+ return null;
14337
+ }
14338
+ }
14339
+ /** Gracefully end the current presence session. */
14340
+ async endPresence() {
14341
+ this.stopPresenceHeartbeat();
14342
+ if (!this._presenceSessionId) return;
14343
+ try {
14344
+ await this.supabase.rpc("end_tester_presence", {
14345
+ p_session_id: this._presenceSessionId
14346
+ });
14347
+ } catch {
14348
+ }
14349
+ this._presenceSessionId = null;
14350
+ }
14351
+ /** Pause heartbeat (tab hidden / app backgrounded). Sends one final beat. */
14352
+ pausePresence() {
14353
+ this._presencePaused = true;
14354
+ this.heartbeatPresence();
14355
+ }
14356
+ /** Resume heartbeat after pause. Restarts if session was cleaned up. */
14357
+ async resumePresence() {
14358
+ if (!this._presenceSessionId) return;
14359
+ this._presencePaused = false;
14360
+ try {
14361
+ const { data } = await this.supabase.rpc("heartbeat_tester_presence", {
14362
+ p_session_id: this._presenceSessionId
14363
+ });
14364
+ if (!data) {
14365
+ this._presenceSessionId = null;
14366
+ }
14367
+ } catch {
14368
+ this._presenceSessionId = null;
14369
+ }
14370
+ }
14371
+ async heartbeatPresence() {
14372
+ if (!this._presenceSessionId || this._presencePaused) return;
14373
+ try {
14374
+ const { data, error } = await this.supabase.rpc("heartbeat_tester_presence", {
14375
+ p_session_id: this._presenceSessionId
14376
+ });
14377
+ if (error || data === false) {
14378
+ this.stopPresenceHeartbeat();
14379
+ this._presenceSessionId = null;
14380
+ }
14381
+ } catch {
14382
+ }
14383
+ }
14384
+ startPresenceHeartbeat() {
14385
+ this.stopPresenceHeartbeat();
14386
+ this._presenceInterval = setInterval(() => this.heartbeatPresence(), 6e4);
14387
+ }
14388
+ stopPresenceHeartbeat() {
14389
+ if (this._presenceInterval) {
14390
+ clearInterval(this._presenceInterval);
14391
+ this._presenceInterval = null;
14392
+ }
14393
+ }
14289
14394
  };
14290
14395
  function createBugBear(config) {
14291
14396
  return new BugBearClient(config);
@@ -14420,6 +14525,9 @@ function setActiveColors(palette) {
14420
14525
  colors = palette;
14421
14526
  shared = createSharedStyles();
14422
14527
  }
14528
+ function withAlpha(hex, alpha) {
14529
+ return hex + Math.round(alpha * 255).toString(16).padStart(2, "0");
14530
+ }
14423
14531
  function createSharedStyles() {
14424
14532
  return StyleSheet.create({
14425
14533
  card: {
@@ -14922,6 +15030,28 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
14922
15030
  }
14923
15031
  return () => subscription.remove();
14924
15032
  }, [client]);
15033
+ useEffect(() => {
15034
+ if (!client || !isTester) return;
15035
+ let mounted = true;
15036
+ const platform = Platform2.OS === "ios" ? "ios" : "android";
15037
+ client.startPresence(platform);
15038
+ const subscription = AppState.addEventListener("change", (nextState) => {
15039
+ if (nextState === "active") {
15040
+ client.resumePresence().then(() => {
15041
+ if (mounted && !client.presenceSessionId) {
15042
+ client.startPresence(platform);
15043
+ }
15044
+ });
15045
+ } else if (nextState === "background" || nextState === "inactive") {
15046
+ client.pausePresence();
15047
+ }
15048
+ });
15049
+ return () => {
15050
+ mounted = false;
15051
+ subscription.remove();
15052
+ client.endPresence();
15053
+ };
15054
+ }, [client, isTester]);
14925
15055
  useEffect(() => {
14926
15056
  if (!client || !isTester) return;
14927
15057
  if (widgetMode === "qa" && !isQAEnabled) return;
@@ -15017,14 +15147,13 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
15017
15147
  }
15018
15148
 
15019
15149
  // src/BugBearButton.tsx
15020
- import React21, { useState as useState17, useRef as useRef6, useMemo as useMemo16 } from "react";
15150
+ import React21, { useState as useState18, useRef as useRef6, useEffect as useEffect12, useMemo as useMemo16 } from "react";
15021
15151
  import {
15022
15152
  View as View21,
15023
15153
  Text as Text19,
15024
15154
  Image as Image4,
15025
15155
  TouchableOpacity as TouchableOpacity18,
15026
- Modal as Modal3,
15027
- ScrollView as ScrollView3,
15156
+ ScrollView as ScrollView4,
15028
15157
  StyleSheet as StyleSheet21,
15029
15158
  Dimensions as Dimensions2,
15030
15159
  KeyboardAvoidingView,
@@ -15032,7 +15161,8 @@ import {
15032
15161
  PanResponder,
15033
15162
  Animated as Animated2,
15034
15163
  ActivityIndicator as ActivityIndicator3,
15035
- Keyboard as Keyboard4
15164
+ Keyboard as Keyboard4,
15165
+ BackHandler
15036
15166
  } from "react-native";
15037
15167
 
15038
15168
  // src/widget/logo.ts
@@ -16934,6 +17064,8 @@ function ReportScreen({ nav, prefill, autoCaptureUri, onAutoCaptureConsumed }) {
16934
17064
  const [severity, setSeverity] = useState8("medium");
16935
17065
  const [category, setCategory] = useState8(null);
16936
17066
  const [description, setDescription] = useState8("");
17067
+ const [myIssues, setMyIssues] = useState8([]);
17068
+ const [similarReports, setSimilarReports] = useState8([]);
16937
17069
  const [affectedScreen, setAffectedScreen] = useState8("");
16938
17070
  const [submitting, setSubmitting] = useState8(false);
16939
17071
  const [error, setError] = useState8(null);
@@ -16949,6 +17081,41 @@ function ReportScreen({ nav, prefill, autoCaptureUri, onAutoCaptureConsumed }) {
16949
17081
  }, [autoCaptureUri, onAutoCaptureConsumed]);
16950
17082
  const isRetestFailure = prefill?.type === "test_fail";
16951
17083
  const isBugType = reportType === "bug" || reportType === "test_fail";
17084
+ useEffect6(() => {
17085
+ if (!client?.getIssues) return;
17086
+ let cancelled = false;
17087
+ const load = async () => {
17088
+ try {
17089
+ const [open, done] = await Promise.all([
17090
+ client.getIssues("open"),
17091
+ client.getIssues("done")
17092
+ ]);
17093
+ if (!cancelled) setMyIssues([...open, ...done]);
17094
+ } catch {
17095
+ }
17096
+ };
17097
+ load();
17098
+ return () => {
17099
+ cancelled = true;
17100
+ };
17101
+ }, [client]);
17102
+ useEffect6(() => {
17103
+ if (description.length < 10 || myIssues.length === 0) {
17104
+ setSimilarReports([]);
17105
+ return;
17106
+ }
17107
+ const words = description.toLowerCase().split(/\s+/).filter((w) => w.length > 3);
17108
+ if (words.length === 0) {
17109
+ setSimilarReports([]);
17110
+ return;
17111
+ }
17112
+ const scored = myIssues.map((issue) => {
17113
+ const text = `${issue.title || ""} ${issue.description || ""}`.toLowerCase();
17114
+ const matches = words.filter((w) => text.includes(w)).length;
17115
+ return { issue, score: matches / words.length };
17116
+ }).filter((s2) => s2.score >= 0.3).sort((a, b) => b.score - a.score).slice(0, 3);
17117
+ setSimilarReports(scored.map((s2) => s2.issue));
17118
+ }, [description, myIssues]);
16952
17119
  useEffect6(() => {
16953
17120
  if (reportType === "feedback" || reportType === "suggestion") {
16954
17121
  setCategory("other");
@@ -17023,7 +17190,41 @@ function ReportScreen({ nav, prefill, autoCaptureUri, onAutoCaptureConsumed }) {
17023
17190
  numberOfLines: 4,
17024
17191
  textAlignVertical: "top"
17025
17192
  }
17026
- )), /* @__PURE__ */ React10.createElement(
17193
+ ), similarReports.length > 0 && /* @__PURE__ */ React10.createElement(View10, { style: {
17194
+ marginTop: 8,
17195
+ padding: 12,
17196
+ borderRadius: 8,
17197
+ backgroundColor: withAlpha(colors.orange, 0.1),
17198
+ borderWidth: 1,
17199
+ borderColor: withAlpha(colors.orange, 0.3)
17200
+ } }, /* @__PURE__ */ React10.createElement(Text8, { style: { fontSize: 12, fontWeight: "600", color: colors.orange, marginBottom: 8 } }, "Similar reports you've already filed:"), similarReports.map((issue) => /* @__PURE__ */ React10.createElement(
17201
+ TouchableOpacity8,
17202
+ {
17203
+ key: issue.id,
17204
+ onPress: () => nav.push({ name: "ISSUE_DETAIL", issue }),
17205
+ style: {
17206
+ flexDirection: "row",
17207
+ alignItems: "center",
17208
+ gap: 8,
17209
+ paddingVertical: 6
17210
+ }
17211
+ },
17212
+ /* @__PURE__ */ React10.createElement(View10, { style: {
17213
+ width: 8,
17214
+ height: 8,
17215
+ borderRadius: 4,
17216
+ backgroundColor: issue.severity === "critical" ? colors.red : issue.severity === "high" ? colors.orange : colors.yellow
17217
+ } }),
17218
+ /* @__PURE__ */ React10.createElement(
17219
+ Text8,
17220
+ {
17221
+ numberOfLines: 1,
17222
+ style: { fontSize: 12, color: colors.textPrimary, flex: 1 }
17223
+ },
17224
+ issue.title
17225
+ ),
17226
+ /* @__PURE__ */ React10.createElement(Text8, { style: { fontSize: 11, color: colors.textMuted } }, issue.status)
17227
+ )))), /* @__PURE__ */ React10.createElement(
17027
17228
  ImagePickerButtons,
17028
17229
  {
17029
17230
  images: images.images,
@@ -17079,7 +17280,41 @@ function ReportScreen({ nav, prefill, autoCaptureUri, onAutoCaptureConsumed }) {
17079
17280
  numberOfLines: 4,
17080
17281
  textAlignVertical: "top"
17081
17282
  }
17082
- )), isBugType && /* @__PURE__ */ React10.createElement(View10, { style: styles5.section }, /* @__PURE__ */ React10.createElement(Text8, { style: shared.label }, "Which screen?"), /* @__PURE__ */ React10.createElement(
17283
+ ), similarReports.length > 0 && /* @__PURE__ */ React10.createElement(View10, { style: {
17284
+ marginTop: 8,
17285
+ padding: 12,
17286
+ borderRadius: 8,
17287
+ backgroundColor: withAlpha(colors.orange, 0.1),
17288
+ borderWidth: 1,
17289
+ borderColor: withAlpha(colors.orange, 0.3)
17290
+ } }, /* @__PURE__ */ React10.createElement(Text8, { style: { fontSize: 12, fontWeight: "600", color: colors.orange, marginBottom: 8 } }, "Similar reports you've already filed:"), similarReports.map((issue) => /* @__PURE__ */ React10.createElement(
17291
+ TouchableOpacity8,
17292
+ {
17293
+ key: issue.id,
17294
+ onPress: () => nav.push({ name: "ISSUE_DETAIL", issue }),
17295
+ style: {
17296
+ flexDirection: "row",
17297
+ alignItems: "center",
17298
+ gap: 8,
17299
+ paddingVertical: 6
17300
+ }
17301
+ },
17302
+ /* @__PURE__ */ React10.createElement(View10, { style: {
17303
+ width: 8,
17304
+ height: 8,
17305
+ borderRadius: 4,
17306
+ backgroundColor: issue.severity === "critical" ? colors.red : issue.severity === "high" ? colors.orange : colors.yellow
17307
+ } }),
17308
+ /* @__PURE__ */ React10.createElement(
17309
+ Text8,
17310
+ {
17311
+ numberOfLines: 1,
17312
+ style: { fontSize: 12, color: colors.textPrimary, flex: 1 }
17313
+ },
17314
+ issue.title
17315
+ ),
17316
+ /* @__PURE__ */ React10.createElement(Text8, { style: { fontSize: 11, color: colors.textMuted } }, issue.status)
17317
+ )))), isBugType && /* @__PURE__ */ React10.createElement(View10, { style: styles5.section }, /* @__PURE__ */ React10.createElement(Text8, { style: shared.label }, "Which screen?"), /* @__PURE__ */ React10.createElement(
17083
17318
  TextInput4,
17084
17319
  {
17085
17320
  style: styles5.screenInput,
@@ -17155,11 +17390,24 @@ function createStyles6() {
17155
17390
  }
17156
17391
 
17157
17392
  // src/widget/screens/MessageListScreen.tsx
17158
- import React12, { useMemo as useMemo7 } from "react";
17159
- import { View as View12, Text as Text10, TouchableOpacity as TouchableOpacity9, StyleSheet as StyleSheet12, Linking as Linking3 } from "react-native";
17393
+ import React12, { useMemo as useMemo7, useState as useState9 } from "react";
17394
+ import { View as View12, Text as Text10, TouchableOpacity as TouchableOpacity9, ScrollView as ScrollView3, StyleSheet as StyleSheet12, Linking as Linking3 } from "react-native";
17160
17395
  function MessageListScreen({ nav }) {
17161
17396
  const { threads, unreadCount, refreshThreads, dashboardUrl, isLoading, widgetColorScheme } = useBugBear();
17162
17397
  const styles5 = useMemo7(() => createStyles7(), [widgetColorScheme]);
17398
+ const [activeFilter, setActiveFilter] = useState9("all");
17399
+ const filteredThreads = threads.filter((thread) => {
17400
+ if (activeFilter === "all") return true;
17401
+ if (activeFilter === "unread") return thread.unreadCount > 0;
17402
+ return thread.threadType === activeFilter;
17403
+ });
17404
+ const filterChips = [
17405
+ { key: "all", label: "All" },
17406
+ { key: "report", label: "Bug Reports" },
17407
+ { key: "direct", label: "Direct" },
17408
+ { key: "announcement", label: "Announcements" },
17409
+ { key: "unread", label: "Unread", count: threads.filter((t) => t.unreadCount > 0).length }
17410
+ ];
17163
17411
  if (isLoading) return /* @__PURE__ */ React12.createElement(MessageListScreenSkeleton, null);
17164
17412
  return /* @__PURE__ */ React12.createElement(View12, null, /* @__PURE__ */ React12.createElement(
17165
17413
  TouchableOpacity9,
@@ -17168,14 +17416,54 @@ function MessageListScreen({ nav }) {
17168
17416
  onPress: () => nav.push({ name: "COMPOSE_MESSAGE" })
17169
17417
  },
17170
17418
  /* @__PURE__ */ React12.createElement(Text10, { style: styles5.newMsgText }, "\u2709\uFE0F New Message")
17171
- ), threads.length === 0 ? /* @__PURE__ */ React12.createElement(View12, { style: shared.emptyState }, /* @__PURE__ */ React12.createElement(Text10, { style: shared.emptyEmoji }, "\u{1F4AC}"), /* @__PURE__ */ React12.createElement(Text10, { style: shared.emptyTitle }, "No messages yet"), /* @__PURE__ */ React12.createElement(Text10, { style: shared.emptySubtitle }, "Start a conversation or wait for messages from admins")) : /* @__PURE__ */ React12.createElement(View12, null, threads.map((thread) => /* @__PURE__ */ React12.createElement(
17419
+ ), /* @__PURE__ */ React12.createElement(
17420
+ ScrollView3,
17421
+ {
17422
+ horizontal: true,
17423
+ showsHorizontalScrollIndicator: false,
17424
+ style: { paddingBottom: 12 },
17425
+ contentContainerStyle: { paddingHorizontal: 16, gap: 8 }
17426
+ },
17427
+ filterChips.map((chip) => /* @__PURE__ */ React12.createElement(
17428
+ TouchableOpacity9,
17429
+ {
17430
+ key: chip.key,
17431
+ onPress: () => setActiveFilter(chip.key),
17432
+ style: {
17433
+ paddingVertical: 6,
17434
+ paddingHorizontal: 12,
17435
+ borderRadius: 16,
17436
+ borderWidth: 1,
17437
+ borderColor: activeFilter === chip.key ? colors.blue : colors.border,
17438
+ backgroundColor: activeFilter === chip.key ? withAlpha(colors.blue, 0.15) : "transparent",
17439
+ flexDirection: "row",
17440
+ alignItems: "center",
17441
+ gap: 4
17442
+ }
17443
+ },
17444
+ /* @__PURE__ */ React12.createElement(Text10, { style: {
17445
+ fontSize: 12,
17446
+ fontWeight: "500",
17447
+ color: activeFilter === chip.key ? colors.blue : colors.textSecondary
17448
+ } }, chip.label),
17449
+ chip.count !== void 0 && chip.count > 0 && /* @__PURE__ */ React12.createElement(View12, { style: {
17450
+ backgroundColor: colors.blue,
17451
+ borderRadius: 8,
17452
+ minWidth: 16,
17453
+ height: 16,
17454
+ justifyContent: "center",
17455
+ alignItems: "center",
17456
+ paddingHorizontal: 4
17457
+ } }, /* @__PURE__ */ React12.createElement(Text10, { style: { fontSize: 10, fontWeight: "bold", color: colors.onPrimary } }, chip.count))
17458
+ ))
17459
+ ), filteredThreads.length === 0 ? /* @__PURE__ */ React12.createElement(View12, { style: shared.emptyState }, /* @__PURE__ */ React12.createElement(Text10, { style: shared.emptyEmoji }, "\u{1F4AC}"), /* @__PURE__ */ React12.createElement(Text10, { style: shared.emptyTitle }, "No messages yet"), /* @__PURE__ */ React12.createElement(Text10, { style: shared.emptySubtitle }, "Start a conversation or wait for messages from admins")) : /* @__PURE__ */ React12.createElement(View12, null, filteredThreads.map((thread) => /* @__PURE__ */ React12.createElement(
17172
17460
  TouchableOpacity9,
17173
17461
  {
17174
17462
  key: thread.id,
17175
17463
  style: [styles5.threadItem, thread.unreadCount > 0 && styles5.threadItemUnread],
17176
17464
  onPress: () => nav.push({ name: "THREAD_DETAIL", thread })
17177
17465
  },
17178
- /* @__PURE__ */ React12.createElement(View12, { style: styles5.threadLeft }, /* @__PURE__ */ React12.createElement(Text10, { style: styles5.threadIcon }, getThreadTypeIcon(thread.threadType)), /* @__PURE__ */ React12.createElement(View12, { style: styles5.threadInfo }, /* @__PURE__ */ React12.createElement(View12, { style: styles5.threadTitleRow }, thread.isPinned && /* @__PURE__ */ React12.createElement(Text10, { style: styles5.pinIcon }, "\u{1F4CC}"), /* @__PURE__ */ React12.createElement(Text10, { style: styles5.threadSubject, numberOfLines: 1 }, thread.subject || "No subject")), thread.lastMessage && /* @__PURE__ */ React12.createElement(Text10, { style: styles5.threadPreview, numberOfLines: 1 }, thread.lastMessage.senderName, ": ", thread.lastMessage.content))),
17466
+ /* @__PURE__ */ React12.createElement(View12, { style: styles5.threadLeft }, /* @__PURE__ */ React12.createElement(Text10, { style: styles5.threadIcon }, getThreadTypeIcon(thread.threadType)), /* @__PURE__ */ React12.createElement(View12, { style: styles5.threadInfo }, /* @__PURE__ */ React12.createElement(View12, { style: styles5.threadTitleRow }, thread.isPinned && /* @__PURE__ */ React12.createElement(Text10, { style: styles5.pinIcon }, "\u{1F4CC}"), /* @__PURE__ */ React12.createElement(Text10, { style: styles5.threadSubject, numberOfLines: 1 }, thread.subject || "No subject")), thread.threadType === "report" && thread.reporterName && /* @__PURE__ */ React12.createElement(Text10, { style: { fontSize: 11, color: colors.textMuted } }, "Reported by: ", thread.reporterName), thread.lastMessage && /* @__PURE__ */ React12.createElement(Text10, { style: styles5.threadPreview, numberOfLines: 1 }, thread.lastMessage.senderName, ": ", thread.lastMessage.content))),
17179
17467
  /* @__PURE__ */ React12.createElement(View12, { style: styles5.threadRight }, /* @__PURE__ */ React12.createElement(Text10, { style: styles5.threadTime }, formatRelativeTime(thread.lastMessageAt)), thread.unreadCount > 0 && /* @__PURE__ */ React12.createElement(View12, { style: styles5.unreadBadge }, /* @__PURE__ */ React12.createElement(Text10, { style: styles5.unreadText }, thread.unreadCount)), thread.priority !== "normal" && /* @__PURE__ */ React12.createElement(View12, { style: [styles5.priorityDot, { backgroundColor: getPriorityColor(thread.priority) }] }))
17180
17468
  ))), dashboardUrl && /* @__PURE__ */ React12.createElement(
17181
17469
  TouchableOpacity9,
@@ -17185,7 +17473,7 @@ function MessageListScreen({ nav }) {
17185
17473
  activeOpacity: 0.7
17186
17474
  },
17187
17475
  /* @__PURE__ */ React12.createElement(Text10, { style: styles5.dashboardLinkText }, "\u{1F310}", " View on Dashboard ", "\u2192")
17188
- ), /* @__PURE__ */ React12.createElement(View12, { style: styles5.footer }, /* @__PURE__ */ React12.createElement(Text10, { style: styles5.footerText }, threads.length, " thread", threads.length !== 1 ? "s" : "", " \xB7 ", unreadCount, " unread"), /* @__PURE__ */ React12.createElement(TouchableOpacity9, { onPress: refreshThreads }, /* @__PURE__ */ React12.createElement(Text10, { style: styles5.refreshText }, "\u21BB Refresh"))));
17476
+ ), /* @__PURE__ */ React12.createElement(View12, { style: styles5.footer }, /* @__PURE__ */ React12.createElement(Text10, { style: styles5.footerText }, filteredThreads.length, " thread", filteredThreads.length !== 1 ? "s" : "", " \xB7 ", unreadCount, " unread"), /* @__PURE__ */ React12.createElement(TouchableOpacity9, { onPress: refreshThreads }, /* @__PURE__ */ React12.createElement(Text10, { style: styles5.refreshText }, "\u21BB Refresh"))));
17189
17477
  }
17190
17478
  function createStyles7() {
17191
17479
  return StyleSheet12.create({
@@ -17214,16 +17502,16 @@ function createStyles7() {
17214
17502
  }
17215
17503
 
17216
17504
  // src/widget/screens/ThreadDetailScreen.tsx
17217
- import React13, { useState as useState9, useEffect as useEffect8, useMemo as useMemo8 } from "react";
17505
+ import React13, { useState as useState10, useEffect as useEffect8, useMemo as useMemo8 } from "react";
17218
17506
  import { View as View13, Text as Text11, TouchableOpacity as TouchableOpacity10, TextInput as TextInput5, StyleSheet as StyleSheet13, Image as Image2 } from "react-native";
17219
17507
  function ThreadDetailScreen({ thread, nav }) {
17220
17508
  const { getThreadMessages, sendMessage, markAsRead, uploadImage, widgetColorScheme } = useBugBear();
17221
17509
  const styles5 = useMemo8(() => createStyles8(), [widgetColorScheme]);
17222
- const [messages, setMessages] = useState9([]);
17223
- const [loading, setLoading] = useState9(true);
17224
- const [replyText, setReplyText] = useState9("");
17225
- const [sending, setSending] = useState9(false);
17226
- const [sendError, setSendError] = useState9(false);
17510
+ const [messages, setMessages] = useState10([]);
17511
+ const [loading, setLoading] = useState10(true);
17512
+ const [replyText, setReplyText] = useState10("");
17513
+ const [sending, setSending] = useState10(false);
17514
+ const [sendError, setSendError] = useState10(false);
17227
17515
  const replyImages = useImageAttachments(uploadImage, 3, "discussion-attachments");
17228
17516
  useEffect8(() => {
17229
17517
  let cancelled = false;
@@ -17335,14 +17623,14 @@ function createStyles8() {
17335
17623
  }
17336
17624
 
17337
17625
  // src/widget/screens/ComposeMessageScreen.tsx
17338
- import React14, { useState as useState10, useMemo as useMemo9 } from "react";
17626
+ import React14, { useState as useState11, useMemo as useMemo9 } from "react";
17339
17627
  import { View as View14, Text as Text12, TextInput as TextInput6, TouchableOpacity as TouchableOpacity11, StyleSheet as StyleSheet14 } from "react-native";
17340
17628
  function ComposeMessageScreen({ nav }) {
17341
17629
  const { createThread, uploadImage, widgetColorScheme } = useBugBear();
17342
17630
  const styles5 = useMemo9(() => createStyles9(), [widgetColorScheme]);
17343
- const [subject, setSubject] = useState10("");
17344
- const [message, setMessage] = useState10("");
17345
- const [sending, setSending] = useState10(false);
17631
+ const [subject, setSubject] = useState11("");
17632
+ const [message, setMessage] = useState11("");
17633
+ const [sending, setSending] = useState11(false);
17346
17634
  const images = useImageAttachments(uploadImage, 3, "discussion-attachments");
17347
17635
  const handleSend = async () => {
17348
17636
  if (!subject.trim() || !message.trim() || sending || images.isUploading) return;
@@ -17412,19 +17700,19 @@ function createStyles9() {
17412
17700
  }
17413
17701
 
17414
17702
  // src/widget/screens/ProfileScreen.tsx
17415
- import React15, { useState as useState11, useEffect as useEffect9, useMemo as useMemo10 } from "react";
17703
+ import React15, { useState as useState12, useEffect as useEffect9, useMemo as useMemo10 } from "react";
17416
17704
  import { View as View15, Text as Text13, TouchableOpacity as TouchableOpacity12, TextInput as TextInput7, StyleSheet as StyleSheet15 } from "react-native";
17417
17705
  function ProfileScreen({ nav }) {
17418
17706
  const { testerInfo, assignments, updateTesterProfile, refreshTesterInfo, widgetColorScheme } = useBugBear();
17419
17707
  const styles5 = useMemo10(() => createStyles10(), [widgetColorScheme]);
17420
- const [editing, setEditing] = useState11(false);
17421
- const [name, setName] = useState11(testerInfo?.name || "");
17422
- const [additionalEmails, setAdditionalEmails] = useState11(testerInfo?.additionalEmails || []);
17423
- const [newEmailInput, setNewEmailInput] = useState11("");
17424
- const [platforms, setPlatforms] = useState11(testerInfo?.platforms || []);
17425
- const [saving, setSaving] = useState11(false);
17426
- const [saved, setSaved] = useState11(false);
17427
- const [showDetails, setShowDetails] = useState11(false);
17708
+ const [editing, setEditing] = useState12(false);
17709
+ const [name, setName] = useState12(testerInfo?.name || "");
17710
+ const [additionalEmails, setAdditionalEmails] = useState12(testerInfo?.additionalEmails || []);
17711
+ const [newEmailInput, setNewEmailInput] = useState12("");
17712
+ const [platforms, setPlatforms] = useState12(testerInfo?.platforms || []);
17713
+ const [saving, setSaving] = useState12(false);
17714
+ const [saved, setSaved] = useState12(false);
17715
+ const [showDetails, setShowDetails] = useState12(false);
17428
17716
  const completedCount = assignments.filter((a) => a.status === "passed" || a.status === "failed").length;
17429
17717
  useEffect9(() => {
17430
17718
  if (testerInfo) {
@@ -17543,8 +17831,8 @@ function createStyles10() {
17543
17831
  }
17544
17832
 
17545
17833
  // src/widget/screens/IssueListScreen.tsx
17546
- import React16, { useState as useState12, useEffect as useEffect10, useMemo as useMemo11 } from "react";
17547
- import { View as View16, Text as Text14, TouchableOpacity as TouchableOpacity13, StyleSheet as StyleSheet16 } from "react-native";
17834
+ import React16, { useState as useState13, useEffect as useEffect10, useMemo as useMemo11 } from "react";
17835
+ import { View as View16, Text as Text14, TextInput as TextInput8, TouchableOpacity as TouchableOpacity13, StyleSheet as StyleSheet16 } from "react-native";
17548
17836
  var CATEGORIES = ["open", "done", "reopened"];
17549
17837
  var SEVERITY_ORDER2 = { critical: 0, high: 1, medium: 2, low: 3 };
17550
17838
  function IssueListScreen({ nav, category }) {
@@ -17561,12 +17849,22 @@ function IssueListScreen({ nav, category }) {
17561
17849
  medium: colors.yellow,
17562
17850
  low: colors.textMuted
17563
17851
  }), [widgetColorScheme]);
17564
- const [activeCategory, setActiveCategory] = useState12(category);
17565
- const [issues, setIssues] = useState12([]);
17566
- const [loading, setLoading] = useState12(true);
17567
- const [counts, setCounts] = useState12(null);
17568
- const [sortMode, setSortMode] = useState12("severity");
17852
+ const [activeCategory, setActiveCategory] = useState13(category);
17853
+ const [issues, setIssues] = useState13([]);
17854
+ const [loading, setLoading] = useState13(true);
17855
+ const [counts, setCounts] = useState13(null);
17856
+ const [sortMode, setSortMode] = useState13("severity");
17857
+ const [searchQuery, setSearchQuery] = useState13("");
17858
+ const [debouncedQuery, setDebouncedQuery] = useState13("");
17569
17859
  const config = CATEGORY_CONFIG[activeCategory];
17860
+ useEffect10(() => {
17861
+ const timer = setTimeout(() => setDebouncedQuery(searchQuery), 300);
17862
+ return () => clearTimeout(timer);
17863
+ }, [searchQuery]);
17864
+ useEffect10(() => {
17865
+ setSearchQuery("");
17866
+ setDebouncedQuery("");
17867
+ }, [activeCategory]);
17570
17868
  useEffect10(() => {
17571
17869
  if (!client) return;
17572
17870
  client.getIssueCounts().then(setCounts).catch(() => {
@@ -17606,6 +17904,9 @@ function IssueListScreen({ nav, category }) {
17606
17904
  }
17607
17905
  return sorted;
17608
17906
  }, [issues, sortMode]);
17907
+ const searchFilteredIssues = debouncedQuery ? sortedIssues.filter(
17908
+ (issue) => (issue.title || "").toLowerCase().includes(debouncedQuery.toLowerCase()) || (issue.description || "").toLowerCase().includes(debouncedQuery.toLowerCase())
17909
+ ) : sortedIssues;
17609
17910
  return /* @__PURE__ */ React16.createElement(View16, null, /* @__PURE__ */ React16.createElement(View16, { style: styles5.tabBar }, CATEGORIES.map((cat) => {
17610
17911
  const catConfig = CATEGORY_CONFIG[cat];
17611
17912
  const isActive = activeCategory === cat;
@@ -17652,7 +17953,25 @@ function IssueListScreen({ nav, category }) {
17652
17953
  styles5.sortBtnText,
17653
17954
  sortMode === s2.key && styles5.sortBtnTextActive
17654
17955
  ] }, s2.label)
17655
- ))), loading ? /* @__PURE__ */ React16.createElement(IssueListScreenSkeleton, null) : sortedIssues.length === 0 ? /* @__PURE__ */ React16.createElement(View16, { style: styles5.emptyContainer }, /* @__PURE__ */ React16.createElement(Text14, { style: styles5.emptyIcon }, config.emptyIcon), /* @__PURE__ */ React16.createElement(Text14, { style: styles5.emptyText }, config.emptyText)) : sortedIssues.map((issue) => /* @__PURE__ */ React16.createElement(
17956
+ ))), /* @__PURE__ */ React16.createElement(View16, { style: { paddingHorizontal: 16, paddingBottom: 12 } }, /* @__PURE__ */ React16.createElement(
17957
+ TextInput8,
17958
+ {
17959
+ value: searchQuery,
17960
+ onChangeText: setSearchQuery,
17961
+ placeholder: "Search my reports...",
17962
+ placeholderTextColor: colors.textMuted,
17963
+ style: {
17964
+ padding: 8,
17965
+ paddingHorizontal: 12,
17966
+ borderRadius: 8,
17967
+ borderWidth: 1,
17968
+ borderColor: colors.border,
17969
+ backgroundColor: colors.card,
17970
+ color: colors.textPrimary,
17971
+ fontSize: 13
17972
+ }
17973
+ }
17974
+ )), loading ? /* @__PURE__ */ React16.createElement(IssueListScreenSkeleton, null) : searchFilteredIssues.length === 0 ? /* @__PURE__ */ React16.createElement(View16, { style: styles5.emptyContainer }, /* @__PURE__ */ React16.createElement(Text14, { style: styles5.emptyIcon }, debouncedQuery ? "\u{1F50D}" : config.emptyIcon), /* @__PURE__ */ React16.createElement(Text14, { style: styles5.emptyText }, debouncedQuery ? "No matching issues" : config.emptyText)) : searchFilteredIssues.map((issue) => /* @__PURE__ */ React16.createElement(
17656
17975
  TouchableOpacity13,
17657
17976
  {
17658
17977
  key: issue.id,
@@ -17817,17 +18136,17 @@ function createStyles11() {
17817
18136
  }
17818
18137
 
17819
18138
  // src/widget/screens/IssueDetailScreen.tsx
17820
- import React17, { useMemo as useMemo12, useState as useState13, useCallback as useCallback5 } from "react";
17821
- import { View as View17, Text as Text15, Image as Image3, StyleSheet as StyleSheet17, Linking as Linking4, TouchableOpacity as TouchableOpacity14, TextInput as TextInput8, ActivityIndicator as ActivityIndicator2 } from "react-native";
18139
+ import React17, { useMemo as useMemo12, useState as useState14, useCallback as useCallback5 } from "react";
18140
+ import { View as View17, Text as Text15, Image as Image3, StyleSheet as StyleSheet17, Linking as Linking4, TouchableOpacity as TouchableOpacity14, TextInput as TextInput9, ActivityIndicator as ActivityIndicator2 } from "react-native";
17822
18141
  var DONE_STATUSES = ["verified", "resolved", "closed", "reviewed"];
17823
18142
  function IssueDetailScreen({ nav, issue }) {
17824
18143
  const { dashboardUrl, widgetColorScheme, reopenReport } = useBugBear();
17825
18144
  const styles5 = useMemo12(() => createStyles12(), [widgetColorScheme]);
17826
- const [showReopenForm, setShowReopenForm] = useState13(false);
17827
- const [reopenReason, setReopenReason] = useState13("");
17828
- const [isSubmitting, setIsSubmitting] = useState13(false);
17829
- const [reopenError, setReopenError] = useState13(null);
17830
- const [wasReopened, setWasReopened] = useState13(false);
18145
+ const [showReopenForm, setShowReopenForm] = useState14(false);
18146
+ const [reopenReason, setReopenReason] = useState14("");
18147
+ const [isSubmitting, setIsSubmitting] = useState14(false);
18148
+ const [reopenError, setReopenError] = useState14(null);
18149
+ const [wasReopened, setWasReopened] = useState14(false);
17831
18150
  const STATUS_LABELS = useMemo12(() => ({
17832
18151
  new: { label: "New", bg: colors.blueDark, color: colors.blueLight },
17833
18152
  triaging: { label: "Triaging", bg: colors.blueDark, color: colors.blueLight },
@@ -17873,7 +18192,7 @@ function IssueDetailScreen({ nav, issue }) {
17873
18192
  },
17874
18193
  /* @__PURE__ */ React17.createElement(Text15, { style: styles5.reopenButtonText }, "\u{1F504}", " Not Fixed \u2014 Reopen Issue")
17875
18194
  ), showReopenForm && !wasReopened && /* @__PURE__ */ React17.createElement(View17, { style: styles5.reopenForm }, /* @__PURE__ */ React17.createElement(Text15, { style: styles5.reopenFormTitle }, "Why isn't this fixed?"), /* @__PURE__ */ React17.createElement(
17876
- TextInput8,
18195
+ TextInput9,
17877
18196
  {
17878
18197
  value: reopenReason,
17879
18198
  onChangeText: setReopenReason,
@@ -18171,18 +18490,20 @@ function createStyles12() {
18171
18490
  }
18172
18491
 
18173
18492
  // src/widget/screens/SessionStartScreen.tsx
18174
- import React18, { useState as useState14, useMemo as useMemo13 } from "react";
18175
- import { View as View18, Text as Text16, TextInput as TextInput9, TouchableOpacity as TouchableOpacity15, StyleSheet as StyleSheet18, Keyboard as Keyboard2 } from "react-native";
18493
+ import React18, { useState as useState15, useMemo as useMemo13 } from "react";
18494
+ import { View as View18, Text as Text16, TextInput as TextInput10, TouchableOpacity as TouchableOpacity15, StyleSheet as StyleSheet18, Keyboard as Keyboard2 } from "react-native";
18176
18495
  function SessionStartScreen({ nav }) {
18177
18496
  const { startSession, assignments, widgetColorScheme } = useBugBear();
18178
18497
  const styles5 = useMemo13(() => createStyles13(), [widgetColorScheme]);
18179
- const [focusArea, setFocusArea] = useState14("");
18180
- const [isStarting, setIsStarting] = useState14(false);
18498
+ const [focusArea, setFocusArea] = useState15("");
18499
+ const [isStarting, setIsStarting] = useState15(false);
18500
+ const [error, setError] = useState15(null);
18181
18501
  const trackNames = Array.from(new Set(
18182
18502
  assignments.filter((a) => a.testCase.track?.name).map((a) => a.testCase.track.name)
18183
18503
  )).slice(0, 6);
18184
18504
  const handleStart = async () => {
18185
18505
  if (isStarting) return;
18506
+ setError(null);
18186
18507
  Keyboard2.dismiss();
18187
18508
  setIsStarting(true);
18188
18509
  try {
@@ -18191,13 +18512,17 @@ function SessionStartScreen({ nav }) {
18191
18512
  });
18192
18513
  if (result.success) {
18193
18514
  nav.replace({ name: "SESSION_ACTIVE" });
18515
+ } else {
18516
+ const msg = result.error || "Failed to start session. Please try again.";
18517
+ console.warn("BugBear: Session start failed:", msg);
18518
+ setError(msg);
18194
18519
  }
18195
18520
  } finally {
18196
18521
  setIsStarting(false);
18197
18522
  }
18198
18523
  };
18199
18524
  return /* @__PURE__ */ React18.createElement(View18, null, /* @__PURE__ */ React18.createElement(View18, { style: styles5.header }, /* @__PURE__ */ React18.createElement(Text16, { style: styles5.headerIcon }, "\u{1F50D}"), /* @__PURE__ */ React18.createElement(Text16, { style: styles5.headerDesc }, "Start an exploratory QA session. Log findings as you go \u2014 bugs, concerns, suggestions, or questions.")), /* @__PURE__ */ React18.createElement(View18, { style: styles5.inputSection }, /* @__PURE__ */ React18.createElement(Text16, { style: styles5.label }, "What are you testing?"), /* @__PURE__ */ React18.createElement(
18200
- TextInput9,
18525
+ TextInput10,
18201
18526
  {
18202
18527
  value: focusArea,
18203
18528
  onChangeText: setFocusArea,
@@ -18216,7 +18541,7 @@ function SessionStartScreen({ nav }) {
18216
18541
  activeOpacity: 0.7
18217
18542
  },
18218
18543
  /* @__PURE__ */ React18.createElement(Text16, { style: [styles5.chipText, focusArea === name && styles5.chipTextActive] }, name)
18219
- )))), /* @__PURE__ */ React18.createElement(
18544
+ )))), error && /* @__PURE__ */ React18.createElement(View18, { style: styles5.errorBox }, /* @__PURE__ */ React18.createElement(Text16, { style: styles5.errorText }, error)), /* @__PURE__ */ React18.createElement(
18220
18545
  TouchableOpacity15,
18221
18546
  {
18222
18547
  style: [styles5.startBtn, isStarting && styles5.startBtnDisabled],
@@ -18295,6 +18620,20 @@ function createStyles13() {
18295
18620
  chipTextActive: {
18296
18621
  color: colors.blueLight
18297
18622
  },
18623
+ errorBox: {
18624
+ marginBottom: 12,
18625
+ paddingVertical: 10,
18626
+ paddingHorizontal: 12,
18627
+ backgroundColor: "rgba(239, 68, 68, 0.1)",
18628
+ borderWidth: 1,
18629
+ borderColor: "rgba(239, 68, 68, 0.3)",
18630
+ borderRadius: 8
18631
+ },
18632
+ errorText: {
18633
+ fontSize: 13,
18634
+ color: "#f87171",
18635
+ lineHeight: 18
18636
+ },
18298
18637
  startBtn: {
18299
18638
  paddingVertical: 14,
18300
18639
  backgroundColor: colors.blueSurface,
@@ -18315,7 +18654,7 @@ function createStyles13() {
18315
18654
  }
18316
18655
 
18317
18656
  // src/widget/screens/SessionActiveScreen.tsx
18318
- import React19, { useState as useState15, useEffect as useEffect11, useRef as useRef5, useMemo as useMemo14 } from "react";
18657
+ import React19, { useState as useState16, useEffect as useEffect11, useRef as useRef5, useMemo as useMemo14 } from "react";
18319
18658
  import { View as View19, Text as Text17, TouchableOpacity as TouchableOpacity16, StyleSheet as StyleSheet19 } from "react-native";
18320
18659
  function SessionActiveScreen({ nav }) {
18321
18660
  const { activeSession, sessionFindings, endSession, refreshSession, widgetColorScheme } = useBugBear();
@@ -18326,8 +18665,8 @@ function SessionActiveScreen({ nav }) {
18326
18665
  suggestion: { icon: "\u{1F4A1}", label: "Suggestion", color: colors.blue },
18327
18666
  question: { icon: "\u2753", label: "Question", color: colors.violet }
18328
18667
  }), [widgetColorScheme]);
18329
- const [isEnding, setIsEnding] = useState15(false);
18330
- const [elapsed, setElapsed] = useState15(0);
18668
+ const [isEnding, setIsEnding] = useState16(false);
18669
+ const [elapsed, setElapsed] = useState16(0);
18331
18670
  const timerRef = useRef5(null);
18332
18671
  useEffect11(() => {
18333
18672
  refreshSession();
@@ -18552,8 +18891,8 @@ function createStyles14() {
18552
18891
  }
18553
18892
 
18554
18893
  // src/widget/screens/SessionFindingScreen.tsx
18555
- import React20, { useState as useState16, useMemo as useMemo15 } from "react";
18556
- import { View as View20, Text as Text18, TextInput as TextInput10, TouchableOpacity as TouchableOpacity17, StyleSheet as StyleSheet20, Keyboard as Keyboard3 } from "react-native";
18894
+ import React20, { useState as useState17, useMemo as useMemo15 } from "react";
18895
+ import { View as View20, Text as Text18, TextInput as TextInput11, TouchableOpacity as TouchableOpacity17, StyleSheet as StyleSheet20, Keyboard as Keyboard3 } from "react-native";
18557
18896
  var FINDING_TYPES = [
18558
18897
  { value: "bug", icon: "\u{1F41B}", label: "Bug" },
18559
18898
  { value: "concern", icon: "\u26A0\uFE0F", label: "Concern" },
@@ -18570,11 +18909,11 @@ function SessionFindingScreen({ nav }) {
18570
18909
  { value: "low", label: "Low", color: colors.textMuted },
18571
18910
  { value: "observation", label: "Note", color: colors.textDim }
18572
18911
  ], [widgetColorScheme]);
18573
- const [type, setType] = useState16("bug");
18574
- const [severity, setSeverity] = useState16("medium");
18575
- const [title, setTitle] = useState16("");
18576
- const [description, setDescription] = useState16("");
18577
- const [isSubmitting, setIsSubmitting] = useState16(false);
18912
+ const [type, setType] = useState17("bug");
18913
+ const [severity, setSeverity] = useState17("medium");
18914
+ const [title, setTitle] = useState17("");
18915
+ const [description, setDescription] = useState17("");
18916
+ const [isSubmitting, setIsSubmitting] = useState17(false);
18578
18917
  const handleSubmit = async () => {
18579
18918
  if (!title.trim() || isSubmitting) return;
18580
18919
  Keyboard3.dismiss();
@@ -18617,7 +18956,7 @@ function SessionFindingScreen({ nav }) {
18617
18956
  },
18618
18957
  /* @__PURE__ */ React20.createElement(Text18, { style: [styles5.severityText, severity === s2.value && { color: s2.color }] }, s2.label)
18619
18958
  )))), /* @__PURE__ */ React20.createElement(View20, { style: styles5.inputSection }, /* @__PURE__ */ React20.createElement(
18620
- TextInput10,
18959
+ TextInput11,
18621
18960
  {
18622
18961
  value: title,
18623
18962
  onChangeText: setTitle,
@@ -18627,7 +18966,7 @@ function SessionFindingScreen({ nav }) {
18627
18966
  returnKeyType: "next"
18628
18967
  }
18629
18968
  )), /* @__PURE__ */ React20.createElement(View20, { style: styles5.inputSection }, /* @__PURE__ */ React20.createElement(
18630
- TextInput10,
18969
+ TextInput11,
18631
18970
  {
18632
18971
  value: description,
18633
18972
  onChangeText: setDescription,
@@ -18772,14 +19111,31 @@ function BugBearButton({
18772
19111
  }) {
18773
19112
  const { shouldShowWidget, testerInfo, isLoading, unreadCount, assignments, widgetMode, widgetColorScheme } = useBugBear();
18774
19113
  const { currentScreen, canGoBack, push, pop, replace, reset } = useNavigation();
18775
- const [modalVisible, setModalVisible] = useState17(false);
19114
+ const [panelVisible, setPanelVisible] = useState18(false);
19115
+ const panelAnim = useRef6(new Animated2.Value(0)).current;
18776
19116
  const styles5 = useMemo16(() => createStyles16(), [widgetColorScheme]);
18777
19117
  const screenCaptureRef = useRef6(null);
18778
- const openModal = () => {
19118
+ const openPanel = () => {
18779
19119
  captureAppScreen().then((uri) => {
18780
19120
  screenCaptureRef.current = uri;
18781
19121
  });
18782
- setModalVisible(true);
19122
+ setPanelVisible(true);
19123
+ Animated2.spring(panelAnim, {
19124
+ toValue: 1,
19125
+ useNativeDriver: true,
19126
+ friction: 8,
19127
+ tension: 65
19128
+ }).start();
19129
+ };
19130
+ const closePanel = () => {
19131
+ Keyboard4.dismiss();
19132
+ Animated2.timing(panelAnim, {
19133
+ toValue: 0,
19134
+ duration: 250,
19135
+ useNativeDriver: true
19136
+ }).start(() => {
19137
+ setPanelVisible(false);
19138
+ });
18783
19139
  };
18784
19140
  const getInitialPosition = () => {
18785
19141
  const buttonSize = 56;
@@ -18830,7 +19186,7 @@ function BugBearButton({
18830
19186
  tension: 40
18831
19187
  }).start();
18832
19188
  if (!isDragging.current && Math.abs(gs.dx) < 5 && Math.abs(gs.dy) < 5) {
18833
- openModal();
19189
+ openPanel();
18834
19190
  }
18835
19191
  isDragging.current = false;
18836
19192
  }
@@ -18838,6 +19194,19 @@ function BugBearButton({
18838
19194
  ).current;
18839
19195
  const pendingTests = widgetMode === "qa" ? assignments.filter((a) => a.status === "pending" || a.status === "in_progress").length : 0;
18840
19196
  const badgeCount = pendingTests + unreadCount;
19197
+ useEffect12(() => {
19198
+ if (!panelVisible || Platform5.OS !== "android") return;
19199
+ const handler = BackHandler.addEventListener("hardwareBackPress", () => {
19200
+ if (canGoBack) {
19201
+ Keyboard4.dismiss();
19202
+ pop();
19203
+ } else {
19204
+ closePanel();
19205
+ }
19206
+ return true;
19207
+ });
19208
+ return () => handler.remove();
19209
+ }, [panelVisible, canGoBack]);
18841
19210
  if (!shouldShowWidget) return null;
18842
19211
  const getHeaderTitle = () => {
18843
19212
  switch (currentScreen.name) {
@@ -18876,8 +19245,7 @@ function BugBearButton({
18876
19245
  }
18877
19246
  };
18878
19247
  const handleClose = () => {
18879
- Keyboard4.dismiss();
18880
- setModalVisible(false);
19248
+ closePanel();
18881
19249
  };
18882
19250
  const nav = {
18883
19251
  push: (screen) => {
@@ -18949,7 +19317,7 @@ function BugBearButton({
18949
19317
  return /* @__PURE__ */ React21.createElement(HomeScreen, { nav });
18950
19318
  }
18951
19319
  };
18952
- return /* @__PURE__ */ React21.createElement(React21.Fragment, null, /* @__PURE__ */ React21.createElement(
19320
+ return /* @__PURE__ */ React21.createElement(React21.Fragment, null, !panelVisible && /* @__PURE__ */ React21.createElement(
18953
19321
  Animated2.View,
18954
19322
  {
18955
19323
  style: [styles5.fabContainer, { transform: pan.getTranslateTransform() }, buttonStyle],
@@ -18959,28 +19327,38 @@ function BugBearButton({
18959
19327
  TouchableOpacity18,
18960
19328
  {
18961
19329
  style: styles5.fab,
18962
- onPress: openModal,
19330
+ onPress: openPanel,
18963
19331
  activeOpacity: draggable ? 1 : 0.7
18964
19332
  },
18965
19333
  /* @__PURE__ */ React21.createElement(Image4, { source: { uri: BUGBEAR_LOGO_BASE64 }, style: styles5.fabIcon }),
18966
19334
  badgeCount > 0 && /* @__PURE__ */ React21.createElement(View21, { style: styles5.badge }, /* @__PURE__ */ React21.createElement(Text19, { style: styles5.badgeText }, badgeCount > 9 ? "9+" : badgeCount))
18967
19335
  )
18968
- ), /* @__PURE__ */ React21.createElement(
18969
- Modal3,
19336
+ ), panelVisible && /* @__PURE__ */ React21.createElement(View21, { style: styles5.panelWrapper, pointerEvents: "box-none" }, /* @__PURE__ */ React21.createElement(
19337
+ KeyboardAvoidingView,
18970
19338
  {
18971
- visible: modalVisible,
18972
- animationType: "slide",
18973
- transparent: true,
18974
- onRequestClose: handleClose
19339
+ behavior: Platform5.OS === "ios" ? "padding" : "height",
19340
+ style: styles5.panelKeyboardAvoid,
19341
+ pointerEvents: "box-none"
18975
19342
  },
18976
19343
  /* @__PURE__ */ React21.createElement(
18977
- KeyboardAvoidingView,
19344
+ Animated2.View,
18978
19345
  {
18979
- behavior: Platform5.OS === "ios" ? "padding" : "height",
18980
- style: styles5.modalOverlay
19346
+ style: [
19347
+ styles5.panelContainer,
19348
+ {
19349
+ transform: [{
19350
+ translateY: panelAnim.interpolate({
19351
+ inputRange: [0, 1],
19352
+ outputRange: [screenHeight, 0]
19353
+ })
19354
+ }]
19355
+ }
19356
+ ]
18981
19357
  },
18982
- /* @__PURE__ */ React21.createElement(View21, { style: styles5.modalContainer }, /* @__PURE__ */ React21.createElement(View21, { style: styles5.header }, /* @__PURE__ */ React21.createElement(View21, { style: styles5.headerLeft }, canGoBack ? /* @__PURE__ */ React21.createElement(TouchableOpacity18, { onPress: () => nav.pop(), style: styles5.backButton }, /* @__PURE__ */ React21.createElement(Text19, { style: styles5.backText }, "\u2190 Back")) : /* @__PURE__ */ React21.createElement(View21, { style: styles5.headerTitleRow }, /* @__PURE__ */ React21.createElement(Text19, { style: styles5.headerTitle }, "BugBear"), testerInfo && /* @__PURE__ */ React21.createElement(TouchableOpacity18, { onPress: () => push({ name: "PROFILE" }) }, /* @__PURE__ */ React21.createElement(Text19, { style: styles5.headerName }, testerInfo.name, " \u270E")))), getHeaderTitle() ? /* @__PURE__ */ React21.createElement(Text19, { style: styles5.headerScreenTitle, numberOfLines: 1 }, getHeaderTitle()) : null, /* @__PURE__ */ React21.createElement(View21, { style: styles5.headerActions }, currentScreen.name !== "HOME" && /* @__PURE__ */ React21.createElement(TouchableOpacity18, { onPress: () => nav.reset(), style: styles5.homeButton }, /* @__PURE__ */ React21.createElement(Text19, { style: styles5.homeIcon }, "\u2302")), /* @__PURE__ */ React21.createElement(TouchableOpacity18, { onPress: handleClose, style: styles5.closeButton }, /* @__PURE__ */ React21.createElement(Text19, { style: styles5.closeText }, "\u2715")))), /* @__PURE__ */ React21.createElement(
18983
- ScrollView3,
19358
+ /* @__PURE__ */ React21.createElement(View21, { style: styles5.panelHandle }, /* @__PURE__ */ React21.createElement(View21, { style: styles5.panelHandleBar })),
19359
+ /* @__PURE__ */ React21.createElement(View21, { style: styles5.header }, /* @__PURE__ */ React21.createElement(View21, { style: styles5.headerLeft }, canGoBack ? /* @__PURE__ */ React21.createElement(TouchableOpacity18, { onPress: () => nav.pop(), style: styles5.backButton }, /* @__PURE__ */ React21.createElement(Text19, { style: styles5.backText }, "\u2190 Back")) : /* @__PURE__ */ React21.createElement(View21, { style: styles5.headerTitleRow }, /* @__PURE__ */ React21.createElement(Text19, { style: styles5.headerTitle }, "BugBear"), testerInfo && /* @__PURE__ */ React21.createElement(TouchableOpacity18, { onPress: () => push({ name: "PROFILE" }) }, /* @__PURE__ */ React21.createElement(Text19, { style: styles5.headerName }, testerInfo.name, " \u270E")))), getHeaderTitle() ? /* @__PURE__ */ React21.createElement(Text19, { style: styles5.headerScreenTitle, numberOfLines: 1 }, getHeaderTitle()) : null, /* @__PURE__ */ React21.createElement(View21, { style: styles5.headerActions }, currentScreen.name !== "HOME" && /* @__PURE__ */ React21.createElement(TouchableOpacity18, { onPress: () => nav.reset(), style: styles5.homeButton }, /* @__PURE__ */ React21.createElement(Text19, { style: styles5.homeIcon }, "\u2302")), /* @__PURE__ */ React21.createElement(TouchableOpacity18, { onPress: handleClose, style: styles5.closeButton }, /* @__PURE__ */ React21.createElement(Text19, { style: styles5.closeText }, "\u2715")))),
19360
+ /* @__PURE__ */ React21.createElement(
19361
+ ScrollView4,
18984
19362
  {
18985
19363
  style: styles5.content,
18986
19364
  contentContainerStyle: styles5.contentContainer,
@@ -18988,9 +19366,9 @@ function BugBearButton({
18988
19366
  showsVerticalScrollIndicator: false
18989
19367
  },
18990
19368
  isLoading ? /* @__PURE__ */ React21.createElement(View21, { style: styles5.loadingContainer }, /* @__PURE__ */ React21.createElement(ActivityIndicator3, { size: "large", color: colors.blue }), /* @__PURE__ */ React21.createElement(Text19, { style: styles5.loadingText }, "Loading...")) : renderScreen()
18991
- ))
19369
+ )
18992
19370
  )
18993
- ));
19371
+ )));
18994
19372
  }
18995
19373
  function createStyles16() {
18996
19374
  return StyleSheet21.create({
@@ -19034,18 +19412,42 @@ function createStyles16() {
19034
19412
  fontSize: 11,
19035
19413
  fontWeight: "700"
19036
19414
  },
19037
- // Modal
19038
- modalOverlay: {
19415
+ // Panel (replaces Modal — no backdrop, app stays interactive)
19416
+ panelWrapper: {
19417
+ position: "absolute",
19418
+ top: 0,
19419
+ left: 0,
19420
+ right: 0,
19421
+ bottom: 0,
19422
+ zIndex: 9998,
19423
+ justifyContent: "flex-end"
19424
+ },
19425
+ panelKeyboardAvoid: {
19039
19426
  flex: 1,
19040
- justifyContent: "flex-end",
19041
- backgroundColor: "rgba(0,0,0,0.5)"
19427
+ justifyContent: "flex-end"
19042
19428
  },
19043
- modalContainer: {
19429
+ panelContainer: {
19044
19430
  backgroundColor: colors.bg,
19045
19431
  borderTopLeftRadius: 20,
19046
19432
  borderTopRightRadius: 20,
19047
- maxHeight: "85%",
19048
- minHeight: "50%"
19433
+ maxHeight: "70%",
19434
+ minHeight: "40%",
19435
+ shadowColor: colors.onBright,
19436
+ shadowOffset: { width: 0, height: -4 },
19437
+ shadowOpacity: 0.25,
19438
+ shadowRadius: 12,
19439
+ elevation: 16
19440
+ },
19441
+ panelHandle: {
19442
+ alignItems: "center",
19443
+ paddingTop: 8,
19444
+ paddingBottom: 4
19445
+ },
19446
+ panelHandleBar: {
19447
+ width: 36,
19448
+ height: 4,
19449
+ borderRadius: 2,
19450
+ backgroundColor: colors.border
19049
19451
  },
19050
19452
  // Header
19051
19453
  header: {
@@ -19128,7 +19530,7 @@ function createStyles16() {
19128
19530
  },
19129
19531
  contentContainer: {
19130
19532
  padding: 16,
19131
- paddingBottom: 32
19533
+ paddingBottom: Platform5.OS === "ios" ? 50 : 28
19132
19534
  },
19133
19535
  // Loading
19134
19536
  loadingContainer: {