yaver-feedback-react-native 0.9.8 → 0.9.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.
@@ -111,9 +111,16 @@ const DogfoodQuickControls = () => {
111
111
  const capability = react_native_1.DeviceEventEmitter.addListener('yaverDogfoodControlCapability', () => {
112
112
  void refresh();
113
113
  });
114
+ // Enrollment approval and Exit Dogfood both change SDK mode while this
115
+ // component remains mounted. Refresh React state immediately; native
116
+ // setEnabled() alone cannot make the fallback Y appear or disappear.
117
+ const mode = react_native_1.DeviceEventEmitter.addListener('yaverFeedback:dogfoodChanged', () => {
118
+ void refresh();
119
+ });
114
120
  return () => {
115
121
  trigger.remove();
116
122
  capability.remove();
123
+ mode.remove();
117
124
  };
118
125
  }, [refresh]);
119
126
  (0, react_1.useEffect)(() => {
@@ -1090,6 +1090,16 @@ class YaverFeedback {
1090
1090
  }
1091
1091
  return base;
1092
1092
  }
1093
+ // Backend approval makes the installation eligible; it does not mean the
1094
+ // tester is currently in Dogfood. Keep all in-app controls absent until
1095
+ // enableDeviceDogfood() has minted a live scoped session, and make Exit
1096
+ // Dogfood actually remove both the gesture and fallback Y.
1097
+ if (!YaverFeedback.getDogfoodStatus().active) {
1098
+ if (typeof native?.setEnabled === 'function') {
1099
+ await native.setEnabled(false, 900).catch(() => undefined);
1100
+ }
1101
+ return { ...base, reason: 'dogfood-session-inactive' };
1102
+ }
1093
1103
  if (IS_HOST_MODE || isRunningInsideYaverHost()) {
1094
1104
  if (typeof native?.setEnabled === 'function') {
1095
1105
  await native.setEnabled(false, 900).catch(() => undefined);
@@ -69,6 +69,25 @@ beforeEach(() => {
69
69
  jest.restoreAllMocks();
70
70
  jest.clearAllMocks();
71
71
  });
72
+ function activeDogfood(controlGesture = true) {
73
+ return {
74
+ enabled: true,
75
+ appId: 'io.example.app',
76
+ installationId: 'phone-1',
77
+ installationStatus: 'active',
78
+ controlGesture,
79
+ };
80
+ }
81
+ function initActiveDogfood(options = {}) {
82
+ const controlGesture = options.controlGesture ?? true;
83
+ YaverFeedback_1.YaverFeedback.init({
84
+ authToken: options.authToken,
85
+ bundleId: 'io.example.app',
86
+ dogfood: { controlGesture },
87
+ });
88
+ Object.assign(YaverFeedback_1.YaverFeedback.getConfig().dogfood, activeDogfood(controlGesture));
89
+ jest.clearAllMocks();
90
+ }
72
91
  describe('YaverFeedback', () => {
73
92
  describe('Dogfood onboarding', () => {
74
93
  it('starts with Yaver OAuth when the host has no session', async () => {
@@ -175,8 +194,7 @@ describe('YaverFeedback', () => {
175
194
  .toHaveBeenCalledWith(false, 'Dogfood');
176
195
  });
177
196
  it('keeps the Y visible until the authorized phone has completed onboarding', async () => {
178
- YaverFeedback_1.YaverFeedback.init({ bundleId: 'io.example.app', dogfood: {} });
179
- YaverFeedback_1.YaverFeedback.getConfig().dogfood.controlGesture = true;
197
+ initActiveDogfood();
180
198
  jest.spyOn(YaverFeedback_1.YaverFeedback, 'getDogfoodAccess').mockResolvedValueOnce({
181
199
  appId: 'io.example.app',
182
200
  yaverAuthenticated: true,
@@ -197,8 +215,7 @@ describe('YaverFeedback', () => {
197
215
  expect(react_native_1.NativeModules.YaverDogfoodGesture.setEnabled).toHaveBeenCalledWith(false, 900);
198
216
  });
199
217
  it('uses the invisible three-finger hold only after onboarding selects it', async () => {
200
- YaverFeedback_1.YaverFeedback.init({ bundleId: 'io.example.app', dogfood: {} });
201
- YaverFeedback_1.YaverFeedback.getConfig().dogfood.controlGesture = true;
218
+ initActiveDogfood();
202
219
  jest.spyOn(YaverFeedback_1.YaverFeedback, 'getDogfoodAccess').mockResolvedValueOnce({
203
220
  appId: 'io.example.app',
204
221
  yaverAuthenticated: true,
@@ -221,8 +238,7 @@ describe('YaverFeedback', () => {
221
238
  expect(react_native_1.NativeModules.YaverDogfoodGesture.setEnabled).toHaveBeenCalledWith(true, 900);
222
239
  });
223
240
  it('keeps the Y when native capability reports support but enabling the gesture fails', async () => {
224
- YaverFeedback_1.YaverFeedback.init({ bundleId: 'io.example.app', dogfood: {} });
225
- YaverFeedback_1.YaverFeedback.getConfig().dogfood.controlGesture = true;
241
+ initActiveDogfood();
226
242
  jest.spyOn(YaverFeedback_1.YaverFeedback, 'getDogfoodAccess').mockResolvedValueOnce({
227
243
  appId: 'io.example.app',
228
244
  yaverAuthenticated: true,
@@ -249,8 +265,7 @@ describe('YaverFeedback', () => {
249
265
  });
250
266
  });
251
267
  it('persists first-run completion for the exact account app installation', async () => {
252
- YaverFeedback_1.YaverFeedback.init({ authToken: 'owner-token', bundleId: 'io.example.app', dogfood: {} });
253
- YaverFeedback_1.YaverFeedback.getConfig().dogfood.controlGesture = true;
268
+ initActiveDogfood({ authToken: 'owner-token' });
254
269
  jest.spyOn(YaverFeedback_1.YaverFeedback, 'getDogfoodAccess').mockResolvedValueOnce({
255
270
  appId: 'io.example.app',
256
271
  yaverAuthenticated: true,
@@ -272,8 +287,7 @@ describe('YaverFeedback', () => {
272
287
  }));
273
288
  });
274
289
  it('falls back to the minimized Y when accessibility owns multi-touch', async () => {
275
- YaverFeedback_1.YaverFeedback.init({ bundleId: 'io.example.app', dogfood: {} });
276
- YaverFeedback_1.YaverFeedback.getConfig().dogfood.controlGesture = { durationMs: 1200 };
290
+ initActiveDogfood({ controlGesture: { durationMs: 1200 } });
277
291
  jest.spyOn(YaverFeedback_1.YaverFeedback, 'getDogfoodAccess').mockResolvedValueOnce({
278
292
  appId: 'io.example.app',
279
293
  yaverAuthenticated: true,
@@ -294,13 +308,24 @@ describe('YaverFeedback', () => {
294
308
  expect(react_native_1.NativeModules.YaverDogfoodGesture.setEnabled).toHaveBeenCalledWith(false, 1200);
295
309
  });
296
310
  it('suppresses guest controls inside the Yaver split-view container', async () => {
297
- YaverFeedback_1.YaverFeedback.init({ bundleId: 'io.example.app', dogfood: {} });
298
- YaverFeedback_1.YaverFeedback.getConfig().dogfood.controlGesture = true;
311
+ initActiveDogfood();
299
312
  react_native_1.NativeModules.YaverInfo = { isYaver: true };
300
313
  const state = await YaverFeedback_1.YaverFeedback.syncDogfoodControlGesture();
301
314
  delete react_native_1.NativeModules.YaverInfo;
302
315
  expect(state).toMatchObject({ gestureEnabled: false, fallbackVisible: false, reason: 'yaver-host-owns-controls' });
303
316
  });
317
+ it('removes the gesture and fallback Y when Dogfood exits', async () => {
318
+ initActiveDogfood();
319
+ await YaverFeedback_1.YaverFeedback.exitDogfoodMode();
320
+ const state = await YaverFeedback_1.YaverFeedback.syncDogfoodControlGesture();
321
+ expect(state).toMatchObject({
322
+ authorized: false,
323
+ gestureEnabled: false,
324
+ fallbackVisible: false,
325
+ reason: 'dogfood-session-inactive',
326
+ });
327
+ expect(react_native_1.NativeModules.YaverDogfoodGesture.setEnabled).toHaveBeenCalledWith(false, 900);
328
+ });
304
329
  });
305
330
  describe('init()', () => {
306
331
  it('sets config correctly with defaults', () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yaver-feedback-react-native",
3
- "version": "0.9.8",
3
+ "version": "0.9.9",
4
4
  "description": "Visual feedback SDK for Yaver — bug reports, screen recording, voice vibe coding, and local-first developer workflows",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -95,9 +95,16 @@ export const DogfoodQuickControls: React.FC = () => {
95
95
  const capability = DeviceEventEmitter.addListener('yaverDogfoodControlCapability', () => {
96
96
  void refresh();
97
97
  });
98
+ // Enrollment approval and Exit Dogfood both change SDK mode while this
99
+ // component remains mounted. Refresh React state immediately; native
100
+ // setEnabled() alone cannot make the fallback Y appear or disappear.
101
+ const mode = DeviceEventEmitter.addListener('yaverFeedback:dogfoodChanged', () => {
102
+ void refresh();
103
+ });
98
104
  return () => {
99
105
  trigger.remove();
100
106
  capability.remove();
107
+ mode.remove();
101
108
  };
102
109
  }, [refresh]);
103
110
 
@@ -1077,6 +1077,16 @@ export class YaverFeedback {
1077
1077
  }
1078
1078
  return base;
1079
1079
  }
1080
+ // Backend approval makes the installation eligible; it does not mean the
1081
+ // tester is currently in Dogfood. Keep all in-app controls absent until
1082
+ // enableDeviceDogfood() has minted a live scoped session, and make Exit
1083
+ // Dogfood actually remove both the gesture and fallback Y.
1084
+ if (!YaverFeedback.getDogfoodStatus().active) {
1085
+ if (typeof native?.setEnabled === 'function') {
1086
+ await native.setEnabled(false, 900).catch(() => undefined);
1087
+ }
1088
+ return { ...base, reason: 'dogfood-session-inactive' };
1089
+ }
1080
1090
  if (IS_HOST_MODE || isRunningInsideYaverHost()) {
1081
1091
  if (typeof native?.setEnabled === 'function') {
1082
1092
  await native.setEnabled(false, 900).catch(() => undefined);
@@ -72,6 +72,27 @@ beforeEach(() => {
72
72
  jest.clearAllMocks();
73
73
  });
74
74
 
75
+ function activeDogfood(controlGesture: true | { durationMs: number } = true) {
76
+ return {
77
+ enabled: true,
78
+ appId: 'io.example.app',
79
+ installationId: 'phone-1',
80
+ installationStatus: 'active' as const,
81
+ controlGesture,
82
+ };
83
+ }
84
+
85
+ function initActiveDogfood(options: { authToken?: string; controlGesture?: true | { durationMs: number } } = {}) {
86
+ const controlGesture = options.controlGesture ?? true;
87
+ YaverFeedback.init({
88
+ authToken: options.authToken,
89
+ bundleId: 'io.example.app',
90
+ dogfood: { controlGesture },
91
+ });
92
+ Object.assign(YaverFeedback.getConfig()!.dogfood!, activeDogfood(controlGesture));
93
+ jest.clearAllMocks();
94
+ }
95
+
75
96
  describe('YaverFeedback', () => {
76
97
  describe('Dogfood onboarding', () => {
77
98
  it('starts with Yaver OAuth when the host has no session', async () => {
@@ -187,8 +208,7 @@ describe('YaverFeedback', () => {
187
208
  });
188
209
 
189
210
  it('keeps the Y visible until the authorized phone has completed onboarding', async () => {
190
- YaverFeedback.init({ bundleId: 'io.example.app', dogfood: {} });
191
- YaverFeedback.getConfig()!.dogfood!.controlGesture = true;
211
+ initActiveDogfood();
192
212
  jest.spyOn(YaverFeedback, 'getDogfoodAccess').mockResolvedValueOnce({
193
213
  appId: 'io.example.app',
194
214
  yaverAuthenticated: true,
@@ -210,8 +230,7 @@ describe('YaverFeedback', () => {
210
230
  });
211
231
 
212
232
  it('uses the invisible three-finger hold only after onboarding selects it', async () => {
213
- YaverFeedback.init({ bundleId: 'io.example.app', dogfood: {} });
214
- YaverFeedback.getConfig()!.dogfood!.controlGesture = true;
233
+ initActiveDogfood();
215
234
  jest.spyOn(YaverFeedback, 'getDogfoodAccess').mockResolvedValueOnce({
216
235
  appId: 'io.example.app',
217
236
  yaverAuthenticated: true,
@@ -235,8 +254,7 @@ describe('YaverFeedback', () => {
235
254
  });
236
255
 
237
256
  it('keeps the Y when native capability reports support but enabling the gesture fails', async () => {
238
- YaverFeedback.init({ bundleId: 'io.example.app', dogfood: {} });
239
- YaverFeedback.getConfig()!.dogfood!.controlGesture = true;
257
+ initActiveDogfood();
240
258
  jest.spyOn(YaverFeedback, 'getDogfoodAccess').mockResolvedValueOnce({
241
259
  appId: 'io.example.app',
242
260
  yaverAuthenticated: true,
@@ -264,8 +282,7 @@ describe('YaverFeedback', () => {
264
282
  });
265
283
 
266
284
  it('persists first-run completion for the exact account app installation', async () => {
267
- YaverFeedback.init({ authToken: 'owner-token', bundleId: 'io.example.app', dogfood: {} });
268
- YaverFeedback.getConfig()!.dogfood!.controlGesture = true;
285
+ initActiveDogfood({ authToken: 'owner-token' });
269
286
  jest.spyOn(YaverFeedback, 'getDogfoodAccess').mockResolvedValueOnce({
270
287
  appId: 'io.example.app',
271
288
  yaverAuthenticated: true,
@@ -288,8 +305,7 @@ describe('YaverFeedback', () => {
288
305
  });
289
306
 
290
307
  it('falls back to the minimized Y when accessibility owns multi-touch', async () => {
291
- YaverFeedback.init({ bundleId: 'io.example.app', dogfood: {} });
292
- YaverFeedback.getConfig()!.dogfood!.controlGesture = { durationMs: 1200 };
308
+ initActiveDogfood({ controlGesture: { durationMs: 1200 } });
293
309
  jest.spyOn(YaverFeedback, 'getDogfoodAccess').mockResolvedValueOnce({
294
310
  appId: 'io.example.app',
295
311
  yaverAuthenticated: true,
@@ -311,13 +327,25 @@ describe('YaverFeedback', () => {
311
327
  });
312
328
 
313
329
  it('suppresses guest controls inside the Yaver split-view container', async () => {
314
- YaverFeedback.init({ bundleId: 'io.example.app', dogfood: {} });
315
- YaverFeedback.getConfig()!.dogfood!.controlGesture = true;
330
+ initActiveDogfood();
316
331
  (NativeModules as any).YaverInfo = { isYaver: true };
317
332
  const state = await YaverFeedback.syncDogfoodControlGesture();
318
333
  delete (NativeModules as any).YaverInfo;
319
334
  expect(state).toMatchObject({ gestureEnabled: false, fallbackVisible: false, reason: 'yaver-host-owns-controls' });
320
335
  });
336
+
337
+ it('removes the gesture and fallback Y when Dogfood exits', async () => {
338
+ initActiveDogfood();
339
+ await YaverFeedback.exitDogfoodMode();
340
+ const state = await YaverFeedback.syncDogfoodControlGesture();
341
+ expect(state).toMatchObject({
342
+ authorized: false,
343
+ gestureEnabled: false,
344
+ fallbackVisible: false,
345
+ reason: 'dogfood-session-inactive',
346
+ });
347
+ expect((NativeModules as any).YaverDogfoodGesture.setEnabled).toHaveBeenCalledWith(false, 900);
348
+ });
321
349
  });
322
350
 
323
351
  describe('init()', () => {