yaver-feedback-react-native 0.8.13 → 0.9.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.
- package/app.plugin.js +126 -9
- package/dist/BlackBox.js +9 -1
- package/dist/DeployPanel.js +65 -0
- package/dist/Discovery.js +13 -2
- package/dist/FeedbackModal.js +477 -66
- package/dist/MachinePickerScreen.js +14 -5
- package/dist/P2PClient.d.ts +94 -1
- package/dist/P2PClient.js +281 -2
- package/dist/ShakeDetector.js +2 -0
- package/dist/VibeChatScreen.d.ts +6 -1
- package/dist/VibeChatScreen.js +204 -1
- package/dist/YaverFeedback.d.ts +98 -0
- package/dist/YaverFeedback.js +509 -46
- package/dist/__tests__/BlackBox.relayPassword.test.d.ts +1 -0
- package/dist/__tests__/BlackBox.relayPassword.test.js +105 -0
- package/dist/__tests__/BlackBoxAutoStart.test.d.ts +1 -0
- package/dist/__tests__/BlackBoxAutoStart.test.js +91 -0
- package/dist/__tests__/BlackBoxAutoStartColdStart.test.d.ts +1 -0
- package/dist/__tests__/BlackBoxAutoStartColdStart.test.js +156 -0
- package/dist/__tests__/BrowserLaneIcon.test.d.ts +3 -0
- package/dist/__tests__/BrowserLaneIcon.test.js +80 -0
- package/dist/__tests__/P2PClient.test.js +2 -2
- package/dist/__tests__/ReportIdentity.test.d.ts +1 -0
- package/dist/__tests__/ReportIdentity.test.js +168 -0
- package/dist/__tests__/SDKToken.test.js +1 -1
- package/dist/__tests__/ShakeToggle.test.d.ts +1 -0
- package/dist/__tests__/ShakeToggle.test.js +121 -0
- package/dist/__tests__/pickTargetDevice.test.d.ts +1 -0
- package/dist/__tests__/pickTargetDevice.test.js +89 -0
- package/dist/__tests__/reloadActions.test.d.ts +1 -0
- package/dist/__tests__/reloadActions.test.js +129 -0
- package/dist/__tests__/reloadActionsParity.test.d.ts +1 -0
- package/dist/__tests__/reloadActionsParity.test.js +42 -0
- package/dist/__tests__/types.test.js +5 -5
- package/dist/_core/device.d.ts +19 -9
- package/dist/_core/device.js +20 -14
- package/dist/capture.d.ts +6 -0
- package/dist/capture.js +53 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +11 -1
- package/dist/reloadActions.d.ts +88 -0
- package/dist/reloadActions.js +200 -0
- package/dist/storeShots.d.ts +67 -0
- package/dist/storeShots.js +137 -0
- package/dist/types.d.ts +215 -3
- package/dist/voice.d.ts +61 -0
- package/dist/voice.js +246 -0
- package/package.json +14 -3
- package/src/BlackBox.ts +10 -1
- package/src/DeployPanel.tsx +74 -0
- package/src/Discovery.ts +13 -2
- package/src/FeedbackModal.tsx +563 -87
- package/src/MachinePickerScreen.tsx +12 -3
- package/src/P2PClient.ts +314 -2
- package/src/ShakeDetector.ts +1 -0
- package/src/VibeChatScreen.tsx +219 -0
- package/src/YaverFeedback.ts +498 -46
- package/src/__tests__/BlackBox.relayPassword.test.ts +129 -0
- package/src/__tests__/BlackBoxAutoStart.test.ts +111 -0
- package/src/__tests__/BlackBoxAutoStartColdStart.test.ts +191 -0
- package/src/__tests__/BrowserLaneIcon.test.ts +85 -0
- package/src/__tests__/P2PClient.test.ts +2 -2
- package/src/__tests__/ReportIdentity.test.ts +203 -0
- package/src/__tests__/SDKToken.test.ts +1 -1
- package/src/__tests__/ShakeToggle.test.ts +153 -0
- package/src/__tests__/pickTargetDevice.test.ts +101 -0
- package/src/__tests__/reloadActions.test.ts +171 -0
- package/src/__tests__/reloadActionsParity.test.ts +49 -0
- package/src/__tests__/types.test.ts +5 -5
- package/src/_core/device.ts +20 -14
- package/src/capture.ts +51 -0
- package/src/index.ts +21 -0
- package/src/reloadActions.ts +273 -0
- package/src/storeShots.ts +189 -0
- package/src/types.ts +217 -3
- package/src/voice.ts +270 -0
package/src/FeedbackModal.tsx
CHANGED
|
@@ -25,15 +25,28 @@ import {
|
|
|
25
25
|
// stopVideoRecording,
|
|
26
26
|
} from './capture';
|
|
27
27
|
import { uploadFeedback } from './upload';
|
|
28
|
-
import {
|
|
28
|
+
import { resolveReportIdentity } from './P2PClient';
|
|
29
|
+
import {
|
|
30
|
+
DeviceInfo,
|
|
31
|
+
FeedbackBundle,
|
|
32
|
+
OpenCodeConfigSummary,
|
|
33
|
+
OpenCodeProviderSummary,
|
|
34
|
+
RunnerAuthStatusRow,
|
|
35
|
+
} from './types';
|
|
29
36
|
import { AuthOverlay } from './AuthOverlay';
|
|
30
37
|
import { QuickActionIcon } from './QuickActionIcon';
|
|
31
38
|
import { VibeChatScreen } from './VibeChatScreen';
|
|
32
39
|
import { DeployPanel } from './DeployPanel';
|
|
33
40
|
import { listReachableDevices, RemoteDevice } from './auth';
|
|
41
|
+
import { reloadActions } from './reloadActions';
|
|
42
|
+
import type { DevServerSnapshot, ReloadAction } from './reloadActions';
|
|
34
43
|
import {
|
|
35
44
|
QUICK_ICON_COLOR_PRESETS,
|
|
36
45
|
QuickIconColorPreset,
|
|
46
|
+
getPreferredModel,
|
|
47
|
+
getPreferredRunner,
|
|
48
|
+
setPreferredModel,
|
|
49
|
+
setPreferredRunner,
|
|
37
50
|
} from './preferences';
|
|
38
51
|
|
|
39
52
|
/**
|
|
@@ -64,6 +77,111 @@ type MachineCardState = {
|
|
|
64
77
|
detail: string;
|
|
65
78
|
};
|
|
66
79
|
|
|
80
|
+
type RunnerTone = 'ok' | 'warning' | 'error' | 'neutral';
|
|
81
|
+
|
|
82
|
+
type RunnerCardState = {
|
|
83
|
+
id: string;
|
|
84
|
+
name: string;
|
|
85
|
+
installed: boolean;
|
|
86
|
+
authConfigured: boolean;
|
|
87
|
+
ready: boolean;
|
|
88
|
+
version?: string;
|
|
89
|
+
tone: RunnerTone;
|
|
90
|
+
statusLine: string;
|
|
91
|
+
detail?: string;
|
|
92
|
+
actionLabel?: string;
|
|
93
|
+
actionRunner?: string;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
type ProviderEditorState = {
|
|
97
|
+
mode: 'add' | 'edit';
|
|
98
|
+
id: string;
|
|
99
|
+
name: string;
|
|
100
|
+
baseUrl: string;
|
|
101
|
+
apiKey: string;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const PRIMARY_RUNNER_IDS = ['claude', 'codex', 'opencode'] as const;
|
|
105
|
+
|
|
106
|
+
function normalizeRunnerStatusRows(rows: RunnerAuthStatusRow[]): RunnerCardState[] {
|
|
107
|
+
const byId = new Map<string, RunnerAuthStatusRow>();
|
|
108
|
+
for (const row of rows) {
|
|
109
|
+
const raw = String(row.id || '').trim().toLowerCase();
|
|
110
|
+
if (!raw) continue;
|
|
111
|
+
const normalized = raw === 'claude-code' ? 'claude' : raw;
|
|
112
|
+
if (!PRIMARY_RUNNER_IDS.includes(normalized as (typeof PRIMARY_RUNNER_IDS)[number])) continue;
|
|
113
|
+
byId.set(normalized, { ...row, id: normalized });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return PRIMARY_RUNNER_IDS.map((id) => {
|
|
117
|
+
const baseName =
|
|
118
|
+
id === 'claude' ? 'Claude Code' : id === 'codex' ? 'OpenAI Codex' : 'OpenCode';
|
|
119
|
+
const row = byId.get(id);
|
|
120
|
+
if (!row) {
|
|
121
|
+
return {
|
|
122
|
+
id,
|
|
123
|
+
name: baseName,
|
|
124
|
+
installed: false,
|
|
125
|
+
authConfigured: false,
|
|
126
|
+
ready: false,
|
|
127
|
+
tone: 'warning',
|
|
128
|
+
statusLine: 'Not installed on the selected machine',
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const versionPrefix = row.version?.trim() ? `${row.version.trim()} · ` : '';
|
|
133
|
+
const detail = row.error?.trim() || row.warning?.trim() || row.detail?.trim() || undefined;
|
|
134
|
+
|
|
135
|
+
if (!row.installed) {
|
|
136
|
+
return {
|
|
137
|
+
id,
|
|
138
|
+
name: row.name || baseName,
|
|
139
|
+
installed: false,
|
|
140
|
+
authConfigured: false,
|
|
141
|
+
ready: false,
|
|
142
|
+
version: row.version,
|
|
143
|
+
tone: 'warning',
|
|
144
|
+
statusLine: 'Not installed on the selected machine',
|
|
145
|
+
detail,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (id === 'opencode') {
|
|
150
|
+
const configured = row.authConfigured || row.ready;
|
|
151
|
+
return {
|
|
152
|
+
id,
|
|
153
|
+
name: row.name || baseName,
|
|
154
|
+
installed: row.installed,
|
|
155
|
+
authConfigured: row.authConfigured,
|
|
156
|
+
ready: row.ready,
|
|
157
|
+
version: row.version,
|
|
158
|
+
tone: configured ? 'ok' : 'warning',
|
|
159
|
+
statusLine: configured
|
|
160
|
+
? `${versionPrefix}Configured on the selected machine`
|
|
161
|
+
: `${versionPrefix}Needs provider config on the selected machine`,
|
|
162
|
+
detail,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const authed = row.authConfigured || row.ready;
|
|
167
|
+
return {
|
|
168
|
+
id,
|
|
169
|
+
name: row.name || baseName,
|
|
170
|
+
installed: row.installed,
|
|
171
|
+
authConfigured: row.authConfigured,
|
|
172
|
+
ready: row.ready,
|
|
173
|
+
version: row.version,
|
|
174
|
+
tone: authed ? 'ok' : 'warning',
|
|
175
|
+
statusLine: authed
|
|
176
|
+
? `${versionPrefix}Signed in on the selected machine`
|
|
177
|
+
: `${versionPrefix}Not signed in on the selected machine`,
|
|
178
|
+
detail,
|
|
179
|
+
actionLabel: authed ? 'Re-auth' : 'Sign in',
|
|
180
|
+
actionRunner: id,
|
|
181
|
+
};
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
67
185
|
export const FeedbackModal: React.FC = () => {
|
|
68
186
|
const { width: winW, height: winH } = useWindowDimensions();
|
|
69
187
|
const isTablet = Math.min(winW, winH) >= 600;
|
|
@@ -80,6 +198,11 @@ export const FeedbackModal: React.FC = () => {
|
|
|
80
198
|
// is our guaranteed UI for bringing the icon back — we surface a
|
|
81
199
|
// small "Show quick icon" row when this is true.
|
|
82
200
|
const [quickIconHidden, setQuickIconHidden] = useState(false);
|
|
201
|
+
// Dev-server snapshot behind the reload buttons. `null` means "we have not
|
|
202
|
+
// been able to ask" — rendered as "not connected", never as "no dev server".
|
|
203
|
+
const [devSnapshot, setDevSnapshot] = useState<DevServerSnapshot | null>(null);
|
|
204
|
+
// Which reload action is in flight, so only that button spins.
|
|
205
|
+
const [reloadingId, setReloadingId] = useState<string | null>(null);
|
|
83
206
|
const [runnerAuthModal, setRunnerAuthModal] = useState<string | null>(null);
|
|
84
207
|
// Vibing-input mode: same expand-on-tap pattern as email login.
|
|
85
208
|
// Tap "Vibing" once → the button reveals an input + Send; that lets
|
|
@@ -101,8 +224,38 @@ export const FeedbackModal: React.FC = () => {
|
|
|
101
224
|
title: 'No machine selected',
|
|
102
225
|
detail: 'Pick a remote dev machine before using the feedback actions.',
|
|
103
226
|
});
|
|
227
|
+
const [runnerCards, setRunnerCards] = useState<RunnerCardState[]>(() =>
|
|
228
|
+
normalizeRunnerStatusRows([]),
|
|
229
|
+
);
|
|
230
|
+
const [runnerStatusLoading, setRunnerStatusLoading] = useState(false);
|
|
231
|
+
const [runnerStatusError, setRunnerStatusError] = useState<string | null>(null);
|
|
232
|
+
const [preferredRunner, setPreferredRunnerState] = useState<string | null>(null);
|
|
233
|
+
const [preferredModel, setPreferredModelState] = useState('');
|
|
234
|
+
const [showOpenCodeConfig, setShowOpenCodeConfig] = useState(false);
|
|
104
235
|
const mountedRef = useRef(true);
|
|
105
236
|
|
|
237
|
+
/**
|
|
238
|
+
* Ask the machine what its dev server is doing, so the reload actions can
|
|
239
|
+
* be enabled/disabled against reality rather than against a guess.
|
|
240
|
+
*
|
|
241
|
+
* Best-effort and deliberately null-on-failure: null means "we could not
|
|
242
|
+
* ask", which the seam renders as "not connected to a machine yet" — a
|
|
243
|
+
* different sentence from "no dev server is running", because they have
|
|
244
|
+
* different fixes.
|
|
245
|
+
*/
|
|
246
|
+
const refreshDevSnapshot = useCallback(async () => {
|
|
247
|
+
try {
|
|
248
|
+
const client = YaverFeedback.getP2PClient();
|
|
249
|
+
if (!client) {
|
|
250
|
+
setDevSnapshot(null);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
setDevSnapshot(await client.getDevServerStatus());
|
|
254
|
+
} catch {
|
|
255
|
+
setDevSnapshot(null);
|
|
256
|
+
}
|
|
257
|
+
}, []);
|
|
258
|
+
|
|
106
259
|
const loadSelectedMachine = useCallback(async () => {
|
|
107
260
|
const cfg = YaverFeedback.getConfig();
|
|
108
261
|
if (!cfg?.authToken) {
|
|
@@ -204,6 +357,69 @@ export const FeedbackModal: React.FC = () => {
|
|
|
204
357
|
}
|
|
205
358
|
}, []);
|
|
206
359
|
|
|
360
|
+
const loadRunnerStatuses = useCallback(async () => {
|
|
361
|
+
const cfg = YaverFeedback.getConfig();
|
|
362
|
+
if (!cfg?.authToken) {
|
|
363
|
+
if (mountedRef.current) {
|
|
364
|
+
setRunnerCards(normalizeRunnerStatusRows([]));
|
|
365
|
+
setRunnerStatusError('Sign in to inspect coding-agent status.');
|
|
366
|
+
setRunnerStatusLoading(false);
|
|
367
|
+
}
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
if (!cfg.preferredDeviceId) {
|
|
371
|
+
if (mountedRef.current) {
|
|
372
|
+
setRunnerCards(normalizeRunnerStatusRows([]));
|
|
373
|
+
setRunnerStatusError('Pick a machine to inspect coding-agent status.');
|
|
374
|
+
setRunnerStatusLoading(false);
|
|
375
|
+
}
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (mountedRef.current) {
|
|
380
|
+
setRunnerStatusLoading(true);
|
|
381
|
+
setRunnerStatusError(null);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
try {
|
|
385
|
+
let client = YaverFeedback.getP2PClient();
|
|
386
|
+
if (!client) {
|
|
387
|
+
const ok = await YaverFeedback.reconnect();
|
|
388
|
+
if (ok) client = YaverFeedback.getP2PClient();
|
|
389
|
+
}
|
|
390
|
+
if (!client) {
|
|
391
|
+
throw new Error('Not connected to the selected machine yet.');
|
|
392
|
+
}
|
|
393
|
+
const rows = await client.getRunnerAuthStatus();
|
|
394
|
+
if (mountedRef.current) {
|
|
395
|
+
setRunnerCards(normalizeRunnerStatusRows(rows));
|
|
396
|
+
}
|
|
397
|
+
} catch (err) {
|
|
398
|
+
if (mountedRef.current) {
|
|
399
|
+
setRunnerCards(normalizeRunnerStatusRows([]));
|
|
400
|
+
setRunnerStatusError(err instanceof Error ? err.message : String(err));
|
|
401
|
+
}
|
|
402
|
+
} finally {
|
|
403
|
+
if (mountedRef.current) setRunnerStatusLoading(false);
|
|
404
|
+
}
|
|
405
|
+
}, []);
|
|
406
|
+
|
|
407
|
+
const loadRoutingPrefs = useCallback(async () => {
|
|
408
|
+
try {
|
|
409
|
+
const [runner, model] = await Promise.all([
|
|
410
|
+
getPreferredRunner(),
|
|
411
|
+
getPreferredModel(),
|
|
412
|
+
]);
|
|
413
|
+
if (!mountedRef.current) return;
|
|
414
|
+
setPreferredRunnerState(runner);
|
|
415
|
+
setPreferredModelState(model ?? '');
|
|
416
|
+
} catch {
|
|
417
|
+
if (!mountedRef.current) return;
|
|
418
|
+
setPreferredRunnerState(null);
|
|
419
|
+
setPreferredModelState('');
|
|
420
|
+
}
|
|
421
|
+
}, []);
|
|
422
|
+
|
|
207
423
|
useEffect(() => {
|
|
208
424
|
mountedRef.current = true;
|
|
209
425
|
const sub = DeviceEventEmitter.addListener('yaverFeedback:startReport', () => {
|
|
@@ -229,6 +445,7 @@ export const FeedbackModal: React.FC = () => {
|
|
|
229
445
|
})
|
|
230
446
|
.catch(() => {});
|
|
231
447
|
void loadSelectedMachine();
|
|
448
|
+
void loadRunnerStatuses();
|
|
232
449
|
}
|
|
233
450
|
});
|
|
234
451
|
// Agent streams build / compile progress through the BlackBox
|
|
@@ -257,15 +474,22 @@ export const FeedbackModal: React.FC = () => {
|
|
|
257
474
|
sub.remove();
|
|
258
475
|
statusSub.remove();
|
|
259
476
|
};
|
|
260
|
-
}, [loadSelectedMachine]);
|
|
477
|
+
}, [loadRunnerStatuses, loadSelectedMachine]);
|
|
261
478
|
|
|
262
479
|
useEffect(() => {
|
|
263
480
|
if (!visible) return;
|
|
481
|
+
// Poll the dev server alongside the machine + runners. A reload button
|
|
482
|
+
// whose enabled state was decided once, when the sheet opened, goes
|
|
483
|
+
// stale the moment the user starts Metro from another surface — and a
|
|
484
|
+
// stale "no dev server is running" reads as the product being broken.
|
|
485
|
+
void refreshDevSnapshot();
|
|
264
486
|
const interval = setInterval(() => {
|
|
265
487
|
void loadSelectedMachine();
|
|
488
|
+
void loadRunnerStatuses();
|
|
489
|
+
void refreshDevSnapshot();
|
|
266
490
|
}, 5000);
|
|
267
491
|
return () => clearInterval(interval);
|
|
268
|
-
}, [loadSelectedMachine, visible]);
|
|
492
|
+
}, [loadRunnerStatuses, loadSelectedMachine, refreshDevSnapshot, visible]);
|
|
269
493
|
|
|
270
494
|
useEffect(() => {
|
|
271
495
|
if (!visible) {
|
|
@@ -301,6 +525,7 @@ export const FeedbackModal: React.FC = () => {
|
|
|
301
525
|
setAction('idle');
|
|
302
526
|
setShowVibeInput(false);
|
|
303
527
|
setVibePrompt('');
|
|
528
|
+
setRunnerStatusError(null);
|
|
304
529
|
}, []);
|
|
305
530
|
|
|
306
531
|
// Helper: run a P2P call; on network failure, ask YaverFeedback to
|
|
@@ -353,7 +578,87 @@ export const FeedbackModal: React.FC = () => {
|
|
|
353
578
|
[],
|
|
354
579
|
);
|
|
355
580
|
|
|
356
|
-
// ─── 1.
|
|
581
|
+
// ─── 1. Reload ─────────────────────────────────────────────────────
|
|
582
|
+
//
|
|
583
|
+
// Three actions now, not one: Hot Reload (mode=fast), Full Reload
|
|
584
|
+
// (mode=full — Flutter's hot RESTART), and Rebuild Bundle
|
|
585
|
+
// (/dev/reload-app, the only one that works with Metro down).
|
|
586
|
+
//
|
|
587
|
+
// WHICH of them render, and which are disabled with what reason, is
|
|
588
|
+
// decided by the pure `reloadActions()` seam — never inline here, so the
|
|
589
|
+
// same policy holds on web, Flutter, Unity, Swift and Kotlin. In
|
|
590
|
+
// particular: a production build (`__DEV__ === false`) gets NONE of them.
|
|
591
|
+
|
|
592
|
+
const availableReloadActions = reloadActions(devSnapshot, {
|
|
593
|
+
// __DEV__ is React Native's own build flag. A release bundle sets it
|
|
594
|
+
// false, so a shipped app renders no reload UI at all — which is the
|
|
595
|
+
// point, and is what reloadActions.test.ts pins.
|
|
596
|
+
isDevBuild: typeof __DEV__ !== 'undefined' && __DEV__ === true,
|
|
597
|
+
connected: devSnapshot !== null,
|
|
598
|
+
machineLabel: machineCard.device?.name || undefined,
|
|
599
|
+
includeRebuild: true,
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
const handleReloadAction = useCallback(
|
|
603
|
+
async (reloadAction: ReloadAction) => {
|
|
604
|
+
if (!reloadAction.enabled) {
|
|
605
|
+
// Pressing a disabled action must SAY why. A row that does nothing
|
|
606
|
+
// is the same defect as a spinner that never resolves.
|
|
607
|
+
setToast(reloadAction.disabledReason || 'Reload is unavailable right now.');
|
|
608
|
+
setError(reloadAction.disabledReason || null);
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
setReloadingId(reloadAction.id);
|
|
612
|
+
setAction('hot-reloading');
|
|
613
|
+
setError(null);
|
|
614
|
+
setProgress(0);
|
|
615
|
+
setToast(`${reloadAction.label}…`);
|
|
616
|
+
try {
|
|
617
|
+
await loadSelectedMachine();
|
|
618
|
+
const selected = await YaverFeedback.getSelectedRemoteDevice();
|
|
619
|
+
if (!selected) {
|
|
620
|
+
YaverFeedback.showMachinePicker();
|
|
621
|
+
throw new Error('No machine selected. Pick a machine and try again.');
|
|
622
|
+
}
|
|
623
|
+
if (selected.needsAuth) {
|
|
624
|
+
YaverFeedback.showMachinePicker();
|
|
625
|
+
throw new Error('Selected machine needs pairing again.');
|
|
626
|
+
}
|
|
627
|
+
if (!selected.isOnline) {
|
|
628
|
+
throw new Error('Selected machine is offline. Start `yaver serve` on it first.');
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
let ackMessage = `${reloadAction.label} requested.`;
|
|
632
|
+
await runWithReconnect(async (client) => {
|
|
633
|
+
const ack = await client.reloadWithMode(reloadAction.mode, devSnapshot);
|
|
634
|
+
ackMessage = ack.message;
|
|
635
|
+
setToast(ack.message);
|
|
636
|
+
setProgress(0.2);
|
|
637
|
+
});
|
|
638
|
+
setToast(ackMessage);
|
|
639
|
+
if (reloadAction.mode === 'bundle') closeSoon(2500);
|
|
640
|
+
} catch (err: unknown) {
|
|
641
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
642
|
+
setError(message);
|
|
643
|
+
setToast(
|
|
644
|
+
message.toLowerCase().indexOf('session expired') >= 0
|
|
645
|
+
? 'Session expired. Sign in again.'
|
|
646
|
+
: message,
|
|
647
|
+
);
|
|
648
|
+
await loadSelectedMachine();
|
|
649
|
+
setProgress(null);
|
|
650
|
+
} finally {
|
|
651
|
+
if (mountedRef.current) {
|
|
652
|
+
setAction('idle');
|
|
653
|
+
setReloadingId(null);
|
|
654
|
+
void refreshDevSnapshot();
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
},
|
|
658
|
+
[closeSoon, devSnapshot, loadSelectedMachine, refreshDevSnapshot, runWithReconnect],
|
|
659
|
+
);
|
|
660
|
+
|
|
661
|
+
/** Kept for the legacy one-tap path (BlackBox command, chat Reload button). */
|
|
357
662
|
const handleHotReload = useCallback(async () => {
|
|
358
663
|
setAction('hot-reloading');
|
|
359
664
|
setError(null);
|
|
@@ -472,19 +777,36 @@ export const FeedbackModal: React.FC = () => {
|
|
|
472
777
|
try {
|
|
473
778
|
const { Dimensions } = require('react-native');
|
|
474
779
|
const { width, height } = Dimensions.get('window');
|
|
780
|
+
const cfg = YaverFeedback.getConfig();
|
|
781
|
+
const identity = resolveReportIdentity({
|
|
782
|
+
projectName: cfg?.projectName,
|
|
783
|
+
bundleId: cfg?.bundleId,
|
|
784
|
+
surface: cfg?.surface,
|
|
785
|
+
surfaces: cfg?.surfaces,
|
|
786
|
+
stack: cfg?.stack,
|
|
787
|
+
stacks: cfg?.stacks,
|
|
788
|
+
testSurfaces: cfg?.testSurfaces,
|
|
789
|
+
feedbackSdk: cfg?.feedbackSdk,
|
|
790
|
+
feedbackTransport: cfg?.feedbackTransport,
|
|
791
|
+
voiceCapabilities: cfg?.voiceCapabilities,
|
|
792
|
+
sttProvider: cfg?.sttProvider,
|
|
793
|
+
ttsProvider: cfg?.ttsProvider,
|
|
794
|
+
});
|
|
475
795
|
const deviceInfo: DeviceInfo = {
|
|
476
796
|
platform: Platform.OS,
|
|
477
797
|
osVersion: String(Platform.Version),
|
|
478
798
|
model: Platform.OS === 'ios' ? 'iOS Device' : 'Android Device',
|
|
479
799
|
screenWidth: width,
|
|
480
800
|
screenHeight: height,
|
|
801
|
+
appName: identity.appName,
|
|
481
802
|
};
|
|
482
803
|
const capturedErrors = YaverFeedback.getCapturedErrors();
|
|
483
804
|
const bundle: FeedbackBundle = {
|
|
484
805
|
metadata: {
|
|
485
806
|
timestamp: new Date().toISOString(),
|
|
486
|
-
|
|
487
|
-
app:
|
|
807
|
+
deviceInfo,
|
|
808
|
+
app: identity.app,
|
|
809
|
+
project: identity.project,
|
|
488
810
|
userNote: '[Screenshot + Fix]',
|
|
489
811
|
},
|
|
490
812
|
screenshots: [path],
|
|
@@ -552,6 +874,9 @@ export const FeedbackModal: React.FC = () => {
|
|
|
552
874
|
const [activeVibe, setActiveVibe] = useState<{
|
|
553
875
|
taskId: string;
|
|
554
876
|
initialPrompt: string;
|
|
877
|
+
project?: string;
|
|
878
|
+
runner?: string;
|
|
879
|
+
model?: string;
|
|
555
880
|
} | null>(null);
|
|
556
881
|
const [includeScreenshot, setIncludeScreenshot] = useState<boolean>(true);
|
|
557
882
|
|
|
@@ -614,7 +939,13 @@ export const FeedbackModal: React.FC = () => {
|
|
|
614
939
|
setLastVibeTaskId(result.taskId);
|
|
615
940
|
// Hand off to VibeChatScreen — it streams the SSE transcript,
|
|
616
941
|
// accepts follow-ups, and surfaces a Reload button.
|
|
617
|
-
setActiveVibe({
|
|
942
|
+
setActiveVibe({
|
|
943
|
+
taskId: result.taskId,
|
|
944
|
+
initialPrompt: promptText,
|
|
945
|
+
project: identity.projectName,
|
|
946
|
+
runner: preferredRunner ?? undefined,
|
|
947
|
+
model: preferredModel ?? undefined,
|
|
948
|
+
});
|
|
618
949
|
setVibePrompt('');
|
|
619
950
|
setShowVibeInput(false);
|
|
620
951
|
} catch (err: unknown) {
|
|
@@ -631,6 +962,11 @@ export const FeedbackModal: React.FC = () => {
|
|
|
631
962
|
*/
|
|
632
963
|
|
|
633
964
|
const busy = action !== 'idle';
|
|
965
|
+
const readyRunnerCount = runnerCards.filter((row) => row.ready || row.authConfigured).length;
|
|
966
|
+
const missingRunnerCount = runnerCards.filter((row) => !row.installed).length;
|
|
967
|
+
const needsAuthRunnerCount = runnerCards.filter(
|
|
968
|
+
(row) => row.installed && !row.authConfigured && !row.ready,
|
|
969
|
+
).length;
|
|
634
970
|
|
|
635
971
|
// Once the user fires off a vibe task, swap the entire modal body
|
|
636
972
|
// for the live chat screen. The chat manages its own SSE
|
|
@@ -653,6 +989,9 @@ export const FeedbackModal: React.FC = () => {
|
|
|
653
989
|
client={client}
|
|
654
990
|
initialTaskId={activeVibe.taskId}
|
|
655
991
|
initialUserPrompt={activeVibe.initialPrompt}
|
|
992
|
+
project={activeVibe.project}
|
|
993
|
+
runner={activeVibe.runner}
|
|
994
|
+
model={activeVibe.model}
|
|
656
995
|
onClose={() => setActiveVibe(null)}
|
|
657
996
|
onReload={async () => {
|
|
658
997
|
const c = YaverFeedback.getP2PClient();
|
|
@@ -766,6 +1105,80 @@ export const FeedbackModal: React.FC = () => {
|
|
|
766
1105
|
<Text style={styles.machineMeta}>{machineCard.detail}</Text>
|
|
767
1106
|
</Pressable>
|
|
768
1107
|
|
|
1108
|
+
<View style={styles.runnerSection}>
|
|
1109
|
+
<View style={styles.runnerSectionHeader}>
|
|
1110
|
+
<View style={{ flex: 1 }}>
|
|
1111
|
+
<Text style={styles.runnerSectionTitle}>Coding Agents</Text>
|
|
1112
|
+
<Text style={styles.runnerSectionSubtitle}>
|
|
1113
|
+
{runnerStatusLoading
|
|
1114
|
+
? 'Refreshing runner status on the selected machine…'
|
|
1115
|
+
: `${readyRunnerCount} ready · ${needsAuthRunnerCount} need sign-in · ${missingRunnerCount} missing`}
|
|
1116
|
+
</Text>
|
|
1117
|
+
</View>
|
|
1118
|
+
<Pressable
|
|
1119
|
+
onPress={() => void loadRunnerStatuses()}
|
|
1120
|
+
style={({ pressed }) => [
|
|
1121
|
+
styles.runnerRefreshBtn,
|
|
1122
|
+
pressed && styles.buttonPressed,
|
|
1123
|
+
]}
|
|
1124
|
+
accessibilityRole="button"
|
|
1125
|
+
accessibilityLabel="Refresh coding-agent status"
|
|
1126
|
+
>
|
|
1127
|
+
<Text style={styles.runnerRefreshBtnText}>
|
|
1128
|
+
{runnerStatusLoading ? 'Refreshing…' : 'Refresh'}
|
|
1129
|
+
</Text>
|
|
1130
|
+
</Pressable>
|
|
1131
|
+
</View>
|
|
1132
|
+
|
|
1133
|
+
{runnerCards.map((row) => (
|
|
1134
|
+
<View
|
|
1135
|
+
key={row.id}
|
|
1136
|
+
style={[
|
|
1137
|
+
styles.runnerCard,
|
|
1138
|
+
row.tone === 'ok' && styles.runnerCardOk,
|
|
1139
|
+
row.tone === 'warning' && styles.runnerCardWarning,
|
|
1140
|
+
row.tone === 'error' && styles.runnerCardError,
|
|
1141
|
+
]}
|
|
1142
|
+
>
|
|
1143
|
+
<View style={styles.runnerCardTop}>
|
|
1144
|
+
<View style={{ flex: 1 }}>
|
|
1145
|
+
<Text style={styles.runnerCardTitle}>{row.name}</Text>
|
|
1146
|
+
<Text
|
|
1147
|
+
style={[
|
|
1148
|
+
styles.runnerCardStatus,
|
|
1149
|
+
row.tone === 'ok' && styles.runnerCardStatusOk,
|
|
1150
|
+
row.tone === 'warning' && styles.runnerCardStatusWarning,
|
|
1151
|
+
row.tone === 'error' && styles.runnerCardStatusError,
|
|
1152
|
+
]}
|
|
1153
|
+
>
|
|
1154
|
+
{row.statusLine}
|
|
1155
|
+
</Text>
|
|
1156
|
+
</View>
|
|
1157
|
+
{row.actionRunner ? (
|
|
1158
|
+
<Pressable
|
|
1159
|
+
onPress={() => setRunnerAuthModal(row.actionRunner ?? null)}
|
|
1160
|
+
style={({ pressed }) => [
|
|
1161
|
+
styles.runnerActionBtn,
|
|
1162
|
+
pressed && styles.buttonPressed,
|
|
1163
|
+
]}
|
|
1164
|
+
accessibilityRole="button"
|
|
1165
|
+
accessibilityLabel={`${row.actionLabel} ${row.name}`}
|
|
1166
|
+
>
|
|
1167
|
+
<Text style={styles.runnerActionBtnText}>{row.actionLabel}</Text>
|
|
1168
|
+
</Pressable>
|
|
1169
|
+
) : null}
|
|
1170
|
+
</View>
|
|
1171
|
+
{row.detail ? (
|
|
1172
|
+
<Text style={styles.runnerCardDetail}>{row.detail}</Text>
|
|
1173
|
+
) : null}
|
|
1174
|
+
</View>
|
|
1175
|
+
))}
|
|
1176
|
+
|
|
1177
|
+
{runnerStatusError ? (
|
|
1178
|
+
<Text style={styles.runnerSectionError}>{runnerStatusError}</Text>
|
|
1179
|
+
) : null}
|
|
1180
|
+
</View>
|
|
1181
|
+
|
|
769
1182
|
{quickIconHidden && (
|
|
770
1183
|
<View style={styles.quickIconNote}>
|
|
771
1184
|
<Text style={styles.quickIconNoteText}>
|
|
@@ -835,16 +1248,36 @@ export const FeedbackModal: React.FC = () => {
|
|
|
835
1248
|
</View>
|
|
836
1249
|
</View>
|
|
837
1250
|
|
|
838
|
-
{/* 1.
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
1251
|
+
{/* 1. Reload — Hot / Full / Rebuild Bundle.
|
|
1252
|
+
Rendered from the shared decision seam, so a production
|
|
1253
|
+
build (__DEV__ false) renders nothing here at all, and a
|
|
1254
|
+
blocked action shows greyed WITH its reason underneath
|
|
1255
|
+
rather than vanishing. */}
|
|
1256
|
+
{availableReloadActions.map((reloadAction) => (
|
|
1257
|
+
<View key={reloadAction.id} style={styles.reloadRow}>
|
|
1258
|
+
<ActionRow
|
|
1259
|
+
label={
|
|
1260
|
+
reloadingId === reloadAction.id
|
|
1261
|
+
? `${reloadAction.label}…`
|
|
1262
|
+
: reloadAction.label
|
|
1263
|
+
}
|
|
1264
|
+
tint={reloadAction.id === 'rebuild' ? '#38bdf8' : '#fbbf24'}
|
|
1265
|
+
onPress={() => {
|
|
1266
|
+
void handleReloadAction(reloadAction);
|
|
1267
|
+
}}
|
|
1268
|
+
// Never `disabled` at the Pressable level for a blocked
|
|
1269
|
+
// action: we WANT the tap so we can say why. Only a
|
|
1270
|
+
// genuinely busy modal blocks the press.
|
|
1271
|
+
disabled={busy && reloadingId !== reloadAction.id}
|
|
1272
|
+
busy={reloadingId === reloadAction.id}
|
|
1273
|
+
/>
|
|
1274
|
+
<Text style={styles.reloadHint}>
|
|
1275
|
+
{reloadAction.enabled
|
|
1276
|
+
? reloadAction.hint
|
|
1277
|
+
: reloadAction.disabledReason}
|
|
1278
|
+
</Text>
|
|
1279
|
+
</View>
|
|
1280
|
+
))}
|
|
848
1281
|
|
|
849
1282
|
{/* 3. Vibing — expands to an input box on first tap
|
|
850
1283
|
so the user says WHAT they want to vibe on, just
|
|
@@ -931,41 +1364,6 @@ export const FeedbackModal: React.FC = () => {
|
|
|
931
1364
|
<DeployPanel onClose={() => setShowDeploy(false)} />
|
|
932
1365
|
)}
|
|
933
1366
|
|
|
934
|
-
{/* Remote sign-in buttons — trigger codex/claude device-auth
|
|
935
|
-
on the selected agent without leaving the app. Opens a
|
|
936
|
-
small native modal showing the verification URL + 8-char
|
|
937
|
-
code the user enters in any browser. No API keys. */}
|
|
938
|
-
<View style={runnerAuthRowStyles.container}>
|
|
939
|
-
<Pressable
|
|
940
|
-
onPress={() => setRunnerAuthModal('codex')}
|
|
941
|
-
disabled={busy}
|
|
942
|
-
style={({ pressed }) => [
|
|
943
|
-
runnerAuthRowStyles.button,
|
|
944
|
-
pressed && runnerAuthRowStyles.buttonPressed,
|
|
945
|
-
busy && runnerAuthRowStyles.buttonDisabled,
|
|
946
|
-
]}
|
|
947
|
-
accessibilityRole="button"
|
|
948
|
-
accessibilityLabel="Remote sign-in Codex"
|
|
949
|
-
>
|
|
950
|
-
<Text style={runnerAuthRowStyles.buttonLabel}>Remote sign-in</Text>
|
|
951
|
-
<Text style={runnerAuthRowStyles.buttonName}>Codex</Text>
|
|
952
|
-
</Pressable>
|
|
953
|
-
<Pressable
|
|
954
|
-
onPress={() => setRunnerAuthModal('claude')}
|
|
955
|
-
disabled={busy}
|
|
956
|
-
style={({ pressed }) => [
|
|
957
|
-
runnerAuthRowStyles.button,
|
|
958
|
-
pressed && runnerAuthRowStyles.buttonPressed,
|
|
959
|
-
busy && runnerAuthRowStyles.buttonDisabled,
|
|
960
|
-
]}
|
|
961
|
-
accessibilityRole="button"
|
|
962
|
-
accessibilityLabel="Remote sign-in Claude"
|
|
963
|
-
>
|
|
964
|
-
<Text style={runnerAuthRowStyles.buttonLabel}>Remote sign-in</Text>
|
|
965
|
-
<Text style={runnerAuthRowStyles.buttonName}>Claude</Text>
|
|
966
|
-
</Pressable>
|
|
967
|
-
</View>
|
|
968
|
-
|
|
969
1367
|
{progress !== null && (
|
|
970
1368
|
<View style={styles.progressTrack}>
|
|
971
1369
|
<View
|
|
@@ -999,7 +1397,10 @@ export const FeedbackModal: React.FC = () => {
|
|
|
999
1397
|
{runnerAuthModal ? (
|
|
1000
1398
|
<RunnerAuthNativeModal
|
|
1001
1399
|
runner={runnerAuthModal}
|
|
1002
|
-
onClose={() =>
|
|
1400
|
+
onClose={() => {
|
|
1401
|
+
setRunnerAuthModal(null);
|
|
1402
|
+
void loadRunnerStatuses();
|
|
1403
|
+
}}
|
|
1003
1404
|
/>
|
|
1004
1405
|
) : null}
|
|
1005
1406
|
</>
|
|
@@ -1045,6 +1446,15 @@ const ActionRow: React.FC<ActionRowProps> = ({
|
|
|
1045
1446
|
);
|
|
1046
1447
|
|
|
1047
1448
|
const styles = StyleSheet.create({
|
|
1449
|
+
reloadRow: {
|
|
1450
|
+
gap: 4,
|
|
1451
|
+
},
|
|
1452
|
+
reloadHint: {
|
|
1453
|
+
color: '#8b8b93',
|
|
1454
|
+
fontSize: 11,
|
|
1455
|
+
lineHeight: 15,
|
|
1456
|
+
paddingHorizontal: 4,
|
|
1457
|
+
},
|
|
1048
1458
|
vibeInputRow: {
|
|
1049
1459
|
backgroundColor: 'rgba(129,140,248,0.08)',
|
|
1050
1460
|
borderColor: 'rgba(129,140,248,0.4)',
|
|
@@ -1227,6 +1637,106 @@ const styles = StyleSheet.create({
|
|
|
1227
1637
|
marginTop: 4,
|
|
1228
1638
|
lineHeight: 17,
|
|
1229
1639
|
},
|
|
1640
|
+
runnerSection: {
|
|
1641
|
+
marginTop: 2,
|
|
1642
|
+
gap: 10,
|
|
1643
|
+
},
|
|
1644
|
+
runnerSectionHeader: {
|
|
1645
|
+
flexDirection: 'row',
|
|
1646
|
+
alignItems: 'center',
|
|
1647
|
+
gap: 10,
|
|
1648
|
+
},
|
|
1649
|
+
runnerSectionTitle: {
|
|
1650
|
+
color: '#f8fafc',
|
|
1651
|
+
fontSize: 16,
|
|
1652
|
+
fontWeight: '700',
|
|
1653
|
+
},
|
|
1654
|
+
runnerSectionSubtitle: {
|
|
1655
|
+
marginTop: 2,
|
|
1656
|
+
color: '#94a3b8',
|
|
1657
|
+
fontSize: 12,
|
|
1658
|
+
},
|
|
1659
|
+
runnerRefreshBtn: {
|
|
1660
|
+
borderRadius: 10,
|
|
1661
|
+
borderWidth: 1,
|
|
1662
|
+
borderColor: 'rgba(148,163,184,0.22)',
|
|
1663
|
+
backgroundColor: 'rgba(15,23,42,0.65)',
|
|
1664
|
+
paddingHorizontal: 10,
|
|
1665
|
+
paddingVertical: 8,
|
|
1666
|
+
},
|
|
1667
|
+
runnerRefreshBtnText: {
|
|
1668
|
+
color: '#cbd5e1',
|
|
1669
|
+
fontSize: 12,
|
|
1670
|
+
fontWeight: '600',
|
|
1671
|
+
},
|
|
1672
|
+
runnerCard: {
|
|
1673
|
+
borderRadius: 12,
|
|
1674
|
+
borderWidth: 1,
|
|
1675
|
+
borderColor: 'rgba(148,163,184,0.14)',
|
|
1676
|
+
backgroundColor: 'rgba(15,23,42,0.45)',
|
|
1677
|
+
paddingHorizontal: 12,
|
|
1678
|
+
paddingVertical: 11,
|
|
1679
|
+
gap: 6,
|
|
1680
|
+
},
|
|
1681
|
+
runnerCardOk: {
|
|
1682
|
+
borderColor: 'rgba(34,197,94,0.28)',
|
|
1683
|
+
backgroundColor: 'rgba(20,83,45,0.20)',
|
|
1684
|
+
},
|
|
1685
|
+
runnerCardWarning: {
|
|
1686
|
+
borderColor: 'rgba(251,191,36,0.28)',
|
|
1687
|
+
backgroundColor: 'rgba(120,53,15,0.18)',
|
|
1688
|
+
},
|
|
1689
|
+
runnerCardError: {
|
|
1690
|
+
borderColor: 'rgba(248,113,113,0.28)',
|
|
1691
|
+
backgroundColor: 'rgba(127,29,29,0.18)',
|
|
1692
|
+
},
|
|
1693
|
+
runnerCardTop: {
|
|
1694
|
+
flexDirection: 'row',
|
|
1695
|
+
alignItems: 'center',
|
|
1696
|
+
gap: 10,
|
|
1697
|
+
},
|
|
1698
|
+
runnerCardTitle: {
|
|
1699
|
+
color: '#f8fafc',
|
|
1700
|
+
fontSize: 14,
|
|
1701
|
+
fontWeight: '700',
|
|
1702
|
+
},
|
|
1703
|
+
runnerCardStatus: {
|
|
1704
|
+
marginTop: 2,
|
|
1705
|
+
fontSize: 12,
|
|
1706
|
+
color: '#cbd5e1',
|
|
1707
|
+
},
|
|
1708
|
+
runnerCardStatusOk: {
|
|
1709
|
+
color: '#86efac',
|
|
1710
|
+
},
|
|
1711
|
+
runnerCardStatusWarning: {
|
|
1712
|
+
color: '#fcd34d',
|
|
1713
|
+
},
|
|
1714
|
+
runnerCardStatusError: {
|
|
1715
|
+
color: '#fca5a5',
|
|
1716
|
+
},
|
|
1717
|
+
runnerCardDetail: {
|
|
1718
|
+
color: '#94a3b8',
|
|
1719
|
+
fontSize: 11,
|
|
1720
|
+
lineHeight: 16,
|
|
1721
|
+
},
|
|
1722
|
+
runnerActionBtn: {
|
|
1723
|
+
borderRadius: 10,
|
|
1724
|
+
borderWidth: 1,
|
|
1725
|
+
borderColor: 'rgba(129,140,248,0.35)',
|
|
1726
|
+
backgroundColor: 'rgba(67,56,202,0.22)',
|
|
1727
|
+
paddingHorizontal: 12,
|
|
1728
|
+
paddingVertical: 8,
|
|
1729
|
+
},
|
|
1730
|
+
runnerActionBtnText: {
|
|
1731
|
+
color: '#c7d2fe',
|
|
1732
|
+
fontSize: 12,
|
|
1733
|
+
fontWeight: '700',
|
|
1734
|
+
},
|
|
1735
|
+
runnerSectionError: {
|
|
1736
|
+
color: '#fca5a5',
|
|
1737
|
+
fontSize: 12,
|
|
1738
|
+
lineHeight: 18,
|
|
1739
|
+
},
|
|
1230
1740
|
captureChoices: {
|
|
1231
1741
|
gap: 10,
|
|
1232
1742
|
},
|
|
@@ -1604,40 +2114,6 @@ const RunnerAuthNativeModal: React.FC<{
|
|
|
1604
2114
|
);
|
|
1605
2115
|
};
|
|
1606
2116
|
|
|
1607
|
-
const runnerAuthRowStyles = StyleSheet.create({
|
|
1608
|
-
container: {
|
|
1609
|
-
flexDirection: 'row',
|
|
1610
|
-
gap: 8,
|
|
1611
|
-
marginTop: 8,
|
|
1612
|
-
flexWrap: 'wrap',
|
|
1613
|
-
},
|
|
1614
|
-
button: {
|
|
1615
|
-
flexGrow: 1,
|
|
1616
|
-
flexBasis: 0,
|
|
1617
|
-
minWidth: 120,
|
|
1618
|
-
paddingHorizontal: 12,
|
|
1619
|
-
paddingVertical: 10,
|
|
1620
|
-
borderRadius: 10,
|
|
1621
|
-
borderWidth: 1,
|
|
1622
|
-
borderColor: 'rgba(148,163,184,0.22)',
|
|
1623
|
-
backgroundColor: 'rgba(15,23,42,0.6)',
|
|
1624
|
-
},
|
|
1625
|
-
buttonPressed: { opacity: 0.7 },
|
|
1626
|
-
buttonDisabled: { opacity: 0.4 },
|
|
1627
|
-
buttonLabel: {
|
|
1628
|
-
fontSize: 10,
|
|
1629
|
-
color: '#94a3b8',
|
|
1630
|
-
textTransform: 'uppercase',
|
|
1631
|
-
letterSpacing: 0.8,
|
|
1632
|
-
},
|
|
1633
|
-
buttonName: {
|
|
1634
|
-
marginTop: 2,
|
|
1635
|
-
fontSize: 14,
|
|
1636
|
-
fontWeight: '600',
|
|
1637
|
-
color: '#f1f5f9',
|
|
1638
|
-
},
|
|
1639
|
-
});
|
|
1640
|
-
|
|
1641
2117
|
const runnerAuthModalStyles = StyleSheet.create({
|
|
1642
2118
|
overlay: {
|
|
1643
2119
|
flex: 1,
|