yaver-feedback-react-native 0.7.15 → 0.7.17

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.
@@ -52,6 +52,14 @@ const FeedbackModal = () => {
52
52
  // hidden button instead of a runtime error.
53
53
  const voiceSupported = (0, react_1.useRef)((0, capture_1.isVoiceCaptureSupported)()).current;
54
54
  const [lastVideo, setLastVideo] = (0, react_1.useState)(null);
55
+ // Vibing-input mode: same expand-on-tap pattern as email login.
56
+ // Tap "Vibing" once → the button reveals an input + Send; that lets
57
+ // the user say WHAT they want to vibe on instead of firing a canned
58
+ // "pick something for me" prompt (which in 0.7.13 pointed Claude at
59
+ // the wrong project because the matcher grepped the prompt itself).
60
+ const [showVibeInput, setShowVibeInput] = (0, react_1.useState)(false);
61
+ const [vibePrompt, setVibePrompt] = (0, react_1.useState)('');
62
+ const [lastVibeTaskId, setLastVibeTaskId] = (0, react_1.useState)(null);
55
63
  const mountedRef = (0, react_1.useRef)(true);
56
64
  (0, react_1.useEffect)(() => {
57
65
  mountedRef.current = true;
@@ -263,7 +271,22 @@ const FeedbackModal = () => {
263
271
  }
264
272
  }, [closeSoon]);
265
273
  // ─── 3. Vibing ─────────────────────────────────────────────────────
266
- const handleVibing = (0, react_1.useCallback)(async () => {
274
+ // First tap expands the input; second submit fires the actual
275
+ // /vibing/execute. Mirrors the Yaver mobile app's Vibing tab —
276
+ // user types what they want, hits Send, sees the task id back. If
277
+ // left blank, we default to "pick the next small improvement"
278
+ // so a one-tap workflow still works for lazy days.
279
+ const handleVibingButton = (0, react_1.useCallback)(() => {
280
+ if (!showVibeInput) {
281
+ setShowVibeInput(true);
282
+ return;
283
+ }
284
+ // collapse if tapped again with empty input
285
+ if (!vibePrompt.trim()) {
286
+ setShowVibeInput(false);
287
+ }
288
+ }, [showVibeInput, vibePrompt]);
289
+ const handleVibingSubmit = (0, react_1.useCallback)(async () => {
267
290
  const client = YaverFeedback_1.YaverFeedback.getP2PClient();
268
291
  if (!client) {
269
292
  setError('Not connected to the agent yet.');
@@ -280,14 +303,16 @@ const FeedbackModal = () => {
280
303
  .map((e) => `- ${e.message}`)
281
304
  .join('\n')
282
305
  : '';
283
- const prompt = 'The user opened the feedback modal on their phone and tapped Vibing. ' +
284
- 'Investigate whatever they are likely to be asking about — pick the ' +
285
- 'next small improvement or fix based on recent activity and the ' +
286
- 'current screen.' +
287
- errNote;
288
- await client.vibing(prompt);
289
- setToast('Vibing task created');
290
- closeSoon(1200);
306
+ const userPrompt = vibePrompt.trim();
307
+ const prompt = userPrompt
308
+ ? userPrompt + errNote
309
+ : 'Pick the next small improvement or fix for this app based on recent activity and the current screen.' +
310
+ errNote;
311
+ const result = await client.vibing(prompt);
312
+ setLastVibeTaskId(result.taskId);
313
+ setToast(`Vibing task ${result.taskId.slice(0, 8)} created`);
314
+ setVibePrompt('');
315
+ setShowVibeInput(false);
291
316
  }
292
317
  catch (err) {
293
318
  setError(err instanceof Error ? err.message : String(err));
@@ -296,7 +321,7 @@ const FeedbackModal = () => {
296
321
  if (mountedRef.current)
297
322
  setAction('idle');
298
323
  }
299
- }, [closeSoon]);
324
+ }, [vibePrompt]);
300
325
  // ─── 4. Toggle screen recording ────────────────────────────────────
301
326
  const handleToggleRecording = (0, react_1.useCallback)(async () => {
302
327
  setError(null);
@@ -489,8 +514,30 @@ const FeedbackModal = () => {
489
514
  ? 'Capturing…'
490
515
  : 'Screenshot & Fix'} tint="#22c55e" onPress={handleScreenshotAndFix} disabled={busy} busy={action === 'capturing'}/>
491
516
 
492
- {/* 3. Vibing */}
493
- <ActionRow label={action === 'vibing' ? 'Starting…' : 'Vibing'} tint="#818cf8" onPress={handleVibing} disabled={busy} busy={action === 'vibing'}/>
517
+ {/* 3. Vibing — expands to an input box on first tap
518
+ so the user says WHAT they want to vibe on, just
519
+ like the Yaver mobile app's Vibing tab. Second
520
+ tap (Send) fires /vibing/execute with the typed
521
+ prompt + resolved bundle id so the agent routes
522
+ to the right repo. */}
523
+ {!showVibeInput ? (<ActionRow label={action === 'vibing' ? 'Starting…' : 'Vibing'} tint="#818cf8" onPress={handleVibingButton} disabled={busy} busy={action === 'vibing'}/>) : (<react_native_1.View style={styles.vibeInputRow}>
524
+ <react_native_1.TextInput style={styles.vibeInput} placeholder="What do you want to vibe on?" placeholderTextColor="#666" value={vibePrompt} onChangeText={setVibePrompt} multiline autoFocus editable={action !== 'vibing'} blurOnSubmit={false}/>
525
+ <react_native_1.View style={styles.vibeInputButtons}>
526
+ <react_native_1.Pressable onPress={() => { setShowVibeInput(false); setVibePrompt(''); }} style={({ pressed }) => [styles.vibeCancelBtn, pressed && styles.buttonPressed]} disabled={action === 'vibing'}>
527
+ <react_native_1.Text style={styles.vibeCancelBtnText}>Cancel</react_native_1.Text>
528
+ </react_native_1.Pressable>
529
+ <react_native_1.Pressable onPress={handleVibingSubmit} style={({ pressed }) => [
530
+ styles.vibeSendBtn,
531
+ pressed && styles.buttonPressed,
532
+ action === 'vibing' && { opacity: 0.6 },
533
+ ]} disabled={action === 'vibing'}>
534
+ {action === 'vibing' ? (<react_native_1.ActivityIndicator color="#fff"/>) : (<react_native_1.Text style={styles.vibeSendBtnText}>Send</react_native_1.Text>)}
535
+ </react_native_1.Pressable>
536
+ </react_native_1.View>
537
+ </react_native_1.View>)}
538
+ {lastVibeTaskId && action !== 'vibing' && (<react_native_1.Text style={styles.vibeTaskLine} numberOfLines={1}>
539
+ Last vibing task: {lastVibeTaskId.slice(0, 12)}…
540
+ </react_native_1.Text>)}
494
541
 
495
542
  {/* Voice note — only rendered when expo-av is installed.
496
543
  Tap to start, tap again to stop → transcribes via
@@ -537,6 +584,56 @@ const ActionRow = ({ label, tint, onPress, disabled, busy, }) => (<react_native_
537
584
  {busy ? (<react_native_1.ActivityIndicator color={tint} size="small"/>) : (<react_native_1.Text style={[styles.actionText, { color: tint }]}>{label}</react_native_1.Text>)}
538
585
  </react_native_1.Pressable>);
539
586
  const styles = react_native_1.StyleSheet.create({
587
+ vibeInputRow: {
588
+ backgroundColor: 'rgba(129,140,248,0.08)',
589
+ borderColor: 'rgba(129,140,248,0.4)',
590
+ borderWidth: 1,
591
+ borderRadius: 12,
592
+ padding: 12,
593
+ gap: 10,
594
+ },
595
+ vibeInput: {
596
+ color: '#fff',
597
+ fontSize: 15,
598
+ minHeight: 64,
599
+ textAlignVertical: 'top',
600
+ padding: 0,
601
+ },
602
+ vibeInputButtons: {
603
+ flexDirection: 'row',
604
+ justifyContent: 'flex-end',
605
+ gap: 10,
606
+ },
607
+ vibeCancelBtn: {
608
+ paddingHorizontal: 14,
609
+ paddingVertical: 8,
610
+ borderRadius: 8,
611
+ backgroundColor: 'transparent',
612
+ },
613
+ vibeCancelBtnText: {
614
+ color: '#999',
615
+ fontSize: 14,
616
+ fontWeight: '600',
617
+ },
618
+ vibeSendBtn: {
619
+ paddingHorizontal: 16,
620
+ paddingVertical: 8,
621
+ borderRadius: 8,
622
+ backgroundColor: '#818cf8',
623
+ minWidth: 72,
624
+ alignItems: 'center',
625
+ },
626
+ vibeSendBtnText: {
627
+ color: '#fff',
628
+ fontSize: 14,
629
+ fontWeight: '700',
630
+ },
631
+ vibeTaskLine: {
632
+ color: '#818cf8',
633
+ fontSize: 12,
634
+ marginTop: -4,
635
+ fontFamily: react_native_1.Platform.select({ ios: 'Menlo', android: 'monospace', default: 'monospace' }),
636
+ },
540
637
  overlay: {
541
638
  flex: 1,
542
639
  backgroundColor: 'rgba(0,0,0,0.55)',
@@ -83,7 +83,11 @@ export declare class P2PClient {
83
83
  * vibing from Claude Code / the Yaver mobile app; this method is a
84
84
  * convenience for the SDK's one-tap bug-report-to-vibing path.
85
85
  */
86
- vibing(prompt: string, projectPath?: string): Promise<{
86
+ vibing(prompt: string, opts?: {
87
+ projectName?: string;
88
+ bundleId?: string;
89
+ projectPath?: string;
90
+ }): Promise<{
87
91
  taskId: string;
88
92
  }>;
89
93
  /**
package/dist/P2PClient.js CHANGED
@@ -327,14 +327,27 @@ class P2PClient {
327
327
  * vibing from Claude Code / the Yaver mobile app; this method is a
328
328
  * convenience for the SDK's one-tap bug-report-to-vibing path.
329
329
  */
330
- async vibing(prompt, projectPath) {
330
+ async vibing(prompt, opts) {
331
+ // Resolve app identity exactly the same way we do for
332
+ // reloadApp — bundle ID from expo-constants or native config.
333
+ // Without this, the agent falls back to "grep the prompt for a
334
+ // word that looks like a project name," which is catastrophically
335
+ // wrong: the prompt 'tapped Vibing' matched 'in' → picked mprint
336
+ // → Claude vibed on the wrong repo. Passing the bundle/name lets
337
+ // the agent go straight to findMobileProjectByName / bundleId.
338
+ const identity = resolveAppIdentity(opts);
331
339
  const response = await fetch(`${this.baseUrl}/vibing/execute`, {
332
340
  method: 'POST',
333
341
  headers: {
334
342
  Authorization: `Bearer ${this.authToken}`,
335
343
  'Content-Type': 'application/json',
336
344
  },
337
- body: JSON.stringify({ prompt, projectPath: projectPath ?? '' }),
345
+ body: JSON.stringify({
346
+ prompt,
347
+ projectPath: identity.projectPath ?? opts?.projectPath ?? '',
348
+ projectName: identity.projectName,
349
+ bundleId: identity.bundleId,
350
+ }),
338
351
  });
339
352
  if (!response.ok) {
340
353
  const text = await response.text().catch(() => '');
@@ -19,7 +19,13 @@ class YaverHotReload: NSObject {
19
19
  static let bundleFile = "main.jsbundle"
20
20
  static let reloadNotification = Notification.Name("YaverHotReloadBundle")
21
21
 
22
- override static func requiresMainQueueSetup() -> Bool { return true }
22
+ // `requiresMainQueueSetup` is an RCTBridgeModule protocol method,
23
+ // not an NSObject method — so it must not be marked `override`.
24
+ // Modern React Native discovers it via the Objective-C runtime
25
+ // (the Swift-generated ObjC interface plus the .m file's
26
+ // RCT_EXPORT_MODULE macro). Marking it `override` errors with
27
+ // "does not override any method from its superclass" on Swift 5+.
28
+ @objc static func requiresMainQueueSetup() -> Bool { return true }
23
29
 
24
30
  /// Download a Hermes bundle from the agent and trigger a bridge reload.
25
31
  @objc func loadBundle(_ urlString: String,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yaver-feedback-react-native",
3
- "version": "0.7.15",
3
+ "version": "0.7.17",
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",
@@ -7,6 +7,7 @@ import {
7
7
  Pressable,
8
8
  StyleSheet,
9
9
  Text,
10
+ TextInput,
10
11
  View,
11
12
  } from 'react-native';
12
13
  import { YaverFeedback } from './YaverFeedback';
@@ -65,6 +66,14 @@ export const FeedbackModal: React.FC = () => {
65
66
  // hidden button instead of a runtime error.
66
67
  const voiceSupported = useRef<boolean>(isVoiceCaptureSupported()).current;
67
68
  const [lastVideo, setLastVideo] = useState<LastVideo | null>(null);
69
+ // Vibing-input mode: same expand-on-tap pattern as email login.
70
+ // Tap "Vibing" once → the button reveals an input + Send; that lets
71
+ // the user say WHAT they want to vibe on instead of firing a canned
72
+ // "pick something for me" prompt (which in 0.7.13 pointed Claude at
73
+ // the wrong project because the matcher grepped the prompt itself).
74
+ const [showVibeInput, setShowVibeInput] = useState(false);
75
+ const [vibePrompt, setVibePrompt] = useState('');
76
+ const [lastVibeTaskId, setLastVibeTaskId] = useState<string | null>(null);
68
77
  const mountedRef = useRef(true);
69
78
 
70
79
  useEffect(() => {
@@ -285,7 +294,23 @@ export const FeedbackModal: React.FC = () => {
285
294
  }, [closeSoon]);
286
295
 
287
296
  // ─── 3. Vibing ─────────────────────────────────────────────────────
288
- const handleVibing = useCallback(async () => {
297
+ // First tap expands the input; second submit fires the actual
298
+ // /vibing/execute. Mirrors the Yaver mobile app's Vibing tab —
299
+ // user types what they want, hits Send, sees the task id back. If
300
+ // left blank, we default to "pick the next small improvement"
301
+ // so a one-tap workflow still works for lazy days.
302
+ const handleVibingButton = useCallback(() => {
303
+ if (!showVibeInput) {
304
+ setShowVibeInput(true);
305
+ return;
306
+ }
307
+ // collapse if tapped again with empty input
308
+ if (!vibePrompt.trim()) {
309
+ setShowVibeInput(false);
310
+ }
311
+ }, [showVibeInput, vibePrompt]);
312
+
313
+ const handleVibingSubmit = useCallback(async () => {
289
314
  const client = YaverFeedback.getP2PClient();
290
315
  if (!client) {
291
316
  setError('Not connected to the agent yet.');
@@ -303,21 +328,22 @@ export const FeedbackModal: React.FC = () => {
303
328
  .map((e) => `- ${e.message}`)
304
329
  .join('\n')
305
330
  : '';
306
- const prompt =
307
- 'The user opened the feedback modal on their phone and tapped Vibing. ' +
308
- 'Investigate whatever they are likely to be asking about — pick the ' +
309
- 'next small improvement or fix based on recent activity and the ' +
310
- 'current screen.' +
311
- errNote;
312
- await client.vibing(prompt);
313
- setToast('Vibing task created');
314
- closeSoon(1200);
331
+ const userPrompt = vibePrompt.trim();
332
+ const prompt = userPrompt
333
+ ? userPrompt + errNote
334
+ : 'Pick the next small improvement or fix for this app based on recent activity and the current screen.' +
335
+ errNote;
336
+ const result = await client.vibing(prompt);
337
+ setLastVibeTaskId(result.taskId);
338
+ setToast(`Vibing task ${result.taskId.slice(0, 8)} created`);
339
+ setVibePrompt('');
340
+ setShowVibeInput(false);
315
341
  } catch (err: unknown) {
316
342
  setError(err instanceof Error ? err.message : String(err));
317
343
  } finally {
318
344
  if (mountedRef.current) setAction('idle');
319
345
  }
320
- }, [closeSoon]);
346
+ }, [vibePrompt]);
321
347
 
322
348
  // ─── 4. Toggle screen recording ────────────────────────────────────
323
349
  const handleToggleRecording = useCallback(async () => {
@@ -537,14 +563,64 @@ export const FeedbackModal: React.FC = () => {
537
563
  busy={action === 'capturing'}
538
564
  />
539
565
 
540
- {/* 3. Vibing */}
541
- <ActionRow
542
- label={action === 'vibing' ? 'Starting…' : 'Vibing'}
543
- tint="#818cf8"
544
- onPress={handleVibing}
545
- disabled={busy}
546
- busy={action === 'vibing'}
547
- />
566
+ {/* 3. Vibing — expands to an input box on first tap
567
+ so the user says WHAT they want to vibe on, just
568
+ like the Yaver mobile app's Vibing tab. Second
569
+ tap (Send) fires /vibing/execute with the typed
570
+ prompt + resolved bundle id so the agent routes
571
+ to the right repo. */}
572
+ {!showVibeInput ? (
573
+ <ActionRow
574
+ label={action === 'vibing' ? 'Starting…' : 'Vibing'}
575
+ tint="#818cf8"
576
+ onPress={handleVibingButton}
577
+ disabled={busy}
578
+ busy={action === 'vibing'}
579
+ />
580
+ ) : (
581
+ <View style={styles.vibeInputRow}>
582
+ <TextInput
583
+ style={styles.vibeInput}
584
+ placeholder="What do you want to vibe on?"
585
+ placeholderTextColor="#666"
586
+ value={vibePrompt}
587
+ onChangeText={setVibePrompt}
588
+ multiline
589
+ autoFocus
590
+ editable={action !== 'vibing'}
591
+ blurOnSubmit={false}
592
+ />
593
+ <View style={styles.vibeInputButtons}>
594
+ <Pressable
595
+ onPress={() => { setShowVibeInput(false); setVibePrompt(''); }}
596
+ style={({ pressed }) => [styles.vibeCancelBtn, pressed && styles.buttonPressed]}
597
+ disabled={action === 'vibing'}
598
+ >
599
+ <Text style={styles.vibeCancelBtnText}>Cancel</Text>
600
+ </Pressable>
601
+ <Pressable
602
+ onPress={handleVibingSubmit}
603
+ style={({ pressed }) => [
604
+ styles.vibeSendBtn,
605
+ pressed && styles.buttonPressed,
606
+ action === 'vibing' && { opacity: 0.6 },
607
+ ]}
608
+ disabled={action === 'vibing'}
609
+ >
610
+ {action === 'vibing' ? (
611
+ <ActivityIndicator color="#fff" />
612
+ ) : (
613
+ <Text style={styles.vibeSendBtnText}>Send</Text>
614
+ )}
615
+ </Pressable>
616
+ </View>
617
+ </View>
618
+ )}
619
+ {lastVibeTaskId && action !== 'vibing' && (
620
+ <Text style={styles.vibeTaskLine} numberOfLines={1}>
621
+ Last vibing task: {lastVibeTaskId.slice(0, 12)}…
622
+ </Text>
623
+ )}
548
624
 
549
625
  {/* Voice note — only rendered when expo-av is installed.
550
626
  Tap to start, tap again to stop → transcribes via
@@ -647,6 +723,56 @@ const ActionRow: React.FC<ActionRowProps> = ({
647
723
  );
648
724
 
649
725
  const styles = StyleSheet.create({
726
+ vibeInputRow: {
727
+ backgroundColor: 'rgba(129,140,248,0.08)',
728
+ borderColor: 'rgba(129,140,248,0.4)',
729
+ borderWidth: 1,
730
+ borderRadius: 12,
731
+ padding: 12,
732
+ gap: 10,
733
+ },
734
+ vibeInput: {
735
+ color: '#fff',
736
+ fontSize: 15,
737
+ minHeight: 64,
738
+ textAlignVertical: 'top',
739
+ padding: 0,
740
+ },
741
+ vibeInputButtons: {
742
+ flexDirection: 'row',
743
+ justifyContent: 'flex-end',
744
+ gap: 10,
745
+ },
746
+ vibeCancelBtn: {
747
+ paddingHorizontal: 14,
748
+ paddingVertical: 8,
749
+ borderRadius: 8,
750
+ backgroundColor: 'transparent',
751
+ },
752
+ vibeCancelBtnText: {
753
+ color: '#999',
754
+ fontSize: 14,
755
+ fontWeight: '600',
756
+ },
757
+ vibeSendBtn: {
758
+ paddingHorizontal: 16,
759
+ paddingVertical: 8,
760
+ borderRadius: 8,
761
+ backgroundColor: '#818cf8',
762
+ minWidth: 72,
763
+ alignItems: 'center',
764
+ },
765
+ vibeSendBtnText: {
766
+ color: '#fff',
767
+ fontSize: 14,
768
+ fontWeight: '700',
769
+ },
770
+ vibeTaskLine: {
771
+ color: '#818cf8',
772
+ fontSize: 12,
773
+ marginTop: -4,
774
+ fontFamily: Platform.select({ ios: 'Menlo', android: 'monospace', default: 'monospace' }),
775
+ },
650
776
  overlay: {
651
777
  flex: 1,
652
778
  backgroundColor: 'rgba(0,0,0,0.55)',
package/src/P2PClient.ts CHANGED
@@ -376,14 +376,30 @@ export class P2PClient {
376
376
  * vibing from Claude Code / the Yaver mobile app; this method is a
377
377
  * convenience for the SDK's one-tap bug-report-to-vibing path.
378
378
  */
379
- async vibing(prompt: string, projectPath?: string): Promise<{ taskId: string }> {
379
+ async vibing(
380
+ prompt: string,
381
+ opts?: { projectName?: string; bundleId?: string; projectPath?: string },
382
+ ): Promise<{ taskId: string }> {
383
+ // Resolve app identity exactly the same way we do for
384
+ // reloadApp — bundle ID from expo-constants or native config.
385
+ // Without this, the agent falls back to "grep the prompt for a
386
+ // word that looks like a project name," which is catastrophically
387
+ // wrong: the prompt 'tapped Vibing' matched 'in' → picked mprint
388
+ // → Claude vibed on the wrong repo. Passing the bundle/name lets
389
+ // the agent go straight to findMobileProjectByName / bundleId.
390
+ const identity = resolveAppIdentity(opts);
380
391
  const response = await fetch(`${this.baseUrl}/vibing/execute`, {
381
392
  method: 'POST',
382
393
  headers: {
383
394
  Authorization: `Bearer ${this.authToken}`,
384
395
  'Content-Type': 'application/json',
385
396
  },
386
- body: JSON.stringify({ prompt, projectPath: projectPath ?? '' }),
397
+ body: JSON.stringify({
398
+ prompt,
399
+ projectPath: identity.projectPath ?? opts?.projectPath ?? '',
400
+ projectName: identity.projectName,
401
+ bundleId: identity.bundleId,
402
+ }),
387
403
  });
388
404
  if (!response.ok) {
389
405
  const text = await response.text().catch(() => '');