yaver-feedback-react-native 0.8.12 → 0.9.0

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.
@@ -41,9 +41,91 @@ const capture_1 = require("./capture");
41
41
  const upload_1 = require("./upload");
42
42
  const AuthOverlay_1 = require("./AuthOverlay");
43
43
  const QuickActionIcon_1 = require("./QuickActionIcon");
44
+ const VibeChatScreen_1 = require("./VibeChatScreen");
45
+ const DeployPanel_1 = require("./DeployPanel");
44
46
  const auth_1 = require("./auth");
45
47
  const preferences_1 = require("./preferences");
48
+ const PRIMARY_RUNNER_IDS = ['claude', 'codex', 'opencode'];
49
+ function normalizeRunnerStatusRows(rows) {
50
+ const byId = new Map();
51
+ for (const row of rows) {
52
+ const raw = String(row.id || '').trim().toLowerCase();
53
+ if (!raw)
54
+ continue;
55
+ const normalized = raw === 'claude-code' ? 'claude' : raw;
56
+ if (!PRIMARY_RUNNER_IDS.includes(normalized))
57
+ continue;
58
+ byId.set(normalized, { ...row, id: normalized });
59
+ }
60
+ return PRIMARY_RUNNER_IDS.map((id) => {
61
+ const baseName = id === 'claude' ? 'Claude Code' : id === 'codex' ? 'OpenAI Codex' : 'OpenCode';
62
+ const row = byId.get(id);
63
+ if (!row) {
64
+ return {
65
+ id,
66
+ name: baseName,
67
+ installed: false,
68
+ authConfigured: false,
69
+ ready: false,
70
+ tone: 'warning',
71
+ statusLine: 'Not installed on the selected machine',
72
+ };
73
+ }
74
+ const versionPrefix = row.version?.trim() ? `${row.version.trim()} · ` : '';
75
+ const detail = row.error?.trim() || row.warning?.trim() || row.detail?.trim() || undefined;
76
+ if (!row.installed) {
77
+ return {
78
+ id,
79
+ name: row.name || baseName,
80
+ installed: false,
81
+ authConfigured: false,
82
+ ready: false,
83
+ version: row.version,
84
+ tone: 'warning',
85
+ statusLine: 'Not installed on the selected machine',
86
+ detail,
87
+ };
88
+ }
89
+ if (id === 'opencode') {
90
+ const configured = row.authConfigured || row.ready;
91
+ return {
92
+ id,
93
+ name: row.name || baseName,
94
+ installed: row.installed,
95
+ authConfigured: row.authConfigured,
96
+ ready: row.ready,
97
+ version: row.version,
98
+ tone: configured ? 'ok' : 'warning',
99
+ statusLine: configured
100
+ ? `${versionPrefix}Configured on the selected machine`
101
+ : `${versionPrefix}Needs provider config on the selected machine`,
102
+ detail,
103
+ };
104
+ }
105
+ const authed = row.authConfigured || row.ready;
106
+ return {
107
+ id,
108
+ name: row.name || baseName,
109
+ installed: row.installed,
110
+ authConfigured: row.authConfigured,
111
+ ready: row.ready,
112
+ version: row.version,
113
+ tone: authed ? 'ok' : 'warning',
114
+ statusLine: authed
115
+ ? `${versionPrefix}Signed in on the selected machine`
116
+ : `${versionPrefix}Not signed in on the selected machine`,
117
+ detail,
118
+ actionLabel: authed ? 'Re-auth' : 'Sign in',
119
+ actionRunner: id,
120
+ };
121
+ });
122
+ }
46
123
  const FeedbackModal = () => {
124
+ const { width: winW, height: winH } = (0, react_native_1.useWindowDimensions)();
125
+ const isTablet = Math.min(winW, winH) >= 600;
126
+ // Tablet color/icon picker fans out to 5/6 cols — 31% (3-col)
127
+ // looks empty on a 1024pt iPad. Mobile keeps 3-col.
128
+ const iconOptionWidthOverride = isTablet ? '18%' : undefined;
47
129
  const [visible, setVisible] = (0, react_1.useState)(false);
48
130
  const [action, setAction] = (0, react_1.useState)('idle');
49
131
  const [error, setError] = (0, react_1.useState)(null);
@@ -61,6 +143,7 @@ const FeedbackModal = () => {
61
143
  // "pick something for me" prompt (which in 0.7.13 pointed Claude at
62
144
  // the wrong project because the matcher grepped the prompt itself).
63
145
  const [showVibeInput, setShowVibeInput] = (0, react_1.useState)(false);
146
+ const [showDeploy, setShowDeploy] = (0, react_1.useState)(false);
64
147
  const [vibePrompt, setVibePrompt] = (0, react_1.useState)('');
65
148
  const [lastVibeTaskId, setLastVibeTaskId] = (0, react_1.useState)(null);
66
149
  const [quickIconColorPreset, setQuickIconColorPreset] = (0, react_1.useState)(null);
@@ -73,6 +156,12 @@ const FeedbackModal = () => {
73
156
  title: 'No machine selected',
74
157
  detail: 'Pick a remote dev machine before using the feedback actions.',
75
158
  });
159
+ const [runnerCards, setRunnerCards] = (0, react_1.useState)(() => normalizeRunnerStatusRows([]));
160
+ const [runnerStatusLoading, setRunnerStatusLoading] = (0, react_1.useState)(false);
161
+ const [runnerStatusError, setRunnerStatusError] = (0, react_1.useState)(null);
162
+ const [preferredRunner, setPreferredRunnerState] = (0, react_1.useState)(null);
163
+ const [preferredModel, setPreferredModelState] = (0, react_1.useState)('');
164
+ const [showOpenCodeConfig, setShowOpenCodeConfig] = (0, react_1.useState)(false);
76
165
  const mountedRef = (0, react_1.useRef)(true);
77
166
  const loadSelectedMachine = (0, react_1.useCallback)(async () => {
78
167
  const cfg = YaverFeedback_1.YaverFeedback.getConfig();
@@ -170,6 +259,72 @@ const FeedbackModal = () => {
170
259
  }
171
260
  }
172
261
  }, []);
262
+ const loadRunnerStatuses = (0, react_1.useCallback)(async () => {
263
+ const cfg = YaverFeedback_1.YaverFeedback.getConfig();
264
+ if (!cfg?.authToken) {
265
+ if (mountedRef.current) {
266
+ setRunnerCards(normalizeRunnerStatusRows([]));
267
+ setRunnerStatusError('Sign in to inspect coding-agent status.');
268
+ setRunnerStatusLoading(false);
269
+ }
270
+ return;
271
+ }
272
+ if (!cfg.preferredDeviceId) {
273
+ if (mountedRef.current) {
274
+ setRunnerCards(normalizeRunnerStatusRows([]));
275
+ setRunnerStatusError('Pick a machine to inspect coding-agent status.');
276
+ setRunnerStatusLoading(false);
277
+ }
278
+ return;
279
+ }
280
+ if (mountedRef.current) {
281
+ setRunnerStatusLoading(true);
282
+ setRunnerStatusError(null);
283
+ }
284
+ try {
285
+ let client = YaverFeedback_1.YaverFeedback.getP2PClient();
286
+ if (!client) {
287
+ const ok = await YaverFeedback_1.YaverFeedback.reconnect();
288
+ if (ok)
289
+ client = YaverFeedback_1.YaverFeedback.getP2PClient();
290
+ }
291
+ if (!client) {
292
+ throw new Error('Not connected to the selected machine yet.');
293
+ }
294
+ const rows = await client.getRunnerAuthStatus();
295
+ if (mountedRef.current) {
296
+ setRunnerCards(normalizeRunnerStatusRows(rows));
297
+ }
298
+ }
299
+ catch (err) {
300
+ if (mountedRef.current) {
301
+ setRunnerCards(normalizeRunnerStatusRows([]));
302
+ setRunnerStatusError(err instanceof Error ? err.message : String(err));
303
+ }
304
+ }
305
+ finally {
306
+ if (mountedRef.current)
307
+ setRunnerStatusLoading(false);
308
+ }
309
+ }, []);
310
+ const loadRoutingPrefs = (0, react_1.useCallback)(async () => {
311
+ try {
312
+ const [runner, model] = await Promise.all([
313
+ (0, preferences_1.getPreferredRunner)(),
314
+ (0, preferences_1.getPreferredModel)(),
315
+ ]);
316
+ if (!mountedRef.current)
317
+ return;
318
+ setPreferredRunnerState(runner);
319
+ setPreferredModelState(model ?? '');
320
+ }
321
+ catch {
322
+ if (!mountedRef.current)
323
+ return;
324
+ setPreferredRunnerState(null);
325
+ setPreferredModelState('');
326
+ }
327
+ }, []);
173
328
  (0, react_1.useEffect)(() => {
174
329
  mountedRef.current = true;
175
330
  const sub = react_native_1.DeviceEventEmitter.addListener('yaverFeedback:startReport', () => {
@@ -197,6 +352,7 @@ const FeedbackModal = () => {
197
352
  })
198
353
  .catch(() => { });
199
354
  void loadSelectedMachine();
355
+ void loadRunnerStatuses();
200
356
  }
201
357
  });
202
358
  // Agent streams build / compile progress through the BlackBox
@@ -224,15 +380,16 @@ const FeedbackModal = () => {
224
380
  sub.remove();
225
381
  statusSub.remove();
226
382
  };
227
- }, [loadSelectedMachine]);
383
+ }, [loadRunnerStatuses, loadSelectedMachine]);
228
384
  (0, react_1.useEffect)(() => {
229
385
  if (!visible)
230
386
  return;
231
387
  const interval = setInterval(() => {
232
388
  void loadSelectedMachine();
389
+ void loadRunnerStatuses();
233
390
  }, 5000);
234
391
  return () => clearInterval(interval);
235
- }, [loadSelectedMachine, visible]);
392
+ }, [loadRunnerStatuses, loadSelectedMachine, visible]);
236
393
  (0, react_1.useEffect)(() => {
237
394
  if (!visible) {
238
395
  setKeyboardInset(0);
@@ -265,6 +422,7 @@ const FeedbackModal = () => {
265
422
  setAction('idle');
266
423
  setShowVibeInput(false);
267
424
  setVibePrompt('');
425
+ setRunnerStatusError(null);
268
426
  }, []);
269
427
  // Helper: run a P2P call; on network failure, ask YaverFeedback to
270
428
  // re-query Convex for the fresh IP and retry once. Solves the common
@@ -488,6 +646,13 @@ const FeedbackModal = () => {
488
646
  setShowVibeInput(false);
489
647
  }
490
648
  }, [showVibeInput, vibePrompt]);
649
+ // Hold the active vibe-chat session — set when handleVibingSubmit
650
+ // returns a fresh taskId. Renders <VibeChatScreen> which streams the
651
+ // SSE transcript, supports multi-turn follow-ups via /tasks/{id}/
652
+ // resume, and exposes a Reload button. Mirrors the in-Yaver native
653
+ // pane's transcript-mode behaviour, just rendered in RN here.
654
+ const [activeVibe, setActiveVibe] = (0, react_1.useState)(null);
655
+ const [includeScreenshot, setIncludeScreenshot] = (0, react_1.useState)(true);
491
656
  const handleVibingSubmit = (0, react_1.useCallback)(async () => {
492
657
  const client = YaverFeedback_1.YaverFeedback.getP2PClient();
493
658
  if (!client) {
@@ -506,13 +671,49 @@ const FeedbackModal = () => {
506
671
  .join('\n')
507
672
  : '';
508
673
  const userPrompt = vibePrompt.trim();
509
- const prompt = userPrompt
674
+ const promptText = userPrompt
510
675
  ? userPrompt + errNote
511
676
  : 'Pick the next small improvement or fix for this app based on recent activity and the current screen.' +
512
677
  errNote;
513
- const result = await client.vibing(prompt);
678
+ // Optional screenshot captured from the host app's window.
679
+ // captureScreenshotBase64 returns null when react-native-view-
680
+ // shot isn't installed; we skip the screenshot rather than
681
+ // abort the whole feedback in that case.
682
+ let screenshotBase64;
683
+ if (includeScreenshot) {
684
+ const cap = await Promise.resolve().then(() => __importStar(require('./capture')));
685
+ const captured = await cap.captureScreenshotBase64();
686
+ if (captured?.base64) {
687
+ screenshotBase64 = captured.base64;
688
+ }
689
+ }
690
+ // Resolve project context the same way reloadApp / vibing did.
691
+ const { resolveAppIdentity } = await Promise.resolve().then(() => __importStar(require('./P2PClient')));
692
+ const identity = resolveAppIdentity();
693
+ // Pull the user's preferred runner / model from local prefs.
694
+ // Both are optional — the agent falls back to whatever runner
695
+ // is signed in if neither is provided.
696
+ const prefs = await Promise.resolve().then(() => __importStar(require('./preferences')));
697
+ const preferredRunner = (await prefs.getPreferredRunner?.()) ?? null;
698
+ const preferredModel = (await prefs.getPreferredModel?.()) ?? null;
699
+ const result = await client.createFeedbackTask({
700
+ userPrompt: promptText,
701
+ projectName: identity.projectName,
702
+ projectPath: identity.projectPath,
703
+ runner: preferredRunner ?? undefined,
704
+ model: preferredModel ?? undefined,
705
+ screenshotBase64,
706
+ });
514
707
  setLastVibeTaskId(result.taskId);
515
- setToast(`Vibing task ${result.taskId.slice(0, 8)} created`);
708
+ // Hand off to VibeChatScreen — it streams the SSE transcript,
709
+ // accepts follow-ups, and surfaces a Reload button.
710
+ setActiveVibe({
711
+ taskId: result.taskId,
712
+ initialPrompt: promptText,
713
+ project: identity.projectName,
714
+ runner: preferredRunner ?? undefined,
715
+ model: preferredModel ?? undefined,
716
+ });
516
717
  setVibePrompt('');
517
718
  setShowVibeInput(false);
518
719
  }
@@ -523,20 +724,57 @@ const FeedbackModal = () => {
523
724
  if (mountedRef.current)
524
725
  setAction('idle');
525
726
  }
526
- }, [vibePrompt]);
727
+ }, [vibePrompt, includeScreenshot]);
527
728
  /*
528
729
  const handleScreenRecording = useCallback(async () => {
529
730
  ...
530
731
  }, [closeSoon, isRecordingVideo, lastVideo]);
531
732
  */
532
733
  const busy = action !== 'idle';
734
+ const readyRunnerCount = runnerCards.filter((row) => row.ready || row.authConfigured).length;
735
+ const missingRunnerCount = runnerCards.filter((row) => !row.installed).length;
736
+ const needsAuthRunnerCount = runnerCards.filter((row) => row.installed && !row.authConfigured && !row.ready).length;
737
+ // Once the user fires off a vibe task, swap the entire modal body
738
+ // for the live chat screen. The chat manages its own SSE
739
+ // subscription, multi-turn follow-ups, and Reload button. Closing
740
+ // the chat returns to idle and clears the active vibe.
741
+ if (visible && activeVibe) {
742
+ const client = YaverFeedback_1.YaverFeedback.getP2PClient();
743
+ return (<>
744
+ <AuthOverlay_1.AuthOverlay />
745
+ <QuickActionIcon_1.QuickActionIcon />
746
+ <react_native_1.Modal visible={visible} animationType="slide" transparent onRequestClose={() => setActiveVibe(null)}>
747
+ {client ? (<VibeChatScreen_1.VibeChatScreen client={client} initialTaskId={activeVibe.taskId} initialUserPrompt={activeVibe.initialPrompt} project={activeVibe.project} runner={activeVibe.runner} model={activeVibe.model} onClose={() => setActiveVibe(null)} onReload={async () => {
748
+ const c = YaverFeedback_1.YaverFeedback.getP2PClient();
749
+ if (!c)
750
+ throw new Error('Not connected');
751
+ await c.reloadApp();
752
+ }}/>) : null}
753
+ </react_native_1.Modal>
754
+ </>);
755
+ }
533
756
  return (<>
534
757
  <AuthOverlay_1.AuthOverlay />
535
758
  <QuickActionIcon_1.QuickActionIcon />
536
759
  {visible && (<react_native_1.Modal visible={visible} animationType="slide" transparent onRequestClose={handleClose}>
537
760
  <react_native_1.Pressable style={styles.overlay} onPress={handleClose}>
538
761
  <react_native_1.KeyboardAvoidingView behavior={react_native_1.Platform.OS === 'ios' ? 'padding' : 'height'} keyboardVerticalOffset={react_native_1.Platform.OS === 'ios' ? 12 : 0} style={styles.kbAvoider} pointerEvents="box-none">
539
- <react_native_1.Pressable style={styles.modal} onPress={(e) => {
762
+ <react_native_1.Pressable
763
+ // Tablet: cap modal width and center as a card-style
764
+ // sheet rather than a phone bottom sheet that stretches
765
+ // across a 12.9" iPad. Phone behaviour unchanged.
766
+ style={[
767
+ styles.modal,
768
+ isTablet
769
+ ? {
770
+ width: '100%',
771
+ maxWidth: 640,
772
+ alignSelf: 'center',
773
+ borderTopLeftRadius: 22,
774
+ borderTopRightRadius: 22,
775
+ }
776
+ : null,
777
+ ]} onPress={(e) => {
540
778
  e.stopPropagation();
541
779
  react_native_1.Keyboard.dismiss();
542
780
  }}>
@@ -585,6 +823,57 @@ const FeedbackModal = () => {
585
823
  <react_native_1.Text style={styles.machineMeta}>{machineCard.detail}</react_native_1.Text>
586
824
  </react_native_1.Pressable>
587
825
 
826
+ <react_native_1.View style={styles.runnerSection}>
827
+ <react_native_1.View style={styles.runnerSectionHeader}>
828
+ <react_native_1.View style={{ flex: 1 }}>
829
+ <react_native_1.Text style={styles.runnerSectionTitle}>Coding Agents</react_native_1.Text>
830
+ <react_native_1.Text style={styles.runnerSectionSubtitle}>
831
+ {runnerStatusLoading
832
+ ? 'Refreshing runner status on the selected machine…'
833
+ : `${readyRunnerCount} ready · ${needsAuthRunnerCount} need sign-in · ${missingRunnerCount} missing`}
834
+ </react_native_1.Text>
835
+ </react_native_1.View>
836
+ <react_native_1.Pressable onPress={() => void loadRunnerStatuses()} style={({ pressed }) => [
837
+ styles.runnerRefreshBtn,
838
+ pressed && styles.buttonPressed,
839
+ ]} accessibilityRole="button" accessibilityLabel="Refresh coding-agent status">
840
+ <react_native_1.Text style={styles.runnerRefreshBtnText}>
841
+ {runnerStatusLoading ? 'Refreshing…' : 'Refresh'}
842
+ </react_native_1.Text>
843
+ </react_native_1.Pressable>
844
+ </react_native_1.View>
845
+
846
+ {runnerCards.map((row) => (<react_native_1.View key={row.id} style={[
847
+ styles.runnerCard,
848
+ row.tone === 'ok' && styles.runnerCardOk,
849
+ row.tone === 'warning' && styles.runnerCardWarning,
850
+ row.tone === 'error' && styles.runnerCardError,
851
+ ]}>
852
+ <react_native_1.View style={styles.runnerCardTop}>
853
+ <react_native_1.View style={{ flex: 1 }}>
854
+ <react_native_1.Text style={styles.runnerCardTitle}>{row.name}</react_native_1.Text>
855
+ <react_native_1.Text style={[
856
+ styles.runnerCardStatus,
857
+ row.tone === 'ok' && styles.runnerCardStatusOk,
858
+ row.tone === 'warning' && styles.runnerCardStatusWarning,
859
+ row.tone === 'error' && styles.runnerCardStatusError,
860
+ ]}>
861
+ {row.statusLine}
862
+ </react_native_1.Text>
863
+ </react_native_1.View>
864
+ {row.actionRunner ? (<react_native_1.Pressable onPress={() => setRunnerAuthModal(row.actionRunner ?? null)} style={({ pressed }) => [
865
+ styles.runnerActionBtn,
866
+ pressed && styles.buttonPressed,
867
+ ]} accessibilityRole="button" accessibilityLabel={`${row.actionLabel} ${row.name}`}>
868
+ <react_native_1.Text style={styles.runnerActionBtnText}>{row.actionLabel}</react_native_1.Text>
869
+ </react_native_1.Pressable>) : null}
870
+ </react_native_1.View>
871
+ {row.detail ? (<react_native_1.Text style={styles.runnerCardDetail}>{row.detail}</react_native_1.Text>) : null}
872
+ </react_native_1.View>))}
873
+
874
+ {runnerStatusError ? (<react_native_1.Text style={styles.runnerSectionError}>{runnerStatusError}</react_native_1.Text>) : null}
875
+ </react_native_1.View>
876
+
588
877
  {quickIconHidden && (<react_native_1.View style={styles.quickIconNote}>
589
878
  <react_native_1.Text style={styles.quickIconNoteText}>
590
879
  Quick access icon is hidden. Shake the phone if you want feedback back fast.
@@ -613,6 +902,7 @@ const FeedbackModal = () => {
613
902
  void YaverFeedback_1.YaverFeedback.setQuickIconColorPreset(preset);
614
903
  }} style={[
615
904
  styles.iconOption,
905
+ iconOptionWidthOverride ? { width: iconOptionWidthOverride } : null,
616
906
  selected && styles.iconOptionSelected,
617
907
  ]}>
618
908
  <react_native_1.View style={[
@@ -667,28 +957,13 @@ const FeedbackModal = () => {
667
957
  {/* Screenshot & Fix */}
668
958
  <ActionRow label={action === 'capturing' ? 'Working…' : 'Screenshot & Fix'} tint="#22c55e" onPress={handleScreenshotAndFix} disabled={busy} busy={action === 'capturing'}/>
669
959
 
670
- {/* Remote sign-in buttons trigger codex/claude device-auth
671
- on the selected agent without leaving the app. Opens a
672
- small native modal showing the verification URL + 8-char
673
- code the user enters in any browser. No API keys. */}
674
- <react_native_1.View style={runnerAuthRowStyles.container}>
675
- <react_native_1.Pressable onPress={() => setRunnerAuthModal('codex')} disabled={busy} style={({ pressed }) => [
676
- runnerAuthRowStyles.button,
677
- pressed && runnerAuthRowStyles.buttonPressed,
678
- busy && runnerAuthRowStyles.buttonDisabled,
679
- ]} accessibilityRole="button" accessibilityLabel="Remote sign-in Codex">
680
- <react_native_1.Text style={runnerAuthRowStyles.buttonLabel}>Remote sign-in</react_native_1.Text>
681
- <react_native_1.Text style={runnerAuthRowStyles.buttonName}>Codex</react_native_1.Text>
682
- </react_native_1.Pressable>
683
- <react_native_1.Pressable onPress={() => setRunnerAuthModal('claude')} disabled={busy} style={({ pressed }) => [
684
- runnerAuthRowStyles.button,
685
- pressed && runnerAuthRowStyles.buttonPressed,
686
- busy && runnerAuthRowStyles.buttonDisabled,
687
- ]} accessibilityRole="button" accessibilityLabel="Remote sign-in Claude">
688
- <react_native_1.Text style={runnerAuthRowStyles.buttonLabel}>Remote sign-in</react_native_1.Text>
689
- <react_native_1.Text style={runnerAuthRowStyles.buttonName}>Claude</react_native_1.Text>
690
- </react_native_1.Pressable>
691
- </react_native_1.View>
960
+ {/* Deploy opens an inline panel that talks to
961
+ /fleet/deploy-options on the agent and lets the user
962
+ pick TestFlight / Play / Both, then a machine to run
963
+ it on. Capabilities (e.g. "Linux can't TestFlight")
964
+ come from the agent's doctor probes — no client-side
965
+ platform smarts here. */}
966
+ {!showDeploy ? (<ActionRow label="Deploy" tint="#7f8cf7" onPress={() => setShowDeploy(true)} disabled={busy}/>) : (<DeployPanel_1.DeployPanel onClose={() => setShowDeploy(false)}/>)}
692
967
 
693
968
  {progress !== null && (<react_native_1.View style={styles.progressTrack}>
694
969
  <react_native_1.View style={[
@@ -710,7 +985,10 @@ const FeedbackModal = () => {
710
985
  </react_native_1.KeyboardAvoidingView>
711
986
  </react_native_1.Pressable>
712
987
  </react_native_1.Modal>)}
713
- {runnerAuthModal ? (<RunnerAuthNativeModal runner={runnerAuthModal} onClose={() => setRunnerAuthModal(null)}/>) : null}
988
+ {runnerAuthModal ? (<RunnerAuthNativeModal runner={runnerAuthModal} onClose={() => {
989
+ setRunnerAuthModal(null);
990
+ void loadRunnerStatuses();
991
+ }}/>) : null}
714
992
  </>);
715
993
  };
716
994
  exports.FeedbackModal = FeedbackModal;
@@ -908,6 +1186,106 @@ const styles = react_native_1.StyleSheet.create({
908
1186
  marginTop: 4,
909
1187
  lineHeight: 17,
910
1188
  },
1189
+ runnerSection: {
1190
+ marginTop: 2,
1191
+ gap: 10,
1192
+ },
1193
+ runnerSectionHeader: {
1194
+ flexDirection: 'row',
1195
+ alignItems: 'center',
1196
+ gap: 10,
1197
+ },
1198
+ runnerSectionTitle: {
1199
+ color: '#f8fafc',
1200
+ fontSize: 16,
1201
+ fontWeight: '700',
1202
+ },
1203
+ runnerSectionSubtitle: {
1204
+ marginTop: 2,
1205
+ color: '#94a3b8',
1206
+ fontSize: 12,
1207
+ },
1208
+ runnerRefreshBtn: {
1209
+ borderRadius: 10,
1210
+ borderWidth: 1,
1211
+ borderColor: 'rgba(148,163,184,0.22)',
1212
+ backgroundColor: 'rgba(15,23,42,0.65)',
1213
+ paddingHorizontal: 10,
1214
+ paddingVertical: 8,
1215
+ },
1216
+ runnerRefreshBtnText: {
1217
+ color: '#cbd5e1',
1218
+ fontSize: 12,
1219
+ fontWeight: '600',
1220
+ },
1221
+ runnerCard: {
1222
+ borderRadius: 12,
1223
+ borderWidth: 1,
1224
+ borderColor: 'rgba(148,163,184,0.14)',
1225
+ backgroundColor: 'rgba(15,23,42,0.45)',
1226
+ paddingHorizontal: 12,
1227
+ paddingVertical: 11,
1228
+ gap: 6,
1229
+ },
1230
+ runnerCardOk: {
1231
+ borderColor: 'rgba(34,197,94,0.28)',
1232
+ backgroundColor: 'rgba(20,83,45,0.20)',
1233
+ },
1234
+ runnerCardWarning: {
1235
+ borderColor: 'rgba(251,191,36,0.28)',
1236
+ backgroundColor: 'rgba(120,53,15,0.18)',
1237
+ },
1238
+ runnerCardError: {
1239
+ borderColor: 'rgba(248,113,113,0.28)',
1240
+ backgroundColor: 'rgba(127,29,29,0.18)',
1241
+ },
1242
+ runnerCardTop: {
1243
+ flexDirection: 'row',
1244
+ alignItems: 'center',
1245
+ gap: 10,
1246
+ },
1247
+ runnerCardTitle: {
1248
+ color: '#f8fafc',
1249
+ fontSize: 14,
1250
+ fontWeight: '700',
1251
+ },
1252
+ runnerCardStatus: {
1253
+ marginTop: 2,
1254
+ fontSize: 12,
1255
+ color: '#cbd5e1',
1256
+ },
1257
+ runnerCardStatusOk: {
1258
+ color: '#86efac',
1259
+ },
1260
+ runnerCardStatusWarning: {
1261
+ color: '#fcd34d',
1262
+ },
1263
+ runnerCardStatusError: {
1264
+ color: '#fca5a5',
1265
+ },
1266
+ runnerCardDetail: {
1267
+ color: '#94a3b8',
1268
+ fontSize: 11,
1269
+ lineHeight: 16,
1270
+ },
1271
+ runnerActionBtn: {
1272
+ borderRadius: 10,
1273
+ borderWidth: 1,
1274
+ borderColor: 'rgba(129,140,248,0.35)',
1275
+ backgroundColor: 'rgba(67,56,202,0.22)',
1276
+ paddingHorizontal: 12,
1277
+ paddingVertical: 8,
1278
+ },
1279
+ runnerActionBtnText: {
1280
+ color: '#c7d2fe',
1281
+ fontSize: 12,
1282
+ fontWeight: '700',
1283
+ },
1284
+ runnerSectionError: {
1285
+ color: '#fca5a5',
1286
+ fontSize: 12,
1287
+ lineHeight: 18,
1288
+ },
911
1289
  captureChoices: {
912
1290
  gap: 10,
913
1291
  },
@@ -1240,39 +1618,6 @@ const RunnerAuthNativeModal = ({ runner, onClose }) => {
1240
1618
  </react_native_1.View>
1241
1619
  </react_native_1.Modal>);
1242
1620
  };
1243
- const runnerAuthRowStyles = react_native_1.StyleSheet.create({
1244
- container: {
1245
- flexDirection: 'row',
1246
- gap: 8,
1247
- marginTop: 8,
1248
- flexWrap: 'wrap',
1249
- },
1250
- button: {
1251
- flexGrow: 1,
1252
- flexBasis: 0,
1253
- minWidth: 120,
1254
- paddingHorizontal: 12,
1255
- paddingVertical: 10,
1256
- borderRadius: 10,
1257
- borderWidth: 1,
1258
- borderColor: 'rgba(148,163,184,0.22)',
1259
- backgroundColor: 'rgba(15,23,42,0.6)',
1260
- },
1261
- buttonPressed: { opacity: 0.7 },
1262
- buttonDisabled: { opacity: 0.4 },
1263
- buttonLabel: {
1264
- fontSize: 10,
1265
- color: '#94a3b8',
1266
- textTransform: 'uppercase',
1267
- letterSpacing: 0.8,
1268
- },
1269
- buttonName: {
1270
- marginTop: 2,
1271
- fontSize: 14,
1272
- fontWeight: '600',
1273
- color: '#f1f5f9',
1274
- },
1275
- });
1276
1621
  const runnerAuthModalStyles = react_native_1.StyleSheet.create({
1277
1622
  overlay: {
1278
1623
  flex: 1,
@@ -41,6 +41,14 @@ const FixReport_1 = require("./FixReport");
41
41
  const BlackBox_1 = require("./BlackBox");
42
42
  const DEFAULT_SIZE = 40;
43
43
  const DEFAULT_COLOR = '#6366f1';
44
+ // Tablet detection — short-edge dp >= 600 means iPad / 7"+ Android
45
+ // tablet / Z Fold open. The SDK has no app-side responsive context
46
+ // to lean on (it's a guest in third-party apps), so we infer
47
+ // locally and bump the button + panel to tablet sizes.
48
+ const TABLET_SHORT_EDGE = 600;
49
+ function isTabletWindow(width, height) {
50
+ return Math.min(width, height) >= TABLET_SHORT_EDGE;
51
+ }
44
52
  const DEFAULT_PANEL_BG = '#2d2d2d';
45
53
  /**
46
54
  * Draggable debug console button for the Yaver Feedback SDK.
@@ -71,7 +79,16 @@ const DEFAULT_PANEL_BG = '#2d2d2d';
71
79
  * - **"quit"** → disable the SDK
72
80
  */
73
81
  const FloatingButton = ({ onPress, initialPosition, size = DEFAULT_SIZE, color = DEFAULT_COLOR, showStatusDot = true, style: stylePreset = 'terminal', icon, agentUrl: agentUrlProp, authToken: authTokenProp, healthCheckInterval = 5000, panelBackgroundColor, }) => {
74
- const { width: screenWidth } = react_native_1.Dimensions.get('window');
82
+ // Read window size live so the SDK overlay re-pins itself when
83
+ // the host app rotates or splits. The legacy snapshot via
84
+ // Dimensions.get only ran once and parked the button off-screen
85
+ // after orientation changes on iPad.
86
+ const { width: screenWidth, height: screenHeight } = (0, react_native_1.useWindowDimensions)();
87
+ const isTablet = isTabletWindow(screenWidth, screenHeight);
88
+ // Tablets get a larger touch target and a wider panel — phones
89
+ // keep the existing 40 / 280 defaults so guest apps aren't
90
+ // disrupted on small screens.
91
+ const effectiveSize = isTablet ? Math.max(size, 52) : size;
75
92
  const defaultX = initialPosition?.x ?? 10;
76
93
  const defaultY = initialPosition?.y ?? 90;
77
94
  const pan = (0, react_1.useRef)(new react_native_1.Animated.ValueXY({ x: defaultX, y: defaultY })).current;
@@ -516,7 +533,12 @@ const FloatingButton = ({ onPress, initialPosition, size = DEFAULT_SIZE, color =
516
533
  const isTerminal = stylePreset === 'terminal';
517
534
  const buttonIcon = icon ?? 'y';
518
535
  const btnBg = isConnected ? color : `${color}88`;
519
- const panelWidth = fullSize ? screenWidth - 24 : 280;
536
+ // Panel sizing tablets get a wider compact panel (420) and a
537
+ // capped full-size panel (max 720 instead of full window) so the
538
+ // overlay doesn't dwarf the host app on a 12.9" iPad.
539
+ const compactPanelWidth = isTablet ? 420 : 280;
540
+ const fullPanelWidth = isTablet ? Math.min(screenWidth - 24, 720) : screenWidth - 24;
541
+ const panelWidth = fullSize ? fullPanelWidth : compactPanelWidth;
520
542
  return (<react_native_1.Animated.View style={[s.root, { transform: [{ translateX: pan.x }, { translateY: pan.y }] }]} {...panResponder.panHandlers}>
521
543
  {/* Console panel */}
522
544
  {chatOpen && (<react_native_1.View style={[
@@ -642,7 +664,7 @@ const FloatingButton = ({ onPress, initialPosition, size = DEFAULT_SIZE, color =
642
664
  <react_native_1.TouchableOpacity style={[
643
665
  s.button,
644
666
  isTerminal ? s.buttonTerminal : s.buttonMinimal,
645
- { backgroundColor: btnBg, width: size, height: size },
667
+ { backgroundColor: btnBg, width: effectiveSize, height: effectiveSize },
646
668
  !isTerminal && { borderRadius: size / 2 },
647
669
  ]} activeOpacity={0.7} onPress={handleTap}>
648
670
  <react_native_1.Text style={[s.buttonIcon, isTerminal && s.mono, { fontSize: 22 }]}>