@bbearai/react-native 0.8.3 → 0.8.4

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
@@ -12235,6 +12235,9 @@ var BugBearClient = class {
12235
12235
  this.monitor = null;
12236
12236
  this.initialized = false;
12237
12237
  this.initError = null;
12238
+ this._presenceSessionId = null;
12239
+ this._presenceInterval = null;
12240
+ this._presencePaused = false;
12238
12241
  this.config = config;
12239
12242
  if (config.apiKey) {
12240
12243
  this.pendingInit = this.resolveFromApiKey(config.apiKey);
@@ -13880,6 +13883,7 @@ var BugBearClient = class {
13880
13883
  lastMessageAt: row.last_message_at,
13881
13884
  createdAt: row.created_at,
13882
13885
  unreadCount: Number(row.unread_count) || 0,
13886
+ reporterName: row.reporter_name || void 0,
13883
13887
  lastMessage: row.last_message_preview ? {
13884
13888
  id: "",
13885
13889
  threadId: row.thread_id,
@@ -14319,6 +14323,93 @@ var BugBearClient = class {
14319
14323
  updatedAt: data.updated_at
14320
14324
  };
14321
14325
  }
14326
+ // ─── Passive Presence Tracking ──────────────────────────────
14327
+ /** Current presence session ID (null if not tracking). */
14328
+ get presenceSessionId() {
14329
+ return this._presenceSessionId;
14330
+ }
14331
+ /**
14332
+ * Start passive presence tracking for this tester.
14333
+ * Idempotent — reuses an existing active session if one exists.
14334
+ */
14335
+ async startPresence(platform) {
14336
+ try {
14337
+ await this.ensureReady();
14338
+ const testerInfo = await this.getTesterInfo();
14339
+ if (!testerInfo) return null;
14340
+ const { data, error } = await this.supabase.rpc("upsert_tester_presence", {
14341
+ p_project_id: this.config.projectId,
14342
+ p_tester_id: testerInfo.id,
14343
+ p_platform: platform
14344
+ });
14345
+ if (error) {
14346
+ console.error("BugBear: Failed to start presence", formatPgError(error));
14347
+ return null;
14348
+ }
14349
+ this._presenceSessionId = data;
14350
+ this._presencePaused = false;
14351
+ this.startPresenceHeartbeat();
14352
+ return data;
14353
+ } catch (err) {
14354
+ console.error("BugBear: Error starting presence", err);
14355
+ return null;
14356
+ }
14357
+ }
14358
+ /** Gracefully end the current presence session. */
14359
+ async endPresence() {
14360
+ this.stopPresenceHeartbeat();
14361
+ if (!this._presenceSessionId) return;
14362
+ try {
14363
+ await this.supabase.rpc("end_tester_presence", {
14364
+ p_session_id: this._presenceSessionId
14365
+ });
14366
+ } catch {
14367
+ }
14368
+ this._presenceSessionId = null;
14369
+ }
14370
+ /** Pause heartbeat (tab hidden / app backgrounded). Sends one final beat. */
14371
+ pausePresence() {
14372
+ this._presencePaused = true;
14373
+ this.heartbeatPresence();
14374
+ }
14375
+ /** Resume heartbeat after pause. Restarts if session was cleaned up. */
14376
+ async resumePresence() {
14377
+ if (!this._presenceSessionId) return;
14378
+ this._presencePaused = false;
14379
+ try {
14380
+ const { data } = await this.supabase.rpc("heartbeat_tester_presence", {
14381
+ p_session_id: this._presenceSessionId
14382
+ });
14383
+ if (!data) {
14384
+ this._presenceSessionId = null;
14385
+ }
14386
+ } catch {
14387
+ this._presenceSessionId = null;
14388
+ }
14389
+ }
14390
+ async heartbeatPresence() {
14391
+ if (!this._presenceSessionId || this._presencePaused) return;
14392
+ try {
14393
+ const { data, error } = await this.supabase.rpc("heartbeat_tester_presence", {
14394
+ p_session_id: this._presenceSessionId
14395
+ });
14396
+ if (error || data === false) {
14397
+ this.stopPresenceHeartbeat();
14398
+ this._presenceSessionId = null;
14399
+ }
14400
+ } catch {
14401
+ }
14402
+ }
14403
+ startPresenceHeartbeat() {
14404
+ this.stopPresenceHeartbeat();
14405
+ this._presenceInterval = setInterval(() => this.heartbeatPresence(), 6e4);
14406
+ }
14407
+ stopPresenceHeartbeat() {
14408
+ if (this._presenceInterval) {
14409
+ clearInterval(this._presenceInterval);
14410
+ this._presenceInterval = null;
14411
+ }
14412
+ }
14322
14413
  };
14323
14414
  function createBugBear(config) {
14324
14415
  return new BugBearClient(config);
@@ -14453,6 +14544,9 @@ function setActiveColors(palette) {
14453
14544
  colors = palette;
14454
14545
  shared = createSharedStyles();
14455
14546
  }
14547
+ function withAlpha(hex, alpha) {
14548
+ return hex + Math.round(alpha * 255).toString(16).padStart(2, "0");
14549
+ }
14456
14550
  function createSharedStyles() {
14457
14551
  return import_react_native.StyleSheet.create({
14458
14552
  card: {
@@ -14955,6 +15049,28 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
14955
15049
  }
14956
15050
  return () => subscription.remove();
14957
15051
  }, [client]);
15052
+ (0, import_react.useEffect)(() => {
15053
+ if (!client || !isTester) return;
15054
+ let mounted = true;
15055
+ const platform = import_react_native2.Platform.OS === "ios" ? "ios" : "android";
15056
+ client.startPresence(platform);
15057
+ const subscription = import_react_native2.AppState.addEventListener("change", (nextState) => {
15058
+ if (nextState === "active") {
15059
+ client.resumePresence().then(() => {
15060
+ if (mounted && !client.presenceSessionId) {
15061
+ client.startPresence(platform);
15062
+ }
15063
+ });
15064
+ } else if (nextState === "background" || nextState === "inactive") {
15065
+ client.pausePresence();
15066
+ }
15067
+ });
15068
+ return () => {
15069
+ mounted = false;
15070
+ subscription.remove();
15071
+ client.endPresence();
15072
+ };
15073
+ }, [client, isTester]);
14958
15074
  (0, import_react.useEffect)(() => {
14959
15075
  if (!client || !isTester) return;
14960
15076
  if (widgetMode === "qa" && !isQAEnabled) return;
@@ -16952,6 +17068,8 @@ function ReportScreen({ nav, prefill, autoCaptureUri, onAutoCaptureConsumed }) {
16952
17068
  const [severity, setSeverity] = (0, import_react12.useState)("medium");
16953
17069
  const [category, setCategory] = (0, import_react12.useState)(null);
16954
17070
  const [description, setDescription] = (0, import_react12.useState)("");
17071
+ const [myIssues, setMyIssues] = (0, import_react12.useState)([]);
17072
+ const [similarReports, setSimilarReports] = (0, import_react12.useState)([]);
16955
17073
  const [affectedScreen, setAffectedScreen] = (0, import_react12.useState)("");
16956
17074
  const [submitting, setSubmitting] = (0, import_react12.useState)(false);
16957
17075
  const [error, setError] = (0, import_react12.useState)(null);
@@ -16967,6 +17085,41 @@ function ReportScreen({ nav, prefill, autoCaptureUri, onAutoCaptureConsumed }) {
16967
17085
  }, [autoCaptureUri, onAutoCaptureConsumed]);
16968
17086
  const isRetestFailure = prefill?.type === "test_fail";
16969
17087
  const isBugType = reportType === "bug" || reportType === "test_fail";
17088
+ (0, import_react12.useEffect)(() => {
17089
+ if (!client?.getIssues) return;
17090
+ let cancelled = false;
17091
+ const load = async () => {
17092
+ try {
17093
+ const [open, done] = await Promise.all([
17094
+ client.getIssues("open"),
17095
+ client.getIssues("done")
17096
+ ]);
17097
+ if (!cancelled) setMyIssues([...open, ...done]);
17098
+ } catch {
17099
+ }
17100
+ };
17101
+ load();
17102
+ return () => {
17103
+ cancelled = true;
17104
+ };
17105
+ }, [client]);
17106
+ (0, import_react12.useEffect)(() => {
17107
+ if (description.length < 10 || myIssues.length === 0) {
17108
+ setSimilarReports([]);
17109
+ return;
17110
+ }
17111
+ const words = description.toLowerCase().split(/\s+/).filter((w) => w.length > 3);
17112
+ if (words.length === 0) {
17113
+ setSimilarReports([]);
17114
+ return;
17115
+ }
17116
+ const scored = myIssues.map((issue) => {
17117
+ const text = `${issue.title || ""} ${issue.description || ""}`.toLowerCase();
17118
+ const matches = words.filter((w) => text.includes(w)).length;
17119
+ return { issue, score: matches / words.length };
17120
+ }).filter((s2) => s2.score >= 0.3).sort((a, b) => b.score - a.score).slice(0, 3);
17121
+ setSimilarReports(scored.map((s2) => s2.issue));
17122
+ }, [description, myIssues]);
16970
17123
  (0, import_react12.useEffect)(() => {
16971
17124
  if (reportType === "feedback" || reportType === "suggestion") {
16972
17125
  setCategory("other");
@@ -17041,7 +17194,41 @@ function ReportScreen({ nav, prefill, autoCaptureUri, onAutoCaptureConsumed }) {
17041
17194
  numberOfLines: 4,
17042
17195
  textAlignVertical: "top"
17043
17196
  }
17044
- )), /* @__PURE__ */ import_react12.default.createElement(
17197
+ ), similarReports.length > 0 && /* @__PURE__ */ import_react12.default.createElement(import_react_native11.View, { style: {
17198
+ marginTop: 8,
17199
+ padding: 12,
17200
+ borderRadius: 8,
17201
+ backgroundColor: withAlpha(colors.orange, 0.1),
17202
+ borderWidth: 1,
17203
+ borderColor: withAlpha(colors.orange, 0.3)
17204
+ } }, /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: { fontSize: 12, fontWeight: "600", color: colors.orange, marginBottom: 8 } }, "Similar reports you've already filed:"), similarReports.map((issue) => /* @__PURE__ */ import_react12.default.createElement(
17205
+ import_react_native11.TouchableOpacity,
17206
+ {
17207
+ key: issue.id,
17208
+ onPress: () => nav.push({ name: "ISSUE_DETAIL", issue }),
17209
+ style: {
17210
+ flexDirection: "row",
17211
+ alignItems: "center",
17212
+ gap: 8,
17213
+ paddingVertical: 6
17214
+ }
17215
+ },
17216
+ /* @__PURE__ */ import_react12.default.createElement(import_react_native11.View, { style: {
17217
+ width: 8,
17218
+ height: 8,
17219
+ borderRadius: 4,
17220
+ backgroundColor: issue.severity === "critical" ? colors.red : issue.severity === "high" ? colors.orange : colors.yellow
17221
+ } }),
17222
+ /* @__PURE__ */ import_react12.default.createElement(
17223
+ import_react_native11.Text,
17224
+ {
17225
+ numberOfLines: 1,
17226
+ style: { fontSize: 12, color: colors.textPrimary, flex: 1 }
17227
+ },
17228
+ issue.title
17229
+ ),
17230
+ /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: { fontSize: 11, color: colors.textMuted } }, issue.status)
17231
+ )))), /* @__PURE__ */ import_react12.default.createElement(
17045
17232
  ImagePickerButtons,
17046
17233
  {
17047
17234
  images: images.images,
@@ -17097,7 +17284,41 @@ function ReportScreen({ nav, prefill, autoCaptureUri, onAutoCaptureConsumed }) {
17097
17284
  numberOfLines: 4,
17098
17285
  textAlignVertical: "top"
17099
17286
  }
17100
- )), isBugType && /* @__PURE__ */ import_react12.default.createElement(import_react_native11.View, { style: styles5.section }, /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: shared.label }, "Which screen?"), /* @__PURE__ */ import_react12.default.createElement(
17287
+ ), similarReports.length > 0 && /* @__PURE__ */ import_react12.default.createElement(import_react_native11.View, { style: {
17288
+ marginTop: 8,
17289
+ padding: 12,
17290
+ borderRadius: 8,
17291
+ backgroundColor: withAlpha(colors.orange, 0.1),
17292
+ borderWidth: 1,
17293
+ borderColor: withAlpha(colors.orange, 0.3)
17294
+ } }, /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: { fontSize: 12, fontWeight: "600", color: colors.orange, marginBottom: 8 } }, "Similar reports you've already filed:"), similarReports.map((issue) => /* @__PURE__ */ import_react12.default.createElement(
17295
+ import_react_native11.TouchableOpacity,
17296
+ {
17297
+ key: issue.id,
17298
+ onPress: () => nav.push({ name: "ISSUE_DETAIL", issue }),
17299
+ style: {
17300
+ flexDirection: "row",
17301
+ alignItems: "center",
17302
+ gap: 8,
17303
+ paddingVertical: 6
17304
+ }
17305
+ },
17306
+ /* @__PURE__ */ import_react12.default.createElement(import_react_native11.View, { style: {
17307
+ width: 8,
17308
+ height: 8,
17309
+ borderRadius: 4,
17310
+ backgroundColor: issue.severity === "critical" ? colors.red : issue.severity === "high" ? colors.orange : colors.yellow
17311
+ } }),
17312
+ /* @__PURE__ */ import_react12.default.createElement(
17313
+ import_react_native11.Text,
17314
+ {
17315
+ numberOfLines: 1,
17316
+ style: { fontSize: 12, color: colors.textPrimary, flex: 1 }
17317
+ },
17318
+ issue.title
17319
+ ),
17320
+ /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: { fontSize: 11, color: colors.textMuted } }, issue.status)
17321
+ )))), isBugType && /* @__PURE__ */ import_react12.default.createElement(import_react_native11.View, { style: styles5.section }, /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: shared.label }, "Which screen?"), /* @__PURE__ */ import_react12.default.createElement(
17101
17322
  import_react_native11.TextInput,
17102
17323
  {
17103
17324
  style: styles5.screenInput,
@@ -17178,6 +17399,19 @@ var import_react_native13 = require("react-native");
17178
17399
  function MessageListScreen({ nav }) {
17179
17400
  const { threads, unreadCount, refreshThreads, dashboardUrl, isLoading, widgetColorScheme } = useBugBear();
17180
17401
  const styles5 = (0, import_react14.useMemo)(() => createStyles7(), [widgetColorScheme]);
17402
+ const [activeFilter, setActiveFilter] = (0, import_react14.useState)("all");
17403
+ const filteredThreads = threads.filter((thread) => {
17404
+ if (activeFilter === "all") return true;
17405
+ if (activeFilter === "unread") return thread.unreadCount > 0;
17406
+ return thread.threadType === activeFilter;
17407
+ });
17408
+ const filterChips = [
17409
+ { key: "all", label: "All" },
17410
+ { key: "report", label: "Bug Reports" },
17411
+ { key: "direct", label: "Direct" },
17412
+ { key: "announcement", label: "Announcements" },
17413
+ { key: "unread", label: "Unread", count: threads.filter((t) => t.unreadCount > 0).length }
17414
+ ];
17181
17415
  if (isLoading) return /* @__PURE__ */ import_react14.default.createElement(MessageListScreenSkeleton, null);
17182
17416
  return /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, null, /* @__PURE__ */ import_react14.default.createElement(
17183
17417
  import_react_native13.TouchableOpacity,
@@ -17186,14 +17420,54 @@ function MessageListScreen({ nav }) {
17186
17420
  onPress: () => nav.push({ name: "COMPOSE_MESSAGE" })
17187
17421
  },
17188
17422
  /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.newMsgText }, "\u2709\uFE0F New Message")
17189
- ), 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(
17423
+ ), /* @__PURE__ */ import_react14.default.createElement(
17424
+ import_react_native13.ScrollView,
17425
+ {
17426
+ horizontal: true,
17427
+ showsHorizontalScrollIndicator: false,
17428
+ style: { paddingBottom: 12 },
17429
+ contentContainerStyle: { paddingHorizontal: 16, gap: 8 }
17430
+ },
17431
+ filterChips.map((chip) => /* @__PURE__ */ import_react14.default.createElement(
17432
+ import_react_native13.TouchableOpacity,
17433
+ {
17434
+ key: chip.key,
17435
+ onPress: () => setActiveFilter(chip.key),
17436
+ style: {
17437
+ paddingVertical: 6,
17438
+ paddingHorizontal: 12,
17439
+ borderRadius: 16,
17440
+ borderWidth: 1,
17441
+ borderColor: activeFilter === chip.key ? colors.blue : colors.border,
17442
+ backgroundColor: activeFilter === chip.key ? withAlpha(colors.blue, 0.15) : "transparent",
17443
+ flexDirection: "row",
17444
+ alignItems: "center",
17445
+ gap: 4
17446
+ }
17447
+ },
17448
+ /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: {
17449
+ fontSize: 12,
17450
+ fontWeight: "500",
17451
+ color: activeFilter === chip.key ? colors.blue : colors.textSecondary
17452
+ } }, chip.label),
17453
+ chip.count !== void 0 && chip.count > 0 && /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: {
17454
+ backgroundColor: colors.blue,
17455
+ borderRadius: 8,
17456
+ minWidth: 16,
17457
+ height: 16,
17458
+ justifyContent: "center",
17459
+ alignItems: "center",
17460
+ paddingHorizontal: 4
17461
+ } }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: { fontSize: 10, fontWeight: "bold", color: colors.onPrimary } }, chip.count))
17462
+ ))
17463
+ ), filteredThreads.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, filteredThreads.map((thread) => /* @__PURE__ */ import_react14.default.createElement(
17190
17464
  import_react_native13.TouchableOpacity,
17191
17465
  {
17192
17466
  key: thread.id,
17193
17467
  style: [styles5.threadItem, thread.unreadCount > 0 && styles5.threadItemUnread],
17194
17468
  onPress: () => nav.push({ name: "THREAD_DETAIL", thread })
17195
17469
  },
17196
- /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles5.threadLeft }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.threadIcon }, getThreadTypeIcon(thread.threadType)), /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles5.threadInfo }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles5.threadTitleRow }, thread.isPinned && /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.pinIcon }, "\u{1F4CC}"), /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.threadSubject, numberOfLines: 1 }, thread.subject || "No subject")), thread.lastMessage && /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.threadPreview, numberOfLines: 1 }, thread.lastMessage.senderName, ": ", thread.lastMessage.content))),
17470
+ /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles5.threadLeft }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.threadIcon }, getThreadTypeIcon(thread.threadType)), /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles5.threadInfo }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles5.threadTitleRow }, thread.isPinned && /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.pinIcon }, "\u{1F4CC}"), /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.threadSubject, numberOfLines: 1 }, thread.subject || "No subject")), thread.threadType === "report" && thread.reporterName && /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: { fontSize: 11, color: colors.textMuted } }, "Reported by: ", thread.reporterName), thread.lastMessage && /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.threadPreview, numberOfLines: 1 }, thread.lastMessage.senderName, ": ", thread.lastMessage.content))),
17197
17471
  /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles5.threadRight }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.threadTime }, formatRelativeTime(thread.lastMessageAt)), thread.unreadCount > 0 && /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles5.unreadBadge }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.unreadText }, thread.unreadCount)), thread.priority !== "normal" && /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: [styles5.priorityDot, { backgroundColor: getPriorityColor(thread.priority) }] }))
17198
17472
  ))), dashboardUrl && /* @__PURE__ */ import_react14.default.createElement(
17199
17473
  import_react_native13.TouchableOpacity,
@@ -17203,7 +17477,7 @@ function MessageListScreen({ nav }) {
17203
17477
  activeOpacity: 0.7
17204
17478
  },
17205
17479
  /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.dashboardLinkText }, "\u{1F310}", " View on Dashboard ", "\u2192")
17206
- ), /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles5.footer }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.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: styles5.refreshText }, "\u21BB Refresh"))));
17480
+ ), /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles5.footer }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.footerText }, filteredThreads.length, " thread", filteredThreads.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: styles5.refreshText }, "\u21BB Refresh"))));
17207
17481
  }
17208
17482
  function createStyles7() {
17209
17483
  return import_react_native13.StyleSheet.create({
@@ -17584,7 +17858,17 @@ function IssueListScreen({ nav, category }) {
17584
17858
  const [loading, setLoading] = (0, import_react18.useState)(true);
17585
17859
  const [counts, setCounts] = (0, import_react18.useState)(null);
17586
17860
  const [sortMode, setSortMode] = (0, import_react18.useState)("severity");
17861
+ const [searchQuery, setSearchQuery] = (0, import_react18.useState)("");
17862
+ const [debouncedQuery, setDebouncedQuery] = (0, import_react18.useState)("");
17587
17863
  const config = CATEGORY_CONFIG[activeCategory];
17864
+ (0, import_react18.useEffect)(() => {
17865
+ const timer = setTimeout(() => setDebouncedQuery(searchQuery), 300);
17866
+ return () => clearTimeout(timer);
17867
+ }, [searchQuery]);
17868
+ (0, import_react18.useEffect)(() => {
17869
+ setSearchQuery("");
17870
+ setDebouncedQuery("");
17871
+ }, [activeCategory]);
17588
17872
  (0, import_react18.useEffect)(() => {
17589
17873
  if (!client) return;
17590
17874
  client.getIssueCounts().then(setCounts).catch(() => {
@@ -17624,6 +17908,9 @@ function IssueListScreen({ nav, category }) {
17624
17908
  }
17625
17909
  return sorted;
17626
17910
  }, [issues, sortMode]);
17911
+ const searchFilteredIssues = debouncedQuery ? sortedIssues.filter(
17912
+ (issue) => (issue.title || "").toLowerCase().includes(debouncedQuery.toLowerCase()) || (issue.description || "").toLowerCase().includes(debouncedQuery.toLowerCase())
17913
+ ) : sortedIssues;
17627
17914
  return /* @__PURE__ */ import_react18.default.createElement(import_react_native17.View, null, /* @__PURE__ */ import_react18.default.createElement(import_react_native17.View, { style: styles5.tabBar }, CATEGORIES.map((cat) => {
17628
17915
  const catConfig = CATEGORY_CONFIG[cat];
17629
17916
  const isActive = activeCategory === cat;
@@ -17670,7 +17957,25 @@ function IssueListScreen({ nav, category }) {
17670
17957
  styles5.sortBtnText,
17671
17958
  sortMode === s2.key && styles5.sortBtnTextActive
17672
17959
  ] }, s2.label)
17673
- ))), loading ? /* @__PURE__ */ import_react18.default.createElement(IssueListScreenSkeleton, null) : sortedIssues.length === 0 ? /* @__PURE__ */ import_react18.default.createElement(import_react_native17.View, { style: styles5.emptyContainer }, /* @__PURE__ */ import_react18.default.createElement(import_react_native17.Text, { style: styles5.emptyIcon }, config.emptyIcon), /* @__PURE__ */ import_react18.default.createElement(import_react_native17.Text, { style: styles5.emptyText }, config.emptyText)) : sortedIssues.map((issue) => /* @__PURE__ */ import_react18.default.createElement(
17960
+ ))), /* @__PURE__ */ import_react18.default.createElement(import_react_native17.View, { style: { paddingHorizontal: 16, paddingBottom: 12 } }, /* @__PURE__ */ import_react18.default.createElement(
17961
+ import_react_native17.TextInput,
17962
+ {
17963
+ value: searchQuery,
17964
+ onChangeText: setSearchQuery,
17965
+ placeholder: "Search my reports...",
17966
+ placeholderTextColor: colors.textMuted,
17967
+ style: {
17968
+ padding: 8,
17969
+ paddingHorizontal: 12,
17970
+ borderRadius: 8,
17971
+ borderWidth: 1,
17972
+ borderColor: colors.border,
17973
+ backgroundColor: colors.card,
17974
+ color: colors.textPrimary,
17975
+ fontSize: 13
17976
+ }
17977
+ }
17978
+ )), loading ? /* @__PURE__ */ import_react18.default.createElement(IssueListScreenSkeleton, null) : searchFilteredIssues.length === 0 ? /* @__PURE__ */ import_react18.default.createElement(import_react_native17.View, { style: styles5.emptyContainer }, /* @__PURE__ */ import_react18.default.createElement(import_react_native17.Text, { style: styles5.emptyIcon }, debouncedQuery ? "\u{1F50D}" : config.emptyIcon), /* @__PURE__ */ import_react18.default.createElement(import_react_native17.Text, { style: styles5.emptyText }, debouncedQuery ? "No matching issues" : config.emptyText)) : searchFilteredIssues.map((issue) => /* @__PURE__ */ import_react18.default.createElement(
17674
17979
  import_react_native17.TouchableOpacity,
17675
17980
  {
17676
17981
  key: issue.id,
@@ -18196,11 +18501,13 @@ function SessionStartScreen({ nav }) {
18196
18501
  const styles5 = (0, import_react20.useMemo)(() => createStyles13(), [widgetColorScheme]);
18197
18502
  const [focusArea, setFocusArea] = (0, import_react20.useState)("");
18198
18503
  const [isStarting, setIsStarting] = (0, import_react20.useState)(false);
18504
+ const [error, setError] = (0, import_react20.useState)(null);
18199
18505
  const trackNames = Array.from(new Set(
18200
18506
  assignments.filter((a) => a.testCase.track?.name).map((a) => a.testCase.track.name)
18201
18507
  )).slice(0, 6);
18202
18508
  const handleStart = async () => {
18203
18509
  if (isStarting) return;
18510
+ setError(null);
18204
18511
  import_react_native19.Keyboard.dismiss();
18205
18512
  setIsStarting(true);
18206
18513
  try {
@@ -18209,6 +18516,10 @@ function SessionStartScreen({ nav }) {
18209
18516
  });
18210
18517
  if (result.success) {
18211
18518
  nav.replace({ name: "SESSION_ACTIVE" });
18519
+ } else {
18520
+ const msg = result.error || "Failed to start session. Please try again.";
18521
+ console.warn("BugBear: Session start failed:", msg);
18522
+ setError(msg);
18212
18523
  }
18213
18524
  } finally {
18214
18525
  setIsStarting(false);
@@ -18234,7 +18545,7 @@ function SessionStartScreen({ nav }) {
18234
18545
  activeOpacity: 0.7
18235
18546
  },
18236
18547
  /* @__PURE__ */ import_react20.default.createElement(import_react_native19.Text, { style: [styles5.chipText, focusArea === name && styles5.chipTextActive] }, name)
18237
- )))), /* @__PURE__ */ import_react20.default.createElement(
18548
+ )))), error && /* @__PURE__ */ import_react20.default.createElement(import_react_native19.View, { style: styles5.errorBox }, /* @__PURE__ */ import_react20.default.createElement(import_react_native19.Text, { style: styles5.errorText }, error)), /* @__PURE__ */ import_react20.default.createElement(
18238
18549
  import_react_native19.TouchableOpacity,
18239
18550
  {
18240
18551
  style: [styles5.startBtn, isStarting && styles5.startBtnDisabled],
@@ -18313,6 +18624,20 @@ function createStyles13() {
18313
18624
  chipTextActive: {
18314
18625
  color: colors.blueLight
18315
18626
  },
18627
+ errorBox: {
18628
+ marginBottom: 12,
18629
+ paddingVertical: 10,
18630
+ paddingHorizontal: 12,
18631
+ backgroundColor: "rgba(239, 68, 68, 0.1)",
18632
+ borderWidth: 1,
18633
+ borderColor: "rgba(239, 68, 68, 0.3)",
18634
+ borderRadius: 8
18635
+ },
18636
+ errorText: {
18637
+ fontSize: 13,
18638
+ color: "#f87171",
18639
+ lineHeight: 18
18640
+ },
18316
18641
  startBtn: {
18317
18642
  paddingVertical: 14,
18318
18643
  backgroundColor: colors.blueSurface,
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);
@@ -13847,6 +13850,7 @@ var BugBearClient = class {
13847
13850
  lastMessageAt: row.last_message_at,
13848
13851
  createdAt: row.created_at,
13849
13852
  unreadCount: Number(row.unread_count) || 0,
13853
+ reporterName: row.reporter_name || void 0,
13850
13854
  lastMessage: row.last_message_preview ? {
13851
13855
  id: "",
13852
13856
  threadId: row.thread_id,
@@ -14286,6 +14290,93 @@ var BugBearClient = class {
14286
14290
  updatedAt: data.updated_at
14287
14291
  };
14288
14292
  }
14293
+ // ─── Passive Presence Tracking ──────────────────────────────
14294
+ /** Current presence session ID (null if not tracking). */
14295
+ get presenceSessionId() {
14296
+ return this._presenceSessionId;
14297
+ }
14298
+ /**
14299
+ * Start passive presence tracking for this tester.
14300
+ * Idempotent — reuses an existing active session if one exists.
14301
+ */
14302
+ async startPresence(platform) {
14303
+ try {
14304
+ await this.ensureReady();
14305
+ const testerInfo = await this.getTesterInfo();
14306
+ if (!testerInfo) return null;
14307
+ const { data, error } = await this.supabase.rpc("upsert_tester_presence", {
14308
+ p_project_id: this.config.projectId,
14309
+ p_tester_id: testerInfo.id,
14310
+ p_platform: platform
14311
+ });
14312
+ if (error) {
14313
+ console.error("BugBear: Failed to start presence", formatPgError(error));
14314
+ return null;
14315
+ }
14316
+ this._presenceSessionId = data;
14317
+ this._presencePaused = false;
14318
+ this.startPresenceHeartbeat();
14319
+ return data;
14320
+ } catch (err) {
14321
+ console.error("BugBear: Error starting presence", err);
14322
+ return null;
14323
+ }
14324
+ }
14325
+ /** Gracefully end the current presence session. */
14326
+ async endPresence() {
14327
+ this.stopPresenceHeartbeat();
14328
+ if (!this._presenceSessionId) return;
14329
+ try {
14330
+ await this.supabase.rpc("end_tester_presence", {
14331
+ p_session_id: this._presenceSessionId
14332
+ });
14333
+ } catch {
14334
+ }
14335
+ this._presenceSessionId = null;
14336
+ }
14337
+ /** Pause heartbeat (tab hidden / app backgrounded). Sends one final beat. */
14338
+ pausePresence() {
14339
+ this._presencePaused = true;
14340
+ this.heartbeatPresence();
14341
+ }
14342
+ /** Resume heartbeat after pause. Restarts if session was cleaned up. */
14343
+ async resumePresence() {
14344
+ if (!this._presenceSessionId) return;
14345
+ this._presencePaused = false;
14346
+ try {
14347
+ const { data } = await this.supabase.rpc("heartbeat_tester_presence", {
14348
+ p_session_id: this._presenceSessionId
14349
+ });
14350
+ if (!data) {
14351
+ this._presenceSessionId = null;
14352
+ }
14353
+ } catch {
14354
+ this._presenceSessionId = null;
14355
+ }
14356
+ }
14357
+ async heartbeatPresence() {
14358
+ if (!this._presenceSessionId || this._presencePaused) return;
14359
+ try {
14360
+ const { data, error } = await this.supabase.rpc("heartbeat_tester_presence", {
14361
+ p_session_id: this._presenceSessionId
14362
+ });
14363
+ if (error || data === false) {
14364
+ this.stopPresenceHeartbeat();
14365
+ this._presenceSessionId = null;
14366
+ }
14367
+ } catch {
14368
+ }
14369
+ }
14370
+ startPresenceHeartbeat() {
14371
+ this.stopPresenceHeartbeat();
14372
+ this._presenceInterval = setInterval(() => this.heartbeatPresence(), 6e4);
14373
+ }
14374
+ stopPresenceHeartbeat() {
14375
+ if (this._presenceInterval) {
14376
+ clearInterval(this._presenceInterval);
14377
+ this._presenceInterval = null;
14378
+ }
14379
+ }
14289
14380
  };
14290
14381
  function createBugBear(config) {
14291
14382
  return new BugBearClient(config);
@@ -14420,6 +14511,9 @@ function setActiveColors(palette) {
14420
14511
  colors = palette;
14421
14512
  shared = createSharedStyles();
14422
14513
  }
14514
+ function withAlpha(hex, alpha) {
14515
+ return hex + Math.round(alpha * 255).toString(16).padStart(2, "0");
14516
+ }
14423
14517
  function createSharedStyles() {
14424
14518
  return StyleSheet.create({
14425
14519
  card: {
@@ -14922,6 +15016,28 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
14922
15016
  }
14923
15017
  return () => subscription.remove();
14924
15018
  }, [client]);
15019
+ useEffect(() => {
15020
+ if (!client || !isTester) return;
15021
+ let mounted = true;
15022
+ const platform = Platform2.OS === "ios" ? "ios" : "android";
15023
+ client.startPresence(platform);
15024
+ const subscription = AppState.addEventListener("change", (nextState) => {
15025
+ if (nextState === "active") {
15026
+ client.resumePresence().then(() => {
15027
+ if (mounted && !client.presenceSessionId) {
15028
+ client.startPresence(platform);
15029
+ }
15030
+ });
15031
+ } else if (nextState === "background" || nextState === "inactive") {
15032
+ client.pausePresence();
15033
+ }
15034
+ });
15035
+ return () => {
15036
+ mounted = false;
15037
+ subscription.remove();
15038
+ client.endPresence();
15039
+ };
15040
+ }, [client, isTester]);
14925
15041
  useEffect(() => {
14926
15042
  if (!client || !isTester) return;
14927
15043
  if (widgetMode === "qa" && !isQAEnabled) return;
@@ -15017,14 +15133,14 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
15017
15133
  }
15018
15134
 
15019
15135
  // src/BugBearButton.tsx
15020
- import React21, { useState as useState17, useRef as useRef6, useMemo as useMemo16 } from "react";
15136
+ import React21, { useState as useState18, useRef as useRef6, useMemo as useMemo16 } from "react";
15021
15137
  import {
15022
15138
  View as View21,
15023
15139
  Text as Text19,
15024
15140
  Image as Image4,
15025
15141
  TouchableOpacity as TouchableOpacity18,
15026
15142
  Modal as Modal3,
15027
- ScrollView as ScrollView3,
15143
+ ScrollView as ScrollView4,
15028
15144
  StyleSheet as StyleSheet21,
15029
15145
  Dimensions as Dimensions2,
15030
15146
  KeyboardAvoidingView,
@@ -16934,6 +17050,8 @@ function ReportScreen({ nav, prefill, autoCaptureUri, onAutoCaptureConsumed }) {
16934
17050
  const [severity, setSeverity] = useState8("medium");
16935
17051
  const [category, setCategory] = useState8(null);
16936
17052
  const [description, setDescription] = useState8("");
17053
+ const [myIssues, setMyIssues] = useState8([]);
17054
+ const [similarReports, setSimilarReports] = useState8([]);
16937
17055
  const [affectedScreen, setAffectedScreen] = useState8("");
16938
17056
  const [submitting, setSubmitting] = useState8(false);
16939
17057
  const [error, setError] = useState8(null);
@@ -16949,6 +17067,41 @@ function ReportScreen({ nav, prefill, autoCaptureUri, onAutoCaptureConsumed }) {
16949
17067
  }, [autoCaptureUri, onAutoCaptureConsumed]);
16950
17068
  const isRetestFailure = prefill?.type === "test_fail";
16951
17069
  const isBugType = reportType === "bug" || reportType === "test_fail";
17070
+ useEffect6(() => {
17071
+ if (!client?.getIssues) return;
17072
+ let cancelled = false;
17073
+ const load = async () => {
17074
+ try {
17075
+ const [open, done] = await Promise.all([
17076
+ client.getIssues("open"),
17077
+ client.getIssues("done")
17078
+ ]);
17079
+ if (!cancelled) setMyIssues([...open, ...done]);
17080
+ } catch {
17081
+ }
17082
+ };
17083
+ load();
17084
+ return () => {
17085
+ cancelled = true;
17086
+ };
17087
+ }, [client]);
17088
+ useEffect6(() => {
17089
+ if (description.length < 10 || myIssues.length === 0) {
17090
+ setSimilarReports([]);
17091
+ return;
17092
+ }
17093
+ const words = description.toLowerCase().split(/\s+/).filter((w) => w.length > 3);
17094
+ if (words.length === 0) {
17095
+ setSimilarReports([]);
17096
+ return;
17097
+ }
17098
+ const scored = myIssues.map((issue) => {
17099
+ const text = `${issue.title || ""} ${issue.description || ""}`.toLowerCase();
17100
+ const matches = words.filter((w) => text.includes(w)).length;
17101
+ return { issue, score: matches / words.length };
17102
+ }).filter((s2) => s2.score >= 0.3).sort((a, b) => b.score - a.score).slice(0, 3);
17103
+ setSimilarReports(scored.map((s2) => s2.issue));
17104
+ }, [description, myIssues]);
16952
17105
  useEffect6(() => {
16953
17106
  if (reportType === "feedback" || reportType === "suggestion") {
16954
17107
  setCategory("other");
@@ -17023,7 +17176,41 @@ function ReportScreen({ nav, prefill, autoCaptureUri, onAutoCaptureConsumed }) {
17023
17176
  numberOfLines: 4,
17024
17177
  textAlignVertical: "top"
17025
17178
  }
17026
- )), /* @__PURE__ */ React10.createElement(
17179
+ ), similarReports.length > 0 && /* @__PURE__ */ React10.createElement(View10, { style: {
17180
+ marginTop: 8,
17181
+ padding: 12,
17182
+ borderRadius: 8,
17183
+ backgroundColor: withAlpha(colors.orange, 0.1),
17184
+ borderWidth: 1,
17185
+ borderColor: withAlpha(colors.orange, 0.3)
17186
+ } }, /* @__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(
17187
+ TouchableOpacity8,
17188
+ {
17189
+ key: issue.id,
17190
+ onPress: () => nav.push({ name: "ISSUE_DETAIL", issue }),
17191
+ style: {
17192
+ flexDirection: "row",
17193
+ alignItems: "center",
17194
+ gap: 8,
17195
+ paddingVertical: 6
17196
+ }
17197
+ },
17198
+ /* @__PURE__ */ React10.createElement(View10, { style: {
17199
+ width: 8,
17200
+ height: 8,
17201
+ borderRadius: 4,
17202
+ backgroundColor: issue.severity === "critical" ? colors.red : issue.severity === "high" ? colors.orange : colors.yellow
17203
+ } }),
17204
+ /* @__PURE__ */ React10.createElement(
17205
+ Text8,
17206
+ {
17207
+ numberOfLines: 1,
17208
+ style: { fontSize: 12, color: colors.textPrimary, flex: 1 }
17209
+ },
17210
+ issue.title
17211
+ ),
17212
+ /* @__PURE__ */ React10.createElement(Text8, { style: { fontSize: 11, color: colors.textMuted } }, issue.status)
17213
+ )))), /* @__PURE__ */ React10.createElement(
17027
17214
  ImagePickerButtons,
17028
17215
  {
17029
17216
  images: images.images,
@@ -17079,7 +17266,41 @@ function ReportScreen({ nav, prefill, autoCaptureUri, onAutoCaptureConsumed }) {
17079
17266
  numberOfLines: 4,
17080
17267
  textAlignVertical: "top"
17081
17268
  }
17082
- )), isBugType && /* @__PURE__ */ React10.createElement(View10, { style: styles5.section }, /* @__PURE__ */ React10.createElement(Text8, { style: shared.label }, "Which screen?"), /* @__PURE__ */ React10.createElement(
17269
+ ), similarReports.length > 0 && /* @__PURE__ */ React10.createElement(View10, { style: {
17270
+ marginTop: 8,
17271
+ padding: 12,
17272
+ borderRadius: 8,
17273
+ backgroundColor: withAlpha(colors.orange, 0.1),
17274
+ borderWidth: 1,
17275
+ borderColor: withAlpha(colors.orange, 0.3)
17276
+ } }, /* @__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(
17277
+ TouchableOpacity8,
17278
+ {
17279
+ key: issue.id,
17280
+ onPress: () => nav.push({ name: "ISSUE_DETAIL", issue }),
17281
+ style: {
17282
+ flexDirection: "row",
17283
+ alignItems: "center",
17284
+ gap: 8,
17285
+ paddingVertical: 6
17286
+ }
17287
+ },
17288
+ /* @__PURE__ */ React10.createElement(View10, { style: {
17289
+ width: 8,
17290
+ height: 8,
17291
+ borderRadius: 4,
17292
+ backgroundColor: issue.severity === "critical" ? colors.red : issue.severity === "high" ? colors.orange : colors.yellow
17293
+ } }),
17294
+ /* @__PURE__ */ React10.createElement(
17295
+ Text8,
17296
+ {
17297
+ numberOfLines: 1,
17298
+ style: { fontSize: 12, color: colors.textPrimary, flex: 1 }
17299
+ },
17300
+ issue.title
17301
+ ),
17302
+ /* @__PURE__ */ React10.createElement(Text8, { style: { fontSize: 11, color: colors.textMuted } }, issue.status)
17303
+ )))), isBugType && /* @__PURE__ */ React10.createElement(View10, { style: styles5.section }, /* @__PURE__ */ React10.createElement(Text8, { style: shared.label }, "Which screen?"), /* @__PURE__ */ React10.createElement(
17083
17304
  TextInput4,
17084
17305
  {
17085
17306
  style: styles5.screenInput,
@@ -17155,11 +17376,24 @@ function createStyles6() {
17155
17376
  }
17156
17377
 
17157
17378
  // 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";
17379
+ import React12, { useMemo as useMemo7, useState as useState9 } from "react";
17380
+ import { View as View12, Text as Text10, TouchableOpacity as TouchableOpacity9, ScrollView as ScrollView3, StyleSheet as StyleSheet12, Linking as Linking3 } from "react-native";
17160
17381
  function MessageListScreen({ nav }) {
17161
17382
  const { threads, unreadCount, refreshThreads, dashboardUrl, isLoading, widgetColorScheme } = useBugBear();
17162
17383
  const styles5 = useMemo7(() => createStyles7(), [widgetColorScheme]);
17384
+ const [activeFilter, setActiveFilter] = useState9("all");
17385
+ const filteredThreads = threads.filter((thread) => {
17386
+ if (activeFilter === "all") return true;
17387
+ if (activeFilter === "unread") return thread.unreadCount > 0;
17388
+ return thread.threadType === activeFilter;
17389
+ });
17390
+ const filterChips = [
17391
+ { key: "all", label: "All" },
17392
+ { key: "report", label: "Bug Reports" },
17393
+ { key: "direct", label: "Direct" },
17394
+ { key: "announcement", label: "Announcements" },
17395
+ { key: "unread", label: "Unread", count: threads.filter((t) => t.unreadCount > 0).length }
17396
+ ];
17163
17397
  if (isLoading) return /* @__PURE__ */ React12.createElement(MessageListScreenSkeleton, null);
17164
17398
  return /* @__PURE__ */ React12.createElement(View12, null, /* @__PURE__ */ React12.createElement(
17165
17399
  TouchableOpacity9,
@@ -17168,14 +17402,54 @@ function MessageListScreen({ nav }) {
17168
17402
  onPress: () => nav.push({ name: "COMPOSE_MESSAGE" })
17169
17403
  },
17170
17404
  /* @__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(
17405
+ ), /* @__PURE__ */ React12.createElement(
17406
+ ScrollView3,
17407
+ {
17408
+ horizontal: true,
17409
+ showsHorizontalScrollIndicator: false,
17410
+ style: { paddingBottom: 12 },
17411
+ contentContainerStyle: { paddingHorizontal: 16, gap: 8 }
17412
+ },
17413
+ filterChips.map((chip) => /* @__PURE__ */ React12.createElement(
17414
+ TouchableOpacity9,
17415
+ {
17416
+ key: chip.key,
17417
+ onPress: () => setActiveFilter(chip.key),
17418
+ style: {
17419
+ paddingVertical: 6,
17420
+ paddingHorizontal: 12,
17421
+ borderRadius: 16,
17422
+ borderWidth: 1,
17423
+ borderColor: activeFilter === chip.key ? colors.blue : colors.border,
17424
+ backgroundColor: activeFilter === chip.key ? withAlpha(colors.blue, 0.15) : "transparent",
17425
+ flexDirection: "row",
17426
+ alignItems: "center",
17427
+ gap: 4
17428
+ }
17429
+ },
17430
+ /* @__PURE__ */ React12.createElement(Text10, { style: {
17431
+ fontSize: 12,
17432
+ fontWeight: "500",
17433
+ color: activeFilter === chip.key ? colors.blue : colors.textSecondary
17434
+ } }, chip.label),
17435
+ chip.count !== void 0 && chip.count > 0 && /* @__PURE__ */ React12.createElement(View12, { style: {
17436
+ backgroundColor: colors.blue,
17437
+ borderRadius: 8,
17438
+ minWidth: 16,
17439
+ height: 16,
17440
+ justifyContent: "center",
17441
+ alignItems: "center",
17442
+ paddingHorizontal: 4
17443
+ } }, /* @__PURE__ */ React12.createElement(Text10, { style: { fontSize: 10, fontWeight: "bold", color: colors.onPrimary } }, chip.count))
17444
+ ))
17445
+ ), 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
17446
  TouchableOpacity9,
17173
17447
  {
17174
17448
  key: thread.id,
17175
17449
  style: [styles5.threadItem, thread.unreadCount > 0 && styles5.threadItemUnread],
17176
17450
  onPress: () => nav.push({ name: "THREAD_DETAIL", thread })
17177
17451
  },
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))),
17452
+ /* @__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
17453
  /* @__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
17454
  ))), dashboardUrl && /* @__PURE__ */ React12.createElement(
17181
17455
  TouchableOpacity9,
@@ -17185,7 +17459,7 @@ function MessageListScreen({ nav }) {
17185
17459
  activeOpacity: 0.7
17186
17460
  },
17187
17461
  /* @__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"))));
17462
+ ), /* @__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
17463
  }
17190
17464
  function createStyles7() {
17191
17465
  return StyleSheet12.create({
@@ -17214,16 +17488,16 @@ function createStyles7() {
17214
17488
  }
17215
17489
 
17216
17490
  // src/widget/screens/ThreadDetailScreen.tsx
17217
- import React13, { useState as useState9, useEffect as useEffect8, useMemo as useMemo8 } from "react";
17491
+ import React13, { useState as useState10, useEffect as useEffect8, useMemo as useMemo8 } from "react";
17218
17492
  import { View as View13, Text as Text11, TouchableOpacity as TouchableOpacity10, TextInput as TextInput5, StyleSheet as StyleSheet13, Image as Image2 } from "react-native";
17219
17493
  function ThreadDetailScreen({ thread, nav }) {
17220
17494
  const { getThreadMessages, sendMessage, markAsRead, uploadImage, widgetColorScheme } = useBugBear();
17221
17495
  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);
17496
+ const [messages, setMessages] = useState10([]);
17497
+ const [loading, setLoading] = useState10(true);
17498
+ const [replyText, setReplyText] = useState10("");
17499
+ const [sending, setSending] = useState10(false);
17500
+ const [sendError, setSendError] = useState10(false);
17227
17501
  const replyImages = useImageAttachments(uploadImage, 3, "discussion-attachments");
17228
17502
  useEffect8(() => {
17229
17503
  let cancelled = false;
@@ -17335,14 +17609,14 @@ function createStyles8() {
17335
17609
  }
17336
17610
 
17337
17611
  // src/widget/screens/ComposeMessageScreen.tsx
17338
- import React14, { useState as useState10, useMemo as useMemo9 } from "react";
17612
+ import React14, { useState as useState11, useMemo as useMemo9 } from "react";
17339
17613
  import { View as View14, Text as Text12, TextInput as TextInput6, TouchableOpacity as TouchableOpacity11, StyleSheet as StyleSheet14 } from "react-native";
17340
17614
  function ComposeMessageScreen({ nav }) {
17341
17615
  const { createThread, uploadImage, widgetColorScheme } = useBugBear();
17342
17616
  const styles5 = useMemo9(() => createStyles9(), [widgetColorScheme]);
17343
- const [subject, setSubject] = useState10("");
17344
- const [message, setMessage] = useState10("");
17345
- const [sending, setSending] = useState10(false);
17617
+ const [subject, setSubject] = useState11("");
17618
+ const [message, setMessage] = useState11("");
17619
+ const [sending, setSending] = useState11(false);
17346
17620
  const images = useImageAttachments(uploadImage, 3, "discussion-attachments");
17347
17621
  const handleSend = async () => {
17348
17622
  if (!subject.trim() || !message.trim() || sending || images.isUploading) return;
@@ -17412,19 +17686,19 @@ function createStyles9() {
17412
17686
  }
17413
17687
 
17414
17688
  // src/widget/screens/ProfileScreen.tsx
17415
- import React15, { useState as useState11, useEffect as useEffect9, useMemo as useMemo10 } from "react";
17689
+ import React15, { useState as useState12, useEffect as useEffect9, useMemo as useMemo10 } from "react";
17416
17690
  import { View as View15, Text as Text13, TouchableOpacity as TouchableOpacity12, TextInput as TextInput7, StyleSheet as StyleSheet15 } from "react-native";
17417
17691
  function ProfileScreen({ nav }) {
17418
17692
  const { testerInfo, assignments, updateTesterProfile, refreshTesterInfo, widgetColorScheme } = useBugBear();
17419
17693
  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);
17694
+ const [editing, setEditing] = useState12(false);
17695
+ const [name, setName] = useState12(testerInfo?.name || "");
17696
+ const [additionalEmails, setAdditionalEmails] = useState12(testerInfo?.additionalEmails || []);
17697
+ const [newEmailInput, setNewEmailInput] = useState12("");
17698
+ const [platforms, setPlatforms] = useState12(testerInfo?.platforms || []);
17699
+ const [saving, setSaving] = useState12(false);
17700
+ const [saved, setSaved] = useState12(false);
17701
+ const [showDetails, setShowDetails] = useState12(false);
17428
17702
  const completedCount = assignments.filter((a) => a.status === "passed" || a.status === "failed").length;
17429
17703
  useEffect9(() => {
17430
17704
  if (testerInfo) {
@@ -17543,8 +17817,8 @@ function createStyles10() {
17543
17817
  }
17544
17818
 
17545
17819
  // 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";
17820
+ import React16, { useState as useState13, useEffect as useEffect10, useMemo as useMemo11 } from "react";
17821
+ import { View as View16, Text as Text14, TextInput as TextInput8, TouchableOpacity as TouchableOpacity13, StyleSheet as StyleSheet16 } from "react-native";
17548
17822
  var CATEGORIES = ["open", "done", "reopened"];
17549
17823
  var SEVERITY_ORDER2 = { critical: 0, high: 1, medium: 2, low: 3 };
17550
17824
  function IssueListScreen({ nav, category }) {
@@ -17561,12 +17835,22 @@ function IssueListScreen({ nav, category }) {
17561
17835
  medium: colors.yellow,
17562
17836
  low: colors.textMuted
17563
17837
  }), [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");
17838
+ const [activeCategory, setActiveCategory] = useState13(category);
17839
+ const [issues, setIssues] = useState13([]);
17840
+ const [loading, setLoading] = useState13(true);
17841
+ const [counts, setCounts] = useState13(null);
17842
+ const [sortMode, setSortMode] = useState13("severity");
17843
+ const [searchQuery, setSearchQuery] = useState13("");
17844
+ const [debouncedQuery, setDebouncedQuery] = useState13("");
17569
17845
  const config = CATEGORY_CONFIG[activeCategory];
17846
+ useEffect10(() => {
17847
+ const timer = setTimeout(() => setDebouncedQuery(searchQuery), 300);
17848
+ return () => clearTimeout(timer);
17849
+ }, [searchQuery]);
17850
+ useEffect10(() => {
17851
+ setSearchQuery("");
17852
+ setDebouncedQuery("");
17853
+ }, [activeCategory]);
17570
17854
  useEffect10(() => {
17571
17855
  if (!client) return;
17572
17856
  client.getIssueCounts().then(setCounts).catch(() => {
@@ -17606,6 +17890,9 @@ function IssueListScreen({ nav, category }) {
17606
17890
  }
17607
17891
  return sorted;
17608
17892
  }, [issues, sortMode]);
17893
+ const searchFilteredIssues = debouncedQuery ? sortedIssues.filter(
17894
+ (issue) => (issue.title || "").toLowerCase().includes(debouncedQuery.toLowerCase()) || (issue.description || "").toLowerCase().includes(debouncedQuery.toLowerCase())
17895
+ ) : sortedIssues;
17609
17896
  return /* @__PURE__ */ React16.createElement(View16, null, /* @__PURE__ */ React16.createElement(View16, { style: styles5.tabBar }, CATEGORIES.map((cat) => {
17610
17897
  const catConfig = CATEGORY_CONFIG[cat];
17611
17898
  const isActive = activeCategory === cat;
@@ -17652,7 +17939,25 @@ function IssueListScreen({ nav, category }) {
17652
17939
  styles5.sortBtnText,
17653
17940
  sortMode === s2.key && styles5.sortBtnTextActive
17654
17941
  ] }, 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(
17942
+ ))), /* @__PURE__ */ React16.createElement(View16, { style: { paddingHorizontal: 16, paddingBottom: 12 } }, /* @__PURE__ */ React16.createElement(
17943
+ TextInput8,
17944
+ {
17945
+ value: searchQuery,
17946
+ onChangeText: setSearchQuery,
17947
+ placeholder: "Search my reports...",
17948
+ placeholderTextColor: colors.textMuted,
17949
+ style: {
17950
+ padding: 8,
17951
+ paddingHorizontal: 12,
17952
+ borderRadius: 8,
17953
+ borderWidth: 1,
17954
+ borderColor: colors.border,
17955
+ backgroundColor: colors.card,
17956
+ color: colors.textPrimary,
17957
+ fontSize: 13
17958
+ }
17959
+ }
17960
+ )), 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
17961
  TouchableOpacity13,
17657
17962
  {
17658
17963
  key: issue.id,
@@ -17817,17 +18122,17 @@ function createStyles11() {
17817
18122
  }
17818
18123
 
17819
18124
  // 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";
18125
+ import React17, { useMemo as useMemo12, useState as useState14, useCallback as useCallback5 } from "react";
18126
+ 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
18127
  var DONE_STATUSES = ["verified", "resolved", "closed", "reviewed"];
17823
18128
  function IssueDetailScreen({ nav, issue }) {
17824
18129
  const { dashboardUrl, widgetColorScheme, reopenReport } = useBugBear();
17825
18130
  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);
18131
+ const [showReopenForm, setShowReopenForm] = useState14(false);
18132
+ const [reopenReason, setReopenReason] = useState14("");
18133
+ const [isSubmitting, setIsSubmitting] = useState14(false);
18134
+ const [reopenError, setReopenError] = useState14(null);
18135
+ const [wasReopened, setWasReopened] = useState14(false);
17831
18136
  const STATUS_LABELS = useMemo12(() => ({
17832
18137
  new: { label: "New", bg: colors.blueDark, color: colors.blueLight },
17833
18138
  triaging: { label: "Triaging", bg: colors.blueDark, color: colors.blueLight },
@@ -17873,7 +18178,7 @@ function IssueDetailScreen({ nav, issue }) {
17873
18178
  },
17874
18179
  /* @__PURE__ */ React17.createElement(Text15, { style: styles5.reopenButtonText }, "\u{1F504}", " Not Fixed \u2014 Reopen Issue")
17875
18180
  ), 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,
18181
+ TextInput9,
17877
18182
  {
17878
18183
  value: reopenReason,
17879
18184
  onChangeText: setReopenReason,
@@ -18171,18 +18476,20 @@ function createStyles12() {
18171
18476
  }
18172
18477
 
18173
18478
  // 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";
18479
+ import React18, { useState as useState15, useMemo as useMemo13 } from "react";
18480
+ import { View as View18, Text as Text16, TextInput as TextInput10, TouchableOpacity as TouchableOpacity15, StyleSheet as StyleSheet18, Keyboard as Keyboard2 } from "react-native";
18176
18481
  function SessionStartScreen({ nav }) {
18177
18482
  const { startSession, assignments, widgetColorScheme } = useBugBear();
18178
18483
  const styles5 = useMemo13(() => createStyles13(), [widgetColorScheme]);
18179
- const [focusArea, setFocusArea] = useState14("");
18180
- const [isStarting, setIsStarting] = useState14(false);
18484
+ const [focusArea, setFocusArea] = useState15("");
18485
+ const [isStarting, setIsStarting] = useState15(false);
18486
+ const [error, setError] = useState15(null);
18181
18487
  const trackNames = Array.from(new Set(
18182
18488
  assignments.filter((a) => a.testCase.track?.name).map((a) => a.testCase.track.name)
18183
18489
  )).slice(0, 6);
18184
18490
  const handleStart = async () => {
18185
18491
  if (isStarting) return;
18492
+ setError(null);
18186
18493
  Keyboard2.dismiss();
18187
18494
  setIsStarting(true);
18188
18495
  try {
@@ -18191,13 +18498,17 @@ function SessionStartScreen({ nav }) {
18191
18498
  });
18192
18499
  if (result.success) {
18193
18500
  nav.replace({ name: "SESSION_ACTIVE" });
18501
+ } else {
18502
+ const msg = result.error || "Failed to start session. Please try again.";
18503
+ console.warn("BugBear: Session start failed:", msg);
18504
+ setError(msg);
18194
18505
  }
18195
18506
  } finally {
18196
18507
  setIsStarting(false);
18197
18508
  }
18198
18509
  };
18199
18510
  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,
18511
+ TextInput10,
18201
18512
  {
18202
18513
  value: focusArea,
18203
18514
  onChangeText: setFocusArea,
@@ -18216,7 +18527,7 @@ function SessionStartScreen({ nav }) {
18216
18527
  activeOpacity: 0.7
18217
18528
  },
18218
18529
  /* @__PURE__ */ React18.createElement(Text16, { style: [styles5.chipText, focusArea === name && styles5.chipTextActive] }, name)
18219
- )))), /* @__PURE__ */ React18.createElement(
18530
+ )))), error && /* @__PURE__ */ React18.createElement(View18, { style: styles5.errorBox }, /* @__PURE__ */ React18.createElement(Text16, { style: styles5.errorText }, error)), /* @__PURE__ */ React18.createElement(
18220
18531
  TouchableOpacity15,
18221
18532
  {
18222
18533
  style: [styles5.startBtn, isStarting && styles5.startBtnDisabled],
@@ -18295,6 +18606,20 @@ function createStyles13() {
18295
18606
  chipTextActive: {
18296
18607
  color: colors.blueLight
18297
18608
  },
18609
+ errorBox: {
18610
+ marginBottom: 12,
18611
+ paddingVertical: 10,
18612
+ paddingHorizontal: 12,
18613
+ backgroundColor: "rgba(239, 68, 68, 0.1)",
18614
+ borderWidth: 1,
18615
+ borderColor: "rgba(239, 68, 68, 0.3)",
18616
+ borderRadius: 8
18617
+ },
18618
+ errorText: {
18619
+ fontSize: 13,
18620
+ color: "#f87171",
18621
+ lineHeight: 18
18622
+ },
18298
18623
  startBtn: {
18299
18624
  paddingVertical: 14,
18300
18625
  backgroundColor: colors.blueSurface,
@@ -18315,7 +18640,7 @@ function createStyles13() {
18315
18640
  }
18316
18641
 
18317
18642
  // src/widget/screens/SessionActiveScreen.tsx
18318
- import React19, { useState as useState15, useEffect as useEffect11, useRef as useRef5, useMemo as useMemo14 } from "react";
18643
+ import React19, { useState as useState16, useEffect as useEffect11, useRef as useRef5, useMemo as useMemo14 } from "react";
18319
18644
  import { View as View19, Text as Text17, TouchableOpacity as TouchableOpacity16, StyleSheet as StyleSheet19 } from "react-native";
18320
18645
  function SessionActiveScreen({ nav }) {
18321
18646
  const { activeSession, sessionFindings, endSession, refreshSession, widgetColorScheme } = useBugBear();
@@ -18326,8 +18651,8 @@ function SessionActiveScreen({ nav }) {
18326
18651
  suggestion: { icon: "\u{1F4A1}", label: "Suggestion", color: colors.blue },
18327
18652
  question: { icon: "\u2753", label: "Question", color: colors.violet }
18328
18653
  }), [widgetColorScheme]);
18329
- const [isEnding, setIsEnding] = useState15(false);
18330
- const [elapsed, setElapsed] = useState15(0);
18654
+ const [isEnding, setIsEnding] = useState16(false);
18655
+ const [elapsed, setElapsed] = useState16(0);
18331
18656
  const timerRef = useRef5(null);
18332
18657
  useEffect11(() => {
18333
18658
  refreshSession();
@@ -18552,8 +18877,8 @@ function createStyles14() {
18552
18877
  }
18553
18878
 
18554
18879
  // 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";
18880
+ import React20, { useState as useState17, useMemo as useMemo15 } from "react";
18881
+ import { View as View20, Text as Text18, TextInput as TextInput11, TouchableOpacity as TouchableOpacity17, StyleSheet as StyleSheet20, Keyboard as Keyboard3 } from "react-native";
18557
18882
  var FINDING_TYPES = [
18558
18883
  { value: "bug", icon: "\u{1F41B}", label: "Bug" },
18559
18884
  { value: "concern", icon: "\u26A0\uFE0F", label: "Concern" },
@@ -18570,11 +18895,11 @@ function SessionFindingScreen({ nav }) {
18570
18895
  { value: "low", label: "Low", color: colors.textMuted },
18571
18896
  { value: "observation", label: "Note", color: colors.textDim }
18572
18897
  ], [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);
18898
+ const [type, setType] = useState17("bug");
18899
+ const [severity, setSeverity] = useState17("medium");
18900
+ const [title, setTitle] = useState17("");
18901
+ const [description, setDescription] = useState17("");
18902
+ const [isSubmitting, setIsSubmitting] = useState17(false);
18578
18903
  const handleSubmit = async () => {
18579
18904
  if (!title.trim() || isSubmitting) return;
18580
18905
  Keyboard3.dismiss();
@@ -18617,7 +18942,7 @@ function SessionFindingScreen({ nav }) {
18617
18942
  },
18618
18943
  /* @__PURE__ */ React20.createElement(Text18, { style: [styles5.severityText, severity === s2.value && { color: s2.color }] }, s2.label)
18619
18944
  )))), /* @__PURE__ */ React20.createElement(View20, { style: styles5.inputSection }, /* @__PURE__ */ React20.createElement(
18620
- TextInput10,
18945
+ TextInput11,
18621
18946
  {
18622
18947
  value: title,
18623
18948
  onChangeText: setTitle,
@@ -18627,7 +18952,7 @@ function SessionFindingScreen({ nav }) {
18627
18952
  returnKeyType: "next"
18628
18953
  }
18629
18954
  )), /* @__PURE__ */ React20.createElement(View20, { style: styles5.inputSection }, /* @__PURE__ */ React20.createElement(
18630
- TextInput10,
18955
+ TextInput11,
18631
18956
  {
18632
18957
  value: description,
18633
18958
  onChangeText: setDescription,
@@ -18772,7 +19097,7 @@ function BugBearButton({
18772
19097
  }) {
18773
19098
  const { shouldShowWidget, testerInfo, isLoading, unreadCount, assignments, widgetMode, widgetColorScheme } = useBugBear();
18774
19099
  const { currentScreen, canGoBack, push, pop, replace, reset } = useNavigation();
18775
- const [modalVisible, setModalVisible] = useState17(false);
19100
+ const [modalVisible, setModalVisible] = useState18(false);
18776
19101
  const styles5 = useMemo16(() => createStyles16(), [widgetColorScheme]);
18777
19102
  const screenCaptureRef = useRef6(null);
18778
19103
  const openModal = () => {
@@ -18980,7 +19305,7 @@ function BugBearButton({
18980
19305
  style: styles5.modalOverlay
18981
19306
  },
18982
19307
  /* @__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,
19308
+ ScrollView4,
18984
19309
  {
18985
19310
  style: styles5.content,
18986
19311
  contentContainerStyle: styles5.contentContainer,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bbearai/react-native",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "description": "BugBear React Native components for mobile apps",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -55,7 +55,7 @@
55
55
  }
56
56
  },
57
57
  "devDependencies": {
58
- "@bbearai/core": "^0.7.0",
58
+ "@bbearai/core": "^0.8.0",
59
59
  "@eslint/js": "^9.39.2",
60
60
  "@testing-library/jest-dom": "^6.9.1",
61
61
  "@testing-library/react": "^16.3.2",