yaver-feedback-react-native 0.6.1 → 0.7.1

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.
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Device deduplication + online-signal merging, ported from the Yaver
3
+ * mobile app's DeviceContext.collapseAliasDevices.
4
+ *
5
+ * Convex stores one row per pair (device, re-install). After a re-pair
6
+ * or hostname change the list can contain 2-3 rows for the same
7
+ * physical machine, with different hwid/publicKey values. The picker
8
+ * then shows duplicates and the user can't tell which one is live.
9
+ *
10
+ * This file collapses rows in three passes:
11
+ * 1. Identity key (hwid → publicKey → "host:os:name" → id → name)
12
+ * 2. Alias key (os + normalized-hostname) — catches re-pairs that
13
+ * mint a new hwid
14
+ * 3. Endpoint key (host:port)
15
+ *
16
+ * When collapsing, `mergeDeviceEntries` prefers: authenticated over
17
+ * needsAuth, online over offline, freshest lastHeartbeat.
18
+ */
19
+ import type { RemoteDevice } from './auth';
20
+ /**
21
+ * Collapse a Convex device list so each physical machine appears once.
22
+ * Safe on an empty list; idempotent on an already-deduped list.
23
+ */
24
+ export declare function collapseRemoteDevices(devices: RemoteDevice[]): RemoteDevice[];
25
+ /**
26
+ * Threshold for "online" based on heartbeat age, in milliseconds.
27
+ * Matches the mobile app (HEARTBEAT_STALE_MS = 90 s). The SDK used
28
+ * 60 s, which flashed yellow on single missed beats.
29
+ */
30
+ export declare const HEARTBEAT_STALE_MS = 90000;
31
+ /**
32
+ * Returns a freshness flag consistent with the mobile app. A device is
33
+ * "fresh" when it was online per Convex AND its heartbeat is within
34
+ * `HEARTBEAT_STALE_MS`.
35
+ */
36
+ export declare function isDeviceFresh(d: RemoteDevice): boolean;
37
+ /**
38
+ * Pick the best candidate for an auto-connect attempt. Preference:
39
+ * 1. matches the preferred deviceId when supplied + still fresh
40
+ * 2. fresh (online + recent heartbeat) + has a quicHost
41
+ * 3. online + has a quicHost
42
+ * 4. first with a quicHost
43
+ */
44
+ export declare function pickTargetDevice(devices: RemoteDevice[], preferredDeviceId?: string): RemoteDevice | null;
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+ /**
3
+ * Device deduplication + online-signal merging, ported from the Yaver
4
+ * mobile app's DeviceContext.collapseAliasDevices.
5
+ *
6
+ * Convex stores one row per pair (device, re-install). After a re-pair
7
+ * or hostname change the list can contain 2-3 rows for the same
8
+ * physical machine, with different hwid/publicKey values. The picker
9
+ * then shows duplicates and the user can't tell which one is live.
10
+ *
11
+ * This file collapses rows in three passes:
12
+ * 1. Identity key (hwid → publicKey → "host:os:name" → id → name)
13
+ * 2. Alias key (os + normalized-hostname) — catches re-pairs that
14
+ * mint a new hwid
15
+ * 3. Endpoint key (host:port)
16
+ *
17
+ * When collapsing, `mergeDeviceEntries` prefers: authenticated over
18
+ * needsAuth, online over offline, freshest lastHeartbeat.
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.HEARTBEAT_STALE_MS = void 0;
22
+ exports.collapseRemoteDevices = collapseRemoteDevices;
23
+ exports.isDeviceFresh = isDeviceFresh;
24
+ exports.pickTargetDevice = pickTargetDevice;
25
+ function normalizedName(name) {
26
+ return String(name || '').trim().toLowerCase().replace(/\.local$/i, '');
27
+ }
28
+ function normalizedHost(host) {
29
+ return String(host || '').trim().toLowerCase().replace(/\.local$/i, '');
30
+ }
31
+ function identityKey(d) {
32
+ if (d.hwid)
33
+ return `hwid:${d.hwid}`;
34
+ if (d.publicKey)
35
+ return `pub:${d.publicKey}`;
36
+ if (d.isGuest) {
37
+ const scope = d.hostEmail || d.hostName || 'guest';
38
+ return `guest:${scope}:${d.deviceId || d.name}`;
39
+ }
40
+ const n = normalizedName(d.name);
41
+ const os = String(d.platform || '').trim().toLowerCase();
42
+ if (n && os)
43
+ return `host:${os}:${n}`;
44
+ if (d.deviceId)
45
+ return `id:${d.deviceId}`;
46
+ return `name:${d.name}`;
47
+ }
48
+ function aliasKey(d) {
49
+ if (d.isGuest)
50
+ return null;
51
+ const n = normalizedName(d.name);
52
+ const os = String(d.platform || '').trim().toLowerCase();
53
+ if (!n || !os)
54
+ return null;
55
+ return `${os}:${n}`;
56
+ }
57
+ function endpointKey(d) {
58
+ if (d.isGuest)
59
+ return null;
60
+ const h = normalizedHost(d.quicHost);
61
+ if (!h)
62
+ return null;
63
+ return `${h}:${d.quicPort || 0}`;
64
+ }
65
+ function mergeEntries(existing, incoming) {
66
+ const incomingWins = (!!existing.needsAuth && !incoming.needsAuth) ||
67
+ (incoming.lastHeartbeat || 0) > (existing.lastHeartbeat || 0) ||
68
+ (!!incoming.isOnline && !existing.isOnline);
69
+ const base = incomingWins ? incoming : existing;
70
+ const other = incomingWins ? existing : incoming;
71
+ return {
72
+ ...other,
73
+ ...base,
74
+ quicHost: base.quicHost || other.quicHost,
75
+ quicPort: base.quicPort || other.quicPort,
76
+ isOnline: base.isOnline || other.isOnline,
77
+ runnerDown: base.runnerDown && other.runnerDown,
78
+ publicKey: base.publicKey || other.publicKey,
79
+ lastHeartbeat: Math.max(existing.lastHeartbeat || 0, incoming.lastHeartbeat || 0),
80
+ };
81
+ }
82
+ // When two rows share the same alias key (hostname + OS) but differ on
83
+ // hwid/publicKey, pick the active one over the stale needs-auth leftover.
84
+ function pickActiveOverStaleNeedsAuth(a, b) {
85
+ const aDead = a.needsAuth && !a.isOnline;
86
+ const bDead = b.needsAuth && !b.isOnline;
87
+ const aLive = !a.needsAuth && a.isOnline;
88
+ const bLive = !b.needsAuth && b.isOnline;
89
+ if (aDead && bLive)
90
+ return b;
91
+ if (bDead && aLive)
92
+ return a;
93
+ return null;
94
+ }
95
+ /**
96
+ * Collapse a Convex device list so each physical machine appears once.
97
+ * Safe on an empty list; idempotent on an already-deduped list.
98
+ */
99
+ function collapseRemoteDevices(devices) {
100
+ if (!Array.isArray(devices) || devices.length === 0)
101
+ return [];
102
+ const byIdentity = new Map();
103
+ for (const d of devices) {
104
+ const k = identityKey(d);
105
+ const prev = byIdentity.get(k);
106
+ byIdentity.set(k, prev ? mergeEntries(prev, d) : d);
107
+ }
108
+ const byAlias = new Map();
109
+ for (const d of byIdentity.values()) {
110
+ const k = aliasKey(d);
111
+ if (!k) {
112
+ byAlias.set(`id:${d.deviceId}`, d);
113
+ continue;
114
+ }
115
+ const prev = byAlias.get(k);
116
+ if (!prev) {
117
+ byAlias.set(k, d);
118
+ continue;
119
+ }
120
+ const strongIdentityConflict = !!prev.publicKey && !!d.publicKey && prev.publicKey !== d.publicKey;
121
+ if (strongIdentityConflict) {
122
+ const winner = pickActiveOverStaleNeedsAuth(prev, d);
123
+ if (winner) {
124
+ byAlias.set(k, winner);
125
+ continue;
126
+ }
127
+ }
128
+ byAlias.set(k, mergeEntries(prev, d));
129
+ }
130
+ const byEndpoint = new Map();
131
+ for (const d of byAlias.values()) {
132
+ const k = endpointKey(d);
133
+ if (!k) {
134
+ byEndpoint.set(`id:${d.deviceId}`, d);
135
+ continue;
136
+ }
137
+ const prev = byEndpoint.get(k);
138
+ byEndpoint.set(k, prev ? mergeEntries(prev, d) : d);
139
+ }
140
+ return [...byEndpoint.values()];
141
+ }
142
+ /**
143
+ * Threshold for "online" based on heartbeat age, in milliseconds.
144
+ * Matches the mobile app (HEARTBEAT_STALE_MS = 90 s). The SDK used
145
+ * 60 s, which flashed yellow on single missed beats.
146
+ */
147
+ exports.HEARTBEAT_STALE_MS = 90000;
148
+ /**
149
+ * Returns a freshness flag consistent with the mobile app. A device is
150
+ * "fresh" when it was online per Convex AND its heartbeat is within
151
+ * `HEARTBEAT_STALE_MS`.
152
+ */
153
+ function isDeviceFresh(d) {
154
+ if (!d.isOnline)
155
+ return false;
156
+ if (!d.lastHeartbeat)
157
+ return true;
158
+ return Date.now() - d.lastHeartbeat < exports.HEARTBEAT_STALE_MS;
159
+ }
160
+ /**
161
+ * Pick the best candidate for an auto-connect attempt. Preference:
162
+ * 1. matches the preferred deviceId when supplied + still fresh
163
+ * 2. fresh (online + recent heartbeat) + has a quicHost
164
+ * 3. online + has a quicHost
165
+ * 4. first with a quicHost
166
+ */
167
+ function pickTargetDevice(devices, preferredDeviceId) {
168
+ if (!devices.length)
169
+ return null;
170
+ if (preferredDeviceId) {
171
+ const preferred = devices.find((d) => d.deviceId === preferredDeviceId && d.quicHost);
172
+ if (preferred && isDeviceFresh(preferred))
173
+ return preferred;
174
+ if (preferred)
175
+ return preferred;
176
+ }
177
+ const fresh = devices.find((d) => isDeviceFresh(d) && d.quicHost);
178
+ if (fresh)
179
+ return fresh;
180
+ const online = devices.find((d) => d.isOnline && d.quicHost);
181
+ if (online)
182
+ return online;
183
+ return devices.find((d) => d.quicHost) || devices[0] || null;
184
+ }
package/dist/expo.d.ts CHANGED
@@ -7,7 +7,6 @@ import type { FeedbackConfig } from './types';
7
7
  *
8
8
  * Defaults:
9
9
  * - trigger: 'shake'
10
- * - feedbackMode: 'batch'
11
10
  * - enabled: __DEV__ (only active in development)
12
11
  *
13
12
  * @param overrides - Optional partial config to override defaults
package/dist/expo.js CHANGED
@@ -32,7 +32,6 @@ const YaverFeedback_1 = require("./YaverFeedback");
32
32
  *
33
33
  * Defaults:
34
34
  * - trigger: 'shake'
35
- * - feedbackMode: 'batch'
36
35
  * - enabled: __DEV__ (only active in development)
37
36
  *
38
37
  * @param overrides - Optional partial config to override defaults
@@ -54,7 +53,6 @@ function initExpo(overrides) {
54
53
  YaverFeedback_1.YaverFeedback.init({
55
54
  authToken: '', // LAN auto-discovery doesn't require a token
56
55
  trigger: 'shake',
57
- feedbackMode: 'batch',
58
56
  enabled: __DEV__,
59
57
  ...overrides,
60
58
  ...(agentUrl ? { agentUrl } : {}),
package/dist/index.d.ts CHANGED
@@ -1,24 +1,29 @@
1
1
  /**
2
- * @yaver/feedback-react-native — Visual feedback SDK for Yaver.
2
+ * yaver-feedback-react-native — Visual feedback SDK for Yaver.
3
3
  *
4
- * Shake-to-report, screenshots, voice annotations, P2P connection,
5
- * device discovery, and live/narrated/batch feedback modes for vibe coding.
4
+ * Shake-to-report surface with five one-tap actions:
5
+ * 1. Hot Reload — instant JS reload
6
+ * 2. Screenshot & Fix — capture the screen under the modal and
7
+ * kick a fix task on the agent
8
+ * 3. Vibing — open a vibing session on the agent
9
+ * 4. Start / Stop Recording — screen recording toggle
10
+ * 5. Send Video — submit the last recording
6
11
  *
7
12
  * @example
8
13
  * ```tsx
9
- * import { YaverFeedback, FeedbackProvider } from '@yaver/feedback-react-native';
14
+ * import { YaverFeedback, FeedbackModal } from 'yaver-feedback-react-native';
10
15
  *
11
16
  * YaverFeedback.init({
12
17
  * agentUrl: 'http://192.168.1.10:18080',
13
18
  * authToken: 'your-token',
14
19
  * trigger: 'shake',
15
- * feedbackMode: 'live',
20
+ * strictNativeAuth: true,
16
21
  * });
17
22
  *
18
- * // Wrap your app root:
19
- * <FeedbackProvider>
23
+ * <>
20
24
  * <App />
21
- * </FeedbackProvider>
25
+ * <FeedbackModal />
26
+ * </>
22
27
  * ```
23
28
  */
24
29
  export { YaverFeedback } from './YaverFeedback';
@@ -40,9 +45,9 @@ export { FeedbackModal } from './FeedbackModal';
40
45
  export { FixReport } from './FixReport';
41
46
  export { configureAuthEndpoints, getConvexSiteUrl, getWebBaseUrl, getToken, saveToken, clearToken, getUser, saveUser, getSelectedDeviceId, saveSelectedDeviceId, clearSelectedDeviceId, validateToken, signInWithApple, signInWithOAuth, signupWithEmail, loginWithEmail, listReachableDevices, DEFAULT_CONVEX_SITE_URL, DEFAULT_WEB_BASE_URL, DEFAULT_OAUTH_REDIRECT, } from './auth';
42
47
  export type { OAuthProvider, User, RemoteDevice, DeviceList, } from './auth';
43
- export { captureScreenshot, startAudioRecording, stopAudioRecording } from './capture';
48
+ export { captureScreenshot, startVideoRecording, stopVideoRecording, isVideoRecording, } from './capture';
44
49
  export { uploadFeedback } from './upload';
45
- export type { FeedbackConfig, FeedbackBundle, FeedbackMetadata, DeviceInfo, AppInfo, TimelineEvent, FeedbackReport, AgentCommentary, FeedbackStreamEvent, VoiceCapability, CapturedError, TestFix, TestSession, } from './types';
50
+ export type { FeedbackConfig, FeedbackBundle, FeedbackMetadata, DeviceInfo, AppInfo, TimelineEvent, FeedbackReport, FeedbackStreamEvent, VoiceCapability, CapturedError, TestFix, TestSession, } from './types';
46
51
  export type { BlackBoxEvent, BlackBoxConfig, BlackBoxCommand, CommandHandler } from './BlackBox';
47
52
  export type { DiscoveryResult } from './Discovery';
48
53
  export type { FeedbackEvent } from './P2PClient';
package/dist/index.js CHANGED
@@ -1,29 +1,34 @@
1
1
  "use strict";
2
2
  /**
3
- * @yaver/feedback-react-native — Visual feedback SDK for Yaver.
3
+ * yaver-feedback-react-native — Visual feedback SDK for Yaver.
4
4
  *
5
- * Shake-to-report, screenshots, voice annotations, P2P connection,
6
- * device discovery, and live/narrated/batch feedback modes for vibe coding.
5
+ * Shake-to-report surface with five one-tap actions:
6
+ * 1. Hot Reload — instant JS reload
7
+ * 2. Screenshot & Fix — capture the screen under the modal and
8
+ * kick a fix task on the agent
9
+ * 3. Vibing — open a vibing session on the agent
10
+ * 4. Start / Stop Recording — screen recording toggle
11
+ * 5. Send Video — submit the last recording
7
12
  *
8
13
  * @example
9
14
  * ```tsx
10
- * import { YaverFeedback, FeedbackProvider } from '@yaver/feedback-react-native';
15
+ * import { YaverFeedback, FeedbackModal } from 'yaver-feedback-react-native';
11
16
  *
12
17
  * YaverFeedback.init({
13
18
  * agentUrl: 'http://192.168.1.10:18080',
14
19
  * authToken: 'your-token',
15
20
  * trigger: 'shake',
16
- * feedbackMode: 'live',
21
+ * strictNativeAuth: true,
17
22
  * });
18
23
  *
19
- * // Wrap your app root:
20
- * <FeedbackProvider>
24
+ * <>
21
25
  * <App />
22
- * </FeedbackProvider>
26
+ * <FeedbackModal />
27
+ * </>
23
28
  * ```
24
29
  */
25
30
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.uploadFeedback = exports.stopAudioRecording = exports.startAudioRecording = exports.captureScreenshot = exports.DEFAULT_OAUTH_REDIRECT = exports.DEFAULT_WEB_BASE_URL = exports.DEFAULT_CONVEX_SITE_URL = exports.listReachableDevices = exports.loginWithEmail = exports.signupWithEmail = exports.signInWithOAuth = exports.signInWithApple = exports.validateToken = exports.clearSelectedDeviceId = exports.saveSelectedDeviceId = exports.getSelectedDeviceId = exports.saveUser = exports.getUser = exports.clearToken = exports.saveToken = exports.getToken = exports.getWebBaseUrl = exports.getConvexSiteUrl = exports.configureAuthEndpoints = exports.FixReport = exports.FeedbackModal = exports.FloatingButton = exports.ShakeDetector = exports.AuthOverlay = exports.YaverMachinePickerScreen = exports.YaverLoginScreen = exports.YaverConnectionScreen = exports.P2PClient = exports.YaverDiscovery = exports.initExpo = exports.YaverUpdates = exports.BlackBox = exports.YaverFeedback = void 0;
31
+ exports.uploadFeedback = exports.isVideoRecording = exports.stopVideoRecording = exports.startVideoRecording = exports.captureScreenshot = exports.DEFAULT_OAUTH_REDIRECT = exports.DEFAULT_WEB_BASE_URL = exports.DEFAULT_CONVEX_SITE_URL = exports.listReachableDevices = exports.loginWithEmail = exports.signupWithEmail = exports.signInWithOAuth = exports.signInWithApple = exports.validateToken = exports.clearSelectedDeviceId = exports.saveSelectedDeviceId = exports.getSelectedDeviceId = exports.saveUser = exports.getUser = exports.clearToken = exports.saveToken = exports.getToken = exports.getWebBaseUrl = exports.getConvexSiteUrl = exports.configureAuthEndpoints = exports.FixReport = exports.FeedbackModal = exports.FloatingButton = exports.ShakeDetector = exports.AuthOverlay = exports.YaverMachinePickerScreen = exports.YaverLoginScreen = exports.YaverConnectionScreen = exports.P2PClient = exports.YaverDiscovery = exports.initExpo = exports.YaverUpdates = exports.BlackBox = exports.YaverFeedback = void 0;
27
32
  var YaverFeedback_1 = require("./YaverFeedback");
28
33
  Object.defineProperty(exports, "YaverFeedback", { enumerable: true, get: function () { return YaverFeedback_1.YaverFeedback; } });
29
34
  var BlackBox_1 = require("./BlackBox");
@@ -75,7 +80,8 @@ Object.defineProperty(exports, "DEFAULT_WEB_BASE_URL", { enumerable: true, get:
75
80
  Object.defineProperty(exports, "DEFAULT_OAUTH_REDIRECT", { enumerable: true, get: function () { return auth_1.DEFAULT_OAUTH_REDIRECT; } });
76
81
  var capture_1 = require("./capture");
77
82
  Object.defineProperty(exports, "captureScreenshot", { enumerable: true, get: function () { return capture_1.captureScreenshot; } });
78
- Object.defineProperty(exports, "startAudioRecording", { enumerable: true, get: function () { return capture_1.startAudioRecording; } });
79
- Object.defineProperty(exports, "stopAudioRecording", { enumerable: true, get: function () { return capture_1.stopAudioRecording; } });
83
+ Object.defineProperty(exports, "startVideoRecording", { enumerable: true, get: function () { return capture_1.startVideoRecording; } });
84
+ Object.defineProperty(exports, "stopVideoRecording", { enumerable: true, get: function () { return capture_1.stopVideoRecording; } });
85
+ Object.defineProperty(exports, "isVideoRecording", { enumerable: true, get: function () { return capture_1.isVideoRecording; } });
80
86
  var upload_1 = require("./upload");
81
87
  Object.defineProperty(exports, "uploadFeedback", { enumerable: true, get: function () { return upload_1.uploadFeedback; } });
package/dist/types.d.ts CHANGED
@@ -65,27 +65,6 @@ export interface FeedbackConfig {
65
65
  reportingOnly?: boolean;
66
66
  /** Max screen recording duration in seconds. Default: 120 */
67
67
  maxRecordingDuration?: number;
68
- /**
69
- * Feedback mode:
70
- * - 'live': stream events to the agent as they happen
71
- * - 'narrated': record everything, send on stop
72
- * - 'batch': dump everything at end (default)
73
- */
74
- feedbackMode?: 'live' | 'narrated' | 'batch';
75
- /**
76
- * Agent commentary level (0-10).
77
- * 0 = silent, 10 = agent comments on everything it sees.
78
- * Only relevant in live mode. Default: 0.
79
- */
80
- agentCommentaryLevel?: number;
81
- /**
82
- * Enable voice input for feedback annotations. Always true by default.
83
- * Audio is recorded on the device and sent to the agent for transcription.
84
- * Works regardless of whether a speech-to-speech provider is configured —
85
- * if STT is available on the agent, audio is auto-transcribed; otherwise
86
- * raw audio is attached to the feedback report.
87
- */
88
- voiceEnabled?: boolean;
89
68
  /**
90
69
  * Maximum number of captured errors to keep in memory (ring buffer).
91
70
  * Oldest errors are evicted when the buffer is full.
@@ -179,13 +158,10 @@ export interface FeedbackConfig {
179
158
  }
180
159
  export interface FeedbackBundle {
181
160
  metadata: FeedbackMetadata;
161
+ /** Screen-recording file path, when produced by the "Start Recording" action. */
182
162
  video?: string;
183
- /** Voice annotation audio file path (WAV). Always available when voiceEnabled. */
184
- audio?: string;
185
- /** Transcribed text from voice annotation (if STT/S2S provider is available on agent). */
186
- audioTranscript?: string;
187
163
  screenshots: string[];
188
- /** Captured errors with stack traces, attached automatically when captureErrors is enabled. */
164
+ /** Captured errors with stack traces, attached via attachError / wrapErrorHandler. */
189
165
  errors?: CapturedError[];
190
166
  }
191
167
  /** An error captured by the SDK's global error handler. */
@@ -231,12 +207,6 @@ export interface FeedbackReport {
231
207
  status: 'pending' | 'uploading' | 'uploaded' | 'failed';
232
208
  error?: string;
233
209
  }
234
- export interface AgentCommentary {
235
- id: string;
236
- timestamp: string;
237
- message: string;
238
- type: 'observation' | 'suggestion' | 'question' | 'action';
239
- }
240
210
  export interface FeedbackStreamEvent {
241
211
  type: string;
242
212
  timestamp: string;
package/dist/upload.d.ts CHANGED
@@ -5,9 +5,14 @@ import { FeedbackBundle } from './types';
5
5
  * The agent receives the bundle at POST /feedback with:
6
6
  * - `metadata` (JSON string)
7
7
  * - `screenshot_0`, `screenshot_1`, ... (image files)
8
- * - `audio` (audio file, if present)
9
8
  * - `video` (video file, if present)
10
9
  *
11
- * @returns The feedback report ID from the agent response.
10
+ * Returns the parsed agent response typically `{ ok, id, reportId }`.
11
+ * Callers can inspect `.id` / `.reportId` to drive a follow-up
12
+ * `/feedback/{id}/fix` kick.
12
13
  */
13
- export declare function uploadFeedback(agentUrl: string, authToken: string, bundle: FeedbackBundle): Promise<string>;
14
+ export declare function uploadFeedback(agentUrl: string, authToken: string, bundle: FeedbackBundle): Promise<{
15
+ id?: string;
16
+ reportId?: string;
17
+ [k: string]: unknown;
18
+ }>;
package/dist/upload.js CHANGED
@@ -8,10 +8,11 @@ const react_native_1 = require("react-native");
8
8
  * The agent receives the bundle at POST /feedback with:
9
9
  * - `metadata` (JSON string)
10
10
  * - `screenshot_0`, `screenshot_1`, ... (image files)
11
- * - `audio` (audio file, if present)
12
11
  * - `video` (video file, if present)
13
12
  *
14
- * @returns The feedback report ID from the agent response.
13
+ * Returns the parsed agent response typically `{ ok, id, reportId }`.
14
+ * Callers can inspect `.id` / `.reportId` to drive a follow-up
15
+ * `/feedback/{id}/fix` kick.
15
16
  */
16
17
  async function uploadFeedback(agentUrl, authToken, bundle) {
17
18
  const formData = new FormData();
@@ -26,14 +27,6 @@ async function uploadFeedback(agentUrl, authToken, bundle) {
26
27
  name: `screenshot_${i}.png`,
27
28
  });
28
29
  }
29
- // Attach audio
30
- if (bundle.audio) {
31
- formData.append('audio', {
32
- uri: react_native_1.Platform.OS === 'android' ? `file://${bundle.audio}` : bundle.audio,
33
- type: 'audio/m4a',
34
- name: 'voice_note.m4a',
35
- });
36
- }
37
30
  // Attach video
38
31
  if (bundle.video) {
39
32
  formData.append('video', {
@@ -54,6 +47,6 @@ async function uploadFeedback(agentUrl, authToken, bundle) {
54
47
  const text = await response.text().catch(() => '');
55
48
  throw new Error(`[YaverFeedback] Upload failed (${response.status}): ${text}`);
56
49
  }
57
- const result = await response.json();
58
- return result.id ?? result.reportId ?? 'unknown';
50
+ const result = await response.json().catch(() => ({}));
51
+ return result;
59
52
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yaver-feedback-react-native",
3
- "version": "0.6.1",
3
+ "version": "0.7.1",
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",