yaver-feedback-react-native 0.8.1 → 0.8.2

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.
@@ -4,6 +4,14 @@ export interface FeedbackEvent {
4
4
  timestamp: string;
5
5
  data: any;
6
6
  }
7
+ export interface ReloadAck {
8
+ ok: boolean;
9
+ mode: 'dev' | 'bundle';
10
+ acknowledged: boolean;
11
+ message: string;
12
+ nativeChangesDetected?: boolean;
13
+ changeClass?: string;
14
+ }
7
15
  /**
8
16
  * Lightweight P2P HTTP client for communicating with a Yaver agent.
9
17
  *
@@ -69,9 +77,7 @@ export declare class P2PClient {
69
77
  projectName?: string;
70
78
  bundleId?: string;
71
79
  projectPath?: string;
72
- }): Promise<{
73
- ok: boolean;
74
- }>;
80
+ }): Promise<ReloadAck>;
75
81
  /**
76
82
  * Open a vibing session on the connected agent. Vibing is the Yaver
77
83
  * interactive coding-agent flow — `/vibing/execute` creates a task with
package/dist/P2PClient.js CHANGED
@@ -291,7 +291,18 @@ class P2PClient {
291
291
  headers: { Authorization: `Bearer ${this.authToken}` },
292
292
  });
293
293
  if (primary.ok) {
294
- return primary.json().catch(() => ({ ok: true }));
294
+ const payload = await primary.json().catch(() => ({}));
295
+ const nativeChangesDetected = payload.nativeChangesDetected === true;
296
+ return {
297
+ ok: true,
298
+ mode: 'dev',
299
+ acknowledged: true,
300
+ nativeChangesDetected,
301
+ changeClass: typeof payload.changeClass === 'string' ? payload.changeClass : undefined,
302
+ message: nativeChangesDetected
303
+ ? 'Reload accepted, but native changes need a rebuild.'
304
+ : 'Hot reload request accepted.',
305
+ };
295
306
  }
296
307
  // Dev mode failed — fall through to bundle rebuild below rather
297
308
  // than surfacing the raw error, so the user never has to know
@@ -321,7 +332,17 @@ class P2PClient {
321
332
  const text = await res.text().catch(() => '');
322
333
  throw new Error(friendlyReloadError(res.status, text));
323
334
  }
324
- return res.json().catch(() => ({ ok: true }));
335
+ const payload = await res.json().catch(() => ({}));
336
+ return {
337
+ ok: true,
338
+ mode: 'bundle',
339
+ acknowledged: true,
340
+ changeClass: typeof payload.changeClass === 'string' ? payload.changeClass : undefined,
341
+ nativeChangesDetected: payload.nativeChangesDetected === true,
342
+ message: typeof payload.message === 'string' && payload.message.trim()
343
+ ? payload.message
344
+ : 'Reload request acknowledged. Agent is rebuilding the bundle.',
345
+ };
325
346
  }
326
347
  /**
327
348
  * Open a vibing session on the connected agent. Vibing is the Yaver
@@ -111,6 +111,7 @@ const QuickActionIcon = ({ color: colorProp, backgroundColor: backgroundColorPro
111
111
  const dragStart = (0, react_1.useRef)(null);
112
112
  const didDrag = (0, react_1.useRef)(false);
113
113
  const [userDisabled, setUserDisabled] = (0, react_1.useState)(null);
114
+ const [colorPreset, setColorPreset] = (0, react_1.useState)(null);
114
115
  const [shakenThisSession, setShakenThisSession] = (0, react_1.useState)(false);
115
116
  const [menuOpen, setMenuOpen] = (0, react_1.useState)(false);
116
117
  const [hostSuppressed] = (0, react_1.useState)(() => isRunningInsideYaverHost());
@@ -123,6 +124,10 @@ const QuickActionIcon = ({ color: colorProp, backgroundColor: backgroundColorPro
123
124
  if (alive)
124
125
  setUserDisabled(v);
125
126
  });
127
+ (0, preferences_1.getQuickIconColorPreset)().then((v) => {
128
+ if (alive)
129
+ setColorPreset(v);
130
+ });
126
131
  return () => {
127
132
  alive = false;
128
133
  };
@@ -146,9 +151,15 @@ const QuickActionIcon = ({ color: colorProp, backgroundColor: backgroundColorPro
146
151
  void (0, preferences_1.setQuickIconDisabled)(true);
147
152
  setMenuOpen(false);
148
153
  });
154
+ const colorSub = react_native_1.DeviceEventEmitter.addListener('yaverFeedback:quickIconColorChange', (next) => {
155
+ const preset = next?.preset ?? null;
156
+ setColorPreset(preset);
157
+ void (0, preferences_1.setQuickIconColorPreset)(preset);
158
+ });
149
159
  return () => {
150
160
  showSub.remove();
151
161
  hideSub.remove();
162
+ colorSub.remove();
152
163
  };
153
164
  }, []);
154
165
  const panResponder = (0, react_1.useRef)(react_native_1.PanResponder.create({
@@ -204,6 +215,7 @@ const QuickActionIcon = ({ color: colorProp, backgroundColor: backgroundColorPro
204
215
  return null;
205
216
  if (!YaverFeedback_1.YaverFeedback.isEnabled())
206
217
  return null;
218
+ const presetColors = colorPreset ? preferences_1.QUICK_ICON_COLOR_PRESETS[colorPreset] : null;
207
219
  const visualSize = size;
208
220
  const radius = visualSize / 2;
209
221
  return (<react_native_1.Animated.View pointerEvents="box-none" style={[
@@ -232,16 +244,16 @@ const QuickActionIcon = ({ color: colorProp, backgroundColor: backgroundColorPro
232
244
  width: visualSize,
233
245
  height: visualSize,
234
246
  borderRadius: radius,
235
- backgroundColor,
236
- borderColor,
237
- shadowColor,
247
+ backgroundColor: presetColors?.backgroundColor ?? backgroundColor,
248
+ borderColor: presetColors?.borderColor ?? borderColor,
249
+ shadowColor: presetColors?.shadowColor ?? shadowColor,
238
250
  opacity: pressed ? 0.85 : 1,
239
251
  },
240
252
  ]}>
241
253
  <react_native_1.Text style={[
242
254
  styles.iconLabel,
243
255
  {
244
- color: foregroundColor,
256
+ color: presetColors?.foregroundColor ?? foregroundColor,
245
257
  fontSize: Math.round(visualSize * 0.5),
246
258
  },
247
259
  ]}>
@@ -1,5 +1,6 @@
1
1
  import { FeedbackConfig, CapturedError } from './types';
2
2
  import { P2PClient } from './P2PClient';
3
+ import { QuickIconColorPreset } from './preferences';
3
4
  /**
4
5
  * Main entry point for the Yaver Feedback SDK.
5
6
  * Call `YaverFeedback.init()` once at app startup.
@@ -60,6 +61,8 @@ export declare class YaverFeedback {
60
61
  * newly-selected machine.
61
62
  */
62
63
  static setPreferredDevice(deviceId: string): Promise<void>;
64
+ /** Resolve the currently selected remote machine from the authenticated device list. */
65
+ static getSelectedRemoteDevice(): Promise<import("./auth").RemoteDevice | null>;
63
66
  /**
64
67
  * Sign out: clear cached token + device, tear down the P2P client. The
65
68
  * SDK stays enabled; the next feedback trigger will re-prompt for login.
@@ -223,6 +226,8 @@ export declare class YaverFeedback {
223
226
  * about the programmatic API.
224
227
  */
225
228
  static isQuickIconHidden(): Promise<boolean>;
229
+ static setQuickIconColorPreset(preset: QuickIconColorPreset | null): Promise<void>;
230
+ static getQuickIconColorPreset(): Promise<QuickIconColorPreset | null>;
226
231
  /** Clear the persisted "user hid the icon" flag. */
227
232
  static resetQuickIconPreference(): Promise<void>;
228
233
  /** Tear down the SDK (stop shake detector, clear state). */
@@ -346,6 +346,15 @@ class YaverFeedback {
346
346
  p2pClient = null;
347
347
  await YaverFeedback.discoverAgent();
348
348
  }
349
+ /** Resolve the currently selected remote machine from the authenticated device list. */
350
+ static async getSelectedRemoteDevice() {
351
+ if (!config?.authToken || !config.preferredDeviceId)
352
+ return null;
353
+ const preferredDeviceId = config.preferredDeviceId;
354
+ const devices = await (0, auth_1.listReachableDevices)(config.authToken);
355
+ const all = [...devices.owned, ...devices.shared];
356
+ return all.find((device) => device.deviceId === preferredDeviceId) ?? null;
357
+ }
349
358
  /**
350
359
  * Sign out: clear cached token + device, tear down the P2P client. The
351
360
  * SDK stays enabled; the next feedback trigger will re-prompt for login.
@@ -809,6 +818,19 @@ class YaverFeedback {
809
818
  static async isQuickIconHidden() {
810
819
  return (0, preferences_1.getQuickIconDisabled)();
811
820
  }
821
+ static async setQuickIconColorPreset(preset) {
822
+ await (0, preferences_1.setQuickIconColorPreset)(preset);
823
+ try {
824
+ const { DeviceEventEmitter } = require('react-native');
825
+ DeviceEventEmitter.emit('yaverFeedback:quickIconColorChange', { preset });
826
+ }
827
+ catch {
828
+ // emitter unavailable — preference is still persisted
829
+ }
830
+ }
831
+ static async getQuickIconColorPreset() {
832
+ return (0, preferences_1.getQuickIconColorPreset)();
833
+ }
812
834
  /** Clear the persisted "user hid the icon" flag. */
813
835
  static async resetQuickIconPreference() {
814
836
  await YaverFeedback.setQuickIconVisible(true);
@@ -166,4 +166,34 @@ describe('P2PClient', () => {
166
166
  expect(result).toEqual(builds);
167
167
  });
168
168
  });
169
+ describe('reloadApp()', () => {
170
+ it('returns an acknowledgement for dev reloads', async () => {
171
+ mockFetch.mockResolvedValue({
172
+ ok: true,
173
+ json: () => Promise.resolve({ ok: true, changeClass: 'js_only' }),
174
+ });
175
+ const client = new P2PClient_1.P2PClient('http://localhost:18080', 'tok');
176
+ const result = await client.reloadApp('dev');
177
+ expect(result).toEqual(expect.objectContaining({
178
+ ok: true,
179
+ mode: 'dev',
180
+ acknowledged: true,
181
+ message: 'Hot reload request accepted.',
182
+ }));
183
+ });
184
+ it('returns an acknowledgement for bundle reloads', async () => {
185
+ mockFetch.mockResolvedValue({
186
+ ok: true,
187
+ json: () => Promise.resolve({ ok: true }),
188
+ });
189
+ const client = new P2PClient_1.P2PClient('http://localhost:18080', 'tok');
190
+ const result = await client.reloadApp('bundle');
191
+ expect(result).toEqual(expect.objectContaining({
192
+ ok: true,
193
+ mode: 'bundle',
194
+ acknowledged: true,
195
+ message: 'Reload request acknowledged. Agent is rebuilding the bundle.',
196
+ }));
197
+ });
198
+ });
169
199
  });
@@ -16,6 +16,33 @@ jest.mock('../Discovery', () => ({
16
16
  discover: jest.fn(),
17
17
  },
18
18
  }));
19
+ jest.mock('../auth', () => ({
20
+ configureAuthEndpoints: jest.fn(),
21
+ setStrictNativeAuth: jest.fn(),
22
+ getToken: jest.fn(async () => null),
23
+ getSelectedDeviceId: jest.fn(async () => null),
24
+ clearToken: jest.fn(async () => { }),
25
+ clearSelectedDeviceId: jest.fn(async () => { }),
26
+ listReachableDevices: jest.fn(async () => ({
27
+ owned: [
28
+ {
29
+ deviceId: 'device-1',
30
+ name: 'Dev Mac',
31
+ platform: 'darwin',
32
+ isOnline: true,
33
+ needsAuth: false,
34
+ runnerDown: false,
35
+ lastHeartbeat: Date.now(),
36
+ isGuest: false,
37
+ accessScope: 'owner',
38
+ quicHost: '127.0.0.1',
39
+ quicPort: 18080,
40
+ },
41
+ ],
42
+ shared: [],
43
+ })),
44
+ DEFAULT_CONVEX_SITE_URL: 'https://example.convex.site',
45
+ }));
19
46
  // Reset module-level state between tests by re-requiring
20
47
  beforeEach(() => {
21
48
  // YaverFeedback uses module-level variables (config, enabled, p2pClient).
@@ -106,6 +133,18 @@ describe('YaverFeedback', () => {
106
133
  expect(cfg.agentUrl).toBe('http://10.0.0.1:18080');
107
134
  });
108
135
  });
136
+ describe('getSelectedRemoteDevice()', () => {
137
+ it('returns the selected device from the reachable device list', async () => {
138
+ YaverFeedback_1.YaverFeedback.init({
139
+ authToken: 'tok',
140
+ preferredDeviceId: 'device-1',
141
+ enabled: true,
142
+ });
143
+ const device = await YaverFeedback_1.YaverFeedback.getSelectedRemoteDevice();
144
+ expect(device?.deviceId).toBe('device-1');
145
+ expect(device?.name).toBe('Dev Mac');
146
+ });
147
+ });
109
148
  describe('startReport()', () => {
110
149
  it('does nothing when not enabled', async () => {
111
150
  YaverFeedback_1.YaverFeedback.init({ authToken: 'tok', enabled: false });
package/dist/index.d.ts CHANGED
@@ -1,12 +1,11 @@
1
1
  /**
2
2
  * yaver-feedback-react-native — Visual feedback SDK for Yaver.
3
3
  *
4
- * Shake-to-report surface with four core actions:
4
+ * Shake-to-report surface with three launch actions:
5
5
  * 1. Hot Reload — instant JS reload
6
6
  * 2. Vibing — open a vibing session on the agent
7
- * 3. Screenshot / Upload — capture the screen under the modal or
8
- * upload existing media
9
- * 4. Screen Recording — start, then stop + upload
7
+ * 3. Screenshot & Fix — capture the current screen and trigger
8
+ * the fix loop
10
9
  *
11
10
  * The small quick-access icon stays hidden until the first shake by
12
11
  * default on mobile, then remains available unless the user hides it.
package/dist/index.js CHANGED
@@ -2,12 +2,11 @@
2
2
  /**
3
3
  * yaver-feedback-react-native — Visual feedback SDK for Yaver.
4
4
  *
5
- * Shake-to-report surface with four core actions:
5
+ * Shake-to-report surface with three launch actions:
6
6
  * 1. Hot Reload — instant JS reload
7
7
  * 2. Vibing — open a vibing session on the agent
8
- * 3. Screenshot / Upload — capture the screen under the modal or
9
- * upload existing media
10
- * 4. Screen Recording — start, then stop + upload
8
+ * 3. Screenshot & Fix — capture the current screen and trigger
9
+ * the fix loop
11
10
  *
12
11
  * The small quick-access icon stays hidden until the first shake by
13
12
  * default on mobile, then remains available unless the user hides it.
@@ -12,7 +12,18 @@
12
12
  * still works (it just can't remember the disable beyond the
13
13
  * in-memory session).
14
14
  */
15
+ export type QuickIconColorPreset = 'orange' | 'lime' | 'cyan' | 'pink' | 'yellow' | 'slate';
16
+ export declare const QUICK_ICON_COLOR_PRESETS: Record<QuickIconColorPreset, {
17
+ label: string;
18
+ backgroundColor: string;
19
+ foregroundColor: string;
20
+ borderColor: string;
21
+ shadowColor: string;
22
+ }>;
15
23
  /** True if the user has long-pressed the icon and chosen "Hide". */
16
24
  export declare function getQuickIconDisabled(): Promise<boolean>;
17
25
  export declare function setQuickIconDisabled(disabled: boolean): Promise<void>;
18
26
  export declare function clearQuickIconDisabled(): Promise<void>;
27
+ export declare function getQuickIconColorPreset(): Promise<QuickIconColorPreset | null>;
28
+ export declare function setQuickIconColorPreset(preset: QuickIconColorPreset | null): Promise<void>;
29
+ export declare function clearQuickIconColorPreset(): Promise<void>;
@@ -14,9 +14,13 @@
14
14
  * in-memory session).
15
15
  */
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.QUICK_ICON_COLOR_PRESETS = void 0;
17
18
  exports.getQuickIconDisabled = getQuickIconDisabled;
18
19
  exports.setQuickIconDisabled = setQuickIconDisabled;
19
20
  exports.clearQuickIconDisabled = clearQuickIconDisabled;
21
+ exports.getQuickIconColorPreset = getQuickIconColorPreset;
22
+ exports.setQuickIconColorPreset = setQuickIconColorPreset;
23
+ exports.clearQuickIconColorPreset = clearQuickIconColorPreset;
20
24
  let AsyncStorage = null;
21
25
  try {
22
26
  AsyncStorage = require('@react-native-async-storage/async-storage').default;
@@ -25,6 +29,51 @@ catch {
25
29
  // not installed — degrade gracefully
26
30
  }
27
31
  const QUICK_ICON_DISABLED_KEY = 'yaver_feedback_quickicon_disabled';
32
+ const QUICK_ICON_COLOR_KEY = 'yaver_feedback_quickicon_color';
33
+ exports.QUICK_ICON_COLOR_PRESETS = {
34
+ orange: {
35
+ label: 'Orange',
36
+ backgroundColor: '#ff6b2c',
37
+ foregroundColor: '#111111',
38
+ borderColor: 'rgba(255,255,255,0.92)',
39
+ shadowColor: '#000000',
40
+ },
41
+ lime: {
42
+ label: 'Lime',
43
+ backgroundColor: '#a3e635',
44
+ foregroundColor: '#111111',
45
+ borderColor: 'rgba(255,255,255,0.85)',
46
+ shadowColor: '#365314',
47
+ },
48
+ cyan: {
49
+ label: 'Cyan',
50
+ backgroundColor: '#22d3ee',
51
+ foregroundColor: '#082f49',
52
+ borderColor: 'rgba(255,255,255,0.82)',
53
+ shadowColor: '#083344',
54
+ },
55
+ pink: {
56
+ label: 'Pink',
57
+ backgroundColor: '#fb7185',
58
+ foregroundColor: '#fff1f2',
59
+ borderColor: 'rgba(255,255,255,0.78)',
60
+ shadowColor: '#4c0519',
61
+ },
62
+ yellow: {
63
+ label: 'Yellow',
64
+ backgroundColor: '#facc15',
65
+ foregroundColor: '#1c1917',
66
+ borderColor: 'rgba(255,255,255,0.88)',
67
+ shadowColor: '#713f12',
68
+ },
69
+ slate: {
70
+ label: 'Slate',
71
+ backgroundColor: '#475569',
72
+ foregroundColor: '#f8fafc',
73
+ borderColor: 'rgba(255,255,255,0.68)',
74
+ shadowColor: '#020617',
75
+ },
76
+ };
28
77
  /** True if the user has long-pressed the icon and chosen "Hide". */
29
78
  async function getQuickIconDisabled() {
30
79
  if (!AsyncStorage)
@@ -55,3 +104,36 @@ async function setQuickIconDisabled(disabled) {
55
104
  async function clearQuickIconDisabled() {
56
105
  await setQuickIconDisabled(false);
57
106
  }
107
+ async function getQuickIconColorPreset() {
108
+ if (!AsyncStorage)
109
+ return null;
110
+ try {
111
+ const v = await AsyncStorage.getItem(QUICK_ICON_COLOR_KEY);
112
+ if (!v)
113
+ return null;
114
+ if (Object.prototype.hasOwnProperty.call(exports.QUICK_ICON_COLOR_PRESETS, v)) {
115
+ return v;
116
+ }
117
+ return null;
118
+ }
119
+ catch {
120
+ return null;
121
+ }
122
+ }
123
+ async function setQuickIconColorPreset(preset) {
124
+ if (!AsyncStorage)
125
+ return;
126
+ try {
127
+ if (!preset) {
128
+ await AsyncStorage.removeItem(QUICK_ICON_COLOR_KEY);
129
+ return;
130
+ }
131
+ await AsyncStorage.setItem(QUICK_ICON_COLOR_KEY, preset);
132
+ }
133
+ catch {
134
+ // best-effort
135
+ }
136
+ }
137
+ async function clearQuickIconColorPreset() {
138
+ await setQuickIconColorPreset(null);
139
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yaver-feedback-react-native",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "Visual feedback SDK for Yaver — bug reports, screen recording, voice annotations, and local-first developer workflows",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -40,15 +40,17 @@
40
40
  }
41
41
  },
42
42
  "devDependencies": {
43
- "jest": "^29.0.0",
44
43
  "@types/jest": "^29.0.0",
44
+ "@types/react": "^19.2.2",
45
+ "jest": "^29.0.0",
45
46
  "ts-jest": "^29.1.0",
46
47
  "typescript": "^5.0.0"
47
48
  },
48
49
  "scripts": {
49
- "build": "rm -rf dist && (tsc || true) && test -f dist/index.js",
50
+ "build": "rm -rf dist && (tsc -p tsconfig.json || true) && test -f dist/index.js",
50
51
  "prepublishOnly": "npm run build",
51
- "test": "jest"
52
+ "test": "jest --runInBand",
53
+ "test:ci": "npm run build && npm test"
52
54
  },
53
55
  "jest": {
54
56
  "preset": "ts-jest",
@@ -87,6 +87,7 @@ export const AuthOverlay: React.FC = () => {
87
87
  {token && (
88
88
  <YaverMachinePickerScreen
89
89
  token={token}
90
+ currentDeviceId={YaverFeedback.getConfig()?.preferredDeviceId}
90
91
  onPick={handleDevicePicked}
91
92
  onCancel={() => setPickerVisible(false)}
92
93
  />