@bbearai/react-native 0.3.8 → 0.3.9

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/README.md CHANGED
@@ -34,6 +34,7 @@ function App() {
34
34
  getCurrentUser: async () => ({
35
35
  id: user.id,
36
36
  email: user.email,
37
+ name: user.name, // Optional, shown on reports
37
38
  }),
38
39
  }}
39
40
  >
@@ -44,6 +45,138 @@ function App() {
44
45
  }
45
46
  ```
46
47
 
48
+ ## Configuration Options
49
+
50
+ ```tsx
51
+ <BugBearProvider
52
+ config={{
53
+ // Required
54
+ projectId: 'your-project-id',
55
+ getCurrentUser: async () => ({ id: user.id, email: user.email }),
56
+
57
+ // Optional — rich context for bug reports
58
+ getAppContext: () => ({
59
+ currentRoute: currentScreenName, // Current screen name
60
+ userRole: currentUser?.role, // 'owner', 'manager', 'guest', etc.
61
+ propertyId: selectedProperty?.id, // App-specific context
62
+ custom: { theme: 'dark' }, // Any additional data
63
+ }),
64
+
65
+ // Optional — callbacks
66
+ dashboardUrl: 'https://app.bugbear.ai', // Web dashboard link in widget
67
+ onNavigate: (route) => navigation.navigate(route), // Deep link handler for test cases
68
+ onReportSubmitted: (report) => { ... }, // After report submission
69
+ getNavigationHistory: () => [...recentScreens], // Custom nav tracking
70
+
71
+ // Optional — self-hosted
72
+ supabaseUrl: '...',
73
+ supabaseAnonKey: '...',
74
+ }}
75
+ enabled={isAuthReady} // Delay init until auth is ready (default: true)
76
+ >
77
+ ```
78
+
79
+ ### Why use `getAppContext`?
80
+
81
+ When a tester reports a bug, BugBear attaches the app context to the report. This tells the developer fixing the bug:
82
+ - **Which screen** the bug is on
83
+ - **What role** the user has (critical for role-dependent bugs)
84
+ - **App-specific state** like selected property, active filters, etc.
85
+
86
+ The bug report form also includes a **"Which screen?"** field so testers can specify the affected screen manually.
87
+
88
+ ## Automatic Context Capture
89
+
90
+ BugBearProvider automatically captures debugging context with zero configuration:
91
+
92
+ | Data | Details |
93
+ |------|---------|
94
+ | **Console logs** | Last 50 `console.log/warn/error/info` calls |
95
+ | **Network requests** | Last 20 `fetch()` calls with method, URL, status, duration |
96
+ | **Failed response bodies** | First 500 chars of 4xx/5xx response bodies |
97
+ | **Performance** | Page load time, memory usage |
98
+ | **Environment** | Language, timezone, online status |
99
+
100
+ This data is attached to every bug report and available to the MCP server's `get_report_context` tool for AI-assisted debugging.
101
+
102
+ > **Note:** For React Native, navigation history is not auto-captured (no `pushState`). Use `getNavigationHistory` or React Navigation integration below for screen tracking.
103
+
104
+ ## Navigation Tracking
105
+
106
+ ### React Navigation integration
107
+
108
+ ```tsx
109
+ import { useNavigationContainerRef } from '@react-navigation/native';
110
+
111
+ function App() {
112
+ const navigationRef = useNavigationContainerRef();
113
+ const routeHistory = useRef<string[]>([]);
114
+
115
+ return (
116
+ <BugBearProvider
117
+ config={{
118
+ projectId: 'your-project-id',
119
+ getCurrentUser: async () => ({ id: user.id, email: user.email }),
120
+ getNavigationHistory: () => routeHistory.current,
121
+ getAppContext: () => ({
122
+ currentRoute: navigationRef.getCurrentRoute()?.name || 'unknown',
123
+ userRole: currentUser?.role,
124
+ }),
125
+ }}
126
+ >
127
+ <NavigationContainer
128
+ ref={navigationRef}
129
+ onStateChange={() => {
130
+ const name = navigationRef.getCurrentRoute()?.name;
131
+ if (name) {
132
+ routeHistory.current.push(name);
133
+ // Keep last 20
134
+ if (routeHistory.current.length > 20) routeHistory.current.shift();
135
+ }
136
+ }}
137
+ >
138
+ {/* Your navigation */}
139
+ </NavigationContainer>
140
+ <BugBearButton />
141
+ </BugBearProvider>
142
+ );
143
+ }
144
+ ```
145
+
146
+ ## Widget Architecture
147
+
148
+ The widget uses a navigation stack pattern with 10 screens:
149
+
150
+ | Screen | Purpose |
151
+ |--------|---------|
152
+ | **Home** | Smart hero banner + 2x2 action grid + progress bar + web dashboard link |
153
+ | **Test Detail** | One-test-at-a-time execution with pass/fail/skip actions |
154
+ | **Test List** | All assignments grouped by folder with filter bar |
155
+ | **Test Feedback** | Star rating + quality flags after pass/fail |
156
+ | **Report** | Bug/feedback submission with type, severity, description, affected screen |
157
+ | **Report Success** | Confirmation with auto-return to home |
158
+ | **Message List** | Thread list with unread badges + compose button |
159
+ | **Thread Detail** | Chat bubbles + reply composer |
160
+ | **Compose Message** | New thread form with subject + message |
161
+ | **Profile** | Tester info, stats, and editable fields |
162
+
163
+ ## Button Configuration
164
+
165
+ The BugBear button is draggable by default with edge-snapping behavior:
166
+
167
+ ```tsx
168
+ <BugBearButton
169
+ draggable={true} // Enable/disable dragging (default: true)
170
+ position="bottom-right" // Initial position: 'bottom-right' | 'bottom-left'
171
+ initialX={100} // Custom initial X position (optional)
172
+ initialY={500} // Custom initial Y position (optional)
173
+ minY={100} // Minimum Y from top (default: 100)
174
+ maxYOffset={160} // Max Y offset from bottom (default: 160)
175
+ />
176
+ ```
177
+
178
+ The button automatically snaps to the nearest screen edge when released.
179
+
47
180
  ## Monorepo Setup
48
181
 
49
182
  When using BugBear in a monorepo (e.g., with Turborepo, Nx, or Yarn Workspaces), you may encounter React duplicate instance errors like:
@@ -105,90 +238,6 @@ If you're using Yarn workspaces, you can also configure hoisting to ensure a sin
105
238
  }
106
239
  ```
107
240
 
108
- ## Configuration Options
109
-
110
- ```tsx
111
- <BugBearProvider
112
- config={{
113
- projectId: 'your-project-id', // Required: Your BugBear project ID
114
- getCurrentUser: async () => {...}, // Required: Return current user info
115
- dashboardUrl: 'https://app.bugbear.ai', // Optional: Web dashboard link in widget
116
- getNavigationHistory: () => [...], // Optional: Custom navigation tracking
117
- onNavigate: (route) => {...}, // Optional: Deep link handler for test cases
118
- onReportSubmitted: (report) => {...}, // Optional: Callback after report submission
119
- }}
120
- >
121
- ```
122
-
123
- ## Widget Architecture
124
-
125
- The widget uses a navigation stack pattern with 10 screens:
126
-
127
- | Screen | Purpose |
128
- |--------|---------|
129
- | **Home** | Smart hero banner + 2x2 action grid + progress bar + web dashboard link |
130
- | **Test Detail** | One-test-at-a-time execution with pass/fail/skip actions |
131
- | **Test List** | All assignments grouped by folder with filter bar |
132
- | **Test Feedback** | Star rating + quality flags after pass/fail |
133
- | **Report** | Bug/feedback submission with type, severity, description |
134
- | **Report Success** | Confirmation with auto-return to home |
135
- | **Message List** | Thread list with unread badges + compose button |
136
- | **Thread Detail** | Chat bubbles + reply composer |
137
- | **Compose Message** | New thread form with subject + message |
138
- | **Profile** | Tester info, stats, and editable fields |
139
-
140
- ## Navigation Tracking
141
-
142
- BugBear can automatically track navigation history for better bug context. If you're using React Navigation:
143
-
144
- ```tsx
145
- import { useNavigationContainerRef } from '@react-navigation/native';
146
-
147
- function App() {
148
- const navigationRef = useNavigationContainerRef();
149
- const routeNameRef = useRef<string>();
150
-
151
- return (
152
- <BugBearProvider
153
- projectId="your-project-id"
154
- getCurrentUser={...}
155
- getNavigationHistory={() => routeNameRef.current ? [routeNameRef.current] : []}
156
- >
157
- <NavigationContainer
158
- ref={navigationRef}
159
- onReady={() => {
160
- routeNameRef.current = navigationRef.getCurrentRoute()?.name;
161
- }}
162
- onStateChange={() => {
163
- const currentRouteName = navigationRef.getCurrentRoute()?.name;
164
- routeNameRef.current = currentRouteName;
165
- }}
166
- >
167
- {/* Your navigation */}
168
- </NavigationContainer>
169
- <BugBearWidget />
170
- </BugBearProvider>
171
- );
172
- }
173
- ```
174
-
175
- ## Button Configuration
176
-
177
- The BugBear button is draggable by default with edge-snapping behavior:
178
-
179
- ```tsx
180
- <BugBearButton
181
- draggable={true} // Enable/disable dragging (default: true)
182
- position="bottom-right" // Initial position: 'bottom-right' | 'bottom-left'
183
- initialX={100} // Custom initial X position (optional)
184
- initialY={500} // Custom initial Y position (optional)
185
- minY={100} // Minimum Y from top (default: 100)
186
- maxYOffset={160} // Max Y offset from bottom (default: 160)
187
- />
188
- ```
189
-
190
- The button automatically snaps to the nearest screen edge when released.
191
-
192
241
  ## Troubleshooting
193
242
 
194
243
  ### Widget not showing
@@ -203,10 +252,12 @@ If you're using your own Supabase instance, provide the credentials:
203
252
 
204
253
  ```tsx
205
254
  <BugBearProvider
206
- projectId="your-project-id"
207
- supabaseUrl="https://your-instance.supabase.co"
208
- supabaseAnonKey="your-anon-key"
209
- getCurrentUser={...}
255
+ config={{
256
+ projectId: 'your-project-id',
257
+ supabaseUrl: 'https://your-instance.supabase.co',
258
+ supabaseAnonKey: 'your-anon-key',
259
+ getCurrentUser: async () => ({ id: user.id, email: user.email }),
260
+ }}
210
261
  >
211
262
  ```
212
263
 
@@ -216,15 +267,11 @@ If you're using your own Supabase instance, provide the credentials:
216
267
 
217
268
  The BugBear button renders within the normal React Native view hierarchy using absolute positioning. When your app displays modals, bottom sheets, or the keyboard, the button may be hidden behind them.
218
269
 
219
- This is a platform limitation - React Native doesn't provide a built-in way to render views above all modals without using a Modal component (which would block touch events on the underlying app).
220
-
221
270
  **Workarounds:**
222
271
  - Accept that the button won't be accessible when modals are open
223
272
  - Consider dismissing the modal to access BugBear
224
273
  - For testing flows that involve modals, provide an alternative way to trigger the BugBear widget
225
274
 
226
- We're investigating solutions using `react-native-portal` or similar libraries for future releases.
227
-
228
275
  ## License
229
276
 
230
277
  MIT
package/dist/index.js CHANGED
@@ -11982,19 +11982,40 @@ var BugBearClient = class {
11982
11982
  return { success: false, error: message };
11983
11983
  }
11984
11984
  }
11985
+ /**
11986
+ * Pass a test assignment — convenience wrapper around updateAssignmentStatus
11987
+ */
11988
+ async passAssignment(assignmentId) {
11989
+ return this.updateAssignmentStatus(assignmentId, "passed");
11990
+ }
11991
+ /**
11992
+ * Fail a test assignment — convenience wrapper around updateAssignmentStatus
11993
+ */
11994
+ async failAssignment(assignmentId) {
11995
+ return this.updateAssignmentStatus(assignmentId, "failed");
11996
+ }
11985
11997
  /**
11986
11998
  * Skip a test assignment with a required reason
11987
11999
  * Marks the assignment as 'skipped' and records why it was skipped
11988
12000
  */
11989
12001
  async skipAssignment(assignmentId, reason, notes) {
12002
+ let actualReason;
12003
+ let actualNotes;
12004
+ if (typeof reason === "object") {
12005
+ actualReason = reason.reason;
12006
+ actualNotes = reason.notes;
12007
+ } else {
12008
+ actualReason = reason;
12009
+ actualNotes = notes;
12010
+ }
11990
12011
  try {
11991
12012
  const updateData = {
11992
12013
  status: "skipped",
11993
- skip_reason: reason,
12014
+ skip_reason: actualReason,
11994
12015
  completed_at: (/* @__PURE__ */ new Date()).toISOString()
11995
12016
  };
11996
- if (notes) {
11997
- updateData.notes = notes;
12017
+ if (actualNotes) {
12018
+ updateData.notes = actualNotes;
11998
12019
  }
11999
12020
  const { error } = await this.supabase.from("test_assignments").update(updateData).eq("id", assignmentId);
12000
12021
  if (error) {
@@ -13478,7 +13499,7 @@ function HomeScreen({ nav }) {
13478
13499
  import_react_native3.TouchableOpacity,
13479
13500
  {
13480
13501
  style: styles.actionCard,
13481
- onPress: () => nav.push({ name: "TEST_DETAIL" }),
13502
+ onPress: () => nav.push({ name: "TEST_LIST" }),
13482
13503
  activeOpacity: 0.7
13483
13504
  },
13484
13505
  /* @__PURE__ */ import_react3.default.createElement(import_react_native3.Text, { style: styles.actionIcon }, "\u2705"),
@@ -13746,12 +13767,14 @@ function TestDetailScreen({ testId, nav }) {
13746
13767
  const currentIndex = displayedAssignment ? assignments.indexOf(displayedAssignment) : -1;
13747
13768
  const handlePass = (0, import_react4.useCallback)(async () => {
13748
13769
  if (!client || !displayedAssignment) return;
13770
+ import_react_native4.Keyboard.dismiss();
13749
13771
  await client.passAssignment(displayedAssignment.id);
13750
13772
  await refreshAssignments();
13751
13773
  nav.replace({ name: "TEST_FEEDBACK", status: "passed", assignmentId: displayedAssignment.id });
13752
13774
  }, [client, displayedAssignment, refreshAssignments, nav]);
13753
13775
  const handleFail = (0, import_react4.useCallback)(async () => {
13754
13776
  if (!client || !displayedAssignment) return;
13777
+ import_react_native4.Keyboard.dismiss();
13755
13778
  await client.failAssignment(displayedAssignment.id);
13756
13779
  await refreshAssignments();
13757
13780
  nav.replace({
@@ -13765,6 +13788,7 @@ function TestDetailScreen({ testId, nav }) {
13765
13788
  }, [client, displayedAssignment, refreshAssignments, nav]);
13766
13789
  const handleSkip = (0, import_react4.useCallback)(async () => {
13767
13790
  if (!client || !displayedAssignment || !selectedSkipReason) return;
13791
+ import_react_native4.Keyboard.dismiss();
13768
13792
  setSkipping(true);
13769
13793
  await client.skipAssignment(displayedAssignment.id, {
13770
13794
  reason: selectedSkipReason,
@@ -13833,7 +13857,9 @@ function TestDetailScreen({ testId, nav }) {
13833
13857
  {
13834
13858
  style: styles2.navigateButton,
13835
13859
  onPress: () => {
13860
+ import_react_native4.Keyboard.dismiss();
13836
13861
  onNavigate(testCase.targetRoute);
13862
+ nav.closeWidget?.();
13837
13863
  }
13838
13864
  },
13839
13865
  /* @__PURE__ */ import_react4.default.createElement(import_react_native4.Text, { style: styles2.navigateText }, "\u{1F9ED} Go to test location")
@@ -14974,10 +15000,29 @@ function BugBearButton({
14974
15000
  }
14975
15001
  };
14976
15002
  const handleClose = () => {
15003
+ import_react_native15.Keyboard.dismiss();
14977
15004
  setModalVisible(false);
14978
- reset();
14979
15005
  };
14980
- const nav = { push, pop, replace, reset, canGoBack };
15006
+ const nav = {
15007
+ push: (screen) => {
15008
+ import_react_native15.Keyboard.dismiss();
15009
+ push(screen);
15010
+ },
15011
+ pop: () => {
15012
+ import_react_native15.Keyboard.dismiss();
15013
+ pop();
15014
+ },
15015
+ replace: (screen) => {
15016
+ import_react_native15.Keyboard.dismiss();
15017
+ replace(screen);
15018
+ },
15019
+ reset: () => {
15020
+ import_react_native15.Keyboard.dismiss();
15021
+ reset();
15022
+ },
15023
+ canGoBack,
15024
+ closeWidget: handleClose
15025
+ };
14981
15026
  const renderScreen = () => {
14982
15027
  switch (currentScreen.name) {
14983
15028
  case "HOME":
@@ -15034,7 +15079,7 @@ function BugBearButton({
15034
15079
  behavior: import_react_native15.Platform.OS === "ios" ? "padding" : "height",
15035
15080
  style: styles13.modalOverlay
15036
15081
  },
15037
- /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.modalContainer }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.header }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.headerLeft }, canGoBack ? /* @__PURE__ */ import_react16.default.createElement(import_react_native15.TouchableOpacity, { onPress: pop, style: styles13.backButton }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.backText }, "\u2190 Back")) : /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.headerTitleRow }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.headerTitle }, "BugBear"), testerInfo && /* @__PURE__ */ import_react16.default.createElement(import_react_native15.TouchableOpacity, { onPress: () => push({ name: "PROFILE" }) }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.headerName }, testerInfo.name, " \u270E")))), getHeaderTitle() ? /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.headerScreenTitle, numberOfLines: 1 }, getHeaderTitle()) : null, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.TouchableOpacity, { onPress: handleClose, style: styles13.closeButton }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.closeText }, "\u2715"))), /* @__PURE__ */ import_react16.default.createElement(
15082
+ /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.modalContainer }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.header }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.headerLeft }, canGoBack ? /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.headerNavRow }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.TouchableOpacity, { onPress: () => nav.pop(), style: styles13.backButton }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.backText }, "\u2190 Back")), /* @__PURE__ */ import_react16.default.createElement(import_react_native15.TouchableOpacity, { onPress: () => nav.reset(), style: styles13.homeButton }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.homeText }, "\u{1F3E0}"))) : /* @__PURE__ */ import_react16.default.createElement(import_react_native15.View, { style: styles13.headerTitleRow }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.headerTitle }, "BugBear"), testerInfo && /* @__PURE__ */ import_react16.default.createElement(import_react_native15.TouchableOpacity, { onPress: () => push({ name: "PROFILE" }) }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.headerName }, testerInfo.name, " \u270E")))), getHeaderTitle() ? /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.headerScreenTitle, numberOfLines: 1 }, getHeaderTitle()) : null, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.TouchableOpacity, { onPress: handleClose, style: styles13.closeButton }, /* @__PURE__ */ import_react16.default.createElement(import_react_native15.Text, { style: styles13.closeText }, "\u2715"))), /* @__PURE__ */ import_react16.default.createElement(
15038
15083
  import_react_native15.ScrollView,
15039
15084
  {
15040
15085
  style: styles13.content,
@@ -15135,15 +15180,27 @@ var styles13 = import_react_native15.StyleSheet.create({
15135
15180
  flex: 1,
15136
15181
  textAlign: "center"
15137
15182
  },
15183
+ headerNavRow: {
15184
+ flexDirection: "row",
15185
+ alignItems: "center",
15186
+ gap: 8
15187
+ },
15138
15188
  backButton: {
15139
15189
  paddingVertical: 2,
15140
- paddingRight: 12
15190
+ paddingRight: 4
15141
15191
  },
15142
15192
  backText: {
15143
15193
  fontSize: 15,
15144
15194
  color: colors.blue,
15145
15195
  fontWeight: "500"
15146
15196
  },
15197
+ homeButton: {
15198
+ paddingVertical: 2,
15199
+ paddingHorizontal: 6
15200
+ },
15201
+ homeText: {
15202
+ fontSize: 16
15203
+ },
15147
15204
  closeButton: {
15148
15205
  width: 32,
15149
15206
  height: 32,
package/dist/index.mjs CHANGED
@@ -11951,19 +11951,40 @@ var BugBearClient = class {
11951
11951
  return { success: false, error: message };
11952
11952
  }
11953
11953
  }
11954
+ /**
11955
+ * Pass a test assignment — convenience wrapper around updateAssignmentStatus
11956
+ */
11957
+ async passAssignment(assignmentId) {
11958
+ return this.updateAssignmentStatus(assignmentId, "passed");
11959
+ }
11960
+ /**
11961
+ * Fail a test assignment — convenience wrapper around updateAssignmentStatus
11962
+ */
11963
+ async failAssignment(assignmentId) {
11964
+ return this.updateAssignmentStatus(assignmentId, "failed");
11965
+ }
11954
11966
  /**
11955
11967
  * Skip a test assignment with a required reason
11956
11968
  * Marks the assignment as 'skipped' and records why it was skipped
11957
11969
  */
11958
11970
  async skipAssignment(assignmentId, reason, notes) {
11971
+ let actualReason;
11972
+ let actualNotes;
11973
+ if (typeof reason === "object") {
11974
+ actualReason = reason.reason;
11975
+ actualNotes = reason.notes;
11976
+ } else {
11977
+ actualReason = reason;
11978
+ actualNotes = notes;
11979
+ }
11959
11980
  try {
11960
11981
  const updateData = {
11961
11982
  status: "skipped",
11962
- skip_reason: reason,
11983
+ skip_reason: actualReason,
11963
11984
  completed_at: (/* @__PURE__ */ new Date()).toISOString()
11964
11985
  };
11965
- if (notes) {
11966
- updateData.notes = notes;
11986
+ if (actualNotes) {
11987
+ updateData.notes = actualNotes;
11967
11988
  }
11968
11989
  const { error } = await this.supabase.from("test_assignments").update(updateData).eq("id", assignmentId);
11969
11990
  if (error) {
@@ -13204,7 +13225,8 @@ import {
13204
13225
  Platform as Platform4,
13205
13226
  PanResponder,
13206
13227
  Animated,
13207
- ActivityIndicator as ActivityIndicator2
13228
+ ActivityIndicator as ActivityIndicator2,
13229
+ Keyboard as Keyboard2
13208
13230
  } from "react-native";
13209
13231
 
13210
13232
  // src/widget/logo.ts
@@ -13461,7 +13483,7 @@ function HomeScreen({ nav }) {
13461
13483
  TouchableOpacity,
13462
13484
  {
13463
13485
  style: styles.actionCard,
13464
- onPress: () => nav.push({ name: "TEST_DETAIL" }),
13486
+ onPress: () => nav.push({ name: "TEST_LIST" }),
13465
13487
  activeOpacity: 0.7
13466
13488
  },
13467
13489
  /* @__PURE__ */ React2.createElement(Text, { style: styles.actionIcon }, "\u2705"),
@@ -13694,7 +13716,7 @@ var styles = StyleSheet2.create({
13694
13716
 
13695
13717
  // src/widget/screens/TestDetailScreen.tsx
13696
13718
  import React3, { useState as useState2, useEffect as useEffect3, useCallback as useCallback2 } from "react";
13697
- import { View as View2, Text as Text2, TouchableOpacity as TouchableOpacity2, StyleSheet as StyleSheet3, Modal, TextInput } from "react-native";
13719
+ import { View as View2, Text as Text2, TouchableOpacity as TouchableOpacity2, StyleSheet as StyleSheet3, Modal, TextInput, Keyboard } from "react-native";
13698
13720
  function TestDetailScreen({ testId, nav }) {
13699
13721
  const { client, assignments, currentAssignment, refreshAssignments, getDeviceInfo, onNavigate } = useBugBear();
13700
13722
  const displayedAssignment = testId ? assignments.find((a) => a.id === testId) || currentAssignment : currentAssignment;
@@ -13729,12 +13751,14 @@ function TestDetailScreen({ testId, nav }) {
13729
13751
  const currentIndex = displayedAssignment ? assignments.indexOf(displayedAssignment) : -1;
13730
13752
  const handlePass = useCallback2(async () => {
13731
13753
  if (!client || !displayedAssignment) return;
13754
+ Keyboard.dismiss();
13732
13755
  await client.passAssignment(displayedAssignment.id);
13733
13756
  await refreshAssignments();
13734
13757
  nav.replace({ name: "TEST_FEEDBACK", status: "passed", assignmentId: displayedAssignment.id });
13735
13758
  }, [client, displayedAssignment, refreshAssignments, nav]);
13736
13759
  const handleFail = useCallback2(async () => {
13737
13760
  if (!client || !displayedAssignment) return;
13761
+ Keyboard.dismiss();
13738
13762
  await client.failAssignment(displayedAssignment.id);
13739
13763
  await refreshAssignments();
13740
13764
  nav.replace({
@@ -13748,6 +13772,7 @@ function TestDetailScreen({ testId, nav }) {
13748
13772
  }, [client, displayedAssignment, refreshAssignments, nav]);
13749
13773
  const handleSkip = useCallback2(async () => {
13750
13774
  if (!client || !displayedAssignment || !selectedSkipReason) return;
13775
+ Keyboard.dismiss();
13751
13776
  setSkipping(true);
13752
13777
  await client.skipAssignment(displayedAssignment.id, {
13753
13778
  reason: selectedSkipReason,
@@ -13816,7 +13841,9 @@ function TestDetailScreen({ testId, nav }) {
13816
13841
  {
13817
13842
  style: styles2.navigateButton,
13818
13843
  onPress: () => {
13844
+ Keyboard.dismiss();
13819
13845
  onNavigate(testCase.targetRoute);
13846
+ nav.closeWidget?.();
13820
13847
  }
13821
13848
  },
13822
13849
  /* @__PURE__ */ React3.createElement(Text2, { style: styles2.navigateText }, "\u{1F9ED} Go to test location")
@@ -14957,10 +14984,29 @@ function BugBearButton({
14957
14984
  }
14958
14985
  };
14959
14986
  const handleClose = () => {
14987
+ Keyboard2.dismiss();
14960
14988
  setModalVisible(false);
14961
- reset();
14962
14989
  };
14963
- const nav = { push, pop, replace, reset, canGoBack };
14990
+ const nav = {
14991
+ push: (screen) => {
14992
+ Keyboard2.dismiss();
14993
+ push(screen);
14994
+ },
14995
+ pop: () => {
14996
+ Keyboard2.dismiss();
14997
+ pop();
14998
+ },
14999
+ replace: (screen) => {
15000
+ Keyboard2.dismiss();
15001
+ replace(screen);
15002
+ },
15003
+ reset: () => {
15004
+ Keyboard2.dismiss();
15005
+ reset();
15006
+ },
15007
+ canGoBack,
15008
+ closeWidget: handleClose
15009
+ };
14964
15010
  const renderScreen = () => {
14965
15011
  switch (currentScreen.name) {
14966
15012
  case "HOME":
@@ -15017,7 +15063,7 @@ function BugBearButton({
15017
15063
  behavior: Platform4.OS === "ios" ? "padding" : "height",
15018
15064
  style: styles13.modalOverlay
15019
15065
  },
15020
- /* @__PURE__ */ React14.createElement(View13, { style: styles13.modalContainer }, /* @__PURE__ */ React14.createElement(View13, { style: styles13.header }, /* @__PURE__ */ React14.createElement(View13, { style: styles13.headerLeft }, canGoBack ? /* @__PURE__ */ React14.createElement(TouchableOpacity12, { onPress: pop, style: styles13.backButton }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.backText }, "\u2190 Back")) : /* @__PURE__ */ React14.createElement(View13, { style: styles13.headerTitleRow }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.headerTitle }, "BugBear"), testerInfo && /* @__PURE__ */ React14.createElement(TouchableOpacity12, { onPress: () => push({ name: "PROFILE" }) }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.headerName }, testerInfo.name, " \u270E")))), getHeaderTitle() ? /* @__PURE__ */ React14.createElement(Text13, { style: styles13.headerScreenTitle, numberOfLines: 1 }, getHeaderTitle()) : null, /* @__PURE__ */ React14.createElement(TouchableOpacity12, { onPress: handleClose, style: styles13.closeButton }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.closeText }, "\u2715"))), /* @__PURE__ */ React14.createElement(
15066
+ /* @__PURE__ */ React14.createElement(View13, { style: styles13.modalContainer }, /* @__PURE__ */ React14.createElement(View13, { style: styles13.header }, /* @__PURE__ */ React14.createElement(View13, { style: styles13.headerLeft }, canGoBack ? /* @__PURE__ */ React14.createElement(View13, { style: styles13.headerNavRow }, /* @__PURE__ */ React14.createElement(TouchableOpacity12, { onPress: () => nav.pop(), style: styles13.backButton }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.backText }, "\u2190 Back")), /* @__PURE__ */ React14.createElement(TouchableOpacity12, { onPress: () => nav.reset(), style: styles13.homeButton }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.homeText }, "\u{1F3E0}"))) : /* @__PURE__ */ React14.createElement(View13, { style: styles13.headerTitleRow }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.headerTitle }, "BugBear"), testerInfo && /* @__PURE__ */ React14.createElement(TouchableOpacity12, { onPress: () => push({ name: "PROFILE" }) }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.headerName }, testerInfo.name, " \u270E")))), getHeaderTitle() ? /* @__PURE__ */ React14.createElement(Text13, { style: styles13.headerScreenTitle, numberOfLines: 1 }, getHeaderTitle()) : null, /* @__PURE__ */ React14.createElement(TouchableOpacity12, { onPress: handleClose, style: styles13.closeButton }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.closeText }, "\u2715"))), /* @__PURE__ */ React14.createElement(
15021
15067
  ScrollView2,
15022
15068
  {
15023
15069
  style: styles13.content,
@@ -15118,15 +15164,27 @@ var styles13 = StyleSheet14.create({
15118
15164
  flex: 1,
15119
15165
  textAlign: "center"
15120
15166
  },
15167
+ headerNavRow: {
15168
+ flexDirection: "row",
15169
+ alignItems: "center",
15170
+ gap: 8
15171
+ },
15121
15172
  backButton: {
15122
15173
  paddingVertical: 2,
15123
- paddingRight: 12
15174
+ paddingRight: 4
15124
15175
  },
15125
15176
  backText: {
15126
15177
  fontSize: 15,
15127
15178
  color: colors.blue,
15128
15179
  fontWeight: "500"
15129
15180
  },
15181
+ homeButton: {
15182
+ paddingVertical: 2,
15183
+ paddingHorizontal: 6
15184
+ },
15185
+ homeText: {
15186
+ fontSize: 16
15187
+ },
15130
15188
  closeButton: {
15131
15189
  width: 32,
15132
15190
  height: 32,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bbearai/react-native",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
4
4
  "description": "BugBear React Native components for mobile apps",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",