yaver-feedback-react-native 0.7.5 → 0.7.6

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.
@@ -58,9 +58,22 @@ const FeedbackModal = () => {
58
58
  setAction('idle');
59
59
  }
60
60
  });
61
+ // Agent streams build / compile progress through the BlackBox
62
+ // SSE command channel as `command: "status"`; YaverFeedback re-emits
63
+ // it as `yaverFeedback:status`. Show the most recent message in the
64
+ // toast so a multi-second rebuild feels like "working" instead of
65
+ // "stuck".
66
+ const statusSub = react_native_1.DeviceEventEmitter.addListener('yaverFeedback:status', (payload) => {
67
+ if (!mountedRef.current)
68
+ return;
69
+ const msg = payload?.message || payload?.phase || '';
70
+ if (msg)
71
+ setToast(msg);
72
+ });
61
73
  return () => {
62
74
  mountedRef.current = false;
63
75
  sub.remove();
76
+ statusSub.remove();
64
77
  };
65
78
  }, []);
66
79
  const closeSoon = (0, react_1.useCallback)((delayMs = 1200) => {
package/dist/P2PClient.js CHANGED
@@ -208,52 +208,46 @@ class P2PClient {
208
208
  * via the BlackBox command channel.
209
209
  * @param mode - "dev" for hot reload, "bundle" for native bundle rebuild
210
210
  */
211
- async reloadApp(mode = 'dev') {
212
- // Primary path: /dev/reload same endpoint the Yaver mobile app uses.
213
- // Triggers Metro/Expo HMR synchronously and emits an SSE `reload` event
214
- // on /dev/events that the FeedbackModal can subscribe to for progress.
211
+ async reloadApp(mode = 'bundle') {
212
+ // Default path: always rebuild a fresh Hermes bundle.
215
213
  //
216
- // Only fall back to /dev/reload-app (the BlackBox-SSE-broadcast path)
217
- // when the primary path reports "no dev server running" that mode is
218
- // really for the mobile app remotely kicking a third-party app, not
219
- // for the app kicking itself.
220
- const primary = await fetch(`${this.baseUrl}/dev/reload`, {
221
- method: 'POST',
222
- headers: { Authorization: `Bearer ${this.authToken}` },
223
- });
224
- if (primary.ok) {
225
- return primary.json().catch(() => ({ ok: true }));
226
- }
227
- // Common shape of the `/dev/reload` failure when Metro isn't running
228
- // on the agent's host: the agent tries to forward the reload to
229
- // `http://127.0.0.1:<metroPort>/reload` and that connection refuses.
230
- // In that case a JS hot-reload is simply not possible — pivot to
231
- // `/dev/reload-app` in bundle mode, which rebuilds + pushes a fresh
232
- // Hermes bundle without needing Metro to be alive.
233
- const primaryText = await primary.text().catch(() => '');
234
- const noDevServer = primary.status === 400 ||
235
- primary.status === 404 ||
236
- primary.status >= 500 ||
237
- /connection refused|no dev server|not running|ECONNREFUSED/i.test(primaryText);
238
- if (noDevServer || mode === 'bundle') {
239
- const nextMode = mode === 'bundle' ? 'bundle' : 'bundle';
240
- const fallback = await fetch(`${this.baseUrl}/dev/reload-app`, {
214
+ // Rationale: the SDK's common caller is a phone user who's not
215
+ // sitting at their Mac they're doing vibe coding with an AI agent
216
+ // editing files on the Mac remotely, or they installed the app via
217
+ // TestFlight and there's no Metro running at all. A Metro-based
218
+ // reload would either fail (Metro offline) or — worse — serve a
219
+ // stale bundle because Metro can be slow to re-index fresh edits.
220
+ // Bundle mode always produces the correct bytecode from the current
221
+ // filesystem state, taking ~30–60 s, and hits /dev/reload-app which
222
+ // uses BlackBox SSE to push the fresh bundle URL to the device.
223
+ //
224
+ // Callers who know they want Metro HMR pass `mode='dev'` explicitly;
225
+ // we still honour that. Everything else defaults to bundle.
226
+ if (mode === 'dev') {
227
+ const primary = await fetch(`${this.baseUrl}/dev/reload`, {
241
228
  method: 'POST',
242
- headers: {
243
- Authorization: `Bearer ${this.authToken}`,
244
- 'Content-Type': 'application/json',
245
- },
246
- body: JSON.stringify({ mode: nextMode }),
229
+ headers: { Authorization: `Bearer ${this.authToken}` },
247
230
  });
248
- if (!fallback.ok) {
249
- const fallbackText = await fallback.text().catch(() => '');
250
- // Give the user something actionable instead of a raw Go error.
251
- const friendlyFromFallback = friendlyReloadError(fallback.status, fallbackText);
252
- throw new Error(friendlyFromFallback);
231
+ if (primary.ok) {
232
+ return primary.json().catch(() => ({ ok: true }));
253
233
  }
254
- return fallback.json().catch(() => ({ ok: true }));
234
+ // Dev mode failed fall through to bundle rebuild below rather
235
+ // than surfacing the raw error, so the user never has to know
236
+ // Metro wasn't running.
237
+ }
238
+ const res = await fetch(`${this.baseUrl}/dev/reload-app`, {
239
+ method: 'POST',
240
+ headers: {
241
+ Authorization: `Bearer ${this.authToken}`,
242
+ 'Content-Type': 'application/json',
243
+ },
244
+ body: JSON.stringify({ mode: 'bundle' }),
245
+ });
246
+ if (!res.ok) {
247
+ const text = await res.text().catch(() => '');
248
+ throw new Error(friendlyReloadError(res.status, text));
255
249
  }
256
- throw new Error(friendlyReloadError(primary.status, primaryText));
250
+ return res.json().catch(() => ({ ok: true }));
257
251
  }
258
252
  /**
259
253
  * Open a vibing session on the connected agent. Vibing is the Yaver
@@ -126,9 +126,13 @@ class YaverFeedback {
126
126
  }
127
127
  });
128
128
  }
129
- // Wire up BlackBox command handlers for reload signals from the agent.
130
- // This enables the vibe coder to trigger reload from the Yaver mobile app
131
- // and have the third-party app (with this SDK) automatically reload.
129
+ // Wire up BlackBox command handlers for reload + status signals from
130
+ // the agent. Opens an SSE channel the agent uses to:
131
+ // - broadcast reload_bundle so the phone picks up a fresh Hermes
132
+ // bundle after a vibe-coding edit;
133
+ // - stream build/compile progress ("Compiling bundle…", "Pushing
134
+ // assets…", "Done") so the SDK can surface it like a normal
135
+ // "Working…" spinner instead of a silent 60-second freeze.
132
136
  if (enabled) {
133
137
  BlackBox_1.BlackBox.onCommand((cmd) => {
134
138
  if (cmd.command === 'reload') {
@@ -149,7 +153,37 @@ class YaverFeedback {
149
153
  YaverFeedback.defaultReloadBundle(bundleUrl, assetsUrl);
150
154
  }
151
155
  }
156
+ else if (cmd.command === 'status') {
157
+ // Pipe agent progress pings to the UI. The FeedbackModal
158
+ // subscribes to this event and renders the message while a
159
+ // reload / build is in flight.
160
+ const message = typeof cmd.data?.message === 'string'
161
+ ? cmd.data.message
162
+ : '';
163
+ const phase = typeof cmd.data?.phase === 'string'
164
+ ? cmd.data.phase
165
+ : '';
166
+ const { DeviceEventEmitter } = require('react-native');
167
+ DeviceEventEmitter.emit('yaverFeedback:status', {
168
+ message,
169
+ phase,
170
+ at: Date.now(),
171
+ });
172
+ }
152
173
  });
174
+ // Auto-open the command-stream SSE channel so `status` +
175
+ // `reload_bundle` messages from the agent actually reach us.
176
+ // Host apps can still call BlackBox.start(...) themselves to
177
+ // customise deviceId / appName, but they don't have to.
178
+ if (!BlackBox_1.BlackBox.isStreaming) {
179
+ try {
180
+ BlackBox_1.BlackBox.start();
181
+ }
182
+ catch {
183
+ // BlackBox.start throws if agentUrl/token aren't ready yet;
184
+ // a later setAuthToken() / discoverAgent() tick will retry.
185
+ }
186
+ }
153
187
  }
154
188
  // NOTE: We intentionally do NOT hook ErrorUtils.setGlobalHandler().
155
189
  // Sentry, Crashlytics, Bugsnag, and other tools all compete for that
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yaver-feedback-react-native",
3
- "version": "0.7.5",
3
+ "version": "0.7.6",
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",
@@ -67,9 +67,23 @@ export const FeedbackModal: React.FC = () => {
67
67
  setAction('idle');
68
68
  }
69
69
  });
70
+ // Agent streams build / compile progress through the BlackBox
71
+ // SSE command channel as `command: "status"`; YaverFeedback re-emits
72
+ // it as `yaverFeedback:status`. Show the most recent message in the
73
+ // toast so a multi-second rebuild feels like "working" instead of
74
+ // "stuck".
75
+ const statusSub = DeviceEventEmitter.addListener(
76
+ 'yaverFeedback:status',
77
+ (payload: { message?: string; phase?: string }) => {
78
+ if (!mountedRef.current) return;
79
+ const msg = payload?.message || payload?.phase || '';
80
+ if (msg) setToast(msg);
81
+ },
82
+ );
70
83
  return () => {
71
84
  mountedRef.current = false;
72
85
  sub.remove();
86
+ statusSub.remove();
73
87
  };
74
88
  }, []);
75
89
 
package/src/P2PClient.ts CHANGED
@@ -244,59 +244,47 @@ export class P2PClient {
244
244
  * via the BlackBox command channel.
245
245
  * @param mode - "dev" for hot reload, "bundle" for native bundle rebuild
246
246
  */
247
- async reloadApp(mode: 'dev' | 'bundle' = 'dev'): Promise<{ ok: boolean }> {
248
- // Primary path: /dev/reload same endpoint the Yaver mobile app uses.
249
- // Triggers Metro/Expo HMR synchronously and emits an SSE `reload` event
250
- // on /dev/events that the FeedbackModal can subscribe to for progress.
247
+ async reloadApp(mode: 'dev' | 'bundle' = 'bundle'): Promise<{ ok: boolean }> {
248
+ // Default path: always rebuild a fresh Hermes bundle.
251
249
  //
252
- // Only fall back to /dev/reload-app (the BlackBox-SSE-broadcast path)
253
- // when the primary path reports "no dev server running" that mode is
254
- // really for the mobile app remotely kicking a third-party app, not
255
- // for the app kicking itself.
256
- const primary = await fetch(`${this.baseUrl}/dev/reload`, {
257
- method: 'POST',
258
- headers: { Authorization: `Bearer ${this.authToken}` },
259
- });
260
- if (primary.ok) {
261
- return primary.json().catch(() => ({ ok: true }));
262
- }
263
-
264
- // Common shape of the `/dev/reload` failure when Metro isn't running
265
- // on the agent's host: the agent tries to forward the reload to
266
- // `http://127.0.0.1:<metroPort>/reload` and that connection refuses.
267
- // In that case a JS hot-reload is simply not possible — pivot to
268
- // `/dev/reload-app` in bundle mode, which rebuilds + pushes a fresh
269
- // Hermes bundle without needing Metro to be alive.
270
- const primaryText = await primary.text().catch(() => '');
271
- const noDevServer =
272
- primary.status === 400 ||
273
- primary.status === 404 ||
274
- primary.status >= 500 ||
275
- /connection refused|no dev server|not running|ECONNREFUSED/i.test(primaryText);
276
-
277
- if (noDevServer || mode === 'bundle') {
278
- const nextMode = mode === 'bundle' ? 'bundle' : 'bundle';
279
- const fallback = await fetch(`${this.baseUrl}/dev/reload-app`, {
250
+ // Rationale: the SDK's common caller is a phone user who's not
251
+ // sitting at their Mac they're doing vibe coding with an AI agent
252
+ // editing files on the Mac remotely, or they installed the app via
253
+ // TestFlight and there's no Metro running at all. A Metro-based
254
+ // reload would either fail (Metro offline) or — worse — serve a
255
+ // stale bundle because Metro can be slow to re-index fresh edits.
256
+ // Bundle mode always produces the correct bytecode from the current
257
+ // filesystem state, taking ~30–60 s, and hits /dev/reload-app which
258
+ // uses BlackBox SSE to push the fresh bundle URL to the device.
259
+ //
260
+ // Callers who know they want Metro HMR pass `mode='dev'` explicitly;
261
+ // we still honour that. Everything else defaults to bundle.
262
+ if (mode === 'dev') {
263
+ const primary = await fetch(`${this.baseUrl}/dev/reload`, {
280
264
  method: 'POST',
281
- headers: {
282
- Authorization: `Bearer ${this.authToken}`,
283
- 'Content-Type': 'application/json',
284
- },
285
- body: JSON.stringify({ mode: nextMode }),
265
+ headers: { Authorization: `Bearer ${this.authToken}` },
286
266
  });
287
- if (!fallback.ok) {
288
- const fallbackText = await fallback.text().catch(() => '');
289
- // Give the user something actionable instead of a raw Go error.
290
- const friendlyFromFallback = friendlyReloadError(
291
- fallback.status,
292
- fallbackText,
293
- );
294
- throw new Error(friendlyFromFallback);
267
+ if (primary.ok) {
268
+ return primary.json().catch(() => ({ ok: true }));
295
269
  }
296
- return fallback.json().catch(() => ({ ok: true }));
270
+ // Dev mode failed fall through to bundle rebuild below rather
271
+ // than surfacing the raw error, so the user never has to know
272
+ // Metro wasn't running.
297
273
  }
298
274
 
299
- throw new Error(friendlyReloadError(primary.status, primaryText));
275
+ const res = await fetch(`${this.baseUrl}/dev/reload-app`, {
276
+ method: 'POST',
277
+ headers: {
278
+ Authorization: `Bearer ${this.authToken}`,
279
+ 'Content-Type': 'application/json',
280
+ },
281
+ body: JSON.stringify({ mode: 'bundle' }),
282
+ });
283
+ if (!res.ok) {
284
+ const text = await res.text().catch(() => '');
285
+ throw new Error(friendlyReloadError(res.status, text));
286
+ }
287
+ return res.json().catch(() => ({ ok: true }));
300
288
  }
301
289
 
302
290
  /**
@@ -142,9 +142,13 @@ export class YaverFeedback {
142
142
  });
143
143
  }
144
144
 
145
- // Wire up BlackBox command handlers for reload signals from the agent.
146
- // This enables the vibe coder to trigger reload from the Yaver mobile app
147
- // and have the third-party app (with this SDK) automatically reload.
145
+ // Wire up BlackBox command handlers for reload + status signals from
146
+ // the agent. Opens an SSE channel the agent uses to:
147
+ // - broadcast reload_bundle so the phone picks up a fresh Hermes
148
+ // bundle after a vibe-coding edit;
149
+ // - stream build/compile progress ("Compiling bundle…", "Pushing
150
+ // assets…", "Done") so the SDK can surface it like a normal
151
+ // "Working…" spinner instead of a silent 60-second freeze.
148
152
  if (enabled) {
149
153
  BlackBox.onCommand((cmd) => {
150
154
  if (cmd.command === 'reload') {
@@ -161,8 +165,38 @@ export class YaverFeedback {
161
165
  } else {
162
166
  YaverFeedback.defaultReloadBundle(bundleUrl, assetsUrl);
163
167
  }
168
+ } else if (cmd.command === 'status') {
169
+ // Pipe agent progress pings to the UI. The FeedbackModal
170
+ // subscribes to this event and renders the message while a
171
+ // reload / build is in flight.
172
+ const message =
173
+ typeof cmd.data?.message === 'string'
174
+ ? (cmd.data.message as string)
175
+ : '';
176
+ const phase =
177
+ typeof cmd.data?.phase === 'string'
178
+ ? (cmd.data.phase as string)
179
+ : '';
180
+ const { DeviceEventEmitter } = require('react-native');
181
+ DeviceEventEmitter.emit('yaverFeedback:status', {
182
+ message,
183
+ phase,
184
+ at: Date.now(),
185
+ });
164
186
  }
165
187
  });
188
+ // Auto-open the command-stream SSE channel so `status` +
189
+ // `reload_bundle` messages from the agent actually reach us.
190
+ // Host apps can still call BlackBox.start(...) themselves to
191
+ // customise deviceId / appName, but they don't have to.
192
+ if (!BlackBox.isStreaming) {
193
+ try {
194
+ BlackBox.start();
195
+ } catch {
196
+ // BlackBox.start throws if agentUrl/token aren't ready yet;
197
+ // a later setAuthToken() / discoverAgent() tick will retry.
198
+ }
199
+ }
166
200
  }
167
201
 
168
202
  // NOTE: We intentionally do NOT hook ErrorUtils.setGlobalHandler().